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 8549908a25..764a115854 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt @@ -118,18 +118,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/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/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/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/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, 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. " + 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() { 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/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/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)) } 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 289ca46330..331f1b940d 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 @@ -326,7 +327,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) } /** 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 9bc750a772..1f91cc92e8 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt @@ -152,7 +152,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")