Modify generateSpend() to send change to a fresh confidential identity (#1522)

* Modify generateSpend() to send change to a fresh confidential identity

* Update cash tests to handle change being sent to a new address

* Add comments explaining intent
This commit is contained in:
Ross Nicoll 2017-09-15 18:39:01 +01:00 committed by josecoll
parent 64f2bf7b09
commit 114cc47024
3 changed files with 27 additions and 7 deletions

View File

@ -327,7 +327,13 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
val totalAmount = payments.map { it.amount }.sumOrThrow() val totalAmount = payments.map { it.amount }.sumOrThrow()
val cashSelection = CashSelection.getInstance({ services.jdbcSession().metaData }) val cashSelection = CashSelection.getInstance({ services.jdbcSession().metaData })
val acceptableCoins = cashSelection.unconsumedCashStatesForSpending(services, totalAmount, onlyFromParties, tx.notary, tx.lockId) val acceptableCoins = cashSelection.unconsumedCashStatesForSpending(services, totalAmount, onlyFromParties, tx.notary, tx.lockId)
val revocationEnabled = false // Revocation is currently unsupported
// Generate a new identity that change will be sent to for confidentiality purposes. This means that a
// third party with a copy of the transaction (such as the notary) cannot identify who the change was
// sent to
val changeIdentity = services.keyManagementService.freshKeyAndCert(services.myInfo.legalIdentityAndCert, revocationEnabled)
return OnLedgerAsset.generateSpend(tx, payments, acceptableCoins, return OnLedgerAsset.generateSpend(tx, payments, acceptableCoins,
changeIdentity.party.anonymise(),
{ state, quantity, owner -> deriveState(state, quantity, owner) }, { state, quantity, owner -> deriveState(state, quantity, owner) },
{ Cash().generateMoveCommand() }) { Cash().generateMoveCommand() })
} }

View File

@ -2,7 +2,6 @@ package net.corda.finance.contracts.asset
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.contracts.Amount.Companion.sumOrThrow import net.corda.core.contracts.Amount.Companion.sumOrThrow
import net.corda.core.contracts.Amount.Companion.zero
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
@ -45,6 +44,8 @@ abstract class OnLedgerAsset<T : Any, C : CommandData, S : FungibleAsset<T>> : C
* @param amount How much currency to send. * @param amount How much currency to send.
* @param to a key of the recipient. * @param to a key of the recipient.
* @param acceptableStates a list of acceptable input states to use. * @param acceptableStates a list of acceptable input states to use.
* @param payChangeTo party to pay any change to; this is normally a confidential identity of the calling
* party.
* @param deriveState a function to derive an output state based on an input state, amount for the output * @param deriveState a function to derive an output state based on an input state, amount for the output
* and public key to pay to. * and public key to pay to.
* @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign * @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign
@ -58,9 +59,10 @@ abstract class OnLedgerAsset<T : Any, C : CommandData, S : FungibleAsset<T>> : C
amount: Amount<T>, amount: Amount<T>,
to: AbstractParty, to: AbstractParty,
acceptableStates: List<StateAndRef<S>>, acceptableStates: List<StateAndRef<S>>,
payChangeTo: AbstractParty,
deriveState: (TransactionState<S>, Amount<Issued<T>>, AbstractParty) -> TransactionState<S>, deriveState: (TransactionState<S>, Amount<Issued<T>>, AbstractParty) -> TransactionState<S>,
generateMoveCommand: () -> CommandData): Pair<TransactionBuilder, List<PublicKey>> { generateMoveCommand: () -> CommandData): Pair<TransactionBuilder, List<PublicKey>> {
return generateSpend(tx, listOf(PartyAndAmount(to, amount)), acceptableStates, deriveState, generateMoveCommand) return generateSpend(tx, listOf(PartyAndAmount(to, amount)), acceptableStates, payChangeTo, deriveState, generateMoveCommand)
} }
/** /**
@ -76,6 +78,8 @@ abstract class OnLedgerAsset<T : Any, C : CommandData, S : FungibleAsset<T>> : C
* @param amount How much currency to send. * @param amount How much currency to send.
* @param to a key of the recipient. * @param to a key of the recipient.
* @param acceptableStates a list of acceptable input states to use. * @param acceptableStates a list of acceptable input states to use.
* @param payChangeTo party to pay any change to; this is normally a confidential identity of the calling
* party. We use a new confidential identity here so that the recipient is not identifiable.
* @param deriveState a function to derive an output state based on an input state, amount for the output * @param deriveState a function to derive an output state based on an input state, amount for the output
* and public key to pay to. * and public key to pay to.
* @param T A type representing a token * @param T A type representing a token
@ -90,6 +94,7 @@ abstract class OnLedgerAsset<T : Any, C : CommandData, S : FungibleAsset<T>> : C
fun <S : FungibleAsset<T>, T: Any> generateSpend(tx: TransactionBuilder, fun <S : FungibleAsset<T>, T: Any> generateSpend(tx: TransactionBuilder,
payments: List<PartyAndAmount<T>>, payments: List<PartyAndAmount<T>>,
acceptableStates: List<StateAndRef<S>>, acceptableStates: List<StateAndRef<S>>,
payChangeTo: AbstractParty,
deriveState: (TransactionState<S>, Amount<Issued<T>>, AbstractParty) -> TransactionState<S>, deriveState: (TransactionState<S>, Amount<Issued<T>>, AbstractParty) -> TransactionState<S>,
generateMoveCommand: () -> CommandData): Pair<TransactionBuilder, List<PublicKey>> { generateMoveCommand: () -> CommandData): Pair<TransactionBuilder, List<PublicKey>> {
// Discussion // Discussion
@ -171,10 +176,9 @@ abstract class OnLedgerAsset<T : Any, C : CommandData, S : FungibleAsset<T>> : C
} }
// Whatever values we have left over for each issuer must become change outputs. // Whatever values we have left over for each issuer must become change outputs.
val myself = gathered.first().state.data.owner
for ((token, amount) in remainingFromEachIssuer) { for ((token, amount) in remainingFromEachIssuer) {
val templateState = statesGroupedByIssuer[token]!!.first().state val templateState = statesGroupedByIssuer[token]!!.first().state
outputStates += deriveState(templateState, amount, myself) outputStates += deriveState(templateState, amount, payChangeTo)
} }
for (state in gathered) tx.addInputState(state) for (state in gathered) tx.addInputState(state)

View File

@ -602,9 +602,19 @@ class CashTests : TestDependencyInjectionBase() {
database.transaction { database.transaction {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val vaultState = vaultStatesUnconsumed.elementAt(0) val vaultState = vaultStatesUnconsumed.elementAt(0)
val changeAmount = 90.DOLLARS `issued by` defaultIssuer
val likelyChangeState = wtx.outputs.map(TransactionState<*>::data).filter { state ->
if (state is Cash.State) {
state.amount == changeAmount
} else {
false
}
}.single()
val changeOwner = (likelyChangeState as Cash.State).owner
assertEquals(1, miniCorpServices.keyManagementService.filterMyKeys(setOf(changeOwner.owningKey)).toList().size)
assertEquals(vaultState.ref, wtx.inputs[0]) assertEquals(vaultState.ref, wtx.inputs[0])
assertEquals(vaultState.state.data.copy(owner = THEIR_IDENTITY_1, amount = 10.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data) assertEquals(vaultState.state.data.copy(owner = THEIR_IDENTITY_1, amount = 10.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data)
assertEquals(vaultState.state.data.copy(amount = 90.DOLLARS `issued by` defaultIssuer), wtx.outputs[1].data) assertEquals(vaultState.state.data.copy(amount = changeAmount, owner = changeOwner), wtx.outputs[1].data)
assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
} }
} }