open-standards/openpay/crates/op-ffi-swift/swift/README.md
Transaction Science 70ecd6caa0 Initial import — three open standards under one shelf
Transaction Science open standards monorepo. Each subdirectory is a
self-contained reference protocol with its own README and licence.

- openpay/    Apache-2.0  payment-acceptance stack (Rust, 26 crates)
- smart-byte/ CC-BY-4.0   content-addressed value carrier (spec)
- eoc/        CC-BY-4.0   energy-optimised compute (spec)

The wire format and the right to fork are public. The optional hosted
services are the offer; the protocols are not.
2026-05-23 07:58:09 -04:00

148 lines
5.3 KiB
Markdown

# OpenPay for Swift / iOS / macOS
Native Swift bindings to the OpenPay Rust core. Built on
[`swift-bridge`](https://github.com/chinedufn/swift-bridge) with a
parallel plain C ABI surface for consumers that don't want the
bridge-build dependency.
## What you get
- **`OpenPay.Vault`** — tokenize / detokenize card data via a Rust
vault. Default ships an in-memory reference implementation backed
by AES-256-GCM-SIV (RFC 8452, misuse-resistant). Production: bring
your own backed by iOS Keychain or your KMS.
- **`OpenPay.CardData`** — validated card representation. PAN never
becomes a Swift `String`; it lives inside the Rust core, zeroized
on drop.
- **`OpenPay.HeuristicScorer`** — fraud scorer. Heuristic rules out
of the box; pluggable for ONNX or Burn-based models.
- **`OpenPayError`** — typed Swift errors mirroring the FFI enum,
with oracle-discipline collapsing of token lookup failures.
## Integration
### Option 1: Prebuilt XCFramework (recommended)
```bash
# One-time: build the artifacts.
bash crates/op-ffi-swift/scripts/build-xcframework.sh --release
```
This produces:
- `target/OpenPay.xcframework/` — prebuilt static libs for iOS device,
iOS simulator (Apple Silicon + Intel), and macOS (arm64 + Intel).
- `target/swift/OpenPay.swift` + `openpay-swift-bridge.h` +
`module.modulemap` — the swift-bridge generated glue.
Then in your app's `Package.swift`:
```swift
.package(path: "../openpay/crates/op-ffi-swift/swift")
```
### Option 2: C ABI only
If you don't want the swift-bridge build dependency, build with the
`c-only` feature:
```bash
cargo build -p op-ffi-swift --features c-only --target aarch64-apple-ios --release
```
Then hand-roll a Swift wrapper using `@_silgen_name` against the
`op_*` functions documented in `src/c_api.rs`.
## Required Rust toolchain
```bash
rustup target add aarch64-apple-ios # iOS device
rustup target add aarch64-apple-ios-sim # iOS simulator (Apple Silicon)
rustup target add x86_64-apple-ios # iOS simulator (Intel)
rustup target add aarch64-apple-darwin # macOS arm64
rustup target add x86_64-apple-darwin # macOS Intel
```
These are the standard tier-2 targets per the rustc platform support
table for `*-apple-ios` and `*-apple-darwin`.
## Usage
```swift
import OpenPay
// Construct an ephemeral vault (replace with KeychainVault in prod).
let vault = OpenPay.Vault.ephemeral(name: "checkout")
// Validate a card.
do {
let card = try OpenPay.CardData(
pan: "4242424242424242",
expMonth: 12,
expYear: 2030
)
// Tokenize for card-on-file.
let token = try vault.tokenize(card: card, policy: .cardOnFile())
print("Saved token: \(token.asString)")
// Save token.asString to your durable store (Core Data, etc.).
} catch let e as OpenPayError {
print("Validation or tokenization failed: \(e.localizedDescription)")
}
// Later, at submit time:
let token = OpenPay.VaultRef(wrapping: /* recovered from storage */)
do {
let card = try vault.detokenize(token: token)
// Hand `card` to your PSP SDK (Stripe, Adyen, …). The PAN is
// never visible to Swift code; the PSP SDK calls the Rust core
// again via the same FFI to read it.
} catch OpenPayError.tokenExpired {
// ask the user to re-enter
} catch OpenPayError.vaultLookupFailed {
// token is gone — re-enroll
} catch {
// other error
}
```
## What's NOT in this crate
- **KeychainVault.** Writing `SecItemAdd` correctly is deployment-
specific (which accessibility class, biometric gating, background
access). Implementing the `Vault` Rust trait from Swift is supported
by `swift-bridge` via `extern "Swift"` blocks; the example
implementation belongs in your app code. We can ship a reference
implementation in a future phase if there's demand.
- **Apple Pay PKPayment unwrapping.** Apple Pay payment tokens are
network tokens, not PANs. They go straight to a PSP that resolves
the cryptogram; OpenPay's vault doesn't see them. Bridge in your
PSP's PKPayment handler directly.
- **Card and A2A rails.** These are large transport-heavy Rust crates
not appropriate for every iOS bundle. If your app submits payments
directly (rare for mobile), link them at the workspace level and
expose via a custom bridge.
## PCI scope on iOS
The Rust core ensures PAN never appears in Swift `String`s. Combined
with using the iOS Keychain as the `Vault` backend, an iOS app
integrating OpenPay can plausibly stay at SAQ-A scope provided no
other code path touches PAN. The PCI DSS Tokenization Guidelines § 3.3
distinguishability rule is satisfied because OpenPay tokens carry the
`tok_v7_` prefix and are not numeric-only.
## Verified ground truth
- `swift-bridge` 0.1.59 (chinedufn/swift-bridge) is the latest stable
as of May 2026. Inspired by `cxx-rs`; emits idiomatic Swift
directly.
- Target triples verified against the rustc platform support pages
for `*-apple-ios` (`aarch64-apple-ios`, `aarch64-apple-ios-sim`,
`x86_64-apple-ios`) — all tier-2.
- XCFramework workflow verified against Apple Developer Forums
threads (lipo simulator arches → xcodebuild -create-xcframework with
one library per platform variant).
- iOS Keychain accessibility constants verified against Apple's
developer documentation: `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`
is the appropriate class for tokens needing background access that
shouldn't migrate via iCloud backup.