Introduce
椭圆加密算法(ECC)是一种公钥加密体制,最初由Neal Koblitz和Victor Miller两人于1985年分别提出,其数学基础是利用椭圆曲线上的有理点构成Abel加法群上椭圆离散对数的计算困难性。
椭圆曲线密码体制来源于对椭圆曲线的研究,所谓椭圆曲线指的是由韦尔斯特拉斯(Weierstrass)方程:
所确定的平面曲线。其中系数 定义在某个域上,可以是有理数域、实数域、复数域,还可以是有限域,椭圆曲线密码体制中用到的椭圆曲线都是定义在有限域上的。
椭圆曲线上所有的点外加一个叫做无穷远点的特殊点构成的集合连同一个定义的加法运算构成一个Abel群。在等式
中,已知和点求点比较容易,反之已知点和点求却是相当困难的,这个问题称为椭圆曲线上点群的离散对数问题。椭圆曲线密码体制正是利用这个困难问题设计而来。
https://playsecurity.org
Curve
描述一条上的椭圆曲线,常用到六个参量:
( 、 、 用来确定一条椭圆曲线,
为基点,
为点的阶,
是椭圆曲线上所有点的个数与相除的整数部分)
这几个参量取值的选择,直接影响了加密的安全性。参量值一般要求满足以下几个条件:
1、 当然越大越安全,但越大,计算速度会变慢,200位左右可以满足一般安全要求;
2、;
3、;
4、;
5、 为素数;
6、。
对应的曲线:
ECC Add
最大值97的整数标绘的同一曲线:
ECC ADD over G(Fp)
在F2m域上进行点加
在Fp域上进行点加
Curve Sample
即:
Sign/Verify
For Alice to sign a message , she follows these steps:
- Calculate , where HASH is a cryptographic hash function, such as SHA-2.
- Let be the leftmost bits of , where is the bit length of the group order .
- Select a cryptographically secure random integer from .
- Calculate the curve point .
- Calculate . If , go back to step 3.
- Calculate . If , go back to step 3.
- The signature is the pair .
For Bob to authenticate Alice's signature, he must have a copy of her public-key curve point . Bob can verify is a valid curve point as follows:
- Check that is not equal to the identity element , and its coordinates are otherwise valid
- Check that lies on the curve
- Check that
After that, Bob follows these steps:
- Verify that and are integers in . If not, the signature is invalid.
- Calculate , where HASH is the same function used in the signature generation.
- Let be the leftmost bits of .
- Calculate .
- Calculate and .
- Calculate the curve point .
- The signature is valid if , invalid otherwise.
public class BCECCDSATest {
static final String EC_G = "046B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C2964FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5";
static final String EC_N = "00FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551";
public static void main(String[] args) {
ECNamedCurveParameterSpec ecn = ECNamedCurveTable.getParameterSpec("secp256r1");
ECCurve p256 = ecn.getCurve();
ECPoint G = p256.decodePoint(Bytes.fromHex(EC_G).getBytes());
BigInteger n = new BigInteger(Bytes.fromHex(EC_N).getBytes());
BigInteger d = new BigInteger(255, new SecureRandom());
ECPoint Q = G.multiply(d).normalize();
System.out.println(d + " - d");
System.out.println(Q + " - Q");
String message = "Hello World.";
byte[] hash = Digests.sha256().digest(message.getBytes(StandardCharsets.UTF_8)).getBytes();
BigInteger e = new BigInteger(1, hash);
System.out.println(e + " - e");
// SIGN
// (x1, x2)=kG
BigInteger k = new BigInteger(255, new SecureRandom());
ECPoint kG = G.multiply(k).normalize();
System.out.println(k + " - k");
System.out.println(kG + " - kG");
// r=x1 mod n
// s=k^-1(e+dr) mod n
BigInteger r = kG.getXCoord().toBigInteger().mod(n);
BigInteger s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n);
System.out.println(r + " - r");
System.out.println(s + " - s");
// VERIFY
// c=s^-1 mod n
// u1=ec mod n
// u2=rc mod n
// (x1, y1)=u1G+u2Q
// v=x1 mod n
BigInteger c = s.modInverse(n);
BigInteger u1 = e.multiply(c).mod(n);
BigInteger u2 = r.multiply(c).mod(n);
ECPoint uGuQ = G.multiply(u1).add(Q.multiply(u2)).normalize();
BigInteger v = uGuQ.getXCoord().toBigInteger().mod(n);
// if v==r then success else fail
System.out.println(v + " - v");
}
}
Outputs:
8339335619003824837316171857090442185503111058507416390261190127801248406975 - d
(e84955cb02aabc9cfe4d9f227fdc64346c4c144f638b14a044b3cba2946c3f55,cf90454675395ec046ba2bdf943df5feaeac30cb3a971e4050aae88443c875df,1) - Q
110694911173530595670578600930991710652668442267815723053237189464905523473554 - e
2736360310200035368467746808912388965894573642370209205024023930990253492263 - k
(60990936a0c1bd64ced00284787a9e0839129e0cd3940c3fa32544851c91f740,470d84efe5e98fd75c4069b8d4e302e90e055705d56e52b8a2637969a86274dc,1) - kG
43692424653388573833405464958344316700408663786782137472117803965929307109184 - r
83697305946043570979035432403058877013859702222209490086131111455117628730238 - s
43692424653388573833405464958344316700408663786782137472117803965929307109184 - v
Public Key Recovery
- First, you find the two points , which have the value as the x-coordinate .
- You also compute , which is the multiplicative inverse of the value from the signature (modulo the order of the generator of the curve).
- Then, you compute which is the lowest bits of the hash of the message (where is the bit size of the curve).
Then, the two public keys are and
public class BCECCDSARecoveryTest {
static final String EC_G = "046B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C2964FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5";
static final String EC_N = "00FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551";
public static void main(String[] args) throws Exception {
ECNamedCurveParameterSpec ecn = ECNamedCurveTable.getParameterSpec("secp256r1");
ECCurve p256 = ecn.getCurve();
ECPoint G = p256.decodePoint(Bytes.fromHex(EC_G).getBytes());
BigInteger n = new BigInteger(Bytes.fromHex(EC_N).getBytes());
BigInteger d = new BigInteger(255, new SecureRandom());
ECPoint Q = G.multiply(d).normalize();
System.out.println(d + " - d");
System.out.println(Q + " - Q");
String message = "Hello World.";
byte[] hash = Digests.sha256().digest(message.getBytes(StandardCharsets.UTF_8)).getBytes();
BigInteger e = new BigInteger(1, hash);
System.out.println(e + " - e");
// SIGN
// (x1, x2)=kG
BigInteger k = new BigInteger(255, new SecureRandom());
ECPoint kG = G.multiply(k).normalize();
System.out.println(k + " - k");
System.out.println(kG + " - kG");
// r=x1 mod n
// s=k^-1(e+dr) mod n
BigInteger r = kG.getXCoord().toBigInteger().mod(n);
BigInteger s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n);
System.out.println(r + " - r");
System.out.println(s + " - s");
// RECOVERY
BigInteger a = p256.getA().toBigInteger();
BigInteger b = p256.getB().toBigInteger();
BigInteger p = p256.getField().getCharacteristic();
// get y from x (r)
BigInteger ysquared = r.pow(3).add(r.multiply(a)).add(b);
BigInteger y = ysquared.modPow((p.add(BigInteger.ONE)).divide(BigInteger.valueOf(4)), p);
// y1=((x^3)+ax+b)^((p+1)/4) mod p
BigInteger y1 = y.mod(p);
// y2=y1*-1 mod p
BigInteger y2 = y.multiply(BigInteger.valueOf(-1)).mod(p);
System.out.println(Bytes.from(y1.toByteArray()).asHex() + " - y1");
System.out.println(Bytes.from(y2.toByteArray()).asHex() + " - y2");
// R=(r, y1)
// P1=r^−1(sR−zG)
ECPoint R = p256.createPoint(r, y1);
ECPoint P1 = R.multiply(s).subtract(G.multiply(e)).multiply(r.modInverse(n)).normalize();
System.out.println(P1 + " - P1");
// R′=(r, y2)
// P2=r^−1(sR′−zG)
ECPoint R2 = p256.createPoint(r, y2);
ECPoint P2 = R2.multiply(s).subtract(G.multiply(e)).multiply(r.modInverse(n)).normalize();
System.out.println(P2 + " - P2");
}
}
Outputs:
38019560345561684150635495742401303901141483643965249577125570930601841099743 - d
(6f3791d5d256991ec7282445ab60b2fa89166084d6e2d5ee9d3481d2deba2579,ff8af5f938a0ff18de95199e4b1930aa8b47cb1eeeed88455848b9cd826b20ef,1) - Q
110694911173530595670578600930991710652668442267815723053237189464905523473554 - e
33251217980662710263323006889465582954860547012072219586250831255749444299899 - k
(ee56d685d07b47f0b43056fc062b7fdf7b3800f78d5281e8e8885df21d5ac012,d1f77031280b1a2b71309f15ad3a626a9c0e86c96c9911763600fe1cf02e8b25,1) - kG
107803887391735132932602215427942167721389756739280384234415683210387469811730 - r
1911350382085751112357226628059825040558795379101842008034989625579356012673 - s
2e088fcdd7f4e5d58ecf60ea52c59d9563f179379366ee89c9ff01e30fd174da - y1
00d1f77031280b1a2b71309f15ad3a626a9c0e86c96c9911763600fe1cf02e8b25 - y2
(ecf83087d980f98c63b627ba6318f7e4861e699b0b25320e17f1f23e15b4b098,43e031098662b267c782f0e16a7e29ec12c17236093dd6e6105705075703367c,1) - P1
(6f3791d5d256991ec7282445ab60b2fa89166084d6e2d5ee9d3481d2deba2579,ff8af5f938a0ff18de95199e4b1930aa8b47cb1eeeed88455848b9cd826b20ef,1) - P2
Schnorr Sign/Verify
生成签名
签名者已知的是: - 椭圆曲线, - 哈希函数, - 待签名消息, - 私钥。
- 选择一个随机数k, 令
- 令
那么,公钥对消息的签名就是:,这一对值即为Schnorr签名。
验证签名
验证者已知的是: - 椭圆曲线, - 哈希函数, - 待签名消息, - 公钥, - Schnorr签名。验证如下等式:
若等式成立,则可证明签名合法。
我们推演一下,此过程包含了一个极其重要的理论:椭圆曲线无法进行除法运算。
- s值的定义:,等式两边都乘以椭圆曲线,则有:
- ,又因 , ,则有:
椭圆曲线无法进行除法运算,所以第3步的等式,无法向前反推出第1步,就不会暴露值以及私钥。同时,也完成了等式验证。
https://hatter.ink/static/resource/muboard/?id=schnorr&version=1
public class BCSchnorrTest {
public static void main(String[] args) throws Exception {
ECNamedCurveParameterSpec ecn = ECNamedCurveTable.getParameterSpec("secp256r1");
ECPoint G = ecn.getG();
BigInteger n = ecn.getN();
BigInteger x = new BigInteger(255, new SecureRandom());
ECPoint P = G.multiply(x).normalize();
System.out.println(x + " - x");
System.out.println(P + " - P");
String message = "Hello World.";
Bytes m = Bytes.from(message).digest(Digests.sha256());
System.out.println(m.asHex() + " - m");
// SIGN
BigInteger k = new BigInteger(255, new SecureRandom());
ECPoint R = G.multiply(k).normalize();
System.out.println(k + " - k");
System.out.println(R + " - R");
// s = k + H(m || R || P)*x
Bytes mRP = m.add(R.getEncoded(false)).add(P.getEncoded(false));
BigInteger HmRP = mRP.digest(Digests.sha256()).asBigIntegerPositive();
BigInteger s = k.add(HmRP.multiply(x));
System.out.println(s + " - s");
// VERIFY
// sG = R + H(m || R || P)P
ECPoint sG = G.multiply(s).normalize();
ECPoint RHP = R.add(P.multiply(HmRP)).normalize();
System.out.println(sG + " - sG");
System.out.println(RHP + " - RHP");
System.out.println("Verify: " + sG.equals(RHP));
}
}
Outputs:
2533036587208286376539174147898174127709182634692228412511093497440673973526 - x
(5174d02ffd2183c73e9f50966c659df7f39889723e48a0537161a7b6e596672f,faad0a9f36baa7dd16568584bf7630ba9ebacc82a1045687a814bc560d5e99c9,1) - P
f4bb1975bf1f81f76ce824f7536c1e101a8060a632a52289d530a6f600d52c92 - m
35301082731546392324339654524130976664742772749797092970006138607448206000731 - k
(b9580f4d830563822b2114b67d1443ff24bb42992146deab143c2f198a500b6,ff9322c35a94c796ec7e157d52cd9ebf1c332fce19d18b6f032ca4a18dc74205,1) - R
160728906149502997500719808109231042843478780716206780005964271445847331325371136589459765984465607443559980939481886989006481498466567275491212484368867 - s
(cba0748557cdaea3dcefeb9ab310d2718681f8f038ac3fb32ff264e060bf9af4,6c13a1519f019fbcfbf70fa70371dd6414b49c990fb1490de80da815d7d83bef,1) - sG
(cba0748557cdaea3dcefeb9ab310d2718681f8f038ac3fb32ff264e060bf9af4,6c13a1519f019fbcfbf70fa70371dd6414b49c990fb1490de80da815d7d83bef,1) - RHP
Verify: true
ECDH
public class BCECCDHTest {
static final String EC_G = "046B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C2964FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5";
public static void main(String[] args) {
ECNamedCurveParameterSpec ecn = ECNamedCurveTable.getParameterSpec("secp256r1");
ECCurve p256 = ecn.getCurve();
ECPoint G = p256.decodePoint(Bytes.fromHex(EC_G).getBytes());
BigInteger d = new BigInteger(255, new SecureRandom());
ECPoint dG = G.multiply(d).normalize();
System.out.println(d + " - d");
System.out.println(dG + " - dG");
BigInteger k = new BigInteger(255, new SecureRandom());
ECPoint kG = G.multiply(k).normalize();
System.out.println(k + " - k");
System.out.println(kG + " - kG");
ECPoint kQ = dG.multiply(k).normalize();
ECPoint dkG = kG.multiply(d).normalize();
System.out.println(kQ + "- kdG");
System.out.println(dkG + " - dkG");
}
}
Outputs:
8497170259015010226357615438591155947454283966527800158758512050710295813937 - d
(225096f66804e079cc0d6fac79f3a9859290acbdccaa29fe16acd36c68ce9eb9,6a3fe961ee97503a8d48f93595e6cddb1b5052ce18612e9ab4dddf7efc01afd6,1) - dG
8953799744019712250647492442755447078321881910728171228752570400323438092916 - k
(98e24a2d4e17dcc7a48f7187dee2a52fdc6c275fcd909cc37de270e61c893824,aa68f7052047ee83d0f3b5884619faba881bfdbe6dae5b1e5b26a6d725b7316,1) - kG
(cdbd94f33f7a582d760c2ec3a0ce074b6337b26c790999fe6a77142a9fcca2ee,279b4ff199cd95f9798f3940f3d1e63314381dad8bbf8079310e9e94a93b7ec2,1)- kdG
(cdbd94f33f7a582d760c2ec3a0ce074b6337b26c790999fe6a77142a9fcca2ee,279b4ff199cd95f9798f3940f3d1e63314381dad8bbf8079310e9e94a93b7ec2,1) - dkG
ECIES
To encrypt a message Alice does the following:
- generates a random number and calculates ;
- derives a shared secret: , where (and );
- uses KDF to derive a symmetric encryption and a MAC keys: ;
- encrypts the message: ;
- computes the tag of encrypted message and {: ;
- outputs .
To decrypt the ciphertext Bob does the following:
- derives the shared secret: , where (it is the same as the one Alice derived because , or outputs failed if ;
- derives keys the same way as Alice did: ;
- uses MAC to check the tag and outputs failed if ;
- uses symmetric encryption scheme to decrypt the message .
Safe Curve
SECG
ECDSA Crack
It is imperative to have a random for every signature: from a pair of signatures that use the same , we can compute and .
public class BCECCDSATestCrack {
static final String EC_G = "046B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C2964FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5";
static final String EC_N = "00FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551";
public static void main(String[] args) throws Exception {
ECNamedCurveParameterSpec ecn = ECNamedCurveTable.getParameterSpec("secp256r1");
ECCurve p256 = ecn.getCurve();
ECPoint G = p256.decodePoint(Bytes.fromHex(EC_G).getBytes());
BigInteger n = new BigInteger(Bytes.fromHex(EC_N).getBytes());
BigInteger d = new BigInteger(255, new SecureRandom());
ECPoint Q = G.multiply(d).normalize();
System.out.println(d + " - d");
System.out.println(Q + " - Q");
String message1 = "Hello World.";
byte[] hash1 = Digests.sha256().digest(message1.getBytes(StandardCharsets.UTF_8)).getBytes();
BigInteger e1 = new BigInteger(1, hash1);
System.out.println(e1 + " - e1");
String message2 = "Hello World 2.";
byte[] hash2 = Digests.sha256().digest(message2.getBytes(StandardCharsets.UTF_8)).getBytes();
BigInteger e2 = new BigInteger(1, hash2);
System.out.println(e2 + " - e2");
// SIGN
// (x1, x2)=kG
BigInteger k = new BigInteger(255, new SecureRandom());
ECPoint kG = G.multiply(k).normalize();
System.out.println(k + " - k");
System.out.println(kG + " - kG");
// r=x1 mod n
// s=k^-1(e+dr) mod n
BigInteger r = kG.getXCoord().toBigInteger().mod(n);
BigInteger s1 = k.modInverse(n).multiply(e1.add(d.multiply(r))).mod(n);
BigInteger s2 = k.modInverse(n).multiply(e2.add(d.multiply(r))).mod(n);
System.out.println(r + " - r");
System.out.println(s1 + " - s1");
System.out.println(s2 + " - s2");
BigInteger r_mul_s1_sub_s2 = r.multiply(s1.subtract(s2));
BigInteger e1_mul_s2_sub_e2_mul_s1 = e1.multiply(s2).subtract(e2.multiply(s1));
System.out.println(r_mul_s1_sub_s2.modInverse(n).multiply(e1_mul_s2_sub_e2_mul_s1).mod(n) + " - cracked d");
}
}
Outputs:
51466347005671828303957295741616726156421161252085572664583613357909095613902 - d
(f931f7bb9a2323c5a5dd1b4613112faf25942a14dac8dfc7e33450dd9e30dacb,fc1915d433c980b4bd00ac187bed3e70250d7fa8530a95a13aa35536d002ee8a,1) - Q
110694911173530595670578600930991710652668442267815723053237189464905523473554 - e1
66922269942863644750565189256646477005079910784186234771680458426111528822211 - e2
41683346796553297944617074501359868116670569134809166234464449225150848805153 - k
(b664941a9efeee94f70e02b394e98f07d1f385e4d8df2cf1d308b57cc25845d7,e4c4d73e9011771df96ed896e9e118c426e15c78673aa69916b2d6686776fa4,1) - kG
82498645324794474475605788790608602539149661560076504197275318302310303024599 - r
106427360426020067926327791739910945706371013406349559855935733611384228853644 - s1
48034008218942858557761667527638353342161906478309771728588300169611795827588 - s2
51466347005671828303957295741616726156421161252085572664583613357909095613902 - cracked d
https://events.ccc.de/
Assisted Software
在GF(p)上的椭圆曲线点加运算
在GF(p)上的椭圆曲线纯量乘法
在R上的椭圆曲线点加运算
在R上的椭圆曲线纯量乘法
Curves over Finite Fields
Elliptic Curves over Finite Fields
Curve Names
SECG | ANSI X9.62 | NIST |
---|---|---|
sect163k1 | NIST K-163 | |
sect163r1 | ||
sect163r2 | NIST B-163 | |
sect193r1 | ||
sect193r2 | ||
sect233k1 | NIST K-233 | |
sect233r1 | NIST B-233 | |
sect239k1 | ||
sect283k1 | NIST K-283 | |
sect283r1 | NIST B-283 | |
sect409k1 | NIST K-409 | |
sect409r1 | NIST B-409 | |
sect571k1 | NIST K-571 | |
sect571r1 | NIST B-571 | |
secp160k1 | ||
secp160r1 | ||
secp160r2 | ||
secp192k1 | ||
secp192r1 | prime192v1 | NIST P-192 |
secp224k1 | ||
secp224r1 | NIST P-224 | |
secp256k1 | ||
secp256r1 | prime256v1 | NIST P-256 |
secp384r1 | NIST P-384 | |
secp521r1 | NIST P-521 |
Curve Security
Security dangers of the NIST curves.pdf
Koblitz Curves and its practical uses in Bitcoin security.pdf
Reference
- h
t - Standards for Efficient Cryptography Groupt p : / / w w w . s e c g . o r g / - h
t t p s : / / w w w . c r y p t o p p . c o m / w i k i / E l l i p t i c _ C u r v e _ C r y p t o g r a p h y - h
t - ECC加密算法入门介绍t p : / / w w w . p e d i y . c o m / k s s d / p e d i y 0 6 / p e d i y 6 0 1 4 . h t m - h
t - SEC 2: Recommended Elliptic Curve Domain Parameters, Version 1.0t p : / / w w w . s e c g . o r g / S E C 2 - V e r - 1 . 0 . p d f - h
t - How to manipulate curve standards: a white paper for the black hatt p s : / / e p r i n t . i a c r . o r g / 2 0 1 4 / 5 7 1 . p d f - h
t - 椭圆典线密码系统(ECC)t p : / / w w w . s l i d e s e r v e . c o m / r o s s - o s b o r n / e c c - h
t - 一个关于椭圆曲线密码学的初级读本(相当容易懂)t p : / / 8 b t c . c o m / t h r e a d - 1 2 4 0 - 1 - 1 . h t m l - h
t t p s : / / a r s t e c h n i c a . c o m / s e c u r i t y / 2 0 1 3 / 1 0 / a - r e l a t i v e l y - e a s y - t o - u n d e r s t a n d - p r i m e r - o n - e l l i p t i c - c u r v e - c r y p t o g r a p h y / - h
t t p s : / / e v e n t s . c c c . d e / c o n g r e s s / 2 0 1 0 / F a h r p l a n / a t t a c h m e n t s / 1 7 8 0 _ 2 7 c 3 _ c o n s o l e _ h a c k i n g _ 2 0 1 0 . p d f - h
t t p : / / s t a r . a u s t . e d u . c n / ~ x j f a n g / c r y p t o / - h
t t p s : / / e n . w i k i p e d i a . o r g / w i k i / I n t e g r a t e d _ E n c r y p t i o n _ S c h e m e - h
t t p : / / i n f o s e c u r i t y . c h / 2 0 1 0 0 9 2 6 / n o t - e v e r y - e l l i p t i c - c u r v e - i s - t h e - s a m e - t r o u g h - o n - e c c - s e c u r i t y / - h
t t p s : / / b l o g . c s d n . n e t / w z y z z u / a r t i c l e / d e t a i l s / 5 0 6 3 5 6 9 6 - h
t t p : / / w w w . d i m a . u n i g e . i t / ~ m o r a f e / M a t e r i a l e C T C / E l l i p t . p d f - h
t - Elliptic Curve Cryptography: finite fields and discrete logarithmst p : / / a n d r e a . c o r b e l l i n i . n a m e / 2 0 1 5 / 0 5 / 2 3 / e l l i p t i c - c u r v e - c r y p t o g r a p h y - f i n i t e - f i e l d s - a n d - d i s c r e t e - l o g a r i t h m s / - h
t t p s : / / b i t c o i n . s t a c k e x c h a n g e . c o m / q u e s t i o n s / 3 8 7 4 0 / b i t c o i n - h o w - t o - g e t - x - v a l u e - f r o m - y - h
t t p s : / / c r y p t o . s t a c k e x c h a n g e . c o m / q u e s t i o n s / 1 8 1 0 5 / h o w - d o e s - r e c o v e r i n g - t h e - p u b l i c - k e y - f r o m - a n - e c d s a - s i g n a t u r e - w o r k - h
t - Schnorr签名介绍t p s : / / p a n z h i b i a o . c o m / 2 0 1 9 / 0 2 / 2 8 / s c h n o r r - s i g a t u r e /