Repository navigation
fix(identity): choose signing keys Platform accepts on protocol 14 - #1117
Claudius-Maginificent wants to merge 74 commits into
Conversation
Pin dash-sdk, rs-sdk-trusted-context-provider, platform-wallet and platform-wallet-storage to dashpay/platform v5.1-dev 063589bf (5.0.0-rc.8). The lockfile moves only the Platform packages. The upstream V019 wallet-storage migration adds a nullable asset-lock operation id; V001-V018 are unchanged. The compatibility bridge gets the 033ecae destination snapshot it compares against, and the pin guard records the new revision with an unchanged migration fingerprint. BREAKING CHANGE: the wallet storage upgrade to V019 is forward only. An older build cannot open upgraded storage; going back needs a copy of the data directory taken before the upgrade. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
The new rust-dashcore wraps secp256k1 keys in EcdsaSecretKey and EcdsaPublicKey, returns secret bytes as Zeroizing, and drops the Secp256k1 context argument. Signers, single-key wallets, DashPay derivation, address and QR handling, migrations and their tests follow the new types. Signing stays on the non-grinding dpp::secp256k1 path, and a parity test pins the BLS signature bytes the previous SDK made. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
Handle the dpp and SDK types the new pin adds, keeping today's behaviour: - Document replace keeps moderated_at and moderated_by; new documents start without them. - The document editor refuses typed-array fields instead of dropping their contents, and keeps referenced key ids as numbers. - Shielded token events show read-only in group actions, and the shielded pool token settings are refused before anything is signed. - Contested document creation states the protocol's base contest fund, so the SDK never auto-quotes a larger one. - Masternode vote reads use ResourceVoteWithCount, the SDK's new vote type; the vote count is not used yet. - New query fields, wallet events, wallet error variants and asset-lock funding modes are handled where the SDK now requires them. - Entropy-only document ids are documented as placeholders from protocol 14, and deferred upstream features link to their issues. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
The SDK was pinned to the protocol 13 table, so it could not build any protocol 14 transition or follow the versioned identity lookup. It now starts at upstream's per-network minimum and ratchets to what proven responses report. - System contracts are cached per protocol version and served through accessors. The context provider serves them at the version the proof verifier asks for and fails rather than serve another version's. - A proved epoch sets the fee multiplier and raises the protocol version. An older epoch than the adopted one is ignored. - Rebuilding the connection resets the protocol version, fee multiplier and adopted epoch under one lock before the new SDK is published, and drops reads begun on the old connection. - Capabilities read the network's version through a helper that stays closed for a version this build does not define. - Contest timing reads the live version; retired vote schedules keep the protocol 13 notice window they were created under. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
Compare the protocol 13 client tables with a recorded snapshot, so an upstream change to them is reviewed, and check that keys and documents DET builds on a protocol 13 network use encodings protocol 13 decodes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
Map the refusals the new protocol adds to flows DET already offers to dedicated TaskError variants: a full username contest, a vote choice the contest does not allow, a name someone else asked for first, a token that cannot be moved, a changed immutable document field, a reference to a document type that cannot be deleted, and a moderators' fee on a contract without moderation. Names supplied by the node stay in the typed source only, never in the message. Token operations that fail inside the wallet runtime keep their SDK cause, so they are classified like any SDK error; an unclassified one names the operation that did not complete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
Add a dated design note for the pin, the protocol version sources, the connection state rules and the protocol 13 guards, and point AGENTS.md at From<SdkError> for TaskError as the SDK error reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
Automatic key choice keeps only keys that are live (not disabled, not expired), held on this device, in scope for the action's contract or document type, and at a security level the action accepts. Among those it prefers a key without limits, and a document type takes a stronger key than the one it requires when that is all the identity holds. Keys are judged as the identity holds them now: a key whose limits were raised on the network is still the same key, so its stored private half stays matched to it, while a key that gains or loses a limit is not. An explicitly chosen DPNS key may have expired; the chooser warns and Platform decides by network time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
Map the refusals Platform gives for a signing key that expired, used up its spending limit, cannot pay for the action, or is bound to another contract or to batch actions only, to dedicated TaskError variants with a message that says what to do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
…t that holds it Every screen and backend task that picks a signing key now asks for the action's scope: the contract or document type for document and token actions, none for identity and contract transitions. DashPay documents use the levels each document type accepts. The manual key chooser leaves out keys bound to another contract or document type, keeps expired and limited keys with a warning, and judges the key the identity holds now. A document action with no usable key says why the form stops. The wallet to unlock, and the owner of an identity stored without a wallet link, follow the key that is actually used. Lookups that only point the user at a key, such as Check Owner Key, do not require the private half on this device. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
|
⛔ Final review complete — 1 blocking finding(s) (commit 1e47ac2) · triage: critical · Phase 2 only (no Phase 1 for this repository) |
…it refusals Protocol version 14 refusals that flows already in DET can hit get dedicated, actionable messages, each keeping the SDK error as its source: - an identity banned or suspended (until when) on a moderated contract; - a gas sponsor short of credits, a gas payer the document type does not offer, and mixed gas payers in one batch; - pre-programmed token distributions of one time over the limit. An overflowing shielded note total is logged at warn: it means corrupt or hostile sync data, not routine noise. Tests cover contracts saved at protocol 12 loading unchanged at 13 and 14, and contracts saved at 14 decoding at 13 and in full again at 14. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
…efusals from the Platform bump Merges 74695e7 from chore/bump-platform-063589bf. The conflict in src/backend_task/error.rs keeps both sides: the signing key refusals and the moderation and gas sponsorship refusals each keep their own section, match arms and test cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
A lookup at another version, as the proof verifier makes at a response's version, swapped out the app's set, so each such lookup reloaded the system contracts and the next app read reloaded them again. Each version now keeps its own set; the app's reads keep the set they last got for a failed reload. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
…poch When the epoch cannot be read, the proved DPNS fallback confirmed the SDK's version, which starts at the network's upstream minimum (14 on devnet), as the network's. It now confirms the version of the responses the SDK verified, and nothing before any. Restores the completed-withdrawal status list with the reason sending FAILED to a protocol 13 network is safe, and its test, now comparing the protocol 13 schema with the latest one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
The derived key DET signs with is copied into secp256k1's own type, which keeps its bytes after use. Wrap it so it is erased on drop, as the derived key it comes from would be; signatures stay byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
… protocol 13 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
The new refusals sent users to another document editor or token client. They now say the app cannot edit this field type, sign this action or change this token setting yet, in one wording, and the shielded setting refusal sits with the token contract errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
Also corrects the platform compatibility fixtures README: no code reads e3cd7cf.sql any more; it is the V001-V018 checksum reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
…ey branch Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
The username screen offered the unlock, and gated paying, on the wallet of the automatically picked key, although a key chosen under Advanced signs the registration. The wallet now follows the key the registration is signed with, and is picked again when that key changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
The document action and DashPay selectors offered every accepted level at once, so a CRITICAL key could sign where a key at the required level was held. Every document key is now chosen like the username key: each accepted level from the weakest up, unlimited keys first within a level. Also puts the scope and wallet doc comments of the document action screen back on their functions, and builds its test keys and identities from the shared test helpers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
Each key chooser entry with a caveat is one complete template instead of a label plus a concatenated tag. The spending limit warning no longer points at a remaining budget the key details do not show, and IDN-024 names the key chooser, not the key list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
|
Re latest review-body findings: Avoid a wallet error while waiting for a manual token key (FIXED): Fixed in 39d41fd. The token action, transfer, purchase, pricing and config constructors now look up the unlock wallet only when a key was picked ( 🤖 Co-authored by Claudius the Magnificent AI Agent |
|
Waiting for bot review — coderabbitai not yet · thepastaclaw not yet. Wait for the missing reviews, or a writer can post |
thepastaclaw
left a comment
There was a problem hiding this comment.
Re-review — Final validation — Phase 2 only (no Phase 1 for this repository)
One verified automatic-selection mismatch remains: profile writes can select an unavailable key even though the profile screen selects an obtainable alternative. The 36 prior findings reconcile to 30 fixes, five tracked deferrals, and one withdrawal. Verification was static-only against head 39d41fd; current CI shows Clippy passing and Test Suite pending.
🔴 1 blocking
Review provenance
Source: reviewer 1: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 3: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 4: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)
- Triage:
criticalbygpt-6.1-sol(effort low) — This large, intricate change directly alters signing-key eligibility and selection in src/model/identity_key_usability.rs and src/model/qualified_identity/mod.rs, plus private-key availability and wallet-unlock handling across transaction flows. - Phase 1 reviewers: not run (disabled for this repository)
- Fresh verifier:
gpt-6.1-sol— final-verifier; agentsol-verifier - Phase 2 reviewers:
gpt-6.1-sol— general (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— rust-quality (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— general (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— rust-quality (completed, effort xhigh); agentphase2-reviewer - Model comparison: every Phase-2 reviewer also ran on
gpt-6-astra; the verifier weighed both sets without knowing which model wrote which
🤖 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 `src/ui/dashpay/profile_screen.rs`:
- [BLOCKING] src/ui/dashpay/profile_screen.rs:141: Use the same availability policy in the upstream profile signer
The new wallet lookup uses `dashpay_signing_key`, which filters candidates through `holds_obtainable_key`, but the actual profile write uses a different availability policy. `WalletBackend::dashpay_write_profile` passes the plain `QualifiedIdentity` to both upstream profile writers; platform-wallet's `profile_signing_key` selects through `Signer::can_sign_with`, whose implementation accepts any matching stored placement. With a lower-ID eligible HIGH ECDSA key stored only as `PrivateKeyData::Encrypted` and a later obtainable HIGH ECDSA key, the screen selects the latter, while the writer selects the former and fails during private-key resolution instead of using the available key. The existing `ensure_profile_signing_key` preflight also uses the weaker predicate and does not prevent this. Supply an external signer whose availability predicate uses `holds_obtainable_key` while delegating signing to the existing secret-access path, leaving selection in platform-wallet, and add a two-key regression covering profile creation and update. This is a missed automatic-selection sibling, not the manual-wallet synchronization tracked in #1137; deferring it requires a linked follow-up covering this specific writer path.
Dismissed by maintainer decision: the remaining blocker (DashPay profile writer's signer availability policy) predates this PR on v1.0-dev and is moved to #1134's scope — see #1117 (comment)
|
@coderabbitai review No review for |
|
@thepastaclaw review No review for |
|
Ready for review — files with no dedicated owner: lklimek · re-review or resolve: lklimek. |
|
@thepastaclaw review |
Conflict in src/ui/identity/register_dpns_name_screen.rs (imports only): kept the base's username_name_admission_reserve_credits import and this branch's SigningScope import. The auto-merged admission-reserve and raised-contest-fee lines sit next to this branch's key-chooser tracking. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Waiting for bot review — coderabbitai not yet · thepastaclaw requested changes — dismiss the review or push a fix. Wait for the missing reviews, or a writer can post |
thepastaclaw
left a comment
There was a problem hiding this comment.
Re-review — Final validation — Phase 2 only (no Phase 1 for this repository)
Verified head 0044d4d against the source and pinned Platform implementation: 30 prior findings are fixed, six have tracked deferrals, and one remains withdrawn. One in-scope blocker remains in token signing-key reconciliation after identity refresh; the DPNS contract-group finding depends on membership that the pinned implementation cannot establish for DPNS. This was a static review with no builds or tests run locally; current-head Clippy passes and Test Suite remains pending.
🔴 1 blocking
1 finding(s) not shown inline (the lines are not part of this PR's diff)
🔴 Blocking: Reconcile automatic key selection when a token identity refreshes
src/ui/tokens/token_action_screen.rs:460-467
The stricter constructor leaves common.selected_key and common.selected_wallet unset when no key is currently usable, but refresh() updates only the identity. When that identity gains a usable candidate, the readiness gate recomputes Usable and opens the ordinary form without selecting the key that submission requires. For example, open the form with an expired CRITICAL key, extend its expiry on-chain, and fetch the identity again: RefreshIdentity persists the updated identity and publishes an Identities cache update, which calls this refresh method, but confirmation still fails with "No signing key selected". Before this change, the constructor retained that public key, so the expiry-extension case did not lose the selection. Adding a CRITICAL key through this form's Add key shortcut and returning through PopScreenAndRefresh also leaves the newly usable form without a selected key. Transfer, claim, purchase, and pricing forms have the same identity-only refresh pattern. Reconcile automatic selection and its unlock wallet when identity data changes, while preserving a deliberate manual choice, and add a regression covering unavailable candidate → usable candidate → refresh → submit. This is separate from the deferred synchronization after a manual chooser change in #1137.
source: gpt-6.1-sol (phase2-reviewer: general)
Review provenance
Source: reviewer 1: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 3: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 4: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)
- Triage:
criticalbygpt-6.1-sol(effort low) — This large, cross-cutting change directly alters signature key eligibility and selection in src/model/identity_key_usability.rs and private-key availability and signing behavior in src/model/qualified_identity/mod.rs, meeting both the intricacy and critical key-handling surface requirements. - Phase 1 reviewers: not run (disabled for this repository)
- Fresh verifier:
gpt-6.1-sol— final-verifier; agentsol-verifier - Phase 2 reviewers:
gpt-6.1-sol— general (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— rust-quality (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— general (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— rust-quality (completed, effort xhigh); agentphase2-reviewer - Model comparison: every Phase-2 reviewer also ran on
gpt-6-astra; the verifier weighed both sets without knowing which model wrote which
🤖 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 `src/ui/tokens/token_action_screen.rs`:
- [BLOCKING] src/ui/tokens/token_action_screen.rs:460-467: Reconcile automatic key selection when a token identity refreshes
The stricter constructor leaves `common.selected_key` and `common.selected_wallet` unset when no key is currently usable, but `refresh()` updates only the identity. When that identity gains a usable candidate, the readiness gate recomputes `Usable` and opens the ordinary form without selecting the key that submission requires. For example, open the form with an expired CRITICAL key, extend its expiry on-chain, and fetch the identity again: `RefreshIdentity` persists the updated identity and publishes an Identities cache update, which calls this refresh method, but confirmation still fails with "No signing key selected". Before this change, the constructor retained that public key, so the expiry-extension case did not lose the selection. Adding a CRITICAL key through this form's Add key shortcut and returning through `PopScreenAndRefresh` also leaves the newly usable form without a selected key. Transfer, claim, purchase, and pricing forms have the same identity-only refresh pattern. Reconcile automatic selection and its unlock wallet when identity data changes, while preserving a deliberate manual choice, and add a regression covering unavailable candidate → usable candidate → refresh → submit. This is separate from the deferred synchronization after a manual chooser change in #1137.
The token action, transfer, claim, purchase and pricing forms picked their signing key only when they opened, and refresh() replaced only the identity. Once the identity's key became usable (expiry extended, a key added through Add key, or its wallet loaded) the gate opened the form with no key, and confirming failed with "No signing key selected". refresh() now picks the automatic key and its unlock wallet while none is chosen, keeping a key chosen by hand. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Fixed in 1e47ac2. The token action, transfer, claim, purchase and pricing forms now call 🤖 Co-authored by Claudius the Magnificent AI Agent |
|
Waiting for bot review — coderabbitai not yet · thepastaclaw requested changes — dismiss the review or push a fix, 1 thread unresolved — resolve it. Wait for the missing reviews, or a writer can post |
thepastaclaw
left a comment
There was a problem hiding this comment.
Re-review — Final validation — Phase 2 only (no Phase 1 for this repository)
Static verification of head 1e47ac2 confirms the token-refresh correction and reconciles all 38 prior findings: 31 fixed, six intentionally deferred with tracking, and one withdrawn. One blocking cross-purpose key-ordering defect remains, alongside two non-blocking suggestions concerning document-create scope and QR integration coverage. Current CI reports Test Suite, Clippy, and Platform pin guard passing; PR Hygiene remains pending, and no local builds or tests were run.
🔴 1 blocking | 🟡 2 suggestion(s)
Review provenance
Source: reviewer 1: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)
- Triage:
criticalbygpt-6.1-sol(effort low) — The large, cross-cutting diff directly changes signature authorization and private-key availability and selection in src/model/identity_key_usability.rs and src/model/qualified_identity/mod.rs, alongside encrypted key handling in src/model/qualified_identity/encrypted_key_storage.rs. - Phase 1 reviewers: not run (disabled for this repository)
- Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
- Fresh verifier:
gpt-6.1-sol— final-verifier; agentsol-verifier - Phase 2 reviewers:
gpt-6.1-sol— general (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— rust-quality (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— general (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— rust-quality (completed, effort xhigh); agentphase2-reviewer
🤖 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 `src/model/qualified_identity/mod.rs`:
- [BLOCKING] src/model/qualified_identity/mod.rs:1337-1344: Prefer unlimited keys across both accepted token-signing purposes
The outer find_map completes AUTHENTICATION selection before considering TRANSFER, so unlimited-first ordering applies only within each purpose. An obtainable, budgeted CRITICAL authentication key therefore wins over an obtainable, unlimited CRITICAL transfer key. This unnecessarily spends the delegated budget; when that budget is exhausted, transfer and claim submissions fail despite an available unrestricted signing key. The pinned DPP BatchTransitionV1 accepts either purpose for a single token transfer or claim, and IDN-024 explicitly prefers an unlimited held key over a limited one. Rank eligible candidates across both purposes by limits first, retaining authentication preference within the same limits class. Add a regression with a limited authentication key and an unlimited transfer key.
In `src/ui/contracts_documents/document_action_screen.rs`:
- [SUGGESTION] src/ui/contracts_documents/document_action_screen.rs:2132-2139: Include create-consumption bounds in the upstream-selector follow-up
This scope covers only the named document type, including for creates. The pinned drive-abci advanced-structure validator also checks every consumable_document_type_names() entry: a key bound to the created type cannot authorize consumption of another type. An earlier unlimited key bound only to the created type can consequently be selected ahead of an unrestricted key, and Platform rejects the create through its paid out-of-bounds path. The owning create-aware validation is being exposed in dashpay/platform#5588 and consolidated in dashpay/platform#5660. Include this caller explicitly in dashpay/dash-evo-tool#1133: its current patch still supplies DocumentType rather than DocumentCreate. Keep the rule upstream instead of adding another DET-side copy, and cover a create that consumes another document type.
In `src/ui/dashpay/qr_code_generator.rs`:
- [SUGGESTION] src/ui/dashpay/qr_code_generator.rs:474-480: Exercise QR generation through the production secret-session path
The two-wallet regression verifies the ownership lookup and unlock-wallet helper but never generates a QR code. The separate interoperability tests construct AutoAcceptKeyExport from a supplied seed and assemble the URI directly, so neither test exercises GenerateAutoAcceptQrCode through dashpay_build_auto_accept_qr, SecretScope::HdSeed, and the upstream builder. Incorrect wallet or secret-scope wiring in that production chain could therefore leave all these tests green. Add an offline async regression with a wired context, two runtime-generated wallets, and a cached username. Dispatch the production task, parse its returned URI, sign a proof, and verify that it succeeds against the linked owner's wallet and fails against the unrelated wallet.
| SINGLE_TOKEN_TRANSITION_PURPOSES | ||
| .into_iter() | ||
| .find_map(|purpose| { | ||
| self.signing_key_now( | ||
| KeyRequirements::new(purpose, &[SecurityLevel::CRITICAL], scope) | ||
| .with_key_types(PLATFORM_SIGNING_KEY_TYPES), | ||
| ) | ||
| }) |
There was a problem hiding this comment.
🔴 Blocking: Prefer unlimited keys across both accepted token-signing purposes
The outer find_map completes AUTHENTICATION selection before considering TRANSFER, so unlimited-first ordering applies only within each purpose. An obtainable, budgeted CRITICAL authentication key therefore wins over an obtainable, unlimited CRITICAL transfer key. This unnecessarily spends the delegated budget; when that budget is exhausted, transfer and claim submissions fail despite an available unrestricted signing key. The pinned DPP BatchTransitionV1 accepts either purpose for a single token transfer or claim, and IDN-024 explicitly prefers an unlimited held key over a limited one. Rank eligible candidates across both purposes by limits first, retaining authentication preference within the same limits class. Add a regression with a limited authentication key and an unlimited transfer key.
source: gpt-6.1-sol (phase2-reviewer: general)
| fn signing_scope<'a>( | ||
| contract: Option<&QualifiedContract>, | ||
| document_type: Option<&'a DocumentType>, | ||
| ) -> SigningScope<'a> { | ||
| match (contract, document_type) { | ||
| (Some(contract), Some(document_type)) => SigningScope::Document { | ||
| contract_id: contract.contract.id(), | ||
| document_type_name: document_type.name().as_str(), |
There was a problem hiding this comment.
🟡 Suggestion: Include create-consumption bounds in the upstream-selector follow-up
This scope covers only the named document type, including for creates. The pinned drive-abci advanced-structure validator also checks every consumable_document_type_names() entry: a key bound to the created type cannot authorize consumption of another type. An earlier unlimited key bound only to the created type can consequently be selected ahead of an unrestricted key, and Platform rejects the create through its paid out-of-bounds path. The owning create-aware validation is being exposed in dashpay/platform#5588 and consolidated in dashpay/platform#5660. Include this caller explicitly in #1133: its current patch still supplies DocumentType rather than DocumentCreate. Keep the rule upstream instead of adding another DET-side copy, and cover a create that consumes another document type.
source: gpt-6.1-sol (phase2-reviewer: general)
| assert_ne!(identity.dashpay_wallet_seed_hash(), Some(owner.0)); | ||
| assert_eq!( | ||
| ctx.contact_qr_wallet_seed_hash(&identity).ok(), | ||
| Some(owner.0) | ||
| ); | ||
| let unlock = qr_wallet(&ctx, &identity).expect("the owner's wallet"); | ||
| assert_eq!(unlock.read().expect("wallet").seed_hash(), owner.0); |
There was a problem hiding this comment.
🟡 Suggestion: Exercise QR generation through the production secret-session path
The two-wallet regression verifies the ownership lookup and unlock-wallet helper but never generates a QR code. The separate interoperability tests construct AutoAcceptKeyExport from a supplied seed and assemble the URI directly, so neither test exercises GenerateAutoAcceptQrCode through dashpay_build_auto_accept_qr, SecretScope::HdSeed, and the upstream builder. Incorrect wallet or secret-scope wiring in that production chain could therefore leave all these tests green. Add an offline async regression with a wired context, two runtime-generated wallets, and a cached username. Dispatch the production task, parse its returned URI, sign a proof, and verify that it succeeds against the linked owner's wallet and fails against the unrelated wallet.
source: gpt-6.1-sol (phase2-reviewer: rust-quality)
TL;DR: The app now signs each action with a key the network will accept. It skips expired keys and keys bound to another contract, prefers keys without limits in the same order as Platform's own wallet library, and unlocks the wallet that actually holds the chosen key. This also changes key choice on protocol 13 networks, as a behaviour fix. DashPay contact QR codes now use the standard DashPay format through Platform's wallet library, so other DashPay wallets can read them.
User story
As a user, I want the app to sign with a key that can actually sign the action, so that my actions are not refused because of an expired, used-up or out-of-scope key (IDN-024).
Scenario
An identity holds an expired HIGH key, a HIGH key bound to DashPay, and a CRITICAL key without limits. The user deletes a document of another contract.
If no key on the device can sign the action, the form now says so instead of stopping silently.
Detailed discussion
Behaviour change on protocol 13 networks
This changes key choice on every network, including mainnet and testnet on protocol 13. It is a behaviour fix, not a protocol 14 feature:
ECDSA_SECP256K1orECDSA_HASH160), as upstream platform-wallet's DashPay writes require.On protocol 13 no key carries limits or an expiry, so the limit and expiry rules have no effect there.
What the rules are
Automatic choice keeps only keys that are:
Among those it follows upstream platform-wallet's
usable_authentication_keyorder: every key without limits, at any accepted level, comes before any key with a budget or an expiry, then key id order. So a delegated key's budget is not spent while an unlimited key is held. Every automatic choice, for DashPay contact requests, usernames, document, token, contract and credit actions, uses this order. DashPay contact details (contactInfo) still pick their key the old way (the first matching key of any type); fix(dashpay): save contact details through platform-wallet (DIP-15) #1102 moves them to platform-wallet's writer, which chooses the key itself. The key is picked again when the contract or document type changes.Key types: every automatic choice and every key chooser takes only key types Platform verifies (
ECDSA_SECP256K1,ECDSA_HASH160,BLS12_381), neverEDDSA_25519_HASH160orBIP13_SCRIPT_HASH. When the identity holds a key that cannot sign here (disabled, expired, bound to another token or app, or not on this device), the token transfer, claim, credit transfer and withdrawal forms say so instead of opening with a disabled button.Username registration: the key levels now satisfy both the preorder and the domain document.
The manual chooser leaves out keys bound to another contract or document type. It keeps expired and limited keys, with a warning, because the user may know better. A DPNS key the user chose explicitly may have expired; Platform decides by network time.
When the only key that can sign a token action is bound to a contract group, the form opens its key chooser with a note, so the user can choose that key if the group includes the token's contract.
Keys as held now: keys are judged as the identity holds them now.
Scope per screen: every caller passes the action's scope.
DashPay contact QR codes
Before, the app derived the QR code's auto-accept key from an identity encryption key used as a wallet seed, and wrote its own
dash:?di=<identity>format, so no other DashPay wallet could use its codes or verify its requests. Contact QR codes now go through upstream platform-wallet:DashPayView::build_auto_accept_qrbuilds it. The key is derived from the identity's wallet seed, which is read through the secret seam for one session only. The app's crypto provider exports only an auto-accept key path on the current network and refuses every other request.parse_dashpay_contact_urianddecode_auto_accept_key_blobread it. An expired code is refused before anything is sent. The contact is found from the code's username, and upstream'ssign_auto_accept_proofsigns the request's proof over the sender, that contact and the request's account reference.di=format are refused.Refusals
The refusals Platform gives for a signing key get dedicated messages that say what to do:
Upstream reference
Where DET and Platform's crates implement the same rule, upstream is the reference.
usable_authentication_key,first_available,allowed_signing_security_levelsandresolve_authentication_signing_keyare crate-private, and dpp's public bounds check needs a built transition). Each place carries aTODO(upstream)marker naming what upstream must expose. A follow-up PR, blocked by feat(platform-wallet)!: expose protocol-14-aware signing-key selection platform#5588, replaces them.Scope
drain_auto_accepts_verified), and sending and accepting contact requests through platform-wallet. Both need a full DashPay crypto provider.Known limitations (by design)
QualifiedIdentity::can_sign_with, which accepts any filed key placement. If a profile-capable key this device cannot sign with has a lower id than a usable one, saving the profile fails before broadcast (no fee). Follow-up tracked in refactor(dashpay)!: sync DashPay through platform-wallet and show it from shared snapshots #1134: a profile signer that also requiresholds_obtainable_key.ECDSA_SECP256K1,ECDSA_HASH160), for parity with upstream platform-wallet's DPNS writes. Platform would accept a BLS key, so an identity whose only registration-capable key is a BLS key cannot register a username from DET.Testing
dashpay,qualified_identity,identity_key_usability,wallet_backend::auto_accept,document_action_screen,dpns: 794 passed). DashPay UI tests (kittest) pass (11).cargo clippy --all-features --all-targets -- -D warningsandcargo fmt --all -- --checkpass.🤖 Co-authored by Claudius the Magnificent AI Agent
🤖 Generated with Claude Code
https://claude.ai/code/session_012ujJpB9DBhvtbjhYB3aG9Q
PR Hygiene ·
1e47ac2docs/user-stories.md,src/backend_task/dashpay.rs,src/backend_task/dashpay/auto_accept_handler.rsand 54 more) — lklimekWhen every merge requirement is met, the
PR Hygienecheck passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.Summary by CodeRabbit