Skip to content

feat(swift-sdk): add Swift wrappers for the encrypted-txMetadata FFI exports - #4194

Open
bfoss765 wants to merge 2 commits into
dashpay:v4.2-devfrom
bfoss765:followup/v4.1/swift-txmetadata-wrappers
Open

feat(swift-sdk): add Swift wrappers for the encrypted-txMetadata FFI exports#4194
bfoss765 wants to merge 2 commits into
dashpay:v4.2-devfrom
bfoss765:followup/v4.1/swift-txmetadata-wrappers

Conversation

@bfoss765

@bfoss765 bfoss765 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Swift wrappers for the encrypted-txMetadata FFI surface.

Rebased onto v4.2-dev — no longer stacked on #4186/#4195

What changed vs. the pre-#4277 version of this PR:

What this adds

#4277 shipped the Rust core, the C ABI, the JNI bridge and the Kotlin host — but zero Swift. This is the iOS half, so both hosts sit on the same surface.

  • ManagedPlatformWallet.createEncryptedDocument(...)platform_wallet_create_encrypted_document_with_signer_auto_index
  • ManagedPlatformWallet.fetchEncryptedDocuments(...)platform_wallet_fetch_encrypted_documents

Both follow the existing wrapper patterns exactly: nested withExtendedLifetime pinning for signer/resolver across the full FFI call, byte buffers via withUnsafeBufferPointer, payload borrowed in place with no added Swift-side copy (the Rust-owned Zeroizing buffer does the scrubbing; callers should scrub their own), and the returned JSON C-string freed exactly once via defer registered after the result check.

createEncryptedDocument takes no encryptionKeyIndex parameter — it binds the auto-index export, matching Kotlin's createEncryptedDocument, whose encryptionKeyIndex defaults to null. The explicit-index export stays in the C ABI for migration/compat tests and is deliberately not surfaced in Swift.

Argument order, types, and the pre-dispatch version ∈ {0, 1} guard match the Kotlin DocumentTransactions counterparts; Kotlin's runtime requires are enforced structurally by stronger Swift types (Identifier, UInt32, UInt64).

EncryptedDocumentVersionValidationTests mirrors the Kotlin DocumentTransactionsVersionValidationTest (2/3/127/255 rejected before any FFI dispatch); the negative-version case is non-compilable in Swift by construction. The happy path is network-gated, noted in-test.

Doc updates forced by #4277's index redesign

The wrapper's doc comments were rewritten to describe the contract that actually shipped. #4195 proposed allocating encryptionKeyIndex as 1 + <count of the identity's txMetadata documents on Platform> behind a per-wallet allocator mutex. #4277 chose a different mechanism: Rust draws a non-zero 31-bit BIP-32 child index from the OS CSPRNG (generate_encryption_key_index), with no Platform count, no allocator state, and no extra network round trip. The rationale — now reflected in these comments — is that encryptionKeyIndex is a per-document derivation input stored on the document, not a protocol sequence number: readers derive each document's key from that document's own {keyIndex, encryptionKeyIndex}, every document carries a fresh IV, and the txMetadata schema has no unique index on the field, so a repeated index is non-lossy.

Stale dashpay/platform#4195 / "allocates from authoritative Platform state" references were replaced with #4277 / CSPRNG wording. No call-site changes were needed: the auto-index export's C signature at v4.2-dev head is byte-for-byte what this wrapper already bound.

