fix(kotlin-sdk): unmanaged-identity reads return absence + typed SigningKeyUnavailable (split from #4183)#4191
Conversation
…otFound Split out of dashpay#4183 (port of dashpay#4060) per review: the unmanaged-identity read fixes are conflict-free on v4.1-dev and should not be blocked on the Keystore rework. The FFI's blanket Option -> result conversion reports an identity the wallet does not manage as PlatformWalletFFIResultCode::NotFound (98), so the Kotlin callers' zero-handle checks were dead and every local read over an unmanaged identity threw instead of returning absence. - Dashpay: route getManagedIdentity through translateManagedIdentityNotFoundToZero so contacts()/syncState()/ payments()/sendContactRequest() treat "not managed" as null/empty/ false; ErrorInvalidHandle still propagates. - Sweep the remaining dead `== 0L` sites: ManagedPlatformWallet .inMemoryIdentityStates (one unmanaged/just-removed id no longer throws through the whole listing) and IdentityRegistration .contestedDpnsNames (the intended "identity is not managed by this wallet" NotFound now actually surfaces). - platform-wallet-ffi: platform_wallet_get_managed_identity keeps the three outcomes distinct (classify_managed_identity_outcome) so a stale/removed wallet surfaces ErrorInvalidHandle and never masquerades as an unmanaged identity; unit tests pin both arms. - DashSdkError: name the code (PLATFORM_WALLET_NOT_FOUND_CODE = 98); the 98 -> DashSdkError.NotFound mapping is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Split out of dashpay#4183 (port of dashpay#4060) per review: the typed signing error is conflict-free on v4.1-dev and should not be blocked on the Keystore rework. The KeystoreSigner "missing key" completion error travels as free text through Rust and used to come back as an opaque PlatformWallet.Generic (or WalletOperation) failure. It is now built from a shared MESSAGE_MARKER constant and recognized on the Kotlin boundary as the typed DashSdkError.PlatformWallet.SigningKeyUnavailable, so hosts can route users to key repair instead of showing a generic error. The marker is only consulted on the catch-all codes (6 / else), so the dedicated retry-semantics types are never overridden. Known limitation (kept as-is from dashpay#4183 by request): the discriminator is message-text-based (message.contains on the marker); a structured error code across the FFI boundary is follow-up work in the parent PR line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (8)
📝 WalkthroughWalkthroughThe PR adds typed missing-signing-key mapping, distinguishes invalid and unmanaged identity lookups in Rust, and centralizes Kotlin translation of managed-identity NotFound errors into zero handles across identity, Dashpay, and wallet flows. ChangesPlatform wallet error handling
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)Signing-key error flowsequenceDiagram
participant KeystoreSigner
participant PlatformWalletNative
participant DashSdkError
KeystoreSigner->>PlatformWalletNative: completion with missing-key marker
PlatformWalletNative->>DashSdkError: native code and message
DashSdkError-->>KeystoreSigner: SigningKeyUnavailable
Managed-identity lookup flowsequenceDiagram
participant KotlinSDK
participant RustFFI
participant Dashpay
KotlinSDK->>RustFFI: get managed identity handle
RustFFI-->>KotlinSDK: handle, NotFound, or InvalidHandle
KotlinSDK->>Dashpay: translate NotFound to zero handle
Dashpay-->>KotlinSDK: managed result or skipped identity
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
✅ Final review complete — no blockers (commit 8acb0bd) |
…the signer wire Replace end-to-end message sniffing for the signer's "missing key" failure with a typed discriminator (dashpay#4060 finding 7): - rs-sdk-ffi: DashSDKSignerErrorCode { Generic = 0, SigningKeyUnavailable = 1, AuthenticationFailed = 2 (reserved) }; SignCompletionCallback and dash_sdk_sign_async_completion gain error_code: i32 (before error_message). SignResult stays Result<Vec<u8>, ProtocolError> (a new rs-dpp ProtocolError variant would carry serialization blast radius), so code 1 rides the single Rust-owned machine prefix DASH_SDK_SIGNER_ERR_KEY_UNAVAILABLE_PREFIX through ProtocolError::Generic — typed at both ABI edges, one constant bridging the string segment. This is an internal coordinated ABI change: every piece versions together in this monorepo. - rs-platform-wallet-ffi: PlatformWalletFFIResultCode:: ErrorSigningKeyUnavailable = 31 (codes 26-28 are reserved for dashpay#4185's reservation-token errors and 29/30 for dashpay#4184's asset-lock errors on sibling branches — documented in the enum as dashpay#4184 does). The From<dpp::ProtocolError> conversion restores the typed code from the prefix FIRST (before the loose keyword sniffs), and the From<PlatformWalletError> blanket impl restores it on the catch-all only (dedicated retry-semantics codes are never overridden) — covering the Sdk(dash_sdk::Error::Protocol(..)) wrapping path. - JNI/Kotlin: SignerNative.completeSign(token, signature, errorCode, errorMessage); KeystoreSigner passes SIGNER_ERROR_CODE_KEY_UNAVAILABLE on the null-key branch (keeping the MESSAGE_MARKER text for the transition window) and Generic everywhere else. DashSdkError maps 31 → PlatformWallet.SigningKeyUnavailable; the dashpay#4191 marker sniff on the catch-all codes remains as a deprecated old-native fallback with a removal note tied to the next minor release. - Swift: KeychainSigner trampolines forward the code (missing-row / missing-scalar outcomes classify as 1); PlatformWalletResultCode gains errorSigningKeyUnavailable = 31 → PlatformWalletError .signingKeyUnavailable (Kotlin parity). - Tests: rs-sdk-ffi completion-code tests (prefix present for code 1, absent for generic), platform-wallet-ffi prefix→31 tests on both conversion points, Kotlin code-31 + fallback-marker tests, Swift mapping and trampoline-classifier tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…g-2 TOCTOU window Round-2 nits (dashpay#4060): - The MESSAGE_MARKER fallback's stated rationale — "old native library + new Kotlin partial builds" — was not viable: the sign-completion JNI arity change (3→4 args) makes that pairing a type-confused native call on every completion, so mixed artifacts are unsupported outright. The fallback's real purpose is the dashpay#4191 merge-order transition (whose marker-based classification predates the typed code 31) plus defense in depth for conversion paths that lose the machine prefix. KDoc/comments in DashSdkError, KeystoreSigner, DashSdkErrorTest, and the parity/leftovers docs now say so; removal horizon unchanged (next minor release). - retrievePrivateKey rung 2 gains a comment naming the accepted fingerprint-read→decrypt TOCTOU window (pre-existing dashpay#4172 parity): a concurrent rotation fails as a wrong-key crypto error into the recovery ladder — never stale plaintext. No behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Reviewed the split (two independent passes): exactly the two conflict-free slices, semantically unchanged from the earlier review, with all five |
…the signer wire Replace end-to-end message sniffing for the signer's "missing key" failure with a typed discriminator (dashpay#4060 finding 7): - rs-sdk-ffi: DashSDKSignerErrorCode { Generic = 0, SigningKeyUnavailable = 1, AuthenticationFailed = 2 (reserved) }; SignCompletionCallback and dash_sdk_sign_async_completion gain error_code: i32 (before error_message). SignResult stays Result<Vec<u8>, ProtocolError> (a new rs-dpp ProtocolError variant would carry serialization blast radius), so code 1 rides the single Rust-owned machine prefix DASH_SDK_SIGNER_ERR_KEY_UNAVAILABLE_PREFIX through ProtocolError::Generic — typed at both ABI edges, one constant bridging the string segment. This is an internal coordinated ABI change: every piece versions together in this monorepo. - rs-platform-wallet-ffi: PlatformWalletFFIResultCode:: ErrorSigningKeyUnavailable = 31 (codes 26-28 are reserved for dashpay#4185's reservation-token errors and 29/30 for dashpay#4184's asset-lock errors on sibling branches — documented in the enum as dashpay#4184 does). The From<dpp::ProtocolError> conversion restores the typed code from the prefix FIRST (before the loose keyword sniffs), and the From<PlatformWalletError> blanket impl restores it on the catch-all only (dedicated retry-semantics codes are never overridden) — covering the Sdk(dash_sdk::Error::Protocol(..)) wrapping path. - JNI/Kotlin: SignerNative.completeSign(token, signature, errorCode, errorMessage); KeystoreSigner passes SIGNER_ERROR_CODE_KEY_UNAVAILABLE on the null-key branch (keeping the MESSAGE_MARKER text for the transition window) and Generic everywhere else. DashSdkError maps 31 → PlatformWallet.SigningKeyUnavailable; the dashpay#4191 marker sniff on the catch-all codes remains as a deprecated old-native fallback with a removal note tied to the next minor release. - Swift: KeychainSigner trampolines forward the code (missing-row / missing-scalar outcomes classify as 1); PlatformWalletResultCode gains errorSigningKeyUnavailable = 31 → PlatformWalletError .signingKeyUnavailable (Kotlin parity). - Tests: rs-sdk-ffi completion-code tests (prefix present for code 1, absent for generic), platform-wallet-ffi prefix→31 tests on both conversion points, Kotlin code-31 + fallback-marker tests, Swift mapping and trampoline-classifier tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…g-2 TOCTOU window Round-2 nits (dashpay#4060): - The MESSAGE_MARKER fallback's stated rationale — "old native library + new Kotlin partial builds" — was not viable: the sign-completion JNI arity change (3→4 args) makes that pairing a type-confused native call on every completion, so mixed artifacts are unsupported outright. The fallback's real purpose is the dashpay#4191 merge-order transition (whose marker-based classification predates the typed code 31) plus defense in depth for conversion paths that lose the machine prefix. KDoc/comments in DashSdkError, KeystoreSigner, DashSdkErrorTest, and the parity/leftovers docs now say so; removal horizon unchanged (next minor release). - retrievePrivateKey rung 2 gains a comment naming the accepted fingerprint-read→decrypt TOCTOU window (pre-existing dashpay#4172 parity): a concurrent rotation fails as a wrong-key crypto error into the recovery ladder — never stale plaintext. No behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Codex + Sonnet
The combined Codex and successful Sonnet reviewer evidence matches the source: the Rust FFI now distinguishes stale wallet handles from valid wallets that do not manage an identity, and all Kotlin managed-identity lookup paths preserve that distinction while returning absence for the latter case. The signing-key mapping shares its marker with the emitting signer and does not override dedicated retry-semantics errors. No concrete in-scope defects were confirmed; the message-based discriminator is an explicitly disclosed and deferred design limitation.
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 (failed),claude-sonnet-5— ffi-engineer (completed)
|
Ran the full test suite on a same-repo ref (dispatch run 29994288801, this head + a v4.1-dev merge): full Rust workspace suite passed, including the two new platform-wallet-ffi tests, and the SwiftDashSDK target compiles against the FFI change (the Swift job's failure there is a pre-existing One ask before merge: |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@coderabbitai review |
✅ Action performedReview finished.
|
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Codex + Sonnet
The incremental change from ce3a17d to 8acb0bd only applies cargo-fmt reflow to an existing unit-test call in packages/rs-platform-wallet-ffi/src/dashpay.rs. It changes no production behavior or test semantics, and cumulative revalidation found no in-scope defects.
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— ffi-engineer (completed),claude-sonnet-5— general (completed),claude-sonnet-5— security-auditor (completed)
Splits the two conflict-free, review-approved slices out of #4183 so they aren't blocked on the
Keystore rework, as suggested by @shumkov in review.
What's included
Unmanaged-identity local reads return absence, not an exception. The FFI's blanket
Option → resultconversion reports an identity the wallet does not manage asPlatformWalletFFIResultCode::NotFound(98), so the Kotlin callers' zero-handle checks were dead andany local read over an unmanaged identity (e.g.
Dashpay.syncStateon a contact's identity) threwinstead of returning null/empty.
DashpayroutesgetManagedIdentitythrough atranslateManagedIdentityNotFoundToZerohelper:contacts()/syncState()/payments()/sendContactRequest()treat "not managed" asnull / empty / false.
ErrorInvalidHandlestill propagates — a stale wallet handle nevermasquerades as an unmanaged identity.
== 0Lsites flagged in review:ManagedPlatformWallet.inMemoryIdentityStates()(one unmanaged id no longer throws through thewhole listing) and
IdentityRegistration.contestedDpnsNames()(the intended "identity is notmanaged by this wallet" error now actually surfaces).
platform-wallet-ffi:platform_wallet_get_managed_identitykeeps the three lookup outcomesdistinct (handle absent / wallet removed from the manager map →
ErrorInvalidHandle; valid walletnot managing the id →
NotFound), with unit tests pinning both error arms.Typed
SigningKeyUnavailable. TheKeystoreSigner"missing key" completion error travels asfree text through Rust and came back as an opaque
Generic/WalletOperationfailure. It is nowbuilt from a shared
MESSAGE_MARKERconstant and recognized at the Kotlin boundary asDashSdkError.PlatformWallet.SigningKeyUnavailable, so hosts can route users to key repair. Themarker is only consulted on the catch-all codes, so dedicated retry-semantics types are never
overridden.
Known limitation, kept deliberately: the discriminator is still message-text-based
(
message.contains); moving to a structured code across the FFI boundary is follow-up work in theparent PR line.
What's NOT included (stays in #4183)
KeySecurityPolicy(AUTH_GATED/DEVICE_BOUND), the keys-alias split andlegacy-blob migration matrix, decrypt-probing, pending-identity-key state, and their tests.
PlatformWallet.NotFound(this PR keeps theexisting 98 →
DashSdkError.NotFoundmapping, only naming the code viaPLATFORM_WALLET_NOT_FOUND_CODE).Testing
:sdk:compileDebugKotlinand:sdk:testDebugUnitTest(new:ManagedIdentityNotFoundTranslationTest,two
SigningKeyUnavailablemapping tests; existing 98-mapping tests unchanged and passing).cargo test -p platform-wallet-ffidashpay unit tests, including the two new outcome-classificationtests.
🤖 Generated with Claude Code
Summary by CodeRabbit