diff --git a/build.gradle b/build.gradle index 1cf2b327..c12027d8 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ apply plugin: 'java' group = 'network.casper' // Version number update for release -version='2.5.7' +version='2.5.8' sourceCompatibility = 1.8 targetCompatibility = 1.8 diff --git a/src/main/java/com/syntifi/crypto/key/Secp256k1PublicKey.java b/src/main/java/com/syntifi/crypto/key/Secp256k1PublicKey.java index 8a2283f5..b9960900 100644 --- a/src/main/java/com/syntifi/crypto/key/Secp256k1PublicKey.java +++ b/src/main/java/com/syntifi/crypto/key/Secp256k1PublicKey.java @@ -4,13 +4,14 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import org.bouncycastle.asn1.*; +import org.web3j.crypto.ECDSASignature; import org.web3j.crypto.Hash; import org.web3j.crypto.Sign; -import org.web3j.crypto.Sign.SignatureData; -import java.io.*; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; import java.math.BigInteger; -import java.security.GeneralSecurityException; import java.util.Arrays; /** @@ -62,21 +63,52 @@ public void writePublicKey(final Writer writer) throws IOException { PemFileHelper.writePemFile(writer, derKey.getEncoded(), ASN1Identifiers.PUBLIC_KEY_DER_HEADER); } + /** + * Iterates possible signature combinations and possible recovery id's + * Casper does not use signature.v so we have to iterate v + * We don't know v so we have to iterate the possible recover id's + * Converts to short public key for comparison + * + * @param message the signed message + * @param signature the signature to check against + * @return true|false public key found + */ @Override - public Boolean verify(final byte[] message, final byte[] signature) throws GeneralSecurityException { - //TODO: Double check the issue the getV(), for now we are trying with both (27 and 28) - final SignatureData signatureData1 = new SignatureData( - (byte) 27, - Arrays.copyOfRange(signature, 0, 32), - Arrays.copyOfRange(signature, 32, 64)); - final BigInteger derivedKey1 = Sign.signedMessageHashToKey(Hash.sha256(message), signatureData1); - final SignatureData signatureData2 = new SignatureData( - (byte) 28, - Arrays.copyOfRange(signature, 0, 32), - Arrays.copyOfRange(signature, 32, 64)); - final BigInteger derivedKey2 = Sign.signedMessageHashToKey(Hash.sha256(message), signatureData2); - return Arrays.equals(Secp256k1PublicKey.getShortKey(derivedKey1.toByteArray()), getKey()) || - Arrays.equals(Secp256k1PublicKey.getShortKey(derivedKey2.toByteArray()), getKey()); + public Boolean verify(byte[] message, byte[] signature) { + + //We need the Public key's short key + byte[] keyToFind = (getKey().length > 33) ? getShortKey(getKey()) : getKey(); + + //Looping possible v's of the signature + for (int i = 27; i <= 34; i++) { + + final Sign.SignatureData signatureData = + new Sign.SignatureData( + (byte) (i), + Arrays.copyOfRange(signature, 0, 32), + Arrays.copyOfRange(signature, 32, 64)); + + //iterate the recovery id's + for (int j = 0; j < 4; j++) { + + final ECDSASignature ecdsaSignature = new ECDSASignature(new BigInteger(1, signatureData.getR()), + new BigInteger(1, signatureData.getS())); + final BigInteger recoveredKey = Sign.recoverFromSignature((byte) j, ecdsaSignature, Hash.sha256(message)); + + if (recoveredKey != null) { + + final byte[] keyFromSignature = getShortKey(recoveredKey.toByteArray()); + + if (Arrays.equals(keyFromSignature, keyToFind)) { + return true; + } + } + } + + } + + return false; + } /** diff --git a/src/test/java/com/syntifi/crypto/key/Secp256k1PublicKeyTests.java b/src/test/java/com/syntifi/crypto/key/Secp256k1PublicKeyTests.java index 74a6672c..1d0f0fc1 100644 --- a/src/test/java/com/syntifi/crypto/key/Secp256k1PublicKeyTests.java +++ b/src/test/java/com/syntifi/crypto/key/Secp256k1PublicKeyTests.java @@ -8,7 +8,6 @@ import java.io.File; import java.io.IOException; import java.net.URISyntaxException; -import java.security.GeneralSecurityException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; @@ -50,7 +49,7 @@ void writePublicKey_should_equal_source_file() throws URISyntaxException, IOExce } @Test - void verify_should_be_ok() throws URISyntaxException, IOException, GeneralSecurityException { + void verify_should_be_ok() throws URISyntaxException, IOException { String hexSignature = "ea5b38fd0db5fb3d871c47fde1fa4c4db75d1a9e1c0ac54d826e178ee0e63707176b4e63b4f838bd031f007fffd6a4f71d920a10c48ea53dd1573fa2b58a829e"; Secp256k1PublicKey pubKey = new Secp256k1PublicKey(); @@ -60,4 +59,91 @@ void verify_should_be_ok() throws URISyntaxException, IOException, GeneralSecuri assertTrue(pubKey.verify("Test message".getBytes(), Hex.decode(hexSignature))); } -} \ No newline at end of file + + @Test + void signAndRecoverPublicKey_1() throws URISyntaxException, IOException { + + //Get the private key + Secp256k1PrivateKey privKey = new Secp256k1PrivateKey(); + String filePath = getResourcesKeyPath("secp256k1/secret_key.pem"); + privKey.readPrivateKey(filePath); + + //Derive the public key + Secp256k1PublicKey publicKey = (Secp256k1PublicKey) privKey.derivePublicKey(); + + String message = "bc81ca4de9b3a991a6514eddf0e994e0035c7ba58f333c4d7ba5dd18b4c9c547"; + + //Generate the signature + byte[] signature = privKey.sign(message.getBytes()); + + //Test + assert publicKey.verify(message.getBytes(), signature); + + } + + @Test + void signAndRecoverPublicKey_2() throws URISyntaxException, IOException { + + //Get the private key + Secp256k1PrivateKey privKey = new Secp256k1PrivateKey(); + + String filePath = getResourcesKeyPath("secp256k1/secret_key.pem"); + privKey.readPrivateKey(filePath); + + //Derive the public key + Secp256k1PublicKey publicKey = (Secp256k1PublicKey) privKey.derivePublicKey(); + + String message = "1df13c9aaa8217657b7e5ec2442594735eeb4ca7e764877b3d2b593c3909d15f"; + + //Generate the signature + byte[] signature = privKey.sign(message.getBytes()); + + //Test + assert publicKey.verify(message.getBytes(), signature); + + } + + @Test + void signAndRecoverPublicKey_3() throws URISyntaxException, IOException { + + //Get the private key + Secp256k1PrivateKey privKey = new Secp256k1PrivateKey(); + String filePath = getResourcesKeyPath("secp256k1/secret_key.pem"); + privKey.readPrivateKey(filePath); + + //Derive the public key + Secp256k1PublicKey publicKey = (Secp256k1PublicKey) privKey.derivePublicKey(); + + String message = "Test message"; + + //Generate the signature + byte[] signature = privKey.sign(message.getBytes()); + + //Test + assert publicKey.verify(message.getBytes(), signature); + + } + + @Test + void signAndRecoverPublicKey_4() throws URISyntaxException, IOException { + + //Get the private key + Secp256k1PrivateKey privKey = new Secp256k1PrivateKey(); + String filePath = getResourcesKeyPath("secp256k1/secret_key.pem"); + privKey.readPrivateKey(filePath); + + //Derive the public key + Secp256k1PublicKey publicKey = (Secp256k1PublicKey) privKey.derivePublicKey(); + + String message = "Test message"; + + //Generate the signature + byte[] signature = privKey.sign(message.getBytes()); + + //Test + assert publicKey.verify(message.getBytes(), signature); + assert !publicKey.verify("Not test message".getBytes(), signature); + + } + +}