Enforce separation of Party and AnonymousParty

This commit is contained in:
Ross Nicoll 2017-02-06 11:03:23 +00:00
parent fa33336d38
commit ed093cdb9d
18 changed files with 81 additions and 53 deletions

View File

@ -0,0 +1,22 @@
package net.corda.core.crypto
import net.corda.core.contracts.PartyAndReference
import net.corda.core.serialization.OpaqueBytes
import java.security.PublicKey
/**
* An [AbstractParty] contains the common elements of [Party] and [AnonymousParty], specifically the owning key of
* the party. In most cases [Party] or [AnonymousParty] should be used, depending on use-case.
*/
abstract class AbstractParty(val owningKey: CompositeKey) {
/** A helper constructor that converts the given [PublicKey] in to a [CompositeKey] with a single node */
constructor(owningKey: PublicKey) : this(owningKey.composite)
/** Anonymised parties do not include any detail apart from owning key, so equality is dependent solely on the key */
override fun equals(other: Any?): Boolean = other is AbstractParty && this.owningKey == other.owningKey
override fun hashCode(): Int = owningKey.hashCode()
abstract fun toAnonymous() : AnonymousParty
abstract fun ref(bytes: OpaqueBytes): PartyAndReference
fun ref(vararg bytes: Byte) = ref(OpaqueBytes.of(*bytes))
}

View File

@ -8,18 +8,14 @@ import java.security.PublicKey
* The [AnonymousParty] class contains enough information to uniquely identify a [Party] while excluding private
* information such as name. It is intended to represent a party on the distributed ledger.
*/
open class AnonymousParty(val owningKey: CompositeKey) {
class AnonymousParty(owningKey: CompositeKey) : AbstractParty(owningKey) {
/** A helper constructor that converts the given [PublicKey] in to a [CompositeKey] with a single node */
constructor(owningKey: PublicKey) : this(owningKey.composite)
/** Anonymised parties do not include any detail apart from owning key, so equality is dependent solely on the key */
override fun equals(other: Any?): Boolean = other is AnonymousParty && this.owningKey == other.owningKey
override fun hashCode(): Int = owningKey.hashCode()
open fun toAnonymous() : AnonymousParty = this
// Use the key as the bulk of the toString(), but include a human readable identifier as well, so that [Party]
// can put in the key and actual name
override fun toString() = "${owningKey.toBase58String()} <Anonymous>"
fun ref(bytes: OpaqueBytes) = PartyAndReference(this, bytes)
fun ref(vararg bytes: Byte) = ref(OpaqueBytes.of(*bytes))
override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes)
override fun toAnonymous() = this
}

View File

@ -1,5 +1,7 @@
package net.corda.core.crypto
import net.corda.core.contracts.PartyAndReference
import net.corda.core.serialization.OpaqueBytes
import java.security.PublicKey
/**
@ -9,7 +11,7 @@ import java.security.PublicKey
* cryptographic public key primitives into a tree structure.
*
* For example: Alice has two key pairs (pub1/priv1 and pub2/priv2), and wants to be able to sign transactions with either of them.
* Her advertised [Party] then has a legal [name] "Alice" and an [owingKey] "pub1 or pub2".
* Her advertised [Party] then has a legal [name] "Alice" and an [owningKey] "pub1 or pub2".
*
* [Party] is also used for service identities. E.g. Alice may also be running an interest rate oracle on her Corda node,
* which requires a separate signing key (and an identifying name). Services can also be distributed run by a coordinated
@ -20,9 +22,11 @@ import java.security.PublicKey
*
* @see CompositeKey
*/
class Party(val name: String, owningKey: CompositeKey) : AnonymousParty(owningKey) {
class Party(val name: String, owningKey: CompositeKey) : AbstractParty(owningKey) {
/** A helper constructor that converts the given [PublicKey] in to a [CompositeKey] with a single node */
constructor(name: String, owningKey: PublicKey) : this(name, owningKey.composite)
override fun toAnonymous(): AnonymousParty = AnonymousParty(owningKey)
override fun toString() = "${owningKey.toBase58String()} (${name})"
override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this.toAnonymous(), bytes)
}

View File

