From a6fd71b7675f5704c8a2f907aa988813323e1548 Mon Sep 17 00:00:00 2001 From: Matthew Leon Date: Mon, 30 Apr 2018 18:08:39 -0400 Subject: [PATCH] ECKey: Factor out findRecoveryId() from signMessage(). --- .../main/java/org/bitcoinj/core/ECKey.java | 32 ++++++++----- .../java/org/bitcoinj/core/ECKeyTest.java | 46 +++++++++++++++++++ 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/ECKey.java b/core/src/main/java/org/bitcoinj/core/ECKey.java index 103a5add..9c49ef81 100644 --- a/core/src/main/java/org/bitcoinj/core/ECKey.java +++ b/core/src/main/java/org/bitcoinj/core/ECKey.java @@ -865,17 +865,7 @@ public class ECKey implements EncryptableItem { byte[] data = formatMessageForSigning(message); Sha256Hash hash = Sha256Hash.twiceOf(data); ECDSASignature sig = sign(hash, aesKey); - // Now we have to work backwards to figure out the recId needed to recover the signature. - int recId = -1; - for (int i = 0; i < 4; i++) { - ECKey k = ECKey.recoverFromSignature(i, sig, hash, isCompressed()); - if (k != null && k.pub.equals(pub)) { - recId = i; - break; - } - } - if (recId == -1) - throw new RuntimeException("Could not construct a recoverable key. This should never happen."); + byte recId = findRecoveryId(hash, sig); int headerByte = recId + 27 + (isCompressed() ? 4 : 0); byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S sigData[0] = (byte)headerByte; @@ -940,6 +930,26 @@ public class ECKey implements EncryptableItem { throw new SignatureException("Signature did not match for message"); } + /** + * Returns the recovery ID, a byte with value between 0 and 3, inclusive, that specifies which of 4 possible + * curve points was used to sign a message. This value is also referred to as "v". + * + * @throws RuntimeException if no recovery ID can be found. + */ + public byte findRecoveryId(Sha256Hash hash, ECDSASignature sig) { + byte recId = -1; + for (byte i = 0; i < 4; i++) { + ECKey k = ECKey.recoverFromSignature(i, sig, hash, isCompressed()); + if (k != null && k.pub.equals(pub)) { + recId = i; + break; + } + } + if (recId == -1) + throw new RuntimeException("Could not construct a recoverable key. This should never happen."); + return recId; + } + /** *

Given the components of a signature and a selector value, recover and return the public key * that generated the signature according to the algorithm in SEC1v2 section 4.1.6.

diff --git a/core/src/test/java/org/bitcoinj/core/ECKeyTest.java b/core/src/test/java/org/bitcoinj/core/ECKeyTest.java index e3d61e24..fa4d5ec5 100644 --- a/core/src/test/java/org/bitcoinj/core/ECKeyTest.java +++ b/core/src/test/java/org/bitcoinj/core/ECKeyTest.java @@ -239,6 +239,52 @@ public class ECKeyTest { assertEquals(expectedAddress, gotAddress); } + @Test + public void findRecoveryId() { + ECKey key = new ECKey(); + String message = "Hello World!"; + Sha256Hash hash = Sha256Hash.of(message.getBytes()); + ECKey.ECDSASignature sig = key.sign(hash); + key = ECKey.fromPublicOnly(key.getPubKeyPoint()); + + List possibleRecIds = Lists.newArrayList((byte) 0, (byte) 1, (byte) 2, (byte) 3); + byte recId = key.findRecoveryId(hash, sig); + assertTrue(possibleRecIds.contains(recId)); + } + + @Test + public void keyRecoveryTestVector() { + // a test that exercises key recovery with findRecoveryId() on a test vector + // test vector from https://crypto.stackexchange.com/a/41339 + ECKey key = ECKey.fromPrivate( + new BigInteger("ebb2c082fd7727890a28ac82f6bdf97bad8de9f5d7c9028692de1a255cad3e0f", 16)); + String message = "Maarten Bodewes generated this test vector on 2016-11-08"; + Sha256Hash hash = Sha256Hash.of(message.getBytes()); + ECKey.ECDSASignature sig = key.sign(hash); + key = ECKey.fromPublicOnly(key.getPubKeyPoint()); + + byte recId = key.findRecoveryId(hash, sig); + byte expectedRecId = 0; + assertEquals(recId, expectedRecId); + + ECKey pubKey = ECKey.fromPublicOnly(key.getPubKeyPoint()); + ECKey recoveredKey = ECKey.recoverFromSignature(recId, sig, hash, true); + assertEquals(recoveredKey, pubKey); + } + + @Test + public void keyRecoveryWithFindRecoveryId() throws Exception { + ECKey key = new ECKey(); + String message = "Hello World!"; + Sha256Hash hash = Sha256Hash.of(message.getBytes()); + ECKey.ECDSASignature sig = key.sign(hash); + + byte recId = key.findRecoveryId(hash, sig); + ECKey pubKey = ECKey.fromPublicOnly(key.getPubKeyPoint()); + ECKey recoveredKey = ECKey.recoverFromSignature(recId, sig, hash, true); + assertEquals(recoveredKey, pubKey); + } + @Test public void keyRecovery() throws Exception { ECKey key = new ECKey();