Minor: clarify the distinction between sighash and txid hash. Fix Cash contract to not expect keys to be sortable.

This commit is contained in:
Mike Hearn 2015-11-27 14:56:36 +01:00
parent de40a2082d
commit 19cd2069c7
3 changed files with 34 additions and 21 deletions

View File

@ -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)
}

View File

@ -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?
)

View File

@ -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() }