From 6d485a33296b3f27568a819162c83db012c3b430 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 4 Jan 2018 10:03:04 +0000 Subject: [PATCH 1/6] SPELLING ERROR FIX --- .../nodeapi/internal/serialization/amqp/SerializationHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index 57903f546e..b3f6d5ce53 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -85,7 +85,7 @@ private fun propertiesForSerializationFromConstructor(kotlinConstructo val matchingProperty = properties[name] ?: try { clazz.getDeclaredField(param.name) - throw NotSerializableException("Property '$name' or it's getter is non public, this renders class '$clazz' unserializable") + throw NotSerializableException("Property '$name' or its getter is non public, this renders class '$clazz' unserializable") } catch (e: NoSuchFieldException) { throw NotSerializableException("No property matching constructor parameter named '$name' of '$clazz'. " + "If using Java, check that you have the -parameters option specified in the Java compiler. " + From 412fead02e76596f6162d4b35d48da25fd8d8d7e Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Thu, 4 Jan 2018 13:32:10 +0000 Subject: [PATCH 2/6] CORDA-785: Add functions for constructing FlowLogicRef without the class (#2134) Add functions for constructing `FlowLogicRef` from class name, rather than requiring the class itself. This avoids requiring that schedulable states have access to the scheduled flow to instantiate, but instead can require it only actually scheduling the flow. This reduces the size of the JAR required to validate transactions containing these states. --- .../net/corda/core/flows/FlowLogicRef.kt | 10 ++++++++++ .../statemachine/FlowLogicRefFactoryImpl.kt | 12 ++++++++++++ .../node/services/events/ScheduledFlowTests.kt | 3 ++- .../FlowLogicRefFactoryImplTest.kt} | 18 +++++++++++------- .../main/kotlin/net/corda/irs/contract/IRS.kt | 2 +- .../net/corda/vega/contracts/PortfolioState.kt | 3 +-- 6 files changed, 37 insertions(+), 11 deletions(-) rename node/src/test/kotlin/net/corda/node/services/{events/FlowLogicRefTest.kt => statemachine/FlowLogicRefFactoryImplTest.kt} (81%) diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt index 4768ff9259..36b0971cb0 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt @@ -10,7 +10,17 @@ import net.corda.core.serialization.CordaSerializable */ @DoNotImplement interface FlowLogicRefFactory { + /** + * Construct a FlowLogicRef. This is intended for cases where the calling code has the relevant class already + * and can provide it directly. + */ + @Deprecated("This should be avoided, and the version which takes a class name used instead to avoid requiring the class on the classpath to deserialize calling code") fun create(flowClass: Class>, vararg args: Any?): FlowLogicRef + /** + * Construct a FlowLogicRef. This is intended for cases where the calling code does not want to require the flow + * class on the classpath for all cases where the calling code is loaded. + */ + fun create(flowClassName: String, vararg args: Any?): FlowLogicRef fun createForRPC(flowClass: Class>, vararg args: Any?): FlowLogicRef fun toFlowLogic(ref: FlowLogicRef): FlowLogic<*> } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt index 1b20e75c5b..8d1aba86c6 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt @@ -40,6 +40,18 @@ class FlowLogicRefFactoryImpl(private val classloader: ClassLoader) : SingletonS return createForRPC(flowClass, *args) } + override fun create(flowClassName: String, vararg args: Any?): FlowLogicRef { + val flowClass = Class.forName(flowClassName, true, classloader).asSubclass(FlowLogic::class.java) + if (flowClass == null) { + throw IllegalArgumentException("The class $flowClassName is not a subclass of FlowLogic.") + } else { + if (!flowClass.isAnnotationPresent(SchedulableFlow::class.java)) { + throw IllegalFlowLogicException(flowClass, "because it's not a schedulable flow") + } + return createForRPC(flowClass, *args) + } + } + override fun createForRPC(flowClass: Class>, vararg args: Any?): FlowLogicRef { // TODO: This is used via RPC but it's probably better if we pass in argument names and values explicitly // to avoid requiring only a single constructor. diff --git a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt index d963030134..600d99e50c 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt @@ -30,6 +30,7 @@ import org.junit.Assert.* import org.junit.Before import org.junit.Test import java.time.Instant +import kotlin.reflect.jvm.jvmName import kotlin.test.assertEquals class ScheduledFlowTests { @@ -52,7 +53,7 @@ class ScheduledFlowTests { override val linearId: UniqueIdentifier = UniqueIdentifier()) : SchedulableState, LinearState { override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? { return if (!processed) { - val logicRef = flowLogicRefFactory.create(ScheduledFlow::class.java, thisStateRef) + val logicRef = flowLogicRefFactory.create(ScheduledFlow::class.jvmName, thisStateRef) ScheduledActivity(logicRef, creationTime) } else { null diff --git a/node/src/test/kotlin/net/corda/node/services/events/FlowLogicRefTest.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImplTest.kt similarity index 81% rename from node/src/test/kotlin/net/corda/node/services/events/FlowLogicRefTest.kt rename to node/src/test/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImplTest.kt index 3794a39ebf..b0953afdc3 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/FlowLogicRefTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImplTest.kt @@ -1,17 +1,20 @@ -package net.corda.node.services.events +package net.corda.node.services.statemachine import net.corda.core.flows.FlowLogic import net.corda.core.flows.IllegalFlowLogicException -import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl +import net.corda.core.flows.SchedulableFlow import org.junit.Test import java.time.Duration +import kotlin.reflect.jvm.jvmName +import kotlin.test.assertEquals -class FlowLogicRefTest { +class FlowLogicRefFactoryImplTest { data class ParamType1(val value: Int) data class ParamType2(val value: String) @Suppress("UNUSED_PARAMETER", "unused") // Things are used via reflection. + @SchedulableFlow class KotlinFlowLogic(A: ParamType1, b: ParamType2) : FlowLogic() { constructor() : this(ParamType1(1), ParamType2("2")) @@ -26,6 +29,7 @@ class FlowLogicRefTest { override fun call() = Unit } + @SchedulableFlow class KotlinNoArgFlowLogic : FlowLogic() { override fun call() = Unit } @@ -37,18 +41,18 @@ class FlowLogicRefTest { private val flowLogicRefFactory = FlowLogicRefFactoryImpl(FlowLogicRefFactoryImpl::class.java.classLoader) @Test fun `create kotlin no arg`() { - flowLogicRefFactory.createForRPC(KotlinNoArgFlowLogic::class.java) + flowLogicRefFactory.create(KotlinNoArgFlowLogic::class.jvmName) } @Test - fun `create kotlin`() { + fun `should create kotlin types`() { val args = mapOf(Pair("A", ParamType1(1)), Pair("b", ParamType2("Hello Jack"))) flowLogicRefFactory.createKotlin(KotlinFlowLogic::class.java, args) } @Test fun `create primary`() { - flowLogicRefFactory.createForRPC(KotlinFlowLogic::class.java, ParamType1(1), ParamType2("Hello Jack")) + flowLogicRefFactory.create(KotlinFlowLogic::class.jvmName, ParamType1(1), ParamType2("Hello Jack")) } @Test @@ -76,6 +80,6 @@ class FlowLogicRefTest { @Test(expected = IllegalFlowLogicException::class) fun `create for non-schedulable flow logic`() { - flowLogicRefFactory.create(NonSchedulableFlow::class.java) + flowLogicRefFactory.create(NonSchedulableFlow::class.jvmName) } } diff --git a/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/contract/IRS.kt b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/contract/IRS.kt index c81d80592c..735c265ed6 100644 --- a/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/contract/IRS.kt +++ b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/contract/IRS.kt @@ -616,7 +616,7 @@ class InterestRateSwap : Contract { // This is perhaps not how we should determine the time point in the business day, but instead expect the schedule to detail some of these aspects val instant = suggestInterestRateAnnouncementTimeWindow(index = nextFixingOf.name, source = floatingLeg.indexSource, date = nextFixingOf.forDay).fromTime!! - return ScheduledActivity(flowLogicRefFactory.create(FixingFlow.FixingRoleDecider::class.java, thisStateRef), instant) + return ScheduledActivity(flowLogicRefFactory.create("net.corda.irs.flows.FixingFlow\$FixingRoleDecider", thisStateRef), instant) } // DOCEND 1 diff --git a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/PortfolioState.kt b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/PortfolioState.kt index 8b9b1d7414..98e490e68d 100644 --- a/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/PortfolioState.kt +++ b/samples/simm-valuation-demo/src/main/kotlin/net/corda/vega/contracts/PortfolioState.kt @@ -7,7 +7,6 @@ import net.corda.core.identity.Party import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.TransactionBuilder import net.corda.finance.contracts.DealState -import net.corda.vega.flows.SimmRevaluation import java.time.LocalDate import java.time.ZoneOffset import java.time.temporal.ChronoUnit @@ -32,7 +31,7 @@ data class PortfolioState(val portfolio: List, val valuer: AbstractParty get() = participants[0] override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity { - val flow = flowLogicRefFactory.create(SimmRevaluation.Initiator::class.java, thisStateRef, LocalDate.now()) + val flow = flowLogicRefFactory.create("net.corda.vega.flows.SimmRevaluation\$Initiator", thisStateRef, LocalDate.now()) return ScheduledActivity(flow, LocalDate.now().plus(1, ChronoUnit.DAYS).atStartOfDay().toInstant(ZoneOffset.UTC)) } From 63734aa3edc91c1f7c6753ef50132d1663cb4c0f Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Thu, 4 Jan 2018 13:35:56 +0000 Subject: [PATCH 3/6] Harmonises the names of the parameters across CashExitFlow constructors. --- .../src/main/kotlin/net/corda/finance/flows/CashExitFlow.kt | 6 +++--- .../main/kotlin/net/corda/explorer/ExplorerSimulation.kt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/finance/src/main/kotlin/net/corda/finance/flows/CashExitFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/CashExitFlow.kt index 23b05a1e8f..adc6f1b55a 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/CashExitFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/CashExitFlow.kt @@ -29,8 +29,8 @@ import java.util.* class CashExitFlow(private val amount: Amount, private val issuerRef: OpaqueBytes, progressTracker: ProgressTracker) : AbstractCashFlow(progressTracker) { - constructor(amount: Amount, issueRef: OpaqueBytes) : this(amount, issueRef, tracker()) - constructor(request: ExitRequest) : this(request.amount, request.issueRef, tracker()) + constructor(amount: Amount, issuerRef: OpaqueBytes) : this(amount, issuerRef, tracker()) + constructor(request: ExitRequest) : this(request.amount, request.issuerRef, tracker()) companion object { fun tracker() = ProgressTracker(GENERATING_TX, SIGNING_TX, FINALISING_TX) @@ -78,5 +78,5 @@ class CashExitFlow(private val amount: Amount, } @CordaSerializable - class ExitRequest(amount: Amount, val issueRef: OpaqueBytes) : AbstractRequest(amount) + class ExitRequest(amount: Amount, val issuerRef: OpaqueBytes) : AbstractRequest(amount) } diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt index c88d2505d0..c3f0c0c173 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt @@ -141,7 +141,7 @@ class ExplorerSimulation(private val options: OptionSet) { it.startFlow(::CashIssueAndPaymentFlow, request).log(i, "${request.amount.token}Issuer") } is ExitRequest -> issuers[request.amount.token]?.let { - println("${Instant.now()} [$i] EXITING ${request.amount} with ref ${request.issueRef}") + println("${Instant.now()} [$i] EXITING ${request.amount} with ref ${request.issuerRef}") it.startFlow(::CashExitFlow, request).log(i, "${request.amount.token}Exit") } else -> throw IllegalArgumentException("Unsupported command: $request") From 01e4880947c775a562414909028f58c926a57f1e Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 4 Jan 2018 15:49:55 +0000 Subject: [PATCH 4/6] SPELLING: updae error message in tests --- .../nodeapi/internal/serialization/amqp/ErrorMessageTests.java | 2 +- .../nodeapi/internal/serialization/amqp/ErrorMessagesTests.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ErrorMessageTests.java b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ErrorMessageTests.java index 2df95a808d..5e98cf9b82 100644 --- a/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ErrorMessageTests.java +++ b/node-api/src/test/java/net/corda/nodeapi/internal/serialization/amqp/ErrorMessageTests.java @@ -10,7 +10,7 @@ public class ErrorMessageTests { private String errMsg(String property, String testname) { return "Property '" + property - + "' or it's getter is non public, this renders class 'class " + + "' or its getter is non public, this renders class 'class " + testname + "$C' unserializable -> class " + testname diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/ErrorMessagesTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/ErrorMessagesTests.kt index 775e5c3405..15f543da0d 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/ErrorMessagesTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/ErrorMessagesTests.kt @@ -10,7 +10,7 @@ class ErrorMessagesTests { } private fun errMsg(property:String, testname: String) = - "Property '$property' or it's getter is non public, this renders class 'class $testname\$C' unserializable -> class $testname\$C" + "Property '$property' or its getter is non public, this renders class 'class $testname\$C' unserializable -> class $testname\$C" @Test fun privateProperty() { From 48be562a2af5f50252a7f254edfc6d3efa575a6c Mon Sep 17 00:00:00 2001 From: Konstantinos Chalkias Date: Thu, 4 Jan 2018 17:52:31 +0000 Subject: [PATCH 5/6] ECDSA entropyToKeyPair + no idempotent signatures (#2210) --- .../kotlin/net/corda/core/crypto/Crypto.kt | 33 ++++- .../net/corda/core/crypto/CryptoUtils.kt | 9 +- .../net/corda/core/crypto/CryptoUtilsTest.kt | 116 +++++++++++++++++- .../transactions/NotaryServiceTests.kt | 2 + .../kotlin/net/corda/testing/node/MockNode.kt | 4 +- 5 files changed, 155 insertions(+), 9 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt index e736628215..1657eb2d4a 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt @@ -30,6 +30,7 @@ import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter import org.bouncycastle.jce.ECNamedCurveTable import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec import org.bouncycastle.jce.spec.ECParameterSpec import org.bouncycastle.jce.spec.ECPrivateKeySpec import org.bouncycastle.jce.spec.ECPublicKeySpec @@ -808,7 +809,7 @@ object Crypto { /** * Returns a key pair derived from the given [BigInteger] entropy. This is useful for unit tests * and other cases where you want hard-coded private keys. - * Currently, [EDDSA_ED25519_SHA512] is the sole scheme supported for this operation. + * Currently, the following schemes are supported: [EDDSA_ED25519_SHA512], [ECDSA_SECP256R1_SHA256] and [ECDSA_SECP256K1_SHA256]. * @param signatureScheme a supported [SignatureScheme], see [Crypto]. * @param entropy a [BigInteger] value. * @return a new [KeyPair] from an entropy input. @@ -818,6 +819,7 @@ object Crypto { fun deriveKeyPairFromEntropy(signatureScheme: SignatureScheme, entropy: BigInteger): KeyPair { return when (signatureScheme) { EDDSA_ED25519_SHA512 -> deriveEdDSAKeyPairFromEntropy(entropy) + ECDSA_SECP256R1_SHA256, ECDSA_SECP256K1_SHA256 -> deriveECDSAKeyPairFromEntropy(signatureScheme, entropy) else -> throw IllegalArgumentException("Unsupported signature scheme for fixed entropy-based key pair " + "generation: ${signatureScheme.schemeCodeName}") } @@ -832,6 +834,9 @@ object Crypto { fun deriveKeyPairFromEntropy(entropy: BigInteger): KeyPair = deriveKeyPairFromEntropy(DEFAULT_SIGNATURE_SCHEME, entropy) // Custom key pair generator from entropy. + // The BigIntenger.toByteArray() uses the two's-complement representation. + // The entropy is transformed to a byte array in big-endian byte-order and + // only the first ed25519.field.getb() / 8 bytes are used. private fun deriveEdDSAKeyPairFromEntropy(entropy: BigInteger): KeyPair { val params = EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec val bytes = entropy.toByteArray().copyOf(params.curve.field.getb() / 8) // Need to pad the entropy to the valid seed length. @@ -840,6 +845,32 @@ object Crypto { return KeyPair(EdDSAPublicKey(pub), EdDSAPrivateKey(priv)) } + // Custom key pair generator from an entropy required for various tests. It is similar to deriveKeyPairECDSA, + // but the accepted range of the input entropy is more relaxed: + // 2 <= entropy < N, where N is the order of base-point G. + private fun deriveECDSAKeyPairFromEntropy(signatureScheme: SignatureScheme, entropy: BigInteger): KeyPair { + val parameterSpec = signatureScheme.algSpec as ECNamedCurveParameterSpec + + // The entropy might be a negative number and/or out of range (e.g. PRNG output). + // In such cases we retry with hash(currentEntropy). + while (entropy < ECConstants.TWO || entropy >= parameterSpec.n) { + return deriveECDSAKeyPairFromEntropy(signatureScheme, BigInteger(1, entropy.toByteArray().sha256().bytes)) + } + + val privateKeySpec = ECPrivateKeySpec(entropy, parameterSpec) + val priv = BCECPrivateKey("EC", privateKeySpec, BouncyCastleProvider.CONFIGURATION) + + val pointQ = FixedPointCombMultiplier().multiply(parameterSpec.g, entropy) + while (pointQ.isInfinity) { + // Instead of throwing an exception, we retry with hash(entropy). + return deriveECDSAKeyPairFromEntropy(signatureScheme, BigInteger(1, entropy.toByteArray().sha256().bytes)) + } + val publicKeySpec = ECPublicKeySpec(pointQ, parameterSpec) + val pub = BCECPublicKey("EC", publicKeySpec, BouncyCastleProvider.CONFIGURATION) + + return KeyPair(pub, priv) + } + // Compute the HMAC-SHA512 using a privateKey as the MAC_key and a seed ByteArray. private fun deriveHMAC(privateKey: PrivateKey, seed: ByteArray): ByteArray { // Compute hmac(privateKey, seed). diff --git a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt index 10e7c2f69c..96f0c85cc2 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt @@ -117,18 +117,19 @@ fun Iterable.byKeys() = map { it.by }.toSet() // Allow Kotlin destructuring: // val (private, public) = keyPair -/* The [PrivateKey] of this [KeyPair] .*/ +/* The [PrivateKey] of this [KeyPair]. */ operator fun KeyPair.component1(): PrivateKey = this.private -/* The [PublicKey] of this [KeyPair] .*/ +/* The [PublicKey] of this [KeyPair]. */ operator fun KeyPair.component2(): PublicKey = this.public -/** A simple wrapper that will make it easier to swap out the EC algorithm we use in future. */ +/** A simple wrapper that will make it easier to swap out the signature algorithm we use in future. */ fun generateKeyPair(): KeyPair = Crypto.generateKeyPair() /** * Returns a key pair derived from the given private key entropy. This is useful for unit tests and other cases where * you want hard-coded private keys. - * This currently works for the default signature scheme EdDSA ed25519 only. + * @param entropy a [BigInteger] value. + * @return a deterministically generated [KeyPair] for the [Crypto.DEFAULT_SIGNATURE_SCHEME]. */ fun entropyToKeyPair(entropy: BigInteger): KeyPair = Crypto.deriveKeyPairFromEntropy(entropy) diff --git a/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt b/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt index d10944bdb8..587700fc32 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/CryptoUtilsTest.kt @@ -14,21 +14,22 @@ import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey import org.bouncycastle.jce.ECNamedCurveTable import org.bouncycastle.jce.interfaces.ECKey +import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey import org.junit.Assert.assertNotEquals import org.junit.Test +import java.math.BigInteger import java.security.KeyPairGenerator import java.util.* import kotlin.test.* /** - * Run tests for cryptographic algorithms + * Run tests for cryptographic algorithms. */ class CryptoUtilsTest { - private val testString = "Hello World" - private val testBytes = testString.toByteArray() + private val testBytes = "Hello World".toByteArray() // key generation test @Test @@ -781,4 +782,113 @@ class CryptoUtilsTest { assertEquals(dpriv2, dpriv_2) assertEquals(dpub2, dpub_2) } + + @Test + fun `EdDSA ed25519 keyPair from entropy`() { + val keyPairPositive = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger("10")) + assertEquals("DLBL3iHCp9uRReWhhCGfCsrxZZpfAm9h9GLbfN8ijqXTq", keyPairPositive.public.toStringShort()) + + val keyPairNegative = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger("-10")) + assertEquals("DLC5HXnYsJAFqmM9hgPj5G8whQ4TpyE9WMBssqCayLBwA2", keyPairNegative.public.toStringShort()) + + val keyPairZero = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger("0")) + assertEquals("DL4UVhGh4tqu1G86UVoGNaDDNCMsBtNHzE6BSZuNNJN7W2", keyPairZero.public.toStringShort()) + + val keyPairOne = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger("1")) + assertEquals("DL8EZUdHixovcCynKMQzrMWBnXQAcbVDHi6ArPphqwJVzq", keyPairOne.public.toStringShort()) + + val keyPairBiggerThan256bits = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger("2").pow(258).minus(BigInteger.TEN)) + assertEquals("DLB9K1UiBrWonn481z6NzkqoWHjMBXpfDeaet3wiwRNWSU", keyPairBiggerThan256bits.public.toStringShort()) + // The underlying implementation uses the first 256 bytes of the entropy. Thus, 2^258-10 and 2^258-50 and 2^514-10 have the same impact. + val keyPairBiggerThan256bitsV2 = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger("2").pow(258).minus(BigInteger("50"))) + assertEquals("DLB9K1UiBrWonn481z6NzkqoWHjMBXpfDeaet3wiwRNWSU", keyPairBiggerThan256bitsV2.public.toStringShort()) + val keyPairBiggerThan512bits = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger("2").pow(514).minus(BigInteger.TEN)) + assertEquals("DLB9K1UiBrWonn481z6NzkqoWHjMBXpfDeaet3wiwRNWSU", keyPairBiggerThan512bits.public.toStringShort()) + + // Try another big number. + val keyPairBiggerThan258bits = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger("2").pow(259).plus(BigInteger.ONE)) + assertEquals("DL5tEFVMXMGrzwjfCAW34JjkhsRkPfFyJ38iEnmpB6L2Z9", keyPairBiggerThan258bits.public.toStringShort()) + } + + @Test + fun `ECDSA R1 keyPair from entropy`() { + val keyPairPositive = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256R1_SHA256, BigInteger("10")) + assertEquals("DLHDcxuSt9J3cbjd2Dsx4rAgYYA7BAP7A8VLrFiq1tH9yy", keyPairPositive.public.toStringShort()) + // The underlying implementation uses the hash of entropy if it is out of range 2 < entropy < N, where N the order of the group. + val keyPairNegative = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256R1_SHA256, BigInteger("-10")) + assertEquals("DLBASmjiMZuu1g3EtdHJxfSueXE8PRoUWbkdU61Qcnpamt", keyPairNegative.public.toStringShort()) + + val keyPairZero = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256R1_SHA256, BigInteger("0")) + assertEquals("DLH2FEHEnsT3MpCJt2gfyNjpqRqcBxeupK4YRPXvDsVEkb", keyPairZero.public.toStringShort()) + // BigIntenger.Zero is out or range, so 1 and hash(1.toByteArray) would have the same impact. + val zeroHashed = BigInteger(1, BigInteger("0").toByteArray().sha256().bytes) + // Check oneHashed < N (order of the group), otherwise we would need an extra hash. + assertEquals(-1, zeroHashed.compareTo((Crypto.ECDSA_SECP256R1_SHA256.algSpec as ECNamedCurveParameterSpec).n)) + val keyPairZeroHashed = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256R1_SHA256, zeroHashed) + assertEquals("DLH2FEHEnsT3MpCJt2gfyNjpqRqcBxeupK4YRPXvDsVEkb", keyPairZeroHashed.public.toStringShort()) + + val keyPairOne = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256R1_SHA256, BigInteger("1")) + assertEquals("DLHrtKwjv6onq9HcrQDJPs8Cgtai5mZU5ZU6sb1ivJjx3z", keyPairOne.public.toStringShort()) + // BigIntenger.ONE is out or range, so 1 and hash(1.toByteArray) would have the same impact. + val oneHashed = BigInteger(1, BigInteger("1").toByteArray().sha256().bytes) + // Check oneHashed < N (order of the group), otherwise we would need an extra hash. + assertEquals(-1, oneHashed.compareTo((Crypto.ECDSA_SECP256R1_SHA256.algSpec as ECNamedCurveParameterSpec).n)) + val keyPairOneHashed = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256R1_SHA256, oneHashed) + assertEquals("DLHrtKwjv6onq9HcrQDJPs8Cgtai5mZU5ZU6sb1ivJjx3z", keyPairOneHashed.public.toStringShort()) + + // 2 is in the range. + val keyPairTwo = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256R1_SHA256, BigInteger("2")) + assertEquals("DLFoz6txJ3vHcKNSM1vFxHJUoEQ69PorBwW64dHsAnEoZB", keyPairTwo.public.toStringShort()) + + // Try big numbers that are out of range. + val keyPairBiggerThan256bits = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256R1_SHA256, BigInteger("2").pow(258).minus(BigInteger.TEN)) + assertEquals("DLBv6fZqaCTbE4L7sgjbt19biXHMgU9CzR5s8g8XBJjZ11", keyPairBiggerThan256bits.public.toStringShort()) + val keyPairBiggerThan256bitsV2 = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256R1_SHA256, BigInteger("2").pow(258).minus(BigInteger("50"))) + assertEquals("DLANmjhGSVdLyghxcPHrn3KuGatscf6LtvqifUDxw7SGU8", keyPairBiggerThan256bitsV2.public.toStringShort()) + val keyPairBiggerThan512bits = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256R1_SHA256, BigInteger("2").pow(514).minus(BigInteger.TEN)) + assertEquals("DL9sKwMExBTD3MnJN6LWGqo496Erkebs9fxZtXLVJUBY9Z", keyPairBiggerThan512bits.public.toStringShort()) + val keyPairBiggerThan258bits = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256R1_SHA256, BigInteger("2").pow(259).plus(BigInteger.ONE)) + assertEquals("DLBwjWwPJSF9E7b1NWaSbEJ4oK8CF7RDGWd648TiBhZoL1", keyPairBiggerThan258bits.public.toStringShort()) + } + + @Test + fun `ECDSA K1 keyPair from entropy`() { + val keyPairPositive = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256K1_SHA256, BigInteger("10")) + assertEquals("DL6pYKUgH17az8MLdonvvUtUPN8TqwpCGcdgLr7vg3skCU", keyPairPositive.public.toStringShort()) + // The underlying implementation uses the hash of entropy if it is out of range 2 <= entropy < N, where N the order of the group. + val keyPairNegative = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256K1_SHA256, BigInteger("-10")) + assertEquals("DLnpXhxece69Nyqgm3pPt3yV7ESQYDJKoYxs1hKgfBAEu", keyPairNegative.public.toStringShort()) + + val keyPairZero = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256K1_SHA256, BigInteger("0")) + assertEquals("DLBC28e18T6KsYwjTFfUWJfhvHjvYVapyVf6antnqUkbgd", keyPairZero.public.toStringShort()) + // BigIntenger.Zero is out or range, so 1 and hash(1.toByteArray) would have the same impact. + val zeroHashed = BigInteger(1, BigInteger("0").toByteArray().sha256().bytes) + // Check oneHashed < N (order of the group), otherwise we would need an extra hash. + assertEquals(-1, zeroHashed.compareTo((Crypto.ECDSA_SECP256K1_SHA256.algSpec as ECNamedCurveParameterSpec).n)) + val keyPairZeroHashed = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256K1_SHA256, zeroHashed) + assertEquals("DLBC28e18T6KsYwjTFfUWJfhvHjvYVapyVf6antnqUkbgd", keyPairZeroHashed.public.toStringShort()) + + val keyPairOne = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256K1_SHA256, BigInteger("1")) + assertEquals("DLBimRXdEQhJUTpL6f9ri9woNdsze6mwkRrhsML13Eh7ET", keyPairOne.public.toStringShort()) + // BigIntenger.ONE is out or range, so 1 and hash(1.toByteArray) would have the same impact. + val oneHashed = BigInteger(1, BigInteger("1").toByteArray().sha256().bytes) + // Check oneHashed < N (order of the group), otherwise we would need an extra hash. + assertEquals(-1, oneHashed.compareTo((Crypto.ECDSA_SECP256K1_SHA256.algSpec as ECNamedCurveParameterSpec).n)) + val keyPairOneHashed = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256K1_SHA256, oneHashed) + assertEquals("DLBimRXdEQhJUTpL6f9ri9woNdsze6mwkRrhsML13Eh7ET", keyPairOneHashed.public.toStringShort()) + + // 2 is in the range. + val keyPairTwo = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256K1_SHA256, BigInteger("2")) + assertEquals("DLG32UWaevGw9YY7w1Rf9mmK88biavgpDnJA9bG4GapVPs", keyPairTwo.public.toStringShort()) + + // Try big numbers that are out of range. + val keyPairBiggerThan256bits = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256K1_SHA256, BigInteger("2").pow(258).minus(BigInteger.TEN)) + assertEquals("DLGHsdv2xeAuM7n3sBc6mFfiphXe6VSf3YxqvviKDU6Vbd", keyPairBiggerThan256bits.public.toStringShort()) + val keyPairBiggerThan256bitsV2 = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256K1_SHA256, BigInteger("2").pow(258).minus(BigInteger("50"))) + assertEquals("DL9yJfiNGqteRrKPjGUkRQkeqzuQ4kwcYQWMCi5YKuUHrk", keyPairBiggerThan256bitsV2.public.toStringShort()) + val keyPairBiggerThan512bits = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256K1_SHA256, BigInteger("2").pow(514).minus(BigInteger.TEN)) + assertEquals("DL3Wr5EQGrMTaKBy5XMvG8rvSfKX1AYZLCRU8kixGbxt1E", keyPairBiggerThan512bits.public.toStringShort()) + val keyPairBiggerThan258bits = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256K1_SHA256, BigInteger("2").pow(259).plus(BigInteger.ONE)) + assertEquals("DL7NbssqvuuJ4cqFkkaVYu9j1MsVswESGgCfbqBS9ULwuM", keyPairBiggerThan258bits.public.toStringShort()) + } } diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt index 70cbc90835..77c5cf797b 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt @@ -24,6 +24,7 @@ import net.corda.testing.node.startFlow import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Test import java.time.Instant import java.util.* @@ -99,6 +100,7 @@ class NotaryServiceTests { assertThat(ex.error).isInstanceOf(NotaryError.TimeWindowInvalid::class.java) } + @Ignore("Only applies to deterministic signature schemes (e.g. EdDSA) and when deterministic metadata is attached (no timestamps or nonces)") @Test fun `should sign identical transaction multiple times (signing is idempotent)`() { val stx = run { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index fabb3d999a..0b8ae32540 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -6,6 +6,7 @@ import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.DoNotImplement import net.corda.core.crypto.entropyToKeyPair +import net.corda.core.crypto.Crypto import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party @@ -325,7 +326,8 @@ open class MockNetwork(private val cordappPackages: List, // This is not thread safe, but node construction is done on a single thread, so that should always be fine override fun generateKeyPair(): KeyPair { counter = counter.add(BigInteger.ONE) - return entropyToKeyPair(counter) + // The MockNode specifically uses EdDSA keys as they are fixed and stored in json files for some tests (e.g IRSSimulation). + return Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, counter) } /** From 00a02b30fe4915d1f0800ebcf13a281760d32062 Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Thu, 4 Jan 2018 18:05:06 +0000 Subject: [PATCH 6/6] Update link for Corda API strategy documentation. (#2316) --- gradle-plugins/api-scanner/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle-plugins/api-scanner/README.md b/gradle-plugins/api-scanner/README.md index 9f8fc4f674..a0256d480b 100644 --- a/gradle-plugins/api-scanner/README.md +++ b/gradle-plugins/api-scanner/README.md @@ -6,7 +6,7 @@ Generates a text summary of Corda's public API that we can check for API-breakin $ gradlew generateApi ``` -See [here](../../docs/source/api-index.rst) for Corda's public API strategy. We will need to +See [here](../../docs/source/corda-api.rst) for Corda's public API strategy. We will need to apply this plugin to other modules in future Corda releases as those modules' APIs stabilise. Basically, this plugin will document a module's `public` and `protected` classes/methods/fields,