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.