Splits verifySigs into verifySigsExcept and verifyRequiredSigs to clarify usage.

This commit is contained in:
Joel Dudley 2017-07-17 15:42:08 +01:00 committed by GitHub
parent 7d1d5cc0f8
commit b37c73827f
20 changed files with 75 additions and 67 deletions

View File

@ -167,14 +167,14 @@ abstract class AbstractStateReplacementFlow {
val mySignature = sign(stx)
val swapSignatures = sendAndReceive<List<DigitalSignature.WithKey>>(otherSide, mySignature)
// TODO: This step should not be necessary, as signatures are re-checked in verifySignatures.
// TODO: This step should not be necessary, as signatures are re-checked in verifyRequiredSignatures.
val allSignatures = swapSignatures.unwrap { signatures ->
signatures.forEach { it.verify(stx.id) }
signatures
}
val finalTx = stx + allSignatures
finalTx.verifySignatures()
finalTx.verifyRequiredSignatures()
serviceHub.recordTransactions(finalTx)
}

View File

@ -86,7 +86,7 @@ class CollectSignaturesFlow(val partiallySignedTx: SignedTransaction,
}
// The signatures must be valid and the transaction must be valid.
partiallySignedTx.verifySignatures(*notSigned.toTypedArray())
partiallySignedTx.verifySignaturesExcept(*notSigned.toTypedArray())
partiallySignedTx.tx.toLedgerTransaction(serviceHub).verify()
// Determine who still needs to sign.
@ -105,7 +105,7 @@ class CollectSignaturesFlow(val partiallySignedTx: SignedTransaction,
// Verify all but the notary's signature if the transaction requires a notary, otherwise verify all signatures.
progressTracker.currentStep = VERIFYING
if (notaryKey != null) stx.verifySignatures(notaryKey) else stx.verifySignatures()
if (notaryKey != null) stx.verifySignaturesExcept(notaryKey) else stx.verifyRequiredSignatures()
return stx
}
@ -223,7 +223,7 @@ abstract class SignTransactionFlow(val otherParty: Party,
val signed = stx.sigs.map { it.by }
val allSigners = stx.tx.mustSign
val notSigned = allSigners - signed
stx.verifySignatures(*notSigned.toTypedArray())
stx.verifySignaturesExcept(*notSigned.toTypedArray())
}
/**

View File

@ -159,7 +159,7 @@ open class FinalityFlow(val transactions: Iterable<SignedTransaction>,
return sorted.map { stx ->
val notary = stx.tx.notary
// The notary signature(s) are allowed to be missing but no others.
val wtx = if (notary != null) stx.verifySignatures(notary.owningKey) else stx.verifySignatures()
val wtx = if (notary != null) stx.verifySignaturesExcept(notary.owningKey) else stx.verifyRequiredSignatures()
val ltx = wtx.toLedgerTransaction(augmentedLookup)
ltx.verify()
stx to ltx

View File

@ -51,7 +51,7 @@ object NotaryFlow {
"Input states must have the same Notary"
}
try {
stx.verifySignatures(notaryParty.owningKey)
stx.verifySignaturesExcept(notaryParty.owningKey)
} catch (ex: SignatureException) {
throw NotaryException(NotaryError.TransactionInvalid(ex))
}

View File

@ -119,7 +119,7 @@ class ResolveTransactionsFlow(private val txHashes: Set<SecureHash>,
// be a clearer API if we do that. But for consistency with the other c'tor we currently do not.
//
// If 'stx' is set, then 'wtx' is the contents (from the c'tor).
val wtx = stx?.verifySignatures() ?: wtx
val wtx = stx?.verifyRequiredSignatures() ?: wtx
wtx?.let {
fetchMissingAttachments(listOf(it))
val ltx = it.toLedgerTransaction(serviceHub)

View File

@ -54,6 +54,17 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
class SignaturesMissingException(val missing: NonEmptySet<PublicKey>, val descriptions: List<String>, override val id: SecureHash)
: NamedByHash, SignatureException("Missing signatures for $descriptions on transaction ${id.prefixChars()} for ${missing.joinToString()}")
/**
* Verifies the signatures on this transaction and throws if any are missing. In this context, "verifying" means
* checking they are valid signatures and that their public keys are in the contained transactions
* [BaseTransaction.mustSign] property.
*
* @throws SignatureException if any signatures are invalid or unrecognised.
* @throws SignaturesMissingException if any signatures should have been present but were not.
*/
@Throws(SignatureException::class)
fun verifyRequiredSignatures() = verifySignaturesExcept()
/**
* Verifies the signatures on this transaction and throws if any are missing which aren't passed as parameters.
* In this context, "verifying" means checking they are valid signatures and that their public keys are in
@ -68,18 +79,14 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
*/
// DOCSTART 2
@Throws(SignatureException::class)
fun verifySignatures(vararg allowedToBeMissing: PublicKey): WireTransaction {
fun verifySignaturesExcept(vararg allowedToBeMissing: PublicKey): WireTransaction {
// DOCEND 2
// Embedded WireTransaction is not deserialised until after we check the signatures.
checkSignaturesAreValid()
val missing = getMissingSignatures()
if (missing.isNotEmpty()) {
val allowed = allowedToBeMissing.toSet()
val needed = missing - allowed
val needed = getMissingSignatures() - allowedToBeMissing
if (needed.isNotEmpty())
throw SignaturesMissingException(needed.toNonEmptySet(), getMissingKeyDescriptions(needed), id)
}
check(tx.id == id)
return tx
}
@ -88,7 +95,7 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
* Mathematically validates the signatures that are present on this transaction. This does not imply that
* the signatures are by the right keys, or that there are sufficient signatures, just that they aren't
* corrupt. If you use this function directly you'll need to do the other checks yourself. Probably you
* want [verifySignatures] instead.
* want [verifySignaturesExcept] instead.
*
* @throws SignatureException if a signature fails to verify.
*/
@ -136,8 +143,8 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
operator fun plus(sigList: Collection<DigitalSignature.WithKey>) = withAdditionalSignatures(sigList)
/**
* Checks the transaction's signatures are valid, optionally calls [verifySignatures] to check
* all required signatures are present, and then calls [WireTransaction.toLedgerTransaction]
* Checks the transaction's signatures are valid, optionally calls [verifyRequiredSignatures] to
* check all required signatures are present, and then calls [WireTransaction.toLedgerTransaction]
* with the passed in [ServiceHub] to resolve the dependencies, returning an unverified
* LedgerTransaction.
*
@ -154,14 +161,14 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
@Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServiceHub, checkSufficientSignatures: Boolean = true): LedgerTransaction {
checkSignaturesAreValid()
if (checkSufficientSignatures) verifySignatures()
if (checkSufficientSignatures) verifyRequiredSignatures()
return tx.toLedgerTransaction(services)
}
/**
* Checks the transaction's signatures are valid, optionally calls [verifySignatures] to check
* all required signatures are present, calls [WireTransaction.toLedgerTransaction] with the
* passed in [ServiceHub] to resolve the dependencies and return an unverified
* Checks the transaction's signatures are valid, optionally calls [verifyRequiredSignatures]
* to check all required signatures are present, calls [WireTransaction.toLedgerTransaction]
* with the passed in [ServiceHub] to resolve the dependencies and return an unverified
* LedgerTransaction, then verifies the LedgerTransaction.
*
* @throws AttachmentResolutionException if a required attachment was not found in storage.
@ -173,7 +180,7 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
@Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class)
fun verify(services: ServiceHub, checkSufficientSignatures: Boolean = true) {
checkSignaturesAreValid()
if (checkSufficientSignatures) verifySignatures()
if (checkSufficientSignatures) verifyRequiredSignatures()
tx.toLedgerTransaction(services).verify()
}

View File

@ -46,18 +46,18 @@ class TransactionTests {
)
assertEquals(
setOf(compKey, DUMMY_KEY_2.public),
assertFailsWith<SignedTransaction.SignaturesMissingException> { makeSigned(wtx, DUMMY_KEY_1).verifySignatures() }.missing
assertFailsWith<SignedTransaction.SignaturesMissingException> { makeSigned(wtx, DUMMY_KEY_1).verifyRequiredSignatures() }.missing
)
assertEquals(
setOf(compKey, DUMMY_KEY_2.public),
assertFailsWith<SignedTransaction.SignaturesMissingException> { makeSigned(wtx, DUMMY_KEY_1, ak).verifySignatures() }.missing
assertFailsWith<SignedTransaction.SignaturesMissingException> { makeSigned(wtx, DUMMY_KEY_1, ak).verifyRequiredSignatures() }.missing
)
makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ak, bk).verifySignatures()
makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ck).verifySignatures()
makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ak, bk, ck).verifySignatures()
makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ak).verifySignatures(compKey)
makeSigned(wtx, DUMMY_KEY_1, ak).verifySignatures(compKey, DUMMY_KEY_2.public) // Mixed allowed to be missing.
makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ak, bk).verifyRequiredSignatures()
makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ck).verifyRequiredSignatures()
makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ak, bk, ck).verifyRequiredSignatures()
makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ak).verifySignaturesExcept(compKey)
makeSigned(wtx, DUMMY_KEY_1, ak).verifySignaturesExcept(compKey, DUMMY_KEY_2.public) // Mixed allowed to be missing.
}
@Test
@ -72,25 +72,25 @@ class TransactionTests {
type = TransactionType.General,
timeWindow = null
)
assertFailsWith<IllegalArgumentException> { makeSigned(wtx).verifySignatures() }
assertFailsWith<IllegalArgumentException> { makeSigned(wtx).verifyRequiredSignatures() }
assertEquals(
setOf(DUMMY_KEY_1.public),
assertFailsWith<SignedTransaction.SignaturesMissingException> { makeSigned(wtx, DUMMY_KEY_2).verifySignatures() }.missing
assertFailsWith<SignedTransaction.SignaturesMissingException> { makeSigned(wtx, DUMMY_KEY_2).verifyRequiredSignatures() }.missing
)
assertEquals(
setOf(DUMMY_KEY_2.public),
assertFailsWith<SignedTransaction.SignaturesMissingException> { makeSigned(wtx, DUMMY_KEY_1).verifySignatures() }.missing
assertFailsWith<SignedTransaction.SignaturesMissingException> { makeSigned(wtx, DUMMY_KEY_1).verifyRequiredSignatures() }.missing
)
assertEquals(
setOf(DUMMY_KEY_2.public),
assertFailsWith<SignedTransaction.SignaturesMissingException> { makeSigned(wtx, DUMMY_CASH_ISSUER_KEY).verifySignatures(DUMMY_KEY_1.public) }.missing
assertFailsWith<SignedTransaction.SignaturesMissingException> { makeSigned(wtx, DUMMY_CASH_ISSUER_KEY).verifySignaturesExcept(DUMMY_KEY_1.public) }.missing
)
makeSigned(wtx, DUMMY_KEY_1).verifySignatures(DUMMY_KEY_2.public)
makeSigned(wtx, DUMMY_KEY_2).verifySignatures(DUMMY_KEY_1.public)
makeSigned(wtx, DUMMY_KEY_1).verifySignaturesExcept(DUMMY_KEY_2.public)
makeSigned(wtx, DUMMY_KEY_2).verifySignaturesExcept(DUMMY_KEY_1.public)
makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2).verifySignatures()
makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2).verifyRequiredSignatures()
}
@Test

View File

@ -141,7 +141,7 @@ class CollectSignaturesFlowTests {
val flow = a.services.startFlow(TestFlowTwo.Initiator(state))
mockNet.runNetwork()
val result = flow.resultFuture.getOrThrow()
result.verifySignatures()
result.verifyRequiredSignatures()
println(result.tx)
println(result.sigs)
}
@ -153,7 +153,7 @@ class CollectSignaturesFlowTests {
val flow = a.services.startFlow(CollectSignaturesFlow(ptx))
mockNet.runNetwork()
val result = flow.resultFuture.getOrThrow()
result.verifySignatures()
result.verifyRequiredSignatures()
println(result.tx)
println(result.sigs)
}

View File

@ -47,7 +47,7 @@ class FinalityFlowTests {
mockNet.runNetwork()
val result = flow.resultFuture.getOrThrow()
val notarisedTx = result.single()
notarisedTx.verifySignatures()
notarisedTx.verifyRequiredSignatures()
val transactionSeenByB = nodeB.services.database.transaction {
nodeB.services.validatedTransactions.getTransaction(notarisedTx.id)
}

View File

@ -50,7 +50,7 @@ class ManualFinalityFlowTests {
mockNet.runNetwork()
val result = flow.resultFuture.getOrThrow()
val notarisedTx = result.single()
notarisedTx.verifySignatures()
notarisedTx.verifyRequiredSignatures()
// We override the participants, so node C will get a copy despite not being involved, and B won't
val transactionSeenByB = nodeB.services.database.transaction {
nodeB.services.validatedTransactions.getTransaction(notarisedTx.id)

View File

@ -64,12 +64,12 @@ class TransactionSerializationTests {
val stx = notaryServices.addSignature(ptx)
// Now check that the signature we just made verifies.
stx.verifySignatures()
stx.verifyRequiredSignatures()
// Corrupt the data and ensure the signature catches the problem.
stx.id.bytes[5] = stx.id.bytes[5].inc()
assertFailsWith(SignatureException::class) {
stx.verifySignatures()
stx.verifyRequiredSignatures()
}
}
@ -92,7 +92,7 @@ class TransactionSerializationTests {
val dummyServices = MockServices(DUMMY_KEY_2)
val stx2 = dummyServices.addSignature(ptx2)
stx.copy(sigs = stx2.sigs).verifySignatures()
stx.copy(sigs = stx2.sigs).verifyRequiredSignatures()
}
}

View File

@ -382,10 +382,11 @@ A ``SignedTransaction`` is a combination of:
:start-after: DOCSTART 1
:end-before: DOCEND 1
Before adding our signature to the transaction, we'll want to verify both the transaction itself and its signatures.
Before adding our signature to the transaction, we'll want to verify both the transaction's contents and the
transaction's signatures.
Verifying the transaction
^^^^^^^^^^^^^^^^^^^^^^^^^
Verifying the transaction's contents
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To verify a transaction, we need to retrieve any states in the transaction chain that our node doesn't
currently have in its local storage from the proposer(s) of the transaction. This process is handled by a built-in flow
called ``ResolveTransactionsFlow``. See :doc:`api-flows` for more details.
@ -460,10 +461,10 @@ the contract. Here's an example of how we might do this:
:end-before: DOCEND 34
:dedent: 12
Verifying the signatures
^^^^^^^^^^^^^^^^^^^^^^^^
We also need to verify the signatures over the transaction to prevent tampering. We do this using
``SignedTransaction.verifySignatures``:
Verifying the transaction's signatures
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
We also need to verify that the transaction has all the required signatures, and that these signatures are valid, to
prevent tampering. We do this using ``SignedTransaction.verifyRequiredSignatures``:
.. container:: codeset
@ -479,8 +480,8 @@ We also need to verify the signatures over the transaction to prevent tampering.
:end-before: DOCEND 35
:dedent: 12
Optionally, we can pass ``verifySignatures`` a ``vararg`` of the public keys for which the signatures are allowed
to be missing:
Alternatively, we can use ``SignedTransaction.verifySignaturesExcept``, which takes a ``vararg`` of the public keys for
which the signatures are allowed to be missing:
.. container:: codeset

View File

@ -474,14 +474,14 @@ public class FlowCookbookJava {
// We can verify that a transaction has all the required
// signatures, and that they're all valid, by running:
// DOCSTART 35
fullySignedTx.verifySignatures();
fullySignedTx.verifyRequiredSignatures();
// DOCEND 35
// If the transaction is only partially signed, we have to pass in
// a list of the public keys corresponding to the missing
// signatures, explicitly telling the system not to check them.
// DOCSTART 36
onceSignedTx.verifySignatures(counterpartyPubKey);
onceSignedTx.verifySignaturesExcept(counterpartyPubKey);
// DOCEND 36
// We can also choose to only check the signatures that are

View File

@ -60,7 +60,7 @@ class MyValidatingNotaryFlow(otherSide: Party, service: MyCustomValidatingNotary
private fun checkSignatures(stx: SignedTransaction) {
try {
stx.verifySignatures(serviceHub.myInfo.notaryIdentity.owningKey)
stx.verifySignaturesExcept(serviceHub.myInfo.notaryIdentity.owningKey)
} catch(e: SignatureException) {
throw NotaryException(NotaryError.TransactionInvalid(e))
}

View File

@ -457,14 +457,14 @@ object FlowCookbook {
// We can verify that a transaction has all the required
// signatures, and that they're all valid, by running:
// DOCSTART 35
fullySignedTx.verifySignatures()
fullySignedTx.verifyRequiredSignatures()
// DOCEND 35
// If the transaction is only partially signed, we have to pass in
// a list of the public keys corresponding to the missing
// signatures, explicitly telling the system not to check them.
// DOCSTART 36
onceSignedTx.verifySignatures(counterpartyPubKey)
onceSignedTx.verifySignaturesExcept(counterpartyPubKey)
// DOCEND 36
// We can also choose to only check the signatures that are

View File

@ -159,7 +159,7 @@ class ForeignExchangeFlow(val tradeId: String,
val allPartySignedTx = sendAndReceive<DigitalSignature.WithKey>(remoteRequestWithNotary.owner, signedTransaction).unwrap {
val withNewSignature = signedTransaction + it
// check all signatures are present except the notary
withNewSignature.verifySignatures(withNewSignature.tx.notary!!.owningKey)
withNewSignature.verifySignaturesExcept(withNewSignature.tx.notary!!.owningKey)
// This verifies that the transaction is contract-valid, even though it is missing signatures.
// In a full solution there would be states tracking the trade request which
@ -234,7 +234,7 @@ class ForeignExchangeRemoteFlow(val source: Party) : FlowLogic<Unit>() {
val proposedTrade = sendAndReceive<SignedTransaction>(source, ourResponse).unwrap {
val wtx = it.tx
// check all signatures are present except our own and the notary
it.verifySignatures(ourKey, wtx.notary!!.owningKey)
it.verifySignaturesExcept(ourKey, wtx.notary!!.owningKey)
// We need to fetch their complete input states and dependencies so that verify can operate
checkDependencies(it)

View File

@ -197,7 +197,7 @@ class SubmitCompletionFlow(val ref: StateRef, val verdict: WorkflowState) : Flow
val agreedTx = selfSignedTx + it
// Receive back their signature and confirm that it is for an unmodified transaction
// Also that the only missing signature is from teh Notary
agreedTx.verifySignatures(notary.owningKey)
agreedTx.verifySignaturesExcept(notary.owningKey)
// Recheck the data of the transaction. Note we run toLedgerTransaction on the WireTransaction
// as we do not have all the signature.
agreedTx.tx.toLedgerTransaction(serviceHub).verify()
@ -226,7 +226,7 @@ class RecordCompletionFlow(val source: Party) : FlowLogic<Unit>() {
// First we receive the verdict transaction signed by their single key
val completeTx = receive<SignedTransaction>(source).unwrap {
// Check the transaction is signed apart from our own key and the notary
val wtx = it.verifySignatures(serviceHub.myInfo.legalIdentity.owningKey, it.tx.notary!!.owningKey)
val wtx = it.verifySignaturesExcept(serviceHub.myInfo.legalIdentity.owningKey, it.tx.notary!!.owningKey)
// Check the transaction data is correctly formed
wtx.toLedgerTransaction(serviceHub).verify()
// Confirm that this is the expected type of transaction

View File

@ -270,8 +270,8 @@ apply any new signatures to its original proposal to ensure the contents
of the transaction has not been altered by the remote parties.
The typical code therefore checks the received ``SignedTransaction``
using the ``verifySignatures`` method, but excluding itself, the notary
and any other parties yet to apply their signature. The contents of the
using the ``verifySignaturesExcept`` method, excluding itself, the
notary and any other parties yet to apply their signature. The contents of the
``WireTransaction`` inside the ``SignedTransaction`` should be fully
verified further by expanding with ``toLedgerTransaction`` and calling
``verify``. Further context specific and business checks should then be

View File

@ -291,7 +291,7 @@ class ObligationTests {
assertEquals(1, stx.tx.outputs.size)
assertEquals(stateAndRef.state.data.copy(lifecycle = Lifecycle.DEFAULTED), stx.tx.outputs[0].data)
stx.verifySignatures()
stx.verifyRequiredSignatures()
// And set it back
stateAndRef = stx.tx.outRef<Obligation.State<Currency>>(0)
@ -302,7 +302,7 @@ class ObligationTests {
stx = notaryServices.addSignature(ptx)
assertEquals(1, stx.tx.outputs.size)
assertEquals(stateAndRef.state.data.copy(lifecycle = Lifecycle.NORMAL), stx.tx.outputs[0].data)
stx.verifySignatures()
stx.verifyRequiredSignatures()
}
/** Test generating a transaction to settle an obligation. */

View File

@ -33,7 +33,7 @@ class ValidatingNotaryFlow(otherSide: Party, service: TrustedAuthorityNotaryServ
private fun checkSignatures(stx: SignedTransaction) {
try {
stx.verifySignatures(serviceHub.myInfo.notaryIdentity.owningKey)
stx.verifySignaturesExcept(serviceHub.myInfo.notaryIdentity.owningKey)
} catch(e: SignatureException) {
throw NotaryException(NotaryError.TransactionInvalid(e))
}