diff --git a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt index 3aa1298d62..aeefce5674 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt @@ -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 Iterable>.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 */ diff --git a/core/src/main/kotlin/net/corda/core/crypto/AnonymousParty.kt b/core/src/main/kotlin/net/corda/core/crypto/AnonymousParty.kt new file mode 100644 index 0000000000..30745dec02 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/crypto/AnonymousParty.kt @@ -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()} " + + fun ref(bytes: OpaqueBytes) = PartyAndReference(this, bytes) + fun ref(vararg bytes: Byte) = ref(OpaqueBytes.of(*bytes)) +} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/crypto/Party.kt b/core/src/main/kotlin/net/corda/core/crypto/Party.kt index a8f0dd7378..600db47bb5 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Party.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Party.kt @@ -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)" } \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt b/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt index e3a17ba222..ef1dcf4d9b 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt @@ -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) } diff --git a/core/src/main/kotlin/net/corda/core/node/services/Services.kt b/core/src/main/kotlin/net/corda/core/node/services/Services.kt index 8d90ba7c77..645dbe43ee 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/Services.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/Services.kt @@ -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, to: CompositeKey, - onlyFromParties: Set? = null): Pair> + onlyFromParties: Set? = null): Pair> } inline fun VaultService.linearHeadsOfType() = linearHeadsOfType_(T::class.java) diff --git a/core/src/main/resources/net/corda/core/node/isolated.jar b/core/src/main/resources/net/corda/core/node/isolated.jar index 1b89810450..0b6d090c58 100644 Binary files a/core/src/main/resources/net/corda/core/node/isolated.jar and b/core/src/main/resources/net/corda/core/node/isolated.jar differ diff --git a/core/src/test/kotlin/net/corda/core/crypto/PartyTest.kt b/core/src/test/kotlin/net/corda/core/crypto/PartyTest.kt new file mode 100644 index 0000000000..d1a4a0b2b3 --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/crypto/PartyTest.kt @@ -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) + } +} \ No newline at end of file diff --git a/docs/source/key-concepts-core-types.rst b/docs/source/key-concepts-core-types.rst index a052a2cebd..4fca77796f 100644 --- a/docs/source/key-concepts-core-types.rst +++ b/docs/source/key-concepts-core-types.rst @@ -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. diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst index 7d0af253a8..95a0e925ab 100644 --- a/docs/source/release-notes.rst +++ b/docs/source/release-notes.rst @@ -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 ----------- diff --git a/finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt b/finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt index 55d195a8fd..0cce72c41b 100644 --- a/finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt +++ b/finance/src/main/kotlin/net/corda/contracts/asset/Cash.kt @@ -192,12 +192,12 @@ fun Iterable.sumCashOrZero(currency: Issued): Amount, 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) diff --git a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt index 0c4ee18a6c..639bc7e951 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt @@ -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) } diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index 80154b1c07..b0b899153b 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -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, to: CompositeKey, - onlyFromParties: Set?): Pair> { + onlyFromParties: Set?): Pair> { // Discussion // // This code is analogous to the Wallet.send() set of methods in bitcoinj, and has the same general outline. diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index b469f33c79..de17df82a4 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -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.fillUpForBuyer( withError: Boolean, owner: CompositeKey, - issuer: Party, + issuer: AnonymousParty, notary: Party): Pair> { val interimOwnerKey = MEGA_CORP_PUBKEY // Bob (Buyer) has some cash he got from the Bank of Elbonia, Alice (Seller) has some commercial paper she diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt b/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt index 97607e55a4..c72bb66b17 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -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) : IdentityService, Single override fun registerIdentity(party: Party) { throw UnsupportedOperationException() } override fun getAllIdentities(): Iterable = 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] } diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/views/cordapps/cash/CashViewer.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/views/cordapps/cash/CashViewer.kt index 477ccfab6d..7bf95c0372 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/views/cordapps/cash/CashViewer.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/views/cordapps/cash/CashViewer.kt @@ -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>, val states: ObservableList>) { - class IssuerNode(val issuer: Party, + class IssuerNode(val issuer: AnonymousParty, sumEquivAmount: ObservableValue>, states: ObservableList>) : ViewerNode(sumEquivAmount, states) diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/CrossCashTest.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/CrossCashTest.kt index 495f468a7d..86f90fd09c 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/CrossCashTest.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/CrossCashTest.kt @@ -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>, + val nodeVaults: Map>, // 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>>> + val diffQueues: Map>>> ) { - fun copyVaults(): HashMap> { - val newNodeVaults = HashMap>() + fun copyVaults(): HashMap> { + val newNodeVaults = HashMap>() for ((key, value) in nodeVaults) { newNodeVaults[key] = HashMap(value) } return newNodeVaults } - fun copyQueues(): HashMap>>> { - val newDiffQueues = HashMap>>>() + fun copyQueues(): HashMap>>> { + val newDiffQueues = HashMap>>>() for ((node, queues) in diffQueues) { - val newQueues = HashMap>>() + val newQueues = HashMap>>() for ((sender, value) in queues) { newQueues[sender] = ArrayList(value) } @@ -216,9 +217,9 @@ val crossCashTest = LoadTest( gatherRemoteState = { previousState -> log.info("Reifying state...") - val currentNodeVaults = HashMap>() + val currentNodeVaults = HashMap>() simpleNodes.forEach { - val quantities = HashMap() + val quantities = HashMap() val vault = it.connection.proxy.vaultAndUpdates().first vault.forEach { val state = it.state.data @@ -230,7 +231,7 @@ val crossCashTest = LoadTest( currentNodeVaults.put(it.info.legalIdentity, quantities) } val (consistentVaults, diffQueues) = if (previousState == null) { - Pair(currentNodeVaults, mapOf>>>()) + Pair(currentNodeVaults, mapOf>>>()) } 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( "\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>>>()) + return@LoadTest CrossCashState(currentNodeVaults, mapOf>>>()) } if (matches.size > 1) { log.warn("Multiple predicted states match the remote state") } - val minimumMatches = matches.fold, HashMap?>(null) { minimum, next -> + val minimumMatches = matches.fold, HashMap?>(null) { minimum, next -> if (minimum == null) { HashMap(next) } else { diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/GenerateHelpers.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/GenerateHelpers.kt index d8e51aee2a..8bce6439a9 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/GenerateHelpers.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/GenerateHelpers.kt @@ -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 ): Generator { return generateAmount(1, max, Generator.pure(Issued(PartyAndReference(issuer, OpaqueBytes.of(0)), currency))).combine( diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/SelfIssueTest.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/SelfIssueTest.kt index 51b0767b1c..c4e7106094 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/SelfIssueTest.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/tests/SelfIssueTest.kt @@ -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 + val vaultsSelfIssued: Map ) { - fun copyVaults(): HashMap { + fun copyVaults(): HashMap { return HashMap(vaultsSelfIssued) } } @@ -70,7 +71,7 @@ val selfIssueTest = LoadTest( }, gatherRemoteState = { previousState -> - val selfIssueVaults = HashMap() + val selfIssueVaults = HashMap() simpleNodes.forEach { node -> val vault = node.connection.proxy.vaultAndUpdates().first vault.forEach {