Elliptic-Curves
ECDSA secp256k1 的完整測試向量集
儘管有幾種 ECDSA secp256k1 public 可以在網際網路上實現(最流行的是OpenSSL),但似乎沒有完整的測試向量集可用。
我能找到的少數測試向量總是會遺漏一些重要資訊:
- 不提供散列整數或安全隨機整數 k。
- 不提供 (r,s) 簽名對,而是僅根據比特幣協議格式提供它的一些雜湊值。
一套完整的測試向量將需要以下資訊:
- 私鑰
- 公鑰 x 座標
- 公鑰y座標
- 雜湊
- 安全隨機整數 k(見下文註釋)
- 簽名
- 簽名
例子:
1. private key: D30519BCAE8D180DBFCC94FE0B8383DC310185B0BE97B4365083EBCECCD75759 2. public key x-coordinate: 3AF1E1EFA4D1E1AD5CB9E3967E98E901DAFCD37C44CF0BFB6C216997F5EE51DF 3. public key y-coordinate: E4ACAC3E6F139E0C7DB2BD736824F51392BDA176965A1C59EB9C3C5FF9E85D7A 4. hash: 3F891FDA3704F0368DAB65FA81EBE616F4AA2A0854995DA4DC0B59D2CADBD64F 5. secure random integer k: CF554F5F4224223D52DC9CA784478FAC3C1A0D0419FDEEF27849A81846C71BA3 6. r signature: A5C7B7756D34D8AAF6AA68F0B71644F0BEF90D8BFD126CE951B6060498345089 7. s signature: BC9644F1625AF13841E589FD00653AE8C763309184EA0DE481E8F06709E5D1CB
請注意,沒有隨機整數k的一組 o 測試向量也可能有助於驗證目的:
- 首先驗證簽名驗證碼
- 而不是使用已驗證的簽名驗證碼來驗證簽名生成碼
我認為包含隨機整數k很重要的原因是,您可以使用它獨立地驗證程式碼的不同部分,從而降低隱藏錯誤的風險。
問題
是否有任何具有上述資訊集的公共可用測試向量?
好的,所以我很快發現Bouncy Castle中有一個測試叫:
org.bouncycastle.crypto.test.ECTest.testECDSASecP224k1sha256()
它準確地測試了您正在嘗試做的事情。
所以我決定創建一個新的測試來複製這個測試,但是針對 SecP256k1 和 SHA-256。
我生成了以下測試向量(我將使用十六進制):
d = ebb2c082fd7727890a28ac82f6bdf97bad8de9f5d7c9028692de1a255cad3e0f k = 49a0d7b786ec9cde0d0721d72804befd06571c974b191efb42ecf322ba9ddd9a h = 4b688df40bcedbe641ddb16ff0a1842d9c67ea1c3bf63f3e0471baa664531d1a
導致:
r = 241097efbf8b63bf145c8961dbdf10c310efbb3b2676bbc0f8b08505c9e2f795 s = 021006b7838609339e8b415a7f9acb1b661828131aef1ecbc7955dfb01f3ca0e
注意 $ s $ 從 6 個零位開始,應該沒問題。
最後,您當然也想驗證簽名,所以這裡是公鑰:
q = 04779dd197a5df977ed2cf6cb31d82d43328b790dc6b3b7d4437a427bd5847dfcde94b724a555b6d017bb7607c3e3281daf5b1699d6ef4124975c9237b917d426f
在哪裡 $ q $ 是一個未壓縮的點,由以下座標組成:
x = 779dd197a5df977ed2cf6cb31d82d43328b790dc6b3b7d4437a427bd5847dfcd y = e94b724a555b6d017bb7607c3e3281daf5b1699d6ef4124975c9237b917d426f
生成參數的方法:
private static final void createAndPrintRequiredParameters() { // code used to generate the random key pair (public point converted to uncompressed point encoding manually) ECKeyPairGenerator gen = new ECKeyPairGenerator(); X9ECParameters p = SECNamedCurves.getByName("secp256k1"); ECDomainParameters params = new ECDomainParameters(p.getCurve(), p.getG(), p.getN(), p.getH()); SecureRandom kpr = new SecureRandom(); ECKeyGenerationParameters genParams = new ECKeyGenerationParameters(params, kpr); gen.init(genParams); AsymmetricCipherKeyPair ecKeyPair = gen.generateKeyPair(); ECPrivateKeyParameters ecPrivate = (ECPrivateKeyParameters) ecKeyPair.getPrivate(); ECPublicKeyParameters ecPublic = (ECPublicKeyParameters) ecKeyPair.getPublic(); System.out.println(ecPrivate.getD().toString(16)); System.out.println(ecPublic.getQ().toString()); // code used to generate h (the hash of the message) byte[] mesg = "Maarten Bodewes generated this test vector on 2016-11-08".getBytes(StandardCharsets.UTF_8); SHA256Digest dig = new SHA256Digest(); dig.update(mesg, 0, mesg.length); byte[] h = new byte[dig.getDigestSize()]; dig.doFinal(h, 0); System.out.printf("h = %s%n", Hex.toHexString(h)); // code used to generate the random k SecureRandom krng = new SecureRandom(); BigInteger k; do { k = new BigInteger(256, krng); } while (k.compareTo(params.getN()) >= 1); System.out.printf("K = %s%n", k.toString(16)); }
以確保 $ k $ 在算法中直接使用我做了一些測試。一個 $ k $ 由所有
FF
字節和一個較短的 $ k $ 兩者都按預期失敗。一個 $ k $ 由7F
字節後跟所有FF
字節組成確實有效。所以價值 $ k $ 在FixedSecureRandom
建構子內部確實是直接使用的。最後是確定性結果的測試方法:
private static void testECDSASecP256k1sha256() { X9ECParameters p = SECNamedCurves.getByName("secp256k1"); ECDomainParameters params = new ECDomainParameters(p.getCurve(), p.getG(), p.getN(), p.getH()); ECPrivateKeyParameters priKey = new ECPrivateKeyParameters( new BigInteger("ebb2c082fd7727890a28ac82f6bdf97bad8de9f5d7c9028692de1a255cad3e0f", 16), // d params); SecureRandom k = new FixedSecureRandom(Hex.decode("49a0d7b786ec9cde0d0721d72804befd06571c974b191efb42ecf322ba9ddd9a")); byte[] h = Hex.decode("4b688df40bcedbe641ddb16ff0a1842d9c67ea1c3bf63f3e0471baa664531d1a"); ECDSASigner dsa = new ECDSASigner(); dsa.init(true, new ParametersWithRandom(priKey, k)); BigInteger[] sig = dsa.generateSignature(h); BigInteger r = new BigInteger("241097efbf8b63bf145c8961dbdf10c310efbb3b2676bbc0f8b08505c9e2f795", 16); BigInteger s = new BigInteger("21006b7838609339e8b415a7f9acb1b661828131aef1ecbc7955dfb01f3ca0e", 16); if (!r.equals(sig[0])) { fail("r component wrong." + Strings.lineSeparator() + " expecting: " + r + Strings.lineSeparator() + " got : " + sig[0]); } if (!s.equals(sig[1])) { fail("s component wrong." + Strings.lineSeparator() + " expecting: " + s + Strings.lineSeparator() + " got : " + sig[1]); } // Verify the signature ECPublicKeyParameters pubKey = new ECPublicKeyParameters( params.getCurve().decodePoint(Hex.decode("04779dd197a5df977ed2cf6cb31d82d43328b790dc6b3b7d4437a427bd5847dfcde94b724a555b6d017bb7607c3e3281daf5b1699d6ef4124975c9237b917d426f")), // Q params); dsa.init(false, pubKey); if (!dsa.verifySignature(h, sig[0], sig[1])) { fail("signature fails"); } }
這是 SecP224k1 和 SHA256 測試的直接副本,但當然參數不同。