Skip to content

feat(platform-wallet): surface provider operator/node keys and reveal address private keys#4072

Merged
QuantumExplorer merged 3 commits into
v4.1-devfrom
claude/swift-wallet-key-display-4910c6
Jul 10, 2026
Merged

feat(platform-wallet): surface provider operator/node keys and reveal address private keys#4072
QuantumExplorer merged 3 commits into
v4.1-devfrom
claude/swift-wallet-key-display-4910c6

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Jul 10, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

The Swift wallet never showed the Provider Operator keys account (BLS) or the Provider Platform Node ID keys account (EdDSA), even though key-wallet derives both at wallet creation: AccountCollection::all_accounts() deliberately excludes them (they live in dedicated Option<BLSAccount> / Option<EdDSAAccount> fields), so every persist/display walk skipped them and restored wallets never rebuilt them.

There was also no way to view the private key for a wallet address, or for the individual provider keys.

What was done?

Provider accounts persist / restore / display

  • Provider account xpubs ride a new provider_key_account_registrations vec on PlatformWalletChangeSet (typed ProviderKeyExtendedPubKey enum), snapshotted at register_wallet, bincode-encoded into the FFI account specs, persisted by Swift, and rebuilt watch-only via BLSAccount::new / EdDSAAccount::new on restore.
  • Both accounts render in the account list; their detail screens show the account extended public key plus a per-index Derived Keys card.

Per-index provider keys

  • PlatformWallet::derive_provider_key_at_index + platform_wallet_provider_key_at_index: BLS operator keys derive non-hardened from the account xpub (no seed); Ed25519 platform-node keys are SLIP-10 hardened-only, so all node-key derivations require the seed.
  • Because hardened-only derivation also rules out gap-limit extension, register_wallet pre-derives 20 platform-node keys (pubkey + hash160 node id) while the seed is in hand and persists them (PersistentAccount.derivedPlatformNodeKeys, written once, Swift-owned thereafter). The Node Keys screen lists all 20 with no keychain prompt; pre-existing wallets fall back to a resolver-gated "Load Keys" path.

Private key reveal

  • PlatformWallet::derive_core_address_private_key + platform_wallet_address_private_key: one FFI call that looks the address up across every account pool, derives via the mnemonic-resolver pattern (two-phase locking, resolver never called under the wallet-manager guard), and returns hex + WIF. Surfaced as a confirm-gated "View Private Key" section on the Core address detail view with tap-to-copy.
  • Per-index reveals (hex only — BLS/Ed25519 have no WIF) on both provider key detail screens.
  • All secret buffers are Zeroizing/non_secure_erased on both sides of the FFI; the free functions zeroize the marshalled hex/WIF strings.

How Has This Been Tested?

  • cargo check on platform-wallet + platform-wallet-ffi including --all-targets, --all-features --tests, and the iOS shielded feature graph; cargo fmt; full build_ios.sh --target sim + SwiftExampleApp build (warnings-as-errors) green.
  • Driven end-to-end on the iOS simulator: created fresh testnet wallets, confirmed both provider accounts appear (SwiftData rows tag 10/11 with 89/72-byte encoded xpubs), the 20 pre-generated node keys render with no prompt, and everything survives app relaunch (watch-only restore + re-persist cycle).
  • Cryptographic cross-checks against independent from-scratch implementations: revealed address keys reproduce the exact hex/WIF/address/pubkey via BIP39→BIP32 (two addresses, one after relaunch); all 20 platform node IDs equal hash160(ed25519 pubkey); revealed Ed25519 private keys regenerate their displayed pubkeys (RFC 8032); BLS priv-derived pubkeys are debug-asserted against watch-only ckd_pub derivation in-process.

Breaking Changes

None. AccountSpecFFI gained appended fields (size pin updated 120→136) and PersistentAccount gained a defaulted SwiftData attribute — pre-release, no migration concerns.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added reveal-gated private-key access for tracked core addresses, including hex and network-aware WIF.
    • Added provider key discovery by index for BLS operator and Ed25519 platform-node keys, with optional private-key reveals.
    • Added wallet UI for provider key details, derived keys, copy-to-clipboard actions, and confirmation prompts.
    • Added persistence and watch-only restoration support for provider key accounts and derived platform-node keys.
  • Bug Fixes
    • Improved secure cleanup/zeroization of sensitive key material and ensured safe, repeatable freeing of returned values.
    • Preserved derived platform-node keys during account updates and improved restore handling for provider key accounts.

