Add AnonymousParty superclass of Party

Add AnonymousParty superclass of Party in preparation for anonymising parties stored in
contract states.

Signed-off-by: Ross Nicoll <ross.nicoll@r3.com>
This commit is contained in:
Ross Nicoll 2017-02-02 16:56:25 +00:00
parent 0287876987
commit 47d260625a
20 changed files with 115 additions and 54 deletions

View File

@ -1,6 +1,7 @@
package net.corda.core.contracts
import net.corda.core.contracts.clauses.Clause
import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
@ -349,8 +350,8 @@ inline fun <reified T : ContractState> Iterable<StateAndRef<ContractState>>.filt
* Reference to something being stored or issued by a party e.g. in a vault or (more likely) on their normal
* ledger. The reference is intended to be encrypted so it's meaningless to anyone other than the party.
*/
data class PartyAndReference(val party: Party, val reference: OpaqueBytes) {
override fun toString() = "${party.name}$reference"
data class PartyAndReference(val party: AnonymousParty, val reference: OpaqueBytes) {
override fun toString() = "${party}$reference"
}
/** Marker interface for classes that represent commands */

View File

@ -0,0 +1,24 @@
package net.corda.core.crypto
import net.corda.core.contracts.PartyAndReference
import net.corda.core.serialization.OpaqueBytes
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) {
/** 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()
// 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))
}

View File

@ -1,7 +1,5 @@
package net.corda.core.crypto
import net.corda.core.contracts.PartyAndReference
import net.corda.core.serialization.OpaqueBytes
import java.security.PublicKey
/**
@ -22,15 +20,8 @@ import java.security.PublicKey
*
* @see CompositeKey
*/
class Party(val name: String, val owningKey: CompositeKey) {
class Party(val name: String, owningKey: CompositeKey) : AnonymousParty(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)
/** 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 Party && this.owningKey == other.owningKey
override fun hashCode(): Int = owningKey.hashCode()
override fun toString() = name
fun ref(bytes: OpaqueBytes) = PartyAndReference(this, bytes)
fun ref(vararg bytes: Byte) = ref(OpaqueBytes.of(*bytes))
override fun toString() = "${owningKey.toBase58String()} (name)"
}

View File

@ -1,5 +1,7 @@
package net.corda.core.node.services
import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
@ -10,6 +12,7 @@ import net.corda.core.crypto.Party
*/
interface IdentityService {
fun registerIdentity(party: Party)
/**
* Get all identities known to the service. This is expensive, and [partyFromKey] or [partyFromName] should be
* used in preference where possible.
@ -22,4 +25,7 @@ interface IdentityService {
fun partyFromKey(key: CompositeKey): Party?
fun partyFromName(name: String): Party?
fun partyFromAnonymous(party: AnonymousParty): Party?
fun partyFromAnonymous(partyRef: PartyAndReference) = partyFromAnonymous(partyRef.party)
}

View File

@ -2,10 +2,7 @@ package net.corda.core.node.services
import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.contracts.*
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.toStringShort
import net.corda.core.crypto.*
import net.corda.core.toFuture
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction
@ -198,7 +195,7 @@ interface VaultService {
fun generateSpend(tx: TransactionBuilder,
amount: Amount<Currency>,
to: CompositeKey,
onlyFromParties: Set<Party>? = null): Pair<TransactionBuilder, List<CompositeKey>>
onlyFromParties: Set<AnonymousParty>? = null): Pair<TransactionBuilder, List<CompositeKey>>
}
inline fun <reified T : LinearState> VaultService.linearHeadsOfType() = linearHeadsOfType_(T::class.java)

View File

@ -0,0 +1,20 @@
package net.corda.core.crypto
import org.junit.Test
import java.math.BigInteger
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
class PartyTest {
@Test
fun `equality`() {
val key = entropyToKeyPair(BigInteger.valueOf(20170207L)).public.composite
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)
}
}

View File

@ -87,14 +87,15 @@ Party and CompositeKey
Entities using the network are called *parties*. Parties can sign structures using keys, and a party may have many
keys under their control.
Parties may sometimes be identified pseudonymously. For example, in a transaction sent to your node as part of a
chain of custody it is important you can convince yourself of the transaction's validity, but equally important that
you don't learn anything about who was involved in that transaction. In these cases a public key may be present
without any identifying information about who owns it.
Parties can be represented either in full (including name) or pseudonymously, using the ``Party`` or ``AnonymousParty``
classes respectively. For example, in a transaction sent to your node as part of a chain of custody it is important you
can convince yourself of the transaction's validity, but equally important that you don't learn anything about who was
involved in that transaction. In these cases ``AnonymousParty`` should be used, which contains a composite public key
without any identifying information about who owns it. In contrast, for internal processing where extended details of
a party are required, the ``Party`` class should be used. The identity service provides functionality for resolving
anonymous parties to full parties.
Identities of parties involved in signing a transaction can be represented simply by a ``CompositeKey``, or by further
information (such as name) using the ``Party`` class. An ``AuthenticatedObject`` represents an object (like a command)
that has been signed by a set of parties.
An ``AuthenticatedObject`` represents an object (like a command) that has been signed by a set of parties.
.. note:: These types are provisional and will change significantly in future as the identity framework becomes more fleshed out.

View File

@ -3,6 +3,13 @@ Release notes
Here are brief summaries of what's changed between each snapshot release.
Milestone 9
-----------
* API:
* Pseudonymous ``AnonymousParty`` class added as a superclass of ``Party``.
Milestone 8
-----------

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: Party) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = amount.token.issuer.copy(party = party))))
fun Cash.State.issuedBy(party: AnonymousParty) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = amount.token.issuer.copy(party = party))))
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: Party) = issuedBy(party)
infix fun Cash.State.`issued by`(party: AnonymousParty) = 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

@ -3,6 +3,7 @@ package net.corda.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.*
import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.Party
import net.corda.core.crypto.keys
import net.corda.core.crypto.toStringShort

View File

@ -12,7 +12,6 @@ import net.corda.core.map
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.core.utilities.DUMMY_NOTARY_KEY
import net.corda.flows.IssuerFlow.IssuanceRequester
import net.corda.testing.*
import net.corda.testing.node.MockNetwork
@ -54,11 +53,12 @@ class IssuerFlowTest {
}
private fun runIssuerAndIssueRequester(amount: Amount<Currency>, issueToPartyAndRef: PartyAndReference) : RunResult {
val resolvedIssuerParty = bankOfCordaNode.services.identityService.partyFromAnonymous(issueToPartyAndRef) ?: throw IllegalStateException()
val issuerFuture = bankOfCordaNode.initiateSingleShotFlow(IssuerFlow.IssuanceRequester::class) {
otherParty -> IssuerFlow.Issuer(issueToPartyAndRef.party)
otherParty -> IssuerFlow.Issuer(resolvedIssuerParty)
}.map { it.stateMachine }
val issueRequest = IssuanceRequester(amount, issueToPartyAndRef.party, issueToPartyAndRef.reference, bankOfCordaNode.info.legalIdentity)
val issueRequest = IssuanceRequester(amount, resolvedIssuerParty, issueToPartyAndRef.reference, bankOfCordaNode.info.legalIdentity)
val issueRequestResultFuture = bankClientNode.services.startFlow(issueRequest).resultFuture
return IssuerFlowTest.RunResult(issuerFuture, issueRequestResultFuture)

View File

@ -1,5 +1,7 @@
package net.corda.node.services.identity
import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.node.services.IdentityService
@ -26,4 +28,11 @@ class InMemoryIdentityService() : SingletonSerializeAsToken(), IdentityService {
override fun partyFromKey(key: CompositeKey): Party? = keyToParties[key]
override fun partyFromName(name: String): Party? = nameToParties[name]
override fun partyFromAnonymous(party: AnonymousParty): Party? {
return if (party is Party)
party
else
partyFromKey(party.owningKey)
}
override fun partyFromAnonymous(partyRef: PartyAndReference) = partyFromAnonymous(partyRef.party)
}

View File

@ -5,8 +5,8 @@ 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.AnonymousParty
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.Vault
@ -198,7 +198,7 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT
override fun generateSpend(tx: TransactionBuilder,
amount: Amount<Currency>,
to: CompositeKey,
onlyFromParties: Set<Party>?): Pair<TransactionBuilder, List<CompositeKey>> {
onlyFromParties: Set<AnonymousParty>?): 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

@ -4,10 +4,7 @@ import net.corda.contracts.CommercialPaper
import net.corda.contracts.asset.*
import net.corda.contracts.testing.fillWithSomeTestCash
import net.corda.core.contracts.*
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.composite
import net.corda.core.crypto.*
import net.corda.core.days
import net.corda.core.flows.FlowStateMachine
import net.corda.core.flows.StateMachineRunId
@ -490,7 +487,7 @@ class TwoPartyTradeFlowTests {
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForBuyer(
withError: Boolean,
owner: CompositeKey,
issuer: Party,
issuer: AnonymousParty,
notary: Party): Pair<Vault, List<WireTransaction>> {
val interimOwnerKey = MEGA_CORP_PUBKEY
// Bob (Buyer) has some cash he got from the Bank of Elbonia, Alice (Seller) has some commercial paper she

View File

@ -2,6 +2,7 @@ package net.corda.testing.node
import kotlinx.support.jdk8.collections.putIfAbsent
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.*
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowStateMachine
@ -73,6 +74,8 @@ class MockIdentityService(val identities: List<Party>) : IdentityService, Single
override fun registerIdentity(party: Party) { throw UnsupportedOperationException() }
override fun getAllIdentities(): Iterable<Party> = ArrayList(keyToParties.values)
override fun partyFromAnonymous(party: AnonymousParty): Party? = keyToParties[party.owningKey]
override fun partyFromAnonymous(partyRef: PartyAndReference): Party? = partyFromAnonymous(partyRef.party)
override fun partyFromKey(key: CompositeKey): Party? = keyToParties[key]
override fun partyFromName(name: String): Party? = nameToParties[name]
}

View File

@ -22,6 +22,7 @@ import net.corda.contracts.asset.Cash
import net.corda.core.contracts.Amount
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.withoutIssuer
import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.Party
import net.corda.explorer.formatters.AmountFormatter
import net.corda.explorer.identicon.identicon
@ -84,7 +85,7 @@ class CashViewer : CordaView("Cash") {
*/
sealed class ViewerNode(val equivAmount: ObservableValue<out Amount<Currency>>,
val states: ObservableList<StateAndRef<Cash.State>>) {
class IssuerNode(val issuer: Party,
class IssuerNode(val issuer: AnonymousParty,
sumEquivAmount: ObservableValue<out Amount<Currency>>,
states: ObservableList<StateAndRef<Cash.State>>) : ViewerNode(sumEquivAmount, states)

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.AnonymousParty
import net.corda.core.crypto.Party
import net.corda.core.flows.FlowException
import net.corda.core.getOrThrow
@ -49,7 +50,7 @@ data class CrossCashCommand(
* Map from node to (map from issuer to USD quantity)
*/
data class CrossCashState(
val nodeVaults: Map<Party, Map<Party, Long>>,
val nodeVaults: Map<AnonymousParty, Map<AnonymousParty, Long>>,
// node -> (notifying node -> [(issuer, amount)])
// This map holds the queues that encode the non-determinism of how tx notifications arrive in the background.
@ -67,20 +68,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<Party, Map<Party, List<Pair<Party, Long>>>>
val diffQueues: Map<AnonymousParty, Map<AnonymousParty, List<Pair<AnonymousParty, Long>>>>
) {
fun copyVaults(): HashMap<Party, HashMap<Party, Long>> {
val newNodeVaults = HashMap<Party, HashMap<Party, Long>>()
fun copyVaults(): HashMap<AnonymousParty, HashMap<AnonymousParty, Long>> {
val newNodeVaults = HashMap<AnonymousParty, HashMap<AnonymousParty, Long>>()
for ((key, value) in nodeVaults) {
newNodeVaults[key] = HashMap(value)
}
return newNodeVaults
}
fun copyQueues(): HashMap<Party, HashMap<Party, ArrayList<Pair<Party, Long>>>> {
val newDiffQueues = HashMap<Party, HashMap<Party, ArrayList<Pair<Party, Long>>>>()
fun copyQueues(): HashMap<AnonymousParty, HashMap<AnonymousParty, ArrayList<Pair<AnonymousParty, Long>>>> {
val newDiffQueues = HashMap<AnonymousParty, HashMap<AnonymousParty, ArrayList<Pair<AnonymousParty, Long>>>>()
for ((node, queues) in diffQueues) {
val newQueues = HashMap<Party, ArrayList<Pair<Party, Long>>>()
val newQueues = HashMap<AnonymousParty, ArrayList<Pair<AnonymousParty, Long>>>()
for ((sender, value) in queues) {
newQueues[sender] = ArrayList(value)
}
@ -216,9 +217,9 @@ val crossCashTest = LoadTest<CrossCashCommand, CrossCashState>(
gatherRemoteState = { previousState ->
log.info("Reifying state...")
val currentNodeVaults = HashMap<Party, HashMap<Party, Long>>()
val currentNodeVaults = HashMap<AnonymousParty, HashMap<AnonymousParty, Long>>()
simpleNodes.forEach {
val quantities = HashMap<Party, Long>()
val quantities = HashMap<AnonymousParty, Long>()
val vault = it.connection.proxy.vaultAndUpdates().first
vault.forEach {
val state = it.state.data
@ -230,7 +231,7 @@ val crossCashTest = LoadTest<CrossCashCommand, CrossCashState>(
currentNodeVaults.put(it.info.legalIdentity, quantities)
}
val (consistentVaults, diffQueues) = if (previousState == null) {
Pair(currentNodeVaults, mapOf<Party, Map<Party, List<Pair<Party, Long>>>>())
Pair(currentNodeVaults, mapOf<AnonymousParty, Map<AnonymousParty, List<Pair<AnonymousParty, Long>>>>())
} else {
log.info("${previousState.diffQueues.values.sumBy { it.values.sumBy { it.size } }} txs in limbo")
val newDiffQueues = previousState.copyQueues()
@ -248,12 +249,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<Party, Map<Party, List<Pair<Party, Long>>>>())
return@LoadTest CrossCashState(currentNodeVaults, mapOf<AnonymousParty, Map<AnonymousParty, List<Pair<AnonymousParty, Long>>>>())
}
if (matches.size > 1) {
log.warn("Multiple predicted states match the remote state")
}
val minimumMatches = matches.fold<Map<Party, Int>, HashMap<Party, Int>?>(null) { minimum, next ->
val minimumMatches = matches.fold<Map<AnonymousParty, Int>, HashMap<AnonymousParty, Int>?>(null) { minimum, next ->
if (minimum == null) {
HashMap(next)
} else {

View File

@ -5,6 +5,7 @@ import net.corda.client.mock.generateAmount
import net.corda.client.mock.pickOne
import net.corda.core.contracts.Issued
import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.Party
import net.corda.core.serialization.OpaqueBytes
import net.corda.flows.CashCommand
@ -27,7 +28,7 @@ fun generateIssue(
fun generateMove(
max: Long,
currency: Currency,
issuer: Party,
issuer: AnonymousParty,
possibleRecipients: List<Party>
): Generator<CashCommand.PayCash> {
return generateAmount(1, max, Generator.pure(Issued(PartyAndReference(issuer, OpaqueBytes.of(0)), currency))).combine(

View File

@ -6,6 +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.Party
import net.corda.core.flows.FlowException
import net.corda.core.getOrThrow
@ -26,9 +27,9 @@ data class SelfIssueCommand(
)
data class SelfIssueState(
val vaultsSelfIssued: Map<Party, Long>
val vaultsSelfIssued: Map<AnonymousParty, Long>
) {
fun copyVaults(): HashMap<Party, Long> {
fun copyVaults(): HashMap<AnonymousParty, Long> {
return HashMap(vaultsSelfIssued)
}
}
@ -70,7 +71,7 @@ val selfIssueTest = LoadTest<SelfIssueCommand, SelfIssueState>(
},
gatherRemoteState = { previousState ->
val selfIssueVaults = HashMap<Party, Long>()
val selfIssueVaults = HashMap<AnonymousParty, Long>()
simpleNodes.forEach { node ->
val vault = node.connection.proxy.vaultAndUpdates().first
vault.forEach {