diff --git a/build.xml b/build.xml index a0a555c..a966899 100644 --- a/build.xml +++ b/build.xml @@ -8,8 +8,51 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/aion-types-22a3be9.jar b/lib/aion-types-22a3be9.jar new file mode 100644 index 0000000..bad1988 Binary files /dev/null and b/lib/aion-types-22a3be9.jar differ diff --git a/lib/guava-25.1-jre.jar b/lib/guava-25.1-jre.jar new file mode 100644 index 0000000..babc175 Binary files /dev/null and b/lib/guava-25.1-jre.jar differ diff --git a/lib/hamcrest-all-1.3.jar b/lib/hamcrest-all-1.3.jar new file mode 100644 index 0000000..6f62ba0 Binary files /dev/null and b/lib/hamcrest-all-1.3.jar differ diff --git a/lib/junit-4.12.jar b/lib/junit-4.12.jar new file mode 100644 index 0000000..3a7fc26 Binary files /dev/null and b/lib/junit-4.12.jar differ diff --git a/lib/util4j-674e4b5.jar b/lib/util4j-674e4b5.jar new file mode 100644 index 0000000..39f7813 Binary files /dev/null and b/lib/util4j-674e4b5.jar differ diff --git a/src/module-info.java b/src/module-info.java index 583ec41..1b0c116 100644 --- a/src/module-info.java +++ b/src/module-info.java @@ -3,4 +3,4 @@ requires aion.rlp; exports main; -} \ No newline at end of file +} diff --git a/test/main/PrivateKey.java b/test/main/PrivateKey.java new file mode 100644 index 0000000..c2b6c71 --- /dev/null +++ b/test/main/PrivateKey.java @@ -0,0 +1,125 @@ +package main; + +import main.crypto.Blake2b; +import net.i2p.crypto.eddsa.EdDSAPrivateKey; +import net.i2p.crypto.eddsa.KeyPairGenerator; +import net.i2p.crypto.eddsa.Utils; +import org.aion.util.conversions.Hex; +import java.nio.ByteBuffer; +import java.security.KeyPair; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Arrays; + + +public final class PrivateKey { + public static final int SIZE = 32; + + private final byte[] key; + private final byte[] address; + + /** + * Constructs a new private key consisting of the provided bytes. + * + * @param privateKeyBytes The bytes of the private key. + */ + private PrivateKey(byte[] privateKeyBytes) throws InvalidKeySpecException { + if (privateKeyBytes == null) { + throw new NullPointerException("private key bytes cannot be null"); + } + if (privateKeyBytes.length != SIZE) { + throw new IllegalArgumentException("bytes of a private key must have a length of " + SIZE); + } + this.key = privateKeyBytes.clone(); + this.address = deriveAddress(this.key); + } + + public static PrivateKey fromBytes(byte[] privateKeyBytes) throws InvalidKeySpecException { + return new PrivateKey(privateKeyBytes); + } + + public static PrivateKey random() { + try { + return new PrivateKey(generatePrivateKey()); + } catch (InvalidKeySpecException e) { + // Hiding the checked exception because this should never actually happen here. We have + // complete control over these bytes and know they are generated in a sound way. + throw new RuntimeException(e.getMessage()); + } + } + + public byte[] copyOfUnderlyingBytes() { + return this.key.clone(); + } + + public byte[] getPublicAionAddress() { + return this.address; + } + + @Override + public String toString() { + return "com.theoan.transactionbuilder.main.PrivateKey { 0x" + Hex.toHexString(this.key) + " }"; + } + + /** + * Returns true only if other is a com.theoan.transactionbuilder.main.PrivateKey with the same underlying bytes. + * + * @param other The other whose equality is to be tested. + * @return whether this is equal to other. + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof PrivateKey)) { + return false; + } else if (other == this) { + return true; + } + return Arrays.equals(this.key, ((PrivateKey) other).key); + } + + @Override + public int hashCode() { + return Arrays.hashCode(this.key); + } + + public static byte[] generatePrivateKey() { + KeyPairGenerator keyPairGenerator = new KeyPairGenerator(); + KeyPair pair = keyPairGenerator.generateKeyPair(); + EdDSAPrivateKey privateKey = (EdDSAPrivateKey) pair.getPrivate(); + return Utils.hexToBytes(Utils.bytesToHex(privateKey.getEncoded()).substring(32, 96)); + } + + private static byte[] deriveAddress(byte[] privateKeyBytes) throws InvalidKeySpecException { + if (privateKeyBytes == null) { + throw new NullPointerException("private key cannot be null"); + } + + if (privateKeyBytes.length != 32){ + throw new IllegalArgumentException("private key mute be 32 bytes"); + } + + EdDSAPrivateKey privateKey = new EdDSAPrivateKey(new PKCS8EncodedKeySpec(addSkPrefix(Utils.bytesToHex(privateKeyBytes)))); + byte[] publicKeyBytes = privateKey.getAbyte(); + + return computeA0Address(publicKeyBytes); + } + + private static byte[] addSkPrefix(String skString){ + String skEncoded = "302e020100300506032b657004220420" + skString; + return Utils.hexToBytes(skEncoded); + } + + private static byte[] computeA0Address(byte[] publicKey) { + byte A0_IDENTIFIER = (byte) 0xa0; + ByteBuffer buf = ByteBuffer.allocate(32); + buf.put(A0_IDENTIFIER); + buf.put(blake256(publicKey), 1, 31); + return buf.array(); + } + + private static byte[] blake256(byte[] input) { + Blake2b digest = Blake2b.Digest.newInstance(32); + digest.update(input); + return digest.digest(); + } +} diff --git a/test/main/VerifierTest.java b/test/main/VerifierTest.java new file mode 100644 index 0000000..276654c --- /dev/null +++ b/test/main/VerifierTest.java @@ -0,0 +1,60 @@ +package main; + +import org.junit.Assert; +import org.junit.Test; + +import java.math.BigInteger; + +public class VerifierTest { + @Test + public void testMatchedSignerRegular() throws Exception{ + PrivateKey random = PrivateKey.random(); + byte[] signed = getSignedTransaction(random); + Assert.assertTrue(SignedTransactionVerifier.isSignerForRegularTransaction(signed, random.getPublicAionAddress())); + + } + + @Test + public void testUnMatchedSignerRegular() throws Exception{ + PrivateKey expectedSigner = PrivateKey.random(); + PrivateKey otherSigner = PrivateKey.random(); + byte[] signed = getSignedTransaction(expectedSigner); + Assert.assertFalse(SignedTransactionVerifier.isSignerForRegularTransaction(signed, otherSigner.copyOfUnderlyingBytes())); + + } + + @Test + public void testMatchedSignerInvokable() throws Exception{ + PrivateKey random = PrivateKey.random(); + byte[] signed = getSignedInvokable(random); + Assert.assertTrue(SignedTransactionVerifier.isSignerForInvokableTransaction(signed, random.getPublicAionAddress())); + + } + + @Test + public void testUnMatchedSignerInvokable() throws Exception{ + PrivateKey expectedSigner = PrivateKey.random(); + PrivateKey otherSigner = PrivateKey.random(); + byte[] signed = getSignedInvokable(expectedSigner); + Assert.assertFalse(SignedTransactionVerifier.isSignerForInvokableTransaction(signed, otherSigner.copyOfUnderlyingBytes())); + + } + + private byte[] getSignedInvokable(PrivateKey privateKey) throws Exception { + return new SignedInvokableTransactionBuilder().privateKey(privateKey.copyOfUnderlyingBytes()) + .executor(PrivateKey.random().getPublicAionAddress()) + .data(new byte[32]) + .destination(PrivateKey.random().getPublicAionAddress()) + .senderNonce(BigInteger.ZERO) + .buildSignedInvokableTransaction(); + } + + private byte[] getSignedTransaction(PrivateKey privateKey) throws Exception { + return new SignedTransactionBuilder().privateKey(privateKey.copyOfUnderlyingBytes()) + .data(new byte[0]) + .energyLimit(200000) + .energyPrice(10000000000L) + .senderNonce(BigInteger.ZERO) + .buildSignedTransaction(); + } +}