… address private keys

Provider Operator (BLS) and Provider Platform Node (EdDSA) accounts were
created by key-wallet at wallet creation but excluded from all_accounts(),
so they never persisted, restored, or displayed. Provider account xpubs
now ride a dedicated changeset vec, round-trip through the FFI account
specs, and rebuild watch-only on restore.

Because SLIP-10 Ed25519 is hardened-only (no public derivation, so no
gap-limit extension is ever possible), register_wallet pre-derives 20
platform-node keys (pubkey + hash160 node id) while the seed is available
and persists them; the Node Keys screen lists them with no keychain
prompt. BLS operator keys derive on demand from the account xpub.

Also adds a confirm-gated "View Private Key" reveal (hex + WIF) on the
Core address detail view, and per-index reveals (hex) for both provider
key accounts, all derived in Rust via the mnemonic-resolver pattern with
zeroized secrets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v4.1.0 milestone Jul 10, 2026
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 69650cca-b12d-484c-99a8-b94a5b5fb754

📥 Commits

Reviewing files that changed from the base of the PR and between f109ff9 and f497398.

📒 Files selected for processing (5)
  • packages/rs-platform-wallet-ffi/src/address_private_key.rs
  • packages/rs-platform-wallet-ffi/src/provider_key_at_index.rs
  • packages/rs-platform-wallet-ffi/src/wallet_restore_types.rs
  • packages/rs-platform-wallet/src/wallet/provider_key_at_index.rs
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageRecordDetailViews.swift
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/rs-platform-wallet-ffi/src/provider_key_at_index.rs
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageRecordDetailViews.swift
  • packages/rs-platform-wallet-ffi/src/address_private_key.rs
  • packages/rs-platform-wallet-ffi/src/wallet_restore_types.rs

📝 Walkthrough

Walkthrough

Adds core-address and provider-key derivation across Rust, FFI, Swift APIs, persistence, and example-app views. Provider account registrations now persist extended public keys and pre-derived platform-node keys, while key reveal flows expose hex, WIF, node IDs, and indexed provider keys.

Changes

Wallet key discovery and persistence

Layer / File(s) Summary
Wallet derivation and registration contracts
packages/rs-platform-wallet/src/changeset/..., packages/rs-platform-wallet/src/wallet/...
Adds core-address and indexed provider-key derivation, provider registration types, changeset storage, and public re-exports.
Provider registration persistence
packages/rs-platform-wallet/src/manager/..., packages/rs-platform-wallet-ffi/src/persistence.rs, packages/rs-platform-wallet-ffi/src/wallet_restore_types.rs, packages/swift-sdk/Sources/SwiftDashSDK/Persistence/...
Persists provider account material and derived platform-node keys, restores provider accounts, and extends FFI layout and callback payloads.
Rust FFI key exports
packages/rs-platform-wallet-ffi/src/address_private_key.rs, packages/rs-platform-wallet-ffi/src/provider_key_at_index.rs, packages/rs-platform-wallet-ffi/src/lib.rs
Exposes resolver-aware C-ABI functions for core-address and provider-key derivation with allocation cleanup and null-safe outputs.
Swift wallet key APIs
packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift
Maps FFI results into Swift key models and manages resolver lifetime and returned allocations.
Key reveal and display UI
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift, packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageRecordDetailViews.swift
Adds provider-key displays, confirmation-gated private-key reveals, and copyable core/provider key values.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AccountDetailView
  participant ManagedPlatformWallet
  participant RustFFI
  participant MnemonicResolver
  participant PlatformWallet
  AccountDetailView->>ManagedPlatformWallet: request indexed provider key
  ManagedPlatformWallet->>RustFFI: call provider key FFI
  RustFFI->>MnemonicResolver: resolve mnemonic when required
  RustFFI->>PlatformWallet: derive key at index
  PlatformWallet-->>RustFFI: public key, node ID, optional private key
  RustFFI-->>ManagedPlatformWallet: C-string outputs
  ManagedPlatformWallet-->>AccountDetailView: Swift provider key
