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

5.3 KiB

OpenPay for Swift / iOS / macOS

Native Swift bindings to the OpenPay Rust core. Built on 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

# 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:

.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:

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

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

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 Strings. 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.