ECKey: Factor out findRecoveryId() from signMessage().

This commit is contained in:
Matthew Leon
2018-04-30 18:08:39 -04:00
committed by Andreas Schildbach
parent a5aac0f4b7
commit a6fd71b767
2 changed files with 67 additions and 11 deletions

View File

@@ -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;
}
/**
* <p>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.</p>

View File

@@ -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<Byte> 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();