-
@ Cryptape
2025-04-28 08:33:05Author: Wanbiao Ye, CKB Core Developer Nervos Network
Secp256k1 and ECDSA aren't just common—they're the backbone of digital signatures in today’s leading blockchains. From Bitcoin (see Bitcoin Wiki) to Ethereum (see Yellow Paper, Appendix E. Precompiled Contract) to CKB (See this RFC), these algorithms are the default choice. They’re what let you prove ownership with your private key — your on-chain assets belong to you, and only you.
Elliptic curves are favored in cryptography because they provide high security with shorter keys. But are they truly secure and perfect? However, the U.S. National Institute of Standards and Technology (NIST) recently indicated concerns about security risks associated with secp256k1 and does not currently endorse its use. Instead, it suggests an alternative curve: secp256r1 (see Recommendations for Discrete Logarithm-based Cryptography, p1). Meanwhile, Bitcoin is evolving—introducing Schnorr signatures in 2021 to replace ECDSA.
The core issue is ECDSA itself—it is highly vulnerable and has caused serious incidents. In this post, we’ll revisit the key attacks, reproduce them, and explain how CKB's segregated witness design and support for upgradable cryptographic algorithms help mitigate these risks. I’ll walk you through a historical overview and attempt to recreate some of the most infamous attacks: nonce reuse attack, invalid curve attack, transaction malleability attacks, and side-channel attacks. Finally, I’ll highlight how CKB addresses these issues through Segregated Witness and support for upgradable cryptographic algorithms—by excluding ECDSA signatures from the transaction hash and enabling algorithm upgrades, so that developers can adopt solutions that best suit their needs without compromising security.
This article uses the following notation:
|
m
| Message | 256-bit integer | | --- | --- | --- | |r
| Part of the signature | 256-bit integer | |s
| Part of the signature | 256-bit integer | |k
| Nonce used during signing | 256-bit integer | |g
| Generator point on the elliptic curve representing (x, y) coordinates | two 256-bit integers |Nonce Reuse Attack
Due to Bitcoin, secp256k1 and ECDSA became widely known. But they weren’t obscure before. For example, Sony used to mark its PlayStation firmware as valid and unmodified using a private key stored at the company's headquarters. PlayStation 3 only requires a public key to verify that the signature is from Sony. But unfortunately, Sony was hacked due to their poor code implementation, meaning that any future system updates they release could be easily decrypted.
At fail0verflow, hackers demonstrated part of Sony’s ECDSA code and discovered that Sony kept the value of the random number always at 4, which caused the random private key
k
in the ECDSA signature process to always have the same value. ECDSA requiresk
to be randomly generated every time. Reusing it leaks the private key.python def get_random_number(): # Chosen by fair dice roll. Guaranteed to be random. return 4
Given:
-
Message
m₁
and signature(r₁, s₁)
-
Message
m₂
and signature(r₂, s₂)
-
Both
m₁
andm₂
are signed with the same unknownk
Then:
```plaintext s₁ = (m₁ + prikey * r₁) / k s₂ = (m₂ + prikey * r₁) / k s₁ / s₂ = (m₁ + prikey * r₁) / (m₂ + prikey * r₁) prikey = (s₁ * m₂ - s₂ * m₁) / (s₂ - s₁) / r₁
```
A real-world example to help you better understand how a private key can be recovered from two signatures that use the same random number
k
:```python import pabtc
m1 = pabtc.secp256k1.Fr(0x72a963cdfb01bc37cd283106875ff1f07f02bc9ad6121b75c3d17629df128d4e) r1 = pabtc.secp256k1.Fr(0x741a1cc1db8aa02cff2e695905ed866e4e1f1e19b10e2b448bf01d4ef3cbd8ed) s1 = pabtc.secp256k1.Fr(0x2222017d7d4b9886a19fe8da9234032e5e8dc5b5b1f27517b03ac8e1dd573c78)
m2 = pabtc.secp256k1.Fr(0x059aa1e67abe518ea1e09587f828264119e3cdae0b8fcaedb542d8c287c3d420) r2 = pabtc.secp256k1.Fr(0x741a1cc1db8aa02cff2e695905ed866e4e1f1e19b10e2b448bf01d4ef3cbd8ed) s2 = pabtc.secp256k1.Fr(0x5c907cdd9ac36fdaf4af60e2ccfb1469c7281c30eb219eca3eddf1f0ad804655)
prikey = (s1 * m2 - s2 * m1) / (s2 - s1) / r1 assert prikey.x == 0x5f6717883bef25f45a129c11fcac1567d74bda5a9ad4cbffc8203c0da2a1473c ```
Invalid Curve Attack
Invalid Curve Attack involves using points not on the intended curve. Attackers can bypass signature verification or key validation by crafting an invalid public key.
During the signing process, an attacker could somehow construct an invalid public key. This invalid public key has some mathematical relationship with the attacker's private key (for example, the attacker signs by forging an invalid public key), which allows the attacker to generate a seemingly valid signature. Normally, the signature verification algorithm would check whether the public key is within the secp256k1 curve range. If the public key is invalid, the system should reject the signature. However, assuming the system does not perform sufficient curve point validity checks, the attacker could submit a request containing an invalid public key and a forged signature. In some cases, the system might incorrectly accept this invalid signature, considering it as legitimate. The attacker's signature could pass the system's checks, causing malicious transactions or operations to be wrongly considered valid, thus executing certain illegal actions, such as transferring funds or modifying data.
A real-world example is the elliptic curve verification vulnerability in OpenSSL. In 2015, an OpenSSL version prior to v1.0.2 had an elliptic curve verification vulnerability. An attacker could construct an invalid elliptic curve point and use it as a public key, exploiting certain vulnerabilities in OpenSSL to bypass verification, thus attacking systems using the library. This vulnerability, known as CVE-2015-1786, allowed attackers to bypass signature verification by forging an invalid public key. The same issue also occurred in the ECDSA library used by Bitcoin Core, where earlier versions of the library did not perform sufficient checks on elliptic curve points.
Before this vulnerability was fixed, attackers could bypass the system's curve validity checks without proper verification, leading to potential denial of service or other security issues.
Transaction Malleability Attack
In ancient times, if you hammered a gold coin, it changed shape but kept its value. This is called malleability.
Mt. Gox was once the largest Bitcoin exchange in the world. The company, headquartered in Tokyo, is estimated to have accounted for 70% of Bitcoin trading volume in 2013. In 2014, the exchange was hacked, resulting in a loss of approximately 850,000 Bitcoins. The hackers employed a technique known as a transaction malleability attack.
The specific process of the attack is as follows:
-
The attacker initiates a withdrawal Tx A on Mt. Gox
-
Before Tx A is confirmed, it manipulates the transaction's signature, causing the transaction hash, which uniquely identifies the transaction, to change and generating a forged Tx B.
-
Tx B is confirmed on the blockchain, and the exchange receives a failure message for Tx A.
The exchange mistakenly believes that the withdrawal has failed and constructs a new withdrawal transaction for the attacker.
The core of this attack is that the attacker can modify parts of the transaction's signature (such as the input signature) or other non-critical fields, thereby changing the transaction's hash, but without altering the actual content of the transaction.
Coincidentally, secp256k1 + ECDSA indeed provides a very convenient method that allows attackers to modify the signature result while still passing the signature verification. If we analyze the ECDSA signature verification algorithm, we find that the verification result is independent of the sign of the s value in the signature (r, s).
To verify this, we write the following test code:
```python import pabtc
prikey = pabtc.secp256k1.Fr(1) pubkey = pabtc.secp256k1.G * prikey msg = pabtc.secp256k1.Fr(0x72a963cdfb01bc37cd283106875ff1f07f02bc9ad6121b75c3d17629df128d4e)
r, s, _ = pabtc.ECDSA.sign(prikey, msg) assert pabtc.ECDSA.verify(pubkey, msg, r, +s) assert pabtc.ECDSA.verify(pubkey, msg, r, -s) ```
In the above code, we signed a message with the private key and then took the negative sign of the s value in the signature. We found that the modified signature still passed the ECDSA verification.
Bitcoin had this attack risk in its earlier versions. Attackers used a malleability attack to break the immutability of transactions, leading to severe security issues. To solve this problem, Bitcoin made improvements in the Segregated Witness (SegWit) upgrade. SegWit separated the signature part of the transaction from the other data, so even if the attacker altered the signature part, the transaction hash would no longer be affected, thus solving the transaction malleability issue.
This problem also has similar impacts on other blockchain systems, so many projects have adopted solutions similar to SegWit to ensure the integrity and traceability of transactions. Another solution is the one taken by Ethereum, where additional requirements were imposed on the s value in the signature. Ethereum requires that s must be smaller than
pabtc.secp256k1.N / 2
. You can find Ethereum's detailed solution to the transaction malleability attack in Appendix F. Signing Transactions in its Yellow Paper (p. 26).As a poem goes:
Mt. Gox, the name on the door,
So many users, could there be more?
Bitcoin stolen, hearts hit the floor.
Hashes changed, transactions no more.
Cold wallets emptied, riches out the door,
Gone with the wind, the wealth we adore.Side-Channel Attack
On a flight, I sat next to a man who kept checking stock prices. We chatted briefly, and he mentioned the market was bad this year, then asked me to guess how much he’d lost.
I said, “Probably around a hundred thousand.”
Surprised, he asked how I knew.
I explained: He wore business attire but carried a Swiss Army backpack—clearly not a top exec, more like business development. His Armani watch suggested mid-level income. His shirt was old but well-pressed—his wife likely takes care of him. The Hello Kitty charm on his bag hinted at a young daughter. His stock picks were all tech-related, so he’s probably in the industry. Based on all this, I estimated his disposable capital was around 200–300k, so losing 100k made sense. Dark circles and thinning hair showed stress; his wife likely didn’t know about the losses. I also saw a newly downloaded crypto trading app on his phone—he was probably planning to gamble on crypto next. Then I tapped open his stock app: 280k invested, 102k lost.
He didn’t say another word during the flight. Just sat there, rubbing his eyes. His meal remained untouched.
The story above comes from the Chinese internet, first appearing in 2015. Due to its widespread reposting, the author is unknown. In this story, "I" launched a side-channel attack on the man. Although he didn’t reveal any information about his personal investments, his financial status influences his attire, so we can reverse-engineer his financial situation through his clothing.
In cryptography, a side-channel attack refers to a method of using physical or behavioral information (such as execution time, power consumption, electromagnetic radiation, etc.) generated during a device's operation to break a cryptographic or signature scheme.
For secp256k1 elliptic curve and ECDSA signature schemes, such an attack could infer the private key by analyzing the execution characteristics of crucial operations.
In ECDSA, the signing process involves generating a random number
k
used to calculate part of the signature. The security of this randomk
is crucial; Once leaked, the private key is compromised:Example:
Given the following information, please compute the secp256k1 private key:
plaintext m = 0x72a963cdfb01bc37cd283106875ff1f07f02bc9ad6121b75c3d17629df128d4e k = 0x1058387903e128125f2715d7de954f53686172b78c3f919521ae4664f30b00ca r = 0x75ee776c554b1dd5e1680a4cc9a3d0e8cb11400742d8af0222ce383e642f98db s = 0x35fd48c9157256558184e20c9392ff3c9517f9753e3745aede06cab285f4bc0d
Answer:
python prikey = (s * k - m) / r assert prikey == 1
The verification code is as follows:
```python import pabtc
m = pabtc.secp256k1.Fr(0x72a963cdfb01bc37cd283106875ff1f07f02bc9ad6121b75c3d17629df128d4e) k = pabtc.secp256k1.Fr(0x1058387903e128125f2715d7de954f53686172b78c3f919521ae4664f30b00ca) r = pabtc.secp256k1.Fr(0x75ee776c554b1dd5e1680a4cc9a3d0e8cb11400742d8af0222ce383e642f98db) s = pabtc.secp256k1.Fr(0x35fd48c9157256558184e20c9392ff3c9517f9753e3745aede06cab285f4bc0d)
prikey = (s * k - m) / r assert prikey == pabtc.secp256k1.Fr(1) ```
The calculation of the random number
k
involves elliptic curve point multiplication and modular inversion operations (typically implemented using the extended Euclidean algorithm). These operations may have execution times that depend onk
, and side-channel attackers can measure execution time differences to extract information aboutk
. To demonstrate the principle, I will try to simplify the attack process.Example: Suppose there is an unknown random number
k
, and a hacker is somehow able to detect the execution time ofg * k
. Try to see if it's possible to extract some information about the random numberk
.Answer: By examining the point multiplication algorithm on elliptic curves, we find that different operations are executed depending on the bit value of
k
. When a bit is 0, the amount of computation is less than when the bit is 1. We first take two differentk
values—one with most bits being 0, and the other with most bits being 1—and measure the difference in their execution times. Then, when a new unknownk
is used in the computation, we detect its execution time and compare it with the previous two values. This allows us to roughly estimate how many 1 bits are present in the unknownk
.The experimental code is shown below. Note: To simplify the attack steps, in the experimental code we assume that the first bit of all participating
k
values is always 1.```python import pabtc import random import timeit
k_one = pabtc.secp256k1.Fr(0x800000...) k_255 = pabtc.secp256k1.Fr(0x7fffff...) k_unknown = pabtc.secp256k1.Fr(random.randint(0, pabtc.secp256k1.N - 1) | k_one.x)
a = timeit.timeit(lambda: pabtc.secp256k1.G * k_one, number=1024) b = timeit.timeit(lambda: pabtc.secp256k1.G * k_255, number=1024) c = timeit.timeit(lambda: pabtc.secp256k1.G * k_unknown, number=1024)
d = (c - a) / ((b - a) / 254) print(d) ```
The attack process described above is a timing attack, which is a type of side-channel attack. To defend against timing attack, you can introduce constant-time operations in the code to avoid leaking information, such as using fixed-time addition and multiplication operations to prevent timing differences from being exploited.
In real-world applications, preventing side-channel attacks in cryptographic algorithms requires comprehensive security optimizations at the algorithm, hardware, and software levels. However, since the secp256k1 and ECDSA schemes were not designed with such attacks in mind, defending against them is particularly difficult and complex.
CKB's Approach: Segregated Witness + Upgradable Cryptographic Algorithms
In terms of transaction structure, CKB adopts Bitcoin’s Segregated Witness scheme, meaning that the transaction hash does not include the ECDSA signature. This design helps prevent transaction malleability attacks. See the Transaction Hash section of the RFC for details.
The Secp256k1 + ECDSA scheme is CKB’s default signature scheme, but thanks to CKB’s custom cryptographic primitives and native account abstraction capabilities, we can easily implement many other cryptographic algorithms in CKB, as well as upgrade existing ones. Developers on CKB are free to choose any algorithm they trust to protect the assets, including but not limited to:
Final Remarks
In short, although secp256k1 and ECDSA are widely used and quite secure when implemented properly and used correctly, we still must not overlook some of their potential vulnerabilities. Thanks to Bitcoin’s development, secp256k1 and ECDSA have gained great fame, but they have also attracted more attention from cryptographers and malicious hackers. In the future, more types of attacks on secp256k1 may be gradually discovered and exploited. Therefore, staying vigilant, updating in time, and following the latest security best practices is critical for maintaining system security. As the field of cryptography continues to advance, there are already some more secure and efficient alternatives available. But regardless of this, understanding and addressing current risks remains the responsibility and challenge of every developer.
🧑💻 About the Author
Wanbiao Ye is a core developer of the CKB-VM. He focuses on improving the virtual machine’s performance and capabilities, and has been exploring areas like instruction set design and macro instruction fusion to make the system more efficient and flexible.
Among his writings and talks are:
-