From 9930e8d5c754d2bebc67defc6c3c0617f3344824 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Thu, 9 Jun 2016 14:55:45 +0100 Subject: [PATCH 01/10] core: add net.i2p.crypto:eddsa dependency --- core/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/build.gradle b/core/build.gradle index b66fb294c9..6c6f36c2b9 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -67,6 +67,9 @@ dependencies { // For JSON compile "com.fasterxml.jackson.core:jackson-databind:2.5.5" + // Java ed25519 implementation. See https://github.com/str4d/ed25519-java/ + compile 'net.i2p.crypto:eddsa:0.1.0' + // Quasar: for the bytecode rewriting for state machines. quasar "co.paralleluniverse:quasar-core:${quasar_version}:jdk8@jar" } From 1fe283c113c1d4826f0d70da556d00014a7356a2 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Thu, 9 Jun 2016 14:56:26 +0100 Subject: [PATCH 02/10] core: switch to ed25519 --- .../kotlin/com/r3corda/core/crypto/CryptoUtilities.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/com/r3corda/core/crypto/CryptoUtilities.kt b/core/src/main/kotlin/com/r3corda/core/crypto/CryptoUtilities.kt index 7361fdf49b..a2bf099c51 100644 --- a/core/src/main/kotlin/com/r3corda/core/crypto/CryptoUtilities.kt +++ b/core/src/main/kotlin/com/r3corda/core/crypto/CryptoUtilities.kt @@ -1,13 +1,14 @@ package com.r3corda.core.crypto import com.google.common.io.BaseEncoding -import com.r3corda.core.crypto.Party import com.r3corda.core.serialization.OpaqueBytes import com.r3corda.core.serialization.SerializedBytes import com.r3corda.core.serialization.deserialize +import net.i2p.crypto.eddsa.EdDSAEngine import java.math.BigInteger import java.security.* import java.security.interfaces.ECPublicKey +import net.i2p.crypto.eddsa.KeyPairGenerator as EddsaKeyPairGenerator // "sealed" here means there can't be any subclasses other than the ones defined here. sealed class SecureHash private constructor(bits: ByteArray) : OpaqueBytes(bits) { @@ -118,7 +119,7 @@ class DummyPublicKey(val s: String) : PublicKey, Comparable { /** Utility to simplify the act of signing a byte array */ fun PrivateKey.signWithECDSA(bits: ByteArray): DigitalSignature { - val signer = Signature.getInstance("SHA256withECDSA") + val signer = EdDSAEngine() signer.initSign(this) signer.update(bits) val sig = signer.sign() @@ -140,7 +141,7 @@ fun KeyPair.signWithECDSA(bitsToSign: ByteArray, party: Party): DigitalSignature /** Utility to simplify the act of verifying a signature */ fun PublicKey.verifyWithECDSA(content: ByteArray, signature: DigitalSignature) { - val verifier = Signature.getInstance("SHA256withECDSA") + val verifier = EdDSAEngine() verifier.initVerify(this) verifier.update(content) if (verifier.verify(signature.bits) == false) @@ -160,4 +161,4 @@ operator fun KeyPair.component1() = this.private operator fun KeyPair.component2() = this.public /** A simple wrapper that will make it easier to swap out the EC algorithm we use in future */ -fun generateKeyPair() = KeyPairGenerator.getInstance("EC").genKeyPair() \ No newline at end of file +fun generateKeyPair() = EddsaKeyPairGenerator().generateKeyPair() \ No newline at end of file From 22567d11b5fbf26b9998b86a10c9a482d8107c7d Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Thu, 9 Jun 2016 14:57:03 +0100 Subject: [PATCH 03/10] core: implement custom serializers for ed25519 keypairs --- .../com/r3corda/core/serialization/Kryo.kt | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt b/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt index f1cc4da867..b825883de8 100644 --- a/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt +++ b/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt @@ -17,6 +17,11 @@ import com.r3corda.core.crypto.sha256 import com.r3corda.core.node.AttachmentsClassLoader import com.r3corda.core.node.services.AttachmentStorage import de.javakaffee.kryoserializers.ArraysAsListSerializer +import net.i2p.crypto.eddsa.EdDSAPrivateKey +import net.i2p.crypto.eddsa.EdDSAPublicKey +import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable +import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec +import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec import org.objenesis.strategy.StdInstantiatorStrategy import java.io.ByteArrayOutputStream import java.io.ObjectInputStream @@ -29,6 +34,7 @@ import java.util.* import javax.annotation.concurrent.ThreadSafe import kotlin.reflect.* import kotlin.reflect.jvm.javaType +import java.security.PrivateKey /** * Serialization utilities, using the Kryo framework with a custom serialiser for immutable data classes and a dead @@ -250,6 +256,39 @@ object WireTransactionSerializer : Serializer() { } } +/** For serialising an ed25519 private key */ +@ThreadSafe +object Ed25519PrivateKeySerializer : Serializer() { + val ed25519Curve = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512) + + override fun write(kryo: Kryo, output: Output, obj: EdDSAPrivateKey) { + check(obj.params.equals(ed25519Curve)) + kryo.writeClassAndObject(output, obj.seed) + } + + override fun read(kryo: Kryo, input: Input, type: Class): EdDSAPrivateKey { + val seed = kryo.readClassAndObject(input) as ByteArray + return EdDSAPrivateKey(EdDSAPrivateKeySpec(seed, ed25519Curve)) + } +} + +/** For serialising an ed25519 public key */ +@ThreadSafe +object Ed25519PublicKeySerializer : Serializer() { + val ed25519Curve = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512) + + override fun write(kryo: Kryo, output: Output, obj: EdDSAPublicKey) { + check(obj.params.equals(ed25519Curve)) + kryo.writeClassAndObject(output, obj.abyte) + } + + override fun read(kryo: Kryo, input: Input, type: Class): EdDSAPublicKey { + val A = kryo.readClassAndObject(input) as ByteArray + return EdDSAPublicKey(EdDSAPublicKeySpec(A, ed25519Curve)) + } +} + + fun createKryo(k: Kryo = Kryo()): Kryo { return k.apply { // Allow any class to be deserialized (this is insecure but for prototyping we don't care) @@ -273,8 +312,8 @@ fun createKryo(k: Kryo = Kryo()): Kryo { // Some things where the JRE provides an efficient custom serialisation. val keyPair = generateKeyPair() - register(keyPair.public.javaClass, ReferencesAwareJavaSerializer) - register(keyPair.private.javaClass, ReferencesAwareJavaSerializer) + register(keyPair.public.javaClass, Ed25519PublicKeySerializer) + register(keyPair.private.javaClass, Ed25519PrivateKeySerializer) register(Instant::class.java, ReferencesAwareJavaSerializer) // Some classes have to be handled with the ImmutableClassSerializer because they need to have their From 376b73b823092855ee8a1b36c881fb907fcab6f6 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Thu, 9 Jun 2016 15:30:02 +0100 Subject: [PATCH 04/10] core: switch to static size bytearray serialization of keypairs --- .../kotlin/com/r3corda/core/serialization/Kryo.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt b/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt index b825883de8..520b54588c 100644 --- a/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt +++ b/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt @@ -260,14 +260,16 @@ object WireTransactionSerializer : Serializer() { @ThreadSafe object Ed25519PrivateKeySerializer : Serializer() { val ed25519Curve = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512) + val seedSize = 64 override fun write(kryo: Kryo, output: Output, obj: EdDSAPrivateKey) { check(obj.params.equals(ed25519Curve)) - kryo.writeClassAndObject(output, obj.seed) + check(obj.seed.size == seedSize) + output.writeBytes(obj.seed) } override fun read(kryo: Kryo, input: Input, type: Class): EdDSAPrivateKey { - val seed = kryo.readClassAndObject(input) as ByteArray + val seed = input.readBytes(seedSize) return EdDSAPrivateKey(EdDSAPrivateKeySpec(seed, ed25519Curve)) } } @@ -276,14 +278,16 @@ object Ed25519PrivateKeySerializer : Serializer() { @ThreadSafe object Ed25519PublicKeySerializer : Serializer() { val ed25519Curve = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512) + val ASize = 32 override fun write(kryo: Kryo, output: Output, obj: EdDSAPublicKey) { check(obj.params.equals(ed25519Curve)) - kryo.writeClassAndObject(output, obj.abyte) + check(obj.abyte.size == ASize) + output.writeBytes(obj.abyte) } override fun read(kryo: Kryo, input: Input, type: Class): EdDSAPublicKey { - val A = kryo.readClassAndObject(input) as ByteArray + val A = input.readBytes(ASize) return EdDSAPublicKey(EdDSAPublicKeySpec(A, ed25519Curve)) } } From af3d87803de2d3d7fcf19f74187e4ffc38a97ce9 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Thu, 9 Jun 2016 16:04:19 +0100 Subject: [PATCH 05/10] core: serialize bytearray sizes instead of relying on static sizes --- .../main/kotlin/com/r3corda/core/serialization/Kryo.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt b/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt index 520b54588c..77b3520ce3 100644 --- a/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt +++ b/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt @@ -260,15 +260,15 @@ object WireTransactionSerializer : Serializer() { @ThreadSafe object Ed25519PrivateKeySerializer : Serializer() { val ed25519Curve = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512) - val seedSize = 64 override fun write(kryo: Kryo, output: Output, obj: EdDSAPrivateKey) { check(obj.params.equals(ed25519Curve)) - check(obj.seed.size == seedSize) + output.writeInt(obj.seed.size) output.writeBytes(obj.seed) } override fun read(kryo: Kryo, input: Input, type: Class): EdDSAPrivateKey { + val seedSize = input.readInt() val seed = input.readBytes(seedSize) return EdDSAPrivateKey(EdDSAPrivateKeySpec(seed, ed25519Curve)) } @@ -278,15 +278,15 @@ object Ed25519PrivateKeySerializer : Serializer() { @ThreadSafe object Ed25519PublicKeySerializer : Serializer() { val ed25519Curve = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512) - val ASize = 32 override fun write(kryo: Kryo, output: Output, obj: EdDSAPublicKey) { check(obj.params.equals(ed25519Curve)) - check(obj.abyte.size == ASize) + output.writeInt(obj.abyte.size) output.writeBytes(obj.abyte) } override fun read(kryo: Kryo, input: Input, type: Class): EdDSAPublicKey { + val ASize = input.readInt() val A = input.readBytes(ASize) return EdDSAPublicKey(EdDSAPublicKeySpec(A, ed25519Curve)) } From cd91c6a0e779b3f63e8f45f6d75c7493abe73dbb Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Thu, 9 Jun 2016 16:04:24 +0100 Subject: [PATCH 06/10] core: add serialization test for keypairs --- .../r3corda/core/serialization/KryoTests.kt | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/core/src/test/kotlin/com/r3corda/core/serialization/KryoTests.kt b/core/src/test/kotlin/com/r3corda/core/serialization/KryoTests.kt index 5f39728ef4..6734f755fb 100644 --- a/core/src/test/kotlin/com/r3corda/core/serialization/KryoTests.kt +++ b/core/src/test/kotlin/com/r3corda/core/serialization/KryoTests.kt @@ -1,7 +1,11 @@ package com.r3corda.core.serialization import com.google.common.primitives.Ints +import com.r3corda.core.crypto.generateKeyPair +import com.r3corda.core.crypto.signWithECDSA +import com.r3corda.core.crypto.verifyWithECDSA import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test import java.time.Instant import java.util.* @@ -54,6 +58,21 @@ class KryoTests { assertThat(bits.deserialize(kryo)).isEqualTo(cyclic) } + @Test + fun `deserialised keypair functions the same as serialised one`() { + val keyPair = generateKeyPair() + val bitsToSign: ByteArray = Ints.toByteArray(0x01234567) + val wrongBits: ByteArray = Ints.toByteArray(0x76543210) + val signature = keyPair.signWithECDSA(bitsToSign) + signature.verifyWithECDSA(bitsToSign) + assertThatThrownBy { signature.verifyWithECDSA(wrongBits) } + + val deserialisedKeyPair = keyPair.serialize(kryo).deserialize(kryo) + val deserialisedSignature = deserialisedKeyPair.signWithECDSA(bitsToSign) + assertThat(deserialisedSignature).isEqualTo(signature) + deserialisedSignature.verifyWithECDSA(bitsToSign) + assertThatThrownBy { deserialisedSignature.verifyWithECDSA(wrongBits) } + } private data class Person(val name: String, val birthday: Instant?) @@ -65,4 +84,4 @@ class KryoTests { override fun toString(): String = "Cyclic($value)" } -} \ No newline at end of file +} From 2b4ebd4f0959e87232baefd865c96c2ff531b33f Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Thu, 9 Jun 2016 16:17:42 +0100 Subject: [PATCH 07/10] core: use optimizedPositive for size serialization --- .../main/kotlin/com/r3corda/core/serialization/Kryo.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt b/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt index 77b3520ce3..d4cda1c338 100644 --- a/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt +++ b/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt @@ -263,12 +263,12 @@ object Ed25519PrivateKeySerializer : Serializer() { override fun write(kryo: Kryo, output: Output, obj: EdDSAPrivateKey) { check(obj.params.equals(ed25519Curve)) - output.writeInt(obj.seed.size) + output.writeInt(obj.seed.size, true) output.writeBytes(obj.seed) } override fun read(kryo: Kryo, input: Input, type: Class): EdDSAPrivateKey { - val seedSize = input.readInt() + val seedSize = input.readInt(true) val seed = input.readBytes(seedSize) return EdDSAPrivateKey(EdDSAPrivateKeySpec(seed, ed25519Curve)) } @@ -281,12 +281,12 @@ object Ed25519PublicKeySerializer : Serializer() { override fun write(kryo: Kryo, output: Output, obj: EdDSAPublicKey) { check(obj.params.equals(ed25519Curve)) - output.writeInt(obj.abyte.size) + output.writeInt(obj.abyte.size, true) output.writeBytes(obj.abyte) } override fun read(kryo: Kryo, input: Input, type: Class): EdDSAPublicKey { - val ASize = input.readInt() + val ASize = input.readInt(true) val A = input.readBytes(ASize) return EdDSAPublicKey(EdDSAPublicKeySpec(A, ed25519Curve)) } From a8ce69ccb9b305b9a9b7df44a32499253d2f5344 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Thu, 9 Jun 2016 16:41:57 +0100 Subject: [PATCH 08/10] docs: add release note on curve switch --- docs/source/release-notes.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst index f8dcde9f73..01daacf3a3 100644 --- a/docs/source/release-notes.rst +++ b/docs/source/release-notes.rst @@ -12,7 +12,7 @@ Here are changes in git master that haven't yet made it to a snapshot release: * Amount class is now generic, to support non-currency types (such as assets, or currency with additional information). * Refactored the Cash contract to have a new FungibleAsset superclass, to model all countable assets that can be merged and split (currency, barrels of oil, etc.) - +* Switched to the ed25519 elliptic curve from secp256r1. Note that this introduces a new external lib dependency. Milestone 0 ----------- @@ -24,4 +24,4 @@ This is the first release, which includes: * The first version of the protocol/orchestration framework * Some initial support for pluggable consensus mechanisms * Tutorials and documentation explaining how it works -* Much more ... \ No newline at end of file +* Much more ... From 5b4c4f167d80a84fa52516ea2fca12dc13e05c46 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Thu, 16 Jun 2016 17:00:32 +0100 Subject: [PATCH 09/10] core: .equals() -> == --- core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt b/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt index d4cda1c338..6fb5a03f20 100644 --- a/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt +++ b/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt @@ -262,7 +262,7 @@ object Ed25519PrivateKeySerializer : Serializer() { val ed25519Curve = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512) override fun write(kryo: Kryo, output: Output, obj: EdDSAPrivateKey) { - check(obj.params.equals(ed25519Curve)) + check(obj.params == ed25519Curve) output.writeInt(obj.seed.size, true) output.writeBytes(obj.seed) } @@ -280,7 +280,7 @@ object Ed25519PublicKeySerializer : Serializer() { val ed25519Curve = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512) override fun write(kryo: Kryo, output: Output, obj: EdDSAPublicKey) { - check(obj.params.equals(ed25519Curve)) + check(obj.params == ed25519Curve) output.writeInt(obj.abyte.size, true) output.writeBytes(obj.abyte) } From 2663a6390e9a67bf0a09f768284e9368ceac776f Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Thu, 16 Jun 2016 17:03:30 +0100 Subject: [PATCH 10/10] core: Kryo extension methods for reading/writing ByteArrays prefixed with their sizes --- .../com/r3corda/core/serialization/Kryo.kt | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt b/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt index 6fb5a03f20..f7f5a1cff7 100644 --- a/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt +++ b/core/src/main/kotlin/com/r3corda/core/serialization/Kryo.kt @@ -211,6 +211,16 @@ inline fun Kryo.useClassLoader(cl: ClassLoader, body: () -> T) : T { } } +inline fun Output.writeBytesWithLength(byteArray: ByteArray) { + this.writeInt(byteArray.size, true) + this.writeBytes(byteArray) +} + +inline fun Input.readBytesWithLength(): ByteArray { + val size = this.readInt(true) + return this.readBytes(size) +} + /** Thrown during deserialisation to indicate that an attachment needed to construct the [WireTransaction] is not found */ class MissingAttachmentsException(val ids: List) : Exception() @@ -263,13 +273,11 @@ object Ed25519PrivateKeySerializer : Serializer() { override fun write(kryo: Kryo, output: Output, obj: EdDSAPrivateKey) { check(obj.params == ed25519Curve) - output.writeInt(obj.seed.size, true) - output.writeBytes(obj.seed) + output.writeBytesWithLength(obj.seed) } override fun read(kryo: Kryo, input: Input, type: Class): EdDSAPrivateKey { - val seedSize = input.readInt(true) - val seed = input.readBytes(seedSize) + val seed = input.readBytesWithLength() return EdDSAPrivateKey(EdDSAPrivateKeySpec(seed, ed25519Curve)) } } @@ -281,13 +289,11 @@ object Ed25519PublicKeySerializer : Serializer() { override fun write(kryo: Kryo, output: Output, obj: EdDSAPublicKey) { check(obj.params == ed25519Curve) - output.writeInt(obj.abyte.size, true) - output.writeBytes(obj.abyte) + output.writeBytesWithLength(obj.abyte) } override fun read(kryo: Kryo, input: Input, type: Class): EdDSAPublicKey { - val ASize = input.readInt(true) - val A = input.readBytes(ASize) + val A = input.readBytesWithLength() return EdDSAPublicKey(EdDSAPublicKeySpec(A, ed25519Curve)) } }