1. 引言
前序博客:
- ECDSA VS Schnorr signature VS BLS signature
- 区块链中的Ed25519(更详细的参看2021年2月总结 Cryptography behind the top 100 cryptocurrencies)
若对网络安全感兴趣,建议了解下:
- ECDSA 代表Elliptic Curve Digital Signature Algorithm(椭圆曲线数字签名算法)
- EdDSA 代表Edwards-curve Digital Signature Algorithm(Edwards曲线数字签名算法)
二者均可用于创建数字签名。
Bob 使用其私钥签署消息,然后 Alice 使用消息、签名和 Bob 的公钥来验证该签名。一旦 Bob 签署了消息,就不可能基于该签名来修改密钥或消息——那样的修改后的签名将无法验证通过。
上图展示了椭圆曲线签名算法概要:
- Bob 使用其私钥 (sk) 对消息M进行哈希签名,并生成签名:(r,s)。
- 签名(r,s)与消息M一起发送给 Alice。
- Alice 计算消息M的哈希,并和签名 (r,s) 以及 Bob 的公钥 (pk)一起进行签名验证。
- 若验证通过,Alice 就知道 Bob 签署了该消息。
ECDSA 和 EdDSA有什么区别呢?什么时候应该使用 ECDSA 而不是 EdDSA,反之亦然。
- 对于与比特币和以太坊兼容的东西,ECDSA 提供了最佳解决方案。
- 不幸的是,ECDSA依赖于要创建的随机 nonce 值,若 nonce 不是随机的,则会显著降低签名的安全性。
- EdDSA 的速度性能与 ECDSA 大致相同,但EdDSA自然支持密钥聚合,以便在签名过程中进行密钥聚合。原因在于EdDSA基于 Schnorr 签名方法。
ECDSA 已存在二十多年,最早由Don Johnson、Alfred Menezes 和 Scott Vanston 2001年论文The Elliptic Curve Digital Signature Algorithm (ECDSA) 中提出:
2011 年,Ed25519 被提出作为一种快速、安全的数字签名方法(见 Daniel J. Bernstein、Niels Duif、Tanja Lange、Peter Schwabe 和 Bo-Yin Yang 2011年论文High-speed high-security signatures):
关于签名的更多信息见:
1.1 EdDSA 和 ECDSA 的要点
EdDSA 和 ECDSA 的关键点有:
- 1)Ed25519 由 Daniel J. Bernstein、Niels Duif、Tanja Lange、Peter Schwabe 和 Bo-Yin Yang 于 2011 年提出,而 ECDSA 由 Don Johnson、Alfred Menezes 和 Scott Vanstone 于 2001 年左右提出。
- 2)ECDSA 使用 secp256k1 曲线,EdDSA 使用 Curve 25519。
- 3)比特币和以太坊使用 ECDSA,而 IOTA 使用 EdDSA。【见IOTA EdDSA Support,2021年8月已用EdDSA签名替代了Winternitz One Time Signature (W-OTS)方案。】
- 4)EdDSA 采用 Schnorr 签名方法,目前Schnorr签名已过专利保护期。EdDSA 支持签名中的密钥聚合,也支持多方签名的聚合。
- 5)为了提高安全性,ECDSA 支持 521-bit 曲线(P521),而 EdDSA 支持 X448。
- 6)ECDSA 在签名创建过程中会创建一个随机 nonce 值,而 EdDSA 则不会。在 ECDSA 中,需要小心确保不会重复使用此 nonce 值,且该nonce值是随机的。
- 7)ECDSA 签名每次都会根据所使用的随机数而改变,而 EdDSA 签名对于同一组密钥和同一条消息不会改变。
- 8)ECDSA 经常被用来保持与比特币和以太坊的兼容性。
- 9)ECDSA 公钥是 (x,y) 坐标,因此有 512 位(对于 secp256k1),而 Ed25519 仅使用点的 y 坐标值,因此有 256 位。
- 10)ECDSA 和 EdDSA 通常具有相同的性能和安全级别。
- 11)EdDSA 是一种确定性签名(意味着总是针对相同的私钥和消息获得相同的签名),而 ECDSA 是非确定性的。
- 12)EdDSA 和 ECDSA 的安全性与 128 位 AES 和 3K RSA 相当。
- 13)EdDSA 对侧信道攻击具有相当强的免疫力。
2. 了解Schnorr签名
1989 年 2 月,德国数学家、密码学家 Claus Schnorr 提交了一项专利,但尚未被授予任何人。该专利有 11 项权利要求,允许为多个signers合并数字签名(见专利文件 Method for identifying subscribers and for generating and verifying electronic signatures in a data exchange system):
使用 Schnorr 签名,为消息 (M) 的哈希值创建签名 (R,s):
- 首先生成私钥 (x),然后从椭圆曲线 (G) 上的某个点派生公钥,得到:
P=x⋅G - 接下来,选择一个随机值(k)来得到R的签名值:
R=k⋅G - 那么 s 的值就是:
s=k−Hash(M,R)⋅x - 对 M 的签名是 (s,R),公钥是 P。
Schnorr验签过程为:
- 计算P⋅Hash(M,R)+s⋅G
对应为: x⋅G⋅Hash(M,R)+(k−Hash(M,R)⋅x)⋅G
即:
x⋅G⋅Hash(M,R)+k⋅G−Hash(M,R)⋅x⋅G=k⋅G - k⋅G 的值等于 R,因此若结果与 R 相同,则签名通过。
3. EdDSA 和 Ed25519
Edwards-curve Digital Signature Algorithm (EdDSA) 使用 Twisted Edwards 曲线增强 Schnorr 签名来创建数字签名。
总体而言,EdDSA比许多其他数字签名方法更快,且安全性更强。
EdDSA 的实例之一是:
- Ed25519,其基于 Curve 25519。Ed25519可提供大约128 位的安全性并生成 64 字节的签名值 (R,s)。除此之外,Ed25519还有 32 字节的公钥和私钥值。
以下是 Golang 代码(EdDSA and Ed25519 in Golang):
package main
import (
"crypto/ed25519"
"encoding/base64"
"fmt"
"os"
)
func Base64Encode(message []byte) []byte {
b := make([]byte, base64.StdEncoding.EncodedLen(len(message)))
base64.StdEncoding.Encode(b, message)
return b
}
func main() {
msg := "Hello 123"
argCount := len(os.Args[1:])
if argCount > 0 {
msg = os.Args[1]
}
publ, priv, _ := ed25519.GenerateKey((nil))
m := []byte(msg)
sig := ed25519.Sign(priv, m)
fmt.Printf("=== Message ===\n")
fmt.Printf("Msg: %s\nHash: %x\n", msg, m)
fmt.Printf("\n=== Public key ===\n")
fmt.Printf("Public key: %x\n", publ)
fmt.Printf(" Public key (Base64): %s\n", Base64Encode(publ))
fmt.Printf("\n=== Private key ===\n")
fmt.Printf("Private key: %x\n", priv[0:32])
fmt.Printf(" Private key (Base64): %s\n", Base64Encode(priv[0:32]))
fmt.Printf(" Private key (Base64) Full key: %s\n", Base64Encode(priv))
fmt.Printf(" Private key (Full key): %x\n", priv)
fmt.Printf("\n=== Signature (R,s) ===\n")
fmt.Printf("Signature: R=%x s=%x\n", sig[0:32], sig[32:64])
fmt.Printf(" Signature (Base64)=%s\n\n", Base64Encode(sig))
rtn := ed25519.Verify(publ, m, sig)
if rtn {
fmt.Printf("Signature verifies")
} else {
fmt.Printf("Signature does not verify")
}
}
使用“Hello”消息运行的以上代码,对应有(EdDSA and Ed25519 in Golang):
=== Message ===
Msg: Hello
Hash: 48656c6c6f
=== Public key ===
Public key: c3903a26c73a433554325859c963056acd2d503fc36313ae21647f911e723fab
Public key (Base64): w5A6Jsc6QzVUMlhZyWMFas0tUD/DYxOuIWR/kR5yP6s=
=== Private key ===
Private key: fc225cb6dd8969541e57754b4120b51e6a92673107c7c8e1dc25a7a3e6b1066b
Private key (Base64): /CJctt2JaVQeV3VLQSC1HmqSZzEHx8jh3CWno+axBms=
Private key (Base64) Full key: /CJctt2JaVQeV3VLQSC1HmqSZzEHx8jh3CWno+axBmvDkDomxzpDNVQyWFnJYwVqzS1QP8NjE64hZH+RHnI/qw==
Private key (Full key): fc225cb6dd8969541e57754b4120b51e6a92673107c7c8e1dc25a7a3e6b1066bc3903a26c73a433554325859c963056acd2d503fc36313ae21647f911e723fab
=== Signature (R,s) ===
Signature: R=fbee75ddd533296a9ebacbe653a3335d1b9a99d6e6c7941d4651e04a6268ad2e s=086b3da235c4f4e426d1a2e76a731c0a81844d98fe59f412abd869fb3008d00d
Signature (Base64)=++513dUzKWqeusvmU6MzXRuamdbmx5QdRlHgSmJorS4Iaz2iNcT05CbRoudqcxwKgYRNmP5Z9BKr2Gn7MAjQDQ==
Signature verifies
其中:
- Ed25519签名长度为 64 字节,其中R值占 32 字节,s值占 32 字节。
- 公钥长度也是 32 字节,私钥长度为 32 字节。
- 总体而言,Ed25519 产生的签名大小是最小的,公钥和私钥的大小都很小。
- 在更加分布式的环境中,使用 Schnorr 签名支持将密钥拆分为shares,并允许不同的各方聚集在一起并生成其签名部分。此方法支持删除私钥,然后将私钥拆分为secret shares。
Ed25519 用于数字签名的好处有:
- 签名速度超快:benchmark显示,在流行的 CPU 上签名只需不到 90K 个周期
- 验证速度超快:benchmark显示,在流行的 CPU 上,验签所需的时间不到 300K 个周期,批量验签的速度甚至更快(每个签名所需的时间不到 134K 个周期)。
- 密钥生成速度超快:在流行的 CPU 上只需不到 6K 个周期。
- 安全级别高:Ed25519 与 128 位 AES、NIST P256 和 3K RSA 相当。
- 抗碰撞。哈希碰撞不会破坏 Ed25519。
- 小签名:Ed25519签名只有512位长(64字节)。
- 小密钥和派生公钥:Ed25519 密钥只有 256 位长(32 字节),且可从私钥派生出公钥。
- Ed25519 签名是确定性的:相同的输入数据将始终导致相同的签名,这与需要随机nonces的 ECDSA 不同。
- 相当程度上免受旁道攻击。
以下是一些示例:
4. ECDSA
ECDSA 方法比基于 RSA 的 DSA 方法显著提高了签名消息的性能。ECDSA使用椭圆曲线方法加快了整个过程,并支持更小的密钥大小。ECDSA的王冠和荣耀在于中本聪选择在其比特币协议中使用ECDSA,然后被以太坊采用。但ECDSA在签名聚合和分布式环境中密钥拆分方面确实遇到了困难。
ECDSA 中的数学知识为:
ECDSA签名为 (r,s) pair,并可验签。
- 与EdDSA不同,ECDSA可使用一系列曲线,而不是使用Curve 25519,如 secp256k1 (P256k1)、P244 和 P521。
secp256k1 的安全级别与 Ed25519 大致相同。
- 在 Ed25591 中,椭圆曲线上某点,只使用 y 坐标点,而在 ECDSA 中,通常为 (x,y) 坐标点。
- ECDSA公钥(即曲线上的点)长度为 512 位,而私钥长度仅为 256 位。
- 总体而言,ECDSA 中的公钥比 EdDSA 中的公钥更大。
- 不过,ECDSA 的主要缺点是其不支持密钥和签名的合并。
ECDSA签名Go代码实例见ECDSA Creation and Verification:
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"fmt"
"os"
"strings"
"github.com/dustinxie/ecc"
)
func getCurve(s string) elliptic.Curve {
if strings.Contains(s, "224") {
return (elliptic.P224())
} else if strings.Contains(s, "384") {
return (elliptic.P384())
} else if strings.Contains(s, "521") {
return (elliptic.P521())
}
return (ecc.P256k1())
}
func main() {
msg := "Hello 123"
curveType := ""
argCount := len(os.Args[1:])
if argCount > 0 {
msg = os.Args[1]
}
if argCount > 1 {
curveType = os.Args[2]
}
pubkeyCurve := getCurve(curveType)
m := []byte(msg)
digest := sha256.Sum256(m)
privatekey, _ := ecdsa.GenerateKey(pubkeyCurve, rand.Reader)
pubkey := privatekey.PublicKey
r, s, _ := ecdsa.Sign(rand.Reader, privatekey, digest[:])
fmt.Printf("=== Message ===\n")
fmt.Printf("Msg=%s\nHash=%x\n", msg, digest)
fmt.Printf("\n=== Private key ===\n")
fmt.Printf("Private key=%x\n", privatekey.D)
fmt.Printf("Curve=%s\n", privatekey.Curve.Params().Name)
fmt.Printf("Bit size=%d\n", privatekey.Curve.Params().BitSize)
fmt.Printf("Base point (G) =(%d, %d)\n", privatekey.Curve.Params().Gx, privatekey.Curve.Params().Gy)
fmt.Printf("Prime=%d, Order=%d", privatekey.Curve.Params().P, privatekey.Curve.Params().N)
fmt.Printf("\n=== Public key (X,Y) ===\n")
fmt.Printf("X=%s Y=%s\n", pubkey.X, pubkey.Y)
fmt.Printf(" Hex: X=%x Y=%x\n", pubkey.X.Bytes(), pubkey.Y.Bytes())
fmt.Printf("\n=== Signature (R,S) ===\n")
fmt.Printf("R=%s S=%s\n", r, s)
fmt.Printf(" Hex: R=%x S=%x\n", r, s)
rtn := ecdsa.Verify(&pubkey, digest[:], r, s)
if rtn {
fmt.Printf(\n"Signature verifies")
} else {
fmt.Printf("\nSignature does not verify")
}
}
对应运行结果为:
=== Message ===
Msg=Hello 123
Hash=859e38d581e214dc7c8c871c425642913363a829065cf4acddd120ed5391b04b
=== Private key ===
Private key=b3645f2efea9a96d28cbeb5bf8a5304a3dc96b2a42bee21c0b3aaa88f595df2d
Curve=P-256k1
Bit size=256
Base point (G) =(55066263022277343669578718895168534326250603453777594175500187360389116729240, 32670510020758816978083085130507043184471273380659243275938904335757337482424)
Prime=115792089237316195423570985008687907853269984665640564039457584007908834671663, Order=115792089237316195423570985008687907852837564279074904382605163141518161494337
=== Public key (X,Y) ===
X=77007236596272499552697218405908714888874625059778411542685725622785792316534 Y=20745252821220973789342590850065442758134973002375340605949893038975196614597
Hex: X=aa408d244da8a2ea673213ef63536ea96486ce0412a5294c9cdf0959cc689476 Y=2ddd65a19ed17f361b0381a72713f740b63d4fdca059427c389239da39004fc5
=== Signature (R,S) ===
R=33027995512220841690000083421269061534408622570666620793995266029032826750381 S=44867240085578618664628913670877492263668786345184239470907981535519639811276
Hex: R=49052ed8fcf1903f530bda10ea9b578b6bb77487ea6b22b5558fc68524e045ad S=6331f53ce5e1a64e4043712631aeeb3f5c0ed753140a0fd76a8c5367e69b34cc
Signature verifies
更多ECDSA签名例子见:
参考资料
[1] 2024年5月19日博客 A Bluffers Guide to EdDSA and ECDSA
附录 各区块链项目背后曲线、签名方案总结
2021年2月总结 Cryptography behind the top 100 cryptocurrencies中总结了当时市值前100 区块链项目背后的所用的签名算法、曲线信息,其中:
- 共有74个项目使用ECDSA和secp256k1曲线,包括比特币、以太坊、和 48个ERC20 tokens。
- 10个项目使用EdDSA和curve25519,如Stellar、Cardano、Elrond、Solana等。
- 8个项目使用了多种签名算法和曲线(通常为既有ECDSA/secp256k1,也有EdDSA/curve25519),如Polkadot、Tezos、NEAR等。
Name | Symbol | Signing Algorithm | Curve | Notes |
---|---|---|---|---|
Bitcoin | BTC | ECDSA | secp256k1 | |
Ethereum | ETH | ECDSA | secp256k1 | |
Tether | USDT | Ethereum ERC20 token | ||
Polkadot | DOT | ECDSA, Schnorr, EdDSA | curve25519, ristretto25519, secp256k1 | |
XRP | XRP | ECDSA, EdDSA | curve25519, secp256k1* | * is the default |
Cardano | ADA | EdDSA | curve25519 | |
Litecoin | LTC | ECDSA | secp256k1 | |
Bitcoin Cash | BCH | ECDSA | secp256k1 | |
Chainlink | LINK | Ethereum ERC20 token | ||
Stellar | XLM | EdDSA | curve25519 | |
Binance Coin | BNB | ECDSA | secp256k1 | |
USD Coin | USDC | Ethereum ERC20 token | ||
Wrapped Bitcoin | WBTC | Ethereum ERC20 token | ||
Bitcoin SV | BSV | ECDSA | secp256k1 | |
EOS | EOS | ECDSA | secp256k1 | |
Monero | XMR | EdDSA*, Bulletproofs | curve25519 | * non-standard hashing algorithm, uses Keccak |
Aave | AAVE | Ethereum ERC20 token | ||
Tron | TRX | ECDSA | secp256k1 | |
VeChain | VET | ECDSA | secp256k1 | |
NEM | XEM | EdDSA | curve25519 | |
Synthetix | SNX | Ethereum ERC20 token | ||
Cosmos | ATOM | ECDSA | secp256k1 | |
Theta Network | THETA | ECDSA | secp256k1 | |
Uniswap | UNI | Ethereum ERC20 token | ||
NEO | NEO | ECDSA | NIST P-256 | |
Crypto.com Coin | CRO | ECDSA | secp256k1 | |
OKB | OKB | Ethereum ERC20 token | ||
Celsius Network | CEL | Ethereum ERC20 token | ||
cUSDC | CUSDC | Ethereum ERC20 token | ||
cETH | CETH | Ethereum ERC20 token | ||
Dai | DAI | Ethereum ERC20 token | ||
LEO Token | LEO | Ethereum ERC20 token | ||
IOTA | MIOTA | **Winternitz OTS【2021年8月,已迁移至EdDSA签名方案】 ** | ||
Tezos | XTZ | ECDSA, EdDSA | secp256k1, curve25519, NIST P-256 | tz1: EdDSA, tz2: ECDSA/secp256k1, tz3: ECDSA/NIST P-256 |
Elrond | EGLD | EdDSA | curve25519 | |
Maker | MKR | Ethereum ERC20 token | ||
Binance USD | BUSD | Ethereum ERC20 token | ||
Dogecoin | DOGE | ECDSA | secp256k1 | |
Dash | DASH | ECDSA | secp256k1 | |
Huobi Token | HT | Ethereum ERC20 token | ||
Filecoin | FIL | ECDSA | secp256k1 | |
Zcash | ZEC | ECDSA, ZK-SNARKs* | secp256k1, BLS12-381-JubJub* | * for shielded/anonymous transactions |
Avalanche | AVAX | ECDSA | secp256k1 | |
Yearn Finance | YFI | Ethereum ERC20 token | ||
Kusama | KSM | ECDSA, EdDSA, Schnorr | secp256k1, curve25519, ristretto25519 | |
Ethereum Classic | ETC | ECDSA | secp256k1 | |
Solana | SOL | EdDSA | curve25519 | |
cDAI | CDAI | Ethereum ERC20 token | ||
FTX Token | FTT | Ethereum ERC20 token | ||
Sushi | SUSHI | Ethereum ERC20 token | ||
Compound | COMP | Ethereum ERC20 token | ||
Huobi BTC | HBTC | Ethereum ERC20 token | ||
Zilliqa | ZIL | EC-Schnorr | secp256k1 | |
Hedera Hashgraph | HBAR | ECDSA, EdDSA, RSA* | NIST P-384, curve25519 | * 3072 bit RSA |
UMA | UMA | Ethereum ERC20 token | ||
Decred | DCR | EdDSA, EC-Schnorr* | curve25519, secp256k1 | * non-standard |
Waves | WAVES | EdDSA | curve25519 | |
Near | NEAR | ECDSA, EdDSA | secp256k1, curve25519 | |
The Graph | GRT | Ethereum ERC20 token | ||
REN | REN | Ethereum ERC20 token | ||
Paxos Standard | PAX | Ethereum ERC20 token | ||
Loopring | LRC | Ethereum ERC20 token | ||
OMG Network | OMG | Ethereum ERC20 token | ||
renBTC | RENBTC | Ethereum ERC20 token | ||
Ontology | ONT | ECDSA | NIST P-256 | |
ICON | ICX | ECDSA | secp256k1 | |
Blockstack | STX | ECDSA | secp256k1 | |
THORChain | RUNE | Binance BEP2 token | ||
TrueUSD | TUSD | Ethereum ERC20 token | ||
Nano | NANO | EdDSA* | curve25519 | * uses Blake2b-512 instead of SHA-512 in key derivation |
Terra | LUNA | ECDSA | secp256k1 | |
NEXO | NEXO | Ethereum ERC20 token | ||
Algorand | ALGO | EdDSA | curve25519 | |
Basic Attention Token | BAT | Ethereum ERC20 token | ||
0x | ZRX | Ethereum ERC20 token | ||
DigiByte | DGB | ECDSA | secp256k1 | |
Reserve Rights Token | RSR | Ethereum ERC20 token | ||
Curve DAO Token | CRV | Ethereum ERC20 token | ||
Enjin Coin | ENJ | Ethereum ERC20 token | ||
Aave Link | ALINK | Ethereum ERC20 token | ||
Nexus Mutual | NXM | Ethereum ERC20 token | ||
Horizen | ZEN | ECDSA | secp256k1 | |
HUSD | HUSD | Ethereum ERC20 token | ||
IOST | IOUST | ECDSA, EdDSA | secp256k1, curve25519 | |
Qtum | QTUM | ECDSA | secp256k1 | |
Kyber Network | KNC | Ethereum ERC20 token | ||
Bitcoin Cash ABC | BCHA | ECDSA | secp256k1 | |
Energy Web Token | EWT | ECDSA | secp256k1 | |
SwissBorg | CHSB | Ethereum ERC20 token | ||
Ampleforth | AMPL | Ethereum ERC20 token | ||
BitTorrent | BTT | Tron TRC20 token | ||
Quant | QNT | Ethereum ERC20 token | ||
Balancer | BAL | Ethereum ERC20 token | ||
Siacoin | SC | EdDSA | curve25519 | |
Ocean Protocol | OCEAN | Ethereum ERC20 token | ||
Aave ETH | AETH | Ethereum ERC20 token | ||
TerraUSD | UST | ECDSA | secp256k1 | |
Voyager Token | VGX | Ethereum ERC20 token | ||
Arweave | AR | RSA* | * 4096 bit RSA | |
Status | SNT | Ethereum ERC20 token |