diff --git a/src/contracts/Cash.kt b/src/contracts/Cash.kt index 4044515365..d5d5c4edc1 100644 --- a/src/contracts/Cash.kt +++ b/src/contracts/Cash.kt @@ -129,8 +129,8 @@ class Cash : Contract { // Now check the digital signatures on the move command. Every input has an owning public key, and we must // see a signature from each of those keys. The actual signatures have been verified against the transaction // data by the platform before execution. - val owningPubKeys = inputs.map { it.owner }.toSortedSet() - val keysThatSigned = tx.commands.requireSingleCommand().signers.toSortedSet() + val owningPubKeys = inputs.map { it.owner }.toSet() + val keysThatSigned = tx.commands.requireSingleCommand().signers.toSet() requireThat { "the owning keys are the same as the signing keys" by keysThatSigned.containsAll(owningPubKeys) } diff --git a/src/core/Transactions.kt b/src/core/Transactions.kt index 53a6a2f053..e7c85f737b 100644 --- a/src/core/Transactions.kt +++ b/src/core/Transactions.kt @@ -19,10 +19,10 @@ import java.util.* * SignedWireTransaction wraps a serialized WireTransaction. It contains one or more ECDSA signatures, each one from * a public key that is mentioned inside a transaction command. * - * WireTransaction is a transaction in a form ready to be serialised/unserialised/hashed. This is the object from which - * a transaction ID (hash) is calculated. It contains no signatures and no timestamp. That means, sending a transaction - * to a timestamping authority does NOT change its hash (this may be an issue that leads to confusion and should be - * examined more closely). + * WireTransaction is a transaction in a form ready to be serialised/unserialised. A WireTransaction can be hashed + * in various ways to calculate a *signature hash* (or sighash), this is the hash that is signed by the various involved + * keypairs. Note that a sighash is not the same thing as a *transaction id*, which is the hash of a + * TimestampedWireTransaction i.e. the outermost serialised form with everything included. * * A PartialTransaction is a transaction class that's mutable (unlike the others which are all immutable). It is * intended to be passed around contracts that may edit it by adding new states/commands or modifying the existing set. @@ -41,20 +41,22 @@ import java.util.* */ /** Serialized command plus pubkey pair: the signature is stored at the end of the serialized bytes */ -data class WireCommand(val command: Command, val pubkeys: List) : SerializeableWithKryo +data class WireCommand(val command: Command, val pubkeys: List) : SerializeableWithKryo { + constructor(command: Command, key: PublicKey) : this(command, listOf(key)) +} /** Transaction ready for serialisation, without any signatures attached. */ data class WireTransaction(val inputStates: List, val outputStates: List, val commands: List) : SerializeableWithKryo { - val hash: SecureHash get() = SecureHash.sha256(serialize()) + fun serializeForSignature(): ByteArray = serialize() - fun toLedgerTransaction(timestamp: Instant, institutionKeyMap: Map): LedgerTransaction { + fun toLedgerTransaction(timestamp: Instant, institutionKeyMap: Map, originalHash: SecureHash): LedgerTransaction { val authenticatedArgs = commands.map { val institutions = it.pubkeys.mapNotNull { pk -> institutionKeyMap[pk] } AuthenticatedObject(it.pubkeys, institutions, it.command) } - return LedgerTransaction(inputStates, outputStates, authenticatedArgs, timestamp, hash) + return LedgerTransaction(inputStates, outputStates, authenticatedArgs, timestamp, originalHash) } } @@ -63,9 +65,14 @@ class PartialTransaction(private val inputStates: MutableList private val outputStates: MutableList = arrayListOf(), private val commands: MutableList = arrayListOf()) { - /** A more convenient constructor that sorts things into the right lists for you */ - constructor(vararg things: Any) : this() { - for (t in things) { + /** A more convenient way to add items to this transaction that calls the add* methods for you based on type */ + constructor(vararg items: Any) : this() { + addItems(*items) + } + + /** A more convenient way to add items to this transaction that calls the add* methods for you based on type */ + public fun addItems(vararg items: Any) { + for (t in items) { when (t) { is ContractStateRef -> inputStates.add(t) is ContractState -> outputStates.add(t) @@ -81,7 +88,7 @@ class PartialTransaction(private val inputStates: MutableList fun signWith(key: KeyPair) { check(currentSigs.none { it.by == key.public }) { "This partial transaction was already signed by ${key.public}" } check(commands.count { it.pubkeys.contains(key.public) } > 0) { "Trying to sign with a key that isn't in any command" } - val bits = toWireTransaction().serialize() + val bits = toWireTransaction().serializeForSignature() currentSigs.add(key.private.signWithECDSA(bits, key.public)) } @@ -106,6 +113,8 @@ class PartialTransaction(private val inputStates: MutableList fun addArg(arg: WireCommand) { check(currentSigs.isEmpty()) + + // We should probably merge the lists of pubkeys for identical commands here. commands.add(arg) } @@ -175,7 +184,7 @@ data class LedgerTransaction( val commands: List>, /** The moment the transaction was timestamped for */ val time: Instant, - /** The hash of the original serialised WireTransaction */ + /** The hash of the original serialised TimestampedWireTransaction or SignedTransaction */ val hash: SecureHash // TODO: nLockTime equivalent? ) diff --git a/tests/core/testutils/TestUtils.kt b/tests/core/testutils/TestUtils.kt index b1e044d328..ceda6a24d5 100644 --- a/tests/core/testutils/TestUtils.kt +++ b/tests/core/testutils/TestUtils.kt @@ -18,8 +18,8 @@ object TestUtils { } // A few dummy values for testing. -val MEGA_CORP_KEY = DummyPublicKey("mini") -val MINI_CORP_KEY = DummyPublicKey("mega") +val MEGA_CORP_KEY = TestUtils.keypair.public +val MINI_CORP_KEY = TestUtils.keypair2.public val DUMMY_PUBKEY_1 = DummyPublicKey("x1") val DUMMY_PUBKEY_2 = DummyPublicKey("x2") val ALICE = DummyPublicKey("alice") @@ -169,7 +169,7 @@ open class TransactionForTest : AbstractTransactionForTest() { fun transaction(body: TransactionForTest.() -> Unit) = TransactionForTest().apply { body() } -class TransactionGroupForTest(private val stateType: Class) { +class TransactionGroupForTest(private val stateType: Class) { open inner class LedgerTransactionForTest : AbstractTransactionForTest() { private val inStates = ArrayList() @@ -178,9 +178,13 @@ class TransactionGroupForTest(private val stateType: Class } + /** + * Converts to a [LedgerTransaction] with the givn time, the test institution map, and just assigns a random + * hash (i.e. pretend it was signed) + */ fun toLedgerTransaction(time: Instant): LedgerTransaction { val wireCmds = commands.map { WireCommand(it.value, it.signers) } - return WireTransaction(inStates, outStates.map { it.state }, wireCmds).toLedgerTransaction(time, TEST_KEYS_TO_CORP_MAP) + return WireTransaction(inStates, outStates.map { it.state }, wireCmds).toLedgerTransaction(time, TEST_KEYS_TO_CORP_MAP, SecureHash.randomSHA256()) } } @@ -208,7 +212,7 @@ class TransactionGroupForTest(private val stateType: Class fun transaction(vararg outputStates: LabeledOutput) { val outs = outputStates.map { it.state } val wtx = WireTransaction(emptyList(), outs, emptyList()) - val ltx = wtx.toLedgerTransaction(TEST_TX_TIME, TEST_KEYS_TO_CORP_MAP) + val ltx = wtx.toLedgerTransaction(TEST_TX_TIME, TEST_KEYS_TO_CORP_MAP, SecureHash.randomSHA256()) outputStates.forEachIndexed { index, labeledOutput -> labelToRefs[labeledOutput.label!!] = ContractStateRef(ltx.hash, index) } rootTxns.add(ltx) } @@ -260,4 +264,4 @@ class TransactionGroupForTest(private val stateType: Class } inline fun transactionGroupFor(body: TransactionGroupForTest.() -> Unit) = TransactionGroupForTest(T::class.java).apply { this.body() } -fun transactionGroup(body: TransactionGroupForTest.() -> Unit) = TransactionGroupForTest(ContractState::class.java).apply { this.body() } +fun transactionGroup(body: TransactionGroupForTest.() -> Unit) = TransactionGroupForTest(ContractState::class.java).apply { this.body() }