fix(client): grind ECDSA descriptor BIP-322 proofs to 71/72 bytes#9
Merged
Merged
Conversation
Root-causes the intermittent mixed_script_e2e flake (also a real product bug). The pinned bip322 = "=0.0.10" verifier hardcodes a 71/72-byte ECDSA witness signature length check and rejects valid 70/73-byte signatures. The WIF client path already grinds signatures to 71/72 via shared::bip322::sign_simple, but the descriptor path (generate / from_descriptor) signed BIP-322 proofs via bdk, which does no grinding. bdk's ECDSA is RFC-6979 deterministic, so ~5% of (descriptor key, round_id) combinations produced a 70/73-byte signature the verifier rejects — surfacing as register_input → 400 INVALID_PROOF for any P2SH-P2WPKH or P2WPKH descriptor-wallet client ~5%+ of the time. P2TR is immune (fixed-length Schnorr). Fix: derive the External index-0 leaf secret key from the descriptor xprv at construction (derive_external_leaf_sk) and store it (external_leaf_sk) only when it controls the registered UTXO (ecdsa_leaf_controls_script); sign_bip322 then routes P2WPKH/P2SH-P2WPKH proofs through sign_simple (grinded). Non-index-0 from_descriptor UTXOs and P2TR fall back to the bdk path. For in-range signatures sign_simple is byte-identical to bdk (existing *_matches_bdk_sign tests), so only the previously-rejected ~5% changes. Verification: new descriptor_ecdsa_proofs_grind_to_verifiable_length test (64 seeds, asserts 71/72 + crate-verifies; confirmed to fail when the fix is disabled); mixed_script_e2e reproduced locally then run 12x green with the fix (former rate ~1/3). Client lib suite 39 passing, workspace clippy -D warnings clean. Learning written to docs/solutions/. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
mixed_script_e2eintermittently failed at input registration (~1-in-3 CI runs), and it turned out to be a real product bug, not a test artifact: any P2SH-P2WPKH or P2WPKH descriptor-wallet client had a ~5%+ chance per round of having its BIP-322 ownership proof silently rejected (register_input→ 400INVALID_PROOF/ "BIP-322 crate verification failed").Root cause
The pinned
bip322 = "=0.0.10"verifier hardcodes amatch signature_length { 71 | 72 }check and rejects valid 70/73-byte ECDSA DER signatures. The WIF client path already grinds signatures to 71/72 viashared::bip322::sign_simple(sign_ecdsa_compat_bip322_length). The descriptor path (generate/from_descriptor) signed proofs via bdk, which does no grinding — and bdk's RFC-6979-deterministic signer produces a 70/73-byte signature for ~5%+ of (key, message) combinations. Because the test generates a fresh key + random round_id each run, ~5%+ of runs hit a bad combination. P2TR is immune (fixed-length Schnorr).This is fundamentally an upstream bug in the
bip322crate's verifier; this PR is the client-side workaround.Fix
Route ECDSA descriptor proofs through the grinding-aware
sign_simple:derive_external_leaf_sk), stored asexternal_leaf_skonly when it controls the registered UTXO (ecdsa_leaf_controls_script).sign_bip322uses it for P2WPKH / P2SH-P2WPKH; P2TR and non-index-0from_descriptorUTXOs fall back to bdk.sign_simpleis byte-identical to bdk (guarded by the existing*_matches_bdk_sign_byte_for_bytetests), so only the previously-rejected ~5% changes.Verification
descriptor_ecdsa_proofs_grind_to_verifiable_length(64 fixed seeds; asserts 71/72 bytes + crate-verifies). Confirmed to fail when the fix is disabled.mixed_script_e2ereproduced locally, then run 12× green with the fix (former rate ~1/3).clippy -D warningsclean.Docs
Root cause + detection notes:
docs/solutions/bip322-ecdsa-signature-length-flake.md.🤖 Generated with Claude Code