From 593631ae8174412be2c06f971d4b902c6f2644a4 Mon Sep 17 00:00:00 2001 From: Lydia Garms Date: Wed, 15 Apr 2026 16:00:33 +0100 Subject: [PATCH 1/9] fix(event-listener): reload keys on receiving new events in case key DB has been modified --- .../common/backup-encrypted-data-listener.mjs | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/boilerplate/common/backup-encrypted-data-listener.mjs b/src/boilerplate/common/backup-encrypted-data-listener.mjs index 199cba19..2e98370c 100644 --- a/src/boilerplate/common/backup-encrypted-data-listener.mjs +++ b/src/boilerplate/common/backup-encrypted-data-listener.mjs @@ -50,6 +50,22 @@ export default class BackupEncryptedDataEventListener { this.lastProcessedBlock = 0; // Track last block we processed } + loadKeys() { + const keys = JSON.parse(fs.readFileSync(keyDb, 'utf-8')); + this.secretKey = generalise(keys.secretKey); + this.publicKey = generalise(keys.publicKey); + this.sharedPublicKey = keys.sharedPublicKey + ? generalise(keys.sharedPublicKey) + : null; + this.sharedSecretKey = keys.sharedSecretKey + ? generalise(keys.sharedSecretKey) + : null; + + if (!keys.secretKey || !keys.publicKey) { + throw new Error('Invalid key file: missing required keys'); + } + } + async init() { try { this.instance = await getContractInstance('CONTRACT_NAME'); @@ -64,15 +80,7 @@ export default class BackupEncryptedDataEventListener { if (!fs.existsSync(keyDb)) await registerKey(utils.randomHex(31), 'CONTRACT_NAME', true); - const keys = JSON.parse(fs.readFileSync(keyDb, 'utf-8')); - this.secretKey = generalise(keys.secretKey); - this.publicKey = generalise(keys.publicKey); - this.sharedPublicKey = generalise(keys.sharedPublicKey); - this.sharedSecretKey = generalise(keys.sharedSecretKey); - - if (!keys.secretKey || !keys.publicKey) { - throw new Error('Invalid key file: missing required keys'); - } + this.loadKeys(); } catch (error) { console.error( 'encrypted-data-listener', @@ -240,6 +248,9 @@ export default class BackupEncryptedDataEventListener { async processBackupEventData(eventData) { activeBackupProcesses += 1; try { + // Shared keys are written to key.json at runtime, so reload them here + // rather than relying on the values captured when the listener started. + this.loadKeys(); const keyPairs = [ { secretKey: this.secretKey, publicKey: this.publicKey }, { secretKey: this.sharedSecretKey, publicKey: this.sharedPublicKey }, From 013605fdf332c1a6dc3a069913c4a2a8dee3fb89 Mon Sep 17 00:00:00 2001 From: Lydia Garms Date: Thu, 16 Apr 2026 12:35:47 +0100 Subject: [PATCH 2/9] fix(contracts): reinitialisable variables need checks in the contract to make sure they can't be reinitialised maliciously --- test/contracts/user-friendly-tests/NFT_Escrow.zol | 1 + test/contracts/user-friendly-tests/Swap.zol | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/contracts/user-friendly-tests/NFT_Escrow.zol b/test/contracts/user-friendly-tests/NFT_Escrow.zol index 7f80ea03..9bc41342 100644 --- a/test/contracts/user-friendly-tests/NFT_Escrow.zol +++ b/test/contracts/user-friendly-tests/NFT_Escrow.zol @@ -35,6 +35,7 @@ contract NFT_Escrow { require(approvals[msg.sender] == sender); require(recipient != address(0), "NFT_Escrow: transfer to the zero address"); require(sender != address(0), "NFT_Escrow: transfer from the zero address"); + require(tokenOwners[tokenId] == sender); tokenOwners[tokenId] = recipient; } diff --git a/test/contracts/user-friendly-tests/Swap.zol b/test/contracts/user-friendly-tests/Swap.zol index 53ca2f28..1ef269aa 100644 --- a/test/contracts/user-friendly-tests/Swap.zol +++ b/test/contracts/user-friendly-tests/Swap.zol @@ -19,7 +19,7 @@ contract Swap { function deposit(secret uint256 amount, secret uint256 tokenId) public { balances[msg.sender] += amount; - reinitialisable tokenOwners[tokenId] = msg.sender; + tokenOwners[tokenId] = msg.sender; } function startSwap(secret address sharedAddress, secret uint256 amountSent, secret uint256 tokenIdSent, secret uint256 tokenIdRecieved) public { From 05cca3209f5b2b8e36b3dcb0c80862325780bff8 Mon Sep 17 00:00:00 2001 From: Lydia Garms Date: Fri, 17 Apr 2026 15:35:32 +0100 Subject: [PATCH 3/9] fix(docs): add comment on re-initialisable variables --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a58c5598..82f50dd0 100644 --- a/README.md +++ b/README.md @@ -254,7 +254,7 @@ contract Swap { --- #### Example: Using the `re-initisalisable` decorator -In the contract below, the owner of `tokenOwners[tokenId]` is the address stored as its mapping value. When `withdraw` is called, the commitment is nullified and the new value is `address(0)`, so no new commitment is created. This creates a problem if deposit is called again: how can a new owner provide a nullifier if the token was previously owned? The `re-initialisable` decorator solves this by allowing the variable to be re-initialised in `deposit`. +In the contract below, the owner of `tokenOwners[tokenId]` is the address stored as its mapping value. When `withdraw` is called, the commitment is nullified and the new value is `address(0)`, so no new commitment is created. This creates a problem if deposit is called again: how can a new owner provide a nullifier if the token was previously owned? The `re-initialisable` decorator solves this by allowing the variable to be re-initialised in `deposit`. Note that in the below contract the check `require(tokenOwners[tokenId] == msg.sender, "You're not the owner of this token.");` is essential to ensure that `tokenOwners[tokenId]` cannot be re-initialised maliciously in either `transfer` or `withdraw`. For a non re-initialisable secret variable this check would not be necessary because only the owner could modify it. ```solidity contract NFT_Escrow { @@ -272,12 +272,12 @@ contract NFT_Escrow { } function transfer(secret address recipient, secret uint256 tokenId) public { - require(tokenOwners[tokenId] == msg.sender, "Youre not the owner of this token."); + require(tokenOwners[tokenId] == msg.sender, "You're not the owner of this token."); tokenOwners[tokenId] = recipient; } function withdraw(uint256 tokenId) public { - require(tokenOwners[tokenId] == msg.sender, "Youre not the owner of this token."); + require(tokenOwners[tokenId] == msg.sender, "You're not the owner of this token."); tokenOwners[tokenId] = address(0); bool success = erc721.transferFrom(address(this), msg.sender, tokenId); require(success == true); From 9ccf84b9c8a10528b77592705d3b88555467ee2a Mon Sep 17 00:00:00 2001 From: Lydia Garms Date: Fri, 17 Apr 2026 15:50:02 +0100 Subject: [PATCH 4/9] fix(tests): remove unnecessary testing logic from contract --- test/contracts/action-tests/Escrow-refs.zol | 22 --------------------- 1 file changed, 22 deletions(-) diff --git a/test/contracts/action-tests/Escrow-refs.zol b/test/contracts/action-tests/Escrow-refs.zol index f7335f5d..901c3167 100644 --- a/test/contracts/action-tests/Escrow-refs.zol +++ b/test/contracts/action-tests/Escrow-refs.zol @@ -8,21 +8,9 @@ contract Escrow { secret mapping(address => uint256) public balances; IERC20 public erc20; - secret bytes20[] public b; - - struct TypeTest { - uint256 amount; - bytes20 amount2; - address amount3; - bool amount4; - } sharedSecret mapping(address => bytes20) references; - secret mapping(uint256 => TypeTest) private d; - secret bytes20 recentReference; - secret TypeTest refStruct; - constructor(address erc20Address) { erc20 = IERC20(erc20Address); } @@ -38,16 +26,6 @@ contract Escrow { balances[msg.sender] -= amount; unknown balances[recipient] += amount; references[sharedAddress] = transferRef; - recentReference = transferRef; - b[0] = transferRef; - refStruct.amount = 5; - refStruct.amount4 = true; - refStruct.amount3 = recipient; - refStruct.amount2 = transferRef; - d[0].amount = 5; - d[0].amount4 = true; - d[0].amount3 = recipient; - d[0].amount2 = transferRef; } From 300b098c2ee3157b3a331acfd6a5dd0d7bb5c2ce Mon Sep 17 00:00:00 2001 From: Lydia Garms Date: Fri, 17 Apr 2026 18:44:29 +0100 Subject: [PATCH 5/9] fix(docs): update swap testing documentation to fix an error where user A and user B are mixed up and to address the fact that in double testing we no longer share a commitment DB --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 82f50dd0..e23134c2 100644 --- a/README.md +++ b/README.md @@ -518,7 +518,7 @@ $ sh bin/startup-double This starts two orchestration servers running the Swap contract that can be reached on ports 3000 and 3001 respectively. Let's say user A runs its server on port 3001 and user B on 3000. -For user A to initiate a swap with B, it needs a shared secret key derived from its public key (`recipientPubKey`) and the private key from user B. This is how B would compute the shared secret using the public key from A, send: +For user A to initiate a swap with B, it needs a shared secret key derived from the public key of user B (`recipientPubKey`) and its own private key. This is how A would compute the shared secret using the public key from B, send: ``` { @@ -538,6 +538,8 @@ Response: Use this shared key as the `sharedAddress` in swap interactions. +User B should do the same before the swap is initiated. Otherwise, their commitment DB will not be automatically updated via the event listener after User A runs `startSwap`. However, if they forget, they can always use the `backupDataRetriever` API to force the commitment DB to update. In their case `recipientPubKey` is set to the public key of user A. + ##### Deposit Tokens Each party deposits tokens they intend to trade. @@ -563,13 +565,14 @@ as a POST request to `http://localhost:3000/deposit`. ##### Initiate Swap -User A proposes a swap to the shared address, they send: +User A proposes a swap to the shared address. First, they must obtain the public key of user B for the encryption, so that user B will receive commitment preimages via the event listener. This public key is saved in `zapps/Swap/orchestration/common/db/key1.json`, lets say it is `18506935782509777864732454467672391704006521439730278073755619393220081058435`. This should be used as the `tokenOwners_tokenIdSent_newOwnerPublicKey`. They send: ``` { "sharedAddress": "0x3e574c310f7bc1657f7e0e127690a8f885e4bcd42c15489a332ed9a6658bfef6", "amountSent": 30, "tokenIdSent": 1, - "tokenIdRecieved": 2 + "tokenIdRecieved": 2, + "tokenOwners_tokenIdSent_newOwnerPublicKey": "18506935782509777864732454467672391704006521439730278073755619393220081058435" } ``` as a POST request to `http://localhost:3001/startSwap`. @@ -577,14 +580,15 @@ as a POST request to `http://localhost:3001/startSwap`. This deducts 30 tokens from User A and locks token 1 for the proposed swap. ##### Complete Swap -User B accepts the swap with a matching offer, they send: +User B accepts the swap with a matching offer. User B must similarly obtain the public key of user A. This public key is saved in `zapps/Swap/orchestration/common/db/key2.json`, lets say it is `19113029868759597499687530931590653617019453270233891831264939544742234896197`. Again, this should be used as the `tokenOwners_tokenIdSent_newOwnerPublicKey`. They send: ``` { "sharedAddress": "0x3e574c310f7bc1657f7e0e127690a8f885e4bcd42c15489a332ed9a6658bfef6", "counterParty": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "tokenIdSent": 2, "tokenIdRecieved": 1, - "amountRecieved": 30 + "amountRecieved": 30, + "tokenOwners_tokenIdSent_newOwnerPublicKey": "19113029868759597499687530931590653617019453270233891831264939544742234896197" } ``` as a POST request to `http://localhost:3000/completeSwap`. From a9b58e1770ede6fde0fee4380a089f5e9c2a5566 Mon Sep 17 00:00:00 2001 From: Lydia Garms Date: Wed, 15 Apr 2026 11:41:38 +0100 Subject: [PATCH 6/9] fix(deployment): parameter defined twice --- src/codeGenerators/orchestration/files/toOrchestration.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codeGenerators/orchestration/files/toOrchestration.ts b/src/codeGenerators/orchestration/files/toOrchestration.ts index 951f4354..4f43ebc7 100644 --- a/src/codeGenerators/orchestration/files/toOrchestration.ts +++ b/src/codeGenerators/orchestration/files/toOrchestration.ts @@ -543,7 +543,7 @@ const prepareMigrationsFile = (file: localFile, node: any) => { } } }); - } else if(constructorParamsIncludesAddr) { + } else if (!node.isConstructor && constructorParamsIncludesAddr) { // for each address in the shield contract constructor... constructorAddrParams.forEach(name => { // we have an address input which is likely not a another contract @@ -556,7 +556,7 @@ const prepareMigrationsFile = (file: localFile, node: any) => { }); } if (node.isConstructor) { - // we have a constructor which requires a proof + // we have a cnstrctr.mjs file if (node.functionNames.includes('cnstrctr') || publicConstructorParams.length > 0) { customProofImport += `const constructorInput = JSON.parse( fs.readFileSync('/app/orchestration/common/db/constructorTx.json', 'utf-8'), From 704b3de35a1ab16cd22084dd59e84e9399f78ad1 Mon Sep 17 00:00:00 2001 From: Lydia Garms Date: Wed, 15 Apr 2026 12:05:36 +0100 Subject: [PATCH 7/9] fix(tests): use bytes20 which is a supported variable type --- test/real-world-zapps/Voting.zol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/real-world-zapps/Voting.zol b/test/real-world-zapps/Voting.zol index 4a585099..3b78791b 100644 --- a/test/real-world-zapps/Voting.zol +++ b/test/real-world-zapps/Voting.zol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; contract Voting { secret mapping (uint256 => uint256) public proposalVotes; - mapping (uint256 => bytes32) public proposals; + mapping (uint256 => bytes20) public proposals; mapping (address => bool) public hasVoted; mapping (address => bool) public canPropose; address public admin; @@ -21,7 +21,7 @@ contract Voting { hasVoted[msg.sender] = true; } - function propose(uint256 proposalId, bytes32 proposalName) public { + function propose(uint256 proposalId, bytes20 proposalName) public { require(canPropose[msg.sender]); proposals[proposalId] = proposalName; } From f466cd30adcd83342d295e5bf7f6e42cbd38db16 Mon Sep 17 00:00:00 2001 From: Lydia Garms Date: Thu, 16 Apr 2026 16:26:59 +0100 Subject: [PATCH 8/9] fix: github workflows as npm 20 deprecated --- .github/workflows/main.yml | 7 +++---- .github/workflows/release.yml | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ed8c72df..c0e529c2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,7 +22,7 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 # Runs a single command using the runners shell - name: compile and test @@ -41,8 +41,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v1 + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 with: node-version: '16.17.0' @@ -76,4 +76,3 @@ jobs: - name: run zapp apitest run: npx mocha --exit --timeout 50000 --require @babel/register apitest.js - diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aeb76920..e6e8de9c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,8 +8,8 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 with: node-version: '14.17.0' - run: | @@ -22,4 +22,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: echo ${{ steps.semantic.outputs.release-version }} \ No newline at end of file + - run: echo ${{ steps.semantic.outputs.release-version }} From 9313551ccdec34908cfc44f9e328946ac6fd62e8 Mon Sep 17 00:00:00 2001 From: Lydia Garms Date: Thu, 16 Apr 2026 16:40:08 +0100 Subject: [PATCH 9/9] fix(workflow): use local TypeScript in CI via npx tsc --- .github/workflows/main.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c0e529c2..fef98fc4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,7 +28,7 @@ jobs: - name: compile and test run: | npm i npm@7 - tsc + npx tsc npm test - name: compile shield contracts and circuits diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e6e8de9c..c8d3bbe7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: node-version: '14.17.0' - run: | npm install - tsc + npx tsc - uses: codfish/semantic-release-action@master id: semantic