Loading

Possibly related issues

Suggested reviewers: shumkov, lklimek, llbartekll, ZocoLini, thepastaclaw

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed It clearly summarizes the main additions: provider operator/node key exposure and core address private-key reveal.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/swift-wallet-key-display-4910c6

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.

@thepastaclaw

thepastaclaw commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

✅ Review complete (commit f497398)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageRecordDetailViews.swift`:
- Around line 1687-1704: Update reveal() to launch the private-key lookup in a
Task, mirroring AccountDetailView.revealPrivateKey(index:), so
coreAddressPrivateKey(address:) runs off the main thread. Set isRevealing and
clear revealError before starting the task, then update privateKey or
revealError from the task and reset isRevealing after completion, ensuring
SwiftUI can render the progress state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a4514d01-67fa-4175-a537-b6baab656fa1

📥 Commits

Reviewing files that changed from the base of the PR and between b42d213 and f109ff9.

📒 Files selected for processing (19)
  • packages/rs-platform-wallet-ffi/src/address_private_key.rs
  • packages/rs-platform-wallet-ffi/src/lib.rs
  • packages/rs-platform-wallet-ffi/src/persistence.rs
  • packages/rs-platform-wallet-ffi/src/provider_key_at_index.rs
  • packages/rs-platform-wallet-ffi/src/wallet_registration_persistence.rs
  • packages/rs-platform-wallet-ffi/src/wallet_restore_types.rs
  • packages/rs-platform-wallet/src/changeset/changeset.rs
  • packages/rs-platform-wallet/src/changeset/mod.rs
  • packages/rs-platform-wallet/src/lib.rs
  • packages/rs-platform-wallet/src/manager/wallet_lifecycle.rs
  • packages/rs-platform-wallet/src/wallet/apply.rs
  • packages/rs-platform-wallet/src/wallet/core_address_key.rs
  • packages/rs-platform-wallet/src/wallet/mod.rs
  • packages/rs-platform-wallet/src/wallet/provider_key_at_index.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentAccount.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageRecordDetailViews.swift

… reveal from main thread

The ProviderPlatformNodeKeyFFI struct was inserted between AccountSpecFFI's
doc comment and the struct it documents, merging the two doc blocks and
tripping clippy::doc_lazy_continuation (-D warnings on CI). Moved the new
struct above the doc comment so both attach to their own items.

Also mirrors AccountDetailView's Task-offloaded reveal in the address
detail view so the synchronous resolver FFI doesn't block the main thread
and the "Revealing…" progress state can actually render (CodeRabbit).

Co-Authored-By: Claude Fable 5 <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.

Code Review

The latest push (f109ff9..7bb69d1) re-anchors the AccountSpecFFI doc comment to satisfy clippy and moves the core-address reveal off the main thread via a Swift Task. It introduces no new defect or ABI/layout change.

Prior finding reconciliation

  • prior-1 — STILL VALID: CString marshalling can still leave an un-zeroized transient hex/WIF heap buffer.
  • prior-2 — STILL VALID: the new cross-curve derivation entry points still have no Rust unit tests.
  • prior-3 — STILL VALID: revealed key/WIF values still go to the general pasteboard without expiration; the anchor moved to lines 1717-1723.

Carried-forward prior findings

All three prior findings are carried forward below.

New findings in the latest delta

None.

Source: reviewers opus (general, security-auditor, rust-quality, ffi-engineer); gpt-5.6-sol (same four lanes, failed: revoked refresh token); verifier opus.

🟡 2 suggestion(s) | 💬 1 nitpick(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-platform-wallet-ffi/src/address_private_key.rs`:
- [SUGGESTION] packages/rs-platform-wallet-ffi/src/address_private_key.rs:170-184: Secret hex/WIF can leave an un-zeroized plaintext copy in freed heap during CString marshalling
  Provenance: carried forward from prior review of SHA f109ff9f (prior-1). Revalidated at current head 7bb69d1e — the latest delta touched only wallet_restore_types.rs and StorageRecordDetailViews.swift, so lines 170-184 here are byte-identical and the finding STILL applies.

  The comment on lines 170-173 asserts the hex + WIF byte buffers 'move directly into the returned CStrings (no residual plaintext copy left on the heap).' That is not guaranteed. `hex::encode(&private_key[..])` (line 177) builds a `String` sized for exactly 2*32 = 64 chars (len == cap), and `CoreAddressPrivateKey.wif` (line 178) is a freshly-formatted `String` with no spare capacity. `CString::new` takes the buffer via `Into<Vec<u8>>` and must append a NUL terminator — with zero spare capacity this reallocates and frees the original buffer containing the plaintext private-key hex / WIF WITHOUT zeroizing it. `platform_wallet_address_private_key_free` (lines 199-214) later scrubs only the final CString buffer, not the transient pre-realloc allocation. The result: a private secp256k1 scalar (and its WIF, which fully encodes the same key) can linger in reclaimed heap after the call — exactly the leak this PR's zeroization design set out to prevent, and the one asymmetry in an otherwise disciplined secret-handling story. The same pattern applies to `private_key_hex` in provider_key_at_index.rs.

  Fix by encoding into a buffer pre-sized to `len + 1` (so no realloc occurs) or into a `Zeroizing<String>`/`Zeroizing<Vec<u8>>` scrubbed on drop, and making `CoreAddressPrivateKey.wif` a zeroizing buffer, rather than relying on the move being copy-free.