@ -197,7 +197,7 @@ interface VaultService {
fun generateSpend(tx: TransactionBuilder,
amount: Amount<Currency>,
to: CompositeKey,
onlyFromParties: Set<AnonymousParty>? = null): Pair<TransactionBuilder, List<CompositeKey>>
onlyFromParties: Set<AbstractParty>? = null): Pair<TransactionBuilder, List<CompositeKey>>
/**
* Return [ContractState]s of a given [Contract] type and list of [Vault.StateStatus]
@ -216,7 +216,7 @@ inline fun <reified T : LinearState> VaultService.linearHeadsOfType() =
states(setOf(T::class.java), EnumSet.of(Vault.StateStatus.UNCONSUMED))
.associateBy { it.state.data.linearId }.mapValues { it.value }
inline fun <reified T : DealState> VaultService.dealsWith(party: AnonymousParty) = linearHeadsOfType<T>().values.filter {
inline fun <reified T : DealState> VaultService.dealsWith(party: AbstractParty) = linearHeadsOfType<T>().values.filter {
it.state.data.parties.any { it == party }
}

View File

@ -300,7 +300,7 @@ object TwoPartyDealFlow {
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
// to have one.
ptx.setTime(serviceHub.clock.instant(), 30.seconds)
return Pair(ptx, arrayListOf(deal.parties.single { it == serviceHub.myInfo.legalIdentity }.owningKey))
return Pair(ptx, arrayListOf(deal.parties.single { it == serviceHub.myInfo.legalIdentity as AbstractParty }.owningKey))
}
}

View File

@ -12,9 +12,9 @@ class PartyTest {
val differentKey = entropyToKeyPair(BigInteger.valueOf(7201702L)).public.composite
val anonymousParty = AnonymousParty(key)
val party = Party("test key", key)
assertEquals(party, anonymousParty)
assertEquals(anonymousParty, party)
assertNotEquals(AnonymousParty(differentKey), anonymousParty)
assertNotEquals(AnonymousParty(differentKey), party)
assertEquals<AbstractParty>(party, anonymousParty)
assertEquals<AbstractParty>(anonymousParty, party)
assertNotEquals<AbstractParty>(AnonymousParty(differentKey), anonymousParty)
assertNotEquals<AbstractParty>(AnonymousParty(differentKey), party)
}
}

View File

@ -192,12 +192,12 @@ fun Iterable<ContractState>.sumCashOrZero(currency: Issued<Currency>): Amount<Is
}
fun Cash.State.ownedBy(owner: CompositeKey) = copy(owner = owner)
fun Cash.State.issuedBy(party: AnonymousParty) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = amount.token.issuer.copy(party = party.toAnonymous()))))
fun Cash.State.issuedBy(party: AbstractParty) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = amount.token.issuer.copy(party = party.toAnonymous()))))
fun Cash.State.issuedBy(deposit: PartyAndReference) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = deposit)))
fun Cash.State.withDeposit(deposit: PartyAndReference): Cash.State = copy(amount = amount.copy(token = amount.token.copy(issuer = deposit)))
infix fun Cash.State.`owned by`(owner: CompositeKey) = ownedBy(owner)
infix fun Cash.State.`issued by`(party: AnonymousParty) = issuedBy(party)
infix fun Cash.State.`issued by`(party: AbstractParty) = issuedBy(party)
infix fun Cash.State.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
infix fun Cash.State.`with deposit`(deposit: PartyAndReference): Cash.State = withDeposit(deposit)

View File

@ -463,7 +463,7 @@ class Obligation<P> : Contract {
* Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.
*/
fun generateIssue(tx: TransactionBuilder,
obligor: AnonymousParty,
obligor: AbstractParty,
issuanceDef: Terms<P>,
pennies: Long,
beneficiary: CompositeKey,
@ -707,9 +707,9 @@ fun <P> Iterable<ContractState>.sumObligationsOrZero(issuanceDef: Issued<Obligat
= filterIsInstance<Obligation.State<P>>().filter { it.lifecycle == Obligation.Lifecycle.NORMAL }.map { it.amount }.sumOrZero(issuanceDef)
infix fun <T> Obligation.State<T>.at(dueBefore: Instant) = copy(template = template.copy(dueBefore = dueBefore))
infix fun <T> Obligation.State<T>.between(parties: Pair<AnonymousParty, CompositeKey>) = copy(obligor = parties.first.toAnonymous(), beneficiary = parties.second)
infix fun <T> Obligation.State<T>.between(parties: Pair<AbstractParty, CompositeKey>) = copy(obligor = parties.first.toAnonymous(), beneficiary = parties.second)
infix fun <T> Obligation.State<T>.`owned by`(owner: CompositeKey) = copy(beneficiary = owner)
infix fun <T> Obligation.State<T>.`issued by`(party: AnonymousParty) = copy(obligor = party.toAnonymous())
infix fun <T> Obligation.State<T>.`issued by`(party: AbstractParty) = copy(obligor = party.toAnonymous())
// For Java users:
@Suppress("unused") fun <T> Obligation.State<T>.ownedBy(owner: CompositeKey) = copy(beneficiary = owner)

View File

@ -4,13 +4,14 @@ import net.corda.core.contracts.Contract
import net.corda.core.contracts.DealState
import net.corda.core.contracts.TransactionForContract
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.transactions.TransactionBuilder
import java.security.PublicKey
class DummyDealContract: Contract {
class DummyDealContract : Contract {
override val legalContractReference: SecureHash = SecureHash.sha256("TestDeal")
override fun verify(tx: TransactionForContract) {}
@ -20,10 +21,11 @@ class DummyDealContract: Contract {
override val participants: List<CompositeKey> = listOf(),
override val linearId: UniqueIdentifier = UniqueIdentifier(),
override val ref: String,
override val parties: List<Party> = listOf()) : DealState {
override val parties: List<AnonymousParty> = listOf()) : DealState {
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
return participants.any { it.containsAny(ourKeys) }
}
override fun generateAgreement(notary: Party): TransactionBuilder {
throw UnsupportedOperationException("not implemented")
}

View File

@ -164,7 +164,7 @@ class CashTests {
assertTrue(tx.inputs.isEmpty())
val s = tx.outputs[0].data as Cash.State
assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount)
assertEquals(MINI_CORP, s.amount.token.issuer.party)
assertEquals(MINI_CORP as AbstractParty, s.amount.token.issuer.party)
assertEquals(DUMMY_PUBKEY_1, s.owner)
assertTrue(tx.commands[0].value is Cash.Commands.Issue)
assertEquals(MINI_CORP_PUBKEY, tx.commands[0].signers[0])

View File

@ -7,6 +7,7 @@ import net.corda.contracts.asset.Cash
import net.corda.core.ThreadBox
import net.corda.core.bufferUntilSubscribed
import net.corda.core.contracts.*
import net.corda.core.crypto.AbstractParty
import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.SecureHash
@ -222,7 +223,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
override fun generateSpend(tx: TransactionBuilder,
amount: Amount<Currency>,
to: CompositeKey,
onlyFromParties: Set<AnonymousParty>?): Pair<TransactionBuilder, List<CompositeKey>> {
onlyFromParties: Set<AbstractParty>?): Pair<TransactionBuilder, List<CompositeKey>> {
// Discussion
//
// This code is analogous to the Wallet.send() set of methods in bitcoinj, and has the same general outline.

View File

@ -2,7 +2,7 @@ package net.corda.irs.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.DealState
import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.AbstractParty
import net.corda.core.flows.FlowLogic
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.node.PluginServiceHub
@ -78,7 +78,7 @@ object AutoOfferFlow {
return stx
}
private fun <T: AnonymousParty> notUs(parties: List<T>): List<T> {
private fun <T: AbstractParty> notUs(parties: List<T>): List<T> {
val notUsParties: MutableList<T> = arrayListOf()
for (party in parties) {
if (serviceHub.myInfo.legalIdentity != party) {

View File

@ -4,7 +4,7 @@ import com.opengamma.strata.basics.currency.MultiCurrencyAmount
import net.corda.core.contracts.DealState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.filterStatesOfType
import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.AbstractParty
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.getOrThrow
@ -34,7 +34,7 @@ class PortfolioApi(val rpc: CordaRPCOps) {
private val ownParty: Party get() = rpc.nodeIdentity().legalIdentity
private val portfolioUtils = PortfolioApiUtils(ownParty)
private inline fun <reified T : DealState> dealsWith(party: AnonymousParty): List<StateAndRef<T>> {
private inline fun <reified T : DealState> dealsWith(party: AbstractParty): List<StateAndRef<T>> {
return rpc.vaultAndUpdates().first.filterStatesOfType<T>().filter { it.state.data.parties.any { it == party } }
}
@ -221,7 +221,7 @@ class PortfolioApi(val rpc: CordaRPCOps) {
return withParty(partyName) { party ->
withPortfolio(party) { state ->
if (state.valuation != null) {
val isValuer = state.valuer as AnonymousParty == ownParty
val isValuer = state.valuer as AbstractParty == ownParty
val rawMtm = state.valuation.presentValues.map {
it.value.amounts.first().amount
}.reduce { a, b -> a + b }

View File

@ -5,6 +5,7 @@ import com.opengamma.strata.product.swap.IborRateCalculation
import com.opengamma.strata.product.swap.RateCalculationSwapLeg
import com.opengamma.strata.product.swap.SwapLegType
import net.corda.core.contracts.hash
import net.corda.core.crypto.AbstractParty
import net.corda.core.crypto.Party
import net.corda.vega.contracts.IRSState
import net.corda.vega.contracts.PortfolioState
@ -122,7 +123,7 @@ class PortfolioApiUtils(private val ownParty: Party) {
val ref: String)
fun createTradeView(state: IRSState): TradeView {
val trade = if (state.buyer == ownParty) state.swap.toFloatingLeg() else state.swap.toFloatingLeg()
val trade = if (state.buyer == ownParty as AbstractParty) state.swap.toFloatingLeg() else state.swap.toFloatingLeg()
val fixedLeg = trade.product.legs.first { it.type == SwapLegType.FIXED } as RateCalculationSwapLeg
val floatingLeg = trade.product.legs.first { it.type != SwapLegType.FIXED } as RateCalculationSwapLeg
val fixedRate = fixedLeg.calculation as FixedRateCalculation

View File

@ -9,7 +9,7 @@ import com.opengamma.strata.product.common.BuySell
import com.opengamma.strata.product.swap.SwapTrade
import com.opengamma.strata.product.swap.type.FixedIborSwapConvention
import com.opengamma.strata.product.swap.type.FixedIborSwapConventions
import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.AbstractParty
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import java.math.BigDecimal
@ -48,7 +48,7 @@ data class SwapData(
val notional: BigDecimal,
val fixedRate: BigDecimal) {
fun getLegForParty(party: AnonymousParty): Leg {
fun getLegForParty(party: AbstractParty): Leg {
return if (party == buyer.second) FixedLeg(notional) else FloatingLeg(notional)
}

View File

@ -15,6 +15,7 @@ import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued
import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.withoutIssuer
import net.corda.core.crypto.AbstractParty
import net.corda.core.crypto.Party
import net.corda.core.flows.FlowException
import net.corda.core.getOrThrow
@ -199,7 +200,7 @@ class NewTransaction : Fragment() {
)
availableAmount.textProperty()
.bind(Bindings.createStringBinding({
val filteredCash = cash.filtered { it.token.issuer.party == issuer.value && it.token.product == currencyChoiceBox.value }
val filteredCash = cash.filtered { it.token.issuer.party as AbstractParty == issuer.value && it.token.product == currencyChoiceBox.value }
.map { it.withoutIssuer().quantity }
"${filteredCash.sum()} ${currencyChoiceBox.value?.currencyCode} Available"
}, arrayOf(currencyChoiceBox.valueProperty(), issuerChoiceBox.valueProperty())))

View File

@ -6,6 +6,7 @@ import net.corda.contracts.asset.Cash
import net.corda.core.contracts.Issued
import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.USD
import net.corda.core.crypto.AbstractParty
import net.corda.core.crypto.AnonymousParty
import net.corda.core.flows.FlowException
import net.corda.core.getOrThrow
@ -50,7 +51,7 @@ data class CrossCashCommand(
* Map from node to (map from issuer to USD quantity)
*/
data class CrossCashState(
val nodeVaults: Map<AnonymousParty, Map<AnonymousParty, Long>>,
val nodeVaults: Map<AbstractParty, Map<AbstractParty, Long>>,
// node -> (notifying node -> [(issuer, amount)])
// This map holds the queues that encode the non-determinism of how tx notifications arrive in the background.
@ -68,20 +69,20 @@ data class CrossCashState(
// requires more concurrent code which is conceptually also more complex than the current design.
// TODO: Alternative: We may possibly reduce the complexity of the search even further using some form of
// knapsack instead of the naive search
val diffQueues: Map<AnonymousParty, Map<AnonymousParty, List<Pair<AnonymousParty, Long>>>>
val diffQueues: Map<AbstractParty, Map<AbstractParty, List<Pair<AbstractParty, Long>>>>
) {
fun copyVaults(): HashMap<AnonymousParty, HashMap<AnonymousParty, Long>> {
val newNodeVaults = HashMap<AnonymousParty, HashMap<AnonymousParty, Long>>()
fun copyVaults(): HashMap<AbstractParty, HashMap<AbstractParty, Long>> {
val newNodeVaults = HashMap<AbstractParty, HashMap<AbstractParty, Long>>()
for ((key, value) in nodeVaults) {
newNodeVaults[key] = HashMap(value)
}
return newNodeVaults
}
fun copyQueues(): HashMap<AnonymousParty, HashMap<AnonymousParty, ArrayList<Pair<AnonymousParty, Long>>>> {
val newDiffQueues = HashMap<AnonymousParty, HashMap<AnonymousParty, ArrayList<Pair<AnonymousParty, Long>>>>()
fun copyQueues(): HashMap<AbstractParty, HashMap<AbstractParty, ArrayList<Pair<AbstractParty, Long>>>> {
val newDiffQueues = HashMap<AbstractParty, HashMap<AbstractParty, ArrayList<Pair<AbstractParty, Long>>>>()
for ((node, queues) in diffQueues) {
val newQueues = HashMap<AnonymousParty, ArrayList<Pair<AnonymousParty, Long>>>()
val newQueues = HashMap<AbstractParty, ArrayList<Pair<AbstractParty, Long>>>()
for ((sender, value) in queues) {
newQueues[sender] = ArrayList(value)
}
@ -217,9 +218,9 @@ val crossCashTest = LoadTest<CrossCashCommand, CrossCashState>(
gatherRemoteState = { previousState ->
log.info("Reifying state...")
val currentNodeVaults = HashMap<AnonymousParty, HashMap<AnonymousParty, Long>>()
val currentNodeVaults = HashMap<AbstractParty, HashMap<AbstractParty, Long>>()
simpleNodes.forEach {
val quantities = HashMap<AnonymousParty, Long>()
val quantities = HashMap<AbstractParty, Long>()
val vault = it.connection.proxy.vaultAndUpdates().first
vault.forEach {
val state = it.state.data
@ -231,7 +232,7 @@ val crossCashTest = LoadTest<CrossCashCommand, CrossCashState>(
currentNodeVaults.put(it.info.legalIdentity, quantities)
}
val (consistentVaults, diffQueues) = if (previousState == null) {
Pair(currentNodeVaults, mapOf<AnonymousParty, Map<AnonymousParty, List<Pair<AnonymousParty, Long>>>>())
Pair(currentNodeVaults, mapOf<AbstractParty, Map<AbstractParty, List<Pair<AbstractParty, Long>>>>())
} else {
log.info("${previousState.diffQueues.values.sumBy { it.values.sumBy { it.size } }} txs in limbo")
val newDiffQueues = previousState.copyQueues()
@ -249,12 +250,12 @@ val crossCashTest = LoadTest<CrossCashCommand, CrossCashState>(
"\nActual gathered state:\n${CrossCashState(currentNodeVaults, mapOf())}"
)
// TODO We should terminate here with an exception, we cannot carry on as we have an inconsistent model. We carry on currently because we always diverge due to notarisation failures
return@LoadTest CrossCashState(currentNodeVaults, mapOf<AnonymousParty, Map<AnonymousParty, List<Pair<AnonymousParty, Long>>>>())
return@LoadTest CrossCashState(currentNodeVaults, mapOf<AbstractParty, Map<AbstractParty, List<Pair<AbstractParty, Long>>>>())
}
if (matches.size > 1) {
log.warn("Multiple predicted states match the remote state")
}
val minimumMatches = matches.fold<Map<AnonymousParty, Int>, HashMap<AnonymousParty, Int>?>(null) { minimum, next ->
val minimumMatches = matches.fold<Map<AbstractParty, Int>, HashMap<AbstractParty, Int>?>(null) { minimum, next ->
if (minimum == null) {
HashMap(next)
} else {

View File

@ -6,7 +6,7 @@ import net.corda.client.mock.pickOne
import net.corda.client.mock.replicatePoisson
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.USD
import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.AbstractParty
import net.corda.core.crypto.Party
import net.corda.core.flows.FlowException
import net.corda.core.getOrThrow
@ -28,9 +28,9 @@ data class SelfIssueCommand(
)
data class SelfIssueState(
val vaultsSelfIssued: Map<AnonymousParty, Long>
val vaultsSelfIssued: Map<AbstractParty, Long>
) {
fun copyVaults(): HashMap<AnonymousParty, Long> {
fun copyVaults(): HashMap<AbstractParty, Long> {
return HashMap(vaultsSelfIssued)
}
}
@ -72,14 +72,14 @@ val selfIssueTest = LoadTest<SelfIssueCommand, SelfIssueState>(
},
gatherRemoteState = { previousState ->
val selfIssueVaults = HashMap<AnonymousParty, Long>()
val selfIssueVaults = HashMap<AbstractParty, Long>()
simpleNodes.forEach { node ->
val vault = node.connection.proxy.vaultAndUpdates().first
vault.forEach {
val state = it.state.data
if (state is Cash.State) {
val issuer = state.amount.token.issuer.party
if (issuer == node.info.legalIdentity as AnonymousParty) {
if (issuer == node.info.legalIdentity as AbstractParty) {
selfIssueVaults.put(issuer, (selfIssueVaults[issuer] ?: 0L) + state.amount.quantity)
}
}