/*
* Copyright (c) [2016] [ <ether.camp> ]
* This file is part of the ethereumJ library.
*
* The ethereumJ library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ethereumJ library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>.
*/
package org.ethereum.crypto;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import org.ethereum.core.Transaction;
import org.ethereum.crypto.ECKey.ECDSASignature;
import org.ethereum.crypto.jce.SpongyCastleProvider;
import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPairGenerator;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.security.SignatureException;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class ECKeyTest {
private static final Logger log = LoggerFactory.getLogger(ECKeyTest.class);
private static final SecureRandom secureRandom = new SecureRandom();
private String privString = "c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4";
private BigInteger privateKey = new BigInteger(privString, 16);
private String pubString = "040947751e3022ecf3016be03ec77ab0ce3c2662b4843898cb068d74f698ccc8ad75aa17564ae80a20bb044ee7a6d903e8e8df624b089c95d66a0570f051e5a05b";
private String compressedPubString = "030947751e3022ecf3016be03ec77ab0ce3c2662b4843898cb068d74f698ccc8ad";
private byte[] pubKey = Hex.decode(pubString);
private byte[] compressedPubKey = Hex.decode(compressedPubString);
private String address = "cd2a3d9f938e13cd947ec05abc7fe734df8dd826";
private String exampleMessage = "This is an example of a signed message.";
private String sigBase64 = "HNLOSI9Nop5o8iywXKwbGbdd8XChK0rRvdRTG46RFcb7dcH+UKlejM/8u1SCoeQvu91jJBMd/nXDs7f5p8ch7Ms=";
private String signatureHex = "d2ce488f4da29e68f22cb05cac1b19b75df170a12b4ad1bdd4531b8e9115c6fb75c1fe50a95e8ccffcbb5482a1e42fbbdd6324131dfe75c3b3b7f9a7c721eccb01";
@Test
public void testHashCode() {
Assert.assertEquals(-351262686, ECKey.fromPrivate(privateKey).hashCode());
}
@Test
public void testECKey() {
ECKey key = new ECKey();
assertTrue(key.isPubKeyCanonical());
assertNotNull(key.getPubKey());
assertNotNull(key.getPrivKeyBytes());
log.debug(Hex.toHexString(key.getPrivKeyBytes()) + " :Generated privkey");
log.debug(Hex.toHexString(key.getPubKey()) + " :Generated pubkey");
}
@Test
public void testFromPrivateKey() {
ECKey key = ECKey.fromPrivate(privateKey);
assertTrue(key.isPubKeyCanonical());
assertTrue(key.hasPrivKey());
assertArrayEquals(pubKey, key.getPubKey());
}
@Test(expected = IllegalArgumentException.class)
public void testPrivatePublicKeyBytesNoArg() {
new ECKey((BigInteger) null, null);
fail("Expecting an IllegalArgumentException for using only null-parameters");
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidPrivateKey() throws Exception {
new ECKey(
Security.getProvider("SunEC"),
KeyPairGenerator.getInstance("RSA").generateKeyPair().getPrivate(),
ECKey.fromPublicOnly(pubKey).getPubKeyPoint());
fail("Expecting an IllegalArgumentException for using an non EC private key");
}
@Test
public void testIsPubKeyOnly() {
ECKey key = ECKey.fromPublicOnly(pubKey);
assertTrue(key.isPubKeyCanonical());
assertTrue(key.isPubKeyOnly());
assertArrayEquals(key.getPubKey(), pubKey);
}
@Test(expected = IllegalArgumentException.class)
public void testSignIncorrectInputSize() {
ECKey key = new ECKey();
String message = "The quick brown fox jumps over the lazy dog.";
ECDSASignature sig = key.doSign(message.getBytes());
fail("Expecting an IllegalArgumentException for a non 32-byte input");
}
@Test(expected = ECKey.MissingPrivateKeyException.class)
public void testSignWithPubKeyOnly() {
ECKey key = ECKey.fromPublicOnly(pubKey);
String message = "The quick brown fox jumps over the lazy dog.";
byte[] input = HashUtil.sha3(message.getBytes());
ECDSASignature sig = key.doSign(input);
fail("Expecting an MissingPrivateKeyException for a public only ECKey");
}
@Test(expected = SignatureException.class)
public void testBadBase64Sig() throws SignatureException {
byte[] messageHash = new byte[32];
ECKey.signatureToKey(messageHash, "This is not valid Base64!");
fail("Expecting a SignatureException for invalid Base64");
}
@Test(expected = SignatureException.class)
public void testInvalidSignatureLength() throws SignatureException {
byte[] messageHash = new byte[32];
ECKey.signatureToKey(messageHash, "abcdefg");
fail("Expecting a SignatureException for invalid signature length");
}
@Test
public void testPublicKeyFromPrivate() {
byte[] pubFromPriv = ECKey.publicKeyFromPrivate(privateKey, false);
assertArrayEquals(pubKey, pubFromPriv);
}
@Test
public void testPublicKeyFromPrivateCompressed() {
byte[] pubFromPriv = ECKey.publicKeyFromPrivate(privateKey, true);
assertArrayEquals(compressedPubKey, pubFromPriv);
}
@Test
public void testGetAddress() {
ECKey key = ECKey.fromPublicOnly(pubKey);
assertArrayEquals(Hex.decode(address), key.getAddress());
}
@Test
public void testToString() {
ECKey key = ECKey.fromPrivate(BigInteger.TEN); // An example private key.
assertEquals("pub:04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7", key.toString());
}
@Test
public void testEthereumSign() throws IOException {
ECKey key = ECKey.fromPrivate(privateKey);
System.out.println("Secret\t: " + Hex.toHexString(key.getPrivKeyBytes()));
System.out.println("Pubkey\t: " + Hex.toHexString(key.getPubKey()));
System.out.println("Data\t: " + exampleMessage);
byte[] messageHash = HashUtil.sha3(exampleMessage.getBytes());
ECDSASignature signature = key.sign(messageHash);
String output = signature.toBase64();
System.out.println("Signtr\t: " + output + " (Base64, length: " + output.length() + ")");
assertEquals(sigBase64, output);
}
/**
* Verified via https://etherchain.org/verify/signature
*/
@Test
public void testEthereumSignToHex() {
ECKey key = ECKey.fromPrivate(privateKey);
byte[] messageHash = HashUtil.sha3(exampleMessage.getBytes());
ECDSASignature signature = key.sign(messageHash);
String output = signature.toHex();
System.out.println("Signature\t: " + output + " (Hex, length: " + output.length() + ")");
assertEquals(signatureHex, output);
}
@Test
public void testVerifySignature1() {
ECKey key = ECKey.fromPublicOnly(pubKey);
BigInteger r = new BigInteger("28157690258821599598544026901946453245423343069728565040002908283498585537001");
BigInteger s = new BigInteger("30212485197630673222315826773656074299979444367665131281281249560925428307087");
ECDSASignature sig = ECDSASignature.fromComponents(r.toByteArray(), s.toByteArray(), (byte) 28);
key.verify(HashUtil.sha3(exampleMessage.getBytes()), sig);
}
@Test
public void testVerifySignature2() {
BigInteger r = new BigInteger("c52c114d4f5a3ba904a9b3036e5e118fe0dbb987fe3955da20f2cd8f6c21ab9c", 16);
BigInteger s = new BigInteger("6ba4c2874299a55ad947dbc98a25ee895aabf6b625c26c435e84bfd70edf2f69", 16);
ECDSASignature sig = ECDSASignature.fromComponents(r.toByteArray(), s.toByteArray(), (byte) 0x1b);
byte[] rawtx = Hex.decode("f82804881bc16d674ec8000094cd2a3d9f938e13cd947ec05abc7fe734df8dd8268609184e72a0006480");
byte[] rawHash = HashUtil.sha3(rawtx);
byte[] address = Hex.decode("cd2a3d9f938e13cd947ec05abc7fe734df8dd826");
try {
ECKey key = ECKey.signatureToKey(rawHash, sig);
System.out.println("Signature public key\t: " + Hex.toHexString(key.getPubKey()));
System.out.println("Sender is\t\t: " + Hex.toHexString(key.getAddress()));
assertEquals(key, ECKey.signatureToKey(rawHash, sig.toBase64()));
assertEquals(key, ECKey.recoverFromSignature(0, sig, rawHash));
assertArrayEquals(key.getPubKey(), ECKey.recoverPubBytesFromSignature(0, sig, rawHash));
assertArrayEquals(address, key.getAddress());
assertArrayEquals(address, ECKey.signatureToAddress(rawHash, sig));
assertArrayEquals(address, ECKey.signatureToAddress(rawHash, sig.toBase64()));
assertArrayEquals(address, ECKey.recoverAddressFromSignature(0, sig, rawHash));
assertTrue(key.verify(rawHash, sig));
} catch (SignatureException e) {
fail();
}
}
@Test
public void testVerifySignature3() throws SignatureException {
byte[] rawtx = Hex.decode("f88080893635c9adc5dea000008609184e72a00094109f3535353535353535353535353535353535359479b08ad8787060333663d19704909ee7b1903e58801ba0899b92d0c76cbf18df24394996beef19c050baa9823b4a9828cd9b260c97112ea0c9e62eb4cf0a9d95ca35c8830afac567619d6b3ebee841a3c8be61d35acd8049");
Transaction tx = new Transaction(rawtx);
ECKey key = ECKey.signatureToKey(HashUtil.sha3(rawtx), tx.getSignature());
System.out.println("Signature public key\t: " + Hex.toHexString(key.getPubKey()));
System.out.println("Sender is\t\t: " + Hex.toHexString(key.getAddress()));
// sender: CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826
// todo: add test assertion when the sign/verify part actually works.
}
@Test
public void testSValue() throws Exception {
// Check that we never generate an S value that is larger than half the curve order. This avoids a malleability
// issue that can allow someone to change a transaction [hash] without invalidating the signature.
final int ITERATIONS = 10;
ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(ITERATIONS));
List<ListenableFuture<ECKey.ECDSASignature>> sigFutures = Lists.newArrayList();
final ECKey key = new ECKey();
for (byte i = 0; i < ITERATIONS; i++) {
final byte[] hash = HashUtil.sha3(new byte[]{i});
sigFutures.add(executor.submit(new Callable<ECDSASignature>() {
@Override
public ECKey.ECDSASignature call() throws Exception {
return key.doSign(hash);
}
}));
}
List<ECKey.ECDSASignature> sigs = Futures.allAsList(sigFutures).get();
for (ECKey.ECDSASignature signature : sigs) {
assertTrue(signature.s.compareTo(ECKey.HALF_CURVE_ORDER) <= 0);
}
final ECKey.ECDSASignature duplicate = new ECKey.ECDSASignature(sigs.get(0).r, sigs.get(0).s);
assertEquals(sigs.get(0), duplicate);
assertEquals(sigs.get(0).hashCode(), duplicate.hashCode());
}
@Test
public void testSignVerify() {
ECKey key = ECKey.fromPrivate(privateKey);
String message = "This is an example of a signed message.";
byte[] input = HashUtil.sha3(message.getBytes());
ECDSASignature sig = key.sign(input);
assertTrue(sig.validateComponents());
assertTrue(key.verify(input, sig));
}
private void testProviderRoundTrip(Provider provider) throws Exception {
ECKey key = new ECKey(provider, secureRandom);
String message = "The quick brown fox jumps over the lazy dog.";
byte[] input = HashUtil.sha3(message.getBytes());
ECDSASignature sig = key.sign(input);
assertTrue(sig.validateComponents());
assertTrue(key.verify(input, sig));
}
@Test
public void testSunECRoundTrip() throws Exception {
Provider provider = Security.getProvider("SunEC");
if (provider != null) {
testProviderRoundTrip(provider);
} else {
System.out.println("Skip test as provider doesn't exist. Must be OpenJDK 1.7 which ships without 'SunEC'");
}
}
@Test
public void testSpongyCastleRoundTrip() throws Exception {
testProviderRoundTrip(SpongyCastleProvider.getInstance());
}
@Test
public void testIsPubKeyCanonicalCorect() {
// Test correct prefix 4, right length 65
byte[] canonicalPubkey1 = new byte[65];
canonicalPubkey1[0] = 0x04;
assertTrue(ECKey.isPubKeyCanonical(canonicalPubkey1));
// Test correct prefix 2, right length 33
byte[] canonicalPubkey2 = new byte[33];
canonicalPubkey2[0] = 0x02;
assertTrue(ECKey.isPubKeyCanonical(canonicalPubkey2));
// Test correct prefix 3, right length 33
byte[] canonicalPubkey3 = new byte[33];
canonicalPubkey3[0] = 0x03;
assertTrue(ECKey.isPubKeyCanonical(canonicalPubkey3));
}
@Test
public void testIsPubKeyCanonicalWrongLength() {
// Test correct prefix 4, but wrong length !65
byte[] nonCanonicalPubkey1 = new byte[64];
nonCanonicalPubkey1[0] = 0x04;
assertFalse(ECKey.isPubKeyCanonical(nonCanonicalPubkey1));
// Test correct prefix 2, but wrong length !33
byte[] nonCanonicalPubkey2 = new byte[32];
nonCanonicalPubkey2[0] = 0x02;
assertFalse(ECKey.isPubKeyCanonical(nonCanonicalPubkey2));
// Test correct prefix 3, but wrong length !33
byte[] nonCanonicalPubkey3 = new byte[32];
nonCanonicalPubkey3[0] = 0x03;
assertFalse(ECKey.isPubKeyCanonical(nonCanonicalPubkey3));
}
@Test
public void testIsPubKeyCanonicalWrongPrefix() {
// Test wrong prefix 4, right length 65
byte[] nonCanonicalPubkey4 = new byte[65];
assertFalse(ECKey.isPubKeyCanonical(nonCanonicalPubkey4));
// Test wrong prefix 2, right length 33
byte[] nonCanonicalPubkey5 = new byte[33];
assertFalse(ECKey.isPubKeyCanonical(nonCanonicalPubkey5));
// Test wrong prefix 3, right length 33
byte[] nonCanonicalPubkey6 = new byte[33];
assertFalse(ECKey.isPubKeyCanonical(nonCanonicalPubkey6));
}
@Test
public void keyRecovery() throws Exception {
ECKey key = new ECKey();
String message = "Hello World!";
byte[] hash = HashUtil.sha256(message.getBytes());
ECKey.ECDSASignature sig = key.doSign(hash);
key = ECKey.fromPublicOnly(key.getPubKeyPoint());
boolean found = false;
for (int i = 0; i < 4; i++) {
ECKey key2 = ECKey.recoverFromSignature(i, sig, hash);
checkNotNull(key2);
if (key.equals(key2)) {
found = true;
break;
}
}
assertTrue(found);
}
@Test
public void testSignedMessageToKey() throws SignatureException {
byte[] messageHash = HashUtil.sha3(exampleMessage.getBytes());
ECKey key = ECKey.signatureToKey(messageHash, sigBase64);
assertNotNull(key);
assertArrayEquals(pubKey, key.getPubKey());
}
@Test
public void testGetPrivKeyBytes() {
ECKey key = new ECKey();
assertNotNull(key.getPrivKeyBytes());
assertEquals(32, key.getPrivKeyBytes().length);
}
@Test
public void testEqualsObject() {
ECKey key0 = new ECKey();
ECKey key1 = ECKey.fromPrivate(privateKey);
ECKey key2 = ECKey.fromPrivate(privateKey);
assertFalse(key0.equals(key1));
assertTrue(key1.equals(key1));
assertTrue(key1.equals(key2));
}
@Test
public void decryptAECSIC(){
ECKey key = ECKey.fromPrivate(Hex.decode("abb51256c1324a1350598653f46aa3ad693ac3cf5d05f36eba3f495a1f51590f"));
byte[] payload = key.decryptAES(Hex.decode("84a727bc81fa4b13947dc9728b88fd08"));
System.out.println(Hex.toHexString(payload));
}
@Test
public void testNodeId() {
ECKey key = ECKey.fromPublicOnly(pubKey);
assertEquals(key, ECKey.fromNodeId(key.getNodeId()));
}
}