9
$\begingroup$

Although there are several implementations of ECDSA secp256k1 public available over the internet (the most popular being OpenSSL), it seems that there are no complete set of test-vectors available.

The few test vectors I could find always miss some important information:

  • do not provide the hash integer or the secure random integer k.
  • do not provide the (r,s) signature pair, instead only provides some hash of it according to bitcoin protocol format.

A complete set of test-vectors would require the following information:

  1. private key
  2. public key x-coordinate
  3. public key y-coordinate
  4. hash
  5. secure random integer k (see note bellow)
  6. r signature
  7. s signature

Example:

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

Note that a set o test-vectors without the random integer k might also be helpful for validation purposes:

  • first validate the signature verification code
  • than use the validated signature verification code to validate the signature generation code

The reason I consider important to include the random integer k is because with it you can validate different parts of the code independently of each other, therefore reducing the risks of hidden bugs.


Question
Is there any public available test-vector with the aforementioned set of information?

$\endgroup$
2
  • $\begingroup$ your k is incorrect : this is correct k DC87789C4C1A09C97FF4DE72C0D0351F261F10A2B9009C80AEE70DDEC77201A0 $\endgroup$ Commented Apr 19, 2018 at 4:12
  • 1
    $\begingroup$ @BlomdahlShin k is a random integer. There is no such thing as an incorrect one. $\endgroup$
    – Mark Messa
    Commented Apr 19, 2018 at 15:04

2 Answers 2

4
$\begingroup$

OK, so I quickly found out that there is a test in Bouncy Castle called:

org.bouncycastle.crypto.test.ECTest.testECDSASecP224k1sha256()

which tests exactly what you are trying to do.

So I decided to create a new test that duplicates this test, but for SecP256k1 and SHA-256.

I've generated the following test vectors (I'll use hexadecimals):

 d = ebb2c082fd7727890a28ac82f6bdf97bad8de9f5d7c9028692de1a255cad3e0f
 k = 49a0d7b786ec9cde0d0721d72804befd06571c974b191efb42ecf322ba9ddd9a
 h = 4b688df40bcedbe641ddb16ff0a1842d9c67ea1c3bf63f3e0471baa664531d1a

resulting in:

 r = 241097efbf8b63bf145c8961dbdf10c310efbb3b2676bbc0f8b08505c9e2f795
 s = 021006b7838609339e8b415a7f9acb1b661828131aef1ecbc7955dfb01f3ca0e

note that $s$ starts with 6 zero bits, which should be fine.

Finally, you'd want to verify the signature as well of course, so here is the public key:

 q = 04779dd197a5df977ed2cf6cb31d82d43328b790dc6b3b7d4437a427bd5847dfcde94b724a555b6d017bb7607c3e3281daf5b1699d6ef4124975c9237b917d426f

where $q$ is an uncompressed point, consisting of the following coordinates:

x = 779dd197a5df977ed2cf6cb31d82d43328b790dc6b3b7d4437a427bd5847dfcd
y = e94b724a555b6d017bb7607c3e3281daf5b1699d6ef4124975c9237b917d426f

Method of generating the parameters:

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));
}

To make sure that the value of $k$ is used directly within the algorithm I did some tests. A $k$ consisting of all FF bytes and a shorter $k$ both failed as expected . A $k$ consisting of 7F byte followed by all FF bytes does work. So the value $k$ within the FixedSecureRandom constructor is indeed directly used.


And finally the method of testing the deterministic result:

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");
    }
}

which is a direct copy of the test for SecP224k1 and SHA256, but of course with the different parameters.

$\endgroup$
1
  • 1
    $\begingroup$ This really helped me out. For anyone out there who meets this in the future coming from the .NET world, there is a subtle difference in the BouncyCastle library that causes an odd result. The r value above appears to be correct while the s value comes out different. The headache was that the BouncyCastle BigInteger .NET ctor has an extra parameter that you have to set to explicitly force it to a positive number (eg for the key). Also you have to check for endianness when converting byte arrays and reverse. The above then works. $\endgroup$
    – Sentinel
    Commented Jan 6, 2018 at 15:05
0
$\begingroup$

The secp256k1 ECDSA vectors provided in bouncycastle are correct, exception made for the s field in the signature. My verification results in:

M = octet[32] 4b688df40bcedbe641ddb16ff0a1842d9c67ea1c3bf63f3e0471baa664531d1a,
d = octet[32] ebb2c082fd7727890a28ac82f6bdf97bad8de9f5d7c9028692de1a255cad3e0f,
k = octet[32] 49a0d7b786ec9cde0d0721d72804befd06571c974b191efb42ecf322ba9ddd9a,
r = octet[32] 241097efbf8b63bf145c8961dbdf10c310efbb3b2676bbc0f8b08505c9e2f795,
s = octet[32] 139c98ddeba50a63bbc95014a47ba1779db5ac846a85eee69bbd95b58bc96044,
x = octet[32] 779dd197a5df977ed2cf6cb31d82d43328b790dc6b3b7d4437a427bd5847dfcd,
y = octet[32] e94b724a555b6d017bb7607c3e3281daf5b1699d6ef4124975c9237b917d426f
P = octet[65] 04779dd197a5df977ed2cf6cb31d82d43328b790dc6b3b7d4437a427bd5847dfcde94b724a555b6d017bb7607c3e3281daf5b1699d6ef4124975c9237b917d426f

Pseudocode (computable in Zenroom)

kp = ECDH.new('secp256k1')
kp:private(d)
x, y = kp:public_xy()
S = kp:sign(M,k)
I.print({ d = d,
          k = k,
          M = M,
          x = x,
          y = y,
          P = kp:public(),
          r = S.r,
          s = S.s })

For clarity, here a legend:

M = message to be signed
d = private key
k = random k factor for sign
x = public key x coordinate
y = private key x coordinate
P = public key, IEEE P1363 notation
r = signature component r
s = signature component s
$\endgroup$
3
  • $\begingroup$ OK, so as this answer does seem on topic, I'll try and make it better by asking what the "specific settings" are that make $s$ differ compared to my answer. $\endgroup$
    – Maarten Bodewes
    Commented Sep 22, 2019 at 10:10
  • $\begingroup$ I am not able to reproduce your setup, so I really don't know. I agree is very interesting to find out and I may stand corrected. $\endgroup$
    – user73105
    Commented Sep 22, 2019 at 10:16
  • $\begingroup$ s = octet[32] 139c ... is incorrect. This can be verified by the verification procedure. $\endgroup$
    – Bo LIN
    Commented Aug 4, 2023 at 19:46

Not the answer you're looking for? Browse other questions tagged or ask your own question.