diff --git a/README.md b/README.md index a58c5598..e23134c2 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); @@ -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`. 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 }, 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; } 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 {