From 87ee66ff44db2263e9359b66c17a322be4ea0b32 Mon Sep 17 00:00:00 2001 From: Filipe Oliveira Date: Tue, 31 Mar 2026 11:11:23 +0100 Subject: [PATCH 1/4] ENT-14766- Sample to support the key rotation. --- .../negotiation-cordapp/repositories.gradle | 24 +++++++++++++++++++ .../negotiation/flows/ModificationFlow.java | 14 +++++++---- .../negotiation/flows/ProposalFlow.java | 2 +- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/Advanced/negotiation-cordapp/repositories.gradle b/Advanced/negotiation-cordapp/repositories.gradle index 9797c0ea..57d5c424 100644 --- a/Advanced/negotiation-cordapp/repositories.gradle +++ b/Advanced/negotiation-cordapp/repositories.gradle @@ -4,4 +4,28 @@ repositories { maven { url 'https://jitpack.io' } maven { url 'https://download.corda.net/maven/corda-dependencies' } maven { url 'https://repo.gradle.org/gradle/libs-releases' } + + // Repository where the user-reported artifact resides + maven { + url "https://software.r3.com/artifactory/r3-corda-releases" + credentials { + username = findProperty('cordaArtifactoryUsername') ?: System.getenv('CORDA_ARTIFACTORY_USERNAME') + password = findProperty('cordaArtifactoryPassword') ?: System.getenv('CORDA_ARTIFACTORY_PASSWORD') + } + content { + includeGroupByRegex 'com\\.r3(\\..*)?' + } + } + + // Repository for corda-dev artifacts (contains net.corda SNAPSHOTs like corda-shell 4.14-SNAPSHOT) + maven { + url "https://software.r3.com/artifactory/corda-dev" + credentials { + username = findProperty('cordaArtifactoryUsername') ?: System.getenv('CORDA_ARTIFACTORY_USERNAME') + password = findProperty('cordaArtifactoryPassword') ?: System.getenv('CORDA_ARTIFACTORY_PASSWORD') + } + content { + includeGroupByRegex 'net\\.corda(\\..*)?' + } + } } diff --git a/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ModificationFlow.java b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ModificationFlow.java index 2d6e8d39..02b7ea77 100644 --- a/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ModificationFlow.java +++ b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ModificationFlow.java @@ -2,6 +2,8 @@ import co.paralleluniverse.fibers.Suspendable; import com.google.common.collect.ImmutableList; +import net.corda.core.crypto.keyrotation.crossprovider.PartyIdentityResolved; +import net.corda.core.crypto.keyrotation.crossprovider.PartyIdentityResolver; import net.corda.samples.negotiation.contracts.ProposalAndTradeContract; import net.corda.samples.negotiation.states.ProposalState; import net.corda.core.contracts.Command; @@ -42,10 +44,12 @@ public SignedTransaction call() throws FlowException { QueryCriteria.LinearStateQueryCriteria inputCriteria = new QueryCriteria.LinearStateQueryCriteria(null, ImmutableList.of(proposalId), Vault.StateStatus.UNCONSUMED, null); StateAndRef inputStateAndRef = getServiceHub().getVaultService().queryBy(ProposalState.class, inputCriteria).getStates().get(0); ProposalState input = (ProposalState) inputStateAndRef.getState().getData(); + Party proposerParty = PartyIdentityResolver.Companion.resolveToCurrentParty(input.getProposer(), getServiceHub().getIdentityService()); //Creating the output - Party counterparty = (getOurIdentity().equals(input.getProposer()))? input.getProposee() : input.getProposer(); - ProposalState output = new ProposalState(newAmount, input.getBuyer(),input.getSeller(), getOurIdentity(), counterparty, input.getLinearId()); + Party myPartyFromInput = (getOurIdentity().equals(proposerParty)) ? input.getProposer() : input.getProposee(); + Party counterpartyFromInput = (myPartyFromInput.equals(input.getProposer()))? input.getProposee() : input.getProposer(); + ProposalState output = new ProposalState(newAmount, input.getBuyer(),input.getSeller(), myPartyFromInput, counterpartyFromInput, input.getLinearId()); //Creating the command List requiredSigners = ImmutableList.of(input.getProposee().getOwningKey(), input.getProposer().getOwningKey()); @@ -62,7 +66,8 @@ public SignedTransaction call() throws FlowException { SignedTransaction partStx = getServiceHub().signInitialTransaction(txBuilder); //Gathering the counterparty's signatures - FlowSession counterpartySession = initiateFlow(counterparty); + Party counterParty = PartyIdentityResolver.Companion.resolveToCurrentParty(counterpartyFromInput, getServiceHub().getIdentityService()); + FlowSession counterpartySession = initiateFlow(counterParty); SignedTransaction fullyStx = subFlow(new CollectSignaturesFlow(partStx, ImmutableList.of(counterpartySession))); //Finalising the transaction @@ -88,7 +93,8 @@ public SignedTransaction call() throws FlowException { protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowException { try { LedgerTransaction ledgerTx = stx.toLedgerTransaction(getServiceHub(), false); - Party proposee = ledgerTx.inputsOfType(ProposalState.class).get(0).getProposee(); + ProposalState input = ledgerTx.inputsOfType(ProposalState.class).get(0); + Party proposee = PartyIdentityResolver.Companion.resolveToCurrentParty(input.getProposee(), getServiceHub().getIdentityService()); if(!proposee.equals(counterpartySession.getCounterparty())){ throw new FlowException("Only the proposee can modify a proposal."); } diff --git a/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ProposalFlow.java b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ProposalFlow.java index 45def7ee..9d1f5b93 100644 --- a/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ProposalFlow.java +++ b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ProposalFlow.java @@ -53,7 +53,7 @@ public UniqueIdentifier call() throws FlowException { // Obtain a reference to a notary we wish to use. /** Explicit selection of notary by CordaX500Name - argument can by coded in flows or parsed from config (Preferred)*/ - final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=Notary,L=London,C=GB")); + final Party notary = getServiceHub().getNetworkMapCache().getNotary(CordaX500Name.parse("O=TestNotaryService, L=London, C=GB")); TransactionBuilder txBuilder = new TransactionBuilder(notary) .addOutputState(output, ProposalAndTradeContract.ID) From 340874d4ec390917f271b16942a965d89ca32b67 Mon Sep 17 00:00:00 2001 From: Filipe Oliveira Date: Mon, 20 Apr 2026 13:10:26 +0100 Subject: [PATCH 2/4] Update. --- .../net/corda/samples/negotiation/flows/ModificationFlow.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ModificationFlow.java b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ModificationFlow.java index 02b7ea77..b6443592 100644 --- a/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ModificationFlow.java +++ b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ModificationFlow.java @@ -66,8 +66,7 @@ public SignedTransaction call() throws FlowException { SignedTransaction partStx = getServiceHub().signInitialTransaction(txBuilder); //Gathering the counterparty's signatures - Party counterParty = PartyIdentityResolver.Companion.resolveToCurrentParty(counterpartyFromInput, getServiceHub().getIdentityService()); - FlowSession counterpartySession = initiateFlow(counterParty); + FlowSession counterpartySession = initiateFlow(counterpartyFromInput); SignedTransaction fullyStx = subFlow(new CollectSignaturesFlow(partStx, ImmutableList.of(counterpartySession))); //Finalising the transaction From df6fcbd3c61f6e232bcbe22d095811b73c897127 Mon Sep 17 00:00:00 2001 From: Filipe Oliveira Date: Thu, 30 Apr 2026 13:59:38 +0100 Subject: [PATCH 3/4] Added comments explaining the changes. --- .../negotiation/flows/ModificationFlow.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ModificationFlow.java b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ModificationFlow.java index b6443592..e84028bb 100644 --- a/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ModificationFlow.java +++ b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/ModificationFlow.java @@ -2,7 +2,6 @@ import co.paralleluniverse.fibers.Suspendable; import com.google.common.collect.ImmutableList; -import net.corda.core.crypto.keyrotation.crossprovider.PartyIdentityResolved; import net.corda.core.crypto.keyrotation.crossprovider.PartyIdentityResolver; import net.corda.samples.negotiation.contracts.ProposalAndTradeContract; import net.corda.samples.negotiation.states.ProposalState; @@ -31,7 +30,6 @@ public class ModificationFlow { public static class Initiator extends FlowLogic{ private UniqueIdentifier proposalId; private int newAmount; - private ProgressTracker progressTracker = new ProgressTracker(); public Initiator(UniqueIdentifier proposalId, int newAmount) { this.proposalId = proposalId; @@ -44,9 +42,15 @@ public SignedTransaction call() throws FlowException { QueryCriteria.LinearStateQueryCriteria inputCriteria = new QueryCriteria.LinearStateQueryCriteria(null, ImmutableList.of(proposalId), Vault.StateStatus.UNCONSUMED, null); StateAndRef inputStateAndRef = getServiceHub().getVaultService().queryBy(ProposalState.class, inputCriteria).getStates().get(0); ProposalState input = (ProposalState) inputStateAndRef.getState().getData(); - Party proposerParty = PartyIdentityResolver.Companion.resolveToCurrentParty(input.getProposer(), getServiceHub().getIdentityService()); //Creating the output + // + // The proposerParty is retrieved from the state and must be resolved to its latest identity + // before it can be compared with the party returned by `getOurIdentity`. Since the intent is not to + // replace the old key with the new one in the output state, calling `resolveToCurrentParty` is sufficient. + // + // Comparing parties that both originate from states is safe without additional resolution. + Party proposerParty = PartyIdentityResolver.Companion.resolveToCurrentParty(input.getProposer(), getServiceHub().getIdentityService()); Party myPartyFromInput = (getOurIdentity().equals(proposerParty)) ? input.getProposer() : input.getProposee(); Party counterpartyFromInput = (myPartyFromInput.equals(input.getProposer()))? input.getProposee() : input.getProposer(); ProposalState output = new ProposalState(newAmount, input.getBuyer(),input.getSeller(), myPartyFromInput, counterpartyFromInput, input.getLinearId()); @@ -66,6 +70,9 @@ public SignedTransaction call() throws FlowException { SignedTransaction partStx = getServiceHub().signInitialTransaction(txBuilder); //Gathering the counterparty's signatures + // + // The counterparty might be an old key, but the session will be initiated with the most up-to-date identity. + // No need to use the resolved party in this case. FlowSession counterpartySession = initiateFlow(counterpartyFromInput); SignedTransaction fullyStx = subFlow(new CollectSignaturesFlow(partStx, ImmutableList.of(counterpartySession))); @@ -93,6 +100,13 @@ protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowExcep try { LedgerTransaction ledgerTx = stx.toLedgerTransaction(getServiceHub(), false); ProposalState input = ledgerTx.inputsOfType(ProposalState.class).get(0); + + // The counterparty session always provides the most up-to-date identity for the counterparty. + // + // Therefore, any party retrieved from a state must be resolved using `resolveToCurrentParty`. + // While `resolveToCurrentParty` does not rely on a proof, it resolves the party to its latest valid identity. + // + // This ensures that equality checks behave as expected after key rotation. Party proposee = PartyIdentityResolver.Companion.resolveToCurrentParty(input.getProposee(), getServiceHub().getIdentityService()); if(!proposee.equals(counterpartySession.getCounterparty())){ throw new FlowException("Only the proposee can modify a proposal."); From d0d506194afee3c7a3979575ed180ac601041a13 Mon Sep 17 00:00:00 2001 From: Filipe Oliveira Date: Thu, 30 Apr 2026 14:00:56 +0100 Subject: [PATCH 4/4] Updated the AcceptanceFlow and added comments. --- .../negotiation/flows/AcceptanceFlow.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/AcceptanceFlow.java b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/AcceptanceFlow.java index 62426e2c..aa0c2dc8 100644 --- a/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/AcceptanceFlow.java +++ b/Advanced/negotiation-cordapp/workflows/src/main/java/net/corda/samples/negotiation/flows/AcceptanceFlow.java @@ -3,6 +3,7 @@ import co.paralleluniverse.fibers.Suspendable; import com.google.common.collect.ImmutableList; +import net.corda.core.crypto.keyrotation.crossprovider.PartyIdentityResolver; import net.corda.samples.negotiation.contracts.ProposalAndTradeContract; import net.corda.samples.negotiation.states.ProposalState; import net.corda.samples.negotiation.states.TradeState; @@ -17,7 +18,6 @@ import net.corda.core.transactions.LedgerTransaction; import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; -import net.corda.core.utilities.ProgressTracker; import org.jetbrains.annotations.NotNull; import java.security.PublicKey; @@ -31,7 +31,6 @@ public class AcceptanceFlow { public static class Initiator extends FlowLogic { private UniqueIdentifier proposalId; - private ProgressTracker progressTracker = new ProgressTracker(); public Initiator(UniqueIdentifier proposalId) { this.proposalId = proposalId; @@ -47,25 +46,31 @@ public SignedTransaction call() throws FlowException { ProposalState input = (ProposalState) inputStateAndRef.getState().getData(); - //Creating the output + // Creating the output TradeState output = new TradeState(input.getAmount(), input.getBuyer(), input.getSeller(), input.getLinearId()); - //Creating the command + // Creating the command List requiredSigners = ImmutableList.of(input.getProposee().getOwningKey(), input.getProposer().getOwningKey()); Command command = new Command(new ProposalAndTradeContract.Commands.Accept(), requiredSigners); - //Building the transaction + // Building the transaction Party notary = inputStateAndRef.getState().getNotary(); TransactionBuilder txBuilder = new TransactionBuilder(notary) .addInputState(inputStateAndRef) .addOutputState(output, ProposalAndTradeContract.ID) .addCommand(command); - //Signing the transaction ourselves + // Signing the transaction ourselves SignedTransaction partStx = getServiceHub().signInitialTransaction(txBuilder); - //Gathering the counterparty's signature - Party counterparty = (getOurIdentity().equals(input.getProposer()))? input.getProposee() : input.getProposer(); + // Gathering the counterparty's signature + // + // The proposer must be resolved to its latest identity before it can be compared with the party returned by `getOurIdentity`. + // + // The counterparty might be an old key, but the session will be initiated with the most up-to-date identity. + // No need to use the resolved party in this case. + Party proposer = PartyIdentityResolver.Companion.resolveToCurrentParty(input.getProposer(), getServiceHub().getIdentityService()); + Party counterparty = (getOurIdentity().equals(proposer))? input.getProposee() : input.getProposer(); FlowSession counterpartySession = initiateFlow(counterparty); SignedTransaction fullyStx = subFlow(new CollectSignaturesFlow(partStx, ImmutableList.of(counterpartySession))); @@ -92,7 +97,10 @@ public SignedTransaction call() throws FlowException { protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowException { try { LedgerTransaction ledgerTx = stx.toLedgerTransaction(getServiceHub(), false); - Party proposee = ledgerTx.inputsOfType(ProposalState.class).get(0).getProposee(); + + // The proposee must be resolved to its latest identity before it can be compared with the party returned by `counterpartySession`. + // `counterpartySession` always returns the most up-to-date identity of the counterparty. + Party proposee = PartyIdentityResolver.Companion.resolveToCurrentParty(ledgerTx.inputsOfType(ProposalState.class).get(0).getProposee(), getServiceHub().getIdentityService()); if(!proposee.equals(counterpartySession.getCounterparty())){ throw new FlowException("Only the proposee can accept a proposal."); }