diff --git a/wallet/src/assets/lottie/check-mark-cross.json b/wallet/src/assets/lottie/check-mark-cross.json new file mode 100644 index 000000000..1e0f044c0 --- /dev/null +++ b/wallet/src/assets/lottie/check-mark-cross.json @@ -0,0 +1 @@ +{"v":"5.4.3","fr":25,"ip":0,"op":75,"w":90,"h":90,"nm":"btn_cocheBad_02","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"good Outlines 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.244],"y":[1]},"o":{"x":[0.959],"y":[0.003]},"n":["0p244_1_0p959_0p003"],"t":55,"s":[0],"e":[91]},{"t":64}],"ix":10},"p":{"a":0,"k":[45,45,0],"ix":2},"a":{"a":0,"k":[29.531,28.758,0],"ix":1},"s":{"a":0,"k":[108.742,108.742,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.012,-0.012],[0,0]],"o":[[0,0],[0,0]],"v":[[-9.334,-9.673],[10.002,9.739]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"n":["0p667_1_0p167_0"],"t":54,"s":[0],"e":[100]},{"t":55}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.72549021244,0.129411771894,0.129411771894,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[29.171,28.691],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":125,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"good Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[45,45,0],"ix":2},"a":{"a":0,"k":[29.531,28.758,0],"ix":1},"s":{"a":0,"k":[108.742,108.742,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.012,-0.012],[0,0]],"o":[[0,0],[0,0]],"v":[[-9.334,-9.673],[10.002,9.739]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.651],"y":[-0.011]},"n":["0p667_1_0p651_-0p011"],"t":41,"s":[0],"e":[0]},{"t":50}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.132],"y":[0.999]},"o":{"x":[0.935],"y":[0.005]},"n":["0p132_0p999_0p935_0p005"],"t":41,"s":[0],"e":[100]},{"t":50}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.72549021244,0.129411771894,0.129411771894,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[29.171,28.691],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":125,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"circle-purple","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[0],"e":[360]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":33,"s":[360],"e":[876]},{"t":54}],"ix":10},"p":{"a":0,"k":[45,45,0],"ix":2},"a":{"a":0,"k":[37.5,37.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-19.054,0],[0,-19.054],[19.054,0],[0,19.054]],"o":[[19.054,0],[0,19.054],[-19.054,0],[0,-19.054]],"v":[[0,-34.5],[34.5,0],[0,34.5],[-34.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[1,1,1,1],"e":[0.72549021244,0.129411771894,0.129411771894,1]},{"t":10}],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[37.5,37.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.123],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p123_1_0p333_0"],"t":0,"s":[0],"e":[100]},{"i":{"x":[0.835],"y":[1]},"o":{"x":[0.229],"y":[0]},"n":["0p835_1_0p229_0"],"t":33,"s":[100],"e":[100]},{"t":54}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.262],"y":[1]},"o":{"x":[0.581],"y":[0]},"n":["0p262_1_0p581_0"],"t":0,"s":[0],"e":[100]},{"i":{"x":[0.825],"y":[1]},"o":{"x":[0.161],"y":[0]},"n":["0p825_1_0p161_0"],"t":33,"s":[100],"e":[0]},{"t":54}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":750,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/wallet/src/components/BridgeComponent/index.jsx b/wallet/src/components/BridgeComponent/index.jsx index c0a43edce..83016c979 100644 --- a/wallet/src/components/BridgeComponent/index.jsx +++ b/wallet/src/components/BridgeComponent/index.jsx @@ -29,9 +29,9 @@ import './toast.css'; import './styles.scss'; import TransferModal from '../Modals/Bridge/Transfer/index.jsx'; import checkMarkYes from '../../assets/lottie/check-mark-yes.json'; +import checkMarkCross from '../../assets/lottie/check-mark-cross.json'; import ERC20 from '../../contract-abis/ERC20.json'; -import { APPROVE_AMOUNT } from '../../constants'; import { retrieveAndDecrypt } from '../../utils/lib/key-storage'; import BigFloat from '../../common-files/classes/bigFloat'; @@ -171,6 +171,7 @@ const BridgeComponent = () => { const [showModalConfirm, setShowModalConfirm] = useState(false); const [showModalTransferInProgress, setShowModalTransferInProgress] = useState(true); const [showModalTransferEnRoute, setShowModalTransferEnRoute] = useState(false); + const [showModalTransferFailed, setShowModalTransferFailed] = useState(false); const [showModalTransferConfirmed, setShowModalTransferConfirmed] = useState(false); const [readyTx, setReadyTx] = useState(''); @@ -183,6 +184,7 @@ const BridgeComponent = () => { setShowModalConfirm(false); setShowModalTransferInProgress(false); setShowModalTransferEnRoute(false); + setShowModalTransferFailed(false); setShowModalTransferConfirmed(false); }; @@ -231,7 +233,12 @@ const BridgeComponent = () => { switch (txType) { case 'deposit': { const pkd = decompressKey(generalise(state.compressedPkd)); - await approve(ercAddress, shieldContractAddress, 'ERC20', APPROVE_AMOUNT); + await approve( + ercAddress, + shieldContractAddress, + 'ERC20', + new BigFloat(transferValue, token.decimals).toBigInt().toString(), + ); setShowModalConfirm(true); setShowModalTransferInProgress(true); await timeout(2000); @@ -248,7 +255,13 @@ const BridgeComponent = () => { tokenType: 'ERC20', }, shieldContractAddress, - ); + ).catch(e => { + console.log('Error in transfer', e); + setShowModalTransferEnRoute(false); + setShowModalTransferFailed(true); + return { transaction: null }; + }); + if (transaction === null) return { type: 'failed_transfer' }; setShowModalTransferEnRoute(false); setShowModalTransferConfirmed(true); console.log('Proof Done'); @@ -277,7 +290,13 @@ const BridgeComponent = () => { fees: 1, }, shieldContractAddress, - ); + ).catch(e => { + console.log('Error in transfer', e); + setShowModalTransferEnRoute(false); + setShowModalTransferFailed(true); + return { transaction: null }; + }); + if (transaction === null) return { type: 'failed_transfer' }; setShowModalTransferEnRoute(false); setShowModalTransferConfirmed(true); return { @@ -597,6 +616,29 @@ const BridgeComponent = () => { )} + + {showModalTransferFailed && ( + + + + transfer failed + + + + + + + +

