From f55a688607a1ae800d2165076deac9185560f8f1 Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Fri, 13 Dec 2024 14:46:55 -0500 Subject: [PATCH] Add duplicate TX rejection on-chain in Pente Signed-off-by: Peter Broadhurst --- .../domains/pente/PentePrivacyGroup.sol | 9 ++++++++ .../test/domains/pente/PentePrivacyGroup.ts | 23 ++++++++++++++----- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/solidity/contracts/domains/pente/PentePrivacyGroup.sol b/solidity/contracts/domains/pente/PentePrivacyGroup.sol index 8bb4f47a5..95cb9c840 100644 --- a/solidity/contracts/domains/pente/PentePrivacyGroup.sol +++ b/solidity/contracts/domains/pente/PentePrivacyGroup.sol @@ -29,11 +29,13 @@ contract PentePrivacyGroup is IPente, UUPSUpgradeable, EIP712Upgradeable { mapping(bytes32 => bool) private _unspent; mapping(bytes32 => address) private _approvals; + mapping(bytes32 => bool) private _txids; address _nextImplementation; // Config follows the convention of a 4 byte type selector, followed by ABI encoded bytes bytes4 public constant PenteConfig_V0 = 0x00010000; + error PenteDuplicateTransaction(bytes32 txId); error PenteUnsupportedConfigType(bytes4 configSelector); error PenteDuplicateEndorser(address signer); error PenteInvalidEndorser(address signer); @@ -155,6 +157,13 @@ contract PentePrivacyGroup is IPente, UUPSUpgradeable, EIP712Upgradeable { States calldata states, ExternalCall[] calldata externalCalls ) internal { + + // On-chain enforcement of TXID uniqueness + if (_txids[txId] != false) { + revert PenteDuplicateTransaction(txId); + } + _txids[txId] = true; + // Perform the state transitions for (uint i = 0; i < states.inputs.length; i++) { if (!_unspent[states.inputs[i]]) { diff --git a/solidity/test/domains/pente/PentePrivacyGroup.ts b/solidity/test/domains/pente/PentePrivacyGroup.ts index 3e53efe6c..96a55dadb 100644 --- a/solidity/test/domains/pente/PentePrivacyGroup.ts +++ b/solidity/test/domains/pente/PentePrivacyGroup.ts @@ -129,15 +129,16 @@ describe("PentePrivacyGroup", function () { ); const tx1ID = randBytes32(); + const tx1 = { + inputs: [], + reads: [], + outputs: stateSet1, + info: info1, + }; await expect( privacyGroup.transition( tx1ID, - { - inputs: [], - reads: [], - outputs: stateSet1, - info: info1, - }, + tx1, [], endorsements1 ) @@ -145,6 +146,16 @@ describe("PentePrivacyGroup", function () { .to.emit(privacyGroup, "PenteTransition") .withArgs(tx1ID, [], [], stateSet1, info1); + // Rejects duplicate + await expect( + privacyGroup.transition( + tx1ID, + tx1, + [], + endorsements1 + ) + ).to.be.rejectedWith("PenteDuplicateTransaction"); + const stateSet2 = [randBytes32(), randBytes32(), randBytes32()]; const inputs2 = [stateSet1[1]]; const reads2 = [stateSet1[0], stateSet1[2]];