6
$\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$
  • $\begingroup$ your k is incorrect : this is correct k DC87789C4C1A09C97FF4DE72C0D0351F261F10A2B9009C80AEE70DDEC77201A0 $\endgroup$ – Blomdahl Shin Apr 19 '18 at 4:12
  • $\begingroup$ @BlomdahlShin k is a random integer. There is no such thing as an incorrect one. $\endgroup$ – Mark Messa Apr 19 '18 at 15:04
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 (with a message the same size of a SHA-256 hash).

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
 M = 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 the message (hash) M
    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[] M = new byte[dig.getDigestSize()];
    dig.doFinal(M, 0);
    System.out.printf("M = %s%n", Hex.toHexString(M));

    // 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[] M = Hex.decode("4b688df40bcedbe641ddb16ff0a1842d9c67ea1c3bf63f3e0471baa664531d1a");

    ECDSASigner dsa = new ECDSASigner();

    dsa.init(true, new ParametersWithRandom(priKey, k));

    BigInteger[] sig = dsa.generateSignature(M);

    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(M, 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$
  • $\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 Jan 6 '18 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$
  • $\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 Sep 22 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$ – Jaromil Sep 22 at 10:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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