Failed To Create Transaction.

+ {/* Please Try Again */} + {/* View on etherscan */} + +
handleCloseConfirmModal()}>Close
+
+
+
+
+ )} diff --git a/wallet/src/components/Modals/sendModal.tsx b/wallet/src/components/Modals/sendModal.tsx index 3b35d65f7..9f1e973a2 100644 --- a/wallet/src/components/Modals/sendModal.tsx +++ b/wallet/src/components/Modals/sendModal.tsx @@ -21,9 +21,15 @@ import successHand from '../../assets/img/modalImages/success-hand.png'; import transferCompletedImg from '../../assets/img/modalImages/tranferCompleted.png'; import BigFloat from '../../common-files/classes/bigFloat'; import checkMarkYes from '../../assets/lottie/check-mark-yes.json'; +import Transaction from '../../common-files/classes/transaction'; +import checkMarkCross from '../../assets/lottie/check-mark-cross.json'; const supportedTokens = importTokens(); +type Transfer = + | { type: 'offchain'; transaction: Transaction; rawTransaction: string } + | { type: 'failed_transfer' }; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const { proposerUrl } = global.config; @@ -297,7 +303,7 @@ const Divider = styled.div` border-bottom: solid 1px #ddd; `; -const SpineerBox = styled.div` +const SpinnerBox = styled.div` display: flex; justify-content: center; align-content: center; @@ -411,6 +417,7 @@ const SendModal = (props: SendModalProps): JSX.Element => { const [showModalConfirm, setShowModalConfirm] = useState(false); const [showModalTransferInProgress, setShowModalTransferInProgress] = useState(true); const [showModalTransferEnRoute, setShowModalTransferEnRoute] = useState(false); + const [showModalTransferFailed, setShowModalTransferFailed] = useState(false); const [showModalTransferConfirmed, setShowModalTransferConfirmed] = useState(false); // const [showTransferModal, setShowTransferModal] = useState(false); @@ -425,6 +432,7 @@ const SendModal = (props: SendModalProps): JSX.Element => { setShowModalConfirm(false); setShowModalTransferInProgress(false); setShowModalTransferEnRoute(false); + setShowModalTransferFailed(false); setShowModalTransferConfirmed(false); }; // const handleClose = () => setShowTransferModal(false); @@ -467,7 +475,7 @@ const SendModal = (props: SendModalProps): JSX.Element => { getShieldAddress(); }, []); - async function sendTx() { + async function sendTx(): Promise { if (shieldContractAddress === '') setShieldAddress((await getContractAddress('Shield')).data.address); setShowModalConfirm(true); @@ -490,7 +498,13 @@ const SendModal = (props: SendModalProps): JSX.Element => { fee: 1, }, shieldContractAddress, - ); + ).catch(e => { + console.log('Error in transfer', e); + setShowModalTransferEnRoute(false); + setShowModalTransferFailed(true); + return { transaction: null, rawTransaction: '' }; + }); + if (transaction === null) return { type: 'failed_transfer' }; setShowModalTransferEnRoute(false); setShowModalTransferConfirmed(true); return { @@ -607,7 +621,8 @@ const SendModal = (props: SendModalProps): JSX.Element => { props.onHide(); setShowModalConfirm(true); setShowModalTransferInProgress(true); - setReadyTx(await sendTx()); + const pendingTx = await sendTx(); + if (pendingTx.type !== 'failed_transfer') setReadyTx(pendingTx); }} > Continue @@ -631,11 +646,11 @@ const SendModal = (props: SendModalProps): JSX.Element => { approve - + - +

Creating Transaction

@@ -654,11 +669,11 @@ const SendModal = (props: SendModalProps): JSX.Element => { deposit confirmed - + - +

Generating Zk Proof

@@ -677,9 +692,9 @@ const SendModal = (props: SendModalProps): JSX.Element => { transfer completed - + success hand - +

Transaction created sucessfully.

Your transfer is ready to send.
@@ -704,6 +719,28 @@ const SendModal = (props: SendModalProps): JSX.Element => { )} + + {showModalTransferFailed && ( + + + + transfer failed + + + + + +
+

Failed To Create Transaction.

+ {/*
Please Try Again
*/} + +
handleCloseConfirmModal()}>Close
+
+ {/* View on etherscan */} +
+
+
+ )} ); diff --git a/wallet/src/nightfall-browser/services/deposit.js b/wallet/src/nightfall-browser/services/deposit.js index a50e1eee0..4a64219d7 100644 --- a/wallet/src/nightfall-browser/services/deposit.js +++ b/wallet/src/nightfall-browser/services/deposit.js @@ -65,35 +65,35 @@ async function deposit(items, shieldContractAddress) { ]; logger.debug(`witness input is ${witnessInput.join(' ')}`); - const zokratesProvider = await initialize(); - const artifacts = { program: new Uint8Array(program), abi }; - const keypair = { pk: new Uint8Array(pk) }; + try { + const zokratesProvider = await initialize(); + const artifacts = { program: new Uint8Array(program), abi }; + const keypair = { pk: new Uint8Array(pk) }; - // computation - const { witness } = zokratesProvider.computeWitness(artifacts, witnessInput); - // generate proof - const { proof } = zokratesProvider.generateProof(artifacts.program, witness, keypair.pk); - const shieldContractInstance = await getContractInstance( - SHIELD_CONTRACT_NAME, - shieldContractAddress, - ); + // computation + const { witness } = zokratesProvider.computeWitness(artifacts, witnessInput); + // generate proof + const { proof } = zokratesProvider.generateProof(artifacts.program, witness, keypair.pk); + const shieldContractInstance = await getContractInstance( + SHIELD_CONTRACT_NAME, + shieldContractAddress, + ); - // next we need to compute the optimistic Transaction object - const optimisticDepositTransaction = new Transaction({ - fee, - transactionType: 0, - tokenType: items.tokenType, - tokenId, - value, - ercAddress, - commitments: [commitment], - proof, - }); - logger.silly( - `Optimistic deposit transaction ${JSON.stringify(optimisticDepositTransaction, null, 2)}`, - ); - // and then we can create an unsigned blockchain transaction - try { + // next we need to compute the optimistic Transaction object + const optimisticDepositTransaction = new Transaction({ + fee, + transactionType: 0, + tokenType: items.tokenType, + tokenId, + value, + ercAddress, + commitments: [commitment], + proof, + }); + logger.silly( + `Optimistic deposit transaction ${JSON.stringify(optimisticDepositTransaction, null, 2)}`, + ); + // and then we can create an unsigned blockchain transaction const rawTransaction = await shieldContractInstance.methods .submitTransaction(Transaction.buildSolidityStruct(optimisticDepositTransaction)) .encodeABI(); diff --git a/wallet/src/nightfall-browser/services/transfer.js b/wallet/src/nightfall-browser/services/transfer.js index 5c64a7db9..1ce441cf5 100644 --- a/wallet/src/nightfall-browser/services/transfer.js +++ b/wallet/src/nightfall-browser/services/transfer.js @@ -53,194 +53,197 @@ async function transfer(transferParams, shieldContractAddress) { ); if (oldCommitments) logger.debug(`Found commitments ${JSON.stringify(oldCommitments, null, 2)}`); else throw new Error('No suitable commitments were found'); // caller to handle - need to get the user to make some commitments or wait until they've been posted to the blockchain and Timber knows about them - // Having found either 1 or 2 commitments, which are suitable inputs to the - // proof, the next step is to compute their nullifiers; - const nullifiers = oldCommitments.map(commitment => new Nullifier(commitment, nsk)); - // then the new output commitment(s) - const totalInputCommitmentValue = oldCommitments.reduce( - (acc, commitment) => acc + commitment.preimage.value.bigInt, - 0n, - ); - // we may need to return change to the recipient - const change = totalInputCommitmentValue - totalValueToSend; - // if so, add an output commitment to do that - if (change !== 0n) { - values.push(new GN(change)); - recipientPkds.push(pkd); - recipientCompressedPkds.push(compressedPkd); - } - const newCommitments = []; - let secrets = []; - const salts = []; - let potentialSalt; - let potentialCommitment; - for (let i = 0; i < recipientCompressedPkds.length; i++) { - // loop to find a new salt until the commitment hash is smaller than the BN128_GROUP_ORDER - do { - // eslint-disable-next-line no-await-in-loop - potentialSalt = new GN((await rand(ZKP_KEY_LENGTH)).bigInt % BN128_GROUP_ORDER); - potentialCommitment = new Commitment({ - ercAddress, - tokenId, - value: values[i], - pkd: recipientPkds[i], - compressedPkd: recipientCompressedPkds[i], - salt: potentialSalt, - }); - // encrypt secrets such as erc20Address, tokenId, value, salt for recipient - if (i === 0) { + try { + // Having found either 1 or 2 commitments, which are suitable inputs to the + // proof, the next step is to compute their nullifiers; + const nullifiers = oldCommitments.map(commitment => new Nullifier(commitment, nsk)); + // then the new output commitment(s) + const totalInputCommitmentValue = oldCommitments.reduce( + (acc, commitment) => acc + commitment.preimage.value.bigInt, + 0n, + ); + // we may need to return change to the recipient + const change = totalInputCommitmentValue - totalValueToSend; + // if so, add an output commitment to do that + if (change !== 0n) { + values.push(new GN(change)); + recipientPkds.push(pkd); + recipientCompressedPkds.push(compressedPkd); + } + const newCommitments = []; + let secrets = []; + const salts = []; + let potentialSalt; + let potentialCommitment; + for (let i = 0; i < recipientCompressedPkds.length; i++) { + // loop to find a new salt until the commitment hash is smaller than the BN128_GROUP_ORDER + do { // eslint-disable-next-line no-await-in-loop - secrets = await Secrets.encryptSecrets( - [ercAddress.bigInt, tokenId.bigInt, values[i].bigInt, potentialSalt.bigInt], - [recipientPkds[0][0].bigInt, recipientPkds[0][1].bigInt], - ); - } - } while (potentialCommitment.hash.bigInt > BN128_GROUP_ORDER); - salts.push(potentialSalt); - newCommitments.push(potentialCommitment); - } + potentialSalt = new GN((await rand(ZKP_KEY_LENGTH)).bigInt % BN128_GROUP_ORDER); + potentialCommitment = new Commitment({ + ercAddress, + tokenId, + value: values[i], + pkd: recipientPkds[i], + compressedPkd: recipientCompressedPkds[i], + salt: potentialSalt, + }); + // encrypt secrets such as erc20Address, tokenId, value, salt for recipient + if (i === 0) { + // eslint-disable-next-line no-await-in-loop + secrets = await Secrets.encryptSecrets( + [ercAddress.bigInt, tokenId.bigInt, values[i].bigInt, potentialSalt.bigInt], + [recipientPkds[0][0].bigInt, recipientPkds[0][1].bigInt], + ); + } + } while (potentialCommitment.hash.bigInt > BN128_GROUP_ORDER); + salts.push(potentialSalt); + newCommitments.push(potentialCommitment); + } - // compress the secrets to save gas - const compressedSecrets = Secrets.compressSecrets(secrets); + // compress the secrets to save gas + const compressedSecrets = Secrets.compressSecrets(secrets); - const commitmentTreeInfo = await Promise.all(oldCommitments.map(c => getSiblingInfo(c))); - const localSiblingPaths = commitmentTreeInfo.map(l => { - const path = l.siblingPath.path.map(p => p.value); - return generalise([l.root].concat(path.reverse())); - }); - const leafIndices = commitmentTreeInfo.map(l => l.leafIndex); - const blockNumberL2s = commitmentTreeInfo.map(l => l.isOnChain); - // time for a quick sanity check. We expect the number of old commitments, - // new commitments and nullifiers to be equal. - if (nullifiers.length !== oldCommitments.length || nullifiers.length !== newCommitments.length) { - logger.error( - `number of old commitments: ${oldCommitments.length}, number of new commitments: ${newCommitments.length}, number of nullifiers: ${nullifiers.length}`, - ); - throw new Error( - 'Commitment or nullifier numbers are mismatched. There should be equal numbers of each', - ); - } + const commitmentTreeInfo = await Promise.all(oldCommitments.map(c => getSiblingInfo(c))); + const localSiblingPaths = commitmentTreeInfo.map(l => { + const path = l.siblingPath.path.map(p => p.value); + return generalise([l.root].concat(path.reverse())); + }); + const leafIndices = commitmentTreeInfo.map(l => l.leafIndex); + const blockNumberL2s = commitmentTreeInfo.map(l => l.isOnChain); + // time for a quick sanity check. We expect the number of old commitments, + // new commitments and nullifiers to be equal. + if ( + nullifiers.length !== oldCommitments.length || + nullifiers.length !== newCommitments.length + ) { + logger.error( + `number of old commitments: ${oldCommitments.length}, number of new commitments: ${newCommitments.length}, number of nullifiers: ${nullifiers.length}`, + ); + throw new Error( + 'Commitment or nullifier numbers are mismatched. There should be equal numbers of each', + ); + } - // now we have everything we need to create a Witness and compute a proof - const witnessInput = [ - oldCommitments.map(commitment => commitment.preimage.ercAddress.integer).flat(), - oldCommitments.map(commitment => { - return { - id: commitment.preimage.tokenId.limbs(32, 8), - value: commitment.preimage.value.limbs(32, 8), - salt: commitment.preimage.salt.limbs(32, 8), - hash: commitment.hash.limbs(32, 8), - ask: ask.field(BN128_GROUP_ORDER), - }; - }), - newCommitments.map(commitment => { - return { - pkdRecipient: [ - commitment.preimage.pkd[0].field(BN128_GROUP_ORDER), - commitment.preimage.pkd[1].field(BN128_GROUP_ORDER), - ], - value: commitment.preimage.value.limbs(32, 8), - salt: commitment.preimage.salt.limbs(32, 8), - }; - }), - newCommitments.map(commitment => commitment.hash.integer), - nullifiers.map(nullifier => nullifier.preimage.nsk.limbs(32, 8)), - nullifiers.map(nullifier => generalise(nullifier.hash.hex(32, 31)).integer), - localSiblingPaths.map(siblingPath => siblingPath[0].field(BN128_GROUP_ORDER, false)), - localSiblingPaths.map(siblingPath => - siblingPath.slice(1).map(node => node.field(BN128_GROUP_ORDER, false)), - ), // siblingPAth[32] is a sha hash and will overflow a field but it's ok to take the mod here - hence the 'false' flag - leafIndices.map(leaf => leaf.toString()), - { - ephemeralKey1: secrets.ephemeralKeys[0].limbs(32, 8), - ephemeralKey2: secrets.ephemeralKeys[1].limbs(32, 8), - ephemeralKey3: secrets.ephemeralKeys[2].limbs(32, 8), - ephemeralKey4: secrets.ephemeralKeys[3].limbs(32, 8), - cipherText: secrets.cipherText.flat().map(text => text.field(BN128_GROUP_ORDER)), - sqrtMessage1: secrets.squareRootsElligator2[0].field(BN128_GROUP_ORDER), - sqrtMessage2: secrets.squareRootsElligator2[1].field(BN128_GROUP_ORDER), - sqrtMessage3: secrets.squareRootsElligator2[2].field(BN128_GROUP_ORDER), - sqrtMessage4: secrets.squareRootsElligator2[3].field(BN128_GROUP_ORDER), - }, - compressedSecrets.map(text => { - const bin = text.binary.padStart(256, '0'); - const parity = bin[0]; - const ordinate = bin.slice(1); - const fields = { - parity: !!Number(parity), // This converts parity into true / false from 1 / 0; - ordinate: new GN(ordinate, 'binary').field(BN128_GROUP_ORDER), - }; - return fields; - }), - ]; + // now we have everything we need to create a Witness and compute a proof + const witnessInput = [ + oldCommitments.map(commitment => commitment.preimage.ercAddress.integer).flat(), + oldCommitments.map(commitment => { + return { + id: commitment.preimage.tokenId.limbs(32, 8), + value: commitment.preimage.value.limbs(32, 8), + salt: commitment.preimage.salt.limbs(32, 8), + hash: commitment.hash.limbs(32, 8), + ask: ask.field(BN128_GROUP_ORDER), + }; + }), + newCommitments.map(commitment => { + return { + pkdRecipient: [ + commitment.preimage.pkd[0].field(BN128_GROUP_ORDER), + commitment.preimage.pkd[1].field(BN128_GROUP_ORDER), + ], + value: commitment.preimage.value.limbs(32, 8), + salt: commitment.preimage.salt.limbs(32, 8), + }; + }), + newCommitments.map(commitment => commitment.hash.integer), + nullifiers.map(nullifier => nullifier.preimage.nsk.limbs(32, 8)), + nullifiers.map(nullifier => generalise(nullifier.hash.hex(32, 31)).integer), + localSiblingPaths.map(siblingPath => siblingPath[0].field(BN128_GROUP_ORDER, false)), + localSiblingPaths.map(siblingPath => + siblingPath.slice(1).map(node => node.field(BN128_GROUP_ORDER, false)), + ), // siblingPAth[32] is a sha hash and will overflow a field but it's ok to take the mod here - hence the 'false' flag + leafIndices.map(leaf => leaf.toString()), + { + ephemeralKey1: secrets.ephemeralKeys[0].limbs(32, 8), + ephemeralKey2: secrets.ephemeralKeys[1].limbs(32, 8), + ephemeralKey3: secrets.ephemeralKeys[2].limbs(32, 8), + ephemeralKey4: secrets.ephemeralKeys[3].limbs(32, 8), + cipherText: secrets.cipherText.flat().map(text => text.field(BN128_GROUP_ORDER)), + sqrtMessage1: secrets.squareRootsElligator2[0].field(BN128_GROUP_ORDER), + sqrtMessage2: secrets.squareRootsElligator2[1].field(BN128_GROUP_ORDER), + sqrtMessage3: secrets.squareRootsElligator2[2].field(BN128_GROUP_ORDER), + sqrtMessage4: secrets.squareRootsElligator2[3].field(BN128_GROUP_ORDER), + }, + compressedSecrets.map(text => { + const bin = text.binary.padStart(256, '0'); + const parity = bin[0]; + const ordinate = bin.slice(1); + const fields = { + parity: !!Number(parity), // This converts parity into true / false from 1 / 0; + ordinate: new GN(ordinate, 'binary').field(BN128_GROUP_ORDER), + }; + return fields; + }), + ]; - const flattenInput = witnessInput.map(w => { - if (w.length === 1) { - const [w_] = w; - return w_; - } - return w; - }); + const flattenInput = witnessInput.map(w => { + if (w.length === 1) { + const [w_] = w; + return w_; + } + return w; + }); - console.log(`witness input is ${JSON.stringify(flattenInput)}`); - // call a zokrates worker to generate the proof - // This is (so far) the only place where we need to get specific about the - // circuit - let abi; - let program; - let pk; - let transactionType; - if (oldCommitments.length === 1) { - transactionType = 1; - blockNumberL2s.push(0); // We need top pad block numbers if we do a single transfer - if (!(await checkIndexDBForCircuit(singleTransfer))) - throw Error('Some circuit data are missing from IndexedDB'); - const [abiData, programData, pkData] = await Promise.all([ - getStoreCircuit(`${singleTransfer}-abi`), - getStoreCircuit(`${singleTransfer}-program`), - getStoreCircuit(`${singleTransfer}-pk`), - ]); - abi = abiData.data; - program = programData.data; - pk = pkData.data; - } else if (oldCommitments.length === 2) { - transactionType = 2; - if (!(await checkIndexDBForCircuit(doubleTransfer))) - throw Error('Some circuit data are missing from IndexedDB'); - const [abiData, programData, pkData] = await Promise.all([ - getStoreCircuit(`${doubleTransfer}-abi`), - getStoreCircuit(`${doubleTransfer}-program`), - getStoreCircuit(`${doubleTransfer}-pk`), - ]); - abi = abiData.data; - program = programData.data; - pk = pkData.data; - } else throw new Error('Unsupported number of commitments'); + console.log(`witness input is ${JSON.stringify(flattenInput)}`); + // call a zokrates worker to generate the proof + // This is (so far) the only place where we need to get specific about the + // circuit + let abi; + let program; + let pk; + let transactionType; + if (oldCommitments.length === 1) { + transactionType = 1; + blockNumberL2s.push(0); // We need top pad block numbers if we do a single transfer + if (!(await checkIndexDBForCircuit(singleTransfer))) + throw Error('Some circuit data are missing from IndexedDB'); + const [abiData, programData, pkData] = await Promise.all([ + getStoreCircuit(`${singleTransfer}-abi`), + getStoreCircuit(`${singleTransfer}-program`), + getStoreCircuit(`${singleTransfer}-pk`), + ]); + abi = abiData.data; + program = programData.data; + pk = pkData.data; + } else if (oldCommitments.length === 2) { + transactionType = 2; + if (!(await checkIndexDBForCircuit(doubleTransfer))) + throw Error('Some circuit data are missing from IndexedDB'); + const [abiData, programData, pkData] = await Promise.all([ + getStoreCircuit(`${doubleTransfer}-abi`), + getStoreCircuit(`${doubleTransfer}-program`), + getStoreCircuit(`${doubleTransfer}-pk`), + ]); + abi = abiData.data; + program = programData.data; + pk = pkData.data; + } else throw new Error('Unsupported number of commitments'); - const zokratesProvider = await initialize(); - const artifacts = { program: new Uint8Array(program), abi }; - const keypair = { pk: new Uint8Array(pk) }; - const { witness } = zokratesProvider.computeWitness(artifacts, flattenInput); - // generate proof - let { proof } = zokratesProvider.generateProof(artifacts.program, witness, keypair.pk); - proof = [...proof.a, ...proof.b, ...proof.c]; - proof = proof.flat(Infinity); - // and work out the ABI encoded data that the caller should sign and send to the shield contract - const shieldContractInstance = await getContractInstance( - SHIELD_CONTRACT_NAME, - shieldContractAddress, - ); - const optimisticTransferTransaction = new Transaction({ - fee, - historicRootBlockNumberL2: blockNumberL2s, - transactionType, - ercAddress: ZERO, - commitments: newCommitments, - nullifiers, - compressedSecrets, - proof, - }); - try { + const zokratesProvider = await initialize(); + const artifacts = { program: new Uint8Array(program), abi }; + const keypair = { pk: new Uint8Array(pk) }; + const { witness } = zokratesProvider.computeWitness(artifacts, flattenInput); + // generate proof + let { proof } = zokratesProvider.generateProof(artifacts.program, witness, keypair.pk); + proof = [...proof.a, ...proof.b, ...proof.c]; + proof = proof.flat(Infinity); + // and work out the ABI encoded data that the caller should sign and send to the shield contract + const shieldContractInstance = await getContractInstance( + SHIELD_CONTRACT_NAME, + shieldContractAddress, + ); + const optimisticTransferTransaction = new Transaction({ + fee, + historicRootBlockNumberL2: blockNumberL2s, + transactionType, + ercAddress: ZERO, + commitments: newCommitments, + nullifiers, + compressedSecrets, + proof, + }); // if (offchain) { // await axios // .post( diff --git a/wallet/src/nightfall-browser/services/withdraw.js b/wallet/src/nightfall-browser/services/withdraw.js index 6f0f05928..dc2f9a5e2 100644 --- a/wallet/src/nightfall-browser/services/withdraw.js +++ b/wallet/src/nightfall-browser/services/withdraw.js @@ -55,67 +55,67 @@ async function withdraw(withdrawParams, shieldContractAddress) { )) || [null]; if (oldCommitment) logger.debug(`Found commitment ${JSON.stringify(oldCommitment, null, 2)}`); else throw new Error('No suitable commitments were found'); // caller to handle - need to get the user to make some commitments or wait until they've been posted to the blockchain and Timber knows about them - // Having found 1 commitment, which is a suitable input to the - // proof, the next step is to compute its nullifier; - const nullifier = new Nullifier(oldCommitment, nsk); - // and the Merkle path from the commitment to the root - const commitmentTreeInfo = await getSiblingInfo(oldCommitment); - const siblingPath = generalise( - [commitmentTreeInfo.root].concat( - commitmentTreeInfo.siblingPath.path.map(p => p.value).reverse(), - ), - ); + try { + // Having found 1 commitment, which is a suitable input to the + // proof, the next step is to compute its nullifier; + const nullifier = new Nullifier(oldCommitment, nsk); + // and the Merkle path from the commitment to the root + const commitmentTreeInfo = await getSiblingInfo(oldCommitment); + const siblingPath = generalise( + [commitmentTreeInfo.root].concat( + commitmentTreeInfo.siblingPath.path.map(p => p.value).reverse(), + ), + ); - // public inputs - const { leafIndex, isOnChain } = commitmentTreeInfo; + // public inputs + const { leafIndex, isOnChain } = commitmentTreeInfo; - // now we have everything we need to create a Witness and compute a proof - const witnessInput = [ - oldCommitment.preimage.ercAddress.integer, - oldCommitment.preimage.tokenId.integer, - oldCommitment.preimage.value.integer, - { - salt: oldCommitment.preimage.salt.limbs(32, 8), - hash: oldCommitment.hash.limbs(32, 8), - ask: ask.field(BN128_GROUP_ORDER), - }, - nullifier.preimage.nsk.limbs(32, 8), - generalise(nullifier.hash.hex(32, 31)).integer, - recipientAddress.field(BN128_GROUP_ORDER), - siblingPath[0].field(BN128_GROUP_ORDER), - siblingPath.slice(1).map(node => node.field(BN128_GROUP_ORDER, false)), // siblingPAth[32] is a sha hash and will overflow a field but it's ok to take the mod here - hence the 'false' flag - leafIndex.toString(), - ]; + // now we have everything we need to create a Witness and compute a proof + const witnessInput = [ + oldCommitment.preimage.ercAddress.integer, + oldCommitment.preimage.tokenId.integer, + oldCommitment.preimage.value.integer, + { + salt: oldCommitment.preimage.salt.limbs(32, 8), + hash: oldCommitment.hash.limbs(32, 8), + ask: ask.field(BN128_GROUP_ORDER), + }, + nullifier.preimage.nsk.limbs(32, 8), + generalise(nullifier.hash.hex(32, 31)).integer, + recipientAddress.field(BN128_GROUP_ORDER), + siblingPath[0].field(BN128_GROUP_ORDER), + siblingPath.slice(1).map(node => node.field(BN128_GROUP_ORDER, false)), // siblingPAth[32] is a sha hash and will overflow a field but it's ok to take the mod here - hence the 'false' flag + leafIndex.toString(), + ]; - logger.debug(`witness input is ${JSON.stringify(witnessInput)}`); - // call a zokrates worker to generate the proof - const zokratesProvider = await initialize(); - const artifacts = { program: new Uint8Array(program), abi }; - const keypair = { pk: new Uint8Array(pk) }; - // computation - const { witness } = zokratesProvider.computeWitness(artifacts, witnessInput); - // generate proof - let { proof } = zokratesProvider.generateProof(artifacts.program, witness, keypair.pk); - proof = [...proof.a, ...proof.b, ...proof.c]; - proof = proof.flat(Infinity); - // and work out the ABI encoded data that the caller should sign and send to the shield contract - const shieldContractInstance = await getContractInstance( - SHIELD_CONTRACT_NAME, - shieldContractAddress, - ); - const optimisticWithdrawTransaction = new Transaction({ - fee, - historicRootBlockNumberL2: [isOnChain, 0], - transactionType: 3, - tokenType: withdrawParams.tokenType, - tokenId, - value, - ercAddress, - recipientAddress, - nullifiers: [nullifier], - proof, - }); - try { + logger.debug(`witness input is ${JSON.stringify(witnessInput)}`); + // call a zokrates worker to generate the proof + const zokratesProvider = await initialize(); + const artifacts = { program: new Uint8Array(program), abi }; + const keypair = { pk: new Uint8Array(pk) }; + // computation + const { witness } = zokratesProvider.computeWitness(artifacts, witnessInput); + // generate proof + let { proof } = zokratesProvider.generateProof(artifacts.program, witness, keypair.pk); + proof = [...proof.a, ...proof.b, ...proof.c]; + proof = proof.flat(Infinity); + // and work out the ABI encoded data that the caller should sign and send to the shield contract + const shieldContractInstance = await getContractInstance( + SHIELD_CONTRACT_NAME, + shieldContractAddress, + ); + const optimisticWithdrawTransaction = new Transaction({ + fee, + historicRootBlockNumberL2: [isOnChain, 0], + transactionType: 3, + tokenType: withdrawParams.tokenType, + tokenId, + value, + ercAddress, + recipientAddress, + nullifiers: [nullifier], + proof, + }); const rawTransaction = await shieldContractInstance.methods .submitTransaction(Transaction.buildSolidityStruct(optimisticWithdrawTransaction)) .encodeABI();