Elliptic-Curves

ECDSA secp256k1 的完整測試向量集

  • November 23, 2021

儘管有幾種 ECDSA secp256k1 public 可以在網際網路上實現(最流行的是OpenSSL),但似乎沒有完整的測試向量集可用。

我能找到的少數測試向量總是會遺漏一些重要資訊:

  • 不提供散列整數或安全隨機整數 k。
  • 不提供 (r,s) 簽名對,而是僅根據比特幣協議格式提供它的一些雜湊值。

一套完整的測試向量將需要以下資訊:

  1. 私鑰
  2. 公鑰 x 座標
  3. 公鑰y座標
  4. 雜湊
  5. 安全隨機整數 k(見下文註釋)
  6. 簽名
  7. 簽名

例子:

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 測試的直接副本,但當然參數不同。

引用自:https://crypto.stackexchange.com/questions/41316