mirror of
https://github.com/corda/corda.git
synced 2024-12-19 04:57:58 +00:00
Minor: clarify the distinction between sighash and txid hash. Fix Cash contract to not expect keys to be sortable.
This commit is contained in:
parent
de40a2082d
commit
19cd2069c7
@ -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<Commands.Move>().signers.toSortedSet()
|
||||
val owningPubKeys = inputs.map { it.owner }.toSet()
|
||||
val keysThatSigned = tx.commands.requireSingleCommand<Commands.Move>().signers.toSet()
|
||||
requireThat {
|
||||
"the owning keys are the same as the signing keys" by keysThatSigned.containsAll(owningPubKeys)
|
||||
}
|
||||
|
@ -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<PublicKey>) : SerializeableWithKryo
|
||||
data class WireCommand(val command: Command, val pubkeys: List<PublicKey>) : SerializeableWithKryo {
|
||||
constructor(command: Command, key: PublicKey) : this(command, listOf(key))
|
||||
}
|
||||
|
||||
/** Transaction ready for serialisation, without any signatures attached. */
|
||||
data class WireTransaction(val inputStates: List<ContractStateRef>,
|
||||
val outputStates: List<ContractState>,
|
||||
val commands: List<WireCommand>) : SerializeableWithKryo {
|
||||
val hash: SecureHash get() = SecureHash.sha256(serialize())
|
||||
fun serializeForSignature(): ByteArray = serialize()
|
||||
|
||||
fun toLedgerTransaction(timestamp: Instant, institutionKeyMap: Map<PublicKey, Institution>): LedgerTransaction {
|
||||
fun toLedgerTransaction(timestamp: Instant, institutionKeyMap: Map<PublicKey, Institution>, 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<ContractStateRef>
|
||||
private val outputStates: MutableList<ContractState> = arrayListOf(),
|
||||
private val commands: MutableList<WireCommand> = 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<ContractStateRef>
|
||||
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<ContractStateRef>
|
||||
|
||||
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<AuthenticatedObject<Command>>,
|
||||
/** 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?
|
||||
)
|
||||
|
@ -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<T : ContractState>(private val stateType: Class<T>) {
|
||||
class TransactionGroupForTest<out T : ContractState>(private val stateType: Class<T>) {
|
||||
open inner class LedgerTransactionForTest : AbstractTransactionForTest() {
|
||||
private val inStates = ArrayList<ContractStateRef>()
|
||||
|
||||
@ -178,9 +178,13 @@ class TransactionGroupForTest<T : ContractState>(private val stateType: Class<T>
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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<T : ContractState>(private val stateType: Class<T>
|
||||
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<T : ContractState>(private val stateType: Class<T>
|
||||
}
|
||||
|
||||
inline fun <reified T : ContractState> transactionGroupFor(body: TransactionGroupForTest<T>.() -> Unit) = TransactionGroupForTest<T>(T::class.java).apply { this.body() }
|
||||
fun transactionGroup(body: TransactionGroupForTest<ContractState>.() -> Unit) = TransactionGroupForTest<ContractState>(ContractState::class.java).apply { this.body() }
|
||||
fun transactionGroup(body: TransactionGroupForTest<ContractState>.() -> Unit) = TransactionGroupForTest(ContractState::class.java).apply { this.body() }
|
||||
|
Loading…
Reference in New Issue
Block a user