From 309159da14de2abc6759c249e60e3c0ff08199c3 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Tue, 26 Jul 2016 20:17:39 +0200 Subject: [PATCH 1/6] Better toString for Amount post-issuer refactoring. --- .../main/kotlin/com/r3corda/core/contracts/FinanceTypes.kt | 2 +- .../main/kotlin/com/r3corda/core/contracts/Structures.kt | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/FinanceTypes.kt b/core/src/main/kotlin/com/r3corda/core/contracts/FinanceTypes.kt index a437b10cdd..88553a1615 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/FinanceTypes.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/FinanceTypes.kt @@ -61,7 +61,7 @@ data class Amount(val quantity: Long, val token: T) : Comparable> { operator fun div(other: Int): Amount = Amount(quantity / other, token) operator fun times(other: Int): Amount = Amount(Math.multiplyExact(quantity, other.toLong()), token) - override fun toString(): String = (BigDecimal(quantity).divide(BigDecimal(100))).setScale(2).toPlainString() + override fun toString(): String = (BigDecimal(quantity).divide(BigDecimal(100))).setScale(2).toPlainString() + " " + token override fun compareTo(other: Amount): Int { checkCurrency(other) diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt b/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt index 313d57fc49..56b60a917e 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt @@ -146,10 +146,9 @@ interface IssuanceDefinition * * @param P the type of product underlying the definition, for example [Currency]. */ -data class Issued( - val issuer: PartyAndReference, - val product: P -) +data class Issued(val issuer: PartyAndReference, val product: P) { + override fun toString() = "$product issued by $issuer" +} /** * A contract state that can have a single owner. From b40fee151270781d5774a0120a33fbc70e1ca35d Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Thu, 28 Jul 2016 11:51:19 +0200 Subject: [PATCH 2/6] Allow for easier conversion from Amount> to Amount and de-issuerify a few places. --- .../contracts/JavaCommercialPaper.java | 2 +- .../com/r3corda/contracts/asset/Cash.kt | 24 +++++++------------ .../r3corda/contracts/asset/FungibleAsset.kt | 4 +++- .../protocols/TwoPartyTradeProtocol.kt | 10 ++++---- .../com/r3corda/core/contracts/Structures.kt | 7 ++++++ .../node/internal/testing/TradeSimulation.kt | 5 +--- .../messaging/TwoPartyTradeProtocolTests.kt | 20 ++++++++-------- .../node/services/WalletWithCashTest.kt | 2 +- .../kotlin/com/r3corda/demos/TraderDemo.kt | 10 ++++---- 9 files changed, 41 insertions(+), 43 deletions(-) diff --git a/contracts/src/main/java/com/r3corda/contracts/JavaCommercialPaper.java b/contracts/src/main/java/com/r3corda/contracts/JavaCommercialPaper.java index 497e5a1cda..b589d0ffb6 100644 --- a/contracts/src/main/java/com/r3corda/contracts/JavaCommercialPaper.java +++ b/contracts/src/main/java/com/r3corda/contracts/JavaCommercialPaper.java @@ -344,7 +344,7 @@ public class JavaCommercialPaper extends ClauseVerifier { } public void generateRedeem(TransactionBuilder tx, StateAndRef paper, List> wallet) throws InsufficientBalanceException { - new Cash().generateSpend(tx, paper.getState().getData().getFaceValue(), paper.getState().getData().getOwner(), wallet); + new Cash().generateSpend(tx, StructuresKt.withoutIssuer(paper.getState().getData().getFaceValue()), paper.getState().getData().getOwner(), wallet, null); tx.addInputState(paper); tx.addCommand(new Command(new Commands.Redeem(paper.getState().getNotary()), paper.getState().getData().getOwner())); } diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/asset/Cash.kt b/contracts/src/main/kotlin/com/r3corda/contracts/asset/Cash.kt index f2915f8ded..7a93ac0c03 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/asset/Cash.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/asset/Cash.kt @@ -211,16 +211,6 @@ class Cash : ClauseVerifier() { tx.addCommand(Cash.Commands.Issue(), at.party.owningKey) } - /** - * Generate a transaction that consumes one or more of the given input states to move money to the given pubkey. - * Note that the wallet list is not updated: it's up to you to do that. - */ - @Throws(InsufficientBalanceException::class) - fun generateSpend(tx: TransactionBuilder, amount: Amount>, to: PublicKey, - cashStates: List>): List = - generateSpend(tx, Amount(amount.quantity, amount.token.product), to, cashStates, - setOf(amount.token.issuer.party)) - /** * Generate a transaction that consumes one or more of the given input states to move money to the given pubkey. * Note that the wallet list is not updated: it's up to you to do that. @@ -301,21 +291,23 @@ class Cash : ClauseVerifier() { /** * Sums the cash states in the list belonging to a single owner, throwing an exception * if there are none, or if any of the cash states cannot be added together (i.e. are - * different currencies). + * different currencies or issuers). */ -fun Iterable.sumCashBy(owner: PublicKey) = filterIsInstance().filter { it.owner == owner }.map { it.amount }.sumOrThrow() +fun Iterable.sumCashBy(owner: PublicKey): Amount> = filterIsInstance().filter { it.owner == owner }.map { it.amount }.sumOrThrow() /** * Sums the cash states in the list, throwing an exception if there are none, or if any of the cash * states cannot be added together (i.e. are different currencies). */ -fun Iterable.sumCash() = filterIsInstance().map { it.amount }.sumOrThrow() +fun Iterable.sumCash(): Amount> = filterIsInstance().map { it.amount }.sumOrThrow() /** Sums the cash states in the list, returning null if there are none. */ -fun Iterable.sumCashOrNull() = filterIsInstance().map { it.amount }.sumOrNull() +fun Iterable.sumCashOrNull(): Amount>? = filterIsInstance().map { it.amount }.sumOrNull() -/** Sums the cash states in the list, returning zero of the given currency if there are none. */ -fun Iterable.sumCashOrZero(currency: Issued) = filterIsInstance().map { it.amount }.sumOrZero>(currency) +/** Sums the cash states in the list, returning zero of the given currency+issuer if there are none. */ +fun Iterable.sumCashOrZero(currency: Issued): Amount> { + return filterIsInstance().map { it.amount }.sumOrZero>(currency) +} /** * Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/asset/FungibleAsset.kt b/contracts/src/main/kotlin/com/r3corda/contracts/asset/FungibleAsset.kt index 67d7e7e910..5636ab50e1 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/asset/FungibleAsset.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/asset/FungibleAsset.kt @@ -4,7 +4,9 @@ import com.r3corda.core.contracts.* import java.security.PublicKey import java.util.* -class InsufficientBalanceException(val amountMissing: Amount<*>) : Exception() +class InsufficientBalanceException(val amountMissing: Amount<*>) : Exception() { + override fun toString() = "Insufficient balance, missing $amountMissing" +} /** * Interface for contract states representing assets which are fungible, countable and issued by a diff --git a/contracts/src/main/kotlin/com/r3corda/protocols/TwoPartyTradeProtocol.kt b/contracts/src/main/kotlin/com/r3corda/protocols/TwoPartyTradeProtocol.kt index a9a16df05d..fe06b16bd3 100644 --- a/contracts/src/main/kotlin/com/r3corda/protocols/TwoPartyTradeProtocol.kt +++ b/contracts/src/main/kotlin/com/r3corda/protocols/TwoPartyTradeProtocol.kt @@ -47,7 +47,7 @@ object TwoPartyTradeProtocol { val TOPIC = "platform.trade" - class UnacceptablePriceException(val givenPrice: Amount>) : Exception() + class UnacceptablePriceException(val givenPrice: Amount) : Exception() class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() { override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName" } @@ -55,7 +55,7 @@ object TwoPartyTradeProtocol { // This object is serialised to the network and is the first protocol message the seller sends to the buyer. class SellerTradeInfo( val assetForSale: StateAndRef, - val price: Amount>, + val price: Amount, val sellerOwnerKey: PublicKey, val sessionID: Long ) @@ -66,7 +66,7 @@ object TwoPartyTradeProtocol { open class Seller(val otherSide: Party, val notaryNode: NodeInfo, val assetToSell: StateAndRef, - val price: Amount>, + val price: Amount, val myKeyPair: KeyPair, val buyerSessionID: Long, override val progressTracker: ProgressTracker = Seller.tracker()) : ProtocolLogic() { @@ -133,7 +133,7 @@ object TwoPartyTradeProtocol { // This verifies that the transaction is contract-valid, even though it is missing signatures. serviceHub.verifyTransaction(wtx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments)) - if (wtx.outputs.map { it.data }.sumCashBy(myKeyPair.public) != price) + if (wtx.outputs.map { it.data }.sumCashBy(myKeyPair.public).withoutIssuer() != price) throw IllegalArgumentException("Transaction is not sending us the right amount of cash") // There are all sorts of funny games a malicious secondary might play here, we should fix them: @@ -178,7 +178,7 @@ object TwoPartyTradeProtocol { open class Buyer(val otherSide: Party, val notary: Party, - val acceptablePrice: Amount>, + val acceptablePrice: Amount, val typeToBuy: Class, val sessionID: Long) : ProtocolLogic() { diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt b/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt index 56b60a917e..8bd276c240 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt @@ -150,6 +150,13 @@ data class Issued(val issuer: PartyAndReference, val product: P) { override fun toString() = "$product issued by $issuer" } +/** + * Strips the issuer and returns an [Amount] of the raw token directly. This is useful when you are mixing code that + * cares about specific issuers with code that will accept any, or which is imposing issuer constraints via some + * other mechanism and the additional type safety is not wanted. + */ +fun Amount>.withoutIssuer(): Amount = Amount(quantity, token.product) + /** * A contract state that can have a single owner. */ diff --git a/node/src/main/kotlin/com/r3corda/node/internal/testing/TradeSimulation.kt b/node/src/main/kotlin/com/r3corda/node/internal/testing/TradeSimulation.kt index 007a1f8d31..ad844dcc13 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/testing/TradeSimulation.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/testing/TradeSimulation.kt @@ -8,8 +8,6 @@ import com.r3corda.contracts.testing.fillWithSomeTestCash import com.r3corda.core.contracts.DOLLARS import com.r3corda.core.contracts.SignedTransaction import com.r3corda.core.contracts.`issued by` -import com.r3corda.core.crypto.Party -import com.r3corda.core.crypto.generateKeyPair import com.r3corda.core.days import com.r3corda.core.random63BitValue import com.r3corda.core.seconds @@ -43,8 +41,7 @@ class TradeSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwo } seller.services.recordTransactions(issuance) - val cashIssuerKey = generateKeyPair() - val amount = 1000.DOLLARS `issued by` Party("Big friendly bank", cashIssuerKey.public).ref(1) + val amount = 1000.DOLLARS val sessionID = random63BitValue() val buyerProtocol = TwoPartyTradeProtocol.Buyer( seller.info.identity, diff --git a/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt b/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt index 99a047ae52..aec019bb17 100644 --- a/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt +++ b/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt @@ -54,14 +54,14 @@ class TwoPartyTradeProtocolTests { lateinit var net: MockNetwork private fun runSeller(smm: StateMachineManager, notary: NodeInfo, - otherSide: Party, assetToSell: StateAndRef, price: Amount>, + otherSide: Party, assetToSell: StateAndRef, price: Amount, myKeyPair: KeyPair, buyerSessionID: Long): ListenableFuture { val seller = TwoPartyTradeProtocol.Seller(otherSide, notary, assetToSell, price, myKeyPair, buyerSessionID) return smm.add("${TwoPartyTradeProtocol.TOPIC}.seller", seller) } private fun runBuyer(smm: StateMachineManager, notaryNode: NodeInfo, - otherSide: Party, acceptablePrice: Amount>, typeToBuy: Class, + otherSide: Party, acceptablePrice: Amount, typeToBuy: Class, sessionID: Long): ListenableFuture { val buyer = TwoPartyTradeProtocol.Buyer(otherSide, notaryNode.identity, acceptablePrice, typeToBuy, sessionID) return smm.add("${TwoPartyTradeProtocol.TOPIC}.buyer", buyer) @@ -106,7 +106,7 @@ class TwoPartyTradeProtocolTests { bobNode.smm, notaryNode.info, aliceNode.info.identity, - 1000.DOLLARS `issued by` issuer, + 1000.DOLLARS, CommercialPaper.State::class.java, buyerSessionID ) @@ -115,7 +115,7 @@ class TwoPartyTradeProtocolTests { notaryNode.info, bobNode.info.identity, "alice's paper".outputStateAndRef(), - 1000.DOLLARS `issued by` issuer, + 1000.DOLLARS, ALICE_KEY, buyerSessionID ) @@ -158,7 +158,7 @@ class TwoPartyTradeProtocolTests { notaryNode.info, bobNode.info.identity, "alice's paper".outputStateAndRef(), - 1000.DOLLARS `issued by` issuer, + 1000.DOLLARS, ALICE_KEY, buyerSessionID ) @@ -166,7 +166,7 @@ class TwoPartyTradeProtocolTests { bobNode.smm, notaryNode.info, aliceNode.info.identity, - 1000.DOLLARS `issued by` issuer, + 1000.DOLLARS, CommercialPaper.State::class.java, buyerSessionID ) @@ -279,7 +279,7 @@ class TwoPartyTradeProtocolTests { notaryNode.info, bobNode.info.identity, "alice's paper".outputStateAndRef(), - 1000.DOLLARS `issued by` issuer, + 1000.DOLLARS, ALICE_KEY, buyerSessionID ) @@ -287,7 +287,7 @@ class TwoPartyTradeProtocolTests { bobNode.smm, notaryNode.info, aliceNode.info.identity, - 1000.DOLLARS `issued by` issuer, + 1000.DOLLARS, CommercialPaper.State::class.java, buyerSessionID ) @@ -390,7 +390,7 @@ class TwoPartyTradeProtocolTests { notaryNode.info, bobNode.info.identity, "alice's paper".outputStateAndRef(), - 1000.DOLLARS `issued by` issuer, + 1000.DOLLARS, ALICE_KEY, buyerSessionID ) @@ -398,7 +398,7 @@ class TwoPartyTradeProtocolTests { bobNode.smm, notaryNode.info, aliceNode.info.identity, - 1000.DOLLARS `issued by` issuer, + 1000.DOLLARS, CommercialPaper.State::class.java, buyerSessionID ) diff --git a/node/src/test/kotlin/com/r3corda/node/services/WalletWithCashTest.kt b/node/src/test/kotlin/com/r3corda/node/services/WalletWithCashTest.kt index 0270e7ffa9..8f8cd5ba3b 100644 --- a/node/src/test/kotlin/com/r3corda/node/services/WalletWithCashTest.kt +++ b/node/src/test/kotlin/com/r3corda/node/services/WalletWithCashTest.kt @@ -76,7 +76,7 @@ class WalletWithCashTest { // A tx that spends our money. val spendTX = TransactionType.General.Builder().apply { - Cash().generateSpend(this, 80.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_PUBKEY, listOf(myOutput)) + Cash().generateSpend(this, 80.DOLLARS, BOB_PUBKEY, listOf(myOutput)) signWith(freshKey) signWith(DUMMY_NOTARY_KEY) }.toSignedTransaction() diff --git a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt index ebdbde4689..c98449523b 100644 --- a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt @@ -164,7 +164,7 @@ fun runTraderDemo(args: Array): Int { // What happens next depends on the role. The buyer sits around waiting for a trade to start. The seller role // will contact the buyer and actually make something happen. - val amount = 1000.DOLLARS `issued by` cashIssuer.ref(0) // Note: "0" has to match the reference used in the wallet filler + val amount = 1000.DOLLARS if (role == Role.BUYER) { runBuyer(node, amount) } else { @@ -174,7 +174,7 @@ fun runTraderDemo(args: Array): Int { return 0 } -private fun runSeller(node: Node, amount: Amount>, otherSide: Party) { +private fun runSeller(node: Node, amount: Amount, otherSide: Party) { // The seller will sell some commercial paper to the buyer, who will pay with (self issued) cash. // // The CP sale transaction comes with a prospectus PDF, which will tag along for the ride in an @@ -202,7 +202,7 @@ private fun runSeller(node: Node, amount: Amount>, otherSide: P node.stop() } -private fun runBuyer(node: Node, amount: Amount>) { +private fun runBuyer(node: Node, amount: Amount) { // Buyer will fetch the attachment from the seller automatically when it resolves the transaction. // For demo purposes just extract attachment jars when saved to disk, so the user can explore them. val attachmentsPath = (node.storage.attachments as NodeAttachmentService).let { @@ -236,7 +236,7 @@ val DEMO_TOPIC = "initiate.demo.trade" private class TraderDemoProtocolBuyer(val otherSide: Party, private val attachmentsPath: Path, - val amount: Amount>, + val amount: Amount, override val progressTracker: ProgressTracker = ProgressTracker(STARTING_BUY)) : ProtocolLogic() { object STARTING_BUY : ProgressTracker.Step("Seller connected, purchasing commercial paper asset") @@ -294,7 +294,7 @@ ${Emoji.renderIfSupported(cpIssuance)}""") } private class TraderDemoProtocolSeller(val otherSide: Party, - val amount: Amount>, + val amount: Amount, override val progressTracker: ProgressTracker = TraderDemoProtocolSeller.tracker()) : ProtocolLogic() { companion object { val PROSPECTUS_HASH = SecureHash.parse("decd098666b9657314870e192ced0c3519c2c9d395507a238338f8d003929de9") From 0662798b0f9c942b60fdd9fceeb2a72728201990 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 29 Jul 2016 12:12:35 +0200 Subject: [PATCH 3/6] Introduce some new cryptographic utility functions 1. Function for converting raw entropy into an EDDSA key pair. This is useful for unit tests when you don't want a random key but would rather be able to identify it from the logs by eyesight, and will be useful later also when implementing deterministic key derivation. 2. Function that can format any collection of public keys using the bitcoin-style base58 form. 3. A dummy NullSignature object, again, useful for tests when you don't want to provide a real signature. Then set a handful of dummy unit testing keys to predictable/fixed values. --- .../com/r3corda/contracts/asset/Cash.kt | 3 +- .../r3corda/core/crypto/CryptoUtilities.kt | 28 +++++++++++++++++-- .../com/r3corda/core/testing/CoreTestUtils.kt | 8 ++---- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/asset/Cash.kt b/contracts/src/main/kotlin/com/r3corda/contracts/asset/Cash.kt index 7a93ac0c03..a34cd9f158 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/asset/Cash.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/asset/Cash.kt @@ -8,6 +8,7 @@ import com.r3corda.core.contracts.clauses.* import com.r3corda.core.crypto.* import com.r3corda.core.node.services.Wallet import com.r3corda.core.utilities.Emoji +import java.math.BigInteger import java.security.PublicKey import java.util.* @@ -334,7 +335,7 @@ infix fun Cash.State.`with deposit`(deposit: PartyAndReference): Cash.State = wi // Unit testing helpers. These could go in a separate file but it's hardly worth it for just a few functions. /** A randomly generated key. */ -val DUMMY_CASH_ISSUER_KEY by lazy { generateKeyPair() } +val DUMMY_CASH_ISSUER_KEY by lazy { entropyToKeyPair(BigInteger.valueOf(10)) } /** A dummy, randomly generated issuer party by the name of "Snake Oil Issuer" */ val DUMMY_CASH_ISSUER by lazy { Party("Snake Oil Issuer", DUMMY_CASH_ISSUER_KEY.public).ref(1) } /** An extension property that lets you write 100.DOLLARS.CASH */ 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 1bace21711..f7682c03e2 100644 --- a/core/src/main/kotlin/com/r3corda/core/crypto/CryptoUtilities.kt +++ b/core/src/main/kotlin/com/r3corda/core/crypto/CryptoUtilities.kt @@ -5,10 +5,14 @@ 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 net.i2p.crypto.eddsa.EdDSAPrivateKey import net.i2p.crypto.eddsa.EdDSAPublicKey +import net.i2p.crypto.eddsa.KeyPairGenerator +import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable +import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec +import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec import java.math.BigInteger import java.security.* -import net.i2p.crypto.eddsa.KeyPairGenerator as EddsaKeyPairGenerator fun newSecureRandom(): SecureRandom { if (System.getProperty("os.name") == "Linux") { @@ -115,6 +119,7 @@ object NullPublicKey : PublicKey, Comparable { override fun toString() = "NULL_KEY" } +// TODO: Clean up this duplication between Null and Dummy public key class DummyPublicKey(val s: String) : PublicKey, Comparable { override fun getAlgorithm() = "DUMMY" override fun getEncoded() = s.toByteArray() @@ -125,6 +130,9 @@ class DummyPublicKey(val s: String) : PublicKey, Comparable { override fun toString() = "PUBKEY[$s]" } +/** A signature with a key and value of zero. Useful when you want a signature object that you know won't ever be used. */ +object NullSignature : DigitalSignature.WithKey(NullPublicKey, ByteArray(32)) + /** Utility to simplify the act of signing a byte array */ fun PrivateKey.signWithECDSA(bits: ByteArray): DigitalSignature { val signer = EdDSAEngine() @@ -163,10 +171,24 @@ fun PublicKey.toStringShort(): String { } ?: toString() } +fun Iterable.toStringsShort(): String = map { it.toStringShort() }.toString() + // Allow Kotlin destructuring: val (private, public) = keypair 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(): KeyPair = EddsaKeyPairGenerator().generateKeyPair() +fun generateKeyPair(): KeyPair = KeyPairGenerator().generateKeyPair() + +/** + * Returns a keypair derived from the given private key entropy. This is useful for unit tests and other cases where + * you want hard-coded private keys. + */ +fun entropyToKeyPair(entropy: BigInteger): KeyPair { + val params = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512) + val bits = entropy.toByteArray().copyOf(params.curve.field.getb() / 8) + val priv = EdDSAPrivateKeySpec(bits, params) + val pub = EdDSAPublicKeySpec(priv.a, params) + val key = KeyPair(EdDSAPublicKey(pub), EdDSAPrivateKey(priv)) + return key +} diff --git a/core/src/main/kotlin/com/r3corda/core/testing/CoreTestUtils.kt b/core/src/main/kotlin/com/r3corda/core/testing/CoreTestUtils.kt index 704601cdec..1a80c1a024 100644 --- a/core/src/main/kotlin/com/r3corda/core/testing/CoreTestUtils.kt +++ b/core/src/main/kotlin/com/r3corda/core/testing/CoreTestUtils.kt @@ -7,14 +7,12 @@ import com.google.common.net.HostAndPort import com.r3corda.core.contracts.Attachment import com.r3corda.core.contracts.StateRef import com.r3corda.core.contracts.TransactionBuilder -import com.r3corda.core.crypto.DummyPublicKey -import com.r3corda.core.crypto.Party -import com.r3corda.core.crypto.SecureHash -import com.r3corda.core.crypto.generateKeyPair +import com.r3corda.core.crypto.* import com.r3corda.core.node.services.IdentityService import com.r3corda.core.node.services.StorageService import com.r3corda.core.node.services.testing.MockIdentityService import com.r3corda.core.node.services.testing.MockStorageService +import java.math.BigInteger import java.net.ServerSocket import java.security.KeyPair import java.security.PublicKey @@ -73,7 +71,7 @@ val CHARLIE: Party get() = Party("Charlie", CHARLIE_PUBKEY) val MEGA_CORP: Party get() = Party("MegaCorp", MEGA_CORP_PUBKEY) val MINI_CORP: Party get() = Party("MiniCorp", MINI_CORP_PUBKEY) -val DUMMY_NOTARY_KEY: KeyPair by lazy { generateKeyPair() } +val DUMMY_NOTARY_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(20)) } val DUMMY_NOTARY: Party get() = Party("Notary Service", DUMMY_NOTARY_KEY.public) val ALL_TEST_KEYS: List get() = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, DUMMY_NOTARY_KEY) From 987c374b98dca3f0101da55f843063e488408ffb Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 29 Jul 2016 12:18:56 +0200 Subject: [PATCH 4/6] Minor: slightly better error message when trying to send to an unknown party. --- .../node/services/statemachine/StateMachineManager.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/node/src/main/kotlin/com/r3corda/node/services/statemachine/StateMachineManager.kt b/node/src/main/kotlin/com/r3corda/node/services/statemachine/StateMachineManager.kt index 64c7f2c423..e6b247d4d9 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/statemachine/StateMachineManager.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/statemachine/StateMachineManager.kt @@ -270,8 +270,10 @@ class StateMachineManager(val serviceHub: ServiceHubInternal, tokenizableService request.payload?.let { psm.logger.trace { "Sending message of type ${it.javaClass.name} using queue $queueID to ${request.destination} (${it.toString().abbreviate(50)})" } val node = serviceHub.networkMapCache.getNodeByLegalName(request.destination!!.name) - requireNotNull(node) { "Don't know about ${request.destination}" } - serviceHub.networkService.send(queueID, it, node!!.address) + if (node == null) { + throw IllegalArgumentException("Don't know about ${request.destination} but trying to send a message of type ${it.javaClass.name} on $queueID (${it.toString().abbreviate(50)})", request.stackTraceInCaseOfProblems) + } + serviceHub.networkService.send(queueID, it, node.address) } if (request is FiberRequest.NotExpectingResponse) { // We sent a message, but don't expect a response, so re-enter the continuation to let it keep going. From daff65f220126a84f54d58a4bf249087a67e4542 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 29 Jul 2016 12:20:09 +0200 Subject: [PATCH 5/6] Minor: add infix utilities for overflow checked arithmetic. --- core/src/main/kotlin/com/r3corda/core/Utils.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/kotlin/com/r3corda/core/Utils.kt b/core/src/main/kotlin/com/r3corda/core/Utils.kt index 68ebed813f..353700e060 100644 --- a/core/src/main/kotlin/com/r3corda/core/Utils.kt +++ b/core/src/main/kotlin/com/r3corda/core/Utils.kt @@ -31,6 +31,11 @@ val Long.bd: BigDecimal get() = BigDecimal(this) fun String.abbreviate(maxWidth: Int): String = if (length <= maxWidth) this else take(maxWidth - 1) + "…" +/** Like the + operator but throws an exception in case of integer overflow. */ +infix fun Int.checkedAdd(b: Int) = Math.addExact(this, b) +/** Like the + operator but throws an exception in case of integer overflow. */ +infix fun Long.checkedAdd(b: Long) = Math.addExact(this, b) + /** * Returns a random positive long generated using a secure RNG. This function sacrifies a bit of entropy in order to * avoid potential bugs where the value is used in a context where negative numbers are not expected. From 5df1caf0cad9ae8c11ae631220a2f924338359d3 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 29 Jul 2016 15:19:14 +0200 Subject: [PATCH 6/6] Minor: couple of tiny reformattings. --- .../kotlin/com/r3corda/core/contracts/TransactionBuilder.kt | 5 ++--- .../com/r3corda/core/testing/TransactionDSLInterpreter.kt | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/TransactionBuilder.kt b/core/src/main/kotlin/com/r3corda/core/contracts/TransactionBuilder.kt index e032b2c6bb..59a76b1c9f 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/TransactionBuilder.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/TransactionBuilder.kt @@ -120,9 +120,9 @@ open class TransactionBuilder( fun toSignedTransaction(checkSufficientSignatures: Boolean = true): SignedTransaction { if (checkSufficientSignatures) { val gotKeys = currentSigs.map { it.by }.toSet() - val missing = signers - gotKeys + val missing: Set = signers - gotKeys if (missing.isNotEmpty()) - throw IllegalStateException("Missing signatures on the transaction for the public keys: ${missing.map { it.toStringShort() }}") + throw IllegalStateException("Missing signatures on the transaction for the public keys: ${missing.toStringsShort()}") } return SignedTransaction(toWireTransaction().serialize(), ArrayList(currentSigs)) } @@ -131,7 +131,6 @@ open class TransactionBuilder( fun addInputState(stateRef: StateRef, notary: Party) { check(currentSigs.isEmpty()) - signers.add(notary.owningKey) inputs.add(stateRef) } diff --git a/core/src/main/kotlin/com/r3corda/core/testing/TransactionDSLInterpreter.kt b/core/src/main/kotlin/com/r3corda/core/testing/TransactionDSLInterpreter.kt index 44d74fcf7b..6d39d9c4fb 100644 --- a/core/src/main/kotlin/com/r3corda/core/testing/TransactionDSLInterpreter.kt +++ b/core/src/main/kotlin/com/r3corda/core/testing/TransactionDSLInterpreter.kt @@ -53,9 +53,7 @@ interface TransactionDSLInterpreter : Verifies, OutputStateLookup { fun tweak(dsl: TransactionDSL.() -> EnforceVerifyOrFail): EnforceVerifyOrFail } -class TransactionDSL (val interpreter: T) : - TransactionDSLInterpreter by interpreter { - +class TransactionDSL(val interpreter: T) : TransactionDSLInterpreter by interpreter { /** * Looks up the output label and adds the found state as an input. * @param stateLabel The label of the output state specified when calling [TransactionDSLInterpreter._output] and friends.