Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
280 commits
Select commit Hold shift + click to select a range
03ba3ee
security(platform-wallet): zeroize + pass-by-ref the seed at the crea…
shumkov Jun 15, 2026
0d897b3
Merge branch 'v3.1-dev' into feat/dashpay-m1-sync-correctness
shumkov Jun 15, 2026
75d12b2
fix(platform-wallet): guard DashPay sync cancel-token cleanup against…
shumkov Jun 15, 2026
09536ff
fix(platform-wallet): don't permanently break a payment channel on a …
shumkov Jun 15, 2026
87a8ddb
fix(platform-wallet): don't let a purpose mismatch mask a hard valida…
shumkov Jun 15, 2026
1ee01d4
fix(platform-wallet): never select a disabled (revoked) key for DashP…
shumkov Jun 15, 2026
bcfd114
fix(platform-wallet-storage): move DashPay schema additions to an app…
shumkov Jun 15, 2026
a5da3f1
fix(platform-wallet-ffi): drop Clone/Copy from DashpayPaymentFFI (own…
shumkov Jun 15, 2026
83521e6
fix(dashpay): restore rejected-request tombstones at load so rejected…
shumkov Jun 15, 2026
bd59bcf
fix(swift-example): address review nits — empty-state, HTTPS avatar, …
shumkov Jun 15, 2026
1734fd9
docs(dashpay): fix markdown-lint nits in research notes
shumkov Jun 15, 2026
a7930b5
refactor(platform-wallet): pass ContactInfoPrivateData to set_contact…
shumkov Jun 15, 2026
4ec751a
fix(platform-wallet): pass seed by reference in shielded_sync_paloma …
shumkov Jun 15, 2026
666bc0a
fix(swift-example): add PersistentDashpayRejectedRequest to Storage E…
shumkov Jun 15, 2026
454ae52
fix(platform-wallet): finish seed-by-ref migration in spv_sync test +…
QuantumExplorer Jun 16, 2026
a3ca03c
Merge branch 'v3.1-dev' into feat/dashpay-m1-sync-correctness
QuantumExplorer Jun 16, 2026
0d8f69f
docs(dashpay): make comments timeless — drop dev-time TDD/milestone n…
QuantumExplorer Jun 16, 2026
bba0a39
fix(platform-wallet): finish seed-by-ref migration in basic_usage + s…
shumkov Jun 16, 2026
18a3696
refactor(platform-wallet-storage): squash V002 into the V001 initial …
shumkov Jun 16, 2026
7b94eab
fix(sdk): correct stale sdk_builder_default_seeds_atomic_to_floor exp…
shumkov Jun 16, 2026
631f6fc
fix(platform-wallet): reject must emit removed_incoming so SQLite del…
shumkov Jun 17, 2026
aec04f2
fix(swift-sdk): pre-delete DashPay payments + rejection tombstones in…
shumkov Jun 17, 2026
ae13d13
security(platform-wallet): zeroize the seed copy in create_wallet_fro…
shumkov Jun 17, 2026
0ad99d0
fix(platform-wallet): update_profile preserves sibling fields (read-m…
shumkov Jun 17, 2026
245d9da
fix(platform-wallet): confirm sent DashPay payments (Pending -> Confi…
shumkov Jun 17, 2026
d7bffcf
docs(dashpay): backlog + sync/contactInfo/ignore specs + kotlin-platf…
shumkov Jun 17, 2026
e8ddb78
docs(dashpay): fold 5-lens review into SYNC_CORRECTNESS_SPEC + merge …
shumkov Jun 17, 2026
3f2051e
fix(dashpay): paginate contact-request fetch + incremental high-water…
shumkov Jun 17, 2026
1f53897
feat(dashpay): contact-profile sync — id-keyed cache for contacts + s…
shumkov Jun 17, 2026
a06fdd0
feat(dashpay): tighten sync cadence 60s -> 15s to match Android ticker
shumkov Jun 17, 2026
ef35ca5
fix(dashpay): address sync-correctness review findings (stages 1-2)
shumkov Jun 17, 2026
755b485
docs(dashpay): record sync stages 1-2 Rust core done; FFI/UI surface …
shumkov Jun 17, 2026
b1936a7
feat(dashpay): surface contact profiles in the UI (stage 2 read path)
shumkov Jun 17, 2026
84769f3
docs(dashpay): mark stage-2 reader/UI done; durable persistence remains
shumkov Jun 17, 2026
06053bf
feat(dashpay): persist contact_profiles through the changeset (Rust l…
shumkov Jun 18, 2026
c071782
docs(dashpay): mark persistence Rust layer done; host-FFI layer remai…
shumkov Jun 18, 2026
87d6cc7
feat(dashpay): durable contact-profile persistence — host-FFI + Swift…
shumkov Jun 18, 2026
c1f3aa5
docs(dashpay): mark durable contact-profile persistence done
shumkov Jun 18, 2026
202b987
docs(dashpay): resolve contactInfo format — keep CBOR, scrap the vari…
shumkov Jun 18, 2026
f585c22
docs(dashpay): use DIP-15 varint for contactInfo.privateData (not CBOR)
shumkov Jun 18, 2026
808b347
feat(dashpay): migrate contactInfo.privateData CBOR -> DIP-15 varint …
shumkov Jun 18, 2026
f15183b
docs(dashpay): resolve R1 — ignore is local-only (no contactInfo leak)
shumkov Jun 18, 2026
4169bcf
feat(dashpay): local-only Ignore — per-sender, reversible (Spec 2)
shumkov Jun 18, 2026
7386c2b
docs(dashpay): mark Spec 2 (local-only ignore) + refactor + Ignored U…
shumkov Jun 18, 2026
e0c8236
fix(dashpay): pad encryptedAccountLabel to >=16 chars (kotlin parity)
shumkov Jun 18, 2026
91e4d6e
test(dashpay): ECDH known-answer test pins the SHA256((y&1|2)||x) con…
shumkov Jun 18, 2026
2a14073
fix(dashpay): paginate the contactInfo fetch (retrieve-all, no trunca…
shumkov Jun 18, 2026
6739ca1
feat(rs-sdk-ffi): expose entropy on DashSDKContactRequestResult
shumkov Jun 18, 2026
091d706
docs(dashpay): resolve accountReference byte-order — keep ours (match…
shumkov Jun 18, 2026
d5c127e
feat(dashpay): per-contact payment-history accessor + TODO sweep
shumkov Jun 18, 2026
7b877ff
docs(dashpay): strip spec-gate/milestone refs from code comments
shumkov Jun 18, 2026
764f64f
docs(dashpay): mark TODO backlog status — implementable items complete
shumkov Jun 18, 2026
822b1ee
docs(dashpay): link upstream rust-dashcore#813 for the account-index …
shumkov Jun 18, 2026
30d5112
docs(dashpay): sync specs to the implemented state of this PR
shumkov Jun 18, 2026
a7b05f7
chore(deps): bump rust-dashcore to ceee4a9b40 + migrate Address::netw…
shumkov Jun 19, 2026
4fe081c
test(platform-wallet-storage): add IdentityEntry contact_profiles/ign…
shumkov Jun 19, 2026
ee8ed5d
style: apply rustfmt to platform-wallet dashpay modules
shumkov Jun 19, 2026
928c2c5
style(platform-wallet): resolve pre-existing clippy warnings
shumkov Jun 19, 2026
adb7b32
docs(dashpay): record UAT signing bug + on-device UAT state
shumkov Jun 19, 2026
acd59be
docs(dashpay): record on-device UAT results (2026-06-20)
shumkov Jun 19, 2026
85e2b37
fix(platform-wallet): confirm sent DashPay payment on block confirmation
shumkov Jun 20, 2026
d8c0d06
fix(swift-sdk): surface DashPay payment confirmation without manual r…
shumkov Jun 20, 2026
1a17326
docs(dashpay): record bug-2 sent-payment fix + on-device verification
shumkov Jun 20, 2026
d949b57
refactor(platform-wallet): harden BlockProcessed payment routing per …
shumkov Jun 20, 2026
d6bf195
fix(swift-sdk): guard refreshPayments against overlapping refreshes
shumkov Jun 20, 2026
40169ae
docs(dashpay): record multi-agent review outcome + bug-2 follow-ups
shumkov Jun 20, 2026
0229162
feat(platform-wallet): InstantSend finality + sent-payment reconcile …
shumkov Jun 20, 2026
b462c73
perf(swift-sdk): skip no-op DashPay payment row rewrites
shumkov Jun 20, 2026
e57587f
docs(dashpay): mark bug-2 review follow-ups (IS finality, reconcile, …
shumkov Jun 20, 2026
ac83a15
fix(platform-wallet): materialize all signable identity keys on import
shumkov Jun 20, 2026
09b415b
docs(dashpay): spec for imported-identity-signing fix (reviewed)
shumkov Jun 20, 2026
c567981
fix(platform-wallet): materialize imported-identity keys by carrying …
shumkov Jun 21, 2026
c6dd28b
fix(swift-sdk): delete contactProfiles during wallet wipe
shumkov Jun 21, 2026
1b4037a
docs(dashpay): make code comments timeless
shumkov Jun 21, 2026
1c060cf
Merge branch 'v3.1-dev' into feat/dashpay-m1-sync-correctness
shumkov Jun 21, 2026
5236d1d
feat(swift-example-app): Storage Explorer coverage for PersistentDash…
shumkov Jun 21, 2026
5d26736
fix(platform-wallet-storage): keep secrets-scan green after carry-scalar
shumkov Jun 21, 2026
a9d65df
refactor(platform-wallet): extract DashPay payment hooks into an even…
shumkov Jun 21, 2026
d309a4b
feat(platform-wallet): route DashPay send_payment + xpub through the …
shumkov Jun 22, 2026
c79f95e
docs(dashpay): mark imported-identity signing bug resolved; drop it a…
shumkov Jun 22, 2026
301fc43
fix(platform-wallet): defer DashPay account build when key material i…
shumkov Jun 22, 2026
2e7eae1
feat(platform-wallet): deferred contact-crypto queue types + changese…
shumkov Jun 22, 2026
508b3ed
feat(platform-wallet): enqueue deferred contact-crypto ops on seedles…
shumkov Jun 22, 2026
d944245
feat(rs-sdk-ffi): ECDH host primitive on MnemonicResolverCoreSigner (…
shumkov Jun 22, 2026
93fe4ea
feat(platform-wallet): register_external_contact_account accepts a pr…
shumkov Jun 22, 2026
9b34217
docs(dashpay): seed-elimination implementation handoff / task tracker
shumkov Jun 22, 2026
6832a52
feat(platform-wallet): register_contact_account accepts a precomputed…
shumkov Jun 22, 2026
6b76d02
docs(dashpay): handoff tracker — both drain register-cores landed, dr…
shumkov Jun 22, 2026
4b6a6f7
feat(platform-wallet): deferred-crypto drain framework + RegisterRece…
shumkov Jun 22, 2026
ecd288c
feat(platform-wallet): drain RegisterExternal op via ECDH provider (s…
shumkov Jun 22, 2026
d1a5d17
docs(dashpay): handoff tracker — drain method done (both register ops)
shumkov Jun 22, 2026
45f903d
feat(rs-sdk-ffi): contactInfo seal/open host primitive (seed-elim sec…
shumkov Jun 22, 2026
0d58114
docs(dashpay): handoff tracker — contactInfo primitive done
shumkov Jun 22, 2026
65f0c5c
feat(rs-sdk-ffi): wrong-seed self-check verify_binds_to_xpub (seed-el…
shumkov Jun 22, 2026
ea6a1ea
feat(platform-wallet): persist deferred-crypto enqueue add-delta (see…
shumkov Jun 22, 2026
7f1a7f7
docs(dashpay): handoff tracker — section 4.8 + enqueue-persist done; …
shumkov Jun 22, 2026
97a9a99
feat(platform-wallet-ffi): drain-pending-contact-crypto FFI + DrainCr…
shumkov Jun 22, 2026
79ca6a1
feat(platform-wallet-storage): persist the deferred-crypto queue in S…
shumkov Jun 22, 2026
3397e4d
docs(dashpay): handoff tracker — drain FFI + SQLite storage done
shumkov Jun 22, 2026
3bce459
refactor(platform-encryption): single-source DIP-15 accountReference
shumkov Jun 22, 2026
4d721d7
docs(dashpay): record accountReference move commit hash
shumkov Jun 22, 2026
787170a
docs(platform-wallet): correct ContactInfoDecrypt drain deferral comment
shumkov Jun 22, 2026
7a23fa1
feat(sdk-ffi): signer accountReference methods for seedless send
shumkov Jun 22, 2026
9d532c2
refactor(platform-wallet): send contact request via ClientSide ECDH
shumkov Jun 22, 2026
88b7fb7
refactor(platform-wallet): generalize DrainCryptoProvider to ContactC…
shumkov Jun 22, 2026
f9b10d8
docs(dashpay): record signer+ClientSide+provider progress; concrete C…
shumkov Jun 22, 2026
07e1821
feat(platform-wallet): seedless send + accept contact request
shumkov Jun 22, 2026
910565e
docs(dashpay): mark seedless send/accept (C2) done; C3 blocked on swe…
shumkov Jun 22, 2026
77344f8
docs(dashpay): ground the C3/section-4.9 ordering gate in the sweep code
shumkov Jun 22, 2026
5f1f75a
test(platform-wallet): seedless ContactCryptoProvider test harness
shumkov Jun 22, 2026
9082d35
fix(platform-wallet): validate key indices in the drain's RegisterExt…
shumkov Jun 22, 2026
1b88d5a
feat(platform-wallet): sweep always-enqueues (signerless, no resident…
shumkov Jun 22, 2026
14566d9
refactor(platform-wallet): delete the resident ECDH path (C3)
shumkov Jun 22, 2026
433e309
docs(dashpay): record sweep/C3 done; §4.9 blocked on 3 raw-secret paths
shumkov Jun 22, 2026
fe8a57d
docs(dashpay): ground each remaining raw-secret path's §4.9 blocker
shumkov Jun 22, 2026
9c97186
docs(dashpay): root-cause the discovery deep blocker (verified_scalar…
shumkov Jun 22, 2026
82fae98
docs(dashpay): verified_scalar is a Rust->FFI->Swift hand-off (decisi…
shumkov Jun 22, 2026
fc5061b
docs(dashpay): record user decision to HOLD §4.9/discovery/Swift for …
shumkov Jun 23, 2026
3b2b418
docs(dashpay): scope Q2 (remove attach_wallet_seed) in spec + TODO; c…
shumkov Jun 23, 2026
8ed2605
docs(dashpay): fold the 4-lens Q2 plan review into the spec + TODO
shumkov Jun 23, 2026
0da8ca5
feat(platform-wallet): contactInfo seal/open on ContactCryptoProvider…
shumkov Jun 23, 2026
413229f
feat(platform-wallet): seedless contactInfo publish via the provider …
shumkov Jun 23, 2026
15ca790
feat(platform-wallet): seedless contactInfo sweep defer + drain op (Q…
shumkov Jun 23, 2026
db18688
refactor(sdk-ffi): delete the dead dash_sdk_dashpay_* contact-request…
shumkov Jun 23, 2026
a97d740
docs(dashpay): record #7 contactInfo done + dead-surface de-dup; §4.9…
shumkov Jun 23, 2026
a427048
refactor(platform-wallet): register_contact_account non-Option (drop …
shumkov Jun 23, 2026
c42d2e4
test(platform-wallet): make test wallets seedless (drop attach_wallet…
shumkov Jun 23, 2026
74f1549
fix(platform-wallet): match RawContactInfoDoc visibility to its pub(s…
shumkov Jun 23, 2026
feb266f
fix(platform-wallet-ffi): scrub derived scalars on the resolver-sign …
shumkov Jun 23, 2026
fe3ab74
refactor(platform-wallet): replace attach_wallet_seed with a seedless…
shumkov Jun 23, 2026
677efa7
docs(dashpay): record §4.2 + attach_wallet_seed deletion / verify_see…
shumkov Jun 23, 2026
70aaf32
feat(swift-sdk): seedless Keychain unlock — verify-binds + drain, no …
shumkov Jun 23, 2026
98b525f
docs(dashpay): Q2 Swift unlock rework landed; only testnet acceptance…
shumkov Jun 23, 2026
0901b28
docs(dashpay): record on-device smoke test; Swift seedless unlock sup…
shumkov Jun 23, 2026
e7ace2c
docs(dashpay): record on-device on-chain validation of seedless DashPay
shumkov Jun 23, 2026
8fd5678
docs(dashpay): full cross-device on-chain acceptance of seedless DashPay
shumkov Jun 23, 2026
82c8522
docs(dashpay): file the contact-profile chunk-fetch DAPI bug (missing…
shumkov Jun 23, 2026
ad64059
refactor(dashpay): address Q2 multi-reviewer findings (dead code, tes…
shumkov Jun 23, 2026
c778393
docs(dashpay): correct verify_binds_to_xpub claim; record Q2 review f…
shumkov Jun 23, 2026
4bdf56b
docs(dashpay): triage Q2 review follow-ups per decision
shumkov Jun 23, 2026
e74e4e2
test(platform-wallet-ffi): pin account_xpub survives the persist→rest…
shumkov Jun 23, 2026
6d90d73
refactor(platform-encryption): split the flat lib.rs into per-concern…
shumkov Jun 23, 2026
df5f03e
refactor(platform-encryption): drop the dashcore dependency (direct s…
shumkov Jun 23, 2026
a425f70
refactor(platform-wallet): move DashPay sync orchestration into the s…
shumkov Jun 23, 2026
c583c78
fix(platform-wallet): order contact-profile chunk query by $ownerId
shumkov Jun 23, 2026
ef3f600
docs(dashpay): check off the contact-profile orderBy P0 (c583c78c81)
shumkov Jun 23, 2026
9963923
feat(platform-wallet): expose pending account-build crypto count (nee…
shumkov Jun 23, 2026
841802c
feat(swift-sdk): surface DashPay needs-unlock / verify-failed banner
shumkov Jun 23, 2026
8b4f110
docs(dashpay): check off needs-unlock/verify-failed signal (on-device…
shumkov Jun 23, 2026
bc83234
docs(dashpay): keep the Signer protocol (decided, do not re-raise)
shumkov Jun 23, 2026
c8e4065
docs(dashpay): research QR auto-accept — spec-only, no interop, depri…
shumkov Jun 23, 2026
8cfb19b
docs(dashpay): QR auto-accept (DIP-15) implementation spec, review-ha…
shumkov Jun 23, 2026
b8ff05c
feat(platform-wallet): DIP-15 auto-accept crypto primitives (QR auto-…
shumkov Jun 23, 2026
a714b4e
feat(platform-wallet): wire DIP-15 auto-accept receive flow (enqueue …
shumkov Jun 23, 2026
b39e0cf
feat(platform-wallet): owner QR-create for DIP-15 auto-accept (scoped…
shumkov Jun 23, 2026
ff2403d
feat(platform-wallet): scanner send-from-QR + thread identity signer …
shumkov Jun 23, 2026
e6b7468
feat(swift-sdk): DIP-15 QR auto-accept UI (My-QR + add-via-QR) + drai…
shumkov Jun 23, 2026
b513b1c
docs(dashpay): mark QR auto-accept implemented; on-device status + P3…
shumkov Jun 23, 2026
6c7bd46
docs(dashpay): code-verified DIP-15 + DIP-16 conformance audit + resc…
shumkov Jun 24, 2026
0f7e598
fix(dashpay): normalize encryptedAccountLabel length in the crypto pr…
shumkov Jun 24, 2026
18483e4
feat(dashpay): backfill rescan for incoming payments missed before a …
shumkov Jun 24, 2026
cba515a
feat(swift-sdk): scan from genesis when importing/restoring an existi…
shumkov Jun 24, 2026
3b84e9e
feat(dashpay): resolve the auto-accept QR's DPNS name on-chain when n…
shumkov Jun 24, 2026
c6a9c4a
feat(platform-wallet-ffi): make the identity signer optional in the d…
shumkov Jun 24, 2026
9035cee
docs(dashpay): record the drain-signer-optional follow-up under QR au…
shumkov Jun 24, 2026
45ca837
docs(dashpay): multi-account spec + 4-lens review → KEEP DEFERRED
shumkov Jun 24, 2026
2803e72
feat(dashpay): surface the contact's DIP-15 encryptedAccountLabel on …
shumkov Jun 24, 2026
2e0bcb9
fix(dashpay): carry encryptedAccountLabel through the sweep ingest + …
shumkov Jun 24, 2026
2d9bbca
fix(dashpay): address final PR review — silent-failure, fail-loud, ro…
shumkov Jun 24, 2026
8798bf4
fix(dashpay): address PR review suggestions — FFI bounds, out-param i…
shumkov Jun 24, 2026
19b767f
fix(dashpay): tighten auto-accept enqueue bound + zero-init QR out-param
shumkov Jun 25, 2026
5606f2b
docs(dashpay): track the two deferred bot-review follow-ups
shumkov Jun 25, 2026
24484f3
fix(dashpay): persist contact-profile refresh timestamps, not just co…
shumkov Jun 25, 2026
ef08463
Merge remote-tracking branch 'origin/v3.1-dev' into feat/dashpay-m1-s…
shumkov Jun 25, 2026
03f1fdb
fix(dashpay): delete stale contact-profile rows when a contact remove…
shumkov Jun 25, 2026
9ce5723
fix(dashpay): bound the contact-request sweep with a per-sweep page b…
shumkov Jun 25, 2026
b36d33b
docs(dashpay): mark the three bot-review follow-ups DONE
shumkov Jun 25, 2026
cf5fb6d
fix(dashpay): surface key-persist failures instead of swallowing them…
shumkov Jun 25, 2026
b240b5e
fix(dashpay): surface contactInfo-metadata persist failures (alias/no…
shumkov Jun 25, 2026
58ac609
fix(dashpay): don't advance the sync cursor past an unpersisted conta…
shumkov Jun 25, 2026
5b7e248
test(dashpay): pin the export_auto_accept_private_key path scope-gate…
shumkov Jun 25, 2026
88ff20a
style(dashpay): cargo fmt --all + fix clippy -D warnings across the P…
shumkov Jun 25, 2026
47d9044
test(dashpay): verify contact-profile tombstone deletes the SwiftData…
shumkov Jun 25, 2026
997191f
docs(dashpay): add DIP-15/16 QA test cases to the iOS test plan
shumkov Jun 25, 2026
a682686
docs(dashpay): record full live e2e pass (10/10) for the QA test cases
shumkov Jun 25, 2026
d227b30
feat(dashpay): mark DashPay payments in the wallet transaction list
shumkov Jun 25, 2026
5a2f21b
feat(dashpay): surface live DashPay sync state in the WalletMemory ex…
shumkov Jun 25, 2026
55f5940
fix(workspace): restore --all-features build after key-wallet rc.2 + …
shumkov Jun 25, 2026
b0ab557
fix(platform-wallet-storage): reword schema doc comments to pass the …
shumkov Jun 25, 2026
c1bdac2
docs(dashpay): close the last QA asterisks — airtight 10/10 live pass
shumkov Jun 25, 2026
2afe923
fix(platform-wallet-storage): allow-list the pending_contact_crypto l…
shumkov Jun 25, 2026
1b247fe
fix(swift-sdk): resolve Swift 6 data-race error in the contact-crypto…
shumkov Jun 25, 2026
ce1604d
fix(swift-sdk): drop misleading DashPay profile-setup subtitle
shumkov Jun 25, 2026
0ff4cd7
fix(swift-sdk): dedupe DPNS contact-search rows by unique row id
QuantumExplorer Jun 25, 2026
71e7e03
docs(dashpay): add identity-key scalar elimination spec (derive-sign-…
shumkov Jun 25, 2026
af9031e
docs(dashpay): DP-03 must verify payments in BOTH directions
shumkov Jun 26, 2026
15d6737
fix(platform-wallet): drain deferred contact-crypto on the send path …
shumkov Jun 26, 2026
bd2b4e9
docs(simulator-control): add testnet-faucet auto-funding workflow (K)
shumkov Jun 26, 2026
e69515f
fix(swift-sdk): gate the DashPay 'set up profile' prompt on the persi…
shumkov Jun 26, 2026
56fb63e
fix(platform-wallet): persist contact metadata before committing to m…
shumkov Jun 26, 2026
90d96c1
fix(swift-sdk): pin the KeychainSigner with withExtendedLifetime in s…
shumkov Jun 26, 2026
391abd4
fix(platform-wallet): roll back record_dashpay_payment on persist fai…
shumkov Jun 26, 2026
9526fac
style(platform-wallet): rustfmt the new contact-metadata + payment re…
shumkov Jun 26, 2026
61d3d61
docs(simulator-control): document build_ios.sh --target sim as the ca…
shumkov Jun 26, 2026
f33ee5a
docs(dashpay): spec for identity-key scalar elimination (derive-sign-…
shumkov Jun 26, 2026
5d99616
test(dashpay): pin pubkey-verify == scalar-verify + seedless DIP-9 si…
shumkov Jun 26, 2026
56ff8ae
feat(dashpay): poll fast in foreground + kick a sync after each mutation
shumkov Jun 26, 2026
fd45d03
fix(dashpay): kick a sync on tab entry + relax cadence on app background
shumkov Jun 26, 2026
7b9022b
feat(dashpay): prompt to register a username when an identity has none
shumkov Jun 26, 2026
5ba83b3
Merge branch 'v3.1-dev' into feat/dashpay-m1-sync-correctness
shumkov Jun 26, 2026
bc98677
feat(dashpay): generalize resolver-sign FFI for identity keys (HASH16…
shumkov Jun 27, 2026
ecbbd0a
refactor(swift-example-app): move Contracts from a root tab into Sett…
shumkov Jun 29, 2026
8d5da85
feat(dashpay): derive-sign-destroy identity signing path (behind lega…
shumkov Jun 29, 2026
40830a7
feat(dashpay): Keychain-driven self-verifying backfill for identity-k…
shumkov Jun 29, 2026
700e53b
docs(dashpay): record as-built deviations + sim testnet faucet
shumkov Jun 29, 2026
7fa00d8
Merge branch 'feat/dashpay-m1-sync-correctness' into feat/dashpay-ide…
shumkov Jun 29, 2026
9c05f41
fix(dashpay): address code-review findings on the derive-sign-destroy…
shumkov Jun 29, 2026
50ee76c
fix(dashpay): hide the register-username prompt after inline registra…
shumkov Jun 30, 2026
a2ee420
fix(platform-wallet): cap dapk length before base58-decoding the cont…
shumkov Jun 30, 2026
7063310
fix(dashpay): preserve dash_sdk::Error taxonomy from send_contact_req…
shumkov Jun 30, 2026
d317636
fix(dashpay-ffi): initialize dashpay-sync scalar out-params before ea…
shumkov Jun 30, 2026
fecde59
docs(dashpay): note the 10-minute-overlap livelock window in the swee…
shumkov Jun 30, 2026
60e91c3
docs(dashpay): correct store_contact_account_label persistence claim
shumkov Jun 30, 2026
fa1098c
test(dashpay): headless resolver-sign integration test (derive-sign-d…
shumkov Jun 30, 2026
930c100
refactor(dashpay): remove the carried identity-key scalar (derive-sig…
shumkov Jun 30, 2026
fb11783
Merge branch 'feat/dashpay-identity-key-scalar-elimination' into feat…
shumkov Jun 30, 2026
e20d730
docs(dashpay): record scalar deletion as shipped in spec as-built notes
shumkov Jun 30, 2026
95d6714
refactor(platform-wallet): stop swallowing Results in the contact FFI…
shumkov Jul 4, 2026
738f1ee
Merge branch 'v4.1-dev' into feat/dashpay-m1-sync-correctness
shumkov Jul 4, 2026
78fa916
fix(platform-wallet-storage): drop stale IdentityKeyEntry.private_key…
shumkov Jul 4, 2026
bb02c41
fix(platform-wallet-ffi): fail closed on inconsistent expected_key_da…
shumkov Jul 4, 2026
6b51892
fix(dpp,platform-wallet): move test-signer extended_public_key to Ext…
shumkov Jul 4, 2026
8dd08d1
fix(platform-wallet-ffi): bound expected_key_data read to the accepte…
shumkov Jul 4, 2026
f06578c
refactor(platform-wallet): extract shared LoopCancelGuard for sync-lo…
QuantumExplorer Jul 4, 2026
f5593bf
fix: address verified findings from the dashpay m1 deep review (#3997)
QuantumExplorer Jul 4, 2026
be1d008
docs: prune, fold + trim the DashPay spec set (26 → 15) (#4012)
shumkov Jul 5, 2026
793fab6
Merge remote-tracking branch 'origin/v4.1-dev' into HEAD
shumkov Jul 5, 2026
d679389
fix(platform-wallet-storage): set pending_contact_crypto on identity …
shumkov Jul 5, 2026
6fb42e2
style(platform-wallet): fix lib.rs module order after the v4.1-dev merge
shumkov Jul 5, 2026
7559f67
docs(dashpay): add DashPayState encapsulation spec (reviewed, rev 2)
shumkov Jul 5, 2026
44cd67b
refactor(platform-wallet): group ManagedIdentity's DashPay fields int…
shumkov Jul 5, 2026
d7c0400
refactor(platform-wallet): seal DashPayState behind invariant-holding…
shumkov Jul 5, 2026
8c155cc
refactor(platform-wallet): namespace DashPay ops behind IdentityWalle…
shumkov Jul 5, 2026
018def2
docs(platform-wallet): fix comments stale after the DashPayState extr…
shumkov Jul 5, 2026
0f22c34
fix(platform-wallet): alias EcdhProvider's unused SdkSide fn type (cl…
shumkov Jul 5, 2026
303c17a
fix(platform-wallet): publish FFI-safe out-param sentinels before fal…
QuantumExplorer Jul 6, 2026
4bc7c41
fix(platform-wallet): extend out-param sentinel hardening to all rema…
QuantumExplorer Jul 6, 2026
0ee06e1
fix(platform-wallet): dashpay M1 review findings — rotation, ignore d…
QuantumExplorer Jul 6, 2026
b2c9d41
docs(dashpay): trim transient working artifacts from the shipped docs
QuantumExplorer Jul 6, 2026
5b7c317
test(platform-wallet-storage): allow-list the ignored_senders load re…
QuantumExplorer Jul 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1,124 changes: 1,124 additions & 0 deletions docs/dashpay/SPEC.md

Large diffs are not rendered by default.

499 changes: 499 additions & 0 deletions docs/dashpay/research/01-dip-spec.md

Large diffs are not rendered by default.

573 changes: 573 additions & 0 deletions docs/dashpay/research/02-rust-dashcore-keywallet.md

Large diffs are not rendered by default.

424 changes: 424 additions & 0 deletions docs/dashpay/research/03-rs-platform-wallet.md

Large diffs are not rendered by default.

376 changes: 376 additions & 0 deletions docs/dashpay/research/04-sdk-and-contract.md

Large diffs are not rendered by default.

365 changes: 365 additions & 0 deletions docs/dashpay/research/05-swift-app.md

Large diffs are not rendered by default.

458 changes: 458 additions & 0 deletions docs/dashpay/research/06-interop-desk-check.md

Large diffs are not rendered by default.

88 changes: 88 additions & 0 deletions docs/dashpay/research/07-contactinfo-conventions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# contactInfo wire conventions (research, 2026-06-12)

Source-verified findings for implementing the DashPay `contactInfo`
document (M3 task 13). Full citations at the bottom.

## Decisive finding: no reference client implements contactInfo

DashSync-iOS (`DSBlockchainIdentity.m` + Identity models), dashj /
android-dpp / kotlin-platform, and dash-shared-core contain **zero**
contactInfo creation or encryption code. DIP-15 + the deployed
dashpay-contract schema are the only authoritative sources, and **this
repo's implementation sets the de-facto wire convention.** There is no
cross-client byte-compatibility constraint — only self-consistency and
schema validity.

## Conventions adopted (CONFIRMED unless marked INFERRED)

### Key derivation (DIP-15)

The "root encryption key" is the identity's **registered ENCRYPTION
key** (DIP-11 purpose 1); `rootEncryptionKeyIndex` is that key's id on
the identity. Two child keys are derived from its extended form in the
owner's HD tree (hardened CKDpriv):

```
encToUserId key: rootEncryptionKey / 65536' / derivationEncryptionKeyIndex' (2^16)
privateData key: rootEncryptionKey / 65537' / derivationEncryptionKeyIndex' (2^16 + 1)
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

The 2^16 offset is DIP-15's explicit "discount other potential
derivations" choice. The AES-256 key is the raw 32-byte child private
key scalar (INFERRED — no hash step is specified anywhere; matches how
contactRequest ECDH consumes key material).

`derivationEncryptionKeyIndex` is sequential per `$ownerId` starting at
0 (one per contactInfo document; the unique index is
`($ownerId, rootEncryptionKeyIndex, derivationEncryptionKeyIndex)`).

### encToUserId (DIP-15, verbatim justification in the DIP)

`AES-256-ECB(toUserId)` — exactly 32 bytes = two blocks, **no IV, no
padding**. ECB is sound here because the plaintext is itself a SHA-256
output and the key is never reused for other purposes.

### privateData

`IV(16) ‖ AES-256-CBC(plaintext)` — IV prepended (INFERRED from the
`encryptedPublicKey` convention; DIP-15 doesn't state placement for
this field).

Plaintext: **CBOR array `[aliasName, note, displayHidden]`** per the
deployed schema's field description — positional, with CBOR `null`
for absent strings (INFERRED). NOTE: DIP-15 prose instead describes
Bitcoin-varint "Dash message data" with extra `version` +
`acceptedAccounts` fields; the deployed schema description wins (it is
what any schema-reading client will expect). `version` /
`acceptedAccounts` are NOT included — re-introducing them later means
a versioned-CBOR convention change.

### Privacy rule (DIP-15, spec-only — no client enforces it today)

> "A client should not transmit a contact info document for a user to
> the network until that user has at least two established contacts."

Enforced at the publish gate: with <2 established contacts the local
state still updates; the document write is deferred until the rule is
satisfied.

## Discrepancy table (DIP-15 prose vs deployed schema)

| Question | DIP-15 prose | Deployed schema |
|---|---|---|
| Plaintext format | Bitcoin varint stream | CBOR array |
| Fields | version, aliasName, note, displayHidden, acceptedAccounts | aliasName, note, displayHidden |
| version | uInt32 present | absent |
| acceptedAccounts | array of uInt32 | absent |

## Sources

- DIP-0015 (dashpay/dips) — derivation offsets, ECB/CBC modes, privacy rule
- dashpay-contract `schema/v1/dashpay.schema.json` — CBOR-array description,
unique index, 48–2048B bounds
- DIP-0011 (key purposes), DIP-0013 (identity key paths), DIP-0009
assignments (15'/16' are incoming-funds / auto-accept — no contactInfo path)
- dashsync-iOS Identity models, android-dpp, kotlin-platform,
dash-shared-core — checked: no contactInfo implementation anywhere
- rs-dpp `lib.rs` `RootEncryptionKeyIndex` / `DerivationEncryptionKeyIndex`
type aliases
3 changes: 3 additions & 0 deletions packages/rs-platform-encryption/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ thiserror = "1.0"

[dev-dependencies]
hex = "0.4"
# Tests use secp256k1's RNG helpers (`generate_keypair`, `secp256k1::rand`),
# which are only available when dashcore's `rand` feature is enabled.
dashcore = { workspace = true, features = ["rand"] }
Comment thread
shumkov marked this conversation as resolved.
Outdated
244 changes: 242 additions & 2 deletions packages/rs-platform-encryption/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ use dashcore::secp256k1::{PublicKey, SecretKey};
type Aes256CbcEnc = cbc::Encryptor<Aes256>;
type Aes256CbcDec = cbc::Decryptor<Aes256>;

/// Length of the DIP-15 compact extended-public-key plaintext, in bytes.
///
/// `parentFingerprint(4) ‖ chainCode(32) ‖ publicKey(33)` = 69 bytes. This is
/// the plaintext layout DIP-15 specifies for `encryptedPublicKey` and the form
/// both reference clients (iOS dash-shared-core, Android dashj) emit and
/// hard-check on receive. Encrypting exactly 69 bytes yields a 96-byte
/// ciphertext (16-byte IV + 80-byte AES-256-CBC/PKCS7 block), matching the
/// deployed contract's `minItems/maxItems: 96`.
pub const COMPACT_XPUB_LEN: usize = 69;

/// Derive a shared secret key using ECDH as specified in DIP-15
///
/// This uses libsecp256k1_ecdh which computes: SHA256((y[31]&0x1|0x2) || x)
Expand Down Expand Up @@ -127,6 +137,66 @@ pub fn decrypt_extended_public_key(
decrypt_aes_256_cbc(shared_key, &iv, ciphertext)
}

/// Assemble the DIP-15 compact extended-public-key plaintext.
///
/// Concatenates `parent_fingerprint ‖ chain_code ‖ pubkey` into the 69-byte
/// compact form that DIP-15 defines for `encryptedPublicKey` (and that both
/// reference clients emit). This is the plaintext that should be fed to
/// [`encrypt_extended_public_key`] — *not* a BIP32/DIP-14 serialization, which
/// carries extra version/depth/child-number metadata the wire format omits.
///
/// # Arguments
/// * `parent_fingerprint` - 4-byte fingerprint of the parent key.
/// * `chain_code` - 32-byte chain code of the shared (account) key.
/// * `pubkey` - 33-byte compressed secp256k1 public key.
///
/// # Returns
/// The 69-byte compact plaintext.
pub fn compact_xpub_bytes(
parent_fingerprint: [u8; 4],
chain_code: [u8; 32],
pubkey: [u8; 33],
) -> [u8; COMPACT_XPUB_LEN] {
let mut out = [0u8; COMPACT_XPUB_LEN];
out[0..4].copy_from_slice(&parent_fingerprint);
out[4..36].copy_from_slice(&chain_code);
out[36..69].copy_from_slice(&pubkey);
out
}

/// Parse a DIP-15 compact extended-public-key plaintext back into its
/// three components.
///
/// Inverse of [`compact_xpub_bytes`]. Rejects any input whose length is not
/// exactly [`COMPACT_XPUB_LEN`] (69) bytes — the reference clients hard-check
/// this on receive, so a non-69-byte payload is not a valid DIP-15 compact
/// xpub and must be handled separately (e.g. a legacy 78/107-byte BIP32/DIP-14
/// serialization) by the caller.
///
/// # Arguments
/// * `bytes` - The decrypted plaintext (must be exactly 69 bytes).
///
/// # Returns
/// `(parent_fingerprint, chain_code, pubkey)` on success.
///
/// # Errors
/// [`CryptoError::InvalidCompactXpubLength`] if `bytes.len() != 69`.
#[allow(clippy::type_complexity)]
pub fn parse_compact_xpub(bytes: &[u8]) -> Result<([u8; 4], [u8; 32], [u8; 33]), CryptoError> {
if bytes.len() != COMPACT_XPUB_LEN {
return Err(CryptoError::InvalidCompactXpubLength(bytes.len()));
}

let mut parent_fingerprint = [0u8; 4];
let mut chain_code = [0u8; 32];
let mut pubkey = [0u8; 33];
parent_fingerprint.copy_from_slice(&bytes[0..4]);
chain_code.copy_from_slice(&bytes[4..36]);
pubkey.copy_from_slice(&bytes[36..69]);

Ok((parent_fingerprint, chain_code, pubkey))
}

/// Encrypt an account label for DashPay (DIP-15)
///
/// # Arguments
Expand Down Expand Up @@ -170,6 +240,60 @@ pub fn decrypt_account_label(
String::from_utf8(decrypted).map_err(|_| CryptoError::InvalidUtf8)
}

/// Encrypt a 32-byte identity id with AES-256-ECB (DIP-15
/// `contactInfo.encToUserId`).
///
/// Exactly two raw AES blocks — **no IV, no padding**. ECB is sound
/// for this one field per DIP-15's own analysis: the plaintext is
/// itself a SHA-256 output (pseudorandom, no repeated-block structure)
/// and the key — a dedicated hardened child at
/// `rootEncryptionKey/2^16'/index'` — is never reused for any other
/// purpose. Do NOT use this for anything but `encToUserId`.
pub fn encrypt_enc_to_user_id(key: &[u8; 32], to_user_id: &[u8; 32]) -> [u8; 32] {
use aes::cipher::{BlockEncrypt, KeyInit};

let cipher = Aes256::new(key.into());
let mut out = *to_user_id;
let (block1, block2) = out.split_at_mut(16);
cipher.encrypt_block(block1.into());
cipher.encrypt_block(block2.into());
out
}

/// Decrypt a 32-byte `contactInfo.encToUserId` ciphertext
/// (inverse of [`encrypt_enc_to_user_id`]).
pub fn decrypt_enc_to_user_id(key: &[u8; 32], ciphertext: &[u8; 32]) -> [u8; 32] {
use aes::cipher::{BlockDecrypt, KeyInit};

let cipher = Aes256::new(key.into());
let mut out = *ciphertext;
let (block1, block2) = out.split_at_mut(16);
cipher.decrypt_block(block1.into());
cipher.decrypt_block(block2.into());
out
}

/// Encrypt a `contactInfo.privateData` plaintext (CBOR bytes) as
/// `IV(16) ‖ AES-256-CBC(plaintext)` — the same prepended-IV layout
/// `encryptedPublicKey` uses (DIP-15 doesn't pin the layout for this
/// field; research/07 adopts the convention).
pub fn encrypt_private_data(key: &[u8; 32], iv: &[u8; 16], plaintext: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(16 + plaintext.len() + 16);
out.extend_from_slice(iv);
out.extend_from_slice(&encrypt_aes_256_cbc(key, iv, plaintext));
out
}

/// Decrypt a `contactInfo.privateData` blob (inverse of
/// [`encrypt_private_data`]).
pub fn decrypt_private_data(key: &[u8; 32], blob: &[u8]) -> Result<Vec<u8>, CryptoError> {
if blob.len() < 16 {
return Err(CryptoError::InvalidCiphertextLength);
}
let iv: [u8; 16] = blob[..16].try_into().expect("length checked above");
decrypt_aes_256_cbc(key, &iv, &blob[16..])
}

/// Errors that can occur during cryptographic operations
#[derive(Debug, thiserror::Error)]
pub enum CryptoError {
Expand All @@ -181,6 +305,9 @@ pub enum CryptoError {

#[error("Invalid ciphertext length (must be at least 16 bytes for IV)")]
InvalidCiphertextLength,

#[error("Invalid compact xpub length (DIP-15 requires exactly 69 bytes, got {0})")]
InvalidCompactXpubLength(usize),
}

#[cfg(test)]
Expand Down Expand Up @@ -232,8 +359,9 @@ mod tests {
let mut iv = [0u8; 16];
thread_rng().fill_bytes(&mut iv);

// Mock extended public key data (78 bytes)
let xpub_data = vec![0x04; 78];
// DIP-15 compact xpub plaintext (69 bytes). 69 → PKCS7 → 80, + 16-byte
// IV = exactly 96 bytes, matching the contract's minItems/maxItems: 96.
let xpub_data = vec![0x04; COMPACT_XPUB_LEN];

// Encrypt and decrypt
let encrypted = encrypt_extended_public_key(&shared_key, &iv, &xpub_data);
Expand All @@ -246,6 +374,66 @@ mod tests {
assert_eq!(xpub_data, decrypted);
}

#[test]
fn test_compact_xpub_round_trip() {
// Distinct byte patterns per field so a mis-sliced offset is caught.
let parent_fingerprint = [0x11u8, 0x22, 0x33, 0x44];
let chain_code = [0xAAu8; 32];
let mut pubkey = [0xBBu8; 33];
pubkey[0] = 0x02; // compressed-pubkey prefix

let compact = compact_xpub_bytes(parent_fingerprint, chain_code, pubkey);
assert_eq!(compact.len(), COMPACT_XPUB_LEN);
assert_eq!(compact.len(), 69);

// Byte-exact layout: fingerprint ‖ chaincode ‖ pubkey.
assert_eq!(&compact[0..4], &parent_fingerprint);
assert_eq!(&compact[4..36], &chain_code);
assert_eq!(&compact[36..69], &pubkey);

let (fp, cc, pk) = parse_compact_xpub(&compact).expect("parse 69-byte compact");
assert_eq!(fp, parent_fingerprint);
assert_eq!(cc, chain_code);
assert_eq!(pk, pubkey);
}

#[test]
fn test_encrypt_compact_xpub_is_exactly_96_bytes() {
// The whole point of the 69-byte compact form: it encrypts to exactly
// 96 bytes (16-byte IV + 80-byte AES-256-CBC/PKCS7), which is what the
// deployed contract enforces. A 107-byte DIP-14 serialization would
// yield 128 bytes and fail the contract's maxItems: 96.
let shared_key = [0x07u8; 32];
let iv = [0x09u8; 16];
let plaintext = [0xCDu8; COMPACT_XPUB_LEN];

let encrypted = encrypt_extended_public_key(&shared_key, &iv, &plaintext);
assert_eq!(
encrypted.len(),
96,
"69-byte compact plaintext must encrypt to exactly 96 bytes"
);

let decrypted = decrypt_extended_public_key(&shared_key, &encrypted).unwrap();
assert_eq!(&decrypted[..], &plaintext[..]);
}

#[test]
fn test_parse_compact_xpub_rejects_wrong_length() {
// Lengths that are NOT 69 must be rejected — including the legacy 78/107
// BIP32/DIP-14 serializations and the empty case.
for bad_len in [0usize, 36, 68, 70, 78, 107, 128] {
let bytes = vec![0u8; bad_len];
let err = parse_compact_xpub(&bytes).expect_err("non-69-byte input must be rejected");
assert!(
matches!(err, CryptoError::InvalidCompactXpubLength(n) if n == bad_len),
"expected InvalidCompactXpubLength({}), got {:?}",
bad_len,
err
);
}
}

#[test]
fn test_account_label_encryption() {
let secp = Secp256k1::new();
Expand Down Expand Up @@ -276,3 +464,55 @@ mod tests {
assert_eq!(label, decrypted);
}
}

#[cfg(test)]
mod contact_info_tests {
use super::*;

#[test]
fn enc_to_user_id_round_trips_and_is_two_independent_blocks() {
let key = [0x11u8; 32];
let mut id = [0u8; 32];
for (i, b) in id.iter_mut().enumerate() {
*b = i as u8;
}

let ct = encrypt_enc_to_user_id(&key, &id);
assert_ne!(ct, id, "ciphertext must differ from plaintext");
assert_eq!(decrypt_enc_to_user_id(&key, &ct), id, "round trip");

// ECB property we rely on: equal plaintext blocks → equal
// ciphertext blocks (sound here only because identity ids are
// hash outputs). This pins that the implementation really is
// ECB and not CBC-with-zero-IV.
let same_blocks = [0xAAu8; 32];
let ct2 = encrypt_enc_to_user_id(&key, &same_blocks);
assert_eq!(ct2[..16], ct2[16..], "ECB: identical blocks encrypt identically");

// Wrong key must not round-trip.
assert_ne!(decrypt_enc_to_user_id(&[0x22u8; 32], &ct), id);
}

#[test]
fn private_data_round_trips_with_prepended_iv() {
let key = [0x33u8; 32];
let iv = [0x44u8; 16];
// Minimal CBOR-ish payload; the schema floor is 48 bytes of
// ciphertext which IV(16) + one padded block satisfies — the
// length policy lives at the document-build layer, not here.
let plaintext = b"[\"alias\",\"note\",false] stand-in cbor";

let blob = encrypt_private_data(&key, &iv, plaintext);
assert_eq!(&blob[..16], &iv, "IV must be prepended verbatim");
assert!(blob.len() >= 48, "IV + padded CBC reaches the schema floor");

let plain = decrypt_private_data(&key, &blob).expect("decrypt");
assert_eq!(plain, plaintext);

// Truncated blob → typed error, not a panic.
assert!(matches!(
decrypt_private_data(&key, &blob[..10]),
Err(CryptoError::InvalidCiphertextLength)
));
}
}
Loading
Loading