Skip to content

fix(platform-wallet): type signer-reported missing key as MessageSigningKeyUnavailable - #4321

Merged
QuantumExplorer merged 1 commit into
v4.2-devfrom
claude/nostalgic-blackburn-ad5ec9
Aug 6, 2026
Merged

fix(platform-wallet): type signer-reported missing key as MessageSigningKeyUnavailable#4321
QuantumExplorer merged 1 commit into
v4.2-devfrom
claude/nostalgic-blackburn-ad5ec9

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Aug 6, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

During classic message signing (CoreWallet::sign_message, added in #4319), every sign_ecdsa failure was wrapped into MessageSigningFailed with context text prepended, which buried the reserved key-unavailable marker mid-string. Since the FFI boundary only recognizes the marker structurally at position 0 (never as a substring — per the #4183 review rule), a missing private key surfaced as ErrorUnknown instead of the typed ErrorSigningKeyUnavailable (31), losing the host's key-repair routing. Flagged by CodeRabbit.

What was done?

Key unavailability is now a typed condition end to end; the FFI boundary never parses formatted reasons. Both halves of the marker contract are closed in-repo:

  • Producer (rs-sdk-ffi): MnemonicResolverSignerError::NotFound — the production missing-key completion (no mnemonic stored for the wallet) — now renders with DASH_SDK_SIGNER_ERR_KEY_UNAVAILABLE_PREFIX at position 0. Since key-wallet's Signer::Error is bounded only by Display, the start-of-rendering marker is the one typed signal that surface can carry.
  • Consumer (rs-platform-wallet): sign_message checks that prefix on the signer's unwrapped rendering BEFORE composing the "signer rejected the digest at {path}: …" context, and returns the typed PlatformWalletError::MessageSigningKeyUnavailable, which the FFI already maps to code 31 structurally. The check remains position-0 only; a mid-string mention of the marker still wraps as MessageSigningFailed.
  • Updated the now-stale limitation notes in rs-platform-wallet's error docs and platform-wallet-ffi's conversion NOTE, and the core_wallet_sign_message doc to name the new code-31 producer.

How Has This Been Tested?

  • signer_key_unavailable_is_not_preserved_during_message_signing — which pinned the old limitation and instructed "flip me when this becomes reachable" — is flipped to signer_key_unavailable_is_typed_during_message_signing, asserting MessageSigningKeyUnavailable with the correct address.
  • New producer test pins NotFound's marker at position 0 (rs-sdk-ffi); new guard test pins that a mid-string marker is NOT promoted (the feat(kotlin-sdk)!: keystore rework — policy-alias split, layered key recovery, durable repair, structured signer errors (stacked on #4191) #4183 substring-sniff rule).
  • cargo test -p platform-wallet --lib (525 passed), cargo test -p rs-sdk-ffi --lib (308 passed), cargo test -p platform-wallet-ffi --lib (227 passed, includes the FFI mapping tests); cargo fmt and cargo clippy --all-targets clean on the touched crates.

Breaking Changes

None. MessageSigningKeyUnavailable and its FFI code 31 already exist and are already mirrored in the host SDKs; this only adds a producer for it. The NotFound Display string gains the machine prefix, which no code matched as a full string.

Checklist:

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

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved message-signing error handling when required signing keys are unavailable.
    • Signing now consistently reports key-unavailable errors, while unrelated signer failures remain classified as signing failures.
    • Prevented incidental mentions of key-unavailable text from being misclassified as key errors.
  • Documentation

    • Clarified signing failure scenarios, including missing wallet mnemonics and unavailable signing keys.

…ingKeyUnavailable

A missing private key reported by the signer during classic message
signing was wrapped into MessageSigningFailed with context text
prepended before the reserved key-unavailable marker, so the FFI
boundary could no longer recognize the condition and it flattened to
ErrorUnknown instead of ErrorSigningKeyUnavailable (31).

Close it with both halves of the marker contract, in-repo:

- MnemonicResolverCoreSigner::NotFound (the production missing-key
  completion) now renders with DASH_SDK_SIGNER_ERR_KEY_UNAVAILABLE_PREFIX
  at position 0 — the one typed signal a Display-only Signer::Error can
  carry.
- CoreWallet::sign_message checks that prefix on the signer's unwrapped
  rendering BEFORE composing the "signer rejected the digest at {path}"
  context, and returns the typed
  PlatformWalletError::MessageSigningKeyUnavailable, which the FFI
  already maps to code 31 without parsing any formatted reason. The
  check stays position-0 only; a mid-string mention of the marker still
  wraps as MessageSigningFailed (per the #4183 review rule).

Flips signer_key_unavailable_is_not_preserved_during_message_signing —
which pinned the old limitation — to
signer_key_unavailable_is_typed_during_message_signing asserting the
typed result, and adds producer (marker at position 0 of NotFound) and
guard (mid-string marker not promoted) tests.

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

coderabbitai Bot commented Aug 6, 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: cda1546a-3d2b-4ec4-9972-cda947eddb07

📥 Commits

Reviewing files that changed from the base of the PR and between d8facb2 and 8f067da.

📒 Files selected for processing (5)
  • packages/rs-platform-wallet-ffi/src/core_wallet/sign_message.rs
  • packages/rs-platform-wallet-ffi/src/error.rs
  • packages/rs-platform-wallet/src/error.rs
  • packages/rs-platform-wallet/src/wallet/core/sign_message.rs
  • packages/rs-sdk-ffi/src/mnemonic_resolver_core_signer.rs

📝 Walkthrough

Walkthrough

Changes

Message-signing error propagation

Layer / File(s) Summary
Signer marker contract
packages/rs-sdk-ffi/src/mnemonic_resolver_core_signer.rs
MnemonicResolverSignerError::NotFound now starts with the reserved key-unavailable marker. A unit test verifies the marker position.
Wallet signing error classification
packages/rs-platform-wallet/src/wallet/core/sign_message.rs
Position-zero markers now produce MessageSigningKeyUnavailable. Other signer errors remain generic. Tests cover both cases.
Wallet and FFI error contract
packages/rs-platform-wallet/src/error.rs, packages/rs-platform-wallet-ffi/src/error.rs, packages/rs-platform-wallet-ffi/src/core_wallet/sign_message.rs
Documentation describes signer-reported missing keys, mnemonic absence, promotion rules, and FFI code mapping.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MnemonicResolverCoreSigner
  participant WalletCoreSignMessage
  participant WalletFFI
  MnemonicResolverCoreSigner->>WalletCoreSignMessage: return marker-prefixed NotFound error
  WalletCoreSignMessage->>WalletCoreSignMessage: classify position-zero marker
  WalletCoreSignMessage->>WalletFFI: convert MessageSigningKeyUnavailable
Loading

Possibly related PRs

Suggested reviewers: lklimek, llbartekll, zocolini

🚥 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 clearly and concisely describes the main change: mapping signer-reported missing keys to MessageSigningKeyUnavailable.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/nostalgic-blackburn-ad5ec9

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added this to the v4.2.0 milestone Aug 6, 2026
@thepastaclaw

thepastaclaw commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

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

@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.61%. Comparing base (920e507) to head (8f067da).
⚠️ Report is 2 commits behind head on v4.2-dev.

Additional details and impacted files
@@             Coverage Diff              @@
##           v4.2-dev    #4321      +/-   ##
============================================
- Coverage     87.61%   87.61%   -0.01%     
============================================
  Files          2704     2704              
  Lines        345206   345211       +5     
============================================
+ Hits         302445   302446       +1     
- Misses        42761    42765       +4     
Components Coverage Δ
dpp 88.83% <ø> (ø)
drive 86.25% <ø> (ø)
drive-abci 89.66% <ø> (+<0.01%) ⬆️
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.88% <ø> (ø)
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 48.02% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@thepastaclaw

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 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 + Opus

This is a narrowly-scoped, well-tested fix: it makes MnemonicResolverSignerError::NotFound stamp the reserved key-unavailable marker at position 0 of its Display, and teaches sign_message to check that marker (position-0 only, correctly avoiding the #4183 substring-sniff antipattern) before composing context, promoting the failure to the typed MessageSigningKeyUnavailable/FFI code 31. All reviewer lanes (Claude and Codex, general/security/rust-quality/ffi-engineer) converge on one real, verified issue: MessageSigningKeyUnavailable's doc comment was updated to describe two producers but its #[error(...)] Display text still only describes the original address-resolution producer, so hosts hitting the new signer-reported-missing-key path see a factually wrong diagnostic ('it belongs to no signable funds account of this wallet') surfaced verbatim via error.to_string() in the FFI result. No other in-scope issues were found; out-of-scope architectural/hardening notes from Codex/Claude specialists were reviewed and excluded as pre-existing or overly broad. Source: reviewer backend model gpt-5.6-sol via Codex (general, security-auditor, rust-quality, ffi-engineer) and claude-sonnet-5 via Claude (general, security-auditor, ffi-engineer, rust-quality retry3); verifier backend model claude-sonnet-5 via Claude (final-verifier). openclaw-agent/cliproxy/gpt-5.6-sol was orchestration-only.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — security-auditor (completed), gpt-5.6-sol — rust-quality (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: claude-sonnet-5 — final-verifier
  • Opus reviewers: claude-sonnet-5 — general (completed), claude-sonnet-5 — security-auditor (completed), claude-sonnet-5 — rust-quality (failed), claude-sonnet-5 — ffi-engineer (completed), claude-sonnet-5 — rust-quality (failed), claude-sonnet-5 — rust-quality (failed), claude-sonnet-5 — rust-quality (completed)

🟡 1 suggestion(s)

1 additional finding(s) omitted (not in diff).

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

In `packages/rs-platform-wallet/src/error.rs`:
- [SUGGESTION] packages/rs-platform-wallet/src/error.rs:142-146: MessageSigningKeyUnavailable's Display text is stale for its new second producer
  The doc comment above `MessageSigningKeyUnavailable` was correctly updated (lines 123-140) to describe two producers: (1) address resolution finding no signable funds account, and (2) the signer itself reporting a missing key via the position-0 marker — the new behavior `sign_message` adds in this PR. The `#[error(...)]` attribute that generates `Display`, however, was left unchanged: "no signing key for message-signing address {address}: it belongs to no signable funds account of this wallet". That text hardcodes producer (1)'s explanation only. After this PR, `sign_message` (packages/rs-platform-wallet/src/wallet/core/sign_message.rs) also constructs this variant when `sign_ecdsa` fails and the signer's rendered error starts with `SIGNER_KEY_UNAVAILABLE_PREFIX` — in that case a derivation path WAS resolved (the address does belong to a signable account) and `sign_ecdsa` was actually invoked; the real cause is the signer/keychain having no stored key (e.g. `MnemonicResolverCoreSigner::NotFound`, no mnemonic present). `PlatformWalletFFIResult::from`/`err(code, error.to_string())` (packages/rs-platform-wallet-ffi/src/error.rs) forwards this exact string to FFI hosts alongside code 31, and Swift/Kotlin bindings preserve it. A host debugging a signer-side key-repair scenario will see a message falsely claiming the address has no signable account, misdirecting troubleshooting toward address correction instead of key/mnemonic restoration.

@QuantumExplorer
QuantumExplorer merged commit 316ee7a into v4.2-dev Aug 6, 2026
21 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/nostalgic-blackburn-ad5ec9 branch August 6, 2026 18:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants