Skip to content

fix(nonce-reuse): verify recovered keys against pubkey and handle mixed s-normalization#18

Merged
oritwoen merged 1 commit intomainfrom
fix/nonce-reuse-verify-and-negated-s
Mar 7, 2026
Merged

fix(nonce-reuse): verify recovered keys against pubkey and handle mixed s-normalization#18
oritwoen merged 1 commit intomainfrom
fix/nonce-reuse-verify-and-negated-s

Conversation

@aeitwoen
Copy link
Collaborator

@aeitwoen aeitwoen commented Mar 6, 2026

Summary

  • When pubkey is available, verify recovered private key (d*G == pubkey) instead of returning unverified results
  • Try both s₂ polarities (original and negated) to handle signatures with mixed BIP62 low-s/high-s normalization
  • Without pubkey, behavior is unchanged (backward compatible)

Problem

The nonce-reuse attack previously returned the first mathematically valid private key without verifying it against the known public key. This caused two issues:

  1. No verification: A recovered key could be algebraically correct but wrong (e.g., if input data has errors)
  2. Mixed s-normalization: When one signature's s was BIP62-canonicalized (low-s) and the other wasn't, the standard formula k = (z₁-z₂)/(s₁-s₂) yields the wrong nonce, and recovery silently fails

Solution

When pubkey is present:

  • Compute d*G and compare against the provided pubkey (compressed and uncompressed)
  • Try both s₂ and -s₂, verifying each candidate — only return the key that matches
  • This follows the same pattern used by biased_nonce::verify_candidate and polynonce::verify_key

When pubkey is absent:

  • Original unverified formula, no behavior change

Tests

  • test_nonce_reuse_recovery_with_pubkey_verification — EC-consistent signatures with known d, verifies correct key recovery
  • test_nonce_reuse_recovery_negated_s_with_pubkey — Same setup but with s₂ negated, proves s-polarity handling works
  • All 73 existing tests pass, clippy clean

…ed s-normalization

When pubkey is available, verify recovered private key via d*G == pubkey
and try both s polarities to handle mixed BIP62 low-s/high-s signatures.
Without pubkey, behavior is unchanged (backward compatible).
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/attack/nonce_reuse.rs">

<violation number="1" location="src/attack/nonce_reuse.rs:94">
P2: Pubkey verification can falsely reject valid recovered keys because `0x` prefixes are not normalized before comparison.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant NRA as NonceReuseAttack
    participant Math as math.rs (Recover logic)
    participant K256 as k256 (Elliptic Curve)

    Note over NRA,K256: try_recover_pair flow

    alt Pubkey is Present
        loop NEW: For s2 and -s2 (BIP62 Normalization)
            NRA->>Math: recover_nonce(z1, z2, s1, current_s2)
            Math-->>NRA: k (nonce)
            
            NRA->>Math: recover_private_key(r1, s1, z1, k)
            Math-->>NRA: d (private key)
            
            NRA->>K256: NEW: verify_key_matches_pubkey(d, expected_pubkey)
            K256->>K256: Calculate ProjectivePoint (d * G)
            K256->>K256: Convert to AffinePoint
            K256-->>NRA: True if hex(compressed/uncompressed) matches
            
            opt Match found
                Note over NRA: Return verified RecoveredKey
            end
        end
    else Pubkey is Missing (Legacy Path)
        NRA->>Math: recover_nonce(z1, z2, s1, s2)
        Math-->>NRA: k (nonce)
        NRA->>Math: recover_private_key(r1, s1, z1, k)
        Math-->>NRA: d (private key)
        Note over NRA: Return unverified RecoveredKey
    end
Loading

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@oritwoen oritwoen merged commit f504f05 into main Mar 7, 2026
4 checks passed
@oritwoen oritwoen deleted the fix/nonce-reuse-verify-and-negated-s branch March 7, 2026 12:56
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