Error codes: none added. The wrappers surface existing PlatformWalletError cases only, so the error-code registry (#4261) is untouched.

Verification

  • cbindgen on platform-wallet-ffi at this base emits both symbols at exactly the bound signatures — ..._auto_index takes no encryption_key_index, and ..._fetch_encrypted_documents is unchanged. Argument-by-argument parity re-audited against the freshly generated header.
  • swiftc -parse -swift-version 6 clean over both touched files and over the whole SwiftDashSDK source set.
  • A full swift build / swift test is not reproducible from a source checkout: Package.swift depends on the DashSDKFFI.xcframework binary target, which is gitignored and must be produced by build_ios.sh; that build additionally needs the dash-network and key-wallet-ffi cbindgen headers, whose crates live outside this repo. The pre-feat(platform-wallet): add encrypted txMetadata document support #4277 revision of this PR was verified with swift build --build-tests + swift test --filter EncryptedDocumentVersionValidationTests against a locally rebuilt framework slice, and the Swift call sites are unchanged since — only comments moved.
  • The commit contains only the two Swift files: no generated headers, no binaries.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@bfoss765, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 38 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a097240b-37d9-4f62-8cad-1f71f7190950

📥 Commits

Reviewing files that changed from the base of the PR and between 97904ed and 5bf1065.

📒 Files selected for processing (2)
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift
  • packages/swift-sdk/SwiftTests/SwiftDashSDKTests/EncryptedDocumentVersionValidationTests.swift
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added this to the v4.1.0 milestone Jul 21, 2026
@thepastaclaw

thepastaclaw commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

✅ Review complete (commit 5bf1065)
Last checked: 2026-08-04 23:00 UTC

@shumkov

shumkov commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Reviewed (two independent passes). As code, the wrappers are solid: faithful Kotlin parity (matching semantics and identical version-guard error text), verified 12/7-arg FFI signatures including the null-payload-at-len-0 contract, correct pin/free memory discipline, typed error mapping, no crypto in Swift, mirrored tests. One blocker to resolve before merge:

Sequencing conflict with #4195. This PR bridges only the explicit-index export with a required encryptionKeyIndex: UInt32 — the exact host-owned allocation policy #4195 retires in favor of the new ..._auto_index export (no *_auto_index symbol exists on this branch). Either #4195 lands first and this reworks to the auto-index wrapper (dropping the parameter), or this lands first and #4195 carries the Swift migration — please pick an order and note it in both PRs. Until the auto-index wrapper exists in Swift, iOS re-diverges from Android on index allocation, which defeats the purpose of #4195.

Also: CI has never compiled these wrappers (the Swift job dies at the runner keychain step, and the Kotlin failure is a locked emulator — both infra) — needs a green run after the rebase onto merged #4186.

@bfoss765

Copy link
Copy Markdown
Contributor Author

Reworked per the sequencing decision: re-stacked onto #4195 and changed createEncryptedDocument to call the new auto-index export, dropping the host encryptionKeyIndex param so iOS matches Android's Rust-side allocation. Memory discipline unchanged (pinning, no extra plaintext copy, single free after result.check()).

Verified: the cbindgen header carries the auto-index symbol at the expected signature and swift build type-checks the reworked call site; swift test compiles and fails only at link against the checked-in prebuilt archive (which predates #4195) — a framework rebuild against #4195 resolves the symbol.

@shumkov

shumkov commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Round-3 verification (two independent passes, reconciled): the rework is verified in full — the branch is exactly re-stacked on #4195's head, the Swift wrapper binds the auto_index export with the 11-argument signature, pointer lifetimes, and single-free discipline all checked, the stale "supply your own index" doc is gone, and Swift CI (warnings-as-errors) is green at this head. Ready, pending #4195 landing first. Two cosmetic notes:

  • The null-JSON failure message at ManagedPlatformWallet.swift:3560 still names the old export (create_encrypted_document_with_signer instead of ..._auto_index).
  • Kotlin retains an explicit-index escape hatch (encryptionKeyIndex: Int? = null, explicit values documented as migration/tests-only) that Swift now drops entirely. Defensible since iOS never shipped explicit-index writes — but it's a deliberate Kotlin/Swift surface asymmetry, so please state it in the body (and the body still says "stacked on feat(kotlin-sdk): wire-compatible encrypted txMetadata document create + decrypt-on-fetch #4186" — needs the re-stack update).

@shumkov

shumkov commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Covered by the same-repo stack dispatch run (29994291221) — full Rust workspace suite green at the stack head, and this PR's own CI already has Swift and Kotlin green. Nothing further from our side beyond the earlier cosmetic notes; lands after #4195.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final validation — Codex + Sonnet

The exact head resolves the prior sequencing concern: Swift calls the Rust auto-index export, and the reviewed ABI arguments, signer/resolver lifetime pinning, version validation, and output ownership match the underlying FFI contracts. No functional or blocking issue remains; the only in-scope defect is a stale diagnostic string that names the explicit-index export instead of the function actually called.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — security-auditor (completed), gpt-5.6-sol — rust-quality (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: gpt-5.6-sol — final-verifier (fallback)
  • Sonnet reviewers: claude-sonnet-5 — security-auditor (completed), claude-sonnet-5 — ffi-engineer (completed), claude-sonnet-5 — general (completed), claude-sonnet-5 — rust-quality (completed)

💬 1 nitpick(s)

// violation. Fail loudly rather than persist an empty body.
guard let jsonPtr = documentJsonPtr else {
throw PlatformWalletError.walletOperation(
"create_encrypted_document_with_signer returned no canonical document JSON"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 Nitpick: Null-JSON diagnostic names the wrong FFI export

This wrapper calls platform_wallet_create_encrypted_document_with_signer_auto_index at line 3531, but its null-output fallback names create_encrypted_document_with_signer, the explicit-index sibling. The mismatch does not affect control flow or memory safety, but it would direct maintainers to the wrong C symbol when diagnosing an FFI contract violation.

Suggested change
"create_encrypted_document_with_signer returned no canonical document JSON"
"create_encrypted_document_with_signer_auto_index returned no canonical document JSON"

source: ['claude', 'codex']

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5bf106573d. The null-JSON diagnostic now names the actual export it calls, create_encrypted_document_with_signer_auto_index, instead of the non-auto-index sibling. (The doc reference at line ~3451 to create_encrypted_document_with_signer is correct — that export still exists separately.)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved in this update — Null-JSON diagnostic names the wrong FFI export no longer present.

Auto-resolved by the review system based on the latest commit diff. If you believe this was closed in error, reopen the thread.

@QuantumExplorer
QuantumExplorer changed the base branch from v4.1-dev to v4.2-dev July 24, 2026 20:11
@github-actions github-actions Bot modified the milestones: v4.1.0, v4.2.0 Jul 24, 2026
@shumkov

shumkov commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

Hi @bfoss765 — heads up before anything changes on my side, and thanks for this work.

I'm going to land this feature as four same-repository branches under dashpay/platform rather than merging these three PRs directly. This is about where the branches live, not about the quality of the code.

Two reasons:

  1. Writable branches. There is a set of fixes that need to go into these layers — payload-size ordering, FFI panic containment, out-pointer contracts, allocator scoping and concurrency, log redaction, and test coverage. They belong in the layer that introduced the code, and I need branches I can commit to in order to put them there.
  2. Isolated, complete per-layer CI. The Rust workspace and wallet jobs were skipped on all three of these PRs, so no layer here has ever had an isolated Rust run. (Swift varied: it was skipped on feat(kotlin-sdk): wire-compatible encrypted txMetadata document create + decrypt-on-fetch #4186, failed on [SUPERSEDED by #4277] feat(platform-wallet): allocate txMetadata encryptionKeyIndex in Rust, not the host #4195, and passed on feat(swift-sdk): add Swift wrappers for the encrypted-txMetadata FFI exports #4194.) The only passing Rust workspace evidence anywhere in this chain is the cumulative run on fix(sdk)!: complete encrypted txMetadata parity #4243, which tells us the union of all four layers builds but nothing about any layer on its own. Same-repository branches get each layer its own complete run before it merges.

What that means concretely:

  • Your commits are replayed with git cherry-pick -x, so you remain the author of every one of them and each carries a link back to its original SHA.
  • Each successor PR body will credit and link this PR, and name the archived source head.
  • Your branch is not modified in any way — I'm not pushing to it or rewriting it.
  • I'll start building the replacements now so the work isn't blocked, but I will not close this PR as superseded until the replacement is verified and you've acknowledged here.

If you'd rather take a different route, say so and I'll stop and we'll work it out first.

@bfoss765
bfoss765 force-pushed the followup/v4.1/swift-txmetadata-wrappers branch 2 times, most recently from 1d812c7 to 560f66a Compare August 4, 2026 05:35
Swift wrappers for the two encrypted-document C-ABI exports that landed
with the encrypted txMetadata feature (dashpay#4277):

  - platform_wallet_create_encrypted_document_with_signer_auto_index
  - platform_wallet_fetch_encrypted_documents

dashpay#4277 shipped the Rust core, the C ABI, the JNI bridge and the Kotlin
host, but ZERO Swift. This adds the iOS half so both hosts sit on the
same surface.

Adds ManagedPlatformWallet.createEncryptedDocument(...) and
.fetchEncryptedDocuments(...) to Sources/SwiftDashSDK/PlatformWallet,
mirroring the existing createDocument (signer + byte-buffer marshalling,
withExtendedLifetime pinning, result-code .check(), string_free) and
previewIdentityRegistrationKeys (internal MnemonicResolver construction
+ pinning) patterns. The plaintext payload is borrowed in place and
handed straight to Rust's Zeroizing buffer with no extra Swift-side
copy, as the neighboring seed path does.

createEncryptedDocument binds the AUTO-INDEX export and takes NO
encryptionKeyIndex parameter: Rust generates the per-document index
(dashpay#4277 draws a non-zero 31-bit BIP-32 child index from the OS CSPRNG),
so hosts never assign it. This matches the Android auto-index path,
where Kotlin's createEncryptedDocument defaults encryptionKeyIndex to
null. The explicit-index export remains in the C ABI for
migration/compat tests and is deliberately not surfaced in Swift.

The version-byte {0,1} guard and argument-order/nullability parity with
the Kotlin counterpart are preserved; Kotlin's runtime `require`s are
enforced structurally by stronger Swift types (Identifier, UInt32,
UInt64).

EncryptedDocumentVersionValidationTests mirrors the Kotlin
DocumentTransactionsVersionValidationTest: the wire-meaningless version
bytes (2/3/127/255) are rejected before any FFI dispatch. The
negative-version case is non-compilable in Swift by construction, and
the happy path is network-gated.

No new error codes: the wrappers surface the existing
PlatformWalletError cases only, so the ERROR_CODE_REGISTRY is untouched.

Verified: cbindgen on platform-wallet-ffi at this base emits both
symbols at exactly the bound signatures (auto_index takes no
encryption_key_index); `swiftc -parse -swift-version 6` is clean over
both touched files and over the whole SwiftDashSDK source set. A full
`swift build`/`swift test` needs the DashSDKFFI.xcframework binary
target, which is gitignored and not present in a source checkout — it
requires a full iOS framework build (build_ios.sh) plus the
dash-network / key-wallet-ffi headers that live outside this repo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bfoss765
bfoss765 force-pushed the followup/v4.1/swift-txmetadata-wrappers branch from 560f66a to d2cc4ac Compare August 4, 2026 12:17
@bfoss765 bfoss765 changed the title feat(swift-sdk): wrappers for the txMetadata encrypted-document FFI exports (stacked on #4186) feat(swift-sdk): Swift wrappers for the encrypted-txMetadata FFI exports Aug 4, 2026
@bfoss765 bfoss765 changed the title feat(swift-sdk): Swift wrappers for the encrypted-txMetadata FFI exports feat(swift-sdk): add Swift wrappers for the encrypted-txMetadata FFI exports Aug 4, 2026
The auto-index wrapper calls
platform_wallet_create_encrypted_document_with_signer_auto_index but its
null-output error message named create_encrypted_document_with_signer (the
non-auto-index sibling), misdirecting anyone diagnosing an ABI contract
violation (dashpay#4194).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final validation — Codex + Sonnet

The one-line delta correctly updates the null-JSON diagnostic to identify the auto-index FFI export actually invoked, resolving the carried-forward finding without changing ABI behavior, ownership, or execution. The affected Swift source and existing test parse successfully under Swift 6, and the combined Codex and Sonnet reviewer evidence identifies no remaining in-scope defect.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — security-auditor (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: gpt-5.6-sol — final-verifier (fallback)
  • Sonnet reviewers: claude-sonnet-5 — general (completed), claude-sonnet-5 — security-auditor (completed), claude-sonnet-5 — ffi-engineer (completed)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants