Skip to content

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

Open
bfoss765 wants to merge 3 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 3 commits into
dashpay:v4.1-devfrom
bfoss765:port/v4.1/unmanaged-identity-reads

Conversation

@bfoss765

@bfoss765 bfoss765 commented Jul 21, 2026

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

Summary by CodeRabbit

  • Bug Fixes
    • Improved error reporting when a required signing key is unavailable, with clearer non-retryable guidance.
    • Managed identities that are missing or removed are now handled consistently instead of exposing raw native errors.
    • Wallet handles that are invalid or stale are now distinguished from identities that simply aren’t managed.
    • Identity-related operations and listings continue gracefully when managed identities are no longer available.

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

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 055edfac-fd8d-4971-a6cb-64c150f4ecb6

📥 Commits

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

📒 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

📝 Walkthrough

Walkthrough

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

Changes

Platform wallet error handling

Layer / File(s) Summary
Signing-key error mapping
packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/errors/DashSdkError.kt, packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/security/KeystoreSigner.kt, packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/errors/DashSdkErrorTest.kt
KeystoreSigner emits a stable missing-key marker, and matching catch-all native errors map to non-retryable SigningKeyUnavailable while dedicated mappings remain unchanged.
Managed-identity native outcomes
packages/rs-platform-wallet-ffi/src/dashpay.rs
Managed-identity lookup classification now separates found, unmanaged, invalid, and stale wallet-handle outcomes, with tests for the resulting native errors.
Managed-identity Kotlin translation
packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/tokens/Dashpay.kt, packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/identity/IdentityRegistration.kt, packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/wallet/ManagedPlatformWallet.kt, packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/tokens/ManagedIdentityNotFoundTranslationTest.kt
Managed-identity NotFound errors are translated to zero handles for Dashpay, identity registration, and in-memory wallet enumeration; other native errors are rethrown and translation behavior is tested.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

Signing-key error flow

sequenceDiagram
  participant KeystoreSigner
  participant PlatformWalletNative
  participant DashSdkError
  KeystoreSigner->>PlatformWalletNative: completion with missing-key marker
  PlatformWalletNative->>DashSdkError: native code and message
  DashSdkError-->>KeystoreSigner: SigningKeyUnavailable
Loading

Managed-identity lookup flow

sequenceDiagram
  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
Loading

Possibly related PRs

  • dashpay/platform#4183: Updates related DashSdkError signing-key mapping and platform-wallet NotFound handling.

Suggested reviewers: lklimek, llbartekll, zocolini, quantumexplorer, shumkov

🚥 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 The title accurately summarizes the two main changes: unmanaged-identity reads now return absence and missing signing keys map to SigningKeyUnavailable.
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 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

✅ Final review complete — no blockers (commit 8acb0bd)

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>

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Codex + Sonnet

The 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)

@shumkov

shumkov commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

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 AppState.swift:185 base error, not this PR).

One ask before merge: cargo fmt — the new get_managed_identity_unknown_wallet_is_invalid_handle test in dashpay.rs fails cargo fmt --check (fork CI never runs the fmt job, so it didn't surface); merging as-is would redden v4.1-dev's formatting check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thepastaclaw

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Codex + Sonnet

The 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)

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