Skip to content

fix(kotlin-sdk): unmanaged-identity reads return absence + typed SigningKeyUnavailable (split from #4183)#4191

Open
bfoss765 wants to merge 2 commits into
dashpay:v4.1-devfrom
bfoss765:port/v4.1/unmanaged-identity-reads
Open

fix(kotlin-sdk): unmanaged-identity reads return absence + typed SigningKeyUnavailable (split from #4183)#4191
bfoss765 wants to merge 2 commits into
dashpay:v4.1-devfrom
bfoss765:port/v4.1/unmanaged-identity-reads

Conversation

@bfoss765

Copy link
Copy Markdown
Contributor

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 → result conversion reports an identity the wallet does not manage as
PlatformWalletFFIResultCode::NotFound (98), so the Kotlin callers' zero-handle checks were dead and
any local read over an unmanaged identity (e.g. Dashpay.syncState on a contact's identity) threw
instead of returning null/empty.

  • Dashpay routes getManagedIdentity through a translateManagedIdentityNotFoundToZero helper:
    contacts() / syncState() / payments() / sendContactRequest() treat "not managed" as
    null / empty / false. ErrorInvalidHandle still propagates — a stale wallet handle never
    masquerades as an unmanaged identity.
  • Sweep of the remaining dead == 0L sites flagged in review:
    ManagedPlatformWallet.inMemoryIdentityStates() (one unmanaged id no longer throws through the
    whole listing) and IdentityRegistration.contestedDpnsNames() (the intended "identity is not
    managed by this wallet" error now actually surfaces).
  • platform-wallet-ffi: platform_wallet_get_managed_identity keeps the three lookup outcomes
    distinct (handle absent / wallet removed from the manager map → ErrorInvalidHandle; valid wallet
    not managing the id → NotFound), with unit tests pinning both error arms.

Typed SigningKeyUnavailable. The KeystoreSigner "missing key" completion error travels as
free text through Rust and came back as an opaque Generic/WalletOperation failure. It is now
built from a shared MESSAGE_MARKER constant and recognized at the Kotlin boundary as
DashSdkError.PlatformWallet.SigningKeyUnavailable, so hosts can route users to key repair. The
marker 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 the
parent PR line.

What's NOT included (stays in #4183)

  • The Keystore rework: KeySecurityPolicy (AUTH_GATED/DEVICE_BOUND), the keys-alias split and
    legacy-blob migration matrix, decrypt-probing, pending-identity-key state, and their tests.
  • The host-visible retarget of native code 98 to a typed PlatformWallet.NotFound (this PR keeps the
    existing 98 → DashSdkError.NotFound mapping, only naming the code via
    PLATFORM_WALLET_NOT_FOUND_CODE).

Testing

  • :sdk:compileDebugKotlin and :sdk:testDebugUnitTest (new: ManagedIdentityNotFoundTranslationTest,
    two SigningKeyUnavailable mapping tests; existing 98-mapping tests unchanged and passing).
  • cargo test -p platform-wallet-ffi dashpay unit tests, including the two new outcome-classification
    tests.

🤖 Generated with Claude Code

bfoss765 and others added 2 commits July 21, 2026 15:04
…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>
@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: 14 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

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

Run ID: d77839e6-6a60-402a-813f-6376392915e0

📥 Commits

Reviewing files that changed from the base of the PR and between 2b3e3fc and ce3a17d.

📒 Files selected for processing (8)
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/errors/DashSdkError.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/identity/IdentityRegistration.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/security/KeystoreSigner.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/tokens/Dashpay.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/wallet/ManagedPlatformWallet.kt
  • packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/errors/DashSdkErrorTest.kt
  • packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/tokens/ManagedIdentityNotFoundTranslationTest.kt
  • packages/rs-platform-wallet-ffi/src/dashpay.rs
✨ 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 in progress — actively reviewing now (commit ce3a17d)
Stage: Sonnet review + final verification
ETA: complete ~05:05 UTC (median 21m across 30 recent reviews)
Running 25m · Last checked: 2026-07-23 05:00 UTC

bfoss765 added a commit to bfoss765/platform that referenced this pull request Jul 21, 2026
…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>
bfoss765 added a commit to bfoss765/platform that referenced this pull request Jul 21, 2026
…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>
@shumkov

shumkov commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Reviewed the split (two independent passes): exactly the two conflict-free slices, semantically unchanged from the earlier review, with all five getManagedIdentity call paths routed through the translation helper — including the two previously-missed sites (ManagedPlatformWallet.kt:556, IdentityRegistration.kt:100) — and the host-breaking 98-remap correctly left in #4183 with an explicit "not included" note. Stale handles now surface as InvalidHandle instead of masquerading as not-managed, unit-tested at both the FFI path and the classification seam. One note: the changed-packages CI gate skipped the new platform-wallet-ffi unit tests — they've only run locally until a Rust job picks them up. Ready from our side.

bfoss765 added a commit to bfoss765/platform that referenced this pull request Jul 23, 2026
…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>
bfoss765 added a commit to bfoss765/platform that referenced this pull request Jul 23, 2026
…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>
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