Trading: in the two party trade protocol, check that the proposed transaction is contract-valid, and that the missing signatures are what is expected.

This commit is contained in:
Mike Hearn 2016-02-17 18:38:24 +01:00
parent cd28733360
commit 1243ca2066
5 changed files with 52 additions and 30 deletions

View File

@ -23,7 +23,6 @@ class TransactionConflictException(val conflictRef: StateRef, val tx1: LedgerTra
* [nonVerifiedRoots] set. Transactions in the non-verified set are ignored other than for looking up input states. * [nonVerifiedRoots] set. Transactions in the non-verified set are ignored other than for looking up input states.
*/ */
class TransactionGroup(val transactions: Set<LedgerTransaction>, val nonVerifiedRoots: Set<LedgerTransaction>) { class TransactionGroup(val transactions: Set<LedgerTransaction>, val nonVerifiedRoots: Set<LedgerTransaction>) {
/** /**
* Verifies the group and returns the set of resolved transactions. * Verifies the group and returns the set of resolved transactions.
*/ */

View File

@ -121,18 +121,25 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>, val s
/** /**
* Verify the signatures, deserialise the wire transaction and then check that the set of signatures found matches * Verify the signatures, deserialise the wire transaction and then check that the set of signatures found matches
* the set of pubkeys in the commands. * the set of pubkeys in the commands. If any signatures are missing, either throws an exception (by default) or
* returns the list of keys that have missing signatures, depending on the parameter.
* *
* @throws SignatureException if the signature is invalid or does not match. * @throws SignatureException if a signature is invalid, does not match or if any signature is missing.
*/ */
fun verify() { fun verify(throwIfSignaturesAreMissing: Boolean = true): Set<PublicKey> {
verifySignatures() verifySignatures()
// Verify that every command key was in the set that we just verified: there should be no commands that were // Verify that every command key was in the set that we just verified: there should be no commands that were
// unverified. // unverified.
val cmdKeys = tx.commands.flatMap { it.pubkeys }.toSet() val cmdKeys = tx.commands.flatMap { it.pubkeys }.toSet()
val sigKeys = sigs.map { it.by }.toSet() val sigKeys = sigs.map { it.by }.toSet()
if (!sigKeys.containsAll(cmdKeys)) if (sigKeys == cmdKeys)
throw SignatureException("Missing signatures on transaction ${id.prefixChars()} for: ${(cmdKeys - sigKeys).map { it.toStringShort() }}") return emptySet()
val missing = cmdKeys - sigKeys
if (throwIfSignaturesAreMissing)
throw SignatureException("Missing signatures on transaction ${id.prefixChars()} for: ${missing.map { it.toStringShort() }}")
else
return missing
} }
/** /**

View File

@ -23,6 +23,7 @@ import core.node.TimestampingProtocol
import core.utilities.trace import core.utilities.trace
import java.security.KeyPair import java.security.KeyPair
import java.security.PublicKey import java.security.PublicKey
import java.security.SignatureException
import java.time.Instant import java.time.Instant
/** /**
@ -110,16 +111,22 @@ object TwoPartyTradeProtocol {
val maybeSTX = sendAndReceive<SignedTransaction>(TRADE_TOPIC, otherSide, buyerSessionID, sessionID, hello) val maybeSTX = sendAndReceive<SignedTransaction>(TRADE_TOPIC, otherSide, buyerSessionID, sessionID, hello)
maybeSTX.validate { maybeSTX.validate {
it.verifySignatures() // Check that the tx proposed by the buyer is valid.
val missingSigs = it.verify(throwIfSignaturesAreMissing = false)
if (missingSigs != setOf(myKeyPair.public, timestampingAuthority.identity.owningKey))
throw SignatureException("The set of missing signatures is not as expected: $missingSigs")
val wtx: WireTransaction = it.tx val wtx: WireTransaction = it.tx
logger.trace { "Received partially signed transaction: ${it.id}" } logger.trace { "Received partially signed transaction: ${it.id}" }
checkDependencies(it) checkDependencies(it)
// TODO: Verify that the transaction is contract-valid even though it lacks sufficient signatures. // This verifies that the transaction is contract-valid, even though it is missing signatures.
serviceHub.verifyTransaction(wtx.toLedgerTransaction(serviceHub.identityService))
if (wtx.outputs.sumCashBy(myKeyPair.public) != price)
throw IllegalArgumentException("Transaction is not sending us the right amounnt of cash")
requireThat {
"transaction sends us the right amount of cash" by (wtx.outputs.sumCashBy(myKeyPair.public) == price)
// There are all sorts of funny games a malicious secondary might play here, we should fix them: // There are all sorts of funny games a malicious secondary might play here, we should fix them:
// //
// - This tx may attempt to send some assets we aren't intending to sell to the secondary, if // - This tx may attempt to send some assets we aren't intending to sell to the secondary, if
@ -127,9 +134,8 @@ object TwoPartyTradeProtocol {
// - This tx may include output states that impose odd conditions on the movement of the cash, // - This tx may include output states that impose odd conditions on the movement of the cash,
// once we implement state pairing. // once we implement state pairing.
// //
// but the goal of this code is not to be fully secure, but rather, just to find good ways to // but the goal of this code is not to be fully secure (yet), but rather, just to find good ways to
// express protocol state machines on top of the messaging layer. // express protocol state machines on top of the messaging layer.
}
return it return it
} }
@ -220,12 +226,7 @@ object TwoPartyTradeProtocol {
ptx.signWith(KeyPair(k, priv)) ptx.signWith(KeyPair(k, priv))
} }
val stx = ptx.toSignedTransaction(checkSufficientSignatures = false) return ptx.toSignedTransaction(checkSufficientSignatures = false)
stx.verifySignatures() // Verifies that we generated a signed transaction correctly.
// TODO: Could run verify() here to make sure the only signature missing is the sellers.
return stx
} }
private fun assembleSharedTX(tradeRequest: SellerTradeInfo): Pair<TransactionBuilder, List<PublicKey>> { private fun assembleSharedTX(tradeRequest: SellerTradeInfo): Pair<TransactionBuilder, List<PublicKey>> {

View File

@ -8,13 +8,10 @@
package core package core
import co.paralleluniverse.fibers.Suspendable
import core.crypto.DigitalSignature
import core.crypto.SecureHash import core.crypto.SecureHash
import core.crypto.generateKeyPair import core.crypto.generateKeyPair
import core.messaging.MessagingService import core.messaging.MessagingService
import core.messaging.NetworkMap import core.messaging.NetworkMap
import core.serialization.SerializedBytes
import java.security.KeyPair import java.security.KeyPair
import java.security.PrivateKey import java.security.PrivateKey
import java.security.PublicKey import java.security.PublicKey
@ -113,4 +110,17 @@ interface ServiceHub {
val storageService: StorageService val storageService: StorageService
val networkService: MessagingService val networkService: MessagingService
val networkMapService: NetworkMap val networkMapService: NetworkMap
/**
* Given a [LedgerTransaction], looks up all its dependencies in the local database, uses the identity service to map
* the [SignedTransaction]s the DB gives back into [LedgerTransaction]s, and then runs the smart contracts for the
* transaction. If no exception is thrown, the transaction is valid.
*/
fun verifyTransaction(ltx: LedgerTransaction) {
val dependencies = ltx.inputs.map {
storageService.validatedTransactions[it.txhash] ?: throw TransactionResolutionException(it.txhash)
}
val ltxns = dependencies.map { it.verifyToLedgerTransaction(identityService) }
TransactionGroup(setOf(ltx), ltxns.toSet()).verify(storageService.contractPrograms)
}
} }

View File

@ -261,7 +261,12 @@ class TwoPartyTradeProtocolTests : TestWithInMemoryNetwork() {
// Bob's transactions are valid, so she commits to the database // Bob's transactions are valid, so she commits to the database
RecordingMap.Put(bobsFakeCash[1].id, bobsSignedTxns[bobsFakeCash[1].id]), RecordingMap.Put(bobsFakeCash[1].id, bobsSignedTxns[bobsFakeCash[1].id]),
RecordingMap.Put(bobsFakeCash[2].id, bobsSignedTxns[bobsFakeCash[2].id]), RecordingMap.Put(bobsFakeCash[2].id, bobsSignedTxns[bobsFakeCash[2].id]),
RecordingMap.Put(bobsFakeCash[0].id, bobsSignedTxns[bobsFakeCash[0].id]) RecordingMap.Put(bobsFakeCash[0].id, bobsSignedTxns[bobsFakeCash[0].id]),
// Now she verifies the transaction is contract-valid (not signature valid) which means
// looking up the states again.
RecordingMap.Get(bobsFakeCash[1].id),
RecordingMap.Get(bobsFakeCash[2].id),
RecordingMap.Get(alicesFakePaper[0].id)
) )
assertEquals(expected, records) assertEquals(expected, records)
} }