In `packages/rs-platform-wallet/src/wallet/provider_key_at_index.rs`:
- [SUGGESTION] packages/rs-platform-wallet/src/wallet/provider_key_at_index.rs:89-158: New cryptographic key-derivation functions ship with no Rust unit tests
  Provenance: carried forward from prior review of SHA f109ff9f (prior-2). Revalidated at current head 7bb69d1e — grep for `#[cfg(test)]` / `#[test]` / `mod tests` in both provider_key_at_index.rs and core_address_key.rs returns zero matches, and the latest delta added no tests, so the gap STILL exists.

  The three derivation entry points introduced by this PR — `derive_platform_node_public_keys`, `PlatformWallet::derive_provider_key_at_index`, and `PlatformWallet::derive_core_address_private_key` (core_address_key.rs) — contain non-trivial, exact-value cross-curve logic (BLS non-hardened `ckd_pub` vs Ed25519 SLIP-10 hardened-only, `node_id = hash160(ed25519 pubkey)`, WIF network byte) whose consistency invariants are enforced only by `debug_assert_eq!` (compiled out of release builds). The only new Rust tests added are wallet-id scoping / duplicate-register tests in wallet_lifecycle.rs, and the PR checklist leaves 'added or updated relevant tests' unchecked — validation was manual on the simulator. A regression in an upstream key-wallet derivation crate or a path/index off-by-one would silently produce wrong key material with no failing CI test to catch it; for provider-operator / platform-node key material this means an operator could be handed a key that does not match its on-chain registration.

  A small deterministic test using the canonical all-`abandon` mnemonic already used in this crate — asserting (a) `derive_platform_node_public_keys` matches `derive_provider_key_at_index(PlatformNode, i, ..)` per index, (b) `node_id == hash160(public_key)`, and (c) a known BIP32/BIP39 vector for `derive_core_address_private_key`'s hex/WIF — would lock these invariants down cheaply.

Comment thread packages/rs-platform-wallet-ffi/src/address_private_key.rs Outdated
Comment thread packages/rs-platform-wallet/src/wallet/provider_key_at_index.rs
…ation tests, expire pasteboard

CString::new over the zero-spare-capacity hex/WIF buffers reallocates to
append the NUL and frees the plaintext original un-zeroized, stranding a
private scalar (and its WIF) in reclaimed heap. Added secret_string_into_raw
— copies into a NUL-sized buffer so CString::new can't realloc, wipes the
Zeroizing source — and routed both the address-key and provider-key private
hex through it (thepastaclaw).

Added golden-vector unit tests for derive_platform_node_public_keys over the
canonical abandon mnemonic: node_id == hash160(pubkey) at every index,
per-index distinctness, cross-network divergence, and pinned index-0 bytes so
an upstream SLIP-10/DIP-9 derivation regression fails CI.

Set a 60s pasteboard expiration on the revealed key/WIF copy so the secret
doesn't linger on the system-wide clipboard.

Related: dashpay/rust-dashcore#858 (BLS/Ed25519 xpriv types not zeroized on drop).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@QuantumExplorer QuantumExplorer merged commit c6b073f into v4.1-dev Jul 10, 2026
18 checks passed
@QuantumExplorer QuantumExplorer deleted the claude/swift-wallet-key-display-4910c6 branch July 10, 2026 10:06

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

Code Review

The latest remediation delta 7bb69d1e..f497398b fixes the secret CString marshalling issue and adds sound platform-node derivation vectors. It introduces no new defect, but two prior findings remain partially unresolved.

Prior findings reconciliation

  • prior-1 — FIXED: secret hex/WIF/provider-key marshalling now avoids an un-zeroized transient heap allocation.
  • prior-2 — STILL VALID (partially fixed): the new tests cover only derive_platform_node_public_keys; the production BLS operator and core-address/WIF derivation paths remain untested.
  • prior-3 — STILL VALID (partially fixed): core-address clipboard copies now expire, but the provider private-key copy path added by this PR still uses the general pasteboard without expiration.

Carried-forward prior findings

One suggestion and one nitpick are carried forward below.

New findings in the latest delta

None.

Source: claude-general: opus completed; codex-general: gpt-5.6-sol failed; claude-security-auditor: opus completed; codex-security-auditor: gpt-5.6-sol failed; claude-rust-quality: opus completed; codex-rust-quality: gpt-5.6-sol failed; claude-ffi-engineer: opus completed; codex-ffi-engineer: gpt-5.6-sol failed; verifier: opus completed; focused prior-2 verifier: opus completed.

🟡 1 suggestion(s) | 💬 1 nitpick(s)

Verified carried-forward findings

suggestion: BLS operator and core-address (WIF) key-derivation paths still have no Rust unit tests

packages/rs-platform-wallet/src/wallet/provider_key_at_index.rs:227-456

Partial fix of prior-2. The three tests added at f497398 cover only the standalone free function derive_platform_node_public_keys (public-key half). Two of the three entry points the original finding named remain untested: (1) PlatformWallet::derive_provider_key_at_index (227-456) — its ProviderKeyKind::Operator branch with ExtendedBLSPrivKey::new_master, bls_public_key.derive_pub, and the release-compiled-out debug_assert_eq! BLS cross-check, plus the include_private reveal and resolved_master watch-only paths for both curves; and (2) PlatformWallet::derive_core_address_private_key in core_address_key.rs:73 (WIF/network-byte logic), whose file still has no test module. The free function duplicates the Ed25519 derivation rather than delegating to derive_provider_key_at_index, so the new tests do not exercise the method production actually calls or cross-check that the two agree per index. A BLS-operator and core-address/WIF golden-vector test, plus an equality check between the free function and derive_provider_key_at_index(PlatformNode, ...), would close the residual gap.

nitpick: Revealed provider private-key hex copied to the system-wide pasteboard without expiration (residual of prior-3)

packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift:884-890

Carried forward from prior-3. This delta fixed the primary anchor in StorageRecordDetailViews.swift (now uses UIPasteboard.general.setItems(_:options:) with a 60s .expirationDate), but the sibling copyDerived here was not updated. Verified the full path: the revealed private-key row is rendered at AccountDetailView.swift:729-734 from revealedPrivateKeys[index] (populated by the includePrivate: true reveal at 863-874, i.e. BLS operator / Ed25519 platform-node secret material), and derivedValueRow wires .onTapGesture { copyDerived(value, copyKey:) } at line 790. So tapping the revealed key runs copyDerived, which does UIPasteboard.general.string = value with no expiration. This is the system-wide pasteboard: other installed apps and clipboard managers can read it, and Universal Clipboard syncs it across the user's devices, giving an unbounded exposure window. AccountDetailView.swift is a file changed by this PR and this is exactly the reveal path the PR added, so the fix should mirror the one already applied to the core-address copy. Impact is bounded (developer example app), matching the original nitpick severity. (SearchWalletsForIdentitiesView.swift:276 has the same pattern but is not a file changed by this PR, so it is out of scope.)

    private func copyDerived(_ value: String, copyKey: String) {
        // Raw private-key hex to the system-wide pasteboard: other apps and
        // clipboard managers can read it and Universal Clipboard syncs it
        // across devices. Set a short expiry so the secret doesn't linger.
        // Fine for this demo app; a production wallet should avoid clipboard
        // export of secrets (or gate it far more tightly).
        UIPasteboard.general.setItems(
            [["public.utf8-plain-text": value]],
            options: [.expirationDate: Date().addingTimeInterval(60)]
        )
        derivedCopiedKey = copyKey
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
            if derivedCopiedKey == copyKey { derivedCopiedKey = nil }
        }
    }

Comment on lines +884 to +890
private func copyDerived(_ value: String, copyKey: String) {
UIPasteboard.general.string = value
derivedCopiedKey = copyKey
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
if derivedCopiedKey == copyKey { derivedCopiedKey = nil }
}
}

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: Revealed provider private-key hex copied to the system-wide pasteboard without expiration (residual of prior-3)

Carried forward from prior-3. This delta fixed the primary anchor in StorageRecordDetailViews.swift (now uses UIPasteboard.general.setItems(_:options:) with a 60s .expirationDate), but the sibling copyDerived here was not updated. Verified the full path: the revealed private-key row is rendered at AccountDetailView.swift:729-734 from revealedPrivateKeys[index] (populated by the includePrivate: true reveal at 863-874, i.e. BLS operator / Ed25519 platform-node secret material), and derivedValueRow wires .onTapGesture { copyDerived(value, copyKey:) } at line 790. So tapping the revealed key runs copyDerived, which does UIPasteboard.general.string = value with no expiration. This is the system-wide pasteboard: other installed apps and clipboard managers can read it, and Universal Clipboard syncs it across the user's devices, giving an unbounded exposure window. AccountDetailView.swift is a file changed by this PR and this is exactly the reveal path the PR added, so the fix should mirror the one already applied to the core-address copy. Impact is bounded (developer example app), matching the original nitpick severity. (SearchWalletsForIdentitiesView.swift:276 has the same pattern but is not a file changed by this PR, so it is out of scope.)

Suggested change
private func copyDerived(_ value: String, copyKey: String) {
UIPasteboard.general.string = value
derivedCopiedKey = copyKey
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
if derivedCopiedKey == copyKey { derivedCopiedKey = nil }
}
}
private func copyDerived(_ value: String, copyKey: String) {
// Raw private-key hex to the system-wide pasteboard: other apps and
// clipboard managers can read it and Universal Clipboard syncs it
// across devices. Set a short expiry so the secret doesn't linger.
// Fine for this demo app; a production wallet should avoid clipboard
// export of secrets (or gate it far more tightly).
UIPasteboard.general.setItems(
[["public.utf8-plain-text": value]],
options: [.expirationDate: Date().addingTimeInterval(60)]
)
derivedCopiedKey = copyKey
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
if derivedCopiedKey == copyKey { derivedCopiedKey = nil }
}
}

source: ['claude']

shumkov added a commit that referenced this pull request Jul 10, 2026
Resolve the base advance (11 commits, incl. #4072 provider operator/node
keys + reveal address private keys) that left the PR CONFLICTING on
GitHub. The one real conflict was in changeset/mod.rs, where both branches
extended the `pub use changeset::{…}` re-export block; kept both sides —
our InvitationChangeSet/InvitationEntry/InvitationStatus and the base's
ProviderKeyAccountEntry/ProviderKeyExtendedPubKey/ProviderPlatformNodePubKey.

Verified post-merge: cargo fmt --check --all clean; platform-wallet and
platform-wallet-ffi compile; the invitation funding-index regression tests
pass (4/4).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LecRMfPQNRrH2pYubp7wtn
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.

2 participants