Merge 18-replace-envelopes-with-zk into main
48af163 parent: a9f7492 modified
CHANGELOG.md +47 -0 | @@ -56,9 +56,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | ||
| 56 | 56 | same `depositId`, so depositor and claimant are linked whenever the claim |
| 57 | 57 | happens; an envelope hides who the claimant is, not that the depositor paid |
| 58 | 58 | them. It is a bearer envelope, not a mixer. |
| 59 | +- Private transfers are now a Groth16 pool instead of hash-locked envelopes. | |
| 60 | + `Love.deposit(bytes32 commitment)` locks a fixed `DENOMINATION` of 0.01 wETH | |
| 61 | + and appends the commitment to a depth-10 Poseidon Merkle tree. | |
| 62 | + `Love.claim(pA, pB, pC, root, nullifierHash, recipient)` pays that note's LOVE | |
| 63 | + to `recipient` against a proof that the note is in the tree, without saying | |
| 64 | + which. `Deposit` and `Claim` no longer share an id, so a claim is not linked | |
| 65 | + to its deposit; the plain `deposit(uint256)` and `withdraw` are unchanged. | |
| 66 | + **Breaking:** `Love`'s creation code changed, so its CREATE2 address moves to | |
| 67 | + `0x986BA449d448912B2dC6C6C1A4E7E92512893f17` at the default salt. Instances | |
| 68 | + already deployed are immutable and unaffected. | |
| 69 | +- `Love` deploys its own `Groth16Verifier` in its constructor rather than taking | |
| 70 | + one, so the creation code stays byte-identical on every chain. | |
| 71 | +- The proof binds the `recipient`, so a front-runner who copies a claim out of | |
| 72 | + the mempool pays the recipient and nobody else. This closes the front-running | |
| 73 | + window `claim` had when it paid whoever proved the secret first. | |
| 74 | +- `docs/ZK.md` replaces `docs/PRIVACY.md`, and the README's "Privacy" section | |
| 75 | + and "Docs" list point at it. | |
| 76 | +- `foundry.toml` excludes the generated `Groth16Verifier.sol` and the vendored | |
| 77 | + `PoseidonT3.sol` from `forge fmt` and `forge lint`. | |
| 78 | + | |
| 79 | +### Removed | |
| 80 | + | |
| 81 | +- The envelope entrypoints `deposit(bytes32,uint256)`, | |
| 82 | + `deposit(bytes32[],uint256[])` and `claim(uint256,bytes32,uint256)`, with the | |
| 83 | + `Envelope` struct, `envelopes`, `nextDepositId`, the errors `UnknownDeposit`, | |
| 84 | + `ZeroSecretHash`, `InvalidBatch`, `WrongSecret` and `InsufficientRemaining`, | |
| 85 | + and the envelope `Deposit` and `Claim` events. **Breaking.** An envelope's | |
| 86 | + secret was published with its claim and its `depositId` tied the claim to the | |
| 87 | + deposit, so it was a bearer note with an anonymity set of one. | |
| 88 | +- `docs/PRIVACY.md` and `test/LoveEnvelope.t.sol`. | |
| 59 | 89 | |
| 60 | 90 | ### Added |
| 61 | 91 | |
| 92 | +- `zk/`: the `withdraw` circom circuit (Poseidon Merkle membership at depth 10, | |
| 93 | + a nullifier, and the recipient bound to the proof, about 6,100 constraints), | |
| 94 | + `pnpm setup` for the Groth16 trusted setup, and `zk/lib.mjs` for making a | |
| 95 | + note, rebuilding the tree and proving a claim. The setup has a single | |
| 96 | + contributor in each of its two phases, so it is testnet-grade only; its | |
| 97 | + outputs (`withdraw.zkey`, `withdraw.wasm`, `verification_key.json`) are | |
| 98 | + committed together with the `Groth16Verifier.sol` generated from them, and a | |
| 99 | + rerun makes a different pair. | |
| 100 | +- `src/MerkleTree.sol`, an append-only incremental Poseidon tree that keeps | |
| 101 | + every root it has had, and `src/PoseidonT3.sol`, vendored from | |
| 102 | + poseidon-solidity 0.0.5 with its entrypoint made inlinable. The tree holds | |
| 103 | + 1,024 leaves, after which `deposit(bytes32)` reverts with `TreeFull`. | |
| 104 | +- `test/LoveZK.t.sol` and `test/MerkleTree.t.sol`, run against | |
| 105 | + `test/fixtures/proofs.json`, real proofs made with the committed zkey. | |
| 106 | + `pnpm fixture` regenerates it after a new setup. | |
| 107 | +- `docs/ZK.md`, covering the workflow, what the pool hides, the trusted setup | |
| 108 | + and its limits. | |
| 62 | 109 | - `Love.deposit(bytes32[] secretHashes, uint256[] wethAmounts)`, which opens one |
| 63 | 110 | secret-locked envelope per entry in a single call, with one wETH pull and one |
| 64 | 111 | mint. A depositor can split one deposit across recipients, each with their own |
| @@ -56,9 +56,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 56 | same `depositId`, so depositor and claimant are linked whenever the claim | 56 | same `depositId`, so depositor and claimant are linked whenever the claim |
| 57 | happens; an envelope hides who the claimant is, not that the depositor paid | 57 | happens; an envelope hides who the claimant is, not that the depositor paid |
| 58 | them. It is a bearer envelope, not a mixer. | 58 | them. It is a bearer envelope, not a mixer. |
| 59 | +- Private transfers are now a Groth16 pool instead of hash-locked envelopes. | ||
| 60 | + `Love.deposit(bytes32 commitment)` locks a fixed `DENOMINATION` of 0.01 wETH | ||
| 61 | + and appends the commitment to a depth-10 Poseidon Merkle tree. | ||
| 62 | + `Love.claim(pA, pB, pC, root, nullifierHash, recipient)` pays that note's LOVE | ||
| 63 | + to `recipient` against a proof that the note is in the tree, without saying | ||
| 64 | + which. `Deposit` and `Claim` no longer share an id, so a claim is not linked | ||
| 65 | + to its deposit; the plain `deposit(uint256)` and `withdraw` are unchanged. | ||
| 66 | + **Breaking:** `Love`'s creation code changed, so its CREATE2 address moves to | ||
| 67 | + `0x986BA449d448912B2dC6C6C1A4E7E92512893f17` at the default salt. Instances | ||
| 68 | + already deployed are immutable and unaffected. | ||
| 69 | +- `Love` deploys its own `Groth16Verifier` in its constructor rather than taking | ||
| 70 | + one, so the creation code stays byte-identical on every chain. | ||
| 71 | +- The proof binds the `recipient`, so a front-runner who copies a claim out of | ||
| 72 | + the mempool pays the recipient and nobody else. This closes the front-running | ||
| 73 | + window `claim` had when it paid whoever proved the secret first. | ||
| 74 | +- `docs/ZK.md` replaces `docs/PRIVACY.md`, and the README's "Privacy" section | ||
| 75 | + and "Docs" list point at it. | ||
| 76 | +- `foundry.toml` excludes the generated `Groth16Verifier.sol` and the vendored | ||
| 77 | + `PoseidonT3.sol` from `forge fmt` and `forge lint`. | ||
| 78 | + | ||
| 79 | +### Removed | ||
| 80 | + | ||
| 81 | +- The envelope entrypoints `deposit(bytes32,uint256)`, | ||
| 82 | + `deposit(bytes32[],uint256[])` and `claim(uint256,bytes32,uint256)`, with the | ||
| 83 | + `Envelope` struct, `envelopes`, `nextDepositId`, the errors `UnknownDeposit`, | ||
| 84 | + `ZeroSecretHash`, `InvalidBatch`, `WrongSecret` and `InsufficientRemaining`, | ||
| 85 | + and the envelope `Deposit` and `Claim` events. **Breaking.** An envelope's | ||
| 86 | + secret was published with its claim and its `depositId` tied the claim to the | ||
| 87 | + deposit, so it was a bearer note with an anonymity set of one. | ||
| 88 | +- `docs/PRIVACY.md` and `test/LoveEnvelope.t.sol`. | ||
| 59 | 89 | ||
| 60 | ### Added | 90 | ### Added |
| 61 | 91 | ||
| 92 | +- `zk/`: the `withdraw` circom circuit (Poseidon Merkle membership at depth 10, | ||
| 93 | + a nullifier, and the recipient bound to the proof, about 6,100 constraints), | ||
| 94 | + `pnpm setup` for the Groth16 trusted setup, and `zk/lib.mjs` for making a | ||
| 95 | + note, rebuilding the tree and proving a claim. The setup has a single | ||
| 96 | + contributor in each of its two phases, so it is testnet-grade only; its | ||
| 97 | + outputs (`withdraw.zkey`, `withdraw.wasm`, `verification_key.json`) are | ||
| 98 | + committed together with the `Groth16Verifier.sol` generated from them, and a | ||
| 99 | + rerun makes a different pair. | ||
| 100 | +- `src/MerkleTree.sol`, an append-only incremental Poseidon tree that keeps | ||
| 101 | + every root it has had, and `src/PoseidonT3.sol`, vendored from | ||
| 102 | + poseidon-solidity 0.0.5 with its entrypoint made inlinable. The tree holds | ||
| 103 | + 1,024 leaves, after which `deposit(bytes32)` reverts with `TreeFull`. | ||
| 104 | +- `test/LoveZK.t.sol` and `test/MerkleTree.t.sol`, run against | ||
| 105 | + `test/fixtures/proofs.json`, real proofs made with the committed zkey. | ||
| 106 | + `pnpm fixture` regenerates it after a new setup. | ||
| 107 | +- `docs/ZK.md`, covering the workflow, what the pool hides, the trusted setup | ||
| 108 | + and its limits. | ||
| 62 | - `Love.deposit(bytes32[] secretHashes, uint256[] wethAmounts)`, which opens one | 109 | - `Love.deposit(bytes32[] secretHashes, uint256[] wethAmounts)`, which opens one |
| 63 | secret-locked envelope per entry in a single call, with one wETH pull and one | 110 | secret-locked envelope per entry in a single call, with one wETH pull and one |
| 64 | mint. A depositor can split one deposit across recipients, each with their own | 111 | mint. A depositor can split one deposit across recipients, each with their own |
modified
README.md +8 -7 | @@ -54,12 +54,13 @@ non-zero if the two LOVE addresses differ. Override `PORT_A`, `PORT_B`, | ||
| 54 | 54 | |
| 55 | 55 | ## Privacy |
| 56 | 56 | |
| 57 | -Besides the plain `deposit(uint256)`, LOVE can be locked behind a secret | |
| 58 | -instead of a recipient address — `deposit(bytes32 secretHash, uint256)` mints | |
| 59 | -into an escrowed envelope, and `claim(depositId, secret, loveAmount)` releases | |
| 60 | -it to whoever proves the secret. Full workflow, the off-chain requirements | |
| 61 | -that make it actually private, and the front-running window it opens: see | |
| 62 | -[docs/PRIVACY.md](docs/PRIVACY.md). | |
| 57 | +Besides the plain `deposit(uint256)`, LOVE has a private pool. `deposit(bytes32 | |
| 58 | +commitment)` locks 0.01 wETH against a note, and `claim(proof, root, | |
| 59 | +nullifierHash, recipient)` pays its LOVE to any address whose owner can prove, | |
| 60 | +in zero knowledge, that they hold a note in the pool, without saying which. The | |
| 61 | +circuit and its [Groth16](https://eprint.iacr.org/2016/260) setup live in | |
| 62 | +[`zk/`](zk/). The setup has a single contributor, so treat it as testnet-grade. | |
| 63 | +Full workflow, what the pool hides and its limits: see [docs/ZK.md](docs/ZK.md). | |
| 63 | 64 | |
| 64 | 65 | ## How wETH is chosen |
| 65 | 66 | |
| @@ -115,7 +116,7 @@ yourself: see [docs/MULTICHAIN.md](docs/MULTICHAIN.md). | ||
| 115 | 116 | ## Docs |
| 116 | 117 | |
| 117 | 118 | - [Multichain deployment](docs/MULTICHAIN.md) — CREATE2, the codehash allowlist, and verifying determinism |
| 118 | -- [Privacy](docs/PRIVACY.md) — sending LOVE by secret instead of by address | |
| 119 | +- [ZK pool](docs/ZK.md) — sending LOVE privately with Groth16 proofs, and where the trust and capacity limits are | |
| 119 | 120 | - [Supported networks](docs/SUPPORTED_NETWORKS.md) — where LOVE deploys today and what could be added |
| 120 | 121 | |
| 121 | 122 | ## License |
| @@ -54,12 +54,13 @@ non-zero if the two LOVE addresses differ. Override `PORT_A`, `PORT_B`, | |||
| 54 | 54 | ||
| 55 | ## Privacy | 55 | ## Privacy |
| 56 | 56 | ||
| 57 | -Besides the plain `deposit(uint256)`, LOVE can be locked behind a secret | 57 | +Besides the plain `deposit(uint256)`, LOVE has a private pool. `deposit(bytes32 |
| 58 | -instead of a recipient address — `deposit(bytes32 secretHash, uint256)` mints | 58 | +commitment)` locks 0.01 wETH against a note, and `claim(proof, root, |
| 59 | -into an escrowed envelope, and `claim(depositId, secret, loveAmount)` releases | 59 | +nullifierHash, recipient)` pays its LOVE to any address whose owner can prove, |
| 60 | -it to whoever proves the secret. Full workflow, the off-chain requirements | 60 | +in zero knowledge, that they hold a note in the pool, without saying which. The |
| 61 | -that make it actually private, and the front-running window it opens: see | 61 | +circuit and its [Groth16](https://eprint.iacr.org/2016/260) setup live in |
| 62 | -[docs/PRIVACY.md](docs/PRIVACY.md). | 62 | +[`zk/`](zk/). The setup has a single contributor, so treat it as testnet-grade. |
| 63 | +Full workflow, what the pool hides and its limits: see [docs/ZK.md](docs/ZK.md). | ||
| 63 | 64 | ||
| 64 | ## How wETH is chosen | 65 | ## How wETH is chosen |
| 65 | 66 | ||
| @@ -115,7 +116,7 @@ yourself: see [docs/MULTICHAIN.md](docs/MULTICHAIN.md). | |||
| 115 | ## Docs | 116 | ## Docs |
| 116 | 117 | ||
| 117 | - [Multichain deployment](docs/MULTICHAIN.md) — CREATE2, the codehash allowlist, and verifying determinism | 118 | - [Multichain deployment](docs/MULTICHAIN.md) — CREATE2, the codehash allowlist, and verifying determinism |
| 118 | -- [Privacy](docs/PRIVACY.md) — sending LOVE by secret instead of by address | 119 | +- [ZK pool](docs/ZK.md) — sending LOVE privately with Groth16 proofs, and where the trust and capacity limits are |
| 119 | - [Supported networks](docs/SUPPORTED_NETWORKS.md) — where LOVE deploys today and what could be added | 120 | - [Supported networks](docs/SUPPORTED_NETWORKS.md) — where LOVE deploys today and what could be added |
| 120 | 121 | ||
| 121 | ## License | 122 | ## License |
deleted
docs/PRIVACY.md +0 -164 | deleted file mode 100644 | ||
| @@ -1,164 +0,0 @@ | ||
| 1 | -# Privacy | |
| 2 | - | |
| 3 | -How to send LOVE by secret instead of by address, what the depositor and recipient each do, and which parts of that flow the contract cannot enforce. | |
| 4 | - | |
| 5 | -## Contents | |
| 6 | - | |
| 7 | -- [Privacy](#privacy) | |
| 8 | - - [Contents](#contents) | |
| 9 | - - [Overview](#overview) | |
| 10 | - - [The workflow](#the-workflow) | |
| 11 | - - [Code snippets](#code-snippets) | |
| 12 | - - [What's happening off-chain](#whats-happening-off-chain) | |
| 13 | - - [Bearer semantics and the front-running window](#bearer-semantics-and-the-front-running-window) | |
| 14 | - - [Splitting one deposit](#splitting-one-deposit) | |
| 15 | - - [Reference](#reference) | |
| 16 | - - [Further reading](#further-reading) | |
| 17 | - | |
| 18 | -## Overview | |
| 19 | - | |
| 20 | -[`Love`](../src/Love.sol) normally works like any [ERC-20](https://eips.ethereum.org/EIPS/eip-20): `deposit(uint256 wethAmount)` locks wETH and mints LOVE straight to the caller. A second, overloaded entrypoint, `deposit(bytes32 secretHash, uint256 wethAmount)`, mints that same LOVE into an escrowed envelope instead — unlockable by whoever can later produce the secret behind `secretHash`, not by whoever the depositor names on-chain. `claim` releases it. | |
| 21 | - | |
| 22 | -That on-chain pair is deliberately minimal: it stores a hash and moves tokens. Whether using it actually keeps a transfer private depends entirely on what the depositor and recipient do around it, off-chain, before and after those two calls. This document walks through that whole flow, on-chain and off. | |
| 23 | - | |
| 24 | -Be clear about what that buys. `Deposit` and `Claim` carry the same `depositId`, so the depositor's address and the claimant's address are linked on-chain, whenever the claim happens. What an envelope hides is who the claimant is: the depositor never names them, and the claiming address can be one nobody ties to the recipient. It is a bearer envelope, not a mixer — there is no shared pool to hide in, and no anonymity set larger than one. | |
| 25 | - | |
| 26 | -## The workflow | |
| 27 | - | |
| 28 | -1. **The depositor generates a secret — off-chain.** A random `bytes32`, produced locally with a cryptographically secure generator ([`crypto.getRandomValues`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) in a browser, or a wallet's native RNG), never sent anywhere. | |
| 29 | -2. **The depositor opens an envelope — on-chain.** `deposit(keccak256(abi.encodePacked(secret)), wethAmount)` pulls the wETH, mints `wethAmount * RATE` LOVE, and locks it under a fresh `depositId`. Only the hash goes on-chain; the secret itself never does. | |
| 30 | -3. **The depositor packages the claim code — off-chain.** `depositId` and `secret` together, typically as a URL fragment (`#depositId=...&secret=...`) or a QR code — a fragment specifically, since fragments never reach a server, unlike a query string. | |
| 31 | -4. **The depositor shares it — off-chain.** Over whatever out-of-band channel they already trust: an end-to-end encrypted chat, a QR code shown in person, a printed card. Never a channel with a persistent public log. | |
| 32 | -5. **The recipient stores it — off-chain.** As a bearer instrument: whoever holds the code can claim, and there is no recovery if it's lost. | |
| 33 | -6. **The recipient claims — on-chain.** `claim(depositId, secret, loveAmount)` proves knowledge of the secret and moves `loveAmount` LOVE from escrow to whoever called it. No address is checked, so this can be anyone who has the code — including, deliberately, someone other than the depositor's intended counterpart. The claim names the same `depositId` as the deposit, so the two stay linked on-chain; what the depositor never publishes is who the claimant is. | |
| 34 | -7. **The recipient redeems for wETH — on-chain, optional, and later.** The claimed LOVE is now an ordinary balance. `withdraw(loveAmount)`, entirely unrelated to any of the above, burns it for wETH whenever the recipient chooses. | |
| 35 | - | |
| 36 | -## Code snippets | |
| 37 | - | |
| 38 | -The off-chain steps above, in [TypeScript](https://www.typescriptlang.org/) using [ethers](https://docs.ethers.org/v6/) v6. `love` is a `Contract` instance for [`Love`](../src/Love.sol); `weth` one for the registered wETH. | |
| 39 | - | |
| 40 | -**Generating a secret.** Client-side, with the browser's CSPRNG — never a library seeded from anything predictable, and never sent to a server. | |
| 41 | - | |
| 42 | -```ts | |
| 43 | -function generateSecret(): `0x${string}` { | |
| 44 | - const bytes = new Uint8Array(32); | |
| 45 | - crypto.getRandomValues(bytes); | |
| 46 | - return ethers.hexlify(bytes) as `0x${string}`; | |
| 47 | -} | |
| 48 | -``` | |
| 49 | - | |
| 50 | -**Opening an envelope.** Hash the secret, approve the wETH, then call the secret-locked `deposit` overload — ethers needs the full signature to pick it over the plain `deposit(uint256)`. | |
| 51 | - | |
| 52 | -```ts | |
| 53 | -async function openEnvelope(wethAmount: bigint) { | |
| 54 | - const secret = generateSecret(); | |
| 55 | - const secretHash = ethers.keccak256(secret); | |
| 56 | - | |
| 57 | - await (await weth.approve(love.target, wethAmount)).wait(); | |
| 58 | - | |
| 59 | - const tx = await love.getFunction("deposit(bytes32,uint256)")(secretHash, wethAmount); | |
| 60 | - const receipt = await tx.wait(); | |
| 61 | - | |
| 62 | - const deposited = receipt.logs | |
| 63 | - .map((log: any) => love.interface.parseLog(log)) | |
| 64 | - .find((parsed: any) => parsed?.name === "Deposit" && parsed.args.length === 5); | |
| 65 | - | |
| 66 | - const depositId: bigint = deposited!.args.depositId; | |
| 67 | - return { depositId, secret }; | |
| 68 | -} | |
| 69 | -``` | |
| 70 | - | |
| 71 | -**Packaging the claim link.** A URL *fragment*, so a link-preview bot hitting the claim page server-side never sees the secret. | |
| 72 | - | |
| 73 | -```ts | |
| 74 | -function buildClaimUrl(depositId: bigint, secret: `0x${string}`): string { | |
| 75 | - const url = new URL("https://app.example/claim"); | |
| 76 | - url.hash = `depositId=${depositId}&secret=${secret}`; | |
| 77 | - return url.toString(); | |
| 78 | -} | |
| 79 | -``` | |
| 80 | - | |
| 81 | -**Parsing it back out**, on the claim page — reading `location.hash` client-side only, never anything the server saw. | |
| 82 | - | |
| 83 | -```ts | |
| 84 | -function parseClaimUrl(url: string): { depositId: bigint; secret: `0x${string}` } { | |
| 85 | - const params = new URLSearchParams(new URL(url).hash.slice(1)); | |
| 86 | - return { | |
| 87 | - depositId: BigInt(params.get("depositId")!), | |
| 88 | - secret: params.get("secret") as `0x${string}`, | |
| 89 | - }; | |
| 90 | -} | |
| 91 | -``` | |
| 92 | - | |
| 93 | -**Claiming.** | |
| 94 | - | |
| 95 | -```ts | |
| 96 | -async function claimEnvelope(depositId: bigint, secret: `0x${string}`, loveAmount: bigint) { | |
| 97 | - const tx = await love.claim(depositId, secret, loveAmount); | |
| 98 | - await tx.wait(); | |
| 99 | -} | |
| 100 | -``` | |
| 101 | - | |
| 102 | -## What's happening off-chain | |
| 103 | - | |
| 104 | -None of the steps above marked off-chain are visible to, or enforced by, the contract. They are the actual privacy mechanism; the contract just custodies funds against a hash. Six things have to go right: | |
| 105 | - | |
| 106 | -- **Generation.** The secret must never exist on a server — not for "generating a shareable link," not for anything. A secret a backend could reconstruct or log defeats the design before the first transaction. | |
| 107 | -- **Encoding.** Packing `depositId` and `secret` into a URL *fragment* rather than a query parameter means a link-preview bot, a server access log, or an analytics pixel on the claim page can't capture the secret just from the page being visited. | |
| 108 | -- **Distribution.** Privacy comes from the channel's own properties — end-to-end encryption, no third-party log — not from anything on-chain. Posting a claim link in a public Discord or a public GitHub issue undoes the whole design before anyone even claims. | |
| 109 | -- **Storage and recovery.** An unclaimed code is a bearer note. There's no on-chain notion of "identity," only "knowledge of the secret," so a lost code means permanently locked funds — the wallet holding it should treat it with the same care as a seed phrase fragment. | |
| 110 | -- **Timing discipline.** Waiting does not unlink anything: the `depositId` ties a `Claim` to its `Deposit` whenever it lands. Timing still matters for who the claimant is. A `Claim` seconds after a `Deposit`, or right after the depositor's known contact with someone, points at the owner of the claiming address. Claim when nothing else ties the claimant to the depositor's activity. | |
| 111 | -- **Operational hygiene.** No indexer should build a queryable depositId-to-claimant lookup, and no front-end should let a server "preview" a claim URL — both leak exactly the correlation this design exists to avoid. | |
| 112 | - | |
| 113 | -The full detail behind each of these — including recommendations for wallets and front-ends building on top of LOVE — is in [`docs/notes/add-privacy-to-love.md`](notes/add-privacy-to-love.md). | |
| 114 | - | |
| 115 | -## Bearer semantics and the front-running window | |
| 116 | - | |
| 117 | -`claim` has no recipient check on purpose: paying out to whoever proves the secret first is what makes the envelope a bearer instrument rather than an address-bound transfer. That same property opens a front-running window. `remaining` supports partial claims, so if a second, smaller claim is expected later, the *first* `claim`'s calldata already reveals `secret` publicly — in the mempool before confirmation, and permanently once mined. A bot watching for that calldata can submit its own `claim` for whatever is `remaining` before the intended recipient's second call lands. | |
| 118 | - | |
| 119 | -Two mitigations, both off-chain, both covered in the note above: | |
| 120 | - | |
| 121 | -- Submit `claim` through a private transaction relay (e.g. [Flashbots Protect](https://docs.flashbots.net/flashbots-protect/overview)) whenever a further partial claim is expected against the same envelope. | |
| 122 | -- Prefer a single full claim for high-value envelopes, reserving multi-claim convenience for lower-stakes amounts where the exposure matters less. | |
| 123 | - | |
| 124 | -## Splitting one deposit | |
| 125 | - | |
| 126 | -To pay several recipients from one deposit, open one envelope per recipient with `deposit(bytes32[] secretHashes, uint256[] wethAmounts)`. It takes one approval, pulls the summed wETH once, mints once, and opens an envelope per entry, each with its own secret and its own amount. Every recipient gets their own claim code and can claim exactly their share, in one full `claim`; the code for one envelope opens nothing else. | |
| 127 | - | |
| 128 | -A single secret cannot do this: whoever holds it can claim everything `remaining`, and nothing caps a claimant at their share. | |
| 129 | - | |
| 130 | -Splitting 1 wETH into 0.2 and 0.8 — ethers needs the full signature to pick this overload: | |
| 131 | - | |
| 132 | -```ts | |
| 133 | -const secrets = [generateSecret(), generateSecret()]; | |
| 134 | -const secretHashes = secrets.map((secret) => ethers.keccak256(secret)); | |
| 135 | -const wethAmounts = [ethers.parseEther("0.2"), ethers.parseEther("0.8")]; | |
| 136 | - | |
| 137 | -await (await weth.approve(love.target, ethers.parseEther("1"))).wait(); | |
| 138 | - | |
| 139 | -const tx = await love.getFunction("deposit(bytes32[],uint256[])")(secretHashes, wethAmounts); | |
| 140 | -const receipt = await tx.wait(); | |
| 141 | -``` | |
| 142 | - | |
| 143 | -The ids are consecutive, starting at `nextDepositId`, and come back in input order; read them from the `Deposit` events as in the single-envelope flow. Package each `depositId` with its own `secret` and hand each recipient only theirs. | |
| 144 | - | |
| 145 | -Splitting is a convenience, not a privacy feature. Every envelope still names the same depositor, and consecutive ids from one transaction show that the envelopes belong together. It does close the front-running window above: each recipient claims in full, once, so no partial claim ever reveals a secret that still guards funds. | |
| 146 | - | |
| 147 | -## Reference | |
| 148 | - | |
| 149 | -| Function | Effect | | |
| 150 | -|---|---| | |
| 151 | -| `deposit(uint256 wethAmount)` | Plain peg deposit — mints LOVE straight to the caller. | | |
| 152 | -| `deposit(bytes32 secretHash, uint256 wethAmount) returns (uint256 depositId)` | Opens an envelope: mints LOVE into escrow, locked behind `secretHash`. | | |
| 153 | -| `deposit(bytes32[] secretHashes, uint256[] wethAmounts) returns (uint256[] depositIds)` | Opens one envelope per entry in a single call, each with its own secret and amount. | | |
| 154 | -| `claim(uint256 depositId, bytes32 secret, uint256 loveAmount)` | Releases up to `loveAmount` LOVE from an envelope to whoever proves `secret`. | | |
| 155 | -| `withdraw(uint256 loveAmount)` | Burns the caller's own LOVE for wETH. Unrelated to envelopes; unchanged by this feature. | | |
| 156 | - | |
| 157 | -Events: `Deposit` (both overloads), `Claim`, `Withdraw`. Errors specific to envelopes: `UnknownDeposit`, `ZeroSecretHash`, `InvalidBatch`, `WrongSecret`, `InsufficientRemaining`. | |
| 158 | - | |
| 159 | -## Further reading | |
| 160 | - | |
| 161 | -- [`docs/notes/add-privacy-to-love.md`](notes/add-privacy-to-love.md) — the off-chain processes this document summarizes, in full | |
| 162 | -- [MDN: Crypto.getRandomValues()](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) | |
| 163 | -- [Flashbots Protect — private transaction relay](https://docs.flashbots.net/flashbots-protect/overview) | |
| 164 | -- [URL fragment identifier — MDN](https://developer.mozilla.org/en-US/docs/Web/URI/Fragment) | |
| deleted file mode 100644 | |||
| @@ -1,164 +0,0 @@ | |||
| 1 | -# Privacy | ||
| 2 | - | ||
| 3 | -How to send LOVE by secret instead of by address, what the depositor and recipient each do, and which parts of that flow the contract cannot enforce. | ||
| 4 | - | ||
| 5 | -## Contents | ||
| 6 | - | ||
| 7 | -- [Privacy](#privacy) | ||
| 8 | - - [Contents](#contents) | ||
| 9 | - - [Overview](#overview) | ||
| 10 | - - [The workflow](#the-workflow) | ||
| 11 | - - [Code snippets](#code-snippets) | ||
| 12 | - - [What's happening off-chain](#whats-happening-off-chain) | ||
| 13 | - - [Bearer semantics and the front-running window](#bearer-semantics-and-the-front-running-window) | ||
| 14 | - - [Splitting one deposit](#splitting-one-deposit) | ||
| 15 | - - [Reference](#reference) | ||
| 16 | - - [Further reading](#further-reading) | ||
| 17 | - | ||
| 18 | -## Overview | ||
| 19 | - | ||
| 20 | -[`Love`](../src/Love.sol) normally works like any [ERC-20](https://eips.ethereum.org/EIPS/eip-20): `deposit(uint256 wethAmount)` locks wETH and mints LOVE straight to the caller. A second, overloaded entrypoint, `deposit(bytes32 secretHash, uint256 wethAmount)`, mints that same LOVE into an escrowed envelope instead — unlockable by whoever can later produce the secret behind `secretHash`, not by whoever the depositor names on-chain. `claim` releases it. | ||
| 21 | - | ||
| 22 | -That on-chain pair is deliberately minimal: it stores a hash and moves tokens. Whether using it actually keeps a transfer private depends entirely on what the depositor and recipient do around it, off-chain, before and after those two calls. This document walks through that whole flow, on-chain and off. | ||
| 23 | - | ||
| 24 | -Be clear about what that buys. `Deposit` and `Claim` carry the same `depositId`, so the depositor's address and the claimant's address are linked on-chain, whenever the claim happens. What an envelope hides is who the claimant is: the depositor never names them, and the claiming address can be one nobody ties to the recipient. It is a bearer envelope, not a mixer — there is no shared pool to hide in, and no anonymity set larger than one. | ||
| 25 | - | ||
| 26 | -## The workflow | ||
| 27 | - | ||
| 28 | -1. **The depositor generates a secret — off-chain.** A random `bytes32`, produced locally with a cryptographically secure generator ([`crypto.getRandomValues`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) in a browser, or a wallet's native RNG), never sent anywhere. | ||
| 29 | -2. **The depositor opens an envelope — on-chain.** `deposit(keccak256(abi.encodePacked(secret)), wethAmount)` pulls the wETH, mints `wethAmount * RATE` LOVE, and locks it under a fresh `depositId`. Only the hash goes on-chain; the secret itself never does. | ||
| 30 | -3. **The depositor packages the claim code — off-chain.** `depositId` and `secret` together, typically as a URL fragment (`#depositId=...&secret=...`) or a QR code — a fragment specifically, since fragments never reach a server, unlike a query string. | ||
| 31 | -4. **The depositor shares it — off-chain.** Over whatever out-of-band channel they already trust: an end-to-end encrypted chat, a QR code shown in person, a printed card. Never a channel with a persistent public log. | ||
| 32 | -5. **The recipient stores it — off-chain.** As a bearer instrument: whoever holds the code can claim, and there is no recovery if it's lost. | ||
| 33 | -6. **The recipient claims — on-chain.** `claim(depositId, secret, loveAmount)` proves knowledge of the secret and moves `loveAmount` LOVE from escrow to whoever called it. No address is checked, so this can be anyone who has the code — including, deliberately, someone other than the depositor's intended counterpart. The claim names the same `depositId` as the deposit, so the two stay linked on-chain; what the depositor never publishes is who the claimant is. | ||
| 34 | -7. **The recipient redeems for wETH — on-chain, optional, and later.** The claimed LOVE is now an ordinary balance. `withdraw(loveAmount)`, entirely unrelated to any of the above, burns it for wETH whenever the recipient chooses. | ||
| 35 | - | ||
| 36 | -## Code snippets | ||
| 37 | - | ||
| 38 | -The off-chain steps above, in [TypeScript](https://www.typescriptlang.org/) using [ethers](https://docs.ethers.org/v6/) v6. `love` is a `Contract` instance for [`Love`](../src/Love.sol); `weth` one for the registered wETH. | ||
| 39 | - | ||
| 40 | -**Generating a secret.** Client-side, with the browser's CSPRNG — never a library seeded from anything predictable, and never sent to a server. | ||
| 41 | - | ||
| 42 | -```ts | ||
| 43 | -function generateSecret(): `0x${string}` { | ||
| 44 | - const bytes = new Uint8Array(32); | ||
| 45 | - crypto.getRandomValues(bytes); | ||
| 46 | - return ethers.hexlify(bytes) as `0x${string}`; | ||
| 47 | -} | ||
| 48 | -``` | ||
| 49 | - | ||
| 50 | -**Opening an envelope.** Hash the secret, approve the wETH, then call the secret-locked `deposit` overload — ethers needs the full signature to pick it over the plain `deposit(uint256)`. | ||
| 51 | - | ||
| 52 | -```ts | ||
| 53 | -async function openEnvelope(wethAmount: bigint) { | ||
| 54 | - const secret = generateSecret(); | ||
| 55 | - const secretHash = ethers.keccak256(secret); | ||
| 56 | - | ||
| 57 | - await (await weth.approve(love.target, wethAmount)).wait(); | ||
| 58 | - | ||
| 59 | - const tx = await love.getFunction("deposit(bytes32,uint256)")(secretHash, wethAmount); | ||
| 60 | - const receipt = await tx.wait(); | ||
| 61 | - | ||
| 62 | - const deposited = receipt.logs | ||
| 63 | - .map((log: any) => love.interface.parseLog(log)) | ||
| 64 | - .find((parsed: any) => parsed?.name === "Deposit" && parsed.args.length === 5); | ||
| 65 | - | ||
| 66 | - const depositId: bigint = deposited!.args.depositId; | ||
| 67 | - return { depositId, secret }; | ||
| 68 | -} | ||
| 69 | -``` | ||
| 70 | - | ||
| 71 | -**Packaging the claim link.** A URL *fragment*, so a link-preview bot hitting the claim page server-side never sees the secret. | ||
| 72 | - | ||
| 73 | -```ts | ||
| 74 | -function buildClaimUrl(depositId: bigint, secret: `0x${string}`): string { | ||
| 75 | - const url = new URL("https://app.example/claim"); | ||
| 76 | - url.hash = `depositId=${depositId}&secret=${secret}`; | ||
| 77 | - return url.toString(); | ||
| 78 | -} | ||
| 79 | -``` | ||
| 80 | - | ||
| 81 | -**Parsing it back out**, on the claim page — reading `location.hash` client-side only, never anything the server saw. | ||
| 82 | - | ||
| 83 | -```ts | ||
| 84 | -function parseClaimUrl(url: string): { depositId: bigint; secret: `0x${string}` } { | ||
| 85 | - const params = new URLSearchParams(new URL(url).hash.slice(1)); | ||
| 86 | - return { | ||
| 87 | - depositId: BigInt(params.get("depositId")!), | ||
| 88 | - secret: params.get("secret") as `0x${string}`, | ||
| 89 | - }; | ||
| 90 | -} | ||
| 91 | -``` | ||
| 92 | - | ||
| 93 | -**Claiming.** | ||
| 94 | - | ||
| 95 | -```ts | ||
| 96 | -async function claimEnvelope(depositId: bigint, secret: `0x${string}`, loveAmount: bigint) { | ||
| 97 | - const tx = await love.claim(depositId, secret, loveAmount); | ||
| 98 | - await tx.wait(); | ||
| 99 | -} | ||
| 100 | -``` | ||
| 101 | - | ||
| 102 | -## What's happening off-chain | ||
| 103 | - | ||
| 104 | -None of the steps above marked off-chain are visible to, or enforced by, the contract. They are the actual privacy mechanism; the contract just custodies funds against a hash. Six things have to go right: | ||
| 105 | - | ||
| 106 | -- **Generation.** The secret must never exist on a server — not for "generating a shareable link," not for anything. A secret a backend could reconstruct or log defeats the design before the first transaction. | ||
| 107 | -- **Encoding.** Packing `depositId` and `secret` into a URL *fragment* rather than a query parameter means a link-preview bot, a server access log, or an analytics pixel on the claim page can't capture the secret just from the page being visited. | ||
| 108 | -- **Distribution.** Privacy comes from the channel's own properties — end-to-end encryption, no third-party log — not from anything on-chain. Posting a claim link in a public Discord or a public GitHub issue undoes the whole design before anyone even claims. | ||
| 109 | -- **Storage and recovery.** An unclaimed code is a bearer note. There's no on-chain notion of "identity," only "knowledge of the secret," so a lost code means permanently locked funds — the wallet holding it should treat it with the same care as a seed phrase fragment. | ||
| 110 | -- **Timing discipline.** Waiting does not unlink anything: the `depositId` ties a `Claim` to its `Deposit` whenever it lands. Timing still matters for who the claimant is. A `Claim` seconds after a `Deposit`, or right after the depositor's known contact with someone, points at the owner of the claiming address. Claim when nothing else ties the claimant to the depositor's activity. | ||
| 111 | -- **Operational hygiene.** No indexer should build a queryable depositId-to-claimant lookup, and no front-end should let a server "preview" a claim URL — both leak exactly the correlation this design exists to avoid. | ||
| 112 | - | ||
| 113 | -The full detail behind each of these — including recommendations for wallets and front-ends building on top of LOVE — is in [`docs/notes/add-privacy-to-love.md`](notes/add-privacy-to-love.md). | ||
| 114 | - | ||
| 115 | -## Bearer semantics and the front-running window | ||
| 116 | - | ||
| 117 | -`claim` has no recipient check on purpose: paying out to whoever proves the secret first is what makes the envelope a bearer instrument rather than an address-bound transfer. That same property opens a front-running window. `remaining` supports partial claims, so if a second, smaller claim is expected later, the *first* `claim`'s calldata already reveals `secret` publicly — in the mempool before confirmation, and permanently once mined. A bot watching for that calldata can submit its own `claim` for whatever is `remaining` before the intended recipient's second call lands. | ||
| 118 | - | ||
| 119 | -Two mitigations, both off-chain, both covered in the note above: | ||
| 120 | - | ||
| 121 | -- Submit `claim` through a private transaction relay (e.g. [Flashbots Protect](https://docs.flashbots.net/flashbots-protect/overview)) whenever a further partial claim is expected against the same envelope. | ||
| 122 | -- Prefer a single full claim for high-value envelopes, reserving multi-claim convenience for lower-stakes amounts where the exposure matters less. | ||
| 123 | - | ||
| 124 | -## Splitting one deposit | ||
| 125 | - | ||
| 126 | -To pay several recipients from one deposit, open one envelope per recipient with `deposit(bytes32[] secretHashes, uint256[] wethAmounts)`. It takes one approval, pulls the summed wETH once, mints once, and opens an envelope per entry, each with its own secret and its own amount. Every recipient gets their own claim code and can claim exactly their share, in one full `claim`; the code for one envelope opens nothing else. | ||
| 127 | - | ||
| 128 | -A single secret cannot do this: whoever holds it can claim everything `remaining`, and nothing caps a claimant at their share. | ||
| 129 | - | ||
| 130 | -Splitting 1 wETH into 0.2 and 0.8 — ethers needs the full signature to pick this overload: | ||
| 131 | - | ||
| 132 | -```ts | ||
| 133 | -const secrets = [generateSecret(), generateSecret()]; | ||
| 134 | -const secretHashes = secrets.map((secret) => ethers.keccak256(secret)); | ||
| 135 | -const wethAmounts = [ethers.parseEther("0.2"), ethers.parseEther("0.8")]; | ||
| 136 | - | ||
| 137 | -await (await weth.approve(love.target, ethers.parseEther("1"))).wait(); | ||
| 138 | - | ||
| 139 | -const tx = await love.getFunction("deposit(bytes32[],uint256[])")(secretHashes, wethAmounts); | ||
| 140 | -const receipt = await tx.wait(); | ||
| 141 | -``` | ||
| 142 | - | ||
| 143 | -The ids are consecutive, starting at `nextDepositId`, and come back in input order; read them from the `Deposit` events as in the single-envelope flow. Package each `depositId` with its own `secret` and hand each recipient only theirs. | ||
| 144 | - | ||
| 145 | -Splitting is a convenience, not a privacy feature. Every envelope still names the same depositor, and consecutive ids from one transaction show that the envelopes belong together. It does close the front-running window above: each recipient claims in full, once, so no partial claim ever reveals a secret that still guards funds. | ||
| 146 | - | ||
| 147 | -## Reference | ||
| 148 | - | ||
| 149 | -| Function | Effect | | ||
| 150 | -|---|---| | ||
| 151 | -| `deposit(uint256 wethAmount)` | Plain peg deposit — mints LOVE straight to the caller. | | ||
| 152 | -| `deposit(bytes32 secretHash, uint256 wethAmount) returns (uint256 depositId)` | Opens an envelope: mints LOVE into escrow, locked behind `secretHash`. | | ||
| 153 | -| `deposit(bytes32[] secretHashes, uint256[] wethAmounts) returns (uint256[] depositIds)` | Opens one envelope per entry in a single call, each with its own secret and amount. | | ||
| 154 | -| `claim(uint256 depositId, bytes32 secret, uint256 loveAmount)` | Releases up to `loveAmount` LOVE from an envelope to whoever proves `secret`. | | ||
| 155 | -| `withdraw(uint256 loveAmount)` | Burns the caller's own LOVE for wETH. Unrelated to envelopes; unchanged by this feature. | | ||
| 156 | - | ||
| 157 | -Events: `Deposit` (both overloads), `Claim`, `Withdraw`. Errors specific to envelopes: `UnknownDeposit`, `ZeroSecretHash`, `InvalidBatch`, `WrongSecret`, `InsufficientRemaining`. | ||
| 158 | - | ||
| 159 | -## Further reading | ||
| 160 | - | ||
| 161 | -- [`docs/notes/add-privacy-to-love.md`](notes/add-privacy-to-love.md) — the off-chain processes this document summarizes, in full | ||
| 162 | -- [MDN: Crypto.getRandomValues()](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) | ||
| 163 | -- [Flashbots Protect — private transaction relay](https://docs.flashbots.net/flashbots-protect/overview) | ||
| 164 | -- [URL fragment identifier — MDN](https://developer.mozilla.org/en-US/docs/Web/URI/Fragment) | ||
added
docs/ZK.md +152 -0 | new file mode 100644 | ||
| @@ -0,0 +1,152 @@ | ||
| 1 | +# Private LOVE with zero-knowledge proofs | |
| 2 | + | |
| 3 | +How LOVE's Groth16 pool works, what the depositor and recipient each do, what it hides, and where its trust and capacity limits are. | |
| 4 | + | |
| 5 | +## Overview | |
| 6 | + | |
| 7 | +[`Love`](../src/Love.sol) is an [ERC-20](https://eips.ethereum.org/EIPS/eip-20) pegged to wETH. Besides the plain `deposit(uint256 wethAmount)`, it has a private pool built on a [zero-knowledge proof](https://en.wikipedia.org/wiki/Zero-knowledge_proof): | |
| 8 | + | |
| 9 | +- `deposit(bytes32 commitment)` locks a fixed `DENOMINATION` of wETH (0.01, worth 1,000 LOVE) and adds `commitment` to a [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree). | |
| 10 | +- `claim(pA, pB, pC, root, nullifierHash, recipient)` pays that much LOVE to `recipient`, given a proof that the caller knows *a* note in the tree. The proof does not say which one. | |
| 11 | + | |
| 12 | +A claim is not linked to its deposit. The contract can't tell which of the deposits in the tree a claim came from, and neither can anyone reading the chain. That is the anonymity set: every deposit under the root the claim was proved against. | |
| 13 | + | |
| 14 | +The commitment-and-nullifier scheme is the one [Tornado Cash](https://en.wikipedia.org/wiki/Tornado_Cash) popularised. The circuit is written in [circom](https://docs.circom.io/), proved and set up with [snarkjs](https://github.com/iden3/snarkjs), and uses the [Groth16](https://eprint.iacr.org/2016/260) proof system, which the EVM verifies cheaply through the alt_bn128 precompiles ([EIP-196](https://eips.ethereum.org/EIPS/eip-196), [EIP-197](https://eips.ethereum.org/EIPS/eip-197)). | |
| 15 | + | |
| 16 | +This replaces the earlier hash-locked envelopes. An envelope's `Deposit` and `Claim` shared a `depositId`, and the claim published the secret's preimage, so the two were linked on-chain by construction. A proof is what lets a claim leave that link out. | |
| 17 | + | |
| 18 | +## What it hides, and what it doesn't | |
| 19 | + | |
| 20 | +| Public | Hidden | | |
| 21 | +|---|---| | |
| 22 | +| Who deposited, and when | Which deposit a claim came from | | |
| 23 | +| That the amount is 0.01 wETH (it always is) | Who is behind the claiming address | | |
| 24 | +| Who was paid, and when | Which note a recipient holds, until they claim | | |
| 25 | +| The `nullifierHash` of a spent note | The note's `nullifier` and `secret` | | |
| 26 | + | |
| 27 | +Two things follow: | |
| 28 | + | |
| 29 | +- **Privacy grows with the pool.** With one deposit in the tree, a claim can only have come from it. The guarantee is "one of the N deposits under this root". | |
| 30 | +- **A root leaks an upper bound.** Proving against an old root says the note was deposited by then. Proving against the latest root gives the largest set. | |
| 31 | + | |
| 32 | +## The workflow | |
| 33 | + | |
| 34 | +1. **The depositor makes a note, off-chain.** `newNote()` draws a random `nullifier` and `secret`, each 248 bits, with the platform's CSPRNG. `noteHashes(note)` gives the `commitment`, `Poseidon(nullifier, secret)`. | |
| 35 | +2. **The depositor deposits, on-chain.** `deposit(commitment)` pulls 0.01 wETH, mints its LOVE into escrow with the contract, and inserts the commitment as the next leaf. Only the commitment goes on-chain. | |
| 36 | +3. **The depositor hands over the note, off-chain.** `encodeNote(note)` is one `0x` string of 124 hex digits. It is a bearer instrument: whoever holds it can claim, and there is no recovery if it is lost. Send it over a channel with no public log. | |
| 37 | +4. **The recipient rebuilds the tree, off-chain.** From the `Deposit` events, in `leafIndex` order. | |
| 38 | +5. **The recipient proves, off-chain.** `prove({ note, commitments, recipient })` produces a Groth16 proof bound to `recipient`, on the recipient's own machine. This takes a couple of seconds. | |
| 39 | +6. **Anyone claims, on-chain.** `claim(...)` verifies the proof, marks the `nullifierHash` spent, and transfers the LOVE to `recipient`. The caller can be any address, because the proof only pays `recipient`. | |
| 40 | +7. **The recipient redeems, on-chain, optional.** Claimed LOVE is an ordinary balance. `withdraw(loveAmount)` burns it for wETH whenever they choose. | |
| 41 | + | |
| 42 | +## Code snippets | |
| 43 | + | |
| 44 | +The client library is [`zk/lib.mjs`](../zk/lib.mjs). The snippets use [ethers](https://docs.ethers.org/v6/) v6, with `love` a `Contract` for [`Love`](../src/Love.sol) and `weth` one for the registered wETH. Ethers needs the full signature to pick between the overloaded `deposit` and `Deposit`. | |
| 45 | + | |
| 46 | +**Making a note and depositing.** | |
| 47 | + | |
| 48 | +```js | |
| 49 | +import { newNote, encodeNote, noteHashes } from "./zk/lib.mjs"; | |
| 50 | + | |
| 51 | +const note = newNote(); | |
| 52 | +const { commitment } = await noteHashes(note); | |
| 53 | + | |
| 54 | +const denomination = await love.DENOMINATION(); | |
| 55 | +await (await weth.approve(love.target, denomination)).wait(); | |
| 56 | +await (await love.getFunction("deposit(bytes32)")(commitment)).wait(); | |
| 57 | + | |
| 58 | +const claimCode = encodeNote(note); // hand this over, and nothing else | |
| 59 | +``` | |
| 60 | + | |
| 61 | +**Proving.** The commitments, in leaf order, are what rebuilds the tree. | |
| 62 | + | |
| 63 | +```js | |
| 64 | +import { decodeNote, prove } from "./zk/lib.mjs"; | |
| 65 | + | |
| 66 | +const logs = await love.queryFilter(love.filters["Deposit(bytes32,uint256)"]()); | |
| 67 | +const commitments = logs | |
| 68 | + .sort((a, b) => Number(a.args.leafIndex - b.args.leafIndex)) | |
| 69 | + .map((log) => log.args.commitment); | |
| 70 | + | |
| 71 | +const claim = await prove({ note: decodeNote(claimCode), commitments, recipient }); | |
| 72 | +``` | |
| 73 | + | |
| 74 | +**Claiming.** From any funded address. Using one that isn't tied to the recipient matters, as the limits below explain. | |
| 75 | + | |
| 76 | +```js | |
| 77 | +await love.claim(claim.pA, claim.pB, claim.pC, claim.root, claim.nullifierHash, claim.recipient); | |
| 78 | +``` | |
| 79 | + | |
| 80 | +`prove` rebuilds the tree from whatever `commitments` it is given, so passing a prefix proves against an older root. Some RPC providers cap the range of a `queryFilter`, in which case page through it. | |
| 81 | + | |
| 82 | +## How it works | |
| 83 | + | |
| 84 | +**The note.** `commitment = Poseidon(nullifier, secret)` and `nullifierHash = Poseidon(nullifier)`, with [Poseidon](https://www.poseidon-hash.info/) as the hash, since it is cheap inside a circuit. Publishing `nullifierHash` on claim marks the note spent without revealing which leaf it is, because nobody can go from `nullifierHash` back to `nullifier` and so to `commitment`. | |
| 85 | + | |
| 86 | +**The tree.** [`MerkleTree`](../src/MerkleTree.sol) is an append-only, incremental Poseidon tree of depth 10, so 1,024 leaves. An insert costs ten hashes and a couple of storage writes however full the tree is. Every root it has ever had stays valid, so a proof made against a slightly stale tree still lands after later deposits. [`PoseidonT3`](../src/PoseidonT3.sol) is vendored from [poseidon-solidity](https://github.com/vimwitch/poseidon-solidity), with its entrypoint changed to be inlinable and its round function untouched; its header says exactly what changed. Its output equals [circomlib](https://github.com/iden3/circomlib)'s `Poseidon(2)`, which is what [`MerkleTree.t.sol`](../test/MerkleTree.t.sol) checks against roots built by `zk/lib.mjs`. | |
| 87 | + | |
| 88 | +**The circuit.** [`withdraw.circom`](../zk/circuits/withdraw.circom) has about 6,100 constraints. | |
| 89 | + | |
| 90 | +| Input | Visibility | | | |
| 91 | +|---|---|---| | |
| 92 | +| `root` | public | A root of the tree | | |
| 93 | +| `nullifierHash` | public | Published on claim | | |
| 94 | +| `recipient` | public | Who gets paid | | |
| 95 | +| `nullifier`, `secret` | private | The note | | |
| 96 | +| `pathElements`, `pathIndices` | private | The Merkle path to the leaf | | |
| 97 | + | |
| 98 | +It proves that hashing the note gives a leaf, that the path takes that leaf to `root`, and that `nullifierHash` belongs to the note. `recipient` is bound into the proof, so a front-runner who copies a claim out of the mempool pays the recipient and nobody else. That closes the [front-running](https://en.wikipedia.org/wiki/Front_running) window the hash envelopes had. | |
| 99 | + | |
| 100 | +**The peg.** `deposit(bytes32)` mints into escrow at the same 1:1 rate as the plain deposit, and `claim` moves LOVE out of escrow instead of minting, so `totalSupply() == WETH.balanceOf(address(this)) * RATE` still holds after every call. | |
| 101 | + | |
| 102 | +## Trusted setup | |
| 103 | + | |
| 104 | +Groth16 needs a [trusted setup](https://en.wikipedia.org/wiki/Trusted_setup): anyone who learns the secret randomness it was made from can forge proofs, and so drain the pool. `zk/scripts/setup.sh` runs both phases, the circuit-independent powers of tau and the circuit-specific key, **with a single contributor each**. That is only as trustworthy as the machine it ran on and the person who ran it. | |
| 105 | + | |
| 106 | +```shell | |
| 107 | +cd zk | |
| 108 | +pnpm install | |
| 109 | +pnpm setup # about three minutes | |
| 110 | +pnpm fixture # regenerate test/fixtures/proofs.json | |
| 111 | +``` | |
| 112 | + | |
| 113 | +`pnpm setup` writes `zk/build/withdraw.zkey`, `withdraw.wasm` and `verification_key.json`, and the verifier to [`src/Groth16Verifier.sol`](../src/Groth16Verifier.sol). All of them are committed, and they belong together: a rerun makes a different key and a different verifier, so commit the outputs as a set and redeploy. The toolchain is circom 2.2.2 and snarkjs 0.7. | |
| 114 | + | |
| 115 | +This is fine for a testnet. Before it holds real value: | |
| 116 | + | |
| 117 | +- Replace phase 1 with a public, many-party powers of tau, such as the [Perpetual Powers of Tau](https://github.com/privacy-scaling-explorations/perpetualpowersoftau) files [snarkjs](https://github.com/iden3/snarkjs) can consume. | |
| 118 | +- Run phase 2 with several independent contributors. | |
| 119 | +- Have the circuit and the vendored Poseidon audited. | |
| 120 | + | |
| 121 | +## Limits | |
| 122 | + | |
| 123 | +- **Small anonymity set.** It starts at one and grows only with deposits. Below a few dozen, treat a claim as weakly private. | |
| 124 | +- **Fixed denomination.** Sending 0.05 wETH takes five notes. Free amounts would let an observer match a deposit to a claim by value. | |
| 125 | +- **1,024 deposits, ever.** The tree does not roll over. Once it is full, `deposit(bytes32)` reverts with `TreeFull`, while the plain deposit and `withdraw` carry on. A bigger tree means a new circuit, a new setup and a new deployment. | |
| 126 | +- **No relayer.** The claimant pays gas, so the submitting address needs funds. If that address is one you have used with the depositor, it links you. Use an address with no history, funded from somewhere unrelated. | |
| 127 | +- **Timing.** A claim minutes after a deposit, or right after the depositor's contact with someone, points at the owner of the claiming address. Claim when nothing else ties you to the depositor's activity. | |
| 128 | +- **Note handling.** The secret must never exist on a server: generate and prove locally, and share the claim code over a channel with no public log. Anything that lets a server preview a claim, or an indexer map notes to recipients, undoes the design. | |
| 129 | +- **Gas.** A deposit costs roughly 0.4 to 0.7 million gas, most of it the ten hashes, and a claim about 0.29 million. That is fine on an L2 and heavy on Ethereum mainnet. | |
| 130 | + | |
| 131 | +## Reference | |
| 132 | + | |
| 133 | +| Function | Effect | | |
| 134 | +|---|---| | |
| 135 | +| `deposit(uint256 wethAmount)` | Plain peg deposit: mints LOVE straight to the caller. | | |
| 136 | +| `deposit(bytes32 commitment)` | Locks `DENOMINATION` wETH and adds `commitment` to the tree. LOVE goes into escrow. | | |
| 137 | +| `claim(pA, pB, pC, root, nullifierHash, recipient)` | Pays `DENOMINATION * RATE` LOVE to `recipient` against a valid proof. | | |
| 138 | +| `withdraw(uint256 loveAmount)` | Burns the caller's own LOVE for wETH. Unrelated to the pool. | | |
| 139 | +| `isKnownRoot(root)`, `isSpent(nullifierHash)`, `nextLeafIndex()` | Read the tree and the spent set. | | |
| 140 | + | |
| 141 | +Events: `Deposit(bytes32 indexed commitment, uint256 leafIndex)` and `Deposit(address indexed account, uint256 wethAmount, uint256 loveAmount)` for the plain one, `Claim(address indexed recipient, bytes32 nullifierHash)`, `Withdraw`. | |
| 142 | + | |
| 143 | +Errors specific to the pool: `UnknownRoot`, `NoteAlreadySpent`, `InvalidProof`, `TreeFull`, `LeafNotInField`. | |
| 144 | + | |
| 145 | +## Further reading | |
| 146 | + | |
| 147 | +- [Groth16, "On the Size of Pairing-based Non-interactive Arguments"](https://eprint.iacr.org/2016/260) | |
| 148 | +- [Poseidon: a new hash function for zero-knowledge proof systems](https://eprint.iacr.org/2019/458) | |
| 149 | +- [circom documentation](https://docs.circom.io/) | |
| 150 | +- [snarkjs](https://github.com/iden3/snarkjs), including its guide to powers of tau ceremonies | |
| 151 | +- [circomlib](https://github.com/iden3/circomlib) and [circomlibjs](https://github.com/iden3/circomlibjs) | |
| 152 | +- [Foundry Book](https://book.getfoundry.sh/), for running the Solidity tests | |
| new file mode 100644 | |||
| @@ -0,0 +1,152 @@ | |||
| 1 | +# Private LOVE with zero-knowledge proofs | ||
| 2 | + | ||
| 3 | +How LOVE's Groth16 pool works, what the depositor and recipient each do, what it hides, and where its trust and capacity limits are. | ||
| 4 | + | ||
| 5 | +## Overview | ||
| 6 | + | ||
| 7 | +[`Love`](../src/Love.sol) is an [ERC-20](https://eips.ethereum.org/EIPS/eip-20) pegged to wETH. Besides the plain `deposit(uint256 wethAmount)`, it has a private pool built on a [zero-knowledge proof](https://en.wikipedia.org/wiki/Zero-knowledge_proof): | ||
| 8 | + | ||
| 9 | +- `deposit(bytes32 commitment)` locks a fixed `DENOMINATION` of wETH (0.01, worth 1,000 LOVE) and adds `commitment` to a [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree). | ||
| 10 | +- `claim(pA, pB, pC, root, nullifierHash, recipient)` pays that much LOVE to `recipient`, given a proof that the caller knows *a* note in the tree. The proof does not say which one. | ||
| 11 | + | ||
| 12 | +A claim is not linked to its deposit. The contract can't tell which of the deposits in the tree a claim came from, and neither can anyone reading the chain. That is the anonymity set: every deposit under the root the claim was proved against. | ||
| 13 | + | ||
| 14 | +The commitment-and-nullifier scheme is the one [Tornado Cash](https://en.wikipedia.org/wiki/Tornado_Cash) popularised. The circuit is written in [circom](https://docs.circom.io/), proved and set up with [snarkjs](https://github.com/iden3/snarkjs), and uses the [Groth16](https://eprint.iacr.org/2016/260) proof system, which the EVM verifies cheaply through the alt_bn128 precompiles ([EIP-196](https://eips.ethereum.org/EIPS/eip-196), [EIP-197](https://eips.ethereum.org/EIPS/eip-197)). | ||
| 15 | + | ||
| 16 | +This replaces the earlier hash-locked envelopes. An envelope's `Deposit` and `Claim` shared a `depositId`, and the claim published the secret's preimage, so the two were linked on-chain by construction. A proof is what lets a claim leave that link out. | ||
| 17 | + | ||
| 18 | +## What it hides, and what it doesn't | ||
| 19 | + | ||
| 20 | +| Public | Hidden | | ||
| 21 | +|---|---| | ||
| 22 | +| Who deposited, and when | Which deposit a claim came from | | ||
| 23 | +| That the amount is 0.01 wETH (it always is) | Who is behind the claiming address | | ||
| 24 | +| Who was paid, and when | Which note a recipient holds, until they claim | | ||
| 25 | +| The `nullifierHash` of a spent note | The note's `nullifier` and `secret` | | ||
| 26 | + | ||
| 27 | +Two things follow: | ||
| 28 | + | ||
| 29 | +- **Privacy grows with the pool.** With one deposit in the tree, a claim can only have come from it. The guarantee is "one of the N deposits under this root". | ||
| 30 | +- **A root leaks an upper bound.** Proving against an old root says the note was deposited by then. Proving against the latest root gives the largest set. | ||
| 31 | + | ||
| 32 | +## The workflow | ||
| 33 | + | ||
| 34 | +1. **The depositor makes a note, off-chain.** `newNote()` draws a random `nullifier` and `secret`, each 248 bits, with the platform's CSPRNG. `noteHashes(note)` gives the `commitment`, `Poseidon(nullifier, secret)`. | ||
| 35 | +2. **The depositor deposits, on-chain.** `deposit(commitment)` pulls 0.01 wETH, mints its LOVE into escrow with the contract, and inserts the commitment as the next leaf. Only the commitment goes on-chain. | ||
| 36 | +3. **The depositor hands over the note, off-chain.** `encodeNote(note)` is one `0x` string of 124 hex digits. It is a bearer instrument: whoever holds it can claim, and there is no recovery if it is lost. Send it over a channel with no public log. | ||
| 37 | +4. **The recipient rebuilds the tree, off-chain.** From the `Deposit` events, in `leafIndex` order. | ||
| 38 | +5. **The recipient proves, off-chain.** `prove({ note, commitments, recipient })` produces a Groth16 proof bound to `recipient`, on the recipient's own machine. This takes a couple of seconds. | ||
| 39 | +6. **Anyone claims, on-chain.** `claim(...)` verifies the proof, marks the `nullifierHash` spent, and transfers the LOVE to `recipient`. The caller can be any address, because the proof only pays `recipient`. | ||
| 40 | +7. **The recipient redeems, on-chain, optional.** Claimed LOVE is an ordinary balance. `withdraw(loveAmount)` burns it for wETH whenever they choose. | ||
| 41 | + | ||
| 42 | +## Code snippets | ||
| 43 | + | ||
| 44 | +The client library is [`zk/lib.mjs`](../zk/lib.mjs). The snippets use [ethers](https://docs.ethers.org/v6/) v6, with `love` a `Contract` for [`Love`](../src/Love.sol) and `weth` one for the registered wETH. Ethers needs the full signature to pick between the overloaded `deposit` and `Deposit`. | ||
| 45 | + | ||
| 46 | +**Making a note and depositing.** | ||
| 47 | + | ||
| 48 | +```js | ||
| 49 | +import { newNote, encodeNote, noteHashes } from "./zk/lib.mjs"; | ||
| 50 | + | ||
| 51 | +const note = newNote(); | ||
| 52 | +const { commitment } = await noteHashes(note); | ||
| 53 | + | ||
| 54 | +const denomination = await love.DENOMINATION(); | ||
| 55 | +await (await weth.approve(love.target, denomination)).wait(); | ||
| 56 | +await (await love.getFunction("deposit(bytes32)")(commitment)).wait(); | ||
| 57 | + | ||
| 58 | +const claimCode = encodeNote(note); // hand this over, and nothing else | ||
| 59 | +``` | ||
| 60 | + | ||
| 61 | +**Proving.** The commitments, in leaf order, are what rebuilds the tree. | ||
| 62 | + | ||
| 63 | +```js | ||
| 64 | +import { decodeNote, prove } from "./zk/lib.mjs"; | ||
| 65 | + | ||
| 66 | +const logs = await love.queryFilter(love.filters["Deposit(bytes32,uint256)"]()); | ||
| 67 | +const commitments = logs | ||
| 68 | + .sort((a, b) => Number(a.args.leafIndex - b.args.leafIndex)) | ||
| 69 | + .map((log) => log.args.commitment); | ||
| 70 | + | ||
| 71 | +const claim = await prove({ note: decodeNote(claimCode), commitments, recipient }); | ||
| 72 | +``` | ||
| 73 | + | ||
| 74 | +**Claiming.** From any funded address. Using one that isn't tied to the recipient matters, as the limits below explain. | ||
| 75 | + | ||
| 76 | +```js | ||
| 77 | +await love.claim(claim.pA, claim.pB, claim.pC, claim.root, claim.nullifierHash, claim.recipient); | ||
| 78 | +``` | ||
| 79 | + | ||
| 80 | +`prove` rebuilds the tree from whatever `commitments` it is given, so passing a prefix proves against an older root. Some RPC providers cap the range of a `queryFilter`, in which case page through it. | ||
| 81 | + | ||
| 82 | +## How it works | ||
| 83 | + | ||
| 84 | +**The note.** `commitment = Poseidon(nullifier, secret)` and `nullifierHash = Poseidon(nullifier)`, with [Poseidon](https://www.poseidon-hash.info/) as the hash, since it is cheap inside a circuit. Publishing `nullifierHash` on claim marks the note spent without revealing which leaf it is, because nobody can go from `nullifierHash` back to `nullifier` and so to `commitment`. | ||
| 85 | + | ||
| 86 | +**The tree.** [`MerkleTree`](../src/MerkleTree.sol) is an append-only, incremental Poseidon tree of depth 10, so 1,024 leaves. An insert costs ten hashes and a couple of storage writes however full the tree is. Every root it has ever had stays valid, so a proof made against a slightly stale tree still lands after later deposits. [`PoseidonT3`](../src/PoseidonT3.sol) is vendored from [poseidon-solidity](https://github.com/vimwitch/poseidon-solidity), with its entrypoint changed to be inlinable and its round function untouched; its header says exactly what changed. Its output equals [circomlib](https://github.com/iden3/circomlib)'s `Poseidon(2)`, which is what [`MerkleTree.t.sol`](../test/MerkleTree.t.sol) checks against roots built by `zk/lib.mjs`. | ||
| 87 | + | ||
| 88 | +**The circuit.** [`withdraw.circom`](../zk/circuits/withdraw.circom) has about 6,100 constraints. | ||
| 89 | + | ||
| 90 | +| Input | Visibility | | | ||
| 91 | +|---|---|---| | ||
| 92 | +| `root` | public | A root of the tree | | ||
| 93 | +| `nullifierHash` | public | Published on claim | | ||
| 94 | +| `recipient` | public | Who gets paid | | ||
| 95 | +| `nullifier`, `secret` | private | The note | | ||
| 96 | +| `pathElements`, `pathIndices` | private | The Merkle path to the leaf | | ||
| 97 | + | ||
| 98 | +It proves that hashing the note gives a leaf, that the path takes that leaf to `root`, and that `nullifierHash` belongs to the note. `recipient` is bound into the proof, so a front-runner who copies a claim out of the mempool pays the recipient and nobody else. That closes the [front-running](https://en.wikipedia.org/wiki/Front_running) window the hash envelopes had. | ||
| 99 | + | ||
| 100 | +**The peg.** `deposit(bytes32)` mints into escrow at the same 1:1 rate as the plain deposit, and `claim` moves LOVE out of escrow instead of minting, so `totalSupply() == WETH.balanceOf(address(this)) * RATE` still holds after every call. | ||
| 101 | + | ||
| 102 | +## Trusted setup | ||
| 103 | + | ||
| 104 | +Groth16 needs a [trusted setup](https://en.wikipedia.org/wiki/Trusted_setup): anyone who learns the secret randomness it was made from can forge proofs, and so drain the pool. `zk/scripts/setup.sh` runs both phases, the circuit-independent powers of tau and the circuit-specific key, **with a single contributor each**. That is only as trustworthy as the machine it ran on and the person who ran it. | ||
| 105 | + | ||
| 106 | +```shell | ||
| 107 | +cd zk | ||
| 108 | +pnpm install | ||
| 109 | +pnpm setup # about three minutes | ||
| 110 | +pnpm fixture # regenerate test/fixtures/proofs.json | ||
| 111 | +``` | ||
| 112 | + | ||
| 113 | +`pnpm setup` writes `zk/build/withdraw.zkey`, `withdraw.wasm` and `verification_key.json`, and the verifier to [`src/Groth16Verifier.sol`](../src/Groth16Verifier.sol). All of them are committed, and they belong together: a rerun makes a different key and a different verifier, so commit the outputs as a set and redeploy. The toolchain is circom 2.2.2 and snarkjs 0.7. | ||
| 114 | + | ||
| 115 | +This is fine for a testnet. Before it holds real value: | ||
| 116 | + | ||
| 117 | +- Replace phase 1 with a public, many-party powers of tau, such as the [Perpetual Powers of Tau](https://github.com/privacy-scaling-explorations/perpetualpowersoftau) files [snarkjs](https://github.com/iden3/snarkjs) can consume. | ||
| 118 | +- Run phase 2 with several independent contributors. | ||
| 119 | +- Have the circuit and the vendored Poseidon audited. | ||
| 120 | + | ||
| 121 | +## Limits | ||
| 122 | + | ||
| 123 | +- **Small anonymity set.** It starts at one and grows only with deposits. Below a few dozen, treat a claim as weakly private. | ||
| 124 | +- **Fixed denomination.** Sending 0.05 wETH takes five notes. Free amounts would let an observer match a deposit to a claim by value. | ||
| 125 | +- **1,024 deposits, ever.** The tree does not roll over. Once it is full, `deposit(bytes32)` reverts with `TreeFull`, while the plain deposit and `withdraw` carry on. A bigger tree means a new circuit, a new setup and a new deployment. | ||
| 126 | +- **No relayer.** The claimant pays gas, so the submitting address needs funds. If that address is one you have used with the depositor, it links you. Use an address with no history, funded from somewhere unrelated. | ||
| 127 | +- **Timing.** A claim minutes after a deposit, or right after the depositor's contact with someone, points at the owner of the claiming address. Claim when nothing else ties you to the depositor's activity. | ||
| 128 | +- **Note handling.** The secret must never exist on a server: generate and prove locally, and share the claim code over a channel with no public log. Anything that lets a server preview a claim, or an indexer map notes to recipients, undoes the design. | ||
| 129 | +- **Gas.** A deposit costs roughly 0.4 to 0.7 million gas, most of it the ten hashes, and a claim about 0.29 million. That is fine on an L2 and heavy on Ethereum mainnet. | ||
| 130 | + | ||
| 131 | +## Reference | ||
| 132 | + | ||
| 133 | +| Function | Effect | | ||
| 134 | +|---|---| | ||
| 135 | +| `deposit(uint256 wethAmount)` | Plain peg deposit: mints LOVE straight to the caller. | | ||
| 136 | +| `deposit(bytes32 commitment)` | Locks `DENOMINATION` wETH and adds `commitment` to the tree. LOVE goes into escrow. | | ||
| 137 | +| `claim(pA, pB, pC, root, nullifierHash, recipient)` | Pays `DENOMINATION * RATE` LOVE to `recipient` against a valid proof. | | ||
| 138 | +| `withdraw(uint256 loveAmount)` | Burns the caller's own LOVE for wETH. Unrelated to the pool. | | ||
| 139 | +| `isKnownRoot(root)`, `isSpent(nullifierHash)`, `nextLeafIndex()` | Read the tree and the spent set. | | ||
| 140 | + | ||
| 141 | +Events: `Deposit(bytes32 indexed commitment, uint256 leafIndex)` and `Deposit(address indexed account, uint256 wethAmount, uint256 loveAmount)` for the plain one, `Claim(address indexed recipient, bytes32 nullifierHash)`, `Withdraw`. | ||
| 142 | + | ||
| 143 | +Errors specific to the pool: `UnknownRoot`, `NoteAlreadySpent`, `InvalidProof`, `TreeFull`, `LeafNotInField`. | ||
| 144 | + | ||
| 145 | +## Further reading | ||
| 146 | + | ||
| 147 | +- [Groth16, "On the Size of Pairing-based Non-interactive Arguments"](https://eprint.iacr.org/2016/260) | ||
| 148 | +- [Poseidon: a new hash function for zero-knowledge proof systems](https://eprint.iacr.org/2019/458) | ||
| 149 | +- [circom documentation](https://docs.circom.io/) | ||
| 150 | +- [snarkjs](https://github.com/iden3/snarkjs), including its guide to powers of tau ceremonies | ||
| 151 | +- [circomlib](https://github.com/iden3/circomlib) and [circomlibjs](https://github.com/iden3/circomlibjs) | ||
| 152 | +- [Foundry Book](https://book.getfoundry.sh/), for running the Solidity tests | ||
modified
foundry.toml +4 -0 | @@ -40,6 +40,10 @@ quote_style = "double" | ||
| 40 | 40 | number_underscore = "thousands" |
| 41 | 41 | multiline_func_header = "attributes_first" |
| 42 | 42 | sort_imports = true |
| 43 | +ignore = ["src/Groth16Verifier.sol", "src/PoseidonT3.sol"] | |
| 44 | + | |
| 45 | +[lint] | |
| 46 | +ignore = ["src/Groth16Verifier.sol", "src/PoseidonT3.sol"] | |
| 43 | 47 | |
| 44 | 48 | [rpc_endpoints] |
| 45 | 49 | base_sepolia = "${BASE_SEPOLIA_RPC_URL}" |
| @@ -40,6 +40,10 @@ quote_style = "double" | |||
| 40 | number_underscore = "thousands" | 40 | number_underscore = "thousands" |
| 41 | multiline_func_header = "attributes_first" | 41 | multiline_func_header = "attributes_first" |
| 42 | sort_imports = true | 42 | sort_imports = true |
| 43 | +ignore = ["src/Groth16Verifier.sol", "src/PoseidonT3.sol"] | ||
| 44 | + | ||
| 45 | +[lint] | ||
| 46 | +ignore = ["src/Groth16Verifier.sol", "src/PoseidonT3.sol"] | ||
| 43 | 47 | ||
| 44 | [rpc_endpoints] | 48 | [rpc_endpoints] |
| 45 | base_sepolia = "${BASE_SEPOLIA_RPC_URL}" | 49 | base_sepolia = "${BASE_SEPOLIA_RPC_URL}" |
added
src/Groth16Verifier.sol +182 -0 | new file mode 100644 | ||
| @@ -0,0 +1,182 @@ | ||
| 1 | +// SPDX-License-Identifier: GPL-3.0 | |
| 2 | +/* | |
| 3 | + Copyright 2021 0KIMS association. | |
| 4 | + | |
| 5 | + This file is generated with [snarkJS](https://github.com/iden3/snarkjs). | |
| 6 | + | |
| 7 | + snarkJS is a free software: you can redistribute it and/or modify it | |
| 8 | + under the terms of the GNU General Public License as published by | |
| 9 | + the Free Software Foundation, either version 3 of the License, or | |
| 10 | + (at your option) any later version. | |
| 11 | + | |
| 12 | + snarkJS is distributed in the hope that it will be useful, but WITHOUT | |
| 13 | + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
| 14 | + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public | |
| 15 | + License for more details. | |
| 16 | + | |
| 17 | + You should have received a copy of the GNU General Public License | |
| 18 | + along with snarkJS. If not, see <https://www.gnu.org/licenses/>. | |
| 19 | +*/ | |
| 20 | + | |
| 21 | +pragma solidity >=0.7.0 <0.9.0; | |
| 22 | + | |
| 23 | +contract Groth16Verifier { | |
| 24 | + // Scalar field size | |
| 25 | + uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; | |
| 26 | + // Base field size | |
| 27 | + uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; | |
| 28 | + | |
| 29 | + // Verification Key data | |
| 30 | + uint256 constant alphax = 2723532631684974918406645525943558732354673276204281662712066914695900576277; | |
| 31 | + uint256 constant alphay = 17514683612750309974728178682926232556971957023838913535669239847332050299446; | |
| 32 | + uint256 constant betax1 = 7823948186918456580333006997609901789595273233568743751946614638907978883711; | |
| 33 | + uint256 constant betax2 = 6034205146605614654431717757495133361402453255257043866720288300974269646538; | |
| 34 | + uint256 constant betay1 = 11638984221817658074004925648741757789613528713645236774002925369248027221209; | |
| 35 | + uint256 constant betay2 = 2508951487354739574077255816074521971727349482553381458702706323318855000618; | |
| 36 | + uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; | |
| 37 | + uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; | |
| 38 | + uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; | |
| 39 | + uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; | |
| 40 | + uint256 constant deltax1 = 3163504613947300382025047233129943754368165108353942835225928277874778583773; | |
| 41 | + uint256 constant deltax2 = 10111709658227999298687264682571617846867694365717728896787905680829339798912; | |
| 42 | + uint256 constant deltay1 = 1104241400641344221595907754664029095916435027517876883451581136875208434535; | |
| 43 | + uint256 constant deltay2 = 8588597490669533320277373248052240038745202137023885169306697052179170691253; | |
| 44 | + | |
| 45 | + | |
| 46 | + uint256 constant IC0x = 4212190150813188043278099338020867811573069332260264154238648301447210039371; | |
| 47 | + uint256 constant IC0y = 1764764369750003778637898487216316888935235560815878582035231064669751096562; | |
| 48 | + | |
| 49 | + uint256 constant IC1x = 11591500916652557381190324906157840239104175179480966128102692187195568131210; | |
| 50 | + uint256 constant IC1y = 5118730949595120121305886043182374039722377242808578639452659793403109061323; | |
| 51 | + | |
| 52 | + uint256 constant IC2x = 17181191618870716694328767264500886479216739164760510841062537495596168566180; | |
| 53 | + uint256 constant IC2y = 6745348094417067550528385806914234773573246785057338772569961117791460595409; | |
| 54 | + | |
| 55 | + uint256 constant IC3x = 17245225309580886468417139451106822105824195874584748226991661740342948111559; | |
| 56 | + uint256 constant IC3y = 9416262901358787120529716248550998086567235238905571238886470859668393020882; | |
| 57 | + | |
| 58 | + | |
| 59 | + // Memory data | |
| 60 | + uint16 constant pVk = 0; | |
| 61 | + uint16 constant pPairing = 128; | |
| 62 | + | |
| 63 | + uint16 constant pLastMem = 896; | |
| 64 | + | |
| 65 | + function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[3] calldata _pubSignals) public view returns (bool) { | |
| 66 | + assembly { | |
| 67 | + function checkField(v) { | |
| 68 | + if iszero(lt(v, r)) { | |
| 69 | + mstore(0, 0) | |
| 70 | + return(0, 0x20) | |
| 71 | + } | |
| 72 | + } | |
| 73 | + | |
| 74 | + // G1 function to multiply a G1 value(x,y) to value in an address | |
| 75 | + function g1_mulAccC(pR, x, y, s) { | |
| 76 | + let success | |
| 77 | + let mIn := mload(0x40) | |
| 78 | + mstore(mIn, x) | |
| 79 | + mstore(add(mIn, 32), y) | |
| 80 | + mstore(add(mIn, 64), s) | |
| 81 | + | |
| 82 | + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) | |
| 83 | + | |
| 84 | + if iszero(success) { | |
| 85 | + mstore(0, 0) | |
| 86 | + return(0, 0x20) | |
| 87 | + } | |
| 88 | + | |
| 89 | + mstore(add(mIn, 64), mload(pR)) | |
| 90 | + mstore(add(mIn, 96), mload(add(pR, 32))) | |
| 91 | + | |
| 92 | + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) | |
| 93 | + | |
| 94 | + if iszero(success) { | |
| 95 | + mstore(0, 0) | |
| 96 | + return(0, 0x20) | |
| 97 | + } | |
| 98 | + } | |
| 99 | + | |
| 100 | + function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk { | |
| 101 | + let _pPairing := add(pMem, pPairing) | |
| 102 | + let _pVk := add(pMem, pVk) | |
| 103 | + | |
| 104 | + mstore(_pVk, IC0x) | |
| 105 | + mstore(add(_pVk, 32), IC0y) | |
| 106 | + | |
| 107 | + // Compute the linear combination vk_x | |
| 108 | + | |
| 109 | + g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0))) | |
| 110 | + | |
| 111 | + g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32))) | |
| 112 | + | |
| 113 | + g1_mulAccC(_pVk, IC3x, IC3y, calldataload(add(pubSignals, 64))) | |
| 114 | + | |
| 115 | + | |
| 116 | + // -A | |
| 117 | + mstore(_pPairing, calldataload(pA)) | |
| 118 | + mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) | |
| 119 | + | |
| 120 | + // B | |
| 121 | + mstore(add(_pPairing, 64), calldataload(pB)) | |
| 122 | + mstore(add(_pPairing, 96), calldataload(add(pB, 32))) | |
| 123 | + mstore(add(_pPairing, 128), calldataload(add(pB, 64))) | |
| 124 | + mstore(add(_pPairing, 160), calldataload(add(pB, 96))) | |
| 125 | + | |
| 126 | + // alpha1 | |
| 127 | + mstore(add(_pPairing, 192), alphax) | |
| 128 | + mstore(add(_pPairing, 224), alphay) | |
| 129 | + | |
| 130 | + // beta2 | |
| 131 | + mstore(add(_pPairing, 256), betax1) | |
| 132 | + mstore(add(_pPairing, 288), betax2) | |
| 133 | + mstore(add(_pPairing, 320), betay1) | |
| 134 | + mstore(add(_pPairing, 352), betay2) | |
| 135 | + | |
| 136 | + // vk_x | |
| 137 | + mstore(add(_pPairing, 384), mload(add(pMem, pVk))) | |
| 138 | + mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) | |
| 139 | + | |
| 140 | + | |
| 141 | + // gamma2 | |
| 142 | + mstore(add(_pPairing, 448), gammax1) | |
| 143 | + mstore(add(_pPairing, 480), gammax2) | |
| 144 | + mstore(add(_pPairing, 512), gammay1) | |
| 145 | + mstore(add(_pPairing, 544), gammay2) | |
| 146 | + | |
| 147 | + // C | |
| 148 | + mstore(add(_pPairing, 576), calldataload(pC)) | |
| 149 | + mstore(add(_pPairing, 608), calldataload(add(pC, 32))) | |
| 150 | + | |
| 151 | + // delta2 | |
| 152 | + mstore(add(_pPairing, 640), deltax1) | |
| 153 | + mstore(add(_pPairing, 672), deltax2) | |
| 154 | + mstore(add(_pPairing, 704), deltay1) | |
| 155 | + mstore(add(_pPairing, 736), deltay2) | |
| 156 | + | |
| 157 | + | |
| 158 | + let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) | |
| 159 | + | |
| 160 | + isOk := and(success, mload(_pPairing)) | |
| 161 | + } | |
| 162 | + | |
| 163 | + let pMem := mload(0x40) | |
| 164 | + mstore(0x40, add(pMem, pLastMem)) | |
| 165 | + | |
| 166 | + // Validate that all evaluations ∈ F | |
| 167 | + | |
| 168 | + checkField(calldataload(add(_pubSignals, 0))) | |
| 169 | + | |
| 170 | + checkField(calldataload(add(_pubSignals, 32))) | |
| 171 | + | |
| 172 | + checkField(calldataload(add(_pubSignals, 64))) | |
| 173 | + | |
| 174 | + | |
| 175 | + // Validate all evaluations | |
| 176 | + let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) | |
| 177 | + | |
| 178 | + mstore(0, isValid) | |
| 179 | + return(0, 0x20) | |
| 180 | + } | |
| 181 | + } | |
| 182 | + } | |
| new file mode 100644 | |||
| @@ -0,0 +1,182 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-3.0 | ||
| 2 | +/* | ||
| 3 | + Copyright 2021 0KIMS association. | ||
| 4 | + | ||
| 5 | + This file is generated with [snarkJS](https://github.com/iden3/snarkjs). | ||
| 6 | + | ||
| 7 | + snarkJS is a free software: you can redistribute it and/or modify it | ||
| 8 | + under the terms of the GNU General Public License as published by | ||
| 9 | + the Free Software Foundation, either version 3 of the License, or | ||
| 10 | + (at your option) any later version. | ||
| 11 | + | ||
| 12 | + snarkJS is distributed in the hope that it will be useful, but WITHOUT | ||
| 13 | + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | ||
| 14 | + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public | ||
| 15 | + License for more details. | ||
| 16 | + | ||
| 17 | + You should have received a copy of the GNU General Public License | ||
| 18 | + along with snarkJS. If not, see <https://www.gnu.org/licenses/>. | ||
| 19 | +*/ | ||
| 20 | + | ||
| 21 | +pragma solidity >=0.7.0 <0.9.0; | ||
| 22 | + | ||
| 23 | +contract Groth16Verifier { | ||
| 24 | + // Scalar field size | ||
| 25 | + uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; | ||
| 26 | + // Base field size | ||
| 27 | + uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; | ||
| 28 | + | ||
| 29 | + // Verification Key data | ||
| 30 | + uint256 constant alphax = 2723532631684974918406645525943558732354673276204281662712066914695900576277; | ||
| 31 | + uint256 constant alphay = 17514683612750309974728178682926232556971957023838913535669239847332050299446; | ||
| 32 | + uint256 constant betax1 = 7823948186918456580333006997609901789595273233568743751946614638907978883711; | ||
| 33 | + uint256 constant betax2 = 6034205146605614654431717757495133361402453255257043866720288300974269646538; | ||
| 34 | + uint256 constant betay1 = 11638984221817658074004925648741757789613528713645236774002925369248027221209; | ||
| 35 | + uint256 constant betay2 = 2508951487354739574077255816074521971727349482553381458702706323318855000618; | ||
| 36 | + uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; | ||
| 37 | + uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; | ||
| 38 | + uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; | ||
| 39 | + uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; | ||
| 40 | + uint256 constant deltax1 = 3163504613947300382025047233129943754368165108353942835225928277874778583773; | ||
| 41 | + uint256 constant deltax2 = 10111709658227999298687264682571617846867694365717728896787905680829339798912; | ||
| 42 | + uint256 constant deltay1 = 1104241400641344221595907754664029095916435027517876883451581136875208434535; | ||
| 43 | + uint256 constant deltay2 = 8588597490669533320277373248052240038745202137023885169306697052179170691253; | ||
| 44 | + | ||
| 45 | + | ||
| 46 | + uint256 constant IC0x = 4212190150813188043278099338020867811573069332260264154238648301447210039371; | ||
| 47 | + uint256 constant IC0y = 1764764369750003778637898487216316888935235560815878582035231064669751096562; | ||
| 48 | + | ||
| 49 | + uint256 constant IC1x = 11591500916652557381190324906157840239104175179480966128102692187195568131210; | ||
| 50 | + uint256 constant IC1y = 5118730949595120121305886043182374039722377242808578639452659793403109061323; | ||
| 51 | + | ||
| 52 | + uint256 constant IC2x = 17181191618870716694328767264500886479216739164760510841062537495596168566180; | ||
| 53 | + uint256 constant IC2y = 6745348094417067550528385806914234773573246785057338772569961117791460595409; | ||
| 54 | + | ||
| 55 | + uint256 constant IC3x = 17245225309580886468417139451106822105824195874584748226991661740342948111559; | ||
| 56 | + uint256 constant IC3y = 9416262901358787120529716248550998086567235238905571238886470859668393020882; | ||
| 57 | + | ||
| 58 | + | ||
| 59 | + // Memory data | ||
| 60 | + uint16 constant pVk = 0; | ||
| 61 | + uint16 constant pPairing = 128; | ||
| 62 | + | ||
| 63 | + uint16 constant pLastMem = 896; | ||
| 64 | + | ||
| 65 | + function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[3] calldata _pubSignals) public view returns (bool) { | ||
| 66 | + assembly { | ||
| 67 | + function checkField(v) { | ||
| 68 | + if iszero(lt(v, r)) { | ||
| 69 | + mstore(0, 0) | ||
| 70 | + return(0, 0x20) | ||
| 71 | + } | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + // G1 function to multiply a G1 value(x,y) to value in an address | ||
| 75 | + function g1_mulAccC(pR, x, y, s) { | ||
| 76 | + let success | ||
| 77 | + let mIn := mload(0x40) | ||
| 78 | + mstore(mIn, x) | ||
| 79 | + mstore(add(mIn, 32), y) | ||
| 80 | + mstore(add(mIn, 64), s) | ||
| 81 | + | ||
| 82 | + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) | ||
| 83 | + | ||
| 84 | + if iszero(success) { | ||
| 85 | + mstore(0, 0) | ||
| 86 | + return(0, 0x20) | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + mstore(add(mIn, 64), mload(pR)) | ||
| 90 | + mstore(add(mIn, 96), mload(add(pR, 32))) | ||
| 91 | + | ||
| 92 | + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) | ||
| 93 | + | ||
| 94 | + if iszero(success) { | ||
| 95 | + mstore(0, 0) | ||
| 96 | + return(0, 0x20) | ||
| 97 | + } | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk { | ||
| 101 | + let _pPairing := add(pMem, pPairing) | ||
| 102 | + let _pVk := add(pMem, pVk) | ||
| 103 | + | ||
| 104 | + mstore(_pVk, IC0x) | ||
| 105 | + mstore(add(_pVk, 32), IC0y) | ||
| 106 | + | ||
| 107 | + // Compute the linear combination vk_x | ||
| 108 | + | ||
| 109 | + g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0))) | ||
| 110 | + | ||
| 111 | + g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32))) | ||
| 112 | + | ||
| 113 | + g1_mulAccC(_pVk, IC3x, IC3y, calldataload(add(pubSignals, 64))) | ||
| 114 | + | ||
| 115 | + | ||
| 116 | + // -A | ||
| 117 | + mstore(_pPairing, calldataload(pA)) | ||
| 118 | + mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) | ||
| 119 | + | ||
| 120 | + // B | ||
| 121 | + mstore(add(_pPairing, 64), calldataload(pB)) | ||
| 122 | + mstore(add(_pPairing, 96), calldataload(add(pB, 32))) | ||
| 123 | + mstore(add(_pPairing, 128), calldataload(add(pB, 64))) | ||
| 124 | + mstore(add(_pPairing, 160), calldataload(add(pB, 96))) | ||
| 125 | + | ||
| 126 | + // alpha1 | ||
| 127 | + mstore(add(_pPairing, 192), alphax) | ||
| 128 | + mstore(add(_pPairing, 224), alphay) | ||
| 129 | + | ||
| 130 | + // beta2 | ||
| 131 | + mstore(add(_pPairing, 256), betax1) | ||
| 132 | + mstore(add(_pPairing, 288), betax2) | ||
| 133 | + mstore(add(_pPairing, 320), betay1) | ||
| 134 | + mstore(add(_pPairing, 352), betay2) | ||
| 135 | + | ||
| 136 | + // vk_x | ||
| 137 | + mstore(add(_pPairing, 384), mload(add(pMem, pVk))) | ||
| 138 | + mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) | ||
| 139 | + | ||
| 140 | + | ||
| 141 | + // gamma2 | ||
| 142 | + mstore(add(_pPairing, 448), gammax1) | ||
| 143 | + mstore(add(_pPairing, 480), gammax2) | ||
| 144 | + mstore(add(_pPairing, 512), gammay1) | ||
| 145 | + mstore(add(_pPairing, 544), gammay2) | ||
| 146 | + | ||
| 147 | + // C | ||
| 148 | + mstore(add(_pPairing, 576), calldataload(pC)) | ||
| 149 | + mstore(add(_pPairing, 608), calldataload(add(pC, 32))) | ||
| 150 | + | ||
| 151 | + // delta2 | ||
| 152 | + mstore(add(_pPairing, 640), deltax1) | ||
| 153 | + mstore(add(_pPairing, 672), deltax2) | ||
| 154 | + mstore(add(_pPairing, 704), deltay1) | ||
| 155 | + mstore(add(_pPairing, 736), deltay2) | ||
| 156 | + | ||
| 157 | + | ||
| 158 | + let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) | ||
| 159 | + | ||
| 160 | + isOk := and(success, mload(_pPairing)) | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + let pMem := mload(0x40) | ||
| 164 | + mstore(0x40, add(pMem, pLastMem)) | ||
| 165 | + | ||
| 166 | + // Validate that all evaluations ∈ F | ||
| 167 | + | ||
| 168 | + checkField(calldataload(add(_pubSignals, 0))) | ||
| 169 | + | ||
| 170 | + checkField(calldataload(add(_pubSignals, 32))) | ||
| 171 | + | ||
| 172 | + checkField(calldataload(add(_pubSignals, 64))) | ||
| 173 | + | ||
| 174 | + | ||
| 175 | + // Validate all evaluations | ||
| 176 | + let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) | ||
| 177 | + | ||
| 178 | + mstore(0, isValid) | ||
| 179 | + return(0, 0x20) | ||
| 180 | + } | ||
| 181 | + } | ||
| 182 | + } | ||
modified
src/Love.sol +84 -141 | @@ -1,7 +1,9 @@ | ||
| 1 | 1 | // SPDX-License-Identifier: MIT |
| 2 | 2 | pragma solidity ^0.8.30; |
| 3 | 3 | |
| 4 | +import {Groth16Verifier} from "./Groth16Verifier.sol"; | |
| 4 | 5 | import {IWETH} from "./IWETH.sol"; |
| 6 | +import {MerkleTree} from "./MerkleTree.sol"; | |
| 5 | 7 | import {WETHRegistry} from "./WETHRegistry.sol"; |
| 6 | 8 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; |
| 7 | 9 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
| @@ -13,11 +15,17 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol | ||
| 13 | 15 | /// Every LOVE in circulation is backed by wETH held by this contract: |
| 14 | 16 | /// supply only moves through `deposit` and `withdraw`, and both are |
| 15 | 17 | /// permissionless. |
| 18 | +/// | |
| 19 | +/// Besides the plain `deposit(uint256)`, LOVE can be sent privately: | |
| 20 | +/// `deposit(bytes32)` locks a fixed `DENOMINATION` of wETH against a | |
| 21 | +/// note's commitment, and `claim` pays that much LOVE to any address | |
| 22 | +/// whose owner can prove, in zero knowledge, that they hold *a* note in | |
| 23 | +/// the pool — not which one. See docs/ZK.md. | |
| 16 | 24 | /// @dev There is no mint entrypoint, no owner and no upgrade path, so the peg |
| 17 | 25 | /// cannot be diluted. `totalSupply() == WETH.balanceOf(address(this)) * RATE` |
| 18 | 26 | /// holds after every call; wETH transferred straight to this contract |
| 19 | 27 | /// raises the backing and is not redeemable. |
| 20 | -contract Love is ERC20 { | |
| 28 | +contract Love is ERC20, MerkleTree { | |
| 21 | 29 | using SafeERC20 for IERC20; |
| 22 | 30 | |
| 23 | 31 | /// @notice LOVE minted per unit of wETH, and burned per unit released. |
| @@ -42,20 +50,19 @@ contract Love is ERC20 { | ||
| 42 | 50 | /// is itself write-once, so nothing can move the token under the peg. |
| 43 | 51 | IERC20 public immutable WETH; |
| 44 | 52 | |
| 45 | - /// @notice One secret-locked deposit: a hash to unlock it, and what's left of it. | |
| 46 | - /// @dev Opened by the `deposit(bytes32,uint256)` overload; see | |
| 47 | - /// docs/notes/add-privacy-to-love.md for the off-chain processes | |
| 48 | - /// (secret generation, sharing, timing discipline) this depends on. | |
| 49 | - struct Envelope { | |
| 50 | - bytes32 secretHash; | |
| 51 | - uint256 remaining; | |
| 52 | - } | |
| 53 | + /// @notice The wETH one private note is worth, and the only amount `deposit(bytes32)` takes. | |
| 54 | + /// @dev Fixed so that amounts tell an observer nothing about which deposit a | |
| 55 | + /// claim came from. Sending 0.05 wETH takes five notes. | |
| 56 | + uint256 public constant DENOMINATION = 0.01 ether; | |
| 53 | 57 | |
| 54 | - /// @notice Envelope storage, by `depositId`. | |
| 55 | - mapping(uint256 => Envelope) public envelopes; | |
| 58 | + /// @notice Checks the proofs `claim` is given. | |
| 59 | + /// @dev Created here rather than passed in, so it lands at an address that | |
| 60 | + /// depends only on this contract's, and the creation code stays | |
| 61 | + /// byte-identical on every chain. | |
| 62 | + Groth16Verifier public immutable VERIFIER; | |
| 56 | 63 | |
| 57 | - /// @notice The `depositId` the next secret-locked `deposit` call will be assigned. | |
| 58 | - uint256 public nextDepositId; | |
| 64 | + /// @notice Whether a note's nullifier has been published, and so the note spent. | |
| 65 | + mapping(bytes32 nullifierHash => bool) public isSpent; | |
| 59 | 66 | |
| 60 | 67 | /// @notice Thrown when a withdrawal amount is not a multiple of `RATE`. |
| 61 | 68 | /// @param loveAmount The rejected LOVE amount. |
| @@ -70,30 +77,16 @@ contract Love is ERC20 { | ||
| 70 | 77 | /// @param registry The registry that was asked. |
| 71 | 78 | error WethNotRegistered(address registry); |
| 72 | 79 | |
| 73 | - /// @notice Thrown when `depositId` has never been deposited into. | |
| 74 | - /// @param depositId The id that doesn't exist. | |
| 75 | - error UnknownDeposit(uint256 depositId); | |
| 76 | - | |
| 77 | - /// @notice Thrown when a privacy deposit is attempted with a zero `secretHash`. | |
| 78 | - /// @dev `claim` uses `secretHash == bytes32(0)` as its "envelope doesn't | |
| 79 | - /// exist" sentinel, so a zero hash would otherwise create an envelope | |
| 80 | - /// that can never be claimed. | |
| 81 | - error ZeroSecretHash(); | |
| 80 | + /// @notice Thrown when a claim is proved against a root the tree never had. | |
| 81 | + /// @param root The unknown root. | |
| 82 | + error UnknownRoot(bytes32 root); | |
| 82 | 83 | |
| 83 | - /// @notice Thrown when a batch deposit has no envelopes, or its two arrays differ in length. | |
| 84 | - /// @param secretHashesLength The number of secret hashes passed. | |
| 85 | - /// @param wethAmountsLength The number of wETH amounts passed. | |
| 86 | - error InvalidBatch(uint256 secretHashesLength, uint256 wethAmountsLength); | |
| 84 | + /// @notice Thrown when a note has already been claimed. | |
| 85 | + /// @param nullifierHash The nullifier hash that was already published. | |
| 86 | + error NoteAlreadySpent(bytes32 nullifierHash); | |
| 87 | 87 | |
| 88 | - /// @notice Thrown when a secret doesn't hash to the envelope's `secretHash`. | |
| 89 | - /// @param depositId The envelope the wrong secret was tried against. | |
| 90 | - error WrongSecret(uint256 depositId); | |
| 91 | - | |
| 92 | - /// @notice Thrown when a claim amount exceeds what's left in the envelope. | |
| 93 | - /// @param depositId The envelope that was asked. | |
| 94 | - /// @param requested The amount asked for. | |
| 95 | - /// @param remaining What was actually left. | |
| 96 | - error InsufficientRemaining(uint256 depositId, uint256 requested, uint256 remaining); | |
| 88 | + /// @notice Thrown when a claim's proof does not verify. | |
| 89 | + error InvalidProof(); | |
| 97 | 90 | |
| 98 | 91 | /// @notice Emitted when wETH is locked and LOVE minted. |
| 99 | 92 | /// @param account The depositor, who pays the wETH and receives the LOVE. |
| @@ -101,15 +94,10 @@ contract Love is ERC20 { | ||
| 101 | 94 | /// @param loveAmount The LOVE minted, `wethAmount * RATE`. |
| 102 | 95 | event Deposit(address indexed account, uint256 wethAmount, uint256 loveAmount); |
| 103 | 96 | |
| 104 | - /// @notice Emitted when wETH is locked and LOVE minted into a secret-locked envelope. | |
| 105 | - /// @param depositId The id this envelope was assigned. | |
| 106 | - /// @param account The depositor, who pays the wETH. | |
| 107 | - /// @param secretHash `keccak256` of the secret that unlocks this envelope. | |
| 108 | - /// @param wethAmount The wETH pulled in. | |
| 109 | - /// @param loveAmount The LOVE minted, `wethAmount * RATE`. | |
| 110 | - event Deposit( | |
| 111 | - uint256 indexed depositId, address indexed account, bytes32 secretHash, uint256 wethAmount, uint256 loveAmount | |
| 112 | - ); | |
| 97 | + /// @notice Emitted when wETH is locked and LOVE minted into the private pool. | |
| 98 | + /// @param commitment The note's commitment, now a leaf of the tree. | |
| 99 | + /// @param leafIndex Where it landed, which is what a prover needs to rebuild the tree. | |
| 100 | + event Deposit(bytes32 indexed commitment, uint256 leafIndex); | |
| 113 | 101 | |
| 114 | 102 | /// @notice Emitted when LOVE is burned and wETH released. |
| 115 | 103 | /// @param account The redeemer, who burns the LOVE and receives the wETH. |
| @@ -117,12 +105,10 @@ contract Love is ERC20 { | ||
| 117 | 105 | /// @param wethAmount The wETH released, `loveAmount / RATE`. |
| 118 | 106 | event Withdraw(address indexed account, uint256 loveAmount, uint256 wethAmount); |
| 119 | 107 | |
| 120 | - /// @notice Emitted when LOVE is released from an envelope against a correct secret. | |
| 121 | - /// @param depositId The envelope claimed from. | |
| 122 | - /// @param account The claimant, who proved the secret and receives the LOVE. | |
| 123 | - /// @param loveAmount The LOVE released. | |
| 124 | - /// @param remaining What's left in the envelope after this claim. | |
| 125 | - event Claim(uint256 indexed depositId, address indexed account, uint256 loveAmount, uint256 remaining); | |
| 108 | + /// @notice Emitted when a note is claimed. | |
| 109 | + /// @param recipient Who was paid. | |
| 110 | + /// @param nullifierHash The note's nullifier hash, which is not linkable to its commitment. | |
| 111 | + event Claim(address indexed recipient, bytes32 nullifierHash); | |
| 126 | 112 | |
| 127 | 113 | /// @notice Peg to whatever wETH the registry has accepted on this chain. |
| 128 | 114 | /// @dev Takes no arguments on purpose. Anything passed in here would land |
| @@ -140,6 +126,7 @@ contract Love is ERC20 { | ||
| 140 | 126 | if (address(registered) == address(0)) revert WethNotRegistered(REGISTRY); |
| 141 | 127 | |
| 142 | 128 | WETH = IERC20(address(registered)); |
| 129 | + VERIFIER = new Groth16Verifier(); | |
| 143 | 130 | } |
| 144 | 131 | |
| 145 | 132 | /// @notice Lock `wethAmount` wETH and mint `wethAmount * RATE` LOVE to the caller. |
| @@ -156,77 +143,24 @@ contract Love is ERC20 { | ||
| 156 | 143 | emit Deposit(msg.sender, wethAmount, loveAmount); |
| 157 | 144 | } |
| 158 | 145 | |
| 159 | - /// @notice Lock `wethAmount` wETH behind `secretHash` instead of crediting the caller directly. | |
| 146 | + /// @notice Lock `DENOMINATION` wETH and mint its LOVE into the private pool, against `commitment`. | |
| 160 | 147 | /// @dev Same wETH pull and mint as the plain `deposit`, but the LOVE lands |
| 161 | - /// in an envelope this contract escrows rather than in the caller's | |
| 162 | - /// balance. Since minting still happens 1:1 against the wETH pulled | |
| 163 | - /// in, `totalSupply() == WETH.balanceOf(address(this)) * RATE` holds | |
| 164 | - /// exactly as it does for a plain deposit. | |
| 148 | + /// in escrow with this contract instead of in the caller's balance, so | |
| 149 | + /// `totalSupply() == WETH.balanceOf(address(this)) * RATE` holds | |
| 150 | + /// exactly as it does for a plain deposit. Nothing about the note is | |
| 151 | + /// revealed except its commitment, `Poseidon(nullifier, secret)`; see | |
| 152 | + /// docs/ZK.md for how to make one. | |
| 165 | 153 | /// |
| 166 | - /// Claim it with `claim`, proving a `secret` that hashes to | |
| 167 | - /// `secretHash` — see docs/notes/add-privacy-to-love.md for how that | |
| 168 | - /// secret should be generated and shared off-chain. | |
| 169 | - /// @param secretHash `keccak256(abi.encodePacked(secret))` for the secret that will unlock this envelope. | |
| 170 | - /// @param wethAmount The wETH to lock, in wei. | |
| 171 | - /// @return depositId The id this envelope was assigned. | |
| 172 | - function deposit(bytes32 secretHash, uint256 wethAmount) external returns (uint256 depositId) { | |
| 173 | - if (secretHash == bytes32(0)) revert ZeroSecretHash(); | |
| 154 | + /// A commitment nobody can open is LOVE nobody can claim, so make it | |
| 155 | + /// with `zk/lib.mjs` rather than by hand. | |
| 156 | + /// @param commitment The note's commitment, a field element. | |
| 157 | + function deposit(bytes32 commitment) external { | |
| 158 | + uint256 leafIndex = _insert(commitment); | |
| 174 | 159 | |
| 175 | - uint256 loveAmount = wethAmount * RATE; | |
| 160 | + WETH.safeTransferFrom(msg.sender, address(this), DENOMINATION); | |
| 161 | + _mint(address(this), DENOMINATION * RATE); | |
| 176 | 162 | |
| 177 | - WETH.safeTransferFrom(msg.sender, address(this), wethAmount); | |
| 178 | - _mint(address(this), loveAmount); | |
| 179 | - | |
| 180 | - depositId = nextDepositId++; | |
| 181 | - envelopes[depositId] = Envelope({secretHash: secretHash, remaining: loveAmount}); | |
| 182 | - | |
| 183 | - emit Deposit(depositId, msg.sender, secretHash, wethAmount, loveAmount); | |
| 184 | - } | |
| 185 | - | |
| 186 | - /// @notice Lock the sum of `wethAmounts` wETH in one go, opening one envelope per entry. | |
| 187 | - /// @dev Equivalent to calling `deposit(bytes32,uint256)` once per entry, | |
| 188 | - /// with a single wETH pull and a single mint. Each envelope has its | |
| 189 | - /// own secret and amount, so each recipient can claim exactly their | |
| 190 | - /// share and no more, with a single full `claim` — no partial claim, | |
| 191 | - /// so none of the front-running window that comes with one. The ids | |
| 192 | - /// are consecutive, starting at `nextDepositId`. | |
| 193 | - /// | |
| 194 | - /// Splitting one deposit this way is not more private than one | |
| 195 | - /// envelope: every envelope still names the same depositor. | |
| 196 | - /// @param secretHashes `keccak256(abi.encodePacked(secret))` for each envelope, none of them zero. | |
| 197 | - /// @param wethAmounts The wETH to lock in each envelope, in wei, one per hash. | |
| 198 | - /// @return depositIds The ids the envelopes were assigned, in input order. | |
| 199 | - function deposit(bytes32[] calldata secretHashes, uint256[] calldata wethAmounts) | |
| 200 | - external | |
| 201 | - returns (uint256[] memory depositIds) | |
| 202 | - { | |
| 203 | - uint256 count = secretHashes.length; | |
| 204 | - if (count == 0 || count != wethAmounts.length) revert InvalidBatch(count, wethAmounts.length); | |
| 205 | - | |
| 206 | - depositIds = new uint256[](count); | |
| 207 | - uint256 nextId = nextDepositId; | |
| 208 | - uint256 totalWeth; | |
| 209 | - | |
| 210 | - for (uint256 i; i < count; ++i) { | |
| 211 | - bytes32 secretHash = secretHashes[i]; | |
| 212 | - if (secretHash == bytes32(0)) revert ZeroSecretHash(); | |
| 213 | - | |
| 214 | - uint256 wethAmount = wethAmounts[i]; | |
| 215 | - uint256 loveAmount = wethAmount * RATE; | |
| 216 | - | |
| 217 | - totalWeth += wethAmount; | |
| 218 | - depositIds[i] = nextId; | |
| 219 | - envelopes[nextId] = Envelope({secretHash: secretHash, remaining: loveAmount}); | |
| 220 | - | |
| 221 | - emit Deposit(nextId, msg.sender, secretHash, wethAmount, loveAmount); | |
| 222 | - | |
| 223 | - ++nextId; | |
| 224 | - } | |
| 225 | - | |
| 226 | - nextDepositId = nextId; | |
| 227 | - | |
| 228 | - WETH.safeTransferFrom(msg.sender, address(this), totalWeth); | |
| 229 | - _mint(address(this), totalWeth * RATE); | |
| 163 | + emit Deposit(commitment, leafIndex); | |
| 230 | 164 | } |
| 231 | 165 | |
| 232 | 166 | /// @notice Burn `loveAmount` LOVE and release `loveAmount / RATE` wETH to the caller. |
| @@ -246,30 +180,39 @@ contract Love is ERC20 { | ||
| 246 | 180 | emit Withdraw(msg.sender, loveAmount, wethAmount); |
| 247 | 181 | } |
| 248 | 182 | |
| 249 | - /// @notice Prove knowledge of the secret behind `depositId` and claim `loveAmount` LOVE from escrow. | |
| 250 | - /// @dev No recipient check: whoever calls this with the right secret is | |
| 251 | - /// paid, by design — see docs/notes/add-privacy-to-love.md for the | |
| 252 | - /// front-running window this opens and how to mitigate it off-chain. | |
| 253 | - /// `remaining` is decremented before the transfer, so this is reusable | |
| 254 | - /// across several partial claims until it hits zero. | |
| 183 | + /// @notice Pay a note's LOVE to `recipient`, given a proof that the note is in the pool. | |
| 184 | + /// @dev Anyone can submit this, and the proof is only good for `recipient`, | |
| 185 | + /// so a front-runner who copies the calldata pays the recipient and | |
| 186 | + /// nobody else. The proof reveals `nullifierHash` but not which | |
| 187 | + /// deposit it belongs to. `nullifierHash` is marked spent before the | |
| 188 | + /// transfer, so a note pays out once. | |
| 255 | 189 | /// |
| 256 | - /// LOVE claimed this way is an ordinary balance afterwards; redeem it | |
| 257 | - /// for wETH through the regular, unrelated `withdraw`. | |
| 258 | - /// @param depositId The envelope to claim from. | |
| 259 | - /// @param secret The preimage of the envelope's `secretHash`. | |
| 260 | - /// @param loveAmount The LOVE to claim, at most what's `remaining`. | |
| 261 | - function claim(uint256 depositId, bytes32 secret, uint256 loveAmount) external { | |
| 262 | - Envelope storage envelope = envelopes[depositId]; | |
| 263 | - if (envelope.secretHash == bytes32(0)) revert UnknownDeposit(depositId); | |
| 264 | - if (keccak256(abi.encodePacked(secret)) != envelope.secretHash) revert WrongSecret(depositId); | |
| 265 | - if (loveAmount > envelope.remaining) { | |
| 266 | - revert InsufficientRemaining(depositId, loveAmount, envelope.remaining); | |
| 267 | - } | |
| 268 | - | |
| 269 | - envelope.remaining -= loveAmount; | |
| 270 | - | |
| 271 | - _transfer(address(this), msg.sender, loveAmount); | |
| 272 | - | |
| 273 | - emit Claim(depositId, msg.sender, loveAmount, envelope.remaining); | |
| 190 | + /// The LOVE paid is an ordinary balance afterwards; redeem it for wETH | |
| 191 | + /// through the regular, unrelated `withdraw`. | |
| 192 | + /// @param pA Proof point A. | |
| 193 | + /// @param pB Proof point B. | |
| 194 | + /// @param pC Proof point C. | |
| 195 | + /// @param root A root of the tree, current or past. | |
| 196 | + /// @param nullifierHash `Poseidon(nullifier)` for the note being spent. | |
| 197 | + /// @param recipient Who to pay. | |
| 198 | + function claim( | |
| 199 | + uint256[2] calldata pA, | |
| 200 | + uint256[2][2] calldata pB, | |
| 201 | + uint256[2] calldata pC, | |
| 202 | + bytes32 root, | |
| 203 | + bytes32 nullifierHash, | |
| 204 | + address recipient | |
| 205 | + ) external { | |
| 206 | + if (!isKnownRoot[root]) revert UnknownRoot(root); | |
| 207 | + if (isSpent[nullifierHash]) revert NoteAlreadySpent(nullifierHash); | |
| 208 | + | |
| 209 | + uint256[3] memory publicSignals = [uint256(root), uint256(nullifierHash), uint256(uint160(recipient))]; | |
| 210 | + if (!VERIFIER.verifyProof(pA, pB, pC, publicSignals)) revert InvalidProof(); | |
| 211 | + | |
| 212 | + isSpent[nullifierHash] = true; | |
| 213 | + | |
| 214 | + _transfer(address(this), recipient, DENOMINATION * RATE); | |
| 215 | + | |
| 216 | + emit Claim(recipient, nullifierHash); | |
| 274 | 217 | } |
| 275 | 218 | } |
| @@ -1,7 +1,9 @@ | |||
| 1 | // SPDX-License-Identifier: MIT | 1 | // SPDX-License-Identifier: MIT |
| 2 | pragma solidity ^0.8.30; | 2 | pragma solidity ^0.8.30; |
| 3 | 3 | ||
| 4 | +import {Groth16Verifier} from "./Groth16Verifier.sol"; | ||
| 4 | import {IWETH} from "./IWETH.sol"; | 5 | import {IWETH} from "./IWETH.sol"; |
| 6 | +import {MerkleTree} from "./MerkleTree.sol"; | ||
| 5 | import {WETHRegistry} from "./WETHRegistry.sol"; | 7 | import {WETHRegistry} from "./WETHRegistry.sol"; |
| 6 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | 8 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; |
| 7 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | 9 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
| @@ -13,11 +15,17 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol | |||
| 13 | /// Every LOVE in circulation is backed by wETH held by this contract: | 15 | /// Every LOVE in circulation is backed by wETH held by this contract: |
| 14 | /// supply only moves through `deposit` and `withdraw`, and both are | 16 | /// supply only moves through `deposit` and `withdraw`, and both are |
| 15 | /// permissionless. | 17 | /// permissionless. |
| 18 | +/// | ||
| 19 | +/// Besides the plain `deposit(uint256)`, LOVE can be sent privately: | ||
| 20 | +/// `deposit(bytes32)` locks a fixed `DENOMINATION` of wETH against a | ||
| 21 | +/// note's commitment, and `claim` pays that much LOVE to any address | ||
| 22 | +/// whose owner can prove, in zero knowledge, that they hold *a* note in | ||
| 23 | +/// the pool — not which one. See docs/ZK.md. | ||
| 16 | /// @dev There is no mint entrypoint, no owner and no upgrade path, so the peg | 24 | /// @dev There is no mint entrypoint, no owner and no upgrade path, so the peg |
| 17 | /// cannot be diluted. `totalSupply() == WETH.balanceOf(address(this)) * RATE` | 25 | /// cannot be diluted. `totalSupply() == WETH.balanceOf(address(this)) * RATE` |
| 18 | /// holds after every call; wETH transferred straight to this contract | 26 | /// holds after every call; wETH transferred straight to this contract |
| 19 | /// raises the backing and is not redeemable. | 27 | /// raises the backing and is not redeemable. |
| 20 | -contract Love is ERC20 { | 28 | +contract Love is ERC20, MerkleTree { |
| 21 | using SafeERC20 for IERC20; | 29 | using SafeERC20 for IERC20; |
| 22 | 30 | ||
| 23 | /// @notice LOVE minted per unit of wETH, and burned per unit released. | 31 | /// @notice LOVE minted per unit of wETH, and burned per unit released. |
| @@ -42,20 +50,19 @@ contract Love is ERC20 { | |||
| 42 | /// is itself write-once, so nothing can move the token under the peg. | 50 | /// is itself write-once, so nothing can move the token under the peg. |
| 43 | IERC20 public immutable WETH; | 51 | IERC20 public immutable WETH; |
| 44 | 52 | ||
| 45 | - /// @notice One secret-locked deposit: a hash to unlock it, and what's left of it. | 53 | + /// @notice The wETH one private note is worth, and the only amount `deposit(bytes32)` takes. |
| 46 | - /// @dev Opened by the `deposit(bytes32,uint256)` overload; see | 54 | + /// @dev Fixed so that amounts tell an observer nothing about which deposit a |
| 47 | - /// docs/notes/add-privacy-to-love.md for the off-chain processes | 55 | + /// claim came from. Sending 0.05 wETH takes five notes. |
| 48 | - /// (secret generation, sharing, timing discipline) this depends on. | 56 | + uint256 public constant DENOMINATION = 0.01 ether; |
| 49 | - struct Envelope { | ||
| 50 | - bytes32 secretHash; | ||
| 51 | - uint256 remaining; | ||
| 52 | - } | ||
| 53 | 57 | ||
| 54 | - /// @notice Envelope storage, by `depositId`. | 58 | + /// @notice Checks the proofs `claim` is given. |
| 55 | - mapping(uint256 => Envelope) public envelopes; | 59 | + /// @dev Created here rather than passed in, so it lands at an address that |
| 60 | + /// depends only on this contract's, and the creation code stays | ||
| 61 | + /// byte-identical on every chain. | ||
| 62 | + Groth16Verifier public immutable VERIFIER; | ||
| 56 | 63 | ||
| 57 | - /// @notice The `depositId` the next secret-locked `deposit` call will be assigned. | 64 | + /// @notice Whether a note's nullifier has been published, and so the note spent. |
| 58 | - uint256 public nextDepositId; | 65 | + mapping(bytes32 nullifierHash => bool) public isSpent; |
| 59 | 66 | ||
| 60 | /// @notice Thrown when a withdrawal amount is not a multiple of `RATE`. | 67 | /// @notice Thrown when a withdrawal amount is not a multiple of `RATE`. |
| 61 | /// @param loveAmount The rejected LOVE amount. | 68 | /// @param loveAmount The rejected LOVE amount. |
| @@ -70,30 +77,16 @@ contract Love is ERC20 { | |||
| 70 | /// @param registry The registry that was asked. | 77 | /// @param registry The registry that was asked. |
| 71 | error WethNotRegistered(address registry); | 78 | error WethNotRegistered(address registry); |
| 72 | 79 | ||
| 73 | - /// @notice Thrown when `depositId` has never been deposited into. | 80 | + /// @notice Thrown when a claim is proved against a root the tree never had. |
| 74 | - /// @param depositId The id that doesn't exist. | 81 | + /// @param root The unknown root. |
| 75 | - error UnknownDeposit(uint256 depositId); | 82 | + error UnknownRoot(bytes32 root); |
| 76 | - | ||
| 77 | - /// @notice Thrown when a privacy deposit is attempted with a zero `secretHash`. | ||
| 78 | - /// @dev `claim` uses `secretHash == bytes32(0)` as its "envelope doesn't | ||
| 79 | - /// exist" sentinel, so a zero hash would otherwise create an envelope | ||
| 80 | - /// that can never be claimed. | ||
| 81 | - error ZeroSecretHash(); | ||
| 82 | 83 | ||
| 83 | - /// @notice Thrown when a batch deposit has no envelopes, or its two arrays differ in length. | 84 | + /// @notice Thrown when a note has already been claimed. |
| 84 | - /// @param secretHashesLength The number of secret hashes passed. | 85 | + /// @param nullifierHash The nullifier hash that was already published. |
| 85 | - /// @param wethAmountsLength The number of wETH amounts passed. | 86 | + error NoteAlreadySpent(bytes32 nullifierHash); |
| 86 | - error InvalidBatch(uint256 secretHashesLength, uint256 wethAmountsLength); | ||
| 87 | 87 | ||
| 88 | - /// @notice Thrown when a secret doesn't hash to the envelope's `secretHash`. | 88 | + /// @notice Thrown when a claim's proof does not verify. |
| 89 | - /// @param depositId The envelope the wrong secret was tried against. | 89 | + error InvalidProof(); |
| 90 | - error WrongSecret(uint256 depositId); | ||
| 91 | - | ||
| 92 | - /// @notice Thrown when a claim amount exceeds what's left in the envelope. | ||
| 93 | - /// @param depositId The envelope that was asked. | ||
| 94 | - /// @param requested The amount asked for. | ||
| 95 | - /// @param remaining What was actually left. | ||
| 96 | - error InsufficientRemaining(uint256 depositId, uint256 requested, uint256 remaining); | ||
| 97 | 90 | ||
| 98 | /// @notice Emitted when wETH is locked and LOVE minted. | 91 | /// @notice Emitted when wETH is locked and LOVE minted. |
| 99 | /// @param account The depositor, who pays the wETH and receives the LOVE. | 92 | /// @param account The depositor, who pays the wETH and receives the LOVE. |
| @@ -101,15 +94,10 @@ contract Love is ERC20 { | |||
| 101 | /// @param loveAmount The LOVE minted, `wethAmount * RATE`. | 94 | /// @param loveAmount The LOVE minted, `wethAmount * RATE`. |
| 102 | event Deposit(address indexed account, uint256 wethAmount, uint256 loveAmount); | 95 | event Deposit(address indexed account, uint256 wethAmount, uint256 loveAmount); |
| 103 | 96 | ||
| 104 | - /// @notice Emitted when wETH is locked and LOVE minted into a secret-locked envelope. | 97 | + /// @notice Emitted when wETH is locked and LOVE minted into the private pool. |
| 105 | - /// @param depositId The id this envelope was assigned. | 98 | + /// @param commitment The note's commitment, now a leaf of the tree. |
| 106 | - /// @param account The depositor, who pays the wETH. | 99 | + /// @param leafIndex Where it landed, which is what a prover needs to rebuild the tree. |
| 107 | - /// @param secretHash `keccak256` of the secret that unlocks this envelope. | 100 | + event Deposit(bytes32 indexed commitment, uint256 leafIndex); |
| 108 | - /// @param wethAmount The wETH pulled in. | ||
| 109 | - /// @param loveAmount The LOVE minted, `wethAmount * RATE`. | ||
| 110 | - event Deposit( | ||
| 111 | - uint256 indexed depositId, address indexed account, bytes32 secretHash, uint256 wethAmount, uint256 loveAmount | ||
| 112 | - ); | ||
| 113 | 101 | ||
| 114 | /// @notice Emitted when LOVE is burned and wETH released. | 102 | /// @notice Emitted when LOVE is burned and wETH released. |
| 115 | /// @param account The redeemer, who burns the LOVE and receives the wETH. | 103 | /// @param account The redeemer, who burns the LOVE and receives the wETH. |
| @@ -117,12 +105,10 @@ contract Love is ERC20 { | |||
| 117 | /// @param wethAmount The wETH released, `loveAmount / RATE`. | 105 | /// @param wethAmount The wETH released, `loveAmount / RATE`. |
| 118 | event Withdraw(address indexed account, uint256 loveAmount, uint256 wethAmount); | 106 | event Withdraw(address indexed account, uint256 loveAmount, uint256 wethAmount); |
| 119 | 107 | ||
| 120 | - /// @notice Emitted when LOVE is released from an envelope against a correct secret. | 108 | + /// @notice Emitted when a note is claimed. |
| 121 | - /// @param depositId The envelope claimed from. | 109 | + /// @param recipient Who was paid. |
| 122 | - /// @param account The claimant, who proved the secret and receives the LOVE. | 110 | + /// @param nullifierHash The note's nullifier hash, which is not linkable to its commitment. |
| 123 | - /// @param loveAmount The LOVE released. | 111 | + event Claim(address indexed recipient, bytes32 nullifierHash); |
| 124 | - /// @param remaining What's left in the envelope after this claim. | ||
| 125 | - event Claim(uint256 indexed depositId, address indexed account, uint256 loveAmount, uint256 remaining); | ||
| 126 | 112 | ||
| 127 | /// @notice Peg to whatever wETH the registry has accepted on this chain. | 113 | /// @notice Peg to whatever wETH the registry has accepted on this chain. |
| 128 | /// @dev Takes no arguments on purpose. Anything passed in here would land | 114 | /// @dev Takes no arguments on purpose. Anything passed in here would land |
| @@ -140,6 +126,7 @@ contract Love is ERC20 { | |||
| 140 | if (address(registered) == address(0)) revert WethNotRegistered(REGISTRY); | 126 | if (address(registered) == address(0)) revert WethNotRegistered(REGISTRY); |
| 141 | 127 | ||
| 142 | WETH = IERC20(address(registered)); | 128 | WETH = IERC20(address(registered)); |
| 129 | + VERIFIER = new Groth16Verifier(); | ||
| 143 | } | 130 | } |
| 144 | 131 | ||
| 145 | /// @notice Lock `wethAmount` wETH and mint `wethAmount * RATE` LOVE to the caller. | 132 | /// @notice Lock `wethAmount` wETH and mint `wethAmount * RATE` LOVE to the caller. |
| @@ -156,77 +143,24 @@ contract Love is ERC20 { | |||
| 156 | emit Deposit(msg.sender, wethAmount, loveAmount); | 143 | emit Deposit(msg.sender, wethAmount, loveAmount); |
| 157 | } | 144 | } |
| 158 | 145 | ||
| 159 | - /// @notice Lock `wethAmount` wETH behind `secretHash` instead of crediting the caller directly. | 146 | + /// @notice Lock `DENOMINATION` wETH and mint its LOVE into the private pool, against `commitment`. |
| 160 | /// @dev Same wETH pull and mint as the plain `deposit`, but the LOVE lands | 147 | /// @dev Same wETH pull and mint as the plain `deposit`, but the LOVE lands |
| 161 | - /// in an envelope this contract escrows rather than in the caller's | 148 | + /// in escrow with this contract instead of in the caller's balance, so |
| 162 | - /// balance. Since minting still happens 1:1 against the wETH pulled | 149 | + /// `totalSupply() == WETH.balanceOf(address(this)) * RATE` holds |
| 163 | - /// in, `totalSupply() == WETH.balanceOf(address(this)) * RATE` holds | 150 | + /// exactly as it does for a plain deposit. Nothing about the note is |
| 164 | - /// exactly as it does for a plain deposit. | 151 | + /// revealed except its commitment, `Poseidon(nullifier, secret)`; see |
| 152 | + /// docs/ZK.md for how to make one. | ||
| 165 | /// | 153 | /// |
| 166 | - /// Claim it with `claim`, proving a `secret` that hashes to | 154 | + /// A commitment nobody can open is LOVE nobody can claim, so make it |
| 167 | - /// `secretHash` — see docs/notes/add-privacy-to-love.md for how that | 155 | + /// with `zk/lib.mjs` rather than by hand. |
| 168 | - /// secret should be generated and shared off-chain. | 156 | + /// @param commitment The note's commitment, a field element. |
| 169 | - /// @param secretHash `keccak256(abi.encodePacked(secret))` for the secret that will unlock this envelope. | 157 | + function deposit(bytes32 commitment) external { |
| 170 | - /// @param wethAmount The wETH to lock, in wei. | 158 | + uint256 leafIndex = _insert(commitment); |
| 171 | - /// @return depositId The id this envelope was assigned. | ||
| 172 | - function deposit(bytes32 secretHash, uint256 wethAmount) external returns (uint256 depositId) { | ||
| 173 | - if (secretHash == bytes32(0)) revert ZeroSecretHash(); | ||
| 174 | 159 | ||
| 175 | - uint256 loveAmount = wethAmount * RATE; | 160 | + WETH.safeTransferFrom(msg.sender, address(this), DENOMINATION); |
| 161 | + _mint(address(this), DENOMINATION * RATE); | ||
| 176 | 162 | ||
| 177 | - WETH.safeTransferFrom(msg.sender, address(this), wethAmount); | 163 | + emit Deposit(commitment, leafIndex); |
| 178 | - _mint(address(this), loveAmount); | ||
| 179 | - | ||
| 180 | - depositId = nextDepositId++; | ||
| 181 | - envelopes[depositId] = Envelope({secretHash: secretHash, remaining: loveAmount}); | ||
| 182 | - | ||
| 183 | - emit Deposit(depositId, msg.sender, secretHash, wethAmount, loveAmount); | ||
| 184 | - } | ||
| 185 | - | ||
| 186 | - /// @notice Lock the sum of `wethAmounts` wETH in one go, opening one envelope per entry. | ||
| 187 | - /// @dev Equivalent to calling `deposit(bytes32,uint256)` once per entry, | ||
| 188 | - /// with a single wETH pull and a single mint. Each envelope has its | ||
| 189 | - /// own secret and amount, so each recipient can claim exactly their | ||
| 190 | - /// share and no more, with a single full `claim` — no partial claim, | ||
| 191 | - /// so none of the front-running window that comes with one. The ids | ||
| 192 | - /// are consecutive, starting at `nextDepositId`. | ||
| 193 | - /// | ||
| 194 | - /// Splitting one deposit this way is not more private than one | ||
| 195 | - /// envelope: every envelope still names the same depositor. | ||
| 196 | - /// @param secretHashes `keccak256(abi.encodePacked(secret))` for each envelope, none of them zero. | ||
| 197 | - /// @param wethAmounts The wETH to lock in each envelope, in wei, one per hash. | ||
| 198 | - /// @return depositIds The ids the envelopes were assigned, in input order. | ||
| 199 | - function deposit(bytes32[] calldata secretHashes, uint256[] calldata wethAmounts) | ||
| 200 | - external | ||
| 201 | - returns (uint256[] memory depositIds) | ||
| 202 | - { | ||
| 203 | - uint256 count = secretHashes.length; | ||
| 204 | - if (count == 0 || count != wethAmounts.length) revert InvalidBatch(count, wethAmounts.length); | ||
| 205 | - | ||
| 206 | - depositIds = new uint256[](count); | ||
| 207 | - uint256 nextId = nextDepositId; | ||
| 208 | - uint256 totalWeth; | ||
| 209 | - | ||
| 210 | - for (uint256 i; i < count; ++i) { | ||
| 211 | - bytes32 secretHash = secretHashes[i]; | ||
| 212 | - if (secretHash == bytes32(0)) revert ZeroSecretHash(); | ||
| 213 | - | ||
| 214 | - uint256 wethAmount = wethAmounts[i]; | ||
| 215 | - uint256 loveAmount = wethAmount * RATE; | ||
| 216 | - | ||
| 217 | - totalWeth += wethAmount; | ||
| 218 | - depositIds[i] = nextId; | ||
| 219 | - envelopes[nextId] = Envelope({secretHash: secretHash, remaining: loveAmount}); | ||
| 220 | - | ||
| 221 | - emit Deposit(nextId, msg.sender, secretHash, wethAmount, loveAmount); | ||
| 222 | - | ||
| 223 | - ++nextId; | ||
| 224 | - } | ||
| 225 | - | ||
| 226 | - nextDepositId = nextId; | ||
| 227 | - | ||
| 228 | - WETH.safeTransferFrom(msg.sender, address(this), totalWeth); | ||
| 229 | - _mint(address(this), totalWeth * RATE); | ||
| 230 | } | 164 | } |
| 231 | 165 | ||
| 232 | /// @notice Burn `loveAmount` LOVE and release `loveAmount / RATE` wETH to the caller. | 166 | /// @notice Burn `loveAmount` LOVE and release `loveAmount / RATE` wETH to the caller. |
| @@ -246,30 +180,39 @@ contract Love is ERC20 { | |||
| 246 | emit Withdraw(msg.sender, loveAmount, wethAmount); | 180 | emit Withdraw(msg.sender, loveAmount, wethAmount); |
| 247 | } | 181 | } |
| 248 | 182 | ||
| 249 | - /// @notice Prove knowledge of the secret behind `depositId` and claim `loveAmount` LOVE from escrow. | 183 | + /// @notice Pay a note's LOVE to `recipient`, given a proof that the note is in the pool. |
| 250 | - /// @dev No recipient check: whoever calls this with the right secret is | 184 | + /// @dev Anyone can submit this, and the proof is only good for `recipient`, |
| 251 | - /// paid, by design — see docs/notes/add-privacy-to-love.md for the | 185 | + /// so a front-runner who copies the calldata pays the recipient and |
| 252 | - /// front-running window this opens and how to mitigate it off-chain. | 186 | + /// nobody else. The proof reveals `nullifierHash` but not which |
| 253 | - /// `remaining` is decremented before the transfer, so this is reusable | 187 | + /// deposit it belongs to. `nullifierHash` is marked spent before the |
| 254 | - /// across several partial claims until it hits zero. | 188 | + /// transfer, so a note pays out once. |
| 255 | /// | 189 | /// |
| 256 | - /// LOVE claimed this way is an ordinary balance afterwards; redeem it | 190 | + /// The LOVE paid is an ordinary balance afterwards; redeem it for wETH |
| 257 | - /// for wETH through the regular, unrelated `withdraw`. | 191 | + /// through the regular, unrelated `withdraw`. |
| 258 | - /// @param depositId The envelope to claim from. | 192 | + /// @param pA Proof point A. |
| 259 | - /// @param secret The preimage of the envelope's `secretHash`. | 193 | + /// @param pB Proof point B. |
| 260 | - /// @param loveAmount The LOVE to claim, at most what's `remaining`. | 194 | + /// @param pC Proof point C. |
| 261 | - function claim(uint256 depositId, bytes32 secret, uint256 loveAmount) external { | 195 | + /// @param root A root of the tree, current or past. |
| 262 | - Envelope storage envelope = envelopes[depositId]; | 196 | + /// @param nullifierHash `Poseidon(nullifier)` for the note being spent. |
| 263 | - if (envelope.secretHash == bytes32(0)) revert UnknownDeposit(depositId); | 197 | + /// @param recipient Who to pay. |
| 264 | - if (keccak256(abi.encodePacked(secret)) != envelope.secretHash) revert WrongSecret(depositId); | 198 | + function claim( |
| 265 | - if (loveAmount > envelope.remaining) { | 199 | + uint256[2] calldata pA, |
| 266 | - revert InsufficientRemaining(depositId, loveAmount, envelope.remaining); | 200 | + uint256[2][2] calldata pB, |
| 267 | - } | 201 | + uint256[2] calldata pC, |
| 268 | - | 202 | + bytes32 root, |
| 269 | - envelope.remaining -= loveAmount; | 203 | + bytes32 nullifierHash, |
| 270 | - | 204 | + address recipient |
| 271 | - _transfer(address(this), msg.sender, loveAmount); | 205 | + ) external { |
| 272 | - | 206 | + if (!isKnownRoot[root]) revert UnknownRoot(root); |
| 273 | - emit Claim(depositId, msg.sender, loveAmount, envelope.remaining); | 207 | + if (isSpent[nullifierHash]) revert NoteAlreadySpent(nullifierHash); |
| 208 | + | ||
| 209 | + uint256[3] memory publicSignals = [uint256(root), uint256(nullifierHash), uint256(uint160(recipient))]; | ||
| 210 | + if (!VERIFIER.verifyProof(pA, pB, pC, publicSignals)) revert InvalidProof(); | ||
| 211 | + | ||
| 212 | + isSpent[nullifierHash] = true; | ||
| 213 | + | ||
| 214 | + _transfer(address(this), recipient, DENOMINATION * RATE); | ||
| 215 | + | ||
| 216 | + emit Claim(recipient, nullifierHash); | ||
| 274 | } | 217 | } |
| 275 | } | 218 | } |
added
src/MerkleTree.sol +86 -0 | new file mode 100644 | ||
| @@ -0,0 +1,86 @@ | ||
| 1 | +// SPDX-License-Identifier: MIT | |
| 2 | +pragma solidity ^0.8.30; | |
| 3 | + | |
| 4 | +import {PoseidonT3} from "./PoseidonT3.sol"; | |
| 5 | + | |
| 6 | +/// @title MerkleTree | |
| 7 | +/// @author Julien Béranger | |
| 8 | +/// @notice An append-only Merkle tree of Poseidon hashes that remembers every | |
| 9 | +/// root it has ever had. | |
| 10 | +/// @dev Incremental, so an insert costs `TREE_DEPTH` hashes and a couple of | |
| 11 | +/// storage writes however full the tree is, and nothing is ever | |
| 12 | +/// recomputed. Empty leaves are zero and the empty subtree at each level | |
| 13 | +/// is the hash of two empty subtrees below it. `zk/lib.mjs` builds the | |
| 14 | +/// same tree, which is what makes a proof made off-chain verify against a | |
| 15 | +/// root made here. | |
| 16 | +/// | |
| 17 | +/// Every root stays valid forever, so a proof made against a slightly | |
| 18 | +/// stale tree still lands after later deposits. The price is that a note | |
| 19 | +/// proved against an old root hides among fewer deposits than the tree | |
| 20 | +/// holds now. | |
| 21 | +abstract contract MerkleTree { | |
| 22 | + /// @notice Levels between a leaf and the root. Fixed by the circuit: changing it means a new | |
| 23 | + /// circuit, a new setup and a new verifier. | |
| 24 | + uint256 public constant TREE_DEPTH = 10; | |
| 25 | + | |
| 26 | + /// @notice The BN254 scalar field. A leaf outside it would not be the value the circuit hashes. | |
| 27 | + uint256 public constant FIELD_SIZE = | |
| 28 | + 21_888_242_871_839_275_222_246_405_745_257_275_088_548_364_400_416_034_343_698_204_186_575_808_495_617; | |
| 29 | + | |
| 30 | + /// @notice The leaf the next insert lands in, and so the number of leaves so far. | |
| 31 | + uint256 public nextLeafIndex; | |
| 32 | + | |
| 33 | + /// @notice Whether `root` is the root of the tree after some insert. | |
| 34 | + mapping(bytes32 root => bool) public isKnownRoot; | |
| 35 | + | |
| 36 | + /// @dev The root of an all-empty subtree of each height, 0 upwards. | |
| 37 | + bytes32[TREE_DEPTH] private _zeros; | |
| 38 | + | |
| 39 | + /// @dev The latest left node at each level: what the next right sibling has to be hashed with. | |
| 40 | + bytes32[TREE_DEPTH] private _filledSubtrees; | |
| 41 | + | |
| 42 | + /// @notice Thrown when every leaf is taken. | |
| 43 | + error TreeFull(); | |
| 44 | + | |
| 45 | + /// @notice Thrown when a leaf is not a field element. | |
| 46 | + /// @param leaf The rejected leaf. | |
| 47 | + error LeafNotInField(bytes32 leaf); | |
| 48 | + | |
| 49 | + constructor() { | |
| 50 | + bytes32 zero; | |
| 51 | + for (uint256 level; level < TREE_DEPTH; ++level) { | |
| 52 | + _zeros[level] = zero; | |
| 53 | + zero = _hash(zero, zero); | |
| 54 | + } | |
| 55 | + } | |
| 56 | + | |
| 57 | + /// @dev Appends `leaf` and records the new root. | |
| 58 | + /// @param leaf The value to append, a field element. | |
| 59 | + /// @return index Where it landed. | |
| 60 | + function _insert(bytes32 leaf) internal returns (uint256 index) { | |
| 61 | + if (uint256(leaf) >= FIELD_SIZE) revert LeafNotInField(leaf); | |
| 62 | + | |
| 63 | + index = nextLeafIndex; | |
| 64 | + if (index == 2 ** TREE_DEPTH) revert TreeFull(); | |
| 65 | + | |
| 66 | + bytes32 node = leaf; | |
| 67 | + uint256 position = index; | |
| 68 | + | |
| 69 | + for (uint256 level; level < TREE_DEPTH; ++level) { | |
| 70 | + if (position & 1 == 0) { | |
| 71 | + _filledSubtrees[level] = node; | |
| 72 | + node = _hash(node, _zeros[level]); | |
| 73 | + } else { | |
| 74 | + node = _hash(_filledSubtrees[level], node); | |
| 75 | + } | |
| 76 | + position >>= 1; | |
| 77 | + } | |
| 78 | + | |
| 79 | + nextLeafIndex = index + 1; | |
| 80 | + isKnownRoot[node] = true; | |
| 81 | + } | |
| 82 | + | |
| 83 | + function _hash(bytes32 left, bytes32 right) private pure returns (bytes32) { | |
| 84 | + return bytes32(PoseidonT3.hash(uint256(left), uint256(right))); | |
| 85 | + } | |
| 86 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,86 @@ | |||
| 1 | +// SPDX-License-Identifier: MIT | ||
| 2 | +pragma solidity ^0.8.30; | ||
| 3 | + | ||
| 4 | +import {PoseidonT3} from "./PoseidonT3.sol"; | ||
| 5 | + | ||
| 6 | +/// @title MerkleTree | ||
| 7 | +/// @author Julien Béranger | ||
| 8 | +/// @notice An append-only Merkle tree of Poseidon hashes that remembers every | ||
| 9 | +/// root it has ever had. | ||
| 10 | +/// @dev Incremental, so an insert costs `TREE_DEPTH` hashes and a couple of | ||
| 11 | +/// storage writes however full the tree is, and nothing is ever | ||
| 12 | +/// recomputed. Empty leaves are zero and the empty subtree at each level | ||
| 13 | +/// is the hash of two empty subtrees below it. `zk/lib.mjs` builds the | ||
| 14 | +/// same tree, which is what makes a proof made off-chain verify against a | ||
| 15 | +/// root made here. | ||
| 16 | +/// | ||
| 17 | +/// Every root stays valid forever, so a proof made against a slightly | ||
| 18 | +/// stale tree still lands after later deposits. The price is that a note | ||
| 19 | +/// proved against an old root hides among fewer deposits than the tree | ||
| 20 | +/// holds now. | ||
| 21 | +abstract contract MerkleTree { | ||
| 22 | + /// @notice Levels between a leaf and the root. Fixed by the circuit: changing it means a new | ||
| 23 | + /// circuit, a new setup and a new verifier. | ||
| 24 | + uint256 public constant TREE_DEPTH = 10; | ||
| 25 | + | ||
| 26 | + /// @notice The BN254 scalar field. A leaf outside it would not be the value the circuit hashes. | ||
| 27 | + uint256 public constant FIELD_SIZE = | ||
| 28 | + 21_888_242_871_839_275_222_246_405_745_257_275_088_548_364_400_416_034_343_698_204_186_575_808_495_617; | ||
| 29 | + | ||
| 30 | + /// @notice The leaf the next insert lands in, and so the number of leaves so far. | ||
| 31 | + uint256 public nextLeafIndex; | ||
| 32 | + | ||
| 33 | + /// @notice Whether `root` is the root of the tree after some insert. | ||
| 34 | + mapping(bytes32 root => bool) public isKnownRoot; | ||
| 35 | + | ||
| 36 | + /// @dev The root of an all-empty subtree of each height, 0 upwards. | ||
| 37 | + bytes32[TREE_DEPTH] private _zeros; | ||
| 38 | + | ||
| 39 | + /// @dev The latest left node at each level: what the next right sibling has to be hashed with. | ||
| 40 | + bytes32[TREE_DEPTH] private _filledSubtrees; | ||
| 41 | + | ||
| 42 | + /// @notice Thrown when every leaf is taken. | ||
| 43 | + error TreeFull(); | ||
| 44 | + | ||
| 45 | + /// @notice Thrown when a leaf is not a field element. | ||
| 46 | + /// @param leaf The rejected leaf. | ||
| 47 | + error LeafNotInField(bytes32 leaf); | ||
| 48 | + | ||
| 49 | + constructor() { | ||
| 50 | + bytes32 zero; | ||
| 51 | + for (uint256 level; level < TREE_DEPTH; ++level) { | ||
| 52 | + _zeros[level] = zero; | ||
| 53 | + zero = _hash(zero, zero); | ||
| 54 | + } | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + /// @dev Appends `leaf` and records the new root. | ||
| 58 | + /// @param leaf The value to append, a field element. | ||
| 59 | + /// @return index Where it landed. | ||
| 60 | + function _insert(bytes32 leaf) internal returns (uint256 index) { | ||
| 61 | + if (uint256(leaf) >= FIELD_SIZE) revert LeafNotInField(leaf); | ||
| 62 | + | ||
| 63 | + index = nextLeafIndex; | ||
| 64 | + if (index == 2 ** TREE_DEPTH) revert TreeFull(); | ||
| 65 | + | ||
| 66 | + bytes32 node = leaf; | ||
| 67 | + uint256 position = index; | ||
| 68 | + | ||
| 69 | + for (uint256 level; level < TREE_DEPTH; ++level) { | ||
| 70 | + if (position & 1 == 0) { | ||
| 71 | + _filledSubtrees[level] = node; | ||
| 72 | + node = _hash(node, _zeros[level]); | ||
| 73 | + } else { | ||
| 74 | + node = _hash(_filledSubtrees[level], node); | ||
| 75 | + } | ||
| 76 | + position >>= 1; | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + nextLeafIndex = index + 1; | ||
| 80 | + isKnownRoot[node] = true; | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + function _hash(bytes32 left, bytes32 right) private pure returns (bytes32) { | ||
| 84 | + return bytes32(PoseidonT3.hash(uint256(left), uint256(right))); | ||
| 85 | + } | ||
| 86 | +} | ||
added
src/PoseidonT3.sol +398 -0 | new file mode 100644 | ||
| @@ -0,0 +1,398 @@ | ||
| 1 | +/// SPDX-License-Identifier: MIT | |
| 2 | +// Vendored from poseidon-solidity 0.0.5 (https://github.com/vimwitch/poseidon-solidity), with the | |
| 3 | +// entrypoint changed and the round function untouched: | |
| 4 | +// | |
| 5 | +// - `hash` took its inputs from memory at 0x80 and 0xa0 and ended in `return(0, 0x20)`, which is how a | |
| 6 | +// `public` library function that is `delegatecall`ed behaves. It now takes `a` and `b` and assigns | |
| 7 | +// `r`, and is `internal`, so it is inlined. A public library function has to be linked, and linking | |
| 8 | +// puts an address in `Love`'s creation code, which has to stay identical on every chain. | |
| 9 | +// | |
| 10 | +// Its output matches circomlib's `Poseidon(2)`, which is what the circuit and `zk/lib.mjs` use. | |
| 11 | +pragma solidity >=0.7.0; | |
| 12 | + | |
| 13 | +library PoseidonT3 { | |
| 14 | + uint constant M00 = 0x109b7f411ba0e4c9b2b70caf5c36a7b194be7c11ad24378bfedb68592ba8118b; | |
| 15 | + uint constant M01 = 0x2969f27eed31a480b9c36c764379dbca2cc8fdd1415c3dded62940bcde0bd771; | |
| 16 | + uint constant M02 = 0x143021ec686a3f330d5f9e654638065ce6cd79e28c5b3753326244ee65a1b1a7; | |
| 17 | + uint constant M10 = 0x16ed41e13bb9c0c66ae119424fddbcbc9314dc9fdbdeea55d6c64543dc4903e0; | |
| 18 | + uint constant M11 = 0x2e2419f9ec02ec394c9871c832963dc1b89d743c8c7b964029b2311687b1fe23; | |
| 19 | + uint constant M12 = 0x176cc029695ad02582a70eff08a6fd99d057e12e58e7d7b6b16cdfabc8ee2911; | |
| 20 | + | |
| 21 | + // See here for a simplified implementation: https://github.com/vimwitch/poseidon-solidity/blob/e57becdabb65d99fdc586fe1e1e09e7108202d53/contracts/Poseidon.sol#L40 | |
| 22 | + // Inspired by: https://github.com/iden3/circomlibjs/blob/v0.0.8/src/poseidon_slow.js | |
| 23 | + function hash(uint a, uint b) internal pure returns (uint r) { | |
| 24 | + assembly { | |
| 25 | + let F := 21888242871839275222246405745257275088548364400416034343698204186575808495617 | |
| 26 | + let M20 := 0x2b90bba00fca0589f617e7dcbfe82e0df706ab640ceb247b791a93b74e36736d | |
| 27 | + let M21 := 0x101071f0032379b697315876690f053d148d4e109f5fb065c8aacc55a0f89bfa | |
| 28 | + let M22 := 0x19a3fc0a56702bf417ba7fee3802593fa644470307043f7773279cd71d25d5e0 | |
| 29 | + | |
| 30 | + // load the inputs from memory | |
| 31 | + let state1 := add(mod(a, F), 0x00f1445235f2148c5986587169fc1bcd887b08d4d00868df5696fff40956e864) | |
| 32 | + let state2 := add(mod(b, F), 0x08dff3487e8ac99e1f29a058d0fa80b930c728730b7ab36ce879f3890ecf73f5) | |
| 33 | + let scratch0 := mulmod(state1, state1, F) | |
| 34 | + state1 := mulmod(mulmod(scratch0, scratch0, F), state1, F) | |
| 35 | + scratch0 := mulmod(state2, state2, F) | |
| 36 | + state2 := mulmod(mulmod(scratch0, scratch0, F), state2, F) | |
| 37 | + scratch0 := add( | |
| 38 | + 0x2f27be690fdaee46c3ce28f7532b13c856c35342c84bda6e20966310fadc01d0, | |
| 39 | + add(add(15452833169820924772166449970675545095234312153403844297388521437673434406763, mulmod(state1, M10, F)), mulmod(state2, M20, F)) | |
| 40 | + ) | |
| 41 | + let scratch1 := add( | |
| 42 | + 0x2b2ae1acf68b7b8d2416bebf3d4f6234b763fe04b8043ee48b8327bebca16cf2, | |
| 43 | + add(add(18674271267752038776579386132900109523609358935013267566297499497165104279117, mulmod(state1, M11, F)), mulmod(state2, M21, F)) | |
| 44 | + ) | |
| 45 | + let scratch2 := add( | |
| 46 | + 0x0319d062072bef7ecca5eac06f97d4d55952c175ab6b03eae64b44c7dbf11cfa, | |
| 47 | + add(add(14817777843080276494683266178512808687156649753153012854386334860566696099579, mulmod(state1, M12, F)), mulmod(state2, M22, F)) | |
| 48 | + ) | |
| 49 | + let state0 := mulmod(scratch0, scratch0, F) | |
| 50 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 51 | + state0 := mulmod(scratch1, scratch1, F) | |
| 52 | + scratch1 := mulmod(mulmod(state0, state0, F), scratch1, F) | |
| 53 | + state0 := mulmod(scratch2, scratch2, F) | |
| 54 | + scratch2 := mulmod(mulmod(state0, state0, F), scratch2, F) | |
| 55 | + state0 := add(0x28813dcaebaeaa828a376df87af4a63bc8b7bf27ad49c6298ef7b387bf28526d, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 56 | + state1 := add(0x2727673b2ccbc903f181bf38e1c1d40d2033865200c352bc150928adddf9cb78, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 57 | + state2 := add(0x234ec45ca27727c2e74abd2b2a1494cd6efbd43e340587d6b8fb9e31e65cc632, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 58 | + scratch0 := mulmod(state0, state0, F) | |
| 59 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 60 | + scratch0 := mulmod(state1, state1, F) | |
| 61 | + state1 := mulmod(mulmod(scratch0, scratch0, F), state1, F) | |
| 62 | + scratch0 := mulmod(state2, state2, F) | |
| 63 | + state2 := mulmod(mulmod(scratch0, scratch0, F), state2, F) | |
| 64 | + scratch0 := add(0x15b52534031ae18f7f862cb2cf7cf760ab10a8150a337b1ccd99ff6e8797d428, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 65 | + scratch1 := add(0x0dc8fad6d9e4b35f5ed9a3d186b79ce38e0e8a8d1b58b132d701d4eecf68d1f6, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 66 | + scratch2 := add(0x1bcd95ffc211fbca600f705fad3fb567ea4eb378f62e1fec97805518a47e4d9c, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 67 | + state0 := mulmod(scratch0, scratch0, F) | |
| 68 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 69 | + state0 := mulmod(scratch1, scratch1, F) | |
| 70 | + scratch1 := mulmod(mulmod(state0, state0, F), scratch1, F) | |
| 71 | + state0 := mulmod(scratch2, scratch2, F) | |
| 72 | + scratch2 := mulmod(mulmod(state0, state0, F), scratch2, F) | |
| 73 | + state0 := add(0x10520b0ab721cadfe9eff81b016fc34dc76da36c2578937817cb978d069de559, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 74 | + state1 := add(0x1f6d48149b8e7f7d9b257d8ed5fbbaf42932498075fed0ace88a9eb81f5627f6, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 75 | + state2 := add(0x1d9655f652309014d29e00ef35a2089bfff8dc1c816f0dc9ca34bdb5460c8705, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 76 | + scratch0 := mulmod(state0, state0, F) | |
| 77 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 78 | + scratch0 := add(0x04df5a56ff95bcafb051f7b1cd43a99ba731ff67e47032058fe3d4185697cc7d, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 79 | + scratch1 := add(0x0672d995f8fff640151b3d290cedaf148690a10a8c8424a7f6ec282b6e4be828, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 80 | + scratch2 := add(0x099952b414884454b21200d7ffafdd5f0c9a9dcc06f2708e9fc1d8209b5c75b9, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 81 | + state0 := mulmod(scratch0, scratch0, F) | |
| 82 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 83 | + state0 := add(0x052cba2255dfd00c7c483143ba8d469448e43586a9b4cd9183fd0e843a6b9fa6, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 84 | + state1 := add(0x0b8badee690adb8eb0bd74712b7999af82de55707251ad7716077cb93c464ddc, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 85 | + state2 := add(0x119b1590f13307af5a1ee651020c07c749c15d60683a8050b963d0a8e4b2bdd1, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 86 | + scratch0 := mulmod(state0, state0, F) | |
| 87 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 88 | + scratch0 := add(0x03150b7cd6d5d17b2529d36be0f67b832c4acfc884ef4ee5ce15be0bfb4a8d09, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 89 | + scratch1 := add(0x2cc6182c5e14546e3cf1951f173912355374efb83d80898abe69cb317c9ea565, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 90 | + scratch2 := add(0x005032551e6378c450cfe129a404b3764218cadedac14e2b92d2cd73111bf0f9, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 91 | + state0 := mulmod(scratch0, scratch0, F) | |
| 92 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 93 | + state0 := add(0x233237e3289baa34bb147e972ebcb9516469c399fcc069fb88f9da2cc28276b5, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 94 | + state1 := add(0x05c8f4f4ebd4a6e3c980d31674bfbe6323037f21b34ae5a4e80c2d4c24d60280, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 95 | + state2 := add(0x0a7b1db13042d396ba05d818a319f25252bcf35ef3aeed91ee1f09b2590fc65b, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 96 | + scratch0 := mulmod(state0, state0, F) | |
| 97 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 98 | + scratch0 := add(0x2a73b71f9b210cf5b14296572c9d32dbf156e2b086ff47dc5df542365a404ec0, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 99 | + scratch1 := add(0x1ac9b0417abcc9a1935107e9ffc91dc3ec18f2c4dbe7f22976a760bb5c50c460, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 100 | + scratch2 := add(0x12c0339ae08374823fabb076707ef479269f3e4d6cb104349015ee046dc93fc0, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 101 | + state0 := mulmod(scratch0, scratch0, F) | |
| 102 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 103 | + state0 := add(0x0b7475b102a165ad7f5b18db4e1e704f52900aa3253baac68246682e56e9a28e, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 104 | + state1 := add(0x037c2849e191ca3edb1c5e49f6e8b8917c843e379366f2ea32ab3aa88d7f8448, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 105 | + state2 := add(0x05a6811f8556f014e92674661e217e9bd5206c5c93a07dc145fdb176a716346f, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 106 | + scratch0 := mulmod(state0, state0, F) | |
| 107 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 108 | + scratch0 := add(0x29a795e7d98028946e947b75d54e9f044076e87a7b2883b47b675ef5f38bd66e, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 109 | + scratch1 := add(0x20439a0c84b322eb45a3857afc18f5826e8c7382c8a1585c507be199981fd22f, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 110 | + scratch2 := add(0x2e0ba8d94d9ecf4a94ec2050c7371ff1bb50f27799a84b6d4a2a6f2a0982c887, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 111 | + state0 := mulmod(scratch0, scratch0, F) | |
| 112 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 113 | + state0 := add(0x143fd115ce08fb27ca38eb7cce822b4517822cd2109048d2e6d0ddcca17d71c8, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 114 | + state1 := add(0x0c64cbecb1c734b857968dbbdcf813cdf8611659323dbcbfc84323623be9caf1, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 115 | + state2 := add(0x028a305847c683f646fca925c163ff5ae74f348d62c2b670f1426cef9403da53, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 116 | + scratch0 := mulmod(state0, state0, F) | |
| 117 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 118 | + scratch0 := add(0x2e4ef510ff0b6fda5fa940ab4c4380f26a6bcb64d89427b824d6755b5db9e30c, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 119 | + scratch1 := add(0x0081c95bc43384e663d79270c956ce3b8925b4f6d033b078b96384f50579400e, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 120 | + scratch2 := add(0x2ed5f0c91cbd9749187e2fade687e05ee2491b349c039a0bba8a9f4023a0bb38, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 121 | + state0 := mulmod(scratch0, scratch0, F) | |
| 122 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 123 | + state0 := add(0x30509991f88da3504bbf374ed5aae2f03448a22c76234c8c990f01f33a735206, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 124 | + state1 := add(0x1c3f20fd55409a53221b7c4d49a356b9f0a1119fb2067b41a7529094424ec6ad, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 125 | + state2 := add(0x10b4e7f3ab5df003049514459b6e18eec46bb2213e8e131e170887b47ddcb96c, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 126 | + scratch0 := mulmod(state0, state0, F) | |
| 127 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 128 | + scratch0 := add(0x2a1982979c3ff7f43ddd543d891c2abddd80f804c077d775039aa3502e43adef, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 129 | + scratch1 := add(0x1c74ee64f15e1db6feddbead56d6d55dba431ebc396c9af95cad0f1315bd5c91, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 130 | + scratch2 := add(0x07533ec850ba7f98eab9303cace01b4b9e4f2e8b82708cfa9c2fe45a0ae146a0, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 131 | + state0 := mulmod(scratch0, scratch0, F) | |
| 132 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 133 | + state0 := add(0x21576b438e500449a151e4eeaf17b154285c68f42d42c1808a11abf3764c0750, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 134 | + state1 := add(0x2f17c0559b8fe79608ad5ca193d62f10bce8384c815f0906743d6930836d4a9e, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 135 | + state2 := add(0x2d477e3862d07708a79e8aae946170bc9775a4201318474ae665b0b1b7e2730e, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 136 | + scratch0 := mulmod(state0, state0, F) | |
| 137 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 138 | + scratch0 := add(0x162f5243967064c390e095577984f291afba2266c38f5abcd89be0f5b2747eab, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 139 | + scratch1 := add(0x2b4cb233ede9ba48264ecd2c8ae50d1ad7a8596a87f29f8a7777a70092393311, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 140 | + scratch2 := add(0x2c8fbcb2dd8573dc1dbaf8f4622854776db2eece6d85c4cf4254e7c35e03b07a, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 141 | + state0 := mulmod(scratch0, scratch0, F) | |
| 142 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 143 | + state0 := add(0x1d6f347725e4816af2ff453f0cd56b199e1b61e9f601e9ade5e88db870949da9, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 144 | + state1 := add(0x204b0c397f4ebe71ebc2d8b3df5b913df9e6ac02b68d31324cd49af5c4565529, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 145 | + state2 := add(0x0c4cb9dc3c4fd8174f1149b3c63c3c2f9ecb827cd7dc25534ff8fb75bc79c502, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 146 | + scratch0 := mulmod(state0, state0, F) | |
| 147 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 148 | + scratch0 := add(0x174ad61a1448c899a25416474f4930301e5c49475279e0639a616ddc45bc7b54, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 149 | + scratch1 := add(0x1a96177bcf4d8d89f759df4ec2f3cde2eaaa28c177cc0fa13a9816d49a38d2ef, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 150 | + scratch2 := add(0x066d04b24331d71cd0ef8054bc60c4ff05202c126a233c1a8242ace360b8a30a, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 151 | + state0 := mulmod(scratch0, scratch0, F) | |
| 152 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 153 | + state0 := add(0x2a4c4fc6ec0b0cf52195782871c6dd3b381cc65f72e02ad527037a62aa1bd804, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 154 | + state1 := add(0x13ab2d136ccf37d447e9f2e14a7cedc95e727f8446f6d9d7e55afc01219fd649, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 155 | + state2 := add(0x1121552fca26061619d24d843dc82769c1b04fcec26f55194c2e3e869acc6a9a, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 156 | + scratch0 := mulmod(state0, state0, F) | |
| 157 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 158 | + scratch0 := add(0x00ef653322b13d6c889bc81715c37d77a6cd267d595c4a8909a5546c7c97cff1, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 159 | + scratch1 := add(0x0e25483e45a665208b261d8ba74051e6400c776d652595d9845aca35d8a397d3, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 160 | + scratch2 := add(0x29f536dcb9dd7682245264659e15d88e395ac3d4dde92d8c46448db979eeba89, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 161 | + state0 := mulmod(scratch0, scratch0, F) | |
| 162 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 163 | + state0 := add(0x2a56ef9f2c53febadfda33575dbdbd885a124e2780bbea170e456baace0fa5be, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 164 | + state1 := add(0x1c8361c78eb5cf5decfb7a2d17b5c409f2ae2999a46762e8ee416240a8cb9af1, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 165 | + state2 := add(0x151aff5f38b20a0fc0473089aaf0206b83e8e68a764507bfd3d0ab4be74319c5, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 166 | + scratch0 := mulmod(state0, state0, F) | |
| 167 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 168 | + scratch0 := add(0x04c6187e41ed881dc1b239c88f7f9d43a9f52fc8c8b6cdd1e76e47615b51f100, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 169 | + scratch1 := add(0x13b37bd80f4d27fb10d84331f6fb6d534b81c61ed15776449e801b7ddc9c2967, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 170 | + scratch2 := add(0x01a5c536273c2d9df578bfbd32c17b7a2ce3664c2a52032c9321ceb1c4e8a8e4, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 171 | + state0 := mulmod(scratch0, scratch0, F) | |
| 172 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 173 | + state0 := add(0x2ab3561834ca73835ad05f5d7acb950b4a9a2c666b9726da832239065b7c3b02, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 174 | + state1 := add(0x1d4d8ec291e720db200fe6d686c0d613acaf6af4e95d3bf69f7ed516a597b646, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 175 | + state2 := add(0x041294d2cc484d228f5784fe7919fd2bb925351240a04b711514c9c80b65af1d, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 176 | + scratch0 := mulmod(state0, state0, F) | |
| 177 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 178 | + scratch0 := add(0x154ac98e01708c611c4fa715991f004898f57939d126e392042971dd90e81fc6, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 179 | + scratch1 := add(0x0b339d8acca7d4f83eedd84093aef51050b3684c88f8b0b04524563bc6ea4da4, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 180 | + scratch2 := add(0x0955e49e6610c94254a4f84cfbab344598f0e71eaff4a7dd81ed95b50839c82e, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 181 | + state0 := mulmod(scratch0, scratch0, F) | |
| 182 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 183 | + state0 := add(0x06746a6156eba54426b9e22206f15abca9a6f41e6f535c6f3525401ea0654626, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 184 | + state1 := add(0x0f18f5a0ecd1423c496f3820c549c27838e5790e2bd0a196ac917c7ff32077fb, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 185 | + state2 := add(0x04f6eeca1751f7308ac59eff5beb261e4bb563583ede7bc92a738223d6f76e13, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 186 | + scratch0 := mulmod(state0, state0, F) | |
| 187 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 188 | + scratch0 := add(0x2b56973364c4c4f5c1a3ec4da3cdce038811eb116fb3e45bc1768d26fc0b3758, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 189 | + scratch1 := add(0x123769dd49d5b054dcd76b89804b1bcb8e1392b385716a5d83feb65d437f29ef, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 190 | + scratch2 := add(0x2147b424fc48c80a88ee52b91169aacea989f6446471150994257b2fb01c63e9, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 191 | + state0 := mulmod(scratch0, scratch0, F) | |
| 192 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 193 | + state0 := add(0x0fdc1f58548b85701a6c5505ea332a29647e6f34ad4243c2ea54ad897cebe54d, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 194 | + state1 := add(0x12373a8251fea004df68abcf0f7786d4bceff28c5dbbe0c3944f685cc0a0b1f2, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 195 | + state2 := add(0x21e4f4ea5f35f85bad7ea52ff742c9e8a642756b6af44203dd8a1f35c1a90035, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 196 | + scratch0 := mulmod(state0, state0, F) | |
| 197 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 198 | + scratch0 := add(0x16243916d69d2ca3dfb4722224d4c462b57366492f45e90d8a81934f1bc3b147, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 199 | + scratch1 := add(0x1efbe46dd7a578b4f66f9adbc88b4378abc21566e1a0453ca13a4159cac04ac2, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 200 | + scratch2 := add(0x07ea5e8537cf5dd08886020e23a7f387d468d5525be66f853b672cc96a88969a, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 201 | + state0 := mulmod(scratch0, scratch0, F) | |
| 202 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 203 | + state0 := add(0x05a8c4f9968b8aa3b7b478a30f9a5b63650f19a75e7ce11ca9fe16c0b76c00bc, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 204 | + state1 := add(0x20f057712cc21654fbfe59bd345e8dac3f7818c701b9c7882d9d57b72a32e83f, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 205 | + state2 := add(0x04a12ededa9dfd689672f8c67fee31636dcd8e88d01d49019bd90b33eb33db69, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 206 | + scratch0 := mulmod(state0, state0, F) | |
| 207 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 208 | + scratch0 := add(0x27e88d8c15f37dcee44f1e5425a51decbd136ce5091a6767e49ec9544ccd101a, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 209 | + scratch1 := add(0x2feed17b84285ed9b8a5c8c5e95a41f66e096619a7703223176c41ee433de4d1, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 210 | + scratch2 := add(0x1ed7cc76edf45c7c404241420f729cf394e5942911312a0d6972b8bd53aff2b8, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 211 | + state0 := mulmod(scratch0, scratch0, F) | |
| 212 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 213 | + state0 := add(0x15742e99b9bfa323157ff8c586f5660eac6783476144cdcadf2874be45466b1a, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 214 | + state1 := add(0x1aac285387f65e82c895fc6887ddf40577107454c6ec0317284f033f27d0c785, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 215 | + state2 := add(0x25851c3c845d4790f9ddadbdb6057357832e2e7a49775f71ec75a96554d67c77, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 216 | + scratch0 := mulmod(state0, state0, F) | |
| 217 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 218 | + scratch0 := add(0x15a5821565cc2ec2ce78457db197edf353b7ebba2c5523370ddccc3d9f146a67, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 219 | + scratch1 := add(0x2411d57a4813b9980efa7e31a1db5966dcf64f36044277502f15485f28c71727, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 220 | + scratch2 := add(0x002e6f8d6520cd4713e335b8c0b6d2e647e9a98e12f4cd2558828b5ef6cb4c9b, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 221 | + state0 := mulmod(scratch0, scratch0, F) | |
| 222 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 223 | + state0 := add(0x2ff7bc8f4380cde997da00b616b0fcd1af8f0e91e2fe1ed7398834609e0315d2, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 224 | + state1 := add(0x00b9831b948525595ee02724471bcd182e9521f6b7bb68f1e93be4febb0d3cbe, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 225 | + state2 := add(0x0a2f53768b8ebf6a86913b0e57c04e011ca408648a4743a87d77adbf0c9c3512, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 226 | + scratch0 := mulmod(state0, state0, F) | |
| 227 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 228 | + scratch0 := add(0x00248156142fd0373a479f91ff239e960f599ff7e94be69b7f2a290305e1198d, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 229 | + scratch1 := add(0x171d5620b87bfb1328cf8c02ab3f0c9a397196aa6a542c2350eb512a2b2bcda9, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 230 | + scratch2 := add(0x170a4f55536f7dc970087c7c10d6fad760c952172dd54dd99d1045e4ec34a808, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 231 | + state0 := mulmod(scratch0, scratch0, F) | |
| 232 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 233 | + state0 := add(0x29aba33f799fe66c2ef3134aea04336ecc37e38c1cd211ba482eca17e2dbfae1, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 234 | + state1 := add(0x1e9bc179a4fdd758fdd1bb1945088d47e70d114a03f6a0e8b5ba650369e64973, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 235 | + state2 := add(0x1dd269799b660fad58f7f4892dfb0b5afeaad869a9c4b44f9c9e1c43bdaf8f09, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 236 | + scratch0 := mulmod(state0, state0, F) | |
| 237 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 238 | + scratch0 := add(0x22cdbc8b70117ad1401181d02e15459e7ccd426fe869c7c95d1dd2cb0f24af38, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 239 | + scratch1 := add(0x0ef042e454771c533a9f57a55c503fcefd3150f52ed94a7cd5ba93b9c7dacefd, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 240 | + scratch2 := add(0x11609e06ad6c8fe2f287f3036037e8851318e8b08a0359a03b304ffca62e8284, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 241 | + state0 := mulmod(scratch0, scratch0, F) | |
| 242 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 243 | + state0 := add(0x1166d9e554616dba9e753eea427c17b7fecd58c076dfe42708b08f5b783aa9af, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 244 | + state1 := add(0x2de52989431a859593413026354413db177fbf4cd2ac0b56f855a888357ee466, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 245 | + state2 := add(0x3006eb4ffc7a85819a6da492f3a8ac1df51aee5b17b8e89d74bf01cf5f71e9ad, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 246 | + scratch0 := mulmod(state0, state0, F) | |
| 247 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 248 | + scratch0 := add(0x2af41fbb61ba8a80fdcf6fff9e3f6f422993fe8f0a4639f962344c8225145086, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 249 | + scratch1 := add(0x119e684de476155fe5a6b41a8ebc85db8718ab27889e85e781b214bace4827c3, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 250 | + scratch2 := add(0x1835b786e2e8925e188bea59ae363537b51248c23828f047cff784b97b3fd800, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 251 | + state0 := mulmod(scratch0, scratch0, F) | |
| 252 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 253 | + state0 := add(0x28201a34c594dfa34d794996c6433a20d152bac2a7905c926c40e285ab32eeb6, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 254 | + state1 := add(0x083efd7a27d1751094e80fefaf78b000864c82eb571187724a761f88c22cc4e7, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 255 | + state2 := add(0x0b6f88a3577199526158e61ceea27be811c16df7774dd8519e079564f61fd13b, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 256 | + scratch0 := mulmod(state0, state0, F) | |
| 257 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 258 | + scratch0 := add(0x0ec868e6d15e51d9644f66e1d6471a94589511ca00d29e1014390e6ee4254f5b, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 259 | + scratch1 := add(0x2af33e3f866771271ac0c9b3ed2e1142ecd3e74b939cd40d00d937ab84c98591, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 260 | + scratch2 := add(0x0b520211f904b5e7d09b5d961c6ace7734568c547dd6858b364ce5e47951f178, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 261 | + state0 := mulmod(scratch0, scratch0, F) | |
| 262 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 263 | + state0 := add(0x0b2d722d0919a1aad8db58f10062a92ea0c56ac4270e822cca228620188a1d40, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 264 | + state1 := add(0x1f790d4d7f8cf094d980ceb37c2453e957b54a9991ca38bbe0061d1ed6e562d4, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 265 | + state2 := add(0x0171eb95dfbf7d1eaea97cd385f780150885c16235a2a6a8da92ceb01e504233, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 266 | + scratch0 := mulmod(state0, state0, F) | |
| 267 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 268 | + scratch0 := add(0x0c2d0e3b5fd57549329bf6885da66b9b790b40defd2c8650762305381b168873, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 269 | + scratch1 := add(0x1162fb28689c27154e5a8228b4e72b377cbcafa589e283c35d3803054407a18d, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 270 | + scratch2 := add(0x2f1459b65dee441b64ad386a91e8310f282c5a92a89e19921623ef8249711bc0, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 271 | + state0 := mulmod(scratch0, scratch0, F) | |
| 272 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 273 | + state0 := add(0x1e6ff3216b688c3d996d74367d5cd4c1bc489d46754eb712c243f70d1b53cfbb, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 274 | + state1 := add(0x01ca8be73832b8d0681487d27d157802d741a6f36cdc2a0576881f9326478875, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 275 | + state2 := add(0x1f7735706ffe9fc586f976d5bdf223dc680286080b10cea00b9b5de315f9650e, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 276 | + scratch0 := mulmod(state0, state0, F) | |
| 277 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 278 | + scratch0 := add(0x2522b60f4ea3307640a0c2dce041fba921ac10a3d5f096ef4745ca838285f019, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 279 | + scratch1 := add(0x23f0bee001b1029d5255075ddc957f833418cad4f52b6c3f8ce16c235572575b, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 280 | + scratch2 := add(0x2bc1ae8b8ddbb81fcaac2d44555ed5685d142633e9df905f66d9401093082d59, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 281 | + state0 := mulmod(scratch0, scratch0, F) | |
| 282 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 283 | + state0 := add(0x0f9406b8296564a37304507b8dba3ed162371273a07b1fc98011fcd6ad72205f, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 284 | + state1 := add(0x2360a8eb0cc7defa67b72998de90714e17e75b174a52ee4acb126c8cd995f0a8, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 285 | + state2 := add(0x15871a5cddead976804c803cbaef255eb4815a5e96df8b006dcbbc2767f88948, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 286 | + scratch0 := mulmod(state0, state0, F) | |
| 287 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 288 | + scratch0 := add(0x193a56766998ee9e0a8652dd2f3b1da0362f4f54f72379544f957ccdeefb420f, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 289 | + scratch1 := add(0x2a394a43934f86982f9be56ff4fab1703b2e63c8ad334834e4309805e777ae0f, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 290 | + scratch2 := add(0x1859954cfeb8695f3e8b635dcb345192892cd11223443ba7b4166e8876c0d142, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 291 | + state0 := mulmod(scratch0, scratch0, F) | |
| 292 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 293 | + state0 := add(0x04e1181763050e58013444dbcb99f1902b11bc25d90bbdca408d3819f4fed32b, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 294 | + state1 := add(0x0fdb253dee83869d40c335ea64de8c5bb10eb82db08b5e8b1f5e5552bfd05f23, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 295 | + state2 := add(0x058cbe8a9a5027bdaa4efb623adead6275f08686f1c08984a9d7c5bae9b4f1c0, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 296 | + scratch0 := mulmod(state0, state0, F) | |
| 297 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 298 | + scratch0 := add(0x1382edce9971e186497eadb1aeb1f52b23b4b83bef023ab0d15228b4cceca59a, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 299 | + scratch1 := add(0x03464990f045c6ee0819ca51fd11b0be7f61b8eb99f14b77e1e6634601d9e8b5, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 300 | + scratch2 := add(0x23f7bfc8720dc296fff33b41f98ff83c6fcab4605db2eb5aaa5bc137aeb70a58, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 301 | + state0 := mulmod(scratch0, scratch0, F) | |
| 302 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 303 | + state0 := add(0x0a59a158e3eec2117e6e94e7f0e9decf18c3ffd5e1531a9219636158bbaf62f2, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 304 | + state1 := add(0x06ec54c80381c052b58bf23b312ffd3ce2c4eba065420af8f4c23ed0075fd07b, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 305 | + state2 := add(0x118872dc832e0eb5476b56648e867ec8b09340f7a7bcb1b4962f0ff9ed1f9d01, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 306 | + scratch0 := mulmod(state0, state0, F) | |
| 307 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 308 | + scratch0 := add(0x13d69fa127d834165ad5c7cba7ad59ed52e0b0f0e42d7fea95e1906b520921b1, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 309 | + scratch1 := add(0x169a177f63ea681270b1c6877a73d21bde143942fb71dc55fd8a49f19f10c77b, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 310 | + scratch2 := add(0x04ef51591c6ead97ef42f287adce40d93abeb032b922f66ffb7e9a5a7450544d, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 311 | + state0 := mulmod(scratch0, scratch0, F) | |
| 312 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 313 | + state0 := add(0x256e175a1dc079390ecd7ca703fb2e3b19ec61805d4f03ced5f45ee6dd0f69ec, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 314 | + state1 := add(0x30102d28636abd5fe5f2af412ff6004f75cc360d3205dd2da002813d3e2ceeb2, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 315 | + state2 := add(0x10998e42dfcd3bbf1c0714bc73eb1bf40443a3fa99bef4a31fd31be182fcc792, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 316 | + scratch0 := mulmod(state0, state0, F) | |
| 317 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 318 | + scratch0 := add(0x193edd8e9fcf3d7625fa7d24b598a1d89f3362eaf4d582efecad76f879e36860, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 319 | + scratch1 := add(0x18168afd34f2d915d0368ce80b7b3347d1c7a561ce611425f2664d7aa51f0b5d, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 320 | + scratch2 := add(0x29383c01ebd3b6ab0c017656ebe658b6a328ec77bc33626e29e2e95b33ea6111, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 321 | + state0 := mulmod(scratch0, scratch0, F) | |
| 322 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 323 | + state0 := add(0x10646d2f2603de39a1f4ae5e7771a64a702db6e86fb76ab600bf573f9010c711, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 324 | + state1 := add(0x0beb5e07d1b27145f575f1395a55bf132f90c25b40da7b3864d0242dcb1117fb, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 325 | + state2 := add(0x16d685252078c133dc0d3ecad62b5c8830f95bb2e54b59abdffbf018d96fa336, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 326 | + scratch0 := mulmod(state0, state0, F) | |
| 327 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 328 | + scratch0 := add(0x0a6abd1d833938f33c74154e0404b4b40a555bbbec21ddfafd672dd62047f01a, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 329 | + scratch1 := add(0x1a679f5d36eb7b5c8ea12a4c2dedc8feb12dffeec450317270a6f19b34cf1860, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 330 | + scratch2 := add(0x0980fb233bd456c23974d50e0ebfde4726a423eada4e8f6ffbc7592e3f1b93d6, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 331 | + state0 := mulmod(scratch0, scratch0, F) | |
| 332 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 333 | + state0 := add(0x161b42232e61b84cbf1810af93a38fc0cece3d5628c9282003ebacb5c312c72b, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 334 | + state1 := add(0x0ada10a90c7f0520950f7d47a60d5e6a493f09787f1564e5d09203db47de1a0b, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 335 | + state2 := add(0x1a730d372310ba82320345a29ac4238ed3f07a8a2b4e121bb50ddb9af407f451, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 336 | + scratch0 := mulmod(state0, state0, F) | |
| 337 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 338 | + scratch0 := add(0x2c8120f268ef054f817064c369dda7ea908377feaba5c4dffbda10ef58e8c556, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 339 | + scratch1 := add(0x1c7c8824f758753fa57c00789c684217b930e95313bcb73e6e7b8649a4968f70, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 340 | + scratch2 := add(0x2cd9ed31f5f8691c8e39e4077a74faa0f400ad8b491eb3f7b47b27fa3fd1cf77, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 341 | + state0 := mulmod(scratch0, scratch0, F) | |
| 342 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 343 | + state0 := add(0x23ff4f9d46813457cf60d92f57618399a5e022ac321ca550854ae23918a22eea, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 344 | + state1 := add(0x09945a5d147a4f66ceece6405dddd9d0af5a2c5103529407dff1ea58f180426d, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 345 | + state2 := add(0x188d9c528025d4c2b67660c6b771b90f7c7da6eaa29d3f268a6dd223ec6fc630, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 346 | + scratch0 := mulmod(state0, state0, F) | |
| 347 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 348 | + scratch0 := add(0x3050e37996596b7f81f68311431d8734dba7d926d3633595e0c0d8ddf4f0f47f, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 349 | + scratch1 := add(0x15af1169396830a91600ca8102c35c426ceae5461e3f95d89d829518d30afd78, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 350 | + scratch2 := add(0x1da6d09885432ea9a06d9f37f873d985dae933e351466b2904284da3320d8acc, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 351 | + state0 := mulmod(scratch0, scratch0, F) | |
| 352 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 353 | + state0 := add(0x2796ea90d269af29f5f8acf33921124e4e4fad3dbe658945e546ee411ddaa9cb, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 354 | + state1 := add(0x202d7dd1da0f6b4b0325c8b3307742f01e15612ec8e9304a7cb0319e01d32d60, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 355 | + state2 := add(0x096d6790d05bb759156a952ba263d672a2d7f9c788f4c831a29dace4c0f8be5f, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 356 | + scratch0 := mulmod(state0, state0, F) | |
| 357 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 358 | + scratch0 := add(0x054efa1f65b0fce283808965275d877b438da23ce5b13e1963798cb1447d25a4, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 359 | + scratch1 := add(0x1b162f83d917e93edb3308c29802deb9d8aa690113b2e14864ccf6e18e4165f1, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 360 | + scratch2 := add(0x21e5241e12564dd6fd9f1cdd2a0de39eedfefc1466cc568ec5ceb745a0506edc, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 361 | + state0 := mulmod(scratch0, scratch0, F) | |
| 362 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 363 | + state0 := mulmod(scratch1, scratch1, F) | |
| 364 | + scratch1 := mulmod(mulmod(state0, state0, F), scratch1, F) | |
| 365 | + state0 := mulmod(scratch2, scratch2, F) | |
| 366 | + scratch2 := mulmod(mulmod(state0, state0, F), scratch2, F) | |
| 367 | + state0 := add(0x1cfb5662e8cf5ac9226a80ee17b36abecb73ab5f87e161927b4349e10e4bdf08, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 368 | + state1 := add(0x0f21177e302a771bbae6d8d1ecb373b62c99af346220ac0129c53f666eb24100, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 369 | + state2 := add(0x1671522374606992affb0dd7f71b12bec4236aede6290546bcef7e1f515c2320, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 370 | + scratch0 := mulmod(state0, state0, F) | |
| 371 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 372 | + scratch0 := mulmod(state1, state1, F) | |
| 373 | + state1 := mulmod(mulmod(scratch0, scratch0, F), state1, F) | |
| 374 | + scratch0 := mulmod(state2, state2, F) | |
| 375 | + state2 := mulmod(mulmod(scratch0, scratch0, F), state2, F) | |
| 376 | + scratch0 := add(0x0fa3ec5b9488259c2eb4cf24501bfad9be2ec9e42c5cc8ccd419d2a692cad870, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | |
| 377 | + scratch1 := add(0x193c0e04e0bd298357cb266c1506080ed36edce85c648cc085e8c57b1ab54bba, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | |
| 378 | + scratch2 := add(0x102adf8ef74735a27e9128306dcbc3c99f6f7291cd406578ce14ea2adaba68f8, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | |
| 379 | + state0 := mulmod(scratch0, scratch0, F) | |
| 380 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | |
| 381 | + state0 := mulmod(scratch1, scratch1, F) | |
| 382 | + scratch1 := mulmod(mulmod(state0, state0, F), scratch1, F) | |
| 383 | + state0 := mulmod(scratch2, scratch2, F) | |
| 384 | + scratch2 := mulmod(mulmod(state0, state0, F), scratch2, F) | |
| 385 | + state0 := add(0x0fe0af7858e49859e2a54d6f1ad945b1316aa24bfbdd23ae40a6d0cb70c3eab1, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | |
| 386 | + state1 := add(0x216f6717bbc7dedb08536a2220843f4e2da5f1daa9ebdefde8a5ea7344798d22, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | |
| 387 | + state2 := add(0x1da55cc900f0d21f4a3e694391918a1b3c23b2ac773c6b3ef88e2e4228325161, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | |
| 388 | + scratch0 := mulmod(state0, state0, F) | |
| 389 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | |
| 390 | + scratch0 := mulmod(state1, state1, F) | |
| 391 | + state1 := mulmod(mulmod(scratch0, scratch0, F), state1, F) | |
| 392 | + scratch0 := mulmod(state2, state2, F) | |
| 393 | + state2 := mulmod(mulmod(scratch0, scratch0, F), state2, F) | |
| 394 | + | |
| 395 | + r := mod(add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F)), F) | |
| 396 | + } | |
| 397 | + } | |
| 398 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,398 @@ | |||
| 1 | +/// SPDX-License-Identifier: MIT | ||
| 2 | +// Vendored from poseidon-solidity 0.0.5 (https://github.com/vimwitch/poseidon-solidity), with the | ||
| 3 | +// entrypoint changed and the round function untouched: | ||
| 4 | +// | ||
| 5 | +// - `hash` took its inputs from memory at 0x80 and 0xa0 and ended in `return(0, 0x20)`, which is how a | ||
| 6 | +// `public` library function that is `delegatecall`ed behaves. It now takes `a` and `b` and assigns | ||
| 7 | +// `r`, and is `internal`, so it is inlined. A public library function has to be linked, and linking | ||
| 8 | +// puts an address in `Love`'s creation code, which has to stay identical on every chain. | ||
| 9 | +// | ||
| 10 | +// Its output matches circomlib's `Poseidon(2)`, which is what the circuit and `zk/lib.mjs` use. | ||
| 11 | +pragma solidity >=0.7.0; | ||
| 12 | + | ||
| 13 | +library PoseidonT3 { | ||
| 14 | + uint constant M00 = 0x109b7f411ba0e4c9b2b70caf5c36a7b194be7c11ad24378bfedb68592ba8118b; | ||
| 15 | + uint constant M01 = 0x2969f27eed31a480b9c36c764379dbca2cc8fdd1415c3dded62940bcde0bd771; | ||
| 16 | + uint constant M02 = 0x143021ec686a3f330d5f9e654638065ce6cd79e28c5b3753326244ee65a1b1a7; | ||
| 17 | + uint constant M10 = 0x16ed41e13bb9c0c66ae119424fddbcbc9314dc9fdbdeea55d6c64543dc4903e0; | ||
| 18 | + uint constant M11 = 0x2e2419f9ec02ec394c9871c832963dc1b89d743c8c7b964029b2311687b1fe23; | ||
| 19 | + uint constant M12 = 0x176cc029695ad02582a70eff08a6fd99d057e12e58e7d7b6b16cdfabc8ee2911; | ||
| 20 | + | ||
| 21 | + // See here for a simplified implementation: https://github.com/vimwitch/poseidon-solidity/blob/e57becdabb65d99fdc586fe1e1e09e7108202d53/contracts/Poseidon.sol#L40 | ||
| 22 | + // Inspired by: https://github.com/iden3/circomlibjs/blob/v0.0.8/src/poseidon_slow.js | ||
| 23 | + function hash(uint a, uint b) internal pure returns (uint r) { | ||
| 24 | + assembly { | ||
| 25 | + let F := 21888242871839275222246405745257275088548364400416034343698204186575808495617 | ||
| 26 | + let M20 := 0x2b90bba00fca0589f617e7dcbfe82e0df706ab640ceb247b791a93b74e36736d | ||
| 27 | + let M21 := 0x101071f0032379b697315876690f053d148d4e109f5fb065c8aacc55a0f89bfa | ||
| 28 | + let M22 := 0x19a3fc0a56702bf417ba7fee3802593fa644470307043f7773279cd71d25d5e0 | ||
| 29 | + | ||
| 30 | + // load the inputs from memory | ||
| 31 | + let state1 := add(mod(a, F), 0x00f1445235f2148c5986587169fc1bcd887b08d4d00868df5696fff40956e864) | ||
| 32 | + let state2 := add(mod(b, F), 0x08dff3487e8ac99e1f29a058d0fa80b930c728730b7ab36ce879f3890ecf73f5) | ||
| 33 | + let scratch0 := mulmod(state1, state1, F) | ||
| 34 | + state1 := mulmod(mulmod(scratch0, scratch0, F), state1, F) | ||
| 35 | + scratch0 := mulmod(state2, state2, F) | ||
| 36 | + state2 := mulmod(mulmod(scratch0, scratch0, F), state2, F) | ||
| 37 | + scratch0 := add( | ||
| 38 | + 0x2f27be690fdaee46c3ce28f7532b13c856c35342c84bda6e20966310fadc01d0, | ||
| 39 | + add(add(15452833169820924772166449970675545095234312153403844297388521437673434406763, mulmod(state1, M10, F)), mulmod(state2, M20, F)) | ||
| 40 | + ) | ||
| 41 | + let scratch1 := add( | ||
| 42 | + 0x2b2ae1acf68b7b8d2416bebf3d4f6234b763fe04b8043ee48b8327bebca16cf2, | ||
| 43 | + add(add(18674271267752038776579386132900109523609358935013267566297499497165104279117, mulmod(state1, M11, F)), mulmod(state2, M21, F)) | ||
| 44 | + ) | ||
| 45 | + let scratch2 := add( | ||
| 46 | + 0x0319d062072bef7ecca5eac06f97d4d55952c175ab6b03eae64b44c7dbf11cfa, | ||
| 47 | + add(add(14817777843080276494683266178512808687156649753153012854386334860566696099579, mulmod(state1, M12, F)), mulmod(state2, M22, F)) | ||
| 48 | + ) | ||
| 49 | + let state0 := mulmod(scratch0, scratch0, F) | ||
| 50 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 51 | + state0 := mulmod(scratch1, scratch1, F) | ||
| 52 | + scratch1 := mulmod(mulmod(state0, state0, F), scratch1, F) | ||
| 53 | + state0 := mulmod(scratch2, scratch2, F) | ||
| 54 | + scratch2 := mulmod(mulmod(state0, state0, F), scratch2, F) | ||
| 55 | + state0 := add(0x28813dcaebaeaa828a376df87af4a63bc8b7bf27ad49c6298ef7b387bf28526d, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 56 | + state1 := add(0x2727673b2ccbc903f181bf38e1c1d40d2033865200c352bc150928adddf9cb78, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 57 | + state2 := add(0x234ec45ca27727c2e74abd2b2a1494cd6efbd43e340587d6b8fb9e31e65cc632, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 58 | + scratch0 := mulmod(state0, state0, F) | ||
| 59 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 60 | + scratch0 := mulmod(state1, state1, F) | ||
| 61 | + state1 := mulmod(mulmod(scratch0, scratch0, F), state1, F) | ||
| 62 | + scratch0 := mulmod(state2, state2, F) | ||
| 63 | + state2 := mulmod(mulmod(scratch0, scratch0, F), state2, F) | ||
| 64 | + scratch0 := add(0x15b52534031ae18f7f862cb2cf7cf760ab10a8150a337b1ccd99ff6e8797d428, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 65 | + scratch1 := add(0x0dc8fad6d9e4b35f5ed9a3d186b79ce38e0e8a8d1b58b132d701d4eecf68d1f6, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 66 | + scratch2 := add(0x1bcd95ffc211fbca600f705fad3fb567ea4eb378f62e1fec97805518a47e4d9c, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 67 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 68 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 69 | + state0 := mulmod(scratch1, scratch1, F) | ||
| 70 | + scratch1 := mulmod(mulmod(state0, state0, F), scratch1, F) | ||
| 71 | + state0 := mulmod(scratch2, scratch2, F) | ||
| 72 | + scratch2 := mulmod(mulmod(state0, state0, F), scratch2, F) | ||
| 73 | + state0 := add(0x10520b0ab721cadfe9eff81b016fc34dc76da36c2578937817cb978d069de559, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 74 | + state1 := add(0x1f6d48149b8e7f7d9b257d8ed5fbbaf42932498075fed0ace88a9eb81f5627f6, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 75 | + state2 := add(0x1d9655f652309014d29e00ef35a2089bfff8dc1c816f0dc9ca34bdb5460c8705, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 76 | + scratch0 := mulmod(state0, state0, F) | ||
| 77 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 78 | + scratch0 := add(0x04df5a56ff95bcafb051f7b1cd43a99ba731ff67e47032058fe3d4185697cc7d, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 79 | + scratch1 := add(0x0672d995f8fff640151b3d290cedaf148690a10a8c8424a7f6ec282b6e4be828, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 80 | + scratch2 := add(0x099952b414884454b21200d7ffafdd5f0c9a9dcc06f2708e9fc1d8209b5c75b9, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 81 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 82 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 83 | + state0 := add(0x052cba2255dfd00c7c483143ba8d469448e43586a9b4cd9183fd0e843a6b9fa6, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 84 | + state1 := add(0x0b8badee690adb8eb0bd74712b7999af82de55707251ad7716077cb93c464ddc, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 85 | + state2 := add(0x119b1590f13307af5a1ee651020c07c749c15d60683a8050b963d0a8e4b2bdd1, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 86 | + scratch0 := mulmod(state0, state0, F) | ||
| 87 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 88 | + scratch0 := add(0x03150b7cd6d5d17b2529d36be0f67b832c4acfc884ef4ee5ce15be0bfb4a8d09, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 89 | + scratch1 := add(0x2cc6182c5e14546e3cf1951f173912355374efb83d80898abe69cb317c9ea565, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 90 | + scratch2 := add(0x005032551e6378c450cfe129a404b3764218cadedac14e2b92d2cd73111bf0f9, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 91 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 92 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 93 | + state0 := add(0x233237e3289baa34bb147e972ebcb9516469c399fcc069fb88f9da2cc28276b5, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 94 | + state1 := add(0x05c8f4f4ebd4a6e3c980d31674bfbe6323037f21b34ae5a4e80c2d4c24d60280, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 95 | + state2 := add(0x0a7b1db13042d396ba05d818a319f25252bcf35ef3aeed91ee1f09b2590fc65b, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 96 | + scratch0 := mulmod(state0, state0, F) | ||
| 97 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 98 | + scratch0 := add(0x2a73b71f9b210cf5b14296572c9d32dbf156e2b086ff47dc5df542365a404ec0, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 99 | + scratch1 := add(0x1ac9b0417abcc9a1935107e9ffc91dc3ec18f2c4dbe7f22976a760bb5c50c460, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 100 | + scratch2 := add(0x12c0339ae08374823fabb076707ef479269f3e4d6cb104349015ee046dc93fc0, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 101 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 102 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 103 | + state0 := add(0x0b7475b102a165ad7f5b18db4e1e704f52900aa3253baac68246682e56e9a28e, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 104 | + state1 := add(0x037c2849e191ca3edb1c5e49f6e8b8917c843e379366f2ea32ab3aa88d7f8448, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 105 | + state2 := add(0x05a6811f8556f014e92674661e217e9bd5206c5c93a07dc145fdb176a716346f, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 106 | + scratch0 := mulmod(state0, state0, F) | ||
| 107 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 108 | + scratch0 := add(0x29a795e7d98028946e947b75d54e9f044076e87a7b2883b47b675ef5f38bd66e, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 109 | + scratch1 := add(0x20439a0c84b322eb45a3857afc18f5826e8c7382c8a1585c507be199981fd22f, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 110 | + scratch2 := add(0x2e0ba8d94d9ecf4a94ec2050c7371ff1bb50f27799a84b6d4a2a6f2a0982c887, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 111 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 112 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 113 | + state0 := add(0x143fd115ce08fb27ca38eb7cce822b4517822cd2109048d2e6d0ddcca17d71c8, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 114 | + state1 := add(0x0c64cbecb1c734b857968dbbdcf813cdf8611659323dbcbfc84323623be9caf1, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 115 | + state2 := add(0x028a305847c683f646fca925c163ff5ae74f348d62c2b670f1426cef9403da53, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 116 | + scratch0 := mulmod(state0, state0, F) | ||
| 117 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 118 | + scratch0 := add(0x2e4ef510ff0b6fda5fa940ab4c4380f26a6bcb64d89427b824d6755b5db9e30c, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 119 | + scratch1 := add(0x0081c95bc43384e663d79270c956ce3b8925b4f6d033b078b96384f50579400e, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 120 | + scratch2 := add(0x2ed5f0c91cbd9749187e2fade687e05ee2491b349c039a0bba8a9f4023a0bb38, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 121 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 122 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 123 | + state0 := add(0x30509991f88da3504bbf374ed5aae2f03448a22c76234c8c990f01f33a735206, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 124 | + state1 := add(0x1c3f20fd55409a53221b7c4d49a356b9f0a1119fb2067b41a7529094424ec6ad, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 125 | + state2 := add(0x10b4e7f3ab5df003049514459b6e18eec46bb2213e8e131e170887b47ddcb96c, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 126 | + scratch0 := mulmod(state0, state0, F) | ||
| 127 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 128 | + scratch0 := add(0x2a1982979c3ff7f43ddd543d891c2abddd80f804c077d775039aa3502e43adef, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 129 | + scratch1 := add(0x1c74ee64f15e1db6feddbead56d6d55dba431ebc396c9af95cad0f1315bd5c91, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 130 | + scratch2 := add(0x07533ec850ba7f98eab9303cace01b4b9e4f2e8b82708cfa9c2fe45a0ae146a0, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 131 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 132 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 133 | + state0 := add(0x21576b438e500449a151e4eeaf17b154285c68f42d42c1808a11abf3764c0750, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 134 | + state1 := add(0x2f17c0559b8fe79608ad5ca193d62f10bce8384c815f0906743d6930836d4a9e, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 135 | + state2 := add(0x2d477e3862d07708a79e8aae946170bc9775a4201318474ae665b0b1b7e2730e, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 136 | + scratch0 := mulmod(state0, state0, F) | ||
| 137 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 138 | + scratch0 := add(0x162f5243967064c390e095577984f291afba2266c38f5abcd89be0f5b2747eab, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 139 | + scratch1 := add(0x2b4cb233ede9ba48264ecd2c8ae50d1ad7a8596a87f29f8a7777a70092393311, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 140 | + scratch2 := add(0x2c8fbcb2dd8573dc1dbaf8f4622854776db2eece6d85c4cf4254e7c35e03b07a, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 141 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 142 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 143 | + state0 := add(0x1d6f347725e4816af2ff453f0cd56b199e1b61e9f601e9ade5e88db870949da9, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 144 | + state1 := add(0x204b0c397f4ebe71ebc2d8b3df5b913df9e6ac02b68d31324cd49af5c4565529, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 145 | + state2 := add(0x0c4cb9dc3c4fd8174f1149b3c63c3c2f9ecb827cd7dc25534ff8fb75bc79c502, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 146 | + scratch0 := mulmod(state0, state0, F) | ||
| 147 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 148 | + scratch0 := add(0x174ad61a1448c899a25416474f4930301e5c49475279e0639a616ddc45bc7b54, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 149 | + scratch1 := add(0x1a96177bcf4d8d89f759df4ec2f3cde2eaaa28c177cc0fa13a9816d49a38d2ef, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 150 | + scratch2 := add(0x066d04b24331d71cd0ef8054bc60c4ff05202c126a233c1a8242ace360b8a30a, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 151 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 152 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 153 | + state0 := add(0x2a4c4fc6ec0b0cf52195782871c6dd3b381cc65f72e02ad527037a62aa1bd804, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 154 | + state1 := add(0x13ab2d136ccf37d447e9f2e14a7cedc95e727f8446f6d9d7e55afc01219fd649, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 155 | + state2 := add(0x1121552fca26061619d24d843dc82769c1b04fcec26f55194c2e3e869acc6a9a, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 156 | + scratch0 := mulmod(state0, state0, F) | ||
| 157 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 158 | + scratch0 := add(0x00ef653322b13d6c889bc81715c37d77a6cd267d595c4a8909a5546c7c97cff1, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 159 | + scratch1 := add(0x0e25483e45a665208b261d8ba74051e6400c776d652595d9845aca35d8a397d3, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 160 | + scratch2 := add(0x29f536dcb9dd7682245264659e15d88e395ac3d4dde92d8c46448db979eeba89, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 161 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 162 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 163 | + state0 := add(0x2a56ef9f2c53febadfda33575dbdbd885a124e2780bbea170e456baace0fa5be, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 164 | + state1 := add(0x1c8361c78eb5cf5decfb7a2d17b5c409f2ae2999a46762e8ee416240a8cb9af1, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 165 | + state2 := add(0x151aff5f38b20a0fc0473089aaf0206b83e8e68a764507bfd3d0ab4be74319c5, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 166 | + scratch0 := mulmod(state0, state0, F) | ||
| 167 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 168 | + scratch0 := add(0x04c6187e41ed881dc1b239c88f7f9d43a9f52fc8c8b6cdd1e76e47615b51f100, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 169 | + scratch1 := add(0x13b37bd80f4d27fb10d84331f6fb6d534b81c61ed15776449e801b7ddc9c2967, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 170 | + scratch2 := add(0x01a5c536273c2d9df578bfbd32c17b7a2ce3664c2a52032c9321ceb1c4e8a8e4, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 171 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 172 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 173 | + state0 := add(0x2ab3561834ca73835ad05f5d7acb950b4a9a2c666b9726da832239065b7c3b02, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 174 | + state1 := add(0x1d4d8ec291e720db200fe6d686c0d613acaf6af4e95d3bf69f7ed516a597b646, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 175 | + state2 := add(0x041294d2cc484d228f5784fe7919fd2bb925351240a04b711514c9c80b65af1d, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 176 | + scratch0 := mulmod(state0, state0, F) | ||
| 177 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 178 | + scratch0 := add(0x154ac98e01708c611c4fa715991f004898f57939d126e392042971dd90e81fc6, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 179 | + scratch1 := add(0x0b339d8acca7d4f83eedd84093aef51050b3684c88f8b0b04524563bc6ea4da4, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 180 | + scratch2 := add(0x0955e49e6610c94254a4f84cfbab344598f0e71eaff4a7dd81ed95b50839c82e, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 181 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 182 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 183 | + state0 := add(0x06746a6156eba54426b9e22206f15abca9a6f41e6f535c6f3525401ea0654626, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 184 | + state1 := add(0x0f18f5a0ecd1423c496f3820c549c27838e5790e2bd0a196ac917c7ff32077fb, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 185 | + state2 := add(0x04f6eeca1751f7308ac59eff5beb261e4bb563583ede7bc92a738223d6f76e13, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 186 | + scratch0 := mulmod(state0, state0, F) | ||
| 187 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 188 | + scratch0 := add(0x2b56973364c4c4f5c1a3ec4da3cdce038811eb116fb3e45bc1768d26fc0b3758, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 189 | + scratch1 := add(0x123769dd49d5b054dcd76b89804b1bcb8e1392b385716a5d83feb65d437f29ef, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 190 | + scratch2 := add(0x2147b424fc48c80a88ee52b91169aacea989f6446471150994257b2fb01c63e9, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 191 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 192 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 193 | + state0 := add(0x0fdc1f58548b85701a6c5505ea332a29647e6f34ad4243c2ea54ad897cebe54d, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 194 | + state1 := add(0x12373a8251fea004df68abcf0f7786d4bceff28c5dbbe0c3944f685cc0a0b1f2, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 195 | + state2 := add(0x21e4f4ea5f35f85bad7ea52ff742c9e8a642756b6af44203dd8a1f35c1a90035, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 196 | + scratch0 := mulmod(state0, state0, F) | ||
| 197 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 198 | + scratch0 := add(0x16243916d69d2ca3dfb4722224d4c462b57366492f45e90d8a81934f1bc3b147, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 199 | + scratch1 := add(0x1efbe46dd7a578b4f66f9adbc88b4378abc21566e1a0453ca13a4159cac04ac2, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 200 | + scratch2 := add(0x07ea5e8537cf5dd08886020e23a7f387d468d5525be66f853b672cc96a88969a, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 201 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 202 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 203 | + state0 := add(0x05a8c4f9968b8aa3b7b478a30f9a5b63650f19a75e7ce11ca9fe16c0b76c00bc, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 204 | + state1 := add(0x20f057712cc21654fbfe59bd345e8dac3f7818c701b9c7882d9d57b72a32e83f, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 205 | + state2 := add(0x04a12ededa9dfd689672f8c67fee31636dcd8e88d01d49019bd90b33eb33db69, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 206 | + scratch0 := mulmod(state0, state0, F) | ||
| 207 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 208 | + scratch0 := add(0x27e88d8c15f37dcee44f1e5425a51decbd136ce5091a6767e49ec9544ccd101a, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 209 | + scratch1 := add(0x2feed17b84285ed9b8a5c8c5e95a41f66e096619a7703223176c41ee433de4d1, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 210 | + scratch2 := add(0x1ed7cc76edf45c7c404241420f729cf394e5942911312a0d6972b8bd53aff2b8, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 211 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 212 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 213 | + state0 := add(0x15742e99b9bfa323157ff8c586f5660eac6783476144cdcadf2874be45466b1a, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 214 | + state1 := add(0x1aac285387f65e82c895fc6887ddf40577107454c6ec0317284f033f27d0c785, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 215 | + state2 := add(0x25851c3c845d4790f9ddadbdb6057357832e2e7a49775f71ec75a96554d67c77, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 216 | + scratch0 := mulmod(state0, state0, F) | ||
| 217 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 218 | + scratch0 := add(0x15a5821565cc2ec2ce78457db197edf353b7ebba2c5523370ddccc3d9f146a67, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 219 | + scratch1 := add(0x2411d57a4813b9980efa7e31a1db5966dcf64f36044277502f15485f28c71727, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 220 | + scratch2 := add(0x002e6f8d6520cd4713e335b8c0b6d2e647e9a98e12f4cd2558828b5ef6cb4c9b, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 221 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 222 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 223 | + state0 := add(0x2ff7bc8f4380cde997da00b616b0fcd1af8f0e91e2fe1ed7398834609e0315d2, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 224 | + state1 := add(0x00b9831b948525595ee02724471bcd182e9521f6b7bb68f1e93be4febb0d3cbe, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 225 | + state2 := add(0x0a2f53768b8ebf6a86913b0e57c04e011ca408648a4743a87d77adbf0c9c3512, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 226 | + scratch0 := mulmod(state0, state0, F) | ||
| 227 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 228 | + scratch0 := add(0x00248156142fd0373a479f91ff239e960f599ff7e94be69b7f2a290305e1198d, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 229 | + scratch1 := add(0x171d5620b87bfb1328cf8c02ab3f0c9a397196aa6a542c2350eb512a2b2bcda9, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 230 | + scratch2 := add(0x170a4f55536f7dc970087c7c10d6fad760c952172dd54dd99d1045e4ec34a808, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 231 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 232 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 233 | + state0 := add(0x29aba33f799fe66c2ef3134aea04336ecc37e38c1cd211ba482eca17e2dbfae1, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 234 | + state1 := add(0x1e9bc179a4fdd758fdd1bb1945088d47e70d114a03f6a0e8b5ba650369e64973, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 235 | + state2 := add(0x1dd269799b660fad58f7f4892dfb0b5afeaad869a9c4b44f9c9e1c43bdaf8f09, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 236 | + scratch0 := mulmod(state0, state0, F) | ||
| 237 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 238 | + scratch0 := add(0x22cdbc8b70117ad1401181d02e15459e7ccd426fe869c7c95d1dd2cb0f24af38, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 239 | + scratch1 := add(0x0ef042e454771c533a9f57a55c503fcefd3150f52ed94a7cd5ba93b9c7dacefd, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 240 | + scratch2 := add(0x11609e06ad6c8fe2f287f3036037e8851318e8b08a0359a03b304ffca62e8284, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 241 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 242 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 243 | + state0 := add(0x1166d9e554616dba9e753eea427c17b7fecd58c076dfe42708b08f5b783aa9af, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 244 | + state1 := add(0x2de52989431a859593413026354413db177fbf4cd2ac0b56f855a888357ee466, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 245 | + state2 := add(0x3006eb4ffc7a85819a6da492f3a8ac1df51aee5b17b8e89d74bf01cf5f71e9ad, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 246 | + scratch0 := mulmod(state0, state0, F) | ||
| 247 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 248 | + scratch0 := add(0x2af41fbb61ba8a80fdcf6fff9e3f6f422993fe8f0a4639f962344c8225145086, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 249 | + scratch1 := add(0x119e684de476155fe5a6b41a8ebc85db8718ab27889e85e781b214bace4827c3, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 250 | + scratch2 := add(0x1835b786e2e8925e188bea59ae363537b51248c23828f047cff784b97b3fd800, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 251 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 252 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 253 | + state0 := add(0x28201a34c594dfa34d794996c6433a20d152bac2a7905c926c40e285ab32eeb6, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 254 | + state1 := add(0x083efd7a27d1751094e80fefaf78b000864c82eb571187724a761f88c22cc4e7, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 255 | + state2 := add(0x0b6f88a3577199526158e61ceea27be811c16df7774dd8519e079564f61fd13b, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 256 | + scratch0 := mulmod(state0, state0, F) | ||
| 257 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 258 | + scratch0 := add(0x0ec868e6d15e51d9644f66e1d6471a94589511ca00d29e1014390e6ee4254f5b, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 259 | + scratch1 := add(0x2af33e3f866771271ac0c9b3ed2e1142ecd3e74b939cd40d00d937ab84c98591, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 260 | + scratch2 := add(0x0b520211f904b5e7d09b5d961c6ace7734568c547dd6858b364ce5e47951f178, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 261 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 262 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 263 | + state0 := add(0x0b2d722d0919a1aad8db58f10062a92ea0c56ac4270e822cca228620188a1d40, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 264 | + state1 := add(0x1f790d4d7f8cf094d980ceb37c2453e957b54a9991ca38bbe0061d1ed6e562d4, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 265 | + state2 := add(0x0171eb95dfbf7d1eaea97cd385f780150885c16235a2a6a8da92ceb01e504233, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 266 | + scratch0 := mulmod(state0, state0, F) | ||
| 267 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 268 | + scratch0 := add(0x0c2d0e3b5fd57549329bf6885da66b9b790b40defd2c8650762305381b168873, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 269 | + scratch1 := add(0x1162fb28689c27154e5a8228b4e72b377cbcafa589e283c35d3803054407a18d, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 270 | + scratch2 := add(0x2f1459b65dee441b64ad386a91e8310f282c5a92a89e19921623ef8249711bc0, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 271 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 272 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 273 | + state0 := add(0x1e6ff3216b688c3d996d74367d5cd4c1bc489d46754eb712c243f70d1b53cfbb, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 274 | + state1 := add(0x01ca8be73832b8d0681487d27d157802d741a6f36cdc2a0576881f9326478875, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 275 | + state2 := add(0x1f7735706ffe9fc586f976d5bdf223dc680286080b10cea00b9b5de315f9650e, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 276 | + scratch0 := mulmod(state0, state0, F) | ||
| 277 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 278 | + scratch0 := add(0x2522b60f4ea3307640a0c2dce041fba921ac10a3d5f096ef4745ca838285f019, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 279 | + scratch1 := add(0x23f0bee001b1029d5255075ddc957f833418cad4f52b6c3f8ce16c235572575b, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 280 | + scratch2 := add(0x2bc1ae8b8ddbb81fcaac2d44555ed5685d142633e9df905f66d9401093082d59, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 281 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 282 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 283 | + state0 := add(0x0f9406b8296564a37304507b8dba3ed162371273a07b1fc98011fcd6ad72205f, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 284 | + state1 := add(0x2360a8eb0cc7defa67b72998de90714e17e75b174a52ee4acb126c8cd995f0a8, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 285 | + state2 := add(0x15871a5cddead976804c803cbaef255eb4815a5e96df8b006dcbbc2767f88948, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 286 | + scratch0 := mulmod(state0, state0, F) | ||
| 287 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 288 | + scratch0 := add(0x193a56766998ee9e0a8652dd2f3b1da0362f4f54f72379544f957ccdeefb420f, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 289 | + scratch1 := add(0x2a394a43934f86982f9be56ff4fab1703b2e63c8ad334834e4309805e777ae0f, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 290 | + scratch2 := add(0x1859954cfeb8695f3e8b635dcb345192892cd11223443ba7b4166e8876c0d142, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 291 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 292 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 293 | + state0 := add(0x04e1181763050e58013444dbcb99f1902b11bc25d90bbdca408d3819f4fed32b, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 294 | + state1 := add(0x0fdb253dee83869d40c335ea64de8c5bb10eb82db08b5e8b1f5e5552bfd05f23, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 295 | + state2 := add(0x058cbe8a9a5027bdaa4efb623adead6275f08686f1c08984a9d7c5bae9b4f1c0, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 296 | + scratch0 := mulmod(state0, state0, F) | ||
| 297 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 298 | + scratch0 := add(0x1382edce9971e186497eadb1aeb1f52b23b4b83bef023ab0d15228b4cceca59a, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 299 | + scratch1 := add(0x03464990f045c6ee0819ca51fd11b0be7f61b8eb99f14b77e1e6634601d9e8b5, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 300 | + scratch2 := add(0x23f7bfc8720dc296fff33b41f98ff83c6fcab4605db2eb5aaa5bc137aeb70a58, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 301 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 302 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 303 | + state0 := add(0x0a59a158e3eec2117e6e94e7f0e9decf18c3ffd5e1531a9219636158bbaf62f2, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 304 | + state1 := add(0x06ec54c80381c052b58bf23b312ffd3ce2c4eba065420af8f4c23ed0075fd07b, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 305 | + state2 := add(0x118872dc832e0eb5476b56648e867ec8b09340f7a7bcb1b4962f0ff9ed1f9d01, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 306 | + scratch0 := mulmod(state0, state0, F) | ||
| 307 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 308 | + scratch0 := add(0x13d69fa127d834165ad5c7cba7ad59ed52e0b0f0e42d7fea95e1906b520921b1, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 309 | + scratch1 := add(0x169a177f63ea681270b1c6877a73d21bde143942fb71dc55fd8a49f19f10c77b, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 310 | + scratch2 := add(0x04ef51591c6ead97ef42f287adce40d93abeb032b922f66ffb7e9a5a7450544d, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 311 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 312 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 313 | + state0 := add(0x256e175a1dc079390ecd7ca703fb2e3b19ec61805d4f03ced5f45ee6dd0f69ec, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 314 | + state1 := add(0x30102d28636abd5fe5f2af412ff6004f75cc360d3205dd2da002813d3e2ceeb2, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 315 | + state2 := add(0x10998e42dfcd3bbf1c0714bc73eb1bf40443a3fa99bef4a31fd31be182fcc792, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 316 | + scratch0 := mulmod(state0, state0, F) | ||
| 317 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 318 | + scratch0 := add(0x193edd8e9fcf3d7625fa7d24b598a1d89f3362eaf4d582efecad76f879e36860, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 319 | + scratch1 := add(0x18168afd34f2d915d0368ce80b7b3347d1c7a561ce611425f2664d7aa51f0b5d, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 320 | + scratch2 := add(0x29383c01ebd3b6ab0c017656ebe658b6a328ec77bc33626e29e2e95b33ea6111, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 321 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 322 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 323 | + state0 := add(0x10646d2f2603de39a1f4ae5e7771a64a702db6e86fb76ab600bf573f9010c711, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 324 | + state1 := add(0x0beb5e07d1b27145f575f1395a55bf132f90c25b40da7b3864d0242dcb1117fb, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 325 | + state2 := add(0x16d685252078c133dc0d3ecad62b5c8830f95bb2e54b59abdffbf018d96fa336, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 326 | + scratch0 := mulmod(state0, state0, F) | ||
| 327 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 328 | + scratch0 := add(0x0a6abd1d833938f33c74154e0404b4b40a555bbbec21ddfafd672dd62047f01a, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 329 | + scratch1 := add(0x1a679f5d36eb7b5c8ea12a4c2dedc8feb12dffeec450317270a6f19b34cf1860, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 330 | + scratch2 := add(0x0980fb233bd456c23974d50e0ebfde4726a423eada4e8f6ffbc7592e3f1b93d6, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 331 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 332 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 333 | + state0 := add(0x161b42232e61b84cbf1810af93a38fc0cece3d5628c9282003ebacb5c312c72b, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 334 | + state1 := add(0x0ada10a90c7f0520950f7d47a60d5e6a493f09787f1564e5d09203db47de1a0b, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 335 | + state2 := add(0x1a730d372310ba82320345a29ac4238ed3f07a8a2b4e121bb50ddb9af407f451, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 336 | + scratch0 := mulmod(state0, state0, F) | ||
| 337 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 338 | + scratch0 := add(0x2c8120f268ef054f817064c369dda7ea908377feaba5c4dffbda10ef58e8c556, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 339 | + scratch1 := add(0x1c7c8824f758753fa57c00789c684217b930e95313bcb73e6e7b8649a4968f70, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 340 | + scratch2 := add(0x2cd9ed31f5f8691c8e39e4077a74faa0f400ad8b491eb3f7b47b27fa3fd1cf77, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 341 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 342 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 343 | + state0 := add(0x23ff4f9d46813457cf60d92f57618399a5e022ac321ca550854ae23918a22eea, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 344 | + state1 := add(0x09945a5d147a4f66ceece6405dddd9d0af5a2c5103529407dff1ea58f180426d, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 345 | + state2 := add(0x188d9c528025d4c2b67660c6b771b90f7c7da6eaa29d3f268a6dd223ec6fc630, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 346 | + scratch0 := mulmod(state0, state0, F) | ||
| 347 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 348 | + scratch0 := add(0x3050e37996596b7f81f68311431d8734dba7d926d3633595e0c0d8ddf4f0f47f, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 349 | + scratch1 := add(0x15af1169396830a91600ca8102c35c426ceae5461e3f95d89d829518d30afd78, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 350 | + scratch2 := add(0x1da6d09885432ea9a06d9f37f873d985dae933e351466b2904284da3320d8acc, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 351 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 352 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 353 | + state0 := add(0x2796ea90d269af29f5f8acf33921124e4e4fad3dbe658945e546ee411ddaa9cb, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 354 | + state1 := add(0x202d7dd1da0f6b4b0325c8b3307742f01e15612ec8e9304a7cb0319e01d32d60, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 355 | + state2 := add(0x096d6790d05bb759156a952ba263d672a2d7f9c788f4c831a29dace4c0f8be5f, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 356 | + scratch0 := mulmod(state0, state0, F) | ||
| 357 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 358 | + scratch0 := add(0x054efa1f65b0fce283808965275d877b438da23ce5b13e1963798cb1447d25a4, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 359 | + scratch1 := add(0x1b162f83d917e93edb3308c29802deb9d8aa690113b2e14864ccf6e18e4165f1, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 360 | + scratch2 := add(0x21e5241e12564dd6fd9f1cdd2a0de39eedfefc1466cc568ec5ceb745a0506edc, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 361 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 362 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 363 | + state0 := mulmod(scratch1, scratch1, F) | ||
| 364 | + scratch1 := mulmod(mulmod(state0, state0, F), scratch1, F) | ||
| 365 | + state0 := mulmod(scratch2, scratch2, F) | ||
| 366 | + scratch2 := mulmod(mulmod(state0, state0, F), scratch2, F) | ||
| 367 | + state0 := add(0x1cfb5662e8cf5ac9226a80ee17b36abecb73ab5f87e161927b4349e10e4bdf08, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 368 | + state1 := add(0x0f21177e302a771bbae6d8d1ecb373b62c99af346220ac0129c53f666eb24100, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 369 | + state2 := add(0x1671522374606992affb0dd7f71b12bec4236aede6290546bcef7e1f515c2320, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 370 | + scratch0 := mulmod(state0, state0, F) | ||
| 371 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 372 | + scratch0 := mulmod(state1, state1, F) | ||
| 373 | + state1 := mulmod(mulmod(scratch0, scratch0, F), state1, F) | ||
| 374 | + scratch0 := mulmod(state2, state2, F) | ||
| 375 | + state2 := mulmod(mulmod(scratch0, scratch0, F), state2, F) | ||
| 376 | + scratch0 := add(0x0fa3ec5b9488259c2eb4cf24501bfad9be2ec9e42c5cc8ccd419d2a692cad870, add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F))) | ||
| 377 | + scratch1 := add(0x193c0e04e0bd298357cb266c1506080ed36edce85c648cc085e8c57b1ab54bba, add(add(mulmod(state0, M01, F), mulmod(state1, M11, F)), mulmod(state2, M21, F))) | ||
| 378 | + scratch2 := add(0x102adf8ef74735a27e9128306dcbc3c99f6f7291cd406578ce14ea2adaba68f8, add(add(mulmod(state0, M02, F), mulmod(state1, M12, F)), mulmod(state2, M22, F))) | ||
| 379 | + state0 := mulmod(scratch0, scratch0, F) | ||
| 380 | + scratch0 := mulmod(mulmod(state0, state0, F), scratch0, F) | ||
| 381 | + state0 := mulmod(scratch1, scratch1, F) | ||
| 382 | + scratch1 := mulmod(mulmod(state0, state0, F), scratch1, F) | ||
| 383 | + state0 := mulmod(scratch2, scratch2, F) | ||
| 384 | + scratch2 := mulmod(mulmod(state0, state0, F), scratch2, F) | ||
| 385 | + state0 := add(0x0fe0af7858e49859e2a54d6f1ad945b1316aa24bfbdd23ae40a6d0cb70c3eab1, add(add(mulmod(scratch0, M00, F), mulmod(scratch1, M10, F)), mulmod(scratch2, M20, F))) | ||
| 386 | + state1 := add(0x216f6717bbc7dedb08536a2220843f4e2da5f1daa9ebdefde8a5ea7344798d22, add(add(mulmod(scratch0, M01, F), mulmod(scratch1, M11, F)), mulmod(scratch2, M21, F))) | ||
| 387 | + state2 := add(0x1da55cc900f0d21f4a3e694391918a1b3c23b2ac773c6b3ef88e2e4228325161, add(add(mulmod(scratch0, M02, F), mulmod(scratch1, M12, F)), mulmod(scratch2, M22, F))) | ||
| 388 | + scratch0 := mulmod(state0, state0, F) | ||
| 389 | + state0 := mulmod(mulmod(scratch0, scratch0, F), state0, F) | ||
| 390 | + scratch0 := mulmod(state1, state1, F) | ||
| 391 | + state1 := mulmod(mulmod(scratch0, scratch0, F), state1, F) | ||
| 392 | + scratch0 := mulmod(state2, state2, F) | ||
| 393 | + state2 := mulmod(mulmod(scratch0, scratch0, F), state2, F) | ||
| 394 | + | ||
| 395 | + r := mod(add(add(mulmod(state0, M00, F), mulmod(state1, M10, F)), mulmod(state2, M20, F)), F) | ||
| 396 | + } | ||
| 397 | + } | ||
| 398 | +} | ||
deleted
test/LoveEnvelope.t.sol +0 -498 | deleted file mode 100644 | ||
| @@ -1,498 +0,0 @@ | ||
| 1 | -// SPDX-License-Identifier: MIT | |
| 2 | -pragma solidity ^0.8.30; | |
| 3 | - | |
| 4 | -import {IWETH} from "../src/IWETH.sol"; | |
| 5 | -import {Love} from "../src/Love.sol"; | |
| 6 | -import {Fixtures} from "./Fixtures.sol"; | |
| 7 | -import {Test} from "forge-std/Test.sol"; | |
| 8 | - | |
| 9 | -/// @title LoveEnvelopeTest | |
| 10 | -/// @notice Exercises the secret-locked `deposit` overload and `claim`: locking | |
| 11 | -/// wETH behind a secret hash instead of crediting the caller, bearer | |
| 12 | -/// claiming, partial-claim accounting, and the revert paths that guard | |
| 13 | -/// wrong secrets, unknown envelopes and over-claiming. `withdraw` is | |
| 14 | -/// untouched by any of this — a claimed balance redeems for wETH the | |
| 15 | -/// same way any other LOVE balance does, exercised in Love.t.sol. | |
| 16 | -contract LoveEnvelopeTest is Test, Fixtures { | |
| 17 | - Love public love; | |
| 18 | - IWETH public weth; | |
| 19 | - | |
| 20 | - address alice = makeAddr("alice"); | |
| 21 | - address bob = makeAddr("bob"); | |
| 22 | - address stranger = makeAddr("stranger"); | |
| 23 | - | |
| 24 | - bytes32 constant SECRET = keccak256("correct secret"); | |
| 25 | - bytes32 constant SECRET_HASH = keccak256(abi.encodePacked(SECRET)); | |
| 26 | - | |
| 27 | - uint256 constant RATE = 100_000; | |
| 28 | - | |
| 29 | - address constant WETH_AT = 0x4200000000000000000000000000000000000006; | |
| 30 | - | |
| 31 | - function setUp() public { | |
| 32 | - (, weth) = setUpChain(WETH_AT, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); | |
| 33 | - love = new Love(); | |
| 34 | - | |
| 35 | - _fund(alice, 10 ether); | |
| 36 | - _fund(bob, 10 ether); | |
| 37 | - } | |
| 38 | - | |
| 39 | - /*////////////////////////////////////////////////////////////// | |
| 40 | - DEPOSIT | |
| 41 | - //////////////////////////////////////////////////////////////*/ | |
| 42 | - | |
| 43 | - function test_Deposit() public { | |
| 44 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | |
| 45 | - | |
| 46 | - (bytes32 secretHash, uint256 remaining) = love.envelopes(depositId); | |
| 47 | - assertEq(depositId, 0); | |
| 48 | - assertEq(secretHash, SECRET_HASH); | |
| 49 | - assertEq(remaining, 1 ether * RATE); | |
| 50 | - assertEq(love.balanceOf(address(love)), 1 ether * RATE); | |
| 51 | - assertEq(love.balanceOf(alice), 0); | |
| 52 | - assertEq(weth.balanceOf(address(love)), 1 ether); | |
| 53 | - } | |
| 54 | - | |
| 55 | - function test_DepositIsPermissionless() public { | |
| 56 | - _fund(stranger, 1 ether); | |
| 57 | - | |
| 58 | - uint256 depositId = _depositEnvelope(stranger, SECRET_HASH, 1 ether); | |
| 59 | - | |
| 60 | - (, uint256 remaining) = love.envelopes(depositId); | |
| 61 | - assertEq(remaining, 1 ether * RATE); | |
| 62 | - } | |
| 63 | - | |
| 64 | - function test_DepositEmitsDeposit() public { | |
| 65 | - vm.prank(alice); | |
| 66 | - weth.approve(address(love), 1 ether); | |
| 67 | - | |
| 68 | - vm.expectEmit(true, true, false, true); | |
| 69 | - emit Love.Deposit(0, alice, SECRET_HASH, 1 ether, 1 ether * RATE); | |
| 70 | - | |
| 71 | - vm.prank(alice); | |
| 72 | - love.deposit(SECRET_HASH, 1 ether); | |
| 73 | - } | |
| 74 | - | |
| 75 | - function test_DepositIncrementsDepositId() public { | |
| 76 | - uint256 first = _depositEnvelope(alice, SECRET_HASH, 0.25 ether); | |
| 77 | - uint256 second = _depositEnvelope(bob, keccak256("another secret"), 0.25 ether); | |
| 78 | - | |
| 79 | - assertEq(first, 0); | |
| 80 | - assertEq(second, 1); | |
| 81 | - assertEq(love.nextDepositId(), 2); | |
| 82 | - } | |
| 83 | - | |
| 84 | - function test_DepositKeepsPegInvariant() public { | |
| 85 | - _depositEnvelope(alice, SECRET_HASH, 1 ether); | |
| 86 | - | |
| 87 | - assertEq(love.totalSupply(), weth.balanceOf(address(love)) * RATE); | |
| 88 | - } | |
| 89 | - | |
| 90 | - function test_RevertWhen_DepositWithoutApproval() public { | |
| 91 | - vm.prank(alice); | |
| 92 | - vm.expectRevert(bytes("")); | |
| 93 | - love.deposit(SECRET_HASH, 1 ether); | |
| 94 | - } | |
| 95 | - | |
| 96 | - function test_RevertWhen_DepositExceedsWethBalance() public { | |
| 97 | - vm.prank(alice); | |
| 98 | - weth.approve(address(love), 100 ether); | |
| 99 | - | |
| 100 | - vm.prank(alice); | |
| 101 | - vm.expectRevert(bytes("")); | |
| 102 | - love.deposit(SECRET_HASH, 100 ether); | |
| 103 | - } | |
| 104 | - | |
| 105 | - function test_RevertWhen_SecretHashIsZero() public { | |
| 106 | - vm.prank(alice); | |
| 107 | - weth.approve(address(love), 1 ether); | |
| 108 | - | |
| 109 | - vm.prank(alice); | |
| 110 | - vm.expectRevert(Love.ZeroSecretHash.selector); | |
| 111 | - love.deposit(bytes32(0), 1 ether); | |
| 112 | - } | |
| 113 | - | |
| 114 | - /*////////////////////////////////////////////////////////////// | |
| 115 | - CLAIM | |
| 116 | - //////////////////////////////////////////////////////////////*/ | |
| 117 | - | |
| 118 | - function test_Claim() public { | |
| 119 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | |
| 120 | - | |
| 121 | - vm.prank(stranger); | |
| 122 | - love.claim(depositId, SECRET, 1 ether * RATE); | |
| 123 | - | |
| 124 | - (, uint256 remaining) = love.envelopes(depositId); | |
| 125 | - assertEq(remaining, 0); | |
| 126 | - assertEq(love.balanceOf(stranger), 1 ether * RATE); | |
| 127 | - assertEq(love.balanceOf(address(love)), 0); | |
| 128 | - } | |
| 129 | - | |
| 130 | - /// @dev Bearer semantics: knowledge of the secret is what pays out, not | |
| 131 | - /// being the original depositor. | |
| 132 | - function test_ClaimByAnyoneWhoKnowsSecret() public { | |
| 133 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | |
| 134 | - | |
| 135 | - vm.prank(bob); | |
| 136 | - love.claim(depositId, SECRET, 1 ether * RATE); | |
| 137 | - | |
| 138 | - assertEq(love.balanceOf(bob), 1 ether * RATE); | |
| 139 | - } | |
| 140 | - | |
| 141 | - function test_PartialClaim() public { | |
| 142 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | |
| 143 | - | |
| 144 | - vm.prank(bob); | |
| 145 | - love.claim(depositId, SECRET, 0.25 ether * RATE); | |
| 146 | - | |
| 147 | - (, uint256 remaining) = love.envelopes(depositId); | |
| 148 | - assertEq(remaining, 0.75 ether * RATE); | |
| 149 | - assertEq(love.balanceOf(bob), 0.25 ether * RATE); | |
| 150 | - } | |
| 151 | - | |
| 152 | - function test_MultiplePartialClaimsDrainEnvelope() public { | |
| 153 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | |
| 154 | - | |
| 155 | - vm.startPrank(bob); | |
| 156 | - love.claim(depositId, SECRET, 0.4 ether * RATE); | |
| 157 | - love.claim(depositId, SECRET, 0.35 ether * RATE); | |
| 158 | - love.claim(depositId, SECRET, 0.25 ether * RATE); | |
| 159 | - vm.stopPrank(); | |
| 160 | - | |
| 161 | - (, uint256 remaining) = love.envelopes(depositId); | |
| 162 | - assertEq(remaining, 0); | |
| 163 | - assertEq(love.balanceOf(address(love)), 0); | |
| 164 | - } | |
| 165 | - | |
| 166 | - function test_ClaimEmitsClaim() public { | |
| 167 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | |
| 168 | - | |
| 169 | - vm.expectEmit(true, true, false, true); | |
| 170 | - emit Love.Claim(depositId, bob, 0.4 ether * RATE, 0.6 ether * RATE); | |
| 171 | - | |
| 172 | - vm.prank(bob); | |
| 173 | - love.claim(depositId, SECRET, 0.4 ether * RATE); | |
| 174 | - } | |
| 175 | - | |
| 176 | - /// @dev A claimed balance is an ordinary LOVE balance: `withdraw` (untouched | |
| 177 | - /// by the envelope feature) redeems it for wETH like any other. | |
| 178 | - function test_ClaimedLoveIsRedeemableThroughWithdraw() public { | |
| 179 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | |
| 180 | - | |
| 181 | - vm.prank(bob); | |
| 182 | - love.claim(depositId, SECRET, 1 ether * RATE); | |
| 183 | - | |
| 184 | - vm.prank(bob); | |
| 185 | - love.withdraw(1 ether * RATE); | |
| 186 | - | |
| 187 | - assertEq(weth.balanceOf(bob), 11 ether); | |
| 188 | - assertEq(love.totalSupply(), 0); | |
| 189 | - } | |
| 190 | - | |
| 191 | - function test_ClaimZero() public { | |
| 192 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | |
| 193 | - | |
| 194 | - vm.prank(bob); | |
| 195 | - love.claim(depositId, SECRET, 0); | |
| 196 | - | |
| 197 | - (, uint256 remaining) = love.envelopes(depositId); | |
| 198 | - assertEq(remaining, 1 ether * RATE); | |
| 199 | - } | |
| 200 | - | |
| 201 | - function test_EnvelopesAreIndependent() public { | |
| 202 | - bytes32 otherSecret = keccak256("another secret"); | |
| 203 | - uint256 aliceEnvelope = _depositEnvelope(alice, SECRET_HASH, 0.5 ether); | |
| 204 | - uint256 bobEnvelope = _depositEnvelope(bob, keccak256(abi.encodePacked(otherSecret)), 0.5 ether); | |
| 205 | - | |
| 206 | - vm.prank(stranger); | |
| 207 | - love.claim(aliceEnvelope, SECRET, 0.5 ether * RATE); | |
| 208 | - | |
| 209 | - (, uint256 bobRemaining) = love.envelopes(bobEnvelope); | |
| 210 | - assertEq(bobRemaining, 0.5 ether * RATE); | |
| 211 | - | |
| 212 | - vm.prank(stranger); | |
| 213 | - love.claim(bobEnvelope, otherSecret, 0.5 ether * RATE); | |
| 214 | - | |
| 215 | - assertEq(love.balanceOf(stranger), 1 ether * RATE); | |
| 216 | - } | |
| 217 | - | |
| 218 | - function test_RevertWhen_ClaimWrongSecret() public { | |
| 219 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | |
| 220 | - | |
| 221 | - vm.prank(stranger); | |
| 222 | - vm.expectRevert(abi.encodeWithSelector(Love.WrongSecret.selector, depositId)); | |
| 223 | - love.claim(depositId, keccak256("wrong guess"), 1 ether * RATE); | |
| 224 | - } | |
| 225 | - | |
| 226 | - function test_RevertWhen_ClaimUnknownDeposit() public { | |
| 227 | - vm.prank(stranger); | |
| 228 | - vm.expectRevert(abi.encodeWithSelector(Love.UnknownDeposit.selector, 42)); | |
| 229 | - love.claim(42, SECRET, 1); | |
| 230 | - } | |
| 231 | - | |
| 232 | - function test_RevertWhen_ClaimExceedsRemaining() public { | |
| 233 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | |
| 234 | - | |
| 235 | - vm.prank(stranger); | |
| 236 | - vm.expectRevert( | |
| 237 | - abi.encodeWithSelector(Love.InsufficientRemaining.selector, depositId, 2 ether * RATE, 1 ether * RATE) | |
| 238 | - ); | |
| 239 | - love.claim(depositId, SECRET, 2 ether * RATE); | |
| 240 | - } | |
| 241 | - | |
| 242 | - function test_RevertWhen_ClaimAfterFullyDrained() public { | |
| 243 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | |
| 244 | - | |
| 245 | - vm.prank(bob); | |
| 246 | - love.claim(depositId, SECRET, 1 ether * RATE); | |
| 247 | - | |
| 248 | - vm.prank(stranger); | |
| 249 | - vm.expectRevert(abi.encodeWithSelector(Love.InsufficientRemaining.selector, depositId, 1, 0)); | |
| 250 | - love.claim(depositId, SECRET, 1); | |
| 251 | - } | |
| 252 | - | |
| 253 | - /*////////////////////////////////////////////////////////////// | |
| 254 | - BATCH DEPOSIT | |
| 255 | - //////////////////////////////////////////////////////////////*/ | |
| 256 | - | |
| 257 | - bytes32 constant SECRET_B = keccak256("second secret"); | |
| 258 | - bytes32 constant SECRET_B_HASH = keccak256(abi.encodePacked(SECRET_B)); | |
| 259 | - | |
| 260 | - function test_BatchDeposit() public { | |
| 261 | - uint256[] memory depositIds = | |
| 262 | - _depositBatch(alice, _hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | |
| 263 | - | |
| 264 | - assertEq(depositIds.length, 2); | |
| 265 | - assertEq(depositIds[0], 0); | |
| 266 | - assertEq(depositIds[1], 1); | |
| 267 | - assertEq(love.nextDepositId(), 2); | |
| 268 | - | |
| 269 | - (bytes32 firstHash, uint256 firstRemaining) = love.envelopes(0); | |
| 270 | - (bytes32 secondHash, uint256 secondRemaining) = love.envelopes(1); | |
| 271 | - assertEq(firstHash, SECRET_HASH); | |
| 272 | - assertEq(firstRemaining, 0.2 ether * RATE); | |
| 273 | - assertEq(secondHash, SECRET_B_HASH); | |
| 274 | - assertEq(secondRemaining, 0.8 ether * RATE); | |
| 275 | - | |
| 276 | - assertEq(love.balanceOf(address(love)), 1 ether * RATE); | |
| 277 | - assertEq(love.balanceOf(alice), 0); | |
| 278 | - assertEq(weth.balanceOf(address(love)), 1 ether); | |
| 279 | - assertEq(weth.balanceOf(alice), 9 ether); | |
| 280 | - } | |
| 281 | - | |
| 282 | - function test_BatchDepositContinuesFromNextDepositId() public { | |
| 283 | - _depositEnvelope(alice, keccak256("earlier"), 0.1 ether); | |
| 284 | - | |
| 285 | - uint256[] memory depositIds = | |
| 286 | - _depositBatch(alice, _hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | |
| 287 | - | |
| 288 | - assertEq(depositIds[0], 1); | |
| 289 | - assertEq(depositIds[1], 2); | |
| 290 | - assertEq(love.nextDepositId(), 3); | |
| 291 | - } | |
| 292 | - | |
| 293 | - function test_BatchDepositEmitsOneDepositPerEnvelope() public { | |
| 294 | - vm.prank(alice); | |
| 295 | - weth.approve(address(love), 1 ether); | |
| 296 | - | |
| 297 | - vm.expectEmit(true, true, false, true); | |
| 298 | - emit Love.Deposit(0, alice, SECRET_HASH, 0.2 ether, 0.2 ether * RATE); | |
| 299 | - vm.expectEmit(true, true, false, true); | |
| 300 | - emit Love.Deposit(1, alice, SECRET_B_HASH, 0.8 ether, 0.8 ether * RATE); | |
| 301 | - | |
| 302 | - vm.prank(alice); | |
| 303 | - love.deposit(_hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | |
| 304 | - } | |
| 305 | - | |
| 306 | - function test_BatchDepositKeepsPegInvariant() public { | |
| 307 | - _depositBatch(alice, _hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | |
| 308 | - | |
| 309 | - assertEq(love.totalSupply(), weth.balanceOf(address(love)) * RATE); | |
| 310 | - } | |
| 311 | - | |
| 312 | - /// @dev The point of the batch: each recipient gets their own share and | |
| 313 | - /// nothing else, with a single full claim each. | |
| 314 | - function test_BatchRecipientsClaimOnlyTheirShare() public { | |
| 315 | - uint256[] memory depositIds = | |
| 316 | - _depositBatch(alice, _hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | |
| 317 | - | |
| 318 | - vm.prank(bob); | |
| 319 | - love.claim(depositIds[0], SECRET, 0.2 ether * RATE); | |
| 320 | - vm.prank(stranger); | |
| 321 | - love.claim(depositIds[1], SECRET_B, 0.8 ether * RATE); | |
| 322 | - | |
| 323 | - assertEq(love.balanceOf(bob), 0.2 ether * RATE); | |
| 324 | - assertEq(love.balanceOf(stranger), 0.8 ether * RATE); | |
| 325 | - assertEq(love.balanceOf(address(love)), 0); | |
| 326 | - } | |
| 327 | - | |
| 328 | - function test_RevertWhen_BatchRecipientClaimsPastTheirShare() public { | |
| 329 | - uint256[] memory depositIds = | |
| 330 | - _depositBatch(alice, _hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | |
| 331 | - | |
| 332 | - vm.prank(bob); | |
| 333 | - vm.expectRevert( | |
| 334 | - abi.encodeWithSelector( | |
| 335 | - Love.InsufficientRemaining.selector, depositIds[0], 0.5 ether * RATE, 0.2 ether * RATE | |
| 336 | - ) | |
| 337 | - ); | |
| 338 | - love.claim(depositIds[0], SECRET, 0.5 ether * RATE); | |
| 339 | - } | |
| 340 | - | |
| 341 | - function test_RevertWhen_BatchSecretOpensTheWrongEnvelope() public { | |
| 342 | - uint256[] memory depositIds = | |
| 343 | - _depositBatch(alice, _hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | |
| 344 | - | |
| 345 | - vm.prank(bob); | |
| 346 | - vm.expectRevert(abi.encodeWithSelector(Love.WrongSecret.selector, depositIds[1])); | |
| 347 | - love.claim(depositIds[1], SECRET, 0.8 ether * RATE); | |
| 348 | - } | |
| 349 | - | |
| 350 | - function test_BatchOfOneMatchesSingleDeposit() public { | |
| 351 | - uint256 single = _depositEnvelope(alice, SECRET_HASH, 1 ether); | |
| 352 | - bytes32[] memory hashes = new bytes32[](1); | |
| 353 | - hashes[0] = SECRET_HASH; | |
| 354 | - uint256[] memory amounts = new uint256[](1); | |
| 355 | - amounts[0] = 1 ether; | |
| 356 | - uint256[] memory batch = _depositBatch(alice, hashes, amounts); | |
| 357 | - | |
| 358 | - (bytes32 singleHash, uint256 singleRemaining) = love.envelopes(single); | |
| 359 | - (bytes32 batchHash, uint256 batchRemaining) = love.envelopes(batch[0]); | |
| 360 | - assertEq(batchHash, singleHash); | |
| 361 | - assertEq(batchRemaining, singleRemaining); | |
| 362 | - } | |
| 363 | - | |
| 364 | - function test_RevertWhen_BatchIsEmpty() public { | |
| 365 | - vm.prank(alice); | |
| 366 | - vm.expectRevert(abi.encodeWithSelector(Love.InvalidBatch.selector, 0, 0)); | |
| 367 | - love.deposit(new bytes32[](0), new uint256[](0)); | |
| 368 | - } | |
| 369 | - | |
| 370 | - function test_RevertWhen_BatchLengthsDiffer() public { | |
| 371 | - bytes32[] memory hashes = _hashes(SECRET_HASH, SECRET_B_HASH); | |
| 372 | - uint256[] memory amounts = new uint256[](1); | |
| 373 | - amounts[0] = 1 ether; | |
| 374 | - | |
| 375 | - vm.prank(alice); | |
| 376 | - vm.expectRevert(abi.encodeWithSelector(Love.InvalidBatch.selector, 2, 1)); | |
| 377 | - love.deposit(hashes, amounts); | |
| 378 | - } | |
| 379 | - | |
| 380 | - function test_RevertWhen_BatchContainsZeroSecretHash() public { | |
| 381 | - vm.prank(alice); | |
| 382 | - weth.approve(address(love), 1 ether); | |
| 383 | - | |
| 384 | - vm.prank(alice); | |
| 385 | - vm.expectRevert(Love.ZeroSecretHash.selector); | |
| 386 | - love.deposit(_hashes(SECRET_HASH, bytes32(0)), _amounts(0.2 ether, 0.8 ether)); | |
| 387 | - | |
| 388 | - assertEq(love.nextDepositId(), 0); | |
| 389 | - assertEq(weth.balanceOf(address(love)), 0); | |
| 390 | - } | |
| 391 | - | |
| 392 | - function test_RevertWhen_BatchWithoutApproval() public { | |
| 393 | - vm.prank(alice); | |
| 394 | - vm.expectRevert(bytes("")); | |
| 395 | - love.deposit(_hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | |
| 396 | - } | |
| 397 | - | |
| 398 | - function test_RevertWhen_BatchApprovalCoversOnlyPartOfTheSum() public { | |
| 399 | - vm.prank(alice); | |
| 400 | - weth.approve(address(love), 0.8 ether); | |
| 401 | - | |
| 402 | - vm.prank(alice); | |
| 403 | - vm.expectRevert(bytes("")); | |
| 404 | - love.deposit(_hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | |
| 405 | - } | |
| 406 | - | |
| 407 | - /*////////////////////////////////////////////////////////////// | |
| 408 | - FUZZ | |
| 409 | - //////////////////////////////////////////////////////////////*/ | |
| 410 | - | |
| 411 | - function testFuzz_BatchDepositTotalsMatch(uint256 first, uint256 second) public { | |
| 412 | - first = bound(first, 0, type(uint256).max / RATE / 2); | |
| 413 | - second = bound(second, 0, type(uint256).max / RATE / 2); | |
| 414 | - | |
| 415 | - _fund(alice, first + second); | |
| 416 | - uint256[] memory depositIds = _depositBatch(alice, _hashes(SECRET_HASH, SECRET_B_HASH), _amounts(first, second)); | |
| 417 | - | |
| 418 | - (, uint256 firstRemaining) = love.envelopes(depositIds[0]); | |
| 419 | - (, uint256 secondRemaining) = love.envelopes(depositIds[1]); | |
| 420 | - assertEq(firstRemaining, first * RATE); | |
| 421 | - assertEq(secondRemaining, second * RATE); | |
| 422 | - assertEq(love.totalSupply(), weth.balanceOf(address(love)) * RATE); | |
| 423 | - } | |
| 424 | - | |
| 425 | - function testFuzz_PartialClaimsSumToTotal(uint256 wethAmount, uint256 first) public { | |
| 426 | - wethAmount = bound(wethAmount, 0, type(uint256).max / RATE); | |
| 427 | - uint256 total = wethAmount * RATE; | |
| 428 | - first = bound(first, 0, total); | |
| 429 | - uint256 second = total - first; | |
| 430 | - | |
| 431 | - _fund(alice, wethAmount); | |
| 432 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, wethAmount); | |
| 433 | - | |
| 434 | - vm.prank(stranger); | |
| 435 | - love.claim(depositId, SECRET, first); | |
| 436 | - vm.prank(stranger); | |
| 437 | - love.claim(depositId, SECRET, second); | |
| 438 | - | |
| 439 | - (, uint256 remaining) = love.envelopes(depositId); | |
| 440 | - assertEq(remaining, 0); | |
| 441 | - assertEq(love.balanceOf(stranger), total); | |
| 442 | - } | |
| 443 | - | |
| 444 | - function testFuzz_WrongSecretAlwaysReverts(bytes32 wrongSecret) public { | |
| 445 | - vm.assume(wrongSecret != SECRET); | |
| 446 | - | |
| 447 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | |
| 448 | - | |
| 449 | - vm.prank(stranger); | |
| 450 | - vm.expectRevert(abi.encodeWithSelector(Love.WrongSecret.selector, depositId)); | |
| 451 | - love.claim(depositId, wrongSecret, 1); | |
| 452 | - } | |
| 453 | - | |
| 454 | - /// @dev Gives `account` `amount` wETH by wrapping fresh ETH. | |
| 455 | - function _fund(address account, uint256 amount) internal { | |
| 456 | - vm.deal(account, amount); | |
| 457 | - vm.prank(account); | |
| 458 | - weth.deposit{value: amount}(); | |
| 459 | - } | |
| 460 | - | |
| 461 | - /// @dev Approves and opens a secret-locked envelope with `wethAmount`, in one step. | |
| 462 | - function _depositEnvelope(address account, bytes32 secretHash, uint256 wethAmount) internal returns (uint256) { | |
| 463 | - vm.startPrank(account); | |
| 464 | - weth.approve(address(love), wethAmount); | |
| 465 | - uint256 depositId = love.deposit(secretHash, wethAmount); | |
| 466 | - vm.stopPrank(); | |
| 467 | - return depositId; | |
| 468 | - } | |
| 469 | - | |
| 470 | - /// @dev Approves the sum of `wethAmounts` and opens one envelope per entry, in one step. | |
| 471 | - function _depositBatch(address account, bytes32[] memory secretHashes, uint256[] memory wethAmounts) | |
| 472 | - internal | |
| 473 | - returns (uint256[] memory) | |
| 474 | - { | |
| 475 | - uint256 total; | |
| 476 | - for (uint256 i; i < wethAmounts.length; ++i) { | |
| 477 | - total += wethAmounts[i]; | |
| 478 | - } | |
| 479 | - | |
| 480 | - vm.startPrank(account); | |
| 481 | - weth.approve(address(love), total); | |
| 482 | - uint256[] memory depositIds = love.deposit(secretHashes, wethAmounts); | |
| 483 | - vm.stopPrank(); | |
| 484 | - return depositIds; | |
| 485 | - } | |
| 486 | - | |
| 487 | - function _hashes(bytes32 a, bytes32 b) internal pure returns (bytes32[] memory hashes) { | |
| 488 | - hashes = new bytes32[](2); | |
| 489 | - hashes[0] = a; | |
| 490 | - hashes[1] = b; | |
| 491 | - } | |
| 492 | - | |
| 493 | - function _amounts(uint256 a, uint256 b) internal pure returns (uint256[] memory amounts) { | |
| 494 | - amounts = new uint256[](2); | |
| 495 | - amounts[0] = a; | |
| 496 | - amounts[1] = b; | |
| 497 | - } | |
| 498 | -} | |
| deleted file mode 100644 | |||
| @@ -1,498 +0,0 @@ | |||
| 1 | -// SPDX-License-Identifier: MIT | ||
| 2 | -pragma solidity ^0.8.30; | ||
| 3 | - | ||
| 4 | -import {IWETH} from "../src/IWETH.sol"; | ||
| 5 | -import {Love} from "../src/Love.sol"; | ||
| 6 | -import {Fixtures} from "./Fixtures.sol"; | ||
| 7 | -import {Test} from "forge-std/Test.sol"; | ||
| 8 | - | ||
| 9 | -/// @title LoveEnvelopeTest | ||
| 10 | -/// @notice Exercises the secret-locked `deposit` overload and `claim`: locking | ||
| 11 | -/// wETH behind a secret hash instead of crediting the caller, bearer | ||
| 12 | -/// claiming, partial-claim accounting, and the revert paths that guard | ||
| 13 | -/// wrong secrets, unknown envelopes and over-claiming. `withdraw` is | ||
| 14 | -/// untouched by any of this — a claimed balance redeems for wETH the | ||
| 15 | -/// same way any other LOVE balance does, exercised in Love.t.sol. | ||
| 16 | -contract LoveEnvelopeTest is Test, Fixtures { | ||
| 17 | - Love public love; | ||
| 18 | - IWETH public weth; | ||
| 19 | - | ||
| 20 | - address alice = makeAddr("alice"); | ||
| 21 | - address bob = makeAddr("bob"); | ||
| 22 | - address stranger = makeAddr("stranger"); | ||
| 23 | - | ||
| 24 | - bytes32 constant SECRET = keccak256("correct secret"); | ||
| 25 | - bytes32 constant SECRET_HASH = keccak256(abi.encodePacked(SECRET)); | ||
| 26 | - | ||
| 27 | - uint256 constant RATE = 100_000; | ||
| 28 | - | ||
| 29 | - address constant WETH_AT = 0x4200000000000000000000000000000000000006; | ||
| 30 | - | ||
| 31 | - function setUp() public { | ||
| 32 | - (, weth) = setUpChain(WETH_AT, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); | ||
| 33 | - love = new Love(); | ||
| 34 | - | ||
| 35 | - _fund(alice, 10 ether); | ||
| 36 | - _fund(bob, 10 ether); | ||
| 37 | - } | ||
| 38 | - | ||
| 39 | - /*////////////////////////////////////////////////////////////// | ||
| 40 | - DEPOSIT | ||
| 41 | - //////////////////////////////////////////////////////////////*/ | ||
| 42 | - | ||
| 43 | - function test_Deposit() public { | ||
| 44 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | ||
| 45 | - | ||
| 46 | - (bytes32 secretHash, uint256 remaining) = love.envelopes(depositId); | ||
| 47 | - assertEq(depositId, 0); | ||
| 48 | - assertEq(secretHash, SECRET_HASH); | ||
| 49 | - assertEq(remaining, 1 ether * RATE); | ||
| 50 | - assertEq(love.balanceOf(address(love)), 1 ether * RATE); | ||
| 51 | - assertEq(love.balanceOf(alice), 0); | ||
| 52 | - assertEq(weth.balanceOf(address(love)), 1 ether); | ||
| 53 | - } | ||
| 54 | - | ||
| 55 | - function test_DepositIsPermissionless() public { | ||
| 56 | - _fund(stranger, 1 ether); | ||
| 57 | - | ||
| 58 | - uint256 depositId = _depositEnvelope(stranger, SECRET_HASH, 1 ether); | ||
| 59 | - | ||
| 60 | - (, uint256 remaining) = love.envelopes(depositId); | ||
| 61 | - assertEq(remaining, 1 ether * RATE); | ||
| 62 | - } | ||
| 63 | - | ||
| 64 | - function test_DepositEmitsDeposit() public { | ||
| 65 | - vm.prank(alice); | ||
| 66 | - weth.approve(address(love), 1 ether); | ||
| 67 | - | ||
| 68 | - vm.expectEmit(true, true, false, true); | ||
| 69 | - emit Love.Deposit(0, alice, SECRET_HASH, 1 ether, 1 ether * RATE); | ||
| 70 | - | ||
| 71 | - vm.prank(alice); | ||
| 72 | - love.deposit(SECRET_HASH, 1 ether); | ||
| 73 | - } | ||
| 74 | - | ||
| 75 | - function test_DepositIncrementsDepositId() public { | ||
| 76 | - uint256 first = _depositEnvelope(alice, SECRET_HASH, 0.25 ether); | ||
| 77 | - uint256 second = _depositEnvelope(bob, keccak256("another secret"), 0.25 ether); | ||
| 78 | - | ||
| 79 | - assertEq(first, 0); | ||
| 80 | - assertEq(second, 1); | ||
| 81 | - assertEq(love.nextDepositId(), 2); | ||
| 82 | - } | ||
| 83 | - | ||
| 84 | - function test_DepositKeepsPegInvariant() public { | ||
| 85 | - _depositEnvelope(alice, SECRET_HASH, 1 ether); | ||
| 86 | - | ||
| 87 | - assertEq(love.totalSupply(), weth.balanceOf(address(love)) * RATE); | ||
| 88 | - } | ||
| 89 | - | ||
| 90 | - function test_RevertWhen_DepositWithoutApproval() public { | ||
| 91 | - vm.prank(alice); | ||
| 92 | - vm.expectRevert(bytes("")); | ||
| 93 | - love.deposit(SECRET_HASH, 1 ether); | ||
| 94 | - } | ||
| 95 | - | ||
| 96 | - function test_RevertWhen_DepositExceedsWethBalance() public { | ||
| 97 | - vm.prank(alice); | ||
| 98 | - weth.approve(address(love), 100 ether); | ||
| 99 | - | ||
| 100 | - vm.prank(alice); | ||
| 101 | - vm.expectRevert(bytes("")); | ||
| 102 | - love.deposit(SECRET_HASH, 100 ether); | ||
| 103 | - } | ||
| 104 | - | ||
| 105 | - function test_RevertWhen_SecretHashIsZero() public { | ||
| 106 | - vm.prank(alice); | ||
| 107 | - weth.approve(address(love), 1 ether); | ||
| 108 | - | ||
| 109 | - vm.prank(alice); | ||
| 110 | - vm.expectRevert(Love.ZeroSecretHash.selector); | ||
| 111 | - love.deposit(bytes32(0), 1 ether); | ||
| 112 | - } | ||
| 113 | - | ||
| 114 | - /*////////////////////////////////////////////////////////////// | ||
| 115 | - CLAIM | ||
| 116 | - //////////////////////////////////////////////////////////////*/ | ||
| 117 | - | ||
| 118 | - function test_Claim() public { | ||
| 119 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | ||
| 120 | - | ||
| 121 | - vm.prank(stranger); | ||
| 122 | - love.claim(depositId, SECRET, 1 ether * RATE); | ||
| 123 | - | ||
| 124 | - (, uint256 remaining) = love.envelopes(depositId); | ||
| 125 | - assertEq(remaining, 0); | ||
| 126 | - assertEq(love.balanceOf(stranger), 1 ether * RATE); | ||
| 127 | - assertEq(love.balanceOf(address(love)), 0); | ||
| 128 | - } | ||
| 129 | - | ||
| 130 | - /// @dev Bearer semantics: knowledge of the secret is what pays out, not | ||
| 131 | - /// being the original depositor. | ||
| 132 | - function test_ClaimByAnyoneWhoKnowsSecret() public { | ||
| 133 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | ||
| 134 | - | ||
| 135 | - vm.prank(bob); | ||
| 136 | - love.claim(depositId, SECRET, 1 ether * RATE); | ||
| 137 | - | ||
| 138 | - assertEq(love.balanceOf(bob), 1 ether * RATE); | ||
| 139 | - } | ||
| 140 | - | ||
| 141 | - function test_PartialClaim() public { | ||
| 142 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | ||
| 143 | - | ||
| 144 | - vm.prank(bob); | ||
| 145 | - love.claim(depositId, SECRET, 0.25 ether * RATE); | ||
| 146 | - | ||
| 147 | - (, uint256 remaining) = love.envelopes(depositId); | ||
| 148 | - assertEq(remaining, 0.75 ether * RATE); | ||
| 149 | - assertEq(love.balanceOf(bob), 0.25 ether * RATE); | ||
| 150 | - } | ||
| 151 | - | ||
| 152 | - function test_MultiplePartialClaimsDrainEnvelope() public { | ||
| 153 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | ||
| 154 | - | ||
| 155 | - vm.startPrank(bob); | ||
| 156 | - love.claim(depositId, SECRET, 0.4 ether * RATE); | ||
| 157 | - love.claim(depositId, SECRET, 0.35 ether * RATE); | ||
| 158 | - love.claim(depositId, SECRET, 0.25 ether * RATE); | ||
| 159 | - vm.stopPrank(); | ||
| 160 | - | ||
| 161 | - (, uint256 remaining) = love.envelopes(depositId); | ||
| 162 | - assertEq(remaining, 0); | ||
| 163 | - assertEq(love.balanceOf(address(love)), 0); | ||
| 164 | - } | ||
| 165 | - | ||
| 166 | - function test_ClaimEmitsClaim() public { | ||
| 167 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | ||
| 168 | - | ||
| 169 | - vm.expectEmit(true, true, false, true); | ||
| 170 | - emit Love.Claim(depositId, bob, 0.4 ether * RATE, 0.6 ether * RATE); | ||
| 171 | - | ||
| 172 | - vm.prank(bob); | ||
| 173 | - love.claim(depositId, SECRET, 0.4 ether * RATE); | ||
| 174 | - } | ||
| 175 | - | ||
| 176 | - /// @dev A claimed balance is an ordinary LOVE balance: `withdraw` (untouched | ||
| 177 | - /// by the envelope feature) redeems it for wETH like any other. | ||
| 178 | - function test_ClaimedLoveIsRedeemableThroughWithdraw() public { | ||
| 179 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | ||
| 180 | - | ||
| 181 | - vm.prank(bob); | ||
| 182 | - love.claim(depositId, SECRET, 1 ether * RATE); | ||
| 183 | - | ||
| 184 | - vm.prank(bob); | ||
| 185 | - love.withdraw(1 ether * RATE); | ||
| 186 | - | ||
| 187 | - assertEq(weth.balanceOf(bob), 11 ether); | ||
| 188 | - assertEq(love.totalSupply(), 0); | ||
| 189 | - } | ||
| 190 | - | ||
| 191 | - function test_ClaimZero() public { | ||
| 192 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | ||
| 193 | - | ||
| 194 | - vm.prank(bob); | ||
| 195 | - love.claim(depositId, SECRET, 0); | ||
| 196 | - | ||
| 197 | - (, uint256 remaining) = love.envelopes(depositId); | ||
| 198 | - assertEq(remaining, 1 ether * RATE); | ||
| 199 | - } | ||
| 200 | - | ||
| 201 | - function test_EnvelopesAreIndependent() public { | ||
| 202 | - bytes32 otherSecret = keccak256("another secret"); | ||
| 203 | - uint256 aliceEnvelope = _depositEnvelope(alice, SECRET_HASH, 0.5 ether); | ||
| 204 | - uint256 bobEnvelope = _depositEnvelope(bob, keccak256(abi.encodePacked(otherSecret)), 0.5 ether); | ||
| 205 | - | ||
| 206 | - vm.prank(stranger); | ||
| 207 | - love.claim(aliceEnvelope, SECRET, 0.5 ether * RATE); | ||
| 208 | - | ||
| 209 | - (, uint256 bobRemaining) = love.envelopes(bobEnvelope); | ||
| 210 | - assertEq(bobRemaining, 0.5 ether * RATE); | ||
| 211 | - | ||
| 212 | - vm.prank(stranger); | ||
| 213 | - love.claim(bobEnvelope, otherSecret, 0.5 ether * RATE); | ||
| 214 | - | ||
| 215 | - assertEq(love.balanceOf(stranger), 1 ether * RATE); | ||
| 216 | - } | ||
| 217 | - | ||
| 218 | - function test_RevertWhen_ClaimWrongSecret() public { | ||
| 219 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | ||
| 220 | - | ||
| 221 | - vm.prank(stranger); | ||
| 222 | - vm.expectRevert(abi.encodeWithSelector(Love.WrongSecret.selector, depositId)); | ||
| 223 | - love.claim(depositId, keccak256("wrong guess"), 1 ether * RATE); | ||
| 224 | - } | ||
| 225 | - | ||
| 226 | - function test_RevertWhen_ClaimUnknownDeposit() public { | ||
| 227 | - vm.prank(stranger); | ||
| 228 | - vm.expectRevert(abi.encodeWithSelector(Love.UnknownDeposit.selector, 42)); | ||
| 229 | - love.claim(42, SECRET, 1); | ||
| 230 | - } | ||
| 231 | - | ||
| 232 | - function test_RevertWhen_ClaimExceedsRemaining() public { | ||
| 233 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | ||
| 234 | - | ||
| 235 | - vm.prank(stranger); | ||
| 236 | - vm.expectRevert( | ||
| 237 | - abi.encodeWithSelector(Love.InsufficientRemaining.selector, depositId, 2 ether * RATE, 1 ether * RATE) | ||
| 238 | - ); | ||
| 239 | - love.claim(depositId, SECRET, 2 ether * RATE); | ||
| 240 | - } | ||
| 241 | - | ||
| 242 | - function test_RevertWhen_ClaimAfterFullyDrained() public { | ||
| 243 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | ||
| 244 | - | ||
| 245 | - vm.prank(bob); | ||
| 246 | - love.claim(depositId, SECRET, 1 ether * RATE); | ||
| 247 | - | ||
| 248 | - vm.prank(stranger); | ||
| 249 | - vm.expectRevert(abi.encodeWithSelector(Love.InsufficientRemaining.selector, depositId, 1, 0)); | ||
| 250 | - love.claim(depositId, SECRET, 1); | ||
| 251 | - } | ||
| 252 | - | ||
| 253 | - /*////////////////////////////////////////////////////////////// | ||
| 254 | - BATCH DEPOSIT | ||
| 255 | - //////////////////////////////////////////////////////////////*/ | ||
| 256 | - | ||
| 257 | - bytes32 constant SECRET_B = keccak256("second secret"); | ||
| 258 | - bytes32 constant SECRET_B_HASH = keccak256(abi.encodePacked(SECRET_B)); | ||
| 259 | - | ||
| 260 | - function test_BatchDeposit() public { | ||
| 261 | - uint256[] memory depositIds = | ||
| 262 | - _depositBatch(alice, _hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | ||
| 263 | - | ||
| 264 | - assertEq(depositIds.length, 2); | ||
| 265 | - assertEq(depositIds[0], 0); | ||
| 266 | - assertEq(depositIds[1], 1); | ||
| 267 | - assertEq(love.nextDepositId(), 2); | ||
| 268 | - | ||
| 269 | - (bytes32 firstHash, uint256 firstRemaining) = love.envelopes(0); | ||
| 270 | - (bytes32 secondHash, uint256 secondRemaining) = love.envelopes(1); | ||
| 271 | - assertEq(firstHash, SECRET_HASH); | ||
| 272 | - assertEq(firstRemaining, 0.2 ether * RATE); | ||
| 273 | - assertEq(secondHash, SECRET_B_HASH); | ||
| 274 | - assertEq(secondRemaining, 0.8 ether * RATE); | ||
| 275 | - | ||
| 276 | - assertEq(love.balanceOf(address(love)), 1 ether * RATE); | ||
| 277 | - assertEq(love.balanceOf(alice), 0); | ||
| 278 | - assertEq(weth.balanceOf(address(love)), 1 ether); | ||
| 279 | - assertEq(weth.balanceOf(alice), 9 ether); | ||
| 280 | - } | ||
| 281 | - | ||
| 282 | - function test_BatchDepositContinuesFromNextDepositId() public { | ||
| 283 | - _depositEnvelope(alice, keccak256("earlier"), 0.1 ether); | ||
| 284 | - | ||
| 285 | - uint256[] memory depositIds = | ||
| 286 | - _depositBatch(alice, _hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | ||
| 287 | - | ||
| 288 | - assertEq(depositIds[0], 1); | ||
| 289 | - assertEq(depositIds[1], 2); | ||
| 290 | - assertEq(love.nextDepositId(), 3); | ||
| 291 | - } | ||
| 292 | - | ||
| 293 | - function test_BatchDepositEmitsOneDepositPerEnvelope() public { | ||
| 294 | - vm.prank(alice); | ||
| 295 | - weth.approve(address(love), 1 ether); | ||
| 296 | - | ||
| 297 | - vm.expectEmit(true, true, false, true); | ||
| 298 | - emit Love.Deposit(0, alice, SECRET_HASH, 0.2 ether, 0.2 ether * RATE); | ||
| 299 | - vm.expectEmit(true, true, false, true); | ||
| 300 | - emit Love.Deposit(1, alice, SECRET_B_HASH, 0.8 ether, 0.8 ether * RATE); | ||
| 301 | - | ||
| 302 | - vm.prank(alice); | ||
| 303 | - love.deposit(_hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | ||
| 304 | - } | ||
| 305 | - | ||
| 306 | - function test_BatchDepositKeepsPegInvariant() public { | ||
| 307 | - _depositBatch(alice, _hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | ||
| 308 | - | ||
| 309 | - assertEq(love.totalSupply(), weth.balanceOf(address(love)) * RATE); | ||
| 310 | - } | ||
| 311 | - | ||
| 312 | - /// @dev The point of the batch: each recipient gets their own share and | ||
| 313 | - /// nothing else, with a single full claim each. | ||
| 314 | - function test_BatchRecipientsClaimOnlyTheirShare() public { | ||
| 315 | - uint256[] memory depositIds = | ||
| 316 | - _depositBatch(alice, _hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | ||
| 317 | - | ||
| 318 | - vm.prank(bob); | ||
| 319 | - love.claim(depositIds[0], SECRET, 0.2 ether * RATE); | ||
| 320 | - vm.prank(stranger); | ||
| 321 | - love.claim(depositIds[1], SECRET_B, 0.8 ether * RATE); | ||
| 322 | - | ||
| 323 | - assertEq(love.balanceOf(bob), 0.2 ether * RATE); | ||
| 324 | - assertEq(love.balanceOf(stranger), 0.8 ether * RATE); | ||
| 325 | - assertEq(love.balanceOf(address(love)), 0); | ||
| 326 | - } | ||
| 327 | - | ||
| 328 | - function test_RevertWhen_BatchRecipientClaimsPastTheirShare() public { | ||
| 329 | - uint256[] memory depositIds = | ||
| 330 | - _depositBatch(alice, _hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | ||
| 331 | - | ||
| 332 | - vm.prank(bob); | ||
| 333 | - vm.expectRevert( | ||
| 334 | - abi.encodeWithSelector( | ||
| 335 | - Love.InsufficientRemaining.selector, depositIds[0], 0.5 ether * RATE, 0.2 ether * RATE | ||
| 336 | - ) | ||
| 337 | - ); | ||
| 338 | - love.claim(depositIds[0], SECRET, 0.5 ether * RATE); | ||
| 339 | - } | ||
| 340 | - | ||
| 341 | - function test_RevertWhen_BatchSecretOpensTheWrongEnvelope() public { | ||
| 342 | - uint256[] memory depositIds = | ||
| 343 | - _depositBatch(alice, _hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | ||
| 344 | - | ||
| 345 | - vm.prank(bob); | ||
| 346 | - vm.expectRevert(abi.encodeWithSelector(Love.WrongSecret.selector, depositIds[1])); | ||
| 347 | - love.claim(depositIds[1], SECRET, 0.8 ether * RATE); | ||
| 348 | - } | ||
| 349 | - | ||
| 350 | - function test_BatchOfOneMatchesSingleDeposit() public { | ||
| 351 | - uint256 single = _depositEnvelope(alice, SECRET_HASH, 1 ether); | ||
| 352 | - bytes32[] memory hashes = new bytes32[](1); | ||
| 353 | - hashes[0] = SECRET_HASH; | ||
| 354 | - uint256[] memory amounts = new uint256[](1); | ||
| 355 | - amounts[0] = 1 ether; | ||
| 356 | - uint256[] memory batch = _depositBatch(alice, hashes, amounts); | ||
| 357 | - | ||
| 358 | - (bytes32 singleHash, uint256 singleRemaining) = love.envelopes(single); | ||
| 359 | - (bytes32 batchHash, uint256 batchRemaining) = love.envelopes(batch[0]); | ||
| 360 | - assertEq(batchHash, singleHash); | ||
| 361 | - assertEq(batchRemaining, singleRemaining); | ||
| 362 | - } | ||
| 363 | - | ||
| 364 | - function test_RevertWhen_BatchIsEmpty() public { | ||
| 365 | - vm.prank(alice); | ||
| 366 | - vm.expectRevert(abi.encodeWithSelector(Love.InvalidBatch.selector, 0, 0)); | ||
| 367 | - love.deposit(new bytes32[](0), new uint256[](0)); | ||
| 368 | - } | ||
| 369 | - | ||
| 370 | - function test_RevertWhen_BatchLengthsDiffer() public { | ||
| 371 | - bytes32[] memory hashes = _hashes(SECRET_HASH, SECRET_B_HASH); | ||
| 372 | - uint256[] memory amounts = new uint256[](1); | ||
| 373 | - amounts[0] = 1 ether; | ||
| 374 | - | ||
| 375 | - vm.prank(alice); | ||
| 376 | - vm.expectRevert(abi.encodeWithSelector(Love.InvalidBatch.selector, 2, 1)); | ||
| 377 | - love.deposit(hashes, amounts); | ||
| 378 | - } | ||
| 379 | - | ||
| 380 | - function test_RevertWhen_BatchContainsZeroSecretHash() public { | ||
| 381 | - vm.prank(alice); | ||
| 382 | - weth.approve(address(love), 1 ether); | ||
| 383 | - | ||
| 384 | - vm.prank(alice); | ||
| 385 | - vm.expectRevert(Love.ZeroSecretHash.selector); | ||
| 386 | - love.deposit(_hashes(SECRET_HASH, bytes32(0)), _amounts(0.2 ether, 0.8 ether)); | ||
| 387 | - | ||
| 388 | - assertEq(love.nextDepositId(), 0); | ||
| 389 | - assertEq(weth.balanceOf(address(love)), 0); | ||
| 390 | - } | ||
| 391 | - | ||
| 392 | - function test_RevertWhen_BatchWithoutApproval() public { | ||
| 393 | - vm.prank(alice); | ||
| 394 | - vm.expectRevert(bytes("")); | ||
| 395 | - love.deposit(_hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | ||
| 396 | - } | ||
| 397 | - | ||
| 398 | - function test_RevertWhen_BatchApprovalCoversOnlyPartOfTheSum() public { | ||
| 399 | - vm.prank(alice); | ||
| 400 | - weth.approve(address(love), 0.8 ether); | ||
| 401 | - | ||
| 402 | - vm.prank(alice); | ||
| 403 | - vm.expectRevert(bytes("")); | ||
| 404 | - love.deposit(_hashes(SECRET_HASH, SECRET_B_HASH), _amounts(0.2 ether, 0.8 ether)); | ||
| 405 | - } | ||
| 406 | - | ||
| 407 | - /*////////////////////////////////////////////////////////////// | ||
| 408 | - FUZZ | ||
| 409 | - //////////////////////////////////////////////////////////////*/ | ||
| 410 | - | ||
| 411 | - function testFuzz_BatchDepositTotalsMatch(uint256 first, uint256 second) public { | ||
| 412 | - first = bound(first, 0, type(uint256).max / RATE / 2); | ||
| 413 | - second = bound(second, 0, type(uint256).max / RATE / 2); | ||
| 414 | - | ||
| 415 | - _fund(alice, first + second); | ||
| 416 | - uint256[] memory depositIds = _depositBatch(alice, _hashes(SECRET_HASH, SECRET_B_HASH), _amounts(first, second)); | ||
| 417 | - | ||
| 418 | - (, uint256 firstRemaining) = love.envelopes(depositIds[0]); | ||
| 419 | - (, uint256 secondRemaining) = love.envelopes(depositIds[1]); | ||
| 420 | - assertEq(firstRemaining, first * RATE); | ||
| 421 | - assertEq(secondRemaining, second * RATE); | ||
| 422 | - assertEq(love.totalSupply(), weth.balanceOf(address(love)) * RATE); | ||
| 423 | - } | ||
| 424 | - | ||
| 425 | - function testFuzz_PartialClaimsSumToTotal(uint256 wethAmount, uint256 first) public { | ||
| 426 | - wethAmount = bound(wethAmount, 0, type(uint256).max / RATE); | ||
| 427 | - uint256 total = wethAmount * RATE; | ||
| 428 | - first = bound(first, 0, total); | ||
| 429 | - uint256 second = total - first; | ||
| 430 | - | ||
| 431 | - _fund(alice, wethAmount); | ||
| 432 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, wethAmount); | ||
| 433 | - | ||
| 434 | - vm.prank(stranger); | ||
| 435 | - love.claim(depositId, SECRET, first); | ||
| 436 | - vm.prank(stranger); | ||
| 437 | - love.claim(depositId, SECRET, second); | ||
| 438 | - | ||
| 439 | - (, uint256 remaining) = love.envelopes(depositId); | ||
| 440 | - assertEq(remaining, 0); | ||
| 441 | - assertEq(love.balanceOf(stranger), total); | ||
| 442 | - } | ||
| 443 | - | ||
| 444 | - function testFuzz_WrongSecretAlwaysReverts(bytes32 wrongSecret) public { | ||
| 445 | - vm.assume(wrongSecret != SECRET); | ||
| 446 | - | ||
| 447 | - uint256 depositId = _depositEnvelope(alice, SECRET_HASH, 1 ether); | ||
| 448 | - | ||
| 449 | - vm.prank(stranger); | ||
| 450 | - vm.expectRevert(abi.encodeWithSelector(Love.WrongSecret.selector, depositId)); | ||
| 451 | - love.claim(depositId, wrongSecret, 1); | ||
| 452 | - } | ||
| 453 | - | ||
| 454 | - /// @dev Gives `account` `amount` wETH by wrapping fresh ETH. | ||
| 455 | - function _fund(address account, uint256 amount) internal { | ||
| 456 | - vm.deal(account, amount); | ||
| 457 | - vm.prank(account); | ||
| 458 | - weth.deposit{value: amount}(); | ||
| 459 | - } | ||
| 460 | - | ||
| 461 | - /// @dev Approves and opens a secret-locked envelope with `wethAmount`, in one step. | ||
| 462 | - function _depositEnvelope(address account, bytes32 secretHash, uint256 wethAmount) internal returns (uint256) { | ||
| 463 | - vm.startPrank(account); | ||
| 464 | - weth.approve(address(love), wethAmount); | ||
| 465 | - uint256 depositId = love.deposit(secretHash, wethAmount); | ||
| 466 | - vm.stopPrank(); | ||
| 467 | - return depositId; | ||
| 468 | - } | ||
| 469 | - | ||
| 470 | - /// @dev Approves the sum of `wethAmounts` and opens one envelope per entry, in one step. | ||
| 471 | - function _depositBatch(address account, bytes32[] memory secretHashes, uint256[] memory wethAmounts) | ||
| 472 | - internal | ||
| 473 | - returns (uint256[] memory) | ||
| 474 | - { | ||
| 475 | - uint256 total; | ||
| 476 | - for (uint256 i; i < wethAmounts.length; ++i) { | ||
| 477 | - total += wethAmounts[i]; | ||
| 478 | - } | ||
| 479 | - | ||
| 480 | - vm.startPrank(account); | ||
| 481 | - weth.approve(address(love), total); | ||
| 482 | - uint256[] memory depositIds = love.deposit(secretHashes, wethAmounts); | ||
| 483 | - vm.stopPrank(); | ||
| 484 | - return depositIds; | ||
| 485 | - } | ||
| 486 | - | ||
| 487 | - function _hashes(bytes32 a, bytes32 b) internal pure returns (bytes32[] memory hashes) { | ||
| 488 | - hashes = new bytes32[](2); | ||
| 489 | - hashes[0] = a; | ||
| 490 | - hashes[1] = b; | ||
| 491 | - } | ||
| 492 | - | ||
| 493 | - function _amounts(uint256 a, uint256 b) internal pure returns (uint256[] memory amounts) { | ||
| 494 | - amounts = new uint256[](2); | ||
| 495 | - amounts[0] = a; | ||
| 496 | - amounts[1] = b; | ||
| 497 | - } | ||
| 498 | -} | ||
added
test/LoveZK.t.sol +299 -0 | new file mode 100644 | ||
| @@ -0,0 +1,299 @@ | ||
| 1 | +// SPDX-License-Identifier: MIT | |
| 2 | +pragma solidity ^0.8.30; | |
| 3 | + | |
| 4 | +import {IWETH} from "../src/IWETH.sol"; | |
| 5 | +import {Love} from "../src/Love.sol"; | |
| 6 | +import {Fixtures} from "./Fixtures.sol"; | |
| 7 | +import {Test} from "forge-std/Test.sol"; | |
| 8 | + | |
| 9 | +/// @title LoveZKTest | |
| 10 | +/// @notice Exercises the private pool: `deposit(bytes32)` and `claim`, against | |
| 11 | +/// real Groth16 proofs. The proofs in `test/fixtures/proofs.json` come | |
| 12 | +/// from `zk/scripts/fixture.mjs`, and only verify for the zkey and the | |
| 13 | +/// verifier they were generated with — regenerate them whenever | |
| 14 | +/// `pnpm setup` is rerun. | |
| 15 | +/// | |
| 16 | +/// The fixture deposits three notes and proves two claims: note 0 | |
| 17 | +/// against the root after the first deposit, and note 2 against the | |
| 18 | +/// root after all three. That a proof built off-chain verifies against | |
| 19 | +/// a root built here is what pins the on-chain tree to the circuit. | |
| 20 | +contract LoveZKTest is Test, Fixtures { | |
| 21 | + struct Proof { | |
| 22 | + uint256[2] pA; | |
| 23 | + uint256[2][2] pB; | |
| 24 | + uint256[2] pC; | |
| 25 | + bytes32 root; | |
| 26 | + bytes32 nullifierHash; | |
| 27 | + } | |
| 28 | + | |
| 29 | + Love public love; | |
| 30 | + IWETH public weth; | |
| 31 | + | |
| 32 | + address alice = makeAddr("alice"); | |
| 33 | + address stranger = makeAddr("stranger"); | |
| 34 | + | |
| 35 | + /// @dev The payee the fixture's proofs are bound to; must match `zk/scripts/fixture.mjs`. | |
| 36 | + address constant RECIPIENT = 0x00000000000000000000000000000000000B0b01; | |
| 37 | + | |
| 38 | + uint256 constant RATE = 100_000; | |
| 39 | + address constant WETH_AT = 0x4200000000000000000000000000000000000006; | |
| 40 | + | |
| 41 | + /// @dev The wETH a note is worth, and the LOVE it mints. | |
| 42 | + uint256 denomination; | |
| 43 | + uint256 noteLove; | |
| 44 | + | |
| 45 | + string fixture; | |
| 46 | + bytes32[] commitments; | |
| 47 | + | |
| 48 | + function setUp() public { | |
| 49 | + (, weth) = setUpChain(WETH_AT, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); | |
| 50 | + love = new Love(); | |
| 51 | + | |
| 52 | + denomination = love.DENOMINATION(); | |
| 53 | + noteLove = denomination * RATE; | |
| 54 | + | |
| 55 | + fixture = vm.readFile("test/fixtures/proofs.json"); | |
| 56 | + commitments = abi.decode(vm.parseJson(fixture, ".commitments"), (bytes32[])); | |
| 57 | + | |
| 58 | + assertEq(vm.parseJsonAddress(fixture, ".recipient"), RECIPIENT); | |
| 59 | + | |
| 60 | + _fund(alice, 10 * denomination); | |
| 61 | + } | |
| 62 | + | |
| 63 | + /*////////////////////////////////////////////////////////////// | |
| 64 | + DEPOSIT | |
| 65 | + //////////////////////////////////////////////////////////////*/ | |
| 66 | + | |
| 67 | + function test_Deposit() public { | |
| 68 | + _deposit(alice, commitments[0]); | |
| 69 | + | |
| 70 | + assertEq(love.nextLeafIndex(), 1); | |
| 71 | + assertEq(weth.balanceOf(address(love)), denomination); | |
| 72 | + assertEq(weth.balanceOf(alice), 9 * denomination); | |
| 73 | + assertEq(love.balanceOf(address(love)), noteLove); | |
| 74 | + assertEq(love.balanceOf(alice), 0); | |
| 75 | + } | |
| 76 | + | |
| 77 | + function test_DepositEmitsDepositWithLeafIndex() public { | |
| 78 | + vm.prank(alice); | |
| 79 | + weth.approve(address(love), 2 * denomination); | |
| 80 | + | |
| 81 | + vm.expectEmit(true, false, false, true); | |
| 82 | + emit Love.Deposit(commitments[0], 0); | |
| 83 | + vm.prank(alice); | |
| 84 | + love.deposit(commitments[0]); | |
| 85 | + | |
| 86 | + vm.expectEmit(true, false, false, true); | |
| 87 | + emit Love.Deposit(commitments[1], 1); | |
| 88 | + vm.prank(alice); | |
| 89 | + love.deposit(commitments[1]); | |
| 90 | + } | |
| 91 | + | |
| 92 | + function test_DepositIsPermissionless() public { | |
| 93 | + _fund(stranger, denomination); | |
| 94 | + _deposit(stranger, commitments[0]); | |
| 95 | + | |
| 96 | + assertEq(love.nextLeafIndex(), 1); | |
| 97 | + } | |
| 98 | + | |
| 99 | + function test_SupplyStaysFullyBackedAcrossDepositAndClaim() public { | |
| 100 | + _depositAll(); | |
| 101 | + assertEq(love.totalSupply(), weth.balanceOf(address(love)) * RATE); | |
| 102 | + | |
| 103 | + _claim(_proof(0), RECIPIENT); | |
| 104 | + assertEq(love.totalSupply(), weth.balanceOf(address(love)) * RATE); | |
| 105 | + } | |
| 106 | + | |
| 107 | + function test_RevertWhen_DepositWithoutApproval() public { | |
| 108 | + vm.prank(alice); | |
| 109 | + vm.expectRevert(); | |
| 110 | + love.deposit(commitments[0]); | |
| 111 | + } | |
| 112 | + | |
| 113 | + function test_RevertWhen_DepositExceedsWethBalance() public { | |
| 114 | + vm.startPrank(stranger); | |
| 115 | + weth.approve(address(love), denomination); | |
| 116 | + | |
| 117 | + vm.expectRevert(); | |
| 118 | + love.deposit(commitments[0]); | |
| 119 | + vm.stopPrank(); | |
| 120 | + } | |
| 121 | + | |
| 122 | + /*////////////////////////////////////////////////////////////// | |
| 123 | + CLAIM | |
| 124 | + //////////////////////////////////////////////////////////////*/ | |
| 125 | + | |
| 126 | + function test_Claim() public { | |
| 127 | + _deposit(alice, commitments[0]); | |
| 128 | + | |
| 129 | + _claim(_proof(0), RECIPIENT); | |
| 130 | + | |
| 131 | + assertEq(love.balanceOf(RECIPIENT), noteLove); | |
| 132 | + assertEq(love.balanceOf(address(love)), 0); | |
| 133 | + } | |
| 134 | + | |
| 135 | + /// @dev Note 2 sits in a leaf whose sibling is empty and whose parent is a right child. | |
| 136 | + function test_ClaimFromAFullerTree() public { | |
| 137 | + _depositAll(); | |
| 138 | + | |
| 139 | + _claim(_proof(1), RECIPIENT); | |
| 140 | + | |
| 141 | + assertEq(love.balanceOf(RECIPIENT), noteLove); | |
| 142 | + assertEq(love.balanceOf(address(love)), 2 * noteLove); | |
| 143 | + } | |
| 144 | + | |
| 145 | + /// @dev Proofs are made against whatever tree the prover saw, and deposits keep landing meanwhile. | |
| 146 | + function test_ClaimAgainstAnOldRoot() public { | |
| 147 | + _depositAll(); | |
| 148 | + | |
| 149 | + _claim(_proof(0), RECIPIENT); | |
| 150 | + | |
| 151 | + assertEq(love.balanceOf(RECIPIENT), noteLove); | |
| 152 | + } | |
| 153 | + | |
| 154 | + function test_ClaimIsPermissionless() public { | |
| 155 | + _deposit(alice, commitments[0]); | |
| 156 | + | |
| 157 | + Proof memory p = _proof(0); | |
| 158 | + vm.prank(stranger); | |
| 159 | + love.claim(p.pA, p.pB, p.pC, p.root, p.nullifierHash, RECIPIENT); | |
| 160 | + | |
| 161 | + assertEq(love.balanceOf(RECIPIENT), noteLove); | |
| 162 | + assertEq(love.balanceOf(stranger), 0); | |
| 163 | + } | |
| 164 | + | |
| 165 | + function test_ClaimEmitsClaim() public { | |
| 166 | + _deposit(alice, commitments[0]); | |
| 167 | + | |
| 168 | + vm.expectEmit(true, false, false, true); | |
| 169 | + emit Love.Claim(RECIPIENT, _proof(0).nullifierHash); | |
| 170 | + | |
| 171 | + _claim(_proof(0), RECIPIENT); | |
| 172 | + } | |
| 173 | + | |
| 174 | + function test_ClaimMarksTheNoteSpent() public { | |
| 175 | + _deposit(alice, commitments[0]); | |
| 176 | + assertFalse(love.isSpent(_proof(0).nullifierHash)); | |
| 177 | + | |
| 178 | + _claim(_proof(0), RECIPIENT); | |
| 179 | + | |
| 180 | + assertTrue(love.isSpent(_proof(0).nullifierHash)); | |
| 181 | + } | |
| 182 | + | |
| 183 | + /// @dev Claimed LOVE is an ordinary balance: `withdraw` redeems it like any other. | |
| 184 | + function test_ClaimedLoveRedeemsForWeth() public { | |
| 185 | + _deposit(alice, commitments[0]); | |
| 186 | + _claim(_proof(0), RECIPIENT); | |
| 187 | + | |
| 188 | + vm.prank(RECIPIENT); | |
| 189 | + love.withdraw(noteLove); | |
| 190 | + | |
| 191 | + assertEq(weth.balanceOf(RECIPIENT), denomination); | |
| 192 | + assertEq(love.totalSupply(), 0); | |
| 193 | + } | |
| 194 | + | |
| 195 | + function test_RevertWhen_ClaimTwice() public { | |
| 196 | + _deposit(alice, commitments[0]); | |
| 197 | + _claim(_proof(0), RECIPIENT); | |
| 198 | + | |
| 199 | + Proof memory p = _proof(0); | |
| 200 | + vm.expectRevert(abi.encodeWithSelector(Love.NoteAlreadySpent.selector, p.nullifierHash)); | |
| 201 | + love.claim(p.pA, p.pB, p.pC, p.root, p.nullifierHash, RECIPIENT); | |
| 202 | + } | |
| 203 | + | |
| 204 | + /// @dev The proof is bound to its recipient, so copying it out of the mempool redirects nothing. | |
| 205 | + function test_RevertWhen_ClaimPaysSomeoneElse() public { | |
| 206 | + _deposit(alice, commitments[0]); | |
| 207 | + | |
| 208 | + Proof memory p = _proof(0); | |
| 209 | + vm.expectRevert(Love.InvalidProof.selector); | |
| 210 | + love.claim(p.pA, p.pB, p.pC, p.root, p.nullifierHash, stranger); | |
| 211 | + } | |
| 212 | + | |
| 213 | + function test_RevertWhen_RootIsUnknown() public { | |
| 214 | + _deposit(alice, commitments[0]); | |
| 215 | + | |
| 216 | + Proof memory p = _proof(1); | |
| 217 | + vm.expectRevert(abi.encodeWithSelector(Love.UnknownRoot.selector, p.root)); | |
| 218 | + love.claim(p.pA, p.pB, p.pC, p.root, p.nullifierHash, RECIPIENT); | |
| 219 | + } | |
| 220 | + | |
| 221 | + function test_RevertWhen_NullifierHashIsSwapped() public { | |
| 222 | + _deposit(alice, commitments[0]); | |
| 223 | + | |
| 224 | + Proof memory p = _proof(0); | |
| 225 | + vm.expectRevert(Love.InvalidProof.selector); | |
| 226 | + love.claim(p.pA, p.pB, p.pC, p.root, bytes32(uint256(p.nullifierHash) + 1), RECIPIENT); | |
| 227 | + } | |
| 228 | + | |
| 229 | + function test_RevertWhen_ProofIsTampered() public { | |
| 230 | + _deposit(alice, commitments[0]); | |
| 231 | + | |
| 232 | + Proof memory p = _proof(0); | |
| 233 | + p.pC[0] += 1; | |
| 234 | + | |
| 235 | + vm.expectRevert(Love.InvalidProof.selector); | |
| 236 | + love.claim(p.pA, p.pB, p.pC, p.root, p.nullifierHash, RECIPIENT); | |
| 237 | + } | |
| 238 | + | |
| 239 | + /// @dev A proof for one note can't be replayed as another's: note 2's proof names note 2's nullifier. | |
| 240 | + function test_RevertWhen_ProofIsPairedWithAnotherNotesNullifier() public { | |
| 241 | + _depositAll(); | |
| 242 | + | |
| 243 | + Proof memory mine = _proof(1); | |
| 244 | + Proof memory other = _proof(0); | |
| 245 | + | |
| 246 | + vm.expectRevert(Love.InvalidProof.selector); | |
| 247 | + love.claim(mine.pA, mine.pB, mine.pC, mine.root, other.nullifierHash, RECIPIENT); | |
| 248 | + } | |
| 249 | + | |
| 250 | + function test_RevertWhen_ClaimedIntoAnEmptyPool() public { | |
| 251 | + Proof memory p = _proof(0); | |
| 252 | + | |
| 253 | + vm.expectRevert(abi.encodeWithSelector(Love.UnknownRoot.selector, p.root)); | |
| 254 | + love.claim(p.pA, p.pB, p.pC, p.root, p.nullifierHash, RECIPIENT); | |
| 255 | + } | |
| 256 | + | |
| 257 | + /*////////////////////////////////////////////////////////////// | |
| 258 | + HELPERS | |
| 259 | + //////////////////////////////////////////////////////////////*/ | |
| 260 | + | |
| 261 | + function _fund(address account, uint256 amount) internal { | |
| 262 | + vm.deal(account, amount); | |
| 263 | + vm.prank(account); | |
| 264 | + weth.deposit{value: amount}(); | |
| 265 | + } | |
| 266 | + | |
| 267 | + function _deposit(address account, bytes32 commitment) internal { | |
| 268 | + vm.startPrank(account); | |
| 269 | + weth.approve(address(love), denomination); | |
| 270 | + love.deposit(commitment); | |
| 271 | + vm.stopPrank(); | |
| 272 | + } | |
| 273 | + | |
| 274 | + function _depositAll() internal { | |
| 275 | + for (uint256 i; i < commitments.length; ++i) { | |
| 276 | + _deposit(alice, commitments[i]); | |
| 277 | + } | |
| 278 | + } | |
| 279 | + | |
| 280 | + function _claim(Proof memory p, address recipient) internal { | |
| 281 | + love.claim(p.pA, p.pB, p.pC, p.root, p.nullifierHash, recipient); | |
| 282 | + } | |
| 283 | + | |
| 284 | + /// @dev The `i`th claim in the fixture: 0 is note 0 against the first root, 1 is note 2 against the last. | |
| 285 | + function _proof(uint256 i) internal view returns (Proof memory p) { | |
| 286 | + string memory key = string.concat(".claims[", vm.toString(i), "]"); | |
| 287 | + | |
| 288 | + p.pA = _pair(string.concat(key, ".pA")); | |
| 289 | + p.pB = [_pair(string.concat(key, ".pB[0]")), _pair(string.concat(key, ".pB[1]"))]; | |
| 290 | + p.pC = _pair(string.concat(key, ".pC")); | |
| 291 | + p.root = vm.parseJsonBytes32(fixture, string.concat(key, ".root")); | |
| 292 | + p.nullifierHash = vm.parseJsonBytes32(fixture, string.concat(key, ".nullifierHash")); | |
| 293 | + } | |
| 294 | + | |
| 295 | + function _pair(string memory key) internal view returns (uint256[2] memory pair) { | |
| 296 | + uint256[] memory values = vm.parseJsonUintArray(fixture, key); | |
| 297 | + pair = [values[0], values[1]]; | |
| 298 | + } | |
| 299 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,299 @@ | |||
| 1 | +// SPDX-License-Identifier: MIT | ||
| 2 | +pragma solidity ^0.8.30; | ||
| 3 | + | ||
| 4 | +import {IWETH} from "../src/IWETH.sol"; | ||
| 5 | +import {Love} from "../src/Love.sol"; | ||
| 6 | +import {Fixtures} from "./Fixtures.sol"; | ||
| 7 | +import {Test} from "forge-std/Test.sol"; | ||
| 8 | + | ||
| 9 | +/// @title LoveZKTest | ||
| 10 | +/// @notice Exercises the private pool: `deposit(bytes32)` and `claim`, against | ||
| 11 | +/// real Groth16 proofs. The proofs in `test/fixtures/proofs.json` come | ||
| 12 | +/// from `zk/scripts/fixture.mjs`, and only verify for the zkey and the | ||
| 13 | +/// verifier they were generated with — regenerate them whenever | ||
| 14 | +/// `pnpm setup` is rerun. | ||
| 15 | +/// | ||
| 16 | +/// The fixture deposits three notes and proves two claims: note 0 | ||
| 17 | +/// against the root after the first deposit, and note 2 against the | ||
| 18 | +/// root after all three. That a proof built off-chain verifies against | ||
| 19 | +/// a root built here is what pins the on-chain tree to the circuit. | ||
| 20 | +contract LoveZKTest is Test, Fixtures { | ||
| 21 | + struct Proof { | ||
| 22 | + uint256[2] pA; | ||
| 23 | + uint256[2][2] pB; | ||
| 24 | + uint256[2] pC; | ||
| 25 | + bytes32 root; | ||
| 26 | + bytes32 nullifierHash; | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + Love public love; | ||
| 30 | + IWETH public weth; | ||
| 31 | + | ||
| 32 | + address alice = makeAddr("alice"); | ||
| 33 | + address stranger = makeAddr("stranger"); | ||
| 34 | + | ||
| 35 | + /// @dev The payee the fixture's proofs are bound to; must match `zk/scripts/fixture.mjs`. | ||
| 36 | + address constant RECIPIENT = 0x00000000000000000000000000000000000B0b01; | ||
| 37 | + | ||
| 38 | + uint256 constant RATE = 100_000; | ||
| 39 | + address constant WETH_AT = 0x4200000000000000000000000000000000000006; | ||
| 40 | + | ||
| 41 | + /// @dev The wETH a note is worth, and the LOVE it mints. | ||
| 42 | + uint256 denomination; | ||
| 43 | + uint256 noteLove; | ||
| 44 | + | ||
| 45 | + string fixture; | ||
| 46 | + bytes32[] commitments; | ||
| 47 | + | ||
| 48 | + function setUp() public { | ||
| 49 | + (, weth) = setUpChain(WETH_AT, WETH9_OP_LEGACY, WETH9_OP_LEGACY_CHAIN_ID); | ||
| 50 | + love = new Love(); | ||
| 51 | + | ||
| 52 | + denomination = love.DENOMINATION(); | ||
| 53 | + noteLove = denomination * RATE; | ||
| 54 | + | ||
| 55 | + fixture = vm.readFile("test/fixtures/proofs.json"); | ||
| 56 | + commitments = abi.decode(vm.parseJson(fixture, ".commitments"), (bytes32[])); | ||
| 57 | + | ||
| 58 | + assertEq(vm.parseJsonAddress(fixture, ".recipient"), RECIPIENT); | ||
| 59 | + | ||
| 60 | + _fund(alice, 10 * denomination); | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + /*////////////////////////////////////////////////////////////// | ||
| 64 | + DEPOSIT | ||
| 65 | + //////////////////////////////////////////////////////////////*/ | ||
| 66 | + | ||
| 67 | + function test_Deposit() public { | ||
| 68 | + _deposit(alice, commitments[0]); | ||
| 69 | + | ||
| 70 | + assertEq(love.nextLeafIndex(), 1); | ||
| 71 | + assertEq(weth.balanceOf(address(love)), denomination); | ||
| 72 | + assertEq(weth.balanceOf(alice), 9 * denomination); | ||
| 73 | + assertEq(love.balanceOf(address(love)), noteLove); | ||
| 74 | + assertEq(love.balanceOf(alice), 0); | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + function test_DepositEmitsDepositWithLeafIndex() public { | ||
| 78 | + vm.prank(alice); | ||
| 79 | + weth.approve(address(love), 2 * denomination); | ||
| 80 | + | ||
| 81 | + vm.expectEmit(true, false, false, true); | ||
| 82 | + emit Love.Deposit(commitments[0], 0); | ||
| 83 | + vm.prank(alice); | ||
| 84 | + love.deposit(commitments[0]); | ||
| 85 | + | ||
| 86 | + vm.expectEmit(true, false, false, true); | ||
| 87 | + emit Love.Deposit(commitments[1], 1); | ||
| 88 | + vm.prank(alice); | ||
| 89 | + love.deposit(commitments[1]); | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + function test_DepositIsPermissionless() public { | ||
| 93 | + _fund(stranger, denomination); | ||
| 94 | + _deposit(stranger, commitments[0]); | ||
| 95 | + | ||
| 96 | + assertEq(love.nextLeafIndex(), 1); | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + function test_SupplyStaysFullyBackedAcrossDepositAndClaim() public { | ||
| 100 | + _depositAll(); | ||
| 101 | + assertEq(love.totalSupply(), weth.balanceOf(address(love)) * RATE); | ||
| 102 | + | ||
| 103 | + _claim(_proof(0), RECIPIENT); | ||
| 104 | + assertEq(love.totalSupply(), weth.balanceOf(address(love)) * RATE); | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + function test_RevertWhen_DepositWithoutApproval() public { | ||
| 108 | + vm.prank(alice); | ||
| 109 | + vm.expectRevert(); | ||
| 110 | + love.deposit(commitments[0]); | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + function test_RevertWhen_DepositExceedsWethBalance() public { | ||
| 114 | + vm.startPrank(stranger); | ||
| 115 | + weth.approve(address(love), denomination); | ||
| 116 | + | ||
| 117 | + vm.expectRevert(); | ||
| 118 | + love.deposit(commitments[0]); | ||
| 119 | + vm.stopPrank(); | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + /*////////////////////////////////////////////////////////////// | ||
| 123 | + CLAIM | ||
| 124 | + //////////////////////////////////////////////////////////////*/ | ||
| 125 | + | ||
| 126 | + function test_Claim() public { | ||
| 127 | + _deposit(alice, commitments[0]); | ||
| 128 | + | ||
| 129 | + _claim(_proof(0), RECIPIENT); | ||
| 130 | + | ||
| 131 | + assertEq(love.balanceOf(RECIPIENT), noteLove); | ||
| 132 | + assertEq(love.balanceOf(address(love)), 0); | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + /// @dev Note 2 sits in a leaf whose sibling is empty and whose parent is a right child. | ||
| 136 | + function test_ClaimFromAFullerTree() public { | ||
| 137 | + _depositAll(); | ||
| 138 | + | ||
| 139 | + _claim(_proof(1), RECIPIENT); | ||
| 140 | + | ||
| 141 | + assertEq(love.balanceOf(RECIPIENT), noteLove); | ||
| 142 | + assertEq(love.balanceOf(address(love)), 2 * noteLove); | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + /// @dev Proofs are made against whatever tree the prover saw, and deposits keep landing meanwhile. | ||
| 146 | + function test_ClaimAgainstAnOldRoot() public { | ||
| 147 | + _depositAll(); | ||
| 148 | + | ||
| 149 | + _claim(_proof(0), RECIPIENT); | ||
| 150 | + | ||
| 151 | + assertEq(love.balanceOf(RECIPIENT), noteLove); | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + function test_ClaimIsPermissionless() public { | ||
| 155 | + _deposit(alice, commitments[0]); | ||
| 156 | + | ||
| 157 | + Proof memory p = _proof(0); | ||
| 158 | + vm.prank(stranger); | ||
| 159 | + love.claim(p.pA, p.pB, p.pC, p.root, p.nullifierHash, RECIPIENT); | ||
| 160 | + | ||
| 161 | + assertEq(love.balanceOf(RECIPIENT), noteLove); | ||
| 162 | + assertEq(love.balanceOf(stranger), 0); | ||
| 163 | + } | ||
| 164 | + | ||
| 165 | + function test_ClaimEmitsClaim() public { | ||
| 166 | + _deposit(alice, commitments[0]); | ||
| 167 | + | ||
| 168 | + vm.expectEmit(true, false, false, true); | ||
| 169 | + emit Love.Claim(RECIPIENT, _proof(0).nullifierHash); | ||
| 170 | + | ||
| 171 | + _claim(_proof(0), RECIPIENT); | ||
| 172 | + } | ||
| 173 | + | ||
| 174 | + function test_ClaimMarksTheNoteSpent() public { | ||
| 175 | + _deposit(alice, commitments[0]); | ||
| 176 | + assertFalse(love.isSpent(_proof(0).nullifierHash)); | ||
| 177 | + | ||
| 178 | + _claim(_proof(0), RECIPIENT); | ||
| 179 | + | ||
| 180 | + assertTrue(love.isSpent(_proof(0).nullifierHash)); | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + /// @dev Claimed LOVE is an ordinary balance: `withdraw` redeems it like any other. | ||
| 184 | + function test_ClaimedLoveRedeemsForWeth() public { | ||
| 185 | + _deposit(alice, commitments[0]); | ||
| 186 | + _claim(_proof(0), RECIPIENT); | ||
| 187 | + | ||
| 188 | + vm.prank(RECIPIENT); | ||
| 189 | + love.withdraw(noteLove); | ||
| 190 | + | ||
| 191 | + assertEq(weth.balanceOf(RECIPIENT), denomination); | ||
| 192 | + assertEq(love.totalSupply(), 0); | ||
| 193 | + } | ||
| 194 | + | ||
| 195 | + function test_RevertWhen_ClaimTwice() public { | ||
| 196 | + _deposit(alice, commitments[0]); | ||
| 197 | + _claim(_proof(0), RECIPIENT); | ||
| 198 | + | ||
| 199 | + Proof memory p = _proof(0); | ||
| 200 | + vm.expectRevert(abi.encodeWithSelector(Love.NoteAlreadySpent.selector, p.nullifierHash)); | ||
| 201 | + love.claim(p.pA, p.pB, p.pC, p.root, p.nullifierHash, RECIPIENT); | ||
| 202 | + } | ||
| 203 | + | ||
| 204 | + /// @dev The proof is bound to its recipient, so copying it out of the mempool redirects nothing. | ||
| 205 | + function test_RevertWhen_ClaimPaysSomeoneElse() public { | ||
| 206 | + _deposit(alice, commitments[0]); | ||
| 207 | + | ||
| 208 | + Proof memory p = _proof(0); | ||
| 209 | + vm.expectRevert(Love.InvalidProof.selector); | ||
| 210 | + love.claim(p.pA, p.pB, p.pC, p.root, p.nullifierHash, stranger); | ||
| 211 | + } | ||
| 212 | + | ||
| 213 | + function test_RevertWhen_RootIsUnknown() public { | ||
| 214 | + _deposit(alice, commitments[0]); | ||
| 215 | + | ||
| 216 | + Proof memory p = _proof(1); | ||
| 217 | + vm.expectRevert(abi.encodeWithSelector(Love.UnknownRoot.selector, p.root)); | ||
| 218 | + love.claim(p.pA, p.pB, p.pC, p.root, p.nullifierHash, RECIPIENT); | ||
| 219 | + } | ||
| 220 | + | ||
| 221 | + function test_RevertWhen_NullifierHashIsSwapped() public { | ||
| 222 | + _deposit(alice, commitments[0]); | ||
| 223 | + | ||
| 224 | + Proof memory p = _proof(0); | ||
| 225 | + vm.expectRevert(Love.InvalidProof.selector); | ||
| 226 | + love.claim(p.pA, p.pB, p.pC, p.root, bytes32(uint256(p.nullifierHash) + 1), RECIPIENT); | ||
| 227 | + } | ||
| 228 | + | ||
| 229 | + function test_RevertWhen_ProofIsTampered() public { | ||
| 230 | + _deposit(alice, commitments[0]); | ||
| 231 | + | ||
| 232 | + Proof memory p = _proof(0); | ||
| 233 | + p.pC[0] += 1; | ||
| 234 | + | ||
| 235 | + vm.expectRevert(Love.InvalidProof.selector); | ||
| 236 | + love.claim(p.pA, p.pB, p.pC, p.root, p.nullifierHash, RECIPIENT); | ||
| 237 | + } | ||
| 238 | + | ||
| 239 | + /// @dev A proof for one note can't be replayed as another's: note 2's proof names note 2's nullifier. | ||
| 240 | + function test_RevertWhen_ProofIsPairedWithAnotherNotesNullifier() public { | ||
| 241 | + _depositAll(); | ||
| 242 | + | ||
| 243 | + Proof memory mine = _proof(1); | ||
| 244 | + Proof memory other = _proof(0); | ||
| 245 | + | ||
| 246 | + vm.expectRevert(Love.InvalidProof.selector); | ||
| 247 | + love.claim(mine.pA, mine.pB, mine.pC, mine.root, other.nullifierHash, RECIPIENT); | ||
| 248 | + } | ||
| 249 | + | ||
| 250 | + function test_RevertWhen_ClaimedIntoAnEmptyPool() public { | ||
| 251 | + Proof memory p = _proof(0); | ||
| 252 | + | ||
| 253 | + vm.expectRevert(abi.encodeWithSelector(Love.UnknownRoot.selector, p.root)); | ||
| 254 | + love.claim(p.pA, p.pB, p.pC, p.root, p.nullifierHash, RECIPIENT); | ||
| 255 | + } | ||
| 256 | + | ||
| 257 | + /*////////////////////////////////////////////////////////////// | ||
| 258 | + HELPERS | ||
| 259 | + //////////////////////////////////////////////////////////////*/ | ||
| 260 | + | ||
| 261 | + function _fund(address account, uint256 amount) internal { | ||
| 262 | + vm.deal(account, amount); | ||
| 263 | + vm.prank(account); | ||
| 264 | + weth.deposit{value: amount}(); | ||
| 265 | + } | ||
| 266 | + | ||
| 267 | + function _deposit(address account, bytes32 commitment) internal { | ||
| 268 | + vm.startPrank(account); | ||
| 269 | + weth.approve(address(love), denomination); | ||
| 270 | + love.deposit(commitment); | ||
| 271 | + vm.stopPrank(); | ||
| 272 | + } | ||
| 273 | + | ||
| 274 | + function _depositAll() internal { | ||
| 275 | + for (uint256 i; i < commitments.length; ++i) { | ||
| 276 | + _deposit(alice, commitments[i]); | ||
| 277 | + } | ||
| 278 | + } | ||
| 279 | + | ||
| 280 | + function _claim(Proof memory p, address recipient) internal { | ||
| 281 | + love.claim(p.pA, p.pB, p.pC, p.root, p.nullifierHash, recipient); | ||
| 282 | + } | ||
| 283 | + | ||
| 284 | + /// @dev The `i`th claim in the fixture: 0 is note 0 against the first root, 1 is note 2 against the last. | ||
| 285 | + function _proof(uint256 i) internal view returns (Proof memory p) { | ||
| 286 | + string memory key = string.concat(".claims[", vm.toString(i), "]"); | ||
| 287 | + | ||
| 288 | + p.pA = _pair(string.concat(key, ".pA")); | ||
| 289 | + p.pB = [_pair(string.concat(key, ".pB[0]")), _pair(string.concat(key, ".pB[1]"))]; | ||
| 290 | + p.pC = _pair(string.concat(key, ".pC")); | ||
| 291 | + p.root = vm.parseJsonBytes32(fixture, string.concat(key, ".root")); | ||
| 292 | + p.nullifierHash = vm.parseJsonBytes32(fixture, string.concat(key, ".nullifierHash")); | ||
| 293 | + } | ||
| 294 | + | ||
| 295 | + function _pair(string memory key) internal view returns (uint256[2] memory pair) { | ||
| 296 | + uint256[] memory values = vm.parseJsonUintArray(fixture, key); | ||
| 297 | + pair = [values[0], values[1]]; | ||
| 298 | + } | ||
| 299 | +} | ||
added
test/MerkleTree.t.sol +87 -0 | new file mode 100644 | ||
| @@ -0,0 +1,87 @@ | ||
| 1 | +// SPDX-License-Identifier: MIT | |
| 2 | +pragma solidity ^0.8.30; | |
| 3 | + | |
| 4 | +import {MerkleTree} from "../src/MerkleTree.sol"; | |
| 5 | +import {StdStorage, stdStorage} from "forge-std/StdStorage.sol"; | |
| 6 | +import {Test} from "forge-std/Test.sol"; | |
| 7 | + | |
| 8 | +/// @dev `_insert` is internal; this is the smallest thing that lets a test reach it. | |
| 9 | +contract MerkleTreeHarness is MerkleTree { | |
| 10 | + function insert(bytes32 leaf) external returns (uint256) { | |
| 11 | + return _insert(leaf); | |
| 12 | + } | |
| 13 | +} | |
| 14 | + | |
| 15 | +/// @title MerkleTreeTest | |
| 16 | +/// @notice The on-chain tree against the roots `zk/lib.mjs` computed for the | |
| 17 | +/// same leaves. A proof only verifies if both sides hash identically, | |
| 18 | +/// so agreeing here is what ties the contract's Poseidon and its | |
| 19 | +/// empty-subtree values to the circuit's. | |
| 20 | +contract MerkleTreeTest is Test { | |
| 21 | + using stdStorage for StdStorage; | |
| 22 | + | |
| 23 | + MerkleTreeHarness tree; | |
| 24 | + bytes32[] commitments; | |
| 25 | + bytes32 rootAfterOne; | |
| 26 | + bytes32 rootAfterThree; | |
| 27 | + | |
| 28 | + function setUp() public { | |
| 29 | + tree = new MerkleTreeHarness(); | |
| 30 | + | |
| 31 | + string memory fixture = vm.readFile("test/fixtures/proofs.json"); | |
| 32 | + commitments = abi.decode(vm.parseJson(fixture, ".commitments"), (bytes32[])); | |
| 33 | + rootAfterOne = vm.parseJsonBytes32(fixture, ".claims[0].root"); | |
| 34 | + rootAfterThree = vm.parseJsonBytes32(fixture, ".claims[1].root"); | |
| 35 | + } | |
| 36 | + | |
| 37 | + function test_InsertBuildsTheRootTheProverExpects() public { | |
| 38 | + assertFalse(tree.isKnownRoot(rootAfterOne)); | |
| 39 | + | |
| 40 | + tree.insert(commitments[0]); | |
| 41 | + assertTrue(tree.isKnownRoot(rootAfterOne)); | |
| 42 | + assertFalse(tree.isKnownRoot(rootAfterThree)); | |
| 43 | + | |
| 44 | + tree.insert(commitments[1]); | |
| 45 | + tree.insert(commitments[2]); | |
| 46 | + assertTrue(tree.isKnownRoot(rootAfterThree)); | |
| 47 | + } | |
| 48 | + | |
| 49 | + function test_InsertKeepsOldRoots() public { | |
| 50 | + tree.insert(commitments[0]); | |
| 51 | + tree.insert(commitments[1]); | |
| 52 | + tree.insert(commitments[2]); | |
| 53 | + | |
| 54 | + assertTrue(tree.isKnownRoot(rootAfterOne)); | |
| 55 | + } | |
| 56 | + | |
| 57 | + function test_InsertAssignsConsecutiveIndexes() public { | |
| 58 | + assertEq(tree.nextLeafIndex(), 0); | |
| 59 | + | |
| 60 | + assertEq(tree.insert(commitments[0]), 0); | |
| 61 | + assertEq(tree.insert(commitments[1]), 1); | |
| 62 | + assertEq(tree.nextLeafIndex(), 2); | |
| 63 | + } | |
| 64 | + | |
| 65 | + function test_RevertWhen_LeafIsNotAFieldElement() public { | |
| 66 | + bytes32 outside = bytes32(tree.FIELD_SIZE()); | |
| 67 | + | |
| 68 | + vm.expectRevert(abi.encodeWithSelector(MerkleTree.LeafNotInField.selector, outside)); | |
| 69 | + tree.insert(outside); | |
| 70 | + } | |
| 71 | + | |
| 72 | + function test_RevertWhen_TreeIsFull() public { | |
| 73 | + stdstore.target(address(tree)).sig("nextLeafIndex()").checked_write(2 ** tree.TREE_DEPTH()); | |
| 74 | + | |
| 75 | + vm.expectRevert(MerkleTree.TreeFull.selector); | |
| 76 | + tree.insert(commitments[0]); | |
| 77 | + } | |
| 78 | + | |
| 79 | + /// @dev The last leaf that fits still goes in. | |
| 80 | + function test_InsertFillsTheTree() public { | |
| 81 | + stdstore.target(address(tree)).sig("nextLeafIndex()").checked_write(2 ** tree.TREE_DEPTH() - 1); | |
| 82 | + | |
| 83 | + tree.insert(commitments[0]); | |
| 84 | + | |
| 85 | + assertEq(tree.nextLeafIndex(), 2 ** tree.TREE_DEPTH()); | |
| 86 | + } | |
| 87 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,87 @@ | |||
| 1 | +// SPDX-License-Identifier: MIT | ||
| 2 | +pragma solidity ^0.8.30; | ||
| 3 | + | ||
| 4 | +import {MerkleTree} from "../src/MerkleTree.sol"; | ||
| 5 | +import {StdStorage, stdStorage} from "forge-std/StdStorage.sol"; | ||
| 6 | +import {Test} from "forge-std/Test.sol"; | ||
| 7 | + | ||
| 8 | +/// @dev `_insert` is internal; this is the smallest thing that lets a test reach it. | ||
| 9 | +contract MerkleTreeHarness is MerkleTree { | ||
| 10 | + function insert(bytes32 leaf) external returns (uint256) { | ||
| 11 | + return _insert(leaf); | ||
| 12 | + } | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +/// @title MerkleTreeTest | ||
| 16 | +/// @notice The on-chain tree against the roots `zk/lib.mjs` computed for the | ||
| 17 | +/// same leaves. A proof only verifies if both sides hash identically, | ||
| 18 | +/// so agreeing here is what ties the contract's Poseidon and its | ||
| 19 | +/// empty-subtree values to the circuit's. | ||
| 20 | +contract MerkleTreeTest is Test { | ||
| 21 | + using stdStorage for StdStorage; | ||
| 22 | + | ||
| 23 | + MerkleTreeHarness tree; | ||
| 24 | + bytes32[] commitments; | ||
| 25 | + bytes32 rootAfterOne; | ||
| 26 | + bytes32 rootAfterThree; | ||
| 27 | + | ||
| 28 | + function setUp() public { | ||
| 29 | + tree = new MerkleTreeHarness(); | ||
| 30 | + | ||
| 31 | + string memory fixture = vm.readFile("test/fixtures/proofs.json"); | ||
| 32 | + commitments = abi.decode(vm.parseJson(fixture, ".commitments"), (bytes32[])); | ||
| 33 | + rootAfterOne = vm.parseJsonBytes32(fixture, ".claims[0].root"); | ||
| 34 | + rootAfterThree = vm.parseJsonBytes32(fixture, ".claims[1].root"); | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + function test_InsertBuildsTheRootTheProverExpects() public { | ||
| 38 | + assertFalse(tree.isKnownRoot(rootAfterOne)); | ||
| 39 | + | ||
| 40 | + tree.insert(commitments[0]); | ||
| 41 | + assertTrue(tree.isKnownRoot(rootAfterOne)); | ||
| 42 | + assertFalse(tree.isKnownRoot(rootAfterThree)); | ||
| 43 | + | ||
| 44 | + tree.insert(commitments[1]); | ||
| 45 | + tree.insert(commitments[2]); | ||
| 46 | + assertTrue(tree.isKnownRoot(rootAfterThree)); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + function test_InsertKeepsOldRoots() public { | ||
| 50 | + tree.insert(commitments[0]); | ||
| 51 | + tree.insert(commitments[1]); | ||
| 52 | + tree.insert(commitments[2]); | ||
| 53 | + | ||
| 54 | + assertTrue(tree.isKnownRoot(rootAfterOne)); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + function test_InsertAssignsConsecutiveIndexes() public { | ||
| 58 | + assertEq(tree.nextLeafIndex(), 0); | ||
| 59 | + | ||
| 60 | + assertEq(tree.insert(commitments[0]), 0); | ||
| 61 | + assertEq(tree.insert(commitments[1]), 1); | ||
| 62 | + assertEq(tree.nextLeafIndex(), 2); | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + function test_RevertWhen_LeafIsNotAFieldElement() public { | ||
| 66 | + bytes32 outside = bytes32(tree.FIELD_SIZE()); | ||
| 67 | + | ||
| 68 | + vm.expectRevert(abi.encodeWithSelector(MerkleTree.LeafNotInField.selector, outside)); | ||
| 69 | + tree.insert(outside); | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + function test_RevertWhen_TreeIsFull() public { | ||
| 73 | + stdstore.target(address(tree)).sig("nextLeafIndex()").checked_write(2 ** tree.TREE_DEPTH()); | ||
| 74 | + | ||
| 75 | + vm.expectRevert(MerkleTree.TreeFull.selector); | ||
| 76 | + tree.insert(commitments[0]); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + /// @dev The last leaf that fits still goes in. | ||
| 80 | + function test_InsertFillsTheTree() public { | ||
| 81 | + stdstore.target(address(tree)).sig("nextLeafIndex()").checked_write(2 ** tree.TREE_DEPTH() - 1); | ||
| 82 | + | ||
| 83 | + tree.insert(commitments[0]); | ||
| 84 | + | ||
| 85 | + assertEq(tree.nextLeafIndex(), 2 ** tree.TREE_DEPTH()); | ||
| 86 | + } | ||
| 87 | +} | ||
modified
test/fixtures/README.md +6 -0 | @@ -20,3 +20,9 @@ cast code 0x4200000000000000000000000000000000000006 --rpc-url https://base-rpc. | ||
| 20 | 20 | Both hashes appear in `script/weth-codehashes.sh` output and in the allowlist in |
| 21 | 21 | `src/WETHRegistry.sol`, so a fixture that has drifted shows up as a test |
| 22 | 22 | failure rather than as a silent pass. |
| 23 | + | |
| 24 | +## proofs.json | |
| 25 | + | |
| 26 | +Three note commitments and two Groth16 claims against them, for `LoveZK.t.sol` and `MerkleTree.t.sol`: note 0 against the root after one deposit, and note 2 against the root after all three. The proofs are real, made with `zk/build/withdraw.zkey`, and verify only against the verifier generated from it. | |
| 27 | + | |
| 28 | +Regenerate with `pnpm fixture` in `zk/`, after every `pnpm setup`. | |
| @@ -20,3 +20,9 @@ cast code 0x4200000000000000000000000000000000000006 --rpc-url https://base-rpc. | |||
| 20 | Both hashes appear in `script/weth-codehashes.sh` output and in the allowlist in | 20 | Both hashes appear in `script/weth-codehashes.sh` output and in the allowlist in |
| 21 | `src/WETHRegistry.sol`, so a fixture that has drifted shows up as a test | 21 | `src/WETHRegistry.sol`, so a fixture that has drifted shows up as a test |
| 22 | failure rather than as a silent pass. | 22 | failure rather than as a silent pass. |
| 23 | + | ||
| 24 | +## proofs.json | ||
| 25 | + | ||
| 26 | +Three note commitments and two Groth16 claims against them, for `LoveZK.t.sol` and `MerkleTree.t.sol`: note 0 against the root after one deposit, and note 2 against the root after all three. The proofs are real, made with `zk/build/withdraw.zkey`, and verify only against the verifier generated from it. | ||
| 27 | + | ||
| 28 | +Regenerate with `pnpm fixture` in `zk/`, after every `pnpm setup`. | ||
added
test/fixtures/proofs.json +58 -0 | new file mode 100644 | ||
| @@ -0,0 +1,58 @@ | ||
| 1 | +{ | |
| 2 | + "recipient": "0x00000000000000000000000000000000000B0B01", | |
| 3 | + "commitments": [ | |
| 4 | + "0x1da93dbb9f270179985b5563006e4e46b605d9b59413c4c319791f15a501c61c", | |
| 5 | + "0x0072b9343811e596d5aa31f097aab21e3c50ec10aa3e1020ed25b37d761a77d4", | |
| 6 | + "0x02c9fbc3efd3c78cea00b587d69dcbd75e3e605db04a82b66f165dea154c6af6" | |
| 7 | + ], | |
| 8 | + "claims": [ | |
| 9 | + { | |
| 10 | + "index": 0, | |
| 11 | + "deposits": 1, | |
| 12 | + "root": "0x0ba282d72dd771697b29cdee3f273a72d43c36d57e7c90f65238345b90e90336", | |
| 13 | + "nullifierHash": "0x2f0409f7962f6673570d88b917021c615ea575c2654391718439eb354f9be8f3", | |
| 14 | + "pA": [ | |
| 15 | + "19985899671685615485230489593063080984230218976730575743972220479878525354049", | |
| 16 | + "9656288351138442445930772903624333254225752574068748718563744491961976477406" | |
| 17 | + ], | |
| 18 | + "pB": [ | |
| 19 | + [ | |
| 20 | + "1352151819468658215853247736840444274277259995188193783613931105233747198465", | |
| 21 | + "8322307584752402776832082583405484591763538563106416575796785963625266956631" | |
| 22 | + ], | |
| 23 | + [ | |
| 24 | + "1411863156363165378213777396029768183547761969520728584483821315334630199590", | |
| 25 | + "10162525744134706365991662211885534223475426960082674576537909458479872393097" | |
| 26 | + ] | |
| 27 | + ], | |
| 28 | + "pC": [ | |
| 29 | + "20726463816286303770990975322938534449886171037463690176035996819580072278900", | |
| 30 | + "20456014587955474124389452784866995880942649202770898209191838909874004161683" | |
| 31 | + ] | |
| 32 | + }, | |
| 33 | + { | |
| 34 | + "index": 2, | |
| 35 | + "deposits": 3, | |
| 36 | + "root": "0x142a3354435cdb81d9051a7ce8f4b0f4bb18abd5d5557ee5eb131a51ee40528a", | |
| 37 | + "nullifierHash": "0x24d3796a66736dbc07c459a93677d8b3c6ea7881b44e4cde0a52fcc03d0f8e7f", | |
| 38 | + "pA": [ | |
| 39 | + "20901554889197988848295402205924955135641347079563813975751832170904327134617", | |
| 40 | + "2525557094458962757766397236762211231289688509955117774709957955625752199622" | |
| 41 | + ], | |
| 42 | + "pB": [ | |
| 43 | + [ | |
| 44 | + "14440994844150224475969788283844910187085584048920166769425489476072925545907", | |
| 45 | + "3103676611479536236305324183488742199507252835268510120145687381186385350820" | |
| 46 | + ], | |
| 47 | + [ | |
| 48 | + "4465650689287975557621011794243733738967152791826462380354226944093598228801", | |
| 49 | + "20780071984714587831543547370261803804816843325929335800868721475351349014634" | |
| 50 | + ] | |
| 51 | + ], | |
| 52 | + "pC": [ | |
| 53 | + "11966980859274316232748964675125812560024845029715176456922542583893404737398", | |
| 54 | + "2071517565535716947846587899177075308672615976542518219555975882197986615667" | |
| 55 | + ] | |
| 56 | + } | |
| 57 | + ] | |
| 58 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | +{ | ||
| 2 | + "recipient": "0x00000000000000000000000000000000000B0B01", | ||
| 3 | + "commitments": [ | ||
| 4 | + "0x1da93dbb9f270179985b5563006e4e46b605d9b59413c4c319791f15a501c61c", | ||
| 5 | + "0x0072b9343811e596d5aa31f097aab21e3c50ec10aa3e1020ed25b37d761a77d4", | ||
| 6 | + "0x02c9fbc3efd3c78cea00b587d69dcbd75e3e605db04a82b66f165dea154c6af6" | ||
| 7 | + ], | ||
| 8 | + "claims": [ | ||
| 9 | + { | ||
| 10 | + "index": 0, | ||
| 11 | + "deposits": 1, | ||
| 12 | + "root": "0x0ba282d72dd771697b29cdee3f273a72d43c36d57e7c90f65238345b90e90336", | ||
| 13 | + "nullifierHash": "0x2f0409f7962f6673570d88b917021c615ea575c2654391718439eb354f9be8f3", | ||
| 14 | + "pA": [ | ||
| 15 | + "19985899671685615485230489593063080984230218976730575743972220479878525354049", | ||
| 16 | + "9656288351138442445930772903624333254225752574068748718563744491961976477406" | ||
| 17 | + ], | ||
| 18 | + "pB": [ | ||
| 19 | + [ | ||
| 20 | + "1352151819468658215853247736840444274277259995188193783613931105233747198465", | ||
| 21 | + "8322307584752402776832082583405484591763538563106416575796785963625266956631" | ||
| 22 | + ], | ||
| 23 | + [ | ||
| 24 | + "1411863156363165378213777396029768183547761969520728584483821315334630199590", | ||
| 25 | + "10162525744134706365991662211885534223475426960082674576537909458479872393097" | ||
| 26 | + ] | ||
| 27 | + ], | ||
| 28 | + "pC": [ | ||
| 29 | + "20726463816286303770990975322938534449886171037463690176035996819580072278900", | ||
| 30 | + "20456014587955474124389452784866995880942649202770898209191838909874004161683" | ||
| 31 | + ] | ||
| 32 | + }, | ||
| 33 | + { | ||
| 34 | + "index": 2, | ||
| 35 | + "deposits": 3, | ||
| 36 | + "root": "0x142a3354435cdb81d9051a7ce8f4b0f4bb18abd5d5557ee5eb131a51ee40528a", | ||
| 37 | + "nullifierHash": "0x24d3796a66736dbc07c459a93677d8b3c6ea7881b44e4cde0a52fcc03d0f8e7f", | ||
| 38 | + "pA": [ | ||
| 39 | + "20901554889197988848295402205924955135641347079563813975751832170904327134617", | ||
| 40 | + "2525557094458962757766397236762211231289688509955117774709957955625752199622" | ||
| 41 | + ], | ||
| 42 | + "pB": [ | ||
| 43 | + [ | ||
| 44 | + "14440994844150224475969788283844910187085584048920166769425489476072925545907", | ||
| 45 | + "3103676611479536236305324183488742199507252835268510120145687381186385350820" | ||
| 46 | + ], | ||
| 47 | + [ | ||
| 48 | + "4465650689287975557621011794243733738967152791826462380354226944093598228801", | ||
| 49 | + "20780071984714587831543547370261803804816843325929335800868721475351349014634" | ||
| 50 | + ] | ||
| 51 | + ], | ||
| 52 | + "pC": [ | ||
| 53 | + "11966980859274316232748964675125812560024845029715176456922542583893404737398", | ||
| 54 | + "2071517565535716947846587899177075308672615976542518219555975882197986615667" | ||
| 55 | + ] | ||
| 56 | + } | ||
| 57 | + ] | ||
| 58 | +} | ||
added
zk/.gitignore +2 -0 | new file mode 100644 | ||
| @@ -0,0 +1,2 @@ | ||
| 1 | +node_modules/ | |
| 2 | +build/tmp/ | |
| new file mode 100644 | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | +node_modules/ | ||
| 2 | +build/tmp/ | ||
added
zk/build/verification_key.json +104 -0 | new file mode 100644 | ||
| @@ -0,0 +1,104 @@ | ||
| 1 | +{ | |
| 2 | + "protocol": "groth16", | |
| 3 | + "curve": "bn128", | |
| 4 | + "nPublic": 3, | |
| 5 | + "vk_alpha_1": [ | |
| 6 | + "2723532631684974918406645525943558732354673276204281662712066914695900576277", | |
| 7 | + "17514683612750309974728178682926232556971957023838913535669239847332050299446", | |
| 8 | + "1" | |
| 9 | + ], | |
| 10 | + "vk_beta_2": [ | |
| 11 | + [ | |
| 12 | + "6034205146605614654431717757495133361402453255257043866720288300974269646538", | |
| 13 | + "7823948186918456580333006997609901789595273233568743751946614638907978883711" | |
| 14 | + ], | |
| 15 | + [ | |
| 16 | + "2508951487354739574077255816074521971727349482553381458702706323318855000618", | |
| 17 | + "11638984221817658074004925648741757789613528713645236774002925369248027221209" | |
| 18 | + ], | |
| 19 | + [ | |
| 20 | + "1", | |
| 21 | + "0" | |
| 22 | + ] | |
| 23 | + ], | |
| 24 | + "vk_gamma_2": [ | |
| 25 | + [ | |
| 26 | + "10857046999023057135944570762232829481370756359578518086990519993285655852781", | |
| 27 | + "11559732032986387107991004021392285783925812861821192530917403151452391805634" | |
| 28 | + ], | |
| 29 | + [ | |
| 30 | + "8495653923123431417604973247489272438418190587263600148770280649306958101930", | |
| 31 | + "4082367875863433681332203403145435568316851327593401208105741076214120093531" | |
| 32 | + ], | |
| 33 | + [ | |
| 34 | + "1", | |
| 35 | + "0" | |
| 36 | + ] | |
| 37 | + ], | |
| 38 | + "vk_delta_2": [ | |
| 39 | + [ | |
| 40 | + "10111709658227999298687264682571617846867694365717728896787905680829339798912", | |
| 41 | + "3163504613947300382025047233129943754368165108353942835225928277874778583773" | |
| 42 | + ], | |
| 43 | + [ | |
| 44 | + "8588597490669533320277373248052240038745202137023885169306697052179170691253", | |
| 45 | + "1104241400641344221595907754664029095916435027517876883451581136875208434535" | |
| 46 | + ], | |
| 47 | + [ | |
| 48 | + "1", | |
| 49 | + "0" | |
| 50 | + ] | |
| 51 | + ], | |
| 52 | + "vk_alphabeta_12": [ | |
| 53 | + [ | |
| 54 | + [ | |
| 55 | + "8122768820946797322435795800104921829508295540179889196084185459193843672065", | |
| 56 | + "17133511030722169437936186167180826412787150828114487088286965583611470398840" | |
| 57 | + ], | |
| 58 | + [ | |
| 59 | + "20169288339483177127556669102761482311931914635505873441077333943449103227113", | |
| 60 | + "8896274844813238706623851876215050463079805676343788452710066445947809711850" | |
| 61 | + ], | |
| 62 | + [ | |
| 63 | + "20162107864760943471422990101227515688878439218760330239424064313340636986759", | |
| 64 | + "12928899788541251365749025031349896620988403752746434682281419466317575902462" | |
| 65 | + ] | |
| 66 | + ], | |
| 67 | + [ | |
| 68 | + [ | |
| 69 | + "19177073875399604010934606870315583581369415138359620818997428181118715056180", | |
| 70 | + "17517714625432794558718305057769516199707606025280897930351326130901078327772" | |
| 71 | + ], | |
| 72 | + [ | |
| 73 | + "21342784343962124357256174265915816982677886097015304395669476165407447709034", | |
| 74 | + "15176190760485998404812863517285039166739825877944110675156644489000366486648" | |
| 75 | + ], | |
| 76 | + [ | |
| 77 | + "2853326677007561999337101580715900660128452807126798515202756154423538551184", | |
| 78 | + "7000601663746983256283910222398646914791675476371528762541929781567939975858" | |
| 79 | + ] | |
| 80 | + ] | |
| 81 | + ], | |
| 82 | + "IC": [ | |
| 83 | + [ | |
| 84 | + "4212190150813188043278099338020867811573069332260264154238648301447210039371", | |
| 85 | + "1764764369750003778637898487216316888935235560815878582035231064669751096562", | |
| 86 | + "1" | |
| 87 | + ], | |
| 88 | + [ | |
| 89 | + "11591500916652557381190324906157840239104175179480966128102692187195568131210", | |
| 90 | + "5118730949595120121305886043182374039722377242808578639452659793403109061323", | |
| 91 | + "1" | |
| 92 | + ], | |
| 93 | + [ | |
| 94 | + "17181191618870716694328767264500886479216739164760510841062537495596168566180", | |
| 95 | + "6745348094417067550528385806914234773573246785057338772569961117791460595409", | |
| 96 | + "1" | |
| 97 | + ], | |
| 98 | + [ | |
| 99 | + "17245225309580886468417139451106822105824195874584748226991661740342948111559", | |
| 100 | + "9416262901358787120529716248550998086567235238905571238886470859668393020882", | |
| 101 | + "1" | |
| 102 | + ] | |
| 103 | + ] | |
| 104 | +} | |
| \ No newline at end of file | ||
| new file mode 100644 | |||
| @@ -0,0 +1,104 @@ | |||
| 1 | +{ | ||
| 2 | + "protocol": "groth16", | ||
| 3 | + "curve": "bn128", | ||
| 4 | + "nPublic": 3, | ||
| 5 | + "vk_alpha_1": [ | ||
| 6 | + "2723532631684974918406645525943558732354673276204281662712066914695900576277", | ||
| 7 | + "17514683612750309974728178682926232556971957023838913535669239847332050299446", | ||
| 8 | + "1" | ||
| 9 | + ], | ||
| 10 | + "vk_beta_2": [ | ||
| 11 | + [ | ||
| 12 | + "6034205146605614654431717757495133361402453255257043866720288300974269646538", | ||
| 13 | + "7823948186918456580333006997609901789595273233568743751946614638907978883711" | ||
| 14 | + ], | ||
| 15 | + [ | ||
| 16 | + "2508951487354739574077255816074521971727349482553381458702706323318855000618", | ||
| 17 | + "11638984221817658074004925648741757789613528713645236774002925369248027221209" | ||
| 18 | + ], | ||
| 19 | + [ | ||
| 20 | + "1", | ||
| 21 | + "0" | ||
| 22 | + ] | ||
| 23 | + ], | ||
| 24 | + "vk_gamma_2": [ | ||
| 25 | + [ | ||
| 26 | + "10857046999023057135944570762232829481370756359578518086990519993285655852781", | ||
| 27 | + "11559732032986387107991004021392285783925812861821192530917403151452391805634" | ||
| 28 | + ], | ||
| 29 | + [ | ||
| 30 | + "8495653923123431417604973247489272438418190587263600148770280649306958101930", | ||
| 31 | + "4082367875863433681332203403145435568316851327593401208105741076214120093531" | ||
| 32 | + ], | ||
| 33 | + [ | ||
| 34 | + "1", | ||
| 35 | + "0" | ||
| 36 | + ] | ||
| 37 | + ], | ||
| 38 | + "vk_delta_2": [ | ||
| 39 | + [ | ||
| 40 | + "10111709658227999298687264682571617846867694365717728896787905680829339798912", | ||
| 41 | + "3163504613947300382025047233129943754368165108353942835225928277874778583773" | ||
| 42 | + ], | ||
| 43 | + [ | ||
| 44 | + "8588597490669533320277373248052240038745202137023885169306697052179170691253", | ||
| 45 | + "1104241400641344221595907754664029095916435027517876883451581136875208434535" | ||
| 46 | + ], | ||
| 47 | + [ | ||
| 48 | + "1", | ||
| 49 | + "0" | ||
| 50 | + ] | ||
| 51 | + ], | ||
| 52 | + "vk_alphabeta_12": [ | ||
| 53 | + [ | ||
| 54 | + [ | ||
| 55 | + "8122768820946797322435795800104921829508295540179889196084185459193843672065", | ||
| 56 | + "17133511030722169437936186167180826412787150828114487088286965583611470398840" | ||
| 57 | + ], | ||
| 58 | + [ | ||
| 59 | + "20169288339483177127556669102761482311931914635505873441077333943449103227113", | ||
| 60 | + "8896274844813238706623851876215050463079805676343788452710066445947809711850" | ||
| 61 | + ], | ||
| 62 | + [ | ||
| 63 | + "20162107864760943471422990101227515688878439218760330239424064313340636986759", | ||
| 64 | + "12928899788541251365749025031349896620988403752746434682281419466317575902462" | ||
| 65 | + ] | ||
| 66 | + ], | ||
| 67 | + [ | ||
| 68 | + [ | ||
| 69 | + "19177073875399604010934606870315583581369415138359620818997428181118715056180", | ||
| 70 | + "17517714625432794558718305057769516199707606025280897930351326130901078327772" | ||
| 71 | + ], | ||
| 72 | + [ | ||
| 73 | + "21342784343962124357256174265915816982677886097015304395669476165407447709034", | ||
| 74 | + "15176190760485998404812863517285039166739825877944110675156644489000366486648" | ||
| 75 | + ], | ||
| 76 | + [ | ||
| 77 | + "2853326677007561999337101580715900660128452807126798515202756154423538551184", | ||
| 78 | + "7000601663746983256283910222398646914791675476371528762541929781567939975858" | ||
| 79 | + ] | ||
| 80 | + ] | ||
| 81 | + ], | ||
| 82 | + "IC": [ | ||
| 83 | + [ | ||
| 84 | + "4212190150813188043278099338020867811573069332260264154238648301447210039371", | ||
| 85 | + "1764764369750003778637898487216316888935235560815878582035231064669751096562", | ||
| 86 | + "1" | ||
| 87 | + ], | ||
| 88 | + [ | ||
| 89 | + "11591500916652557381190324906157840239104175179480966128102692187195568131210", | ||
| 90 | + "5118730949595120121305886043182374039722377242808578639452659793403109061323", | ||
| 91 | + "1" | ||
| 92 | + ], | ||
| 93 | + [ | ||
| 94 | + "17181191618870716694328767264500886479216739164760510841062537495596168566180", | ||
| 95 | + "6745348094417067550528385806914234773573246785057338772569961117791460595409", | ||
| 96 | + "1" | ||
| 97 | + ], | ||
| 98 | + [ | ||
| 99 | + "17245225309580886468417139451106822105824195874584748226991661740342948111559", | ||
| 100 | + "9416262901358787120529716248550998086567235238905571238886470859668393020882", | ||
| 101 | + "1" | ||
| 102 | + ] | ||
| 103 | + ] | ||
| 104 | +} | ||
| \ No newline at end of file | \ No newline at end of file | ||
added
zk/build/withdraw.wasm +0 -0 | new file mode 100644 | ||
| Binary files /dev/null and b/zk/build/withdraw.wasm differ | ||
| new file mode 100644 | |||
| Binary files /dev/null and b/zk/build/withdraw.wasm differ | Binary files /dev/null and b/zk/build/withdraw.wasm differ | ||
added
zk/build/withdraw.zkey +0 -0 | new file mode 100644 | ||
| Binary files /dev/null and b/zk/build/withdraw.zkey differ | ||
| new file mode 100644 | |||
| Binary files /dev/null and b/zk/build/withdraw.zkey differ | Binary files /dev/null and b/zk/build/withdraw.zkey differ | ||
added
zk/circuits/withdraw.circom +70 -0 | new file mode 100644 | ||
| @@ -0,0 +1,70 @@ | ||
| 1 | +pragma circom 2.0.0; | |
| 2 | + | |
| 3 | +include "circomlib/circuits/poseidon.circom"; | |
| 4 | + | |
| 5 | +// Swaps `in` when `s` is 1, so a Merkle path can put the running hash on | |
| 6 | +// either side of its sibling. | |
| 7 | +template DualMux() { | |
| 8 | + signal input in[2]; | |
| 9 | + signal input s; | |
| 10 | + signal output out[2]; | |
| 11 | + | |
| 12 | + s * (1 - s) === 0; | |
| 13 | + out[0] <== (in[1] - in[0]) * s + in[0]; | |
| 14 | + out[1] <== (in[0] - in[1]) * s + in[1]; | |
| 15 | +} | |
| 16 | + | |
| 17 | +// Proves knowledge of a note in the tree under `root`, without saying which. | |
| 18 | +// | |
| 19 | +// commitment = Poseidon(nullifier, secret) the leaf `deposit` inserted | |
| 20 | +// nullifierHash = Poseidon(nullifier) published by `claim`, once | |
| 21 | +// | |
| 22 | +// `recipient` is only bound, never used: it makes the proof valid for one | |
| 23 | +// payee, so a front-runner replaying the calldata pays the same address. | |
| 24 | +template Withdraw(levels) { | |
| 25 | + signal input root; | |
| 26 | + signal input nullifierHash; | |
| 27 | + signal input recipient; | |
| 28 | + | |
| 29 | + signal input nullifier; | |
| 30 | + signal input secret; | |
| 31 | + signal input pathElements[levels]; | |
| 32 | + signal input pathIndices[levels]; | |
| 33 | + | |
| 34 | + component nullifierHasher = Poseidon(1); | |
| 35 | + nullifierHasher.inputs[0] <== nullifier; | |
| 36 | + nullifierHash === nullifierHasher.out; | |
| 37 | + | |
| 38 | + component commitmentHasher = Poseidon(2); | |
| 39 | + commitmentHasher.inputs[0] <== nullifier; | |
| 40 | + commitmentHasher.inputs[1] <== secret; | |
| 41 | + | |
| 42 | + component muxes[levels]; | |
| 43 | + component hashers[levels]; | |
| 44 | + | |
| 45 | + signal hashes[levels + 1]; | |
| 46 | + hashes[0] <== commitmentHasher.out; | |
| 47 | + | |
| 48 | + for (var i = 0; i < levels; i++) { | |
| 49 | + muxes[i] = DualMux(); | |
| 50 | + muxes[i].in[0] <== hashes[i]; | |
| 51 | + muxes[i].in[1] <== pathElements[i]; | |
| 52 | + muxes[i].s <== pathIndices[i]; | |
| 53 | + | |
| 54 | + hashers[i] = Poseidon(2); | |
| 55 | + hashers[i].inputs[0] <== muxes[i].out[0]; | |
| 56 | + hashers[i].inputs[1] <== muxes[i].out[1]; | |
| 57 | + | |
| 58 | + hashes[i + 1] <== hashers[i].out; | |
| 59 | + } | |
| 60 | + | |
| 61 | + root === hashes[levels]; | |
| 62 | + | |
| 63 | + // Groth16 doesn't constrain a public input that no constraint touches, | |
| 64 | + // so a proof could be re-issued for another `recipient`. Squaring it | |
| 65 | + // pins it. | |
| 66 | + signal recipientSquare; | |
| 67 | + recipientSquare <== recipient * recipient; | |
| 68 | +} | |
| 69 | + | |
| 70 | +component main {public [root, nullifierHash, recipient]} = Withdraw(10); | |
| new file mode 100644 | |||
| @@ -0,0 +1,70 @@ | |||
| 1 | +pragma circom 2.0.0; | ||
| 2 | + | ||
| 3 | +include "circomlib/circuits/poseidon.circom"; | ||
| 4 | + | ||
| 5 | +// Swaps `in` when `s` is 1, so a Merkle path can put the running hash on | ||
| 6 | +// either side of its sibling. | ||
| 7 | +template DualMux() { | ||
| 8 | + signal input in[2]; | ||
| 9 | + signal input s; | ||
| 10 | + signal output out[2]; | ||
| 11 | + | ||
| 12 | + s * (1 - s) === 0; | ||
| 13 | + out[0] <== (in[1] - in[0]) * s + in[0]; | ||
| 14 | + out[1] <== (in[0] - in[1]) * s + in[1]; | ||
| 15 | +} | ||
| 16 | + | ||
| 17 | +// Proves knowledge of a note in the tree under `root`, without saying which. | ||
| 18 | +// | ||
| 19 | +// commitment = Poseidon(nullifier, secret) the leaf `deposit` inserted | ||
| 20 | +// nullifierHash = Poseidon(nullifier) published by `claim`, once | ||
| 21 | +// | ||
| 22 | +// `recipient` is only bound, never used: it makes the proof valid for one | ||
| 23 | +// payee, so a front-runner replaying the calldata pays the same address. | ||
| 24 | +template Withdraw(levels) { | ||
| 25 | + signal input root; | ||
| 26 | + signal input nullifierHash; | ||
| 27 | + signal input recipient; | ||
| 28 | + | ||
| 29 | + signal input nullifier; | ||
| 30 | + signal input secret; | ||
| 31 | + signal input pathElements[levels]; | ||
| 32 | + signal input pathIndices[levels]; | ||
| 33 | + | ||
| 34 | + component nullifierHasher = Poseidon(1); | ||
| 35 | + nullifierHasher.inputs[0] <== nullifier; | ||
| 36 | + nullifierHash === nullifierHasher.out; | ||
| 37 | + | ||
| 38 | + component commitmentHasher = Poseidon(2); | ||
| 39 | + commitmentHasher.inputs[0] <== nullifier; | ||
| 40 | + commitmentHasher.inputs[1] <== secret; | ||
| 41 | + | ||
| 42 | + component muxes[levels]; | ||
| 43 | + component hashers[levels]; | ||
| 44 | + | ||
| 45 | + signal hashes[levels + 1]; | ||
| 46 | + hashes[0] <== commitmentHasher.out; | ||
| 47 | + | ||
| 48 | + for (var i = 0; i < levels; i++) { | ||
| 49 | + muxes[i] = DualMux(); | ||
| 50 | + muxes[i].in[0] <== hashes[i]; | ||
| 51 | + muxes[i].in[1] <== pathElements[i]; | ||
| 52 | + muxes[i].s <== pathIndices[i]; | ||
| 53 | + | ||
| 54 | + hashers[i] = Poseidon(2); | ||
| 55 | + hashers[i].inputs[0] <== muxes[i].out[0]; | ||
| 56 | + hashers[i].inputs[1] <== muxes[i].out[1]; | ||
| 57 | + | ||
| 58 | + hashes[i + 1] <== hashers[i].out; | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + root === hashes[levels]; | ||
| 62 | + | ||
| 63 | + // Groth16 doesn't constrain a public input that no constraint touches, | ||
| 64 | + // so a proof could be re-issued for another `recipient`. Squaring it | ||
| 65 | + // pins it. | ||
| 66 | + signal recipientSquare; | ||
| 67 | + recipientSquare <== recipient * recipient; | ||
| 68 | +} | ||
| 69 | + | ||
| 70 | +component main {public [root, nullifierHash, recipient]} = Withdraw(10); | ||
added
zk/lib.mjs +120 -0 | new file mode 100644 | ||
| @@ -0,0 +1,120 @@ | ||
| 1 | +// Client side of LOVE's ZK pool: make a note, mirror the on-chain tree, prove a claim. | |
| 2 | +// | |
| 3 | +// Everything here runs locally. A note is a bearer secret: whoever holds it can | |
| 4 | +// claim, so never send it to a server. | |
| 5 | +import { randomBytes } from "node:crypto"; | |
| 6 | +import { fileURLToPath } from "node:url"; | |
| 7 | +import { buildPoseidon } from "circomlibjs"; | |
| 8 | +import * as snarkjs from "snarkjs"; | |
| 9 | + | |
| 10 | +/** Depth of the tree; must match `MerkleTree.TREE_DEPTH` and the circuit. */ | |
| 11 | +export const LEVELS = 10; | |
| 12 | + | |
| 13 | +const WASM = fileURLToPath(new URL("./build/withdraw.wasm", import.meta.url)); | |
| 14 | +const ZKEY = fileURLToPath(new URL("./build/withdraw.zkey", import.meta.url)); | |
| 15 | + | |
| 16 | +let poseidon; | |
| 17 | + | |
| 18 | +async function hasher() { | |
| 19 | + poseidon ??= await buildPoseidon(); | |
| 20 | + return (...inputs) => poseidon.F.toObject(poseidon(inputs)); | |
| 21 | +} | |
| 22 | + | |
| 23 | +const hex = (n, bytes = 32) => "0x" + n.toString(16).padStart(bytes * 2, "0"); | |
| 24 | + | |
| 25 | +/** A fresh note: two random 248-bit values, which sit below the field size. */ | |
| 26 | +export function newNote() { | |
| 27 | + return { | |
| 28 | + nullifier: BigInt("0x" + randomBytes(31).toString("hex")), | |
| 29 | + secret: BigInt("0x" + randomBytes(31).toString("hex")), | |
| 30 | + }; | |
| 31 | +} | |
| 32 | + | |
| 33 | +/** The note as one string, to hand over or store: `0x` + nullifier + secret, 31 bytes each. */ | |
| 34 | +export function encodeNote({ nullifier, secret }) { | |
| 35 | + return "0x" + hex(nullifier, 31).slice(2) + hex(secret, 31).slice(2); | |
| 36 | +} | |
| 37 | + | |
| 38 | +export function decodeNote(encoded) { | |
| 39 | + if (!/^0x[0-9a-f]{124}$/i.test(encoded)) throw new Error("not a note"); | |
| 40 | + return { nullifier: BigInt("0x" + encoded.slice(2, 64)), secret: BigInt("0x" + encoded.slice(64)) }; | |
| 41 | +} | |
| 42 | + | |
| 43 | +/** What goes on-chain for a note: `commitment` to `deposit`, `nullifierHash` shows up in `claim`. */ | |
| 44 | +export async function noteHashes({ nullifier, secret }) { | |
| 45 | + const H = await hasher(); | |
| 46 | + return { commitment: hex(H(nullifier, secret)), nullifierHash: hex(H(nullifier)) }; | |
| 47 | +} | |
| 48 | + | |
| 49 | +/** | |
| 50 | + * The tree over `leaves` (commitments in deposit order), padded with the same | |
| 51 | + * zero values the contract uses. | |
| 52 | + */ | |
| 53 | +export async function buildTree(leaves) { | |
| 54 | + const H = await hasher(); | |
| 55 | + | |
| 56 | + const zeros = [0n]; | |
| 57 | + for (let i = 0; i < LEVELS; i++) zeros.push(H(zeros[i], zeros[i])); | |
| 58 | + | |
| 59 | + const layers = [leaves.map(BigInt)]; | |
| 60 | + for (let level = 0; level < LEVELS; level++) { | |
| 61 | + const layer = layers[level]; | |
| 62 | + const next = []; | |
| 63 | + for (let i = 0; i < layer.length; i += 2) next.push(H(layer[i], layer[i + 1] ?? zeros[level])); | |
| 64 | + layers.push(next); | |
| 65 | + } | |
| 66 | + | |
| 67 | + return { | |
| 68 | + root: hex(layers[LEVELS][0] ?? zeros[LEVELS]), | |
| 69 | + path(index) { | |
| 70 | + const pathElements = []; | |
| 71 | + const pathIndices = []; | |
| 72 | + for (let level = 0; level < LEVELS; level++) { | |
| 73 | + pathElements.push(layers[level][index ^ 1] ?? zeros[level]); | |
| 74 | + pathIndices.push(index & 1); | |
| 75 | + index >>= 1; | |
| 76 | + } | |
| 77 | + return { pathElements, pathIndices }; | |
| 78 | + }, | |
| 79 | + }; | |
| 80 | +} | |
| 81 | + | |
| 82 | +/** | |
| 83 | + * Prove `note` is in the tree over `commitments`, payable to `recipient`. | |
| 84 | + * | |
| 85 | + * `commitments` are the `Deposit` events' commitments in `leafIndex` order. | |
| 86 | + * Pass a prefix of them to prove against an older root. Returns what | |
| 87 | + * `Love.claim` takes, in the order it takes it. | |
| 88 | + */ | |
| 89 | +export async function prove({ note, commitments, recipient }) { | |
| 90 | + const { commitment, nullifierHash } = await noteHashes(note); | |
| 91 | + const index = commitments.findIndex((c) => BigInt(c) === BigInt(commitment)); | |
| 92 | + if (index < 0) throw new Error("note's commitment is not among the commitments"); | |
| 93 | + | |
| 94 | + const tree = await buildTree(commitments); | |
| 95 | + const { proof } = await snarkjs.groth16.fullProve( | |
| 96 | + { | |
| 97 | + root: BigInt(tree.root), | |
| 98 | + nullifierHash: BigInt(nullifierHash), | |
| 99 | + recipient: BigInt(recipient), | |
| 100 | + nullifier: note.nullifier, | |
| 101 | + secret: note.secret, | |
| 102 | + ...tree.path(index), | |
| 103 | + }, | |
| 104 | + WASM, | |
| 105 | + ZKEY, | |
| 106 | + ); | |
| 107 | + | |
| 108 | + // The verifier takes G2 coordinates in the opposite order to snarkjs's JSON. | |
| 109 | + return { | |
| 110 | + pA: [proof.pi_a[0], proof.pi_a[1]], | |
| 111 | + pB: [ | |
| 112 | + [proof.pi_b[0][1], proof.pi_b[0][0]], | |
| 113 | + [proof.pi_b[1][1], proof.pi_b[1][0]], | |
| 114 | + ], | |
| 115 | + pC: [proof.pi_c[0], proof.pi_c[1]], | |
| 116 | + root: tree.root, | |
| 117 | + nullifierHash, | |
| 118 | + recipient, | |
| 119 | + }; | |
| 120 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,120 @@ | |||
| 1 | +// Client side of LOVE's ZK pool: make a note, mirror the on-chain tree, prove a claim. | ||
| 2 | +// | ||
| 3 | +// Everything here runs locally. A note is a bearer secret: whoever holds it can | ||
| 4 | +// claim, so never send it to a server. | ||
| 5 | +import { randomBytes } from "node:crypto"; | ||
| 6 | +import { fileURLToPath } from "node:url"; | ||
| 7 | +import { buildPoseidon } from "circomlibjs"; | ||
| 8 | +import * as snarkjs from "snarkjs"; | ||
| 9 | + | ||
| 10 | +/** Depth of the tree; must match `MerkleTree.TREE_DEPTH` and the circuit. */ | ||
| 11 | +export const LEVELS = 10; | ||
| 12 | + | ||
| 13 | +const WASM = fileURLToPath(new URL("./build/withdraw.wasm", import.meta.url)); | ||
| 14 | +const ZKEY = fileURLToPath(new URL("./build/withdraw.zkey", import.meta.url)); | ||
| 15 | + | ||
| 16 | +let poseidon; | ||
| 17 | + | ||
| 18 | +async function hasher() { | ||
| 19 | + poseidon ??= await buildPoseidon(); | ||
| 20 | + return (...inputs) => poseidon.F.toObject(poseidon(inputs)); | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +const hex = (n, bytes = 32) => "0x" + n.toString(16).padStart(bytes * 2, "0"); | ||
| 24 | + | ||
| 25 | +/** A fresh note: two random 248-bit values, which sit below the field size. */ | ||
| 26 | +export function newNote() { | ||
| 27 | + return { | ||
| 28 | + nullifier: BigInt("0x" + randomBytes(31).toString("hex")), | ||
| 29 | + secret: BigInt("0x" + randomBytes(31).toString("hex")), | ||
| 30 | + }; | ||
| 31 | +} | ||
| 32 | + | ||
| 33 | +/** The note as one string, to hand over or store: `0x` + nullifier + secret, 31 bytes each. */ | ||
| 34 | +export function encodeNote({ nullifier, secret }) { | ||
| 35 | + return "0x" + hex(nullifier, 31).slice(2) + hex(secret, 31).slice(2); | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +export function decodeNote(encoded) { | ||
| 39 | + if (!/^0x[0-9a-f]{124}$/i.test(encoded)) throw new Error("not a note"); | ||
| 40 | + return { nullifier: BigInt("0x" + encoded.slice(2, 64)), secret: BigInt("0x" + encoded.slice(64)) }; | ||
| 41 | +} | ||
| 42 | + | ||
| 43 | +/** What goes on-chain for a note: `commitment` to `deposit`, `nullifierHash` shows up in `claim`. */ | ||
| 44 | +export async function noteHashes({ nullifier, secret }) { | ||
| 45 | + const H = await hasher(); | ||
| 46 | + return { commitment: hex(H(nullifier, secret)), nullifierHash: hex(H(nullifier)) }; | ||
| 47 | +} | ||
| 48 | + | ||
| 49 | +/** | ||
| 50 | + * The tree over `leaves` (commitments in deposit order), padded with the same | ||
| 51 | + * zero values the contract uses. | ||
| 52 | + */ | ||
| 53 | +export async function buildTree(leaves) { | ||
| 54 | + const H = await hasher(); | ||
| 55 | + | ||
| 56 | + const zeros = [0n]; | ||
| 57 | + for (let i = 0; i < LEVELS; i++) zeros.push(H(zeros[i], zeros[i])); | ||
| 58 | + | ||
| 59 | + const layers = [leaves.map(BigInt)]; | ||
| 60 | + for (let level = 0; level < LEVELS; level++) { | ||
| 61 | + const layer = layers[level]; | ||
| 62 | + const next = []; | ||
| 63 | + for (let i = 0; i < layer.length; i += 2) next.push(H(layer[i], layer[i + 1] ?? zeros[level])); | ||
| 64 | + layers.push(next); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + return { | ||
| 68 | + root: hex(layers[LEVELS][0] ?? zeros[LEVELS]), | ||
| 69 | + path(index) { | ||
| 70 | + const pathElements = []; | ||
| 71 | + const pathIndices = []; | ||
| 72 | + for (let level = 0; level < LEVELS; level++) { | ||
| 73 | + pathElements.push(layers[level][index ^ 1] ?? zeros[level]); | ||
| 74 | + pathIndices.push(index & 1); | ||
| 75 | + index >>= 1; | ||
| 76 | + } | ||
| 77 | + return { pathElements, pathIndices }; | ||
| 78 | + }, | ||
| 79 | + }; | ||
| 80 | +} | ||
| 81 | + | ||
| 82 | +/** | ||
| 83 | + * Prove `note` is in the tree over `commitments`, payable to `recipient`. | ||
| 84 | + * | ||
| 85 | + * `commitments` are the `Deposit` events' commitments in `leafIndex` order. | ||
| 86 | + * Pass a prefix of them to prove against an older root. Returns what | ||
| 87 | + * `Love.claim` takes, in the order it takes it. | ||
| 88 | + */ | ||
| 89 | +export async function prove({ note, commitments, recipient }) { | ||
| 90 | + const { commitment, nullifierHash } = await noteHashes(note); | ||
| 91 | + const index = commitments.findIndex((c) => BigInt(c) === BigInt(commitment)); | ||
| 92 | + if (index < 0) throw new Error("note's commitment is not among the commitments"); | ||
| 93 | + | ||
| 94 | + const tree = await buildTree(commitments); | ||
| 95 | + const { proof } = await snarkjs.groth16.fullProve( | ||
| 96 | + { | ||
| 97 | + root: BigInt(tree.root), | ||
| 98 | + nullifierHash: BigInt(nullifierHash), | ||
| 99 | + recipient: BigInt(recipient), | ||
| 100 | + nullifier: note.nullifier, | ||
| 101 | + secret: note.secret, | ||
| 102 | + ...tree.path(index), | ||
| 103 | + }, | ||
| 104 | + WASM, | ||
| 105 | + ZKEY, | ||
| 106 | + ); | ||
| 107 | + | ||
| 108 | + // The verifier takes G2 coordinates in the opposite order to snarkjs's JSON. | ||
| 109 | + return { | ||
| 110 | + pA: [proof.pi_a[0], proof.pi_a[1]], | ||
| 111 | + pB: [ | ||
| 112 | + [proof.pi_b[0][1], proof.pi_b[0][0]], | ||
| 113 | + [proof.pi_b[1][1], proof.pi_b[1][0]], | ||
| 114 | + ], | ||
| 115 | + pC: [proof.pi_c[0], proof.pi_c[1]], | ||
| 116 | + root: tree.root, | ||
| 117 | + nullifierHash, | ||
| 118 | + recipient, | ||
| 119 | + }; | ||
| 120 | +} | ||
added
zk/package.json +16 -0 | new file mode 100644 | ||
| @@ -0,0 +1,16 @@ | ||
| 1 | +{ | |
| 2 | + "name": "love-zk", | |
| 3 | + "private": true, | |
| 4 | + "type": "module", | |
| 5 | + "description": "Circuit, trusted setup and prover helpers for LOVE's ZK pool", | |
| 6 | + "scripts": { | |
| 7 | + "compile": "circom circuits/withdraw.circom --r1cs --wasm -l node_modules -o build/tmp", | |
| 8 | + "setup": "./scripts/setup.sh", | |
| 9 | + "fixture": "node scripts/fixture.mjs" | |
| 10 | + }, | |
| 11 | + "dependencies": { | |
| 12 | + "circomlib": "^2.0.5", | |
| 13 | + "circomlibjs": "^0.1.7", | |
| 14 | + "snarkjs": "^0.7.6" | |
| 15 | + } | |
| 16 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | +{ | ||
| 2 | + "name": "love-zk", | ||
| 3 | + "private": true, | ||
| 4 | + "type": "module", | ||
| 5 | + "description": "Circuit, trusted setup and prover helpers for LOVE's ZK pool", | ||
| 6 | + "scripts": { | ||
| 7 | + "compile": "circom circuits/withdraw.circom --r1cs --wasm -l node_modules -o build/tmp", | ||
| 8 | + "setup": "./scripts/setup.sh", | ||
| 9 | + "fixture": "node scripts/fixture.mjs" | ||
| 10 | + }, | ||
| 11 | + "dependencies": { | ||
| 12 | + "circomlib": "^2.0.5", | ||
| 13 | + "circomlibjs": "^0.1.7", | ||
| 14 | + "snarkjs": "^0.7.6" | ||
| 15 | + } | ||
| 16 | +} | ||
added
zk/pnpm-lock.yaml +867 -0 | new file mode 100644 | ||
| @@ -0,0 +1,867 @@ | ||
| 1 | +lockfileVersion: '9.0' | |
| 2 | + | |
| 3 | +settings: | |
| 4 | + autoInstallPeers: true | |
| 5 | + excludeLinksFromLockfile: false | |
| 6 | + | |
| 7 | +importers: | |
| 8 | + | |
| 9 | + .: | |
| 10 | + dependencies: | |
| 11 | + circomlib: | |
| 12 | + specifier: ^2.0.5 | |
| 13 | + version: 2.0.5 | |
| 14 | + circomlibjs: | |
| 15 | + specifier: ^0.1.7 | |
| 16 | + version: 0.1.7 | |
| 17 | + snarkjs: | |
| 18 | + specifier: ^0.7.6 | |
| 19 | + version: 0.7.6 | |
| 20 | + | |
| 21 | +packages: | |
| 22 | + | |
| 23 | + '@ethersproject/abi@5.8.0': | |
| 24 | + resolution: {integrity: sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==} | |
| 25 | + | |
| 26 | + '@ethersproject/abstract-provider@5.8.0': | |
| 27 | + resolution: {integrity: sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==} | |
| 28 | + | |
| 29 | + '@ethersproject/abstract-signer@5.8.0': | |
| 30 | + resolution: {integrity: sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==} | |
| 31 | + | |
| 32 | + '@ethersproject/address@5.8.0': | |
| 33 | + resolution: {integrity: sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==} | |
| 34 | + | |
| 35 | + '@ethersproject/base64@5.8.0': | |
| 36 | + resolution: {integrity: sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==} | |
| 37 | + | |
| 38 | + '@ethersproject/basex@5.8.0': | |
| 39 | + resolution: {integrity: sha512-PIgTszMlDRmNwW9nhS6iqtVfdTAKosA7llYXNmGPw4YAI1PUyMv28988wAb41/gHF/WqGdoLv0erHaRcHRKW2Q==} | |
| 40 | + | |
| 41 | + '@ethersproject/bignumber@5.8.0': | |
| 42 | + resolution: {integrity: sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==} | |
| 43 | + | |
| 44 | + '@ethersproject/bytes@5.8.0': | |
| 45 | + resolution: {integrity: sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==} | |
| 46 | + | |
| 47 | + '@ethersproject/constants@5.8.0': | |
| 48 | + resolution: {integrity: sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==} | |
| 49 | + | |
| 50 | + '@ethersproject/contracts@5.8.0': | |
| 51 | + resolution: {integrity: sha512-0eFjGz9GtuAi6MZwhb4uvUM216F38xiuR0yYCjKJpNfSEy4HUM8hvqqBj9Jmm0IUz8l0xKEhWwLIhPgxNY0yvQ==} | |
| 52 | + | |
| 53 | + '@ethersproject/hash@5.8.0': | |
| 54 | + resolution: {integrity: sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==} | |
| 55 | + | |
| 56 | + '@ethersproject/hdnode@5.8.0': | |
| 57 | + resolution: {integrity: sha512-4bK1VF6E83/3/Im0ERnnUeWOY3P1BZml4ZD3wcH8Ys0/d1h1xaFt6Zc+Dh9zXf9TapGro0T4wvO71UTCp3/uoA==} | |
| 58 | + | |
| 59 | + '@ethersproject/json-wallets@5.8.0': | |
| 60 | + resolution: {integrity: sha512-HxblNck8FVUtNxS3VTEYJAcwiKYsBIF77W15HufqlBF9gGfhmYOJtYZp8fSDZtn9y5EaXTE87zDwzxRoTFk11w==} | |
| 61 | + | |
| 62 | + '@ethersproject/keccak256@5.8.0': | |
| 63 | + resolution: {integrity: sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==} | |
| 64 | + | |
| 65 | + '@ethersproject/logger@5.8.0': | |
| 66 | + resolution: {integrity: sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==} | |
| 67 | + | |
| 68 | + '@ethersproject/networks@5.8.0': | |
| 69 | + resolution: {integrity: sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==} | |
| 70 | + | |
| 71 | + '@ethersproject/pbkdf2@5.8.0': | |
| 72 | + resolution: {integrity: sha512-wuHiv97BrzCmfEaPbUFpMjlVg/IDkZThp9Ri88BpjRleg4iePJaj2SW8AIyE8cXn5V1tuAaMj6lzvsGJkGWskg==} | |
| 73 | + | |
| 74 | + '@ethersproject/properties@5.8.0': | |
| 75 | + resolution: {integrity: sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==} | |
| 76 | + | |
| 77 | + '@ethersproject/providers@5.8.0': | |
| 78 | + resolution: {integrity: sha512-3Il3oTzEx3o6kzcg9ZzbE+oCZYyY+3Zh83sKkn4s1DZfTUjIegHnN2Cm0kbn9YFy45FDVcuCLLONhU7ny0SsCw==} | |
| 79 | + | |
| 80 | + '@ethersproject/random@5.8.0': | |
| 81 | + resolution: {integrity: sha512-E4I5TDl7SVqyg4/kkA/qTfuLWAQGXmSOgYyO01So8hLfwgKvYK5snIlzxJMk72IFdG/7oh8yuSqY2KX7MMwg+A==} | |
| 82 | + | |
| 83 | + '@ethersproject/rlp@5.8.0': | |
| 84 | + resolution: {integrity: sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==} | |
| 85 | + | |
| 86 | + '@ethersproject/sha2@5.8.0': | |
| 87 | + resolution: {integrity: sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A==} | |
| 88 | + | |
| 89 | + '@ethersproject/signing-key@5.8.0': | |
| 90 | + resolution: {integrity: sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==} | |
| 91 | + | |
| 92 | + '@ethersproject/solidity@5.8.0': | |
| 93 | + resolution: {integrity: sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA==} | |
| 94 | + | |
| 95 | + '@ethersproject/strings@5.8.0': | |
| 96 | + resolution: {integrity: sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==} | |
| 97 | + | |
| 98 | + '@ethersproject/transactions@5.8.0': | |
| 99 | + resolution: {integrity: sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==} | |
| 100 | + | |
| 101 | + '@ethersproject/units@5.8.0': | |
| 102 | + resolution: {integrity: sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==} | |
| 103 | + | |
| 104 | + '@ethersproject/wallet@5.8.0': | |
| 105 | + resolution: {integrity: sha512-G+jnzmgg6UxurVKRKvw27h0kvG75YKXZKdlLYmAHeF32TGUzHkOFd7Zn6QHOTYRFWnfjtSSFjBowKo7vfrXzPA==} | |
| 106 | + | |
| 107 | + '@ethersproject/web@5.8.0': | |
| 108 | + resolution: {integrity: sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==} | |
| 109 | + | |
| 110 | + '@ethersproject/wordlists@5.8.0': | |
| 111 | + resolution: {integrity: sha512-2df9bbXicZws2Sb5S6ET493uJ0Z84Fjr3pC4tu/qlnZERibZCeUVuqdtt+7Tv9xxhUxHoIekIA7avrKUWHrezg==} | |
| 112 | + | |
| 113 | + '@iden3/bigarray@0.0.2': | |
| 114 | + resolution: {integrity: sha512-Xzdyxqm1bOFF6pdIsiHLLl3HkSLjbhqJHVyqaTxXt3RqXBEnmsUmEW47H7VOi/ak7TdkRpNkxjyK5Zbkm+y52g==} | |
| 115 | + | |
| 116 | + '@iden3/binfileutils@0.0.12': | |
| 117 | + resolution: {integrity: sha512-naAmzuDufRIcoNfQ1d99d7hGHufLA3wZSibtr4dMe6ZeiOPV1KwOZWTJ1YVz4HbaWlpDuzVU72dS4ATQS4PXBQ==} | |
| 118 | + | |
| 119 | + '@noble/hashes@1.8.0': | |
| 120 | + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} | |
| 121 | + engines: {node: ^14.21.3 || >=16} | |
| 122 | + | |
| 123 | + aes-js@3.0.0: | |
| 124 | + resolution: {integrity: sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==} | |
| 125 | + | |
| 126 | + async@3.2.6: | |
| 127 | + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} | |
| 128 | + | |
| 129 | + b4a@1.9.0: | |
| 130 | + resolution: {integrity: sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==} | |
| 131 | + peerDependencies: | |
| 132 | + react-native-b4a: '*' | |
| 133 | + peerDependenciesMeta: | |
| 134 | + react-native-b4a: | |
| 135 | + optional: true | |
| 136 | + | |
| 137 | + balanced-match@1.0.2: | |
| 138 | + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} | |
| 139 | + | |
| 140 | + bech32@1.1.4: | |
| 141 | + resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} | |
| 142 | + | |
| 143 | + bfj@7.1.0: | |
| 144 | + resolution: {integrity: sha512-I6MMLkn+anzNdCUp9hMRyui1HaNEUCco50lxbvNS4+EyXg8lN3nJ48PjPWtbH8UVS9CuMoaKE9U2V3l29DaRQw==} | |
| 145 | + engines: {node: '>= 8.0.0'} | |
| 146 | + | |
| 147 | + blake-hash@2.0.0: | |
| 148 | + resolution: {integrity: sha512-Igj8YowDu1PRkRsxZA7NVkdFNxH5rKv5cpLxQ0CVXSIA77pVYwCPRQJ2sMew/oneUpfuYRyjG6r8SmmmnbZb1w==} | |
| 149 | + engines: {node: '>= 10'} | |
| 150 | + | |
| 151 | + blake2b-wasm@2.4.0: | |
| 152 | + resolution: {integrity: sha512-S1kwmW2ZhZFFFOghcx73+ZajEfKBqhP82JMssxtLVMxlaPea1p9uoLiUZ5WYyHn0KddwbLc+0vh4wR0KBNoT5w==} | |
| 153 | + | |
| 154 | + blake2b@2.1.4: | |
| 155 | + resolution: {integrity: sha512-AyBuuJNI64gIvwx13qiICz6H6hpmjvYS5DGkG6jbXMOT8Z3WUJ3V1X0FlhIoT1b/5JtHE3ki+xjtMvu1nn+t9A==} | |
| 156 | + | |
| 157 | + bluebird@3.7.2: | |
| 158 | + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} | |
| 159 | + | |
| 160 | + bn.js@4.12.5: | |
| 161 | + resolution: {integrity: sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==} | |
| 162 | + | |
| 163 | + bn.js@5.2.5: | |
| 164 | + resolution: {integrity: sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg==} | |
| 165 | + | |
| 166 | + brace-expansion@2.1.7: | |
| 167 | + resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==} | |
| 168 | + | |
| 169 | + brorand@1.1.0: | |
| 170 | + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} | |
| 171 | + | |
| 172 | + check-types@11.2.3: | |
| 173 | + resolution: {integrity: sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==} | |
| 174 | + | |
| 175 | + circom_runtime@0.1.28: | |
| 176 | + resolution: {integrity: sha512-ACagpQ7zBRLKDl5xRZ4KpmYIcZDUjOiNRuxvXLqhnnlLSVY1Dbvh73TI853nqoR0oEbihtWmMSjgc5f+pXf/jQ==} | |
| 177 | + hasBin: true | |
| 178 | + | |
| 179 | + circomlib@2.0.5: | |
| 180 | + resolution: {integrity: sha512-O7NQ8OS+J4eshBuoy36z/TwQU0YHw8W3zxZcs4hVwpEll3e4hDm3mgkIPqItN8FDeLEKZFK3YeT/+k8TiLF3/A==} | |
| 181 | + | |
| 182 | + circomlibjs@0.1.7: | |
| 183 | + resolution: {integrity: sha512-GRAUoAlKAsiiTa+PA725G9RmEmJJRc8tRFxw/zKktUxlQISGznT4hH4ESvW8FNTsrGg/nNd06sGP/Wlx0LUHVg==} | |
| 184 | + | |
| 185 | + ejs@3.1.10: | |
| 186 | + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} | |
| 187 | + engines: {node: '>=0.10.0'} | |
| 188 | + hasBin: true | |
| 189 | + | |
| 190 | + elliptic@6.6.1: | |
| 191 | + resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} | |
| 192 | + | |
| 193 | + escodegen@2.1.0: | |
| 194 | + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} | |
| 195 | + engines: {node: '>=6.0'} | |
| 196 | + hasBin: true | |
| 197 | + | |
| 198 | + esprima@1.2.5: | |
| 199 | + resolution: {integrity: sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==} | |
| 200 | + engines: {node: '>=0.4.0'} | |
| 201 | + hasBin: true | |
| 202 | + | |
| 203 | + esprima@4.0.1: | |
| 204 | + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} | |
| 205 | + engines: {node: '>=4'} | |
| 206 | + hasBin: true | |
| 207 | + | |
| 208 | + estraverse@5.3.0: | |
| 209 | + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} | |
| 210 | + engines: {node: '>=4.0'} | |
| 211 | + | |
| 212 | + esutils@2.0.3: | |
| 213 | + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} | |
| 214 | + engines: {node: '>=0.10.0'} | |
| 215 | + | |
| 216 | + ethers@5.8.0: | |
| 217 | + resolution: {integrity: sha512-DUq+7fHrCg1aPDFCHx6UIPb3nmt2XMpM7Y/g2gLhsl3lIBqeAfOJIl1qEvRf2uq3BiKxmh6Fh5pfp2ieyek7Kg==} | |
| 218 | + | |
| 219 | + fastfile@0.0.20: | |
| 220 | + resolution: {integrity: sha512-r5ZDbgImvVWCP0lA/cGNgQcZqR+aYdFx3u+CtJqUE510pBUVGMn4ulL/iRTI4tACTYsNJ736uzFxEBXesPAktA==} | |
| 221 | + | |
| 222 | + ffjavascript@0.2.63: | |
| 223 | + resolution: {integrity: sha512-dBgdsfGks58b66JnUZeZpGxdMIDQ4QsD3VYlRJyFVrKQHb2kJy4R2gufx5oetrTxXPT+aEjg0dOvOLg1N0on4A==} | |
| 224 | + | |
| 225 | + ffjavascript@0.3.0: | |
| 226 | + resolution: {integrity: sha512-l7sR5kmU3gRwDy8g0Z2tYBXy5ttmafRPFOqY7S6af5cq51JqJWt5eQ/lSR/rs2wQNbDYaYlQr5O+OSUf/oMLoQ==} | |
| 227 | + | |
| 228 | + ffjavascript@0.3.1: | |
| 229 | + resolution: {integrity: sha512-4PbK1WYodQtuF47D4pRI5KUg3Q392vuP5WjE1THSnceHdXwU3ijaoS0OqxTzLknCtz4Z2TtABzkBdBdMn3B/Aw==} | |
| 230 | + | |
| 231 | + filelist@1.0.6: | |
| 232 | + resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} | |
| 233 | + | |
| 234 | + hash.js@1.1.7: | |
| 235 | + resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} | |
| 236 | + | |
| 237 | + hmac-drbg@1.0.1: | |
| 238 | + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} | |
| 239 | + | |
| 240 | + hoopy@0.1.4: | |
| 241 | + resolution: {integrity: sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==} | |
| 242 | + engines: {node: '>= 6.0.0'} | |
| 243 | + | |
| 244 | + inherits@2.0.4: | |
| 245 | + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} | |
| 246 | + | |
| 247 | + jake@10.9.4: | |
| 248 | + resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} | |
| 249 | + engines: {node: '>=10'} | |
| 250 | + hasBin: true | |
| 251 | + | |
| 252 | + js-sha3@0.8.0: | |
| 253 | + resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} | |
| 254 | + | |
| 255 | + jsonpath@1.3.0: | |
| 256 | + resolution: {integrity: sha512-0kjkYHJBkAy50Z5QzArZ7udmvxrJzkpKYW27fiF//BrMY7TQibYLl+FYIXN2BiYmwMIVzSfD8aDRj6IzgBX2/w==} | |
| 257 | + | |
| 258 | + logplease@1.2.15: | |
| 259 | + resolution: {integrity: sha512-jLlHnlsPSJjpwUfcNyUxXCl33AYg2cHhIf9QhGL2T4iPT0XPB+xP1LRKFPgIg1M/sg9kAJvy94w9CzBNrfnstA==} | |
| 260 | + | |
| 261 | + minimalistic-assert@1.0.1: | |
| 262 | + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} | |
| 263 | + | |
| 264 | + minimalistic-crypto-utils@1.0.1: | |
| 265 | + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} | |
| 266 | + | |
| 267 | + minimatch@5.1.9: | |
| 268 | + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} | |
| 269 | + engines: {node: '>=10'} | |
| 270 | + | |
| 271 | + nanoassert@2.0.0: | |
| 272 | + resolution: {integrity: sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==} | |
| 273 | + | |
| 274 | + node-addon-api@3.2.1: | |
| 275 | + resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} | |
| 276 | + | |
| 277 | + node-gyp-build@4.8.4: | |
| 278 | + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} | |
| 279 | + hasBin: true | |
| 280 | + | |
| 281 | + picocolors@1.1.1: | |
| 282 | + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} | |
| 283 | + | |
| 284 | + r1csfile@0.0.48: | |
| 285 | + resolution: {integrity: sha512-kHRkKUJNaor31l05f2+RFzvcH5XSa7OfEfd/l4hzjte6NL6fjRkSMfZ4BjySW9wmfdwPOtq3mXurzPvPGEf5Tw==} | |
| 286 | + | |
| 287 | + readable-stream@3.6.2: | |
| 288 | + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} | |
| 289 | + engines: {node: '>= 6'} | |
| 290 | + | |
| 291 | + safe-buffer@5.2.1: | |
| 292 | + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} | |
| 293 | + | |
| 294 | + scrypt-js@3.0.1: | |
| 295 | + resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} | |
| 296 | + | |
| 297 | + snarkjs@0.7.6: | |
| 298 | + resolution: {integrity: sha512-4uH1xA5JzVU5jaaWS2fXej3+RC6L5Erhr6INTJtUA27du4Elbh4VXCeeRjB4QiwL6N6y7SNKePw5prTxyEf4Zg==} | |
| 299 | + hasBin: true | |
| 300 | + | |
| 301 | + source-map@0.6.1: | |
| 302 | + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} | |
| 303 | + engines: {node: '>=0.10.0'} | |
| 304 | + | |
| 305 | + static-eval@2.1.1: | |
| 306 | + resolution: {integrity: sha512-MgWpQ/ZjGieSVB3eOJVs4OA2LT/q1vx98KPCTTQPzq/aLr0YUXTsgryTXr4SLfR0ZfUUCiedM9n/ABeDIyy4mA==} | |
| 307 | + | |
| 308 | + string_decoder@1.3.0: | |
| 309 | + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} | |
| 310 | + | |
| 311 | + tryer@1.0.1: | |
| 312 | + resolution: {integrity: sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==} | |
| 313 | + | |
| 314 | + underscore@1.13.6: | |
| 315 | + resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==} | |
| 316 | + | |
| 317 | + util-deprecate@1.0.2: | |
| 318 | + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} | |
| 319 | + | |
| 320 | + wasmbuilder@0.0.16: | |
| 321 | + resolution: {integrity: sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==} | |
| 322 | + | |
| 323 | + wasmcurves@0.2.2: | |
| 324 | + resolution: {integrity: sha512-JRY908NkmKjFl4ytnTu5ED6AwPD+8VJ9oc94kdq7h5bIwbj0L4TDJ69mG+2aLs2SoCmGfqIesMWTEJjtYsoQXQ==} | |
| 325 | + | |
| 326 | + web-worker@1.2.0: | |
| 327 | + resolution: {integrity: sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==} | |
| 328 | + | |
| 329 | + ws@8.18.0: | |
| 330 | + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} | |
| 331 | + engines: {node: '>=10.0.0'} | |
| 332 | + peerDependencies: | |
| 333 | + bufferutil: ^4.0.1 | |
| 334 | + utf-8-validate: '>=5.0.2' | |
| 335 | + peerDependenciesMeta: | |
| 336 | + bufferutil: | |
| 337 | + optional: true | |
| 338 | + utf-8-validate: | |
| 339 | + optional: true | |
| 340 | + | |
| 341 | +snapshots: | |
| 342 | + | |
| 343 | + '@ethersproject/abi@5.8.0': | |
| 344 | + dependencies: | |
| 345 | + '@ethersproject/address': 5.8.0 | |
| 346 | + '@ethersproject/bignumber': 5.8.0 | |
| 347 | + '@ethersproject/bytes': 5.8.0 | |
| 348 | + '@ethersproject/constants': 5.8.0 | |
| 349 | + '@ethersproject/hash': 5.8.0 | |
| 350 | + '@ethersproject/keccak256': 5.8.0 | |
| 351 | + '@ethersproject/logger': 5.8.0 | |
| 352 | + '@ethersproject/properties': 5.8.0 | |
| 353 | + '@ethersproject/strings': 5.8.0 | |
| 354 | + | |
| 355 | + '@ethersproject/abstract-provider@5.8.0': | |
| 356 | + dependencies: | |
| 357 | + '@ethersproject/bignumber': 5.8.0 | |
| 358 | + '@ethersproject/bytes': 5.8.0 | |
| 359 | + '@ethersproject/logger': 5.8.0 | |
| 360 | + '@ethersproject/networks': 5.8.0 | |
| 361 | + '@ethersproject/properties': 5.8.0 | |
| 362 | + '@ethersproject/transactions': 5.8.0 | |
| 363 | + '@ethersproject/web': 5.8.0 | |
| 364 | + | |
| 365 | + '@ethersproject/abstract-signer@5.8.0': | |
| 366 | + dependencies: | |
| 367 | + '@ethersproject/abstract-provider': 5.8.0 | |
| 368 | + '@ethersproject/bignumber': 5.8.0 | |
| 369 | + '@ethersproject/bytes': 5.8.0 | |
| 370 | + '@ethersproject/logger': 5.8.0 | |
| 371 | + '@ethersproject/properties': 5.8.0 | |
| 372 | + | |
| 373 | + '@ethersproject/address@5.8.0': | |
| 374 | + dependencies: | |
| 375 | + '@ethersproject/bignumber': 5.8.0 | |
| 376 | + '@ethersproject/bytes': 5.8.0 | |
| 377 | + '@ethersproject/keccak256': 5.8.0 | |
| 378 | + '@ethersproject/logger': 5.8.0 | |
| 379 | + '@ethersproject/rlp': 5.8.0 | |
| 380 | + | |
| 381 | + '@ethersproject/base64@5.8.0': | |
| 382 | + dependencies: | |
| 383 | + '@ethersproject/bytes': 5.8.0 | |
| 384 | + | |
| 385 | + '@ethersproject/basex@5.8.0': | |
| 386 | + dependencies: | |
| 387 | + '@ethersproject/bytes': 5.8.0 | |
| 388 | + '@ethersproject/properties': 5.8.0 | |
| 389 | + | |
| 390 | + '@ethersproject/bignumber@5.8.0': | |
| 391 | + dependencies: | |
| 392 | + '@ethersproject/bytes': 5.8.0 | |
| 393 | + '@ethersproject/logger': 5.8.0 | |
| 394 | + bn.js: 5.2.5 | |
| 395 | + | |
| 396 | + '@ethersproject/bytes@5.8.0': | |
| 397 | + dependencies: | |
| 398 | + '@ethersproject/logger': 5.8.0 | |
| 399 | + | |
| 400 | + '@ethersproject/constants@5.8.0': | |
| 401 | + dependencies: | |
| 402 | + '@ethersproject/bignumber': 5.8.0 | |
| 403 | + | |
| 404 | + '@ethersproject/contracts@5.8.0': | |
| 405 | + dependencies: | |
| 406 | + '@ethersproject/abi': 5.8.0 | |
| 407 | + '@ethersproject/abstract-provider': 5.8.0 | |
| 408 | + '@ethersproject/abstract-signer': 5.8.0 | |
| 409 | + '@ethersproject/address': 5.8.0 | |
| 410 | + '@ethersproject/bignumber': 5.8.0 | |
| 411 | + '@ethersproject/bytes': 5.8.0 | |
| 412 | + '@ethersproject/constants': 5.8.0 | |
| 413 | + '@ethersproject/logger': 5.8.0 | |
| 414 | + '@ethersproject/properties': 5.8.0 | |
| 415 | + '@ethersproject/transactions': 5.8.0 | |
| 416 | + | |
| 417 | + '@ethersproject/hash@5.8.0': | |
| 418 | + dependencies: | |
| 419 | + '@ethersproject/abstract-signer': 5.8.0 | |
| 420 | + '@ethersproject/address': 5.8.0 | |
| 421 | + '@ethersproject/base64': 5.8.0 | |
| 422 | + '@ethersproject/bignumber': 5.8.0 | |
| 423 | + '@ethersproject/bytes': 5.8.0 | |
| 424 | + '@ethersproject/keccak256': 5.8.0 | |
| 425 | + '@ethersproject/logger': 5.8.0 | |
| 426 | + '@ethersproject/properties': 5.8.0 | |
| 427 | + '@ethersproject/strings': 5.8.0 | |
| 428 | + | |
| 429 | + '@ethersproject/hdnode@5.8.0': | |
| 430 | + dependencies: | |
| 431 | + '@ethersproject/abstract-signer': 5.8.0 | |
| 432 | + '@ethersproject/basex': 5.8.0 | |
| 433 | + '@ethersproject/bignumber': 5.8.0 | |
| 434 | + '@ethersproject/bytes': 5.8.0 | |
| 435 | + '@ethersproject/logger': 5.8.0 | |
| 436 | + '@ethersproject/pbkdf2': 5.8.0 | |
| 437 | + '@ethersproject/properties': 5.8.0 | |
| 438 | + '@ethersproject/sha2': 5.8.0 | |
| 439 | + '@ethersproject/signing-key': 5.8.0 | |
| 440 | + '@ethersproject/strings': 5.8.0 | |
| 441 | + '@ethersproject/transactions': 5.8.0 | |
| 442 | + '@ethersproject/wordlists': 5.8.0 | |
| 443 | + | |
| 444 | + '@ethersproject/json-wallets@5.8.0': | |
| 445 | + dependencies: | |
| 446 | + '@ethersproject/abstract-signer': 5.8.0 | |
| 447 | + '@ethersproject/address': 5.8.0 | |
| 448 | + '@ethersproject/bytes': 5.8.0 | |
| 449 | + '@ethersproject/hdnode': 5.8.0 | |
| 450 | + '@ethersproject/keccak256': 5.8.0 | |
| 451 | + '@ethersproject/logger': 5.8.0 | |
| 452 | + '@ethersproject/pbkdf2': 5.8.0 | |
| 453 | + '@ethersproject/properties': 5.8.0 | |
| 454 | + '@ethersproject/random': 5.8.0 | |
| 455 | + '@ethersproject/strings': 5.8.0 | |
| 456 | + '@ethersproject/transactions': 5.8.0 | |
| 457 | + aes-js: 3.0.0 | |
| 458 | + scrypt-js: 3.0.1 | |
| 459 | + | |
| 460 | + '@ethersproject/keccak256@5.8.0': | |
| 461 | + dependencies: | |
| 462 | + '@ethersproject/bytes': 5.8.0 | |
| 463 | + js-sha3: 0.8.0 | |
| 464 | + | |
| 465 | + '@ethersproject/logger@5.8.0': {} | |
| 466 | + | |
| 467 | + '@ethersproject/networks@5.8.0': | |
| 468 | + dependencies: | |
| 469 | + '@ethersproject/logger': 5.8.0 | |
| 470 | + | |
| 471 | + '@ethersproject/pbkdf2@5.8.0': | |
| 472 | + dependencies: | |
| 473 | + '@ethersproject/bytes': 5.8.0 | |
| 474 | + '@ethersproject/sha2': 5.8.0 | |
| 475 | + | |
| 476 | + '@ethersproject/properties@5.8.0': | |
| 477 | + dependencies: | |
| 478 | + '@ethersproject/logger': 5.8.0 | |
| 479 | + | |
| 480 | + '@ethersproject/providers@5.8.0': | |
| 481 | + dependencies: | |
| 482 | + '@ethersproject/abstract-provider': 5.8.0 | |
| 483 | + '@ethersproject/abstract-signer': 5.8.0 | |
| 484 | + '@ethersproject/address': 5.8.0 | |
| 485 | + '@ethersproject/base64': 5.8.0 | |
| 486 | + '@ethersproject/basex': 5.8.0 | |
| 487 | + '@ethersproject/bignumber': 5.8.0 | |
| 488 | + '@ethersproject/bytes': 5.8.0 | |
| 489 | + '@ethersproject/constants': 5.8.0 | |
| 490 | + '@ethersproject/hash': 5.8.0 | |
| 491 | + '@ethersproject/logger': 5.8.0 | |
| 492 | + '@ethersproject/networks': 5.8.0 | |
| 493 | + '@ethersproject/properties': 5.8.0 | |
| 494 | + '@ethersproject/random': 5.8.0 | |
| 495 | + '@ethersproject/rlp': 5.8.0 | |
| 496 | + '@ethersproject/sha2': 5.8.0 | |
| 497 | + '@ethersproject/strings': 5.8.0 | |
| 498 | + '@ethersproject/transactions': 5.8.0 | |
| 499 | + '@ethersproject/web': 5.8.0 | |
| 500 | + bech32: 1.1.4 | |
| 501 | + ws: 8.18.0 | |
| 502 | + transitivePeerDependencies: | |
| 503 | + - bufferutil | |
| 504 | + - utf-8-validate | |
| 505 | + | |
| 506 | + '@ethersproject/random@5.8.0': | |
| 507 | + dependencies: | |
| 508 | + '@ethersproject/bytes': 5.8.0 | |
| 509 | + '@ethersproject/logger': 5.8.0 | |
| 510 | + | |
| 511 | + '@ethersproject/rlp@5.8.0': | |
| 512 | + dependencies: | |
| 513 | + '@ethersproject/bytes': 5.8.0 | |
| 514 | + '@ethersproject/logger': 5.8.0 | |
| 515 | + | |
| 516 | + '@ethersproject/sha2@5.8.0': | |
| 517 | + dependencies: | |
| 518 | + '@ethersproject/bytes': 5.8.0 | |
| 519 | + '@ethersproject/logger': 5.8.0 | |
| 520 | + hash.js: 1.1.7 | |
| 521 | + | |
| 522 | + '@ethersproject/signing-key@5.8.0': | |
| 523 | + dependencies: | |
| 524 | + '@ethersproject/bytes': 5.8.0 | |
| 525 | + '@ethersproject/logger': 5.8.0 | |
| 526 | + '@ethersproject/properties': 5.8.0 | |
| 527 | + bn.js: 5.2.5 | |
| 528 | + elliptic: 6.6.1 | |
| 529 | + hash.js: 1.1.7 | |
| 530 | + | |
| 531 | + '@ethersproject/solidity@5.8.0': | |
| 532 | + dependencies: | |
| 533 | + '@ethersproject/bignumber': 5.8.0 | |
| 534 | + '@ethersproject/bytes': 5.8.0 | |
| 535 | + '@ethersproject/keccak256': 5.8.0 | |
| 536 | + '@ethersproject/logger': 5.8.0 | |
| 537 | + '@ethersproject/sha2': 5.8.0 | |
| 538 | + '@ethersproject/strings': 5.8.0 | |
| 539 | + | |
| 540 | + '@ethersproject/strings@5.8.0': | |
| 541 | + dependencies: | |
| 542 | + '@ethersproject/bytes': 5.8.0 | |
| 543 | + '@ethersproject/constants': 5.8.0 | |
| 544 | + '@ethersproject/logger': 5.8.0 | |
| 545 | + | |
| 546 | + '@ethersproject/transactions@5.8.0': | |
| 547 | + dependencies: | |
| 548 | + '@ethersproject/address': 5.8.0 | |
| 549 | + '@ethersproject/bignumber': 5.8.0 | |
| 550 | + '@ethersproject/bytes': 5.8.0 | |
| 551 | + '@ethersproject/constants': 5.8.0 | |
| 552 | + '@ethersproject/keccak256': 5.8.0 | |
| 553 | + '@ethersproject/logger': 5.8.0 | |
| 554 | + '@ethersproject/properties': 5.8.0 | |
| 555 | + '@ethersproject/rlp': 5.8.0 | |
| 556 | + '@ethersproject/signing-key': 5.8.0 | |
| 557 | + | |
| 558 | + '@ethersproject/units@5.8.0': | |
| 559 | + dependencies: | |
| 560 | + '@ethersproject/bignumber': 5.8.0 | |
| 561 | + '@ethersproject/constants': 5.8.0 | |
| 562 | + '@ethersproject/logger': 5.8.0 | |
| 563 | + | |
| 564 | + '@ethersproject/wallet@5.8.0': | |
| 565 | + dependencies: | |
| 566 | + '@ethersproject/abstract-provider': 5.8.0 | |
| 567 | + '@ethersproject/abstract-signer': 5.8.0 | |
| 568 | + '@ethersproject/address': 5.8.0 | |
| 569 | + '@ethersproject/bignumber': 5.8.0 | |
| 570 | + '@ethersproject/bytes': 5.8.0 | |
| 571 | + '@ethersproject/hash': 5.8.0 | |
| 572 | + '@ethersproject/hdnode': 5.8.0 | |
| 573 | + '@ethersproject/json-wallets': 5.8.0 | |
| 574 | + '@ethersproject/keccak256': 5.8.0 | |
| 575 | + '@ethersproject/logger': 5.8.0 | |
| 576 | + '@ethersproject/properties': 5.8.0 | |
| 577 | + '@ethersproject/random': 5.8.0 | |
| 578 | + '@ethersproject/signing-key': 5.8.0 | |
| 579 | + '@ethersproject/transactions': 5.8.0 | |
| 580 | + '@ethersproject/wordlists': 5.8.0 | |
| 581 | + | |
| 582 | + '@ethersproject/web@5.8.0': | |
| 583 | + dependencies: | |
| 584 | + '@ethersproject/base64': 5.8.0 | |
| 585 | + '@ethersproject/bytes': 5.8.0 | |
| 586 | + '@ethersproject/logger': 5.8.0 | |
| 587 | + '@ethersproject/properties': 5.8.0 | |
| 588 | + '@ethersproject/strings': 5.8.0 | |
| 589 | + | |
| 590 | + '@ethersproject/wordlists@5.8.0': | |
| 591 | + dependencies: | |
| 592 | + '@ethersproject/bytes': 5.8.0 | |
| 593 | + '@ethersproject/hash': 5.8.0 | |
| 594 | + '@ethersproject/logger': 5.8.0 | |
| 595 | + '@ethersproject/properties': 5.8.0 | |
| 596 | + '@ethersproject/strings': 5.8.0 | |
| 597 | + | |
| 598 | + '@iden3/bigarray@0.0.2': {} | |
| 599 | + | |
| 600 | + '@iden3/binfileutils@0.0.12': | |
| 601 | + dependencies: | |
| 602 | + fastfile: 0.0.20 | |
| 603 | + ffjavascript: 0.3.1 | |
| 604 | + | |
| 605 | + '@noble/hashes@1.8.0': {} | |
| 606 | + | |
| 607 | + aes-js@3.0.0: {} | |
| 608 | + | |
| 609 | + async@3.2.6: {} | |
| 610 | + | |
| 611 | + b4a@1.9.0: {} | |
| 612 | + | |
| 613 | + balanced-match@1.0.2: {} | |
| 614 | + | |
| 615 | + bech32@1.1.4: {} | |
| 616 | + | |
| 617 | + bfj@7.1.0: | |
| 618 | + dependencies: | |
| 619 | + bluebird: 3.7.2 | |
| 620 | + check-types: 11.2.3 | |
| 621 | + hoopy: 0.1.4 | |
| 622 | + jsonpath: 1.3.0 | |
| 623 | + tryer: 1.0.1 | |
| 624 | + | |
| 625 | + blake-hash@2.0.0: | |
| 626 | + dependencies: | |
| 627 | + node-addon-api: 3.2.1 | |
| 628 | + node-gyp-build: 4.8.4 | |
| 629 | + readable-stream: 3.6.2 | |
| 630 | + | |
| 631 | + blake2b-wasm@2.4.0: | |
| 632 | + dependencies: | |
| 633 | + b4a: 1.9.0 | |
| 634 | + nanoassert: 2.0.0 | |
| 635 | + transitivePeerDependencies: | |
| 636 | + - react-native-b4a | |
| 637 | + | |
| 638 | + blake2b@2.1.4: | |
| 639 | + dependencies: | |
| 640 | + blake2b-wasm: 2.4.0 | |
| 641 | + nanoassert: 2.0.0 | |
| 642 | + transitivePeerDependencies: | |
| 643 | + - react-native-b4a | |
| 644 | + | |
| 645 | + bluebird@3.7.2: {} | |
| 646 | + | |
| 647 | + bn.js@4.12.5: {} | |
| 648 | + | |
| 649 | + bn.js@5.2.5: {} | |
| 650 | + | |
| 651 | + brace-expansion@2.1.7: | |
| 652 | + dependencies: | |
| 653 | + balanced-match: 1.0.2 | |
| 654 | + | |
| 655 | + brorand@1.1.0: {} | |
| 656 | + | |
| 657 | + check-types@11.2.3: {} | |
| 658 | + | |
| 659 | + circom_runtime@0.1.28: | |
| 660 | + dependencies: | |
| 661 | + ffjavascript: 0.3.1 | |
| 662 | + | |
| 663 | + circomlib@2.0.5: {} | |
| 664 | + | |
| 665 | + circomlibjs@0.1.7: | |
| 666 | + dependencies: | |
| 667 | + blake-hash: 2.0.0 | |
| 668 | + blake2b: 2.1.4 | |
| 669 | + ethers: 5.8.0 | |
| 670 | + ffjavascript: 0.2.63 | |
| 671 | + transitivePeerDependencies: | |
| 672 | + - bufferutil | |
| 673 | + - react-native-b4a | |
| 674 | + - utf-8-validate | |
| 675 | + | |
| 676 | + ejs@3.1.10: | |
| 677 | + dependencies: | |
| 678 | + jake: 10.9.4 | |
| 679 | + | |
| 680 | + elliptic@6.6.1: | |
| 681 | + dependencies: | |
| 682 | + bn.js: 4.12.5 | |
| 683 | + brorand: 1.1.0 | |
| 684 | + hash.js: 1.1.7 | |
| 685 | + hmac-drbg: 1.0.1 | |
| 686 | + inherits: 2.0.4 | |
| 687 | + minimalistic-assert: 1.0.1 | |
| 688 | + minimalistic-crypto-utils: 1.0.1 | |
| 689 | + | |
| 690 | + escodegen@2.1.0: | |
| 691 | + dependencies: | |
| 692 | + esprima: 4.0.1 | |
| 693 | + estraverse: 5.3.0 | |
| 694 | + esutils: 2.0.3 | |
| 695 | + optionalDependencies: | |
| 696 | + source-map: 0.6.1 | |
| 697 | + | |
| 698 | + esprima@1.2.5: {} | |
| 699 | + | |
| 700 | + esprima@4.0.1: {} | |
| 701 | + | |
| 702 | + estraverse@5.3.0: {} | |
| 703 | + | |
| 704 | + esutils@2.0.3: {} | |
| 705 | + | |
| 706 | + ethers@5.8.0: | |
| 707 | + dependencies: | |
| 708 | + '@ethersproject/abi': 5.8.0 | |
| 709 | + '@ethersproject/abstract-provider': 5.8.0 | |
| 710 | + '@ethersproject/abstract-signer': 5.8.0 | |
| 711 | + '@ethersproject/address': 5.8.0 | |
| 712 | + '@ethersproject/base64': 5.8.0 | |
| 713 | + '@ethersproject/basex': 5.8.0 | |
| 714 | + '@ethersproject/bignumber': 5.8.0 | |
| 715 | + '@ethersproject/bytes': 5.8.0 | |
| 716 | + '@ethersproject/constants': 5.8.0 | |
| 717 | + '@ethersproject/contracts': 5.8.0 | |
| 718 | + '@ethersproject/hash': 5.8.0 | |
| 719 | + '@ethersproject/hdnode': 5.8.0 | |
| 720 | + '@ethersproject/json-wallets': 5.8.0 | |
| 721 | + '@ethersproject/keccak256': 5.8.0 | |
| 722 | + '@ethersproject/logger': 5.8.0 | |
| 723 | + '@ethersproject/networks': 5.8.0 | |
| 724 | + '@ethersproject/pbkdf2': 5.8.0 | |
| 725 | + '@ethersproject/properties': 5.8.0 | |
| 726 | + '@ethersproject/providers': 5.8.0 | |
| 727 | + '@ethersproject/random': 5.8.0 | |
| 728 | + '@ethersproject/rlp': 5.8.0 | |
| 729 | + '@ethersproject/sha2': 5.8.0 | |
| 730 | + '@ethersproject/signing-key': 5.8.0 | |
| 731 | + '@ethersproject/solidity': 5.8.0 | |
| 732 | + '@ethersproject/strings': 5.8.0 | |
| 733 | + '@ethersproject/transactions': 5.8.0 | |
| 734 | + '@ethersproject/units': 5.8.0 | |
| 735 | + '@ethersproject/wallet': 5.8.0 | |
| 736 | + '@ethersproject/web': 5.8.0 | |
| 737 | + '@ethersproject/wordlists': 5.8.0 | |
| 738 | + transitivePeerDependencies: | |
| 739 | + - bufferutil | |
| 740 | + - utf-8-validate | |
| 741 | + | |
| 742 | + fastfile@0.0.20: {} | |
| 743 | + | |
| 744 | + ffjavascript@0.2.63: | |
| 745 | + dependencies: | |
| 746 | + wasmbuilder: 0.0.16 | |
| 747 | + wasmcurves: 0.2.2 | |
| 748 | + web-worker: 1.2.0 | |
| 749 | + | |
| 750 | + ffjavascript@0.3.0: | |
| 751 | + dependencies: | |
| 752 | + wasmbuilder: 0.0.16 | |
| 753 | + wasmcurves: 0.2.2 | |
| 754 | + web-worker: 1.2.0 | |
| 755 | + | |
| 756 | + ffjavascript@0.3.1: | |
| 757 | + dependencies: | |
| 758 | + wasmbuilder: 0.0.16 | |
| 759 | + wasmcurves: 0.2.2 | |
| 760 | + web-worker: 1.2.0 | |
| 761 | + | |
| 762 | + filelist@1.0.6: | |
| 763 | + dependencies: | |
| 764 | + minimatch: 5.1.9 | |
| 765 | + | |
| 766 | + hash.js@1.1.7: | |
| 767 | + dependencies: | |
| 768 | + inherits: 2.0.4 | |
| 769 | + minimalistic-assert: 1.0.1 | |
| 770 | + | |
| 771 | + hmac-drbg@1.0.1: | |
| 772 | + dependencies: | |
| 773 | + hash.js: 1.1.7 | |
| 774 | + minimalistic-assert: 1.0.1 | |
| 775 | + minimalistic-crypto-utils: 1.0.1 | |
| 776 | + | |
| 777 | + hoopy@0.1.4: {} | |
| 778 | + | |
| 779 | + inherits@2.0.4: {} | |
| 780 | + | |
| 781 | + jake@10.9.4: | |
| 782 | + dependencies: | |
| 783 | + async: 3.2.6 | |
| 784 | + filelist: 1.0.6 | |
| 785 | + picocolors: 1.1.1 | |
| 786 | + | |
| 787 | + js-sha3@0.8.0: {} | |
| 788 | + | |
| 789 | + jsonpath@1.3.0: | |
| 790 | + dependencies: | |
| 791 | + esprima: 1.2.5 | |
| 792 | + static-eval: 2.1.1 | |
| 793 | + underscore: 1.13.6 | |
| 794 | + | |
| 795 | + logplease@1.2.15: {} | |
| 796 | + | |
| 797 | + minimalistic-assert@1.0.1: {} | |
| 798 | + | |
| 799 | + minimalistic-crypto-utils@1.0.1: {} | |
| 800 | + | |
| 801 | + minimatch@5.1.9: | |
| 802 | + dependencies: | |
| 803 | + brace-expansion: 2.1.7 | |
| 804 | + | |
| 805 | + nanoassert@2.0.0: {} | |
| 806 | + | |
| 807 | + node-addon-api@3.2.1: {} | |
| 808 | + | |
| 809 | + node-gyp-build@4.8.4: {} | |
| 810 | + | |
| 811 | + picocolors@1.1.1: {} | |
| 812 | + | |
| 813 | + r1csfile@0.0.48: | |
| 814 | + dependencies: | |
| 815 | + '@iden3/bigarray': 0.0.2 | |
| 816 | + '@iden3/binfileutils': 0.0.12 | |
| 817 | + fastfile: 0.0.20 | |
| 818 | + ffjavascript: 0.3.0 | |
| 819 | + | |
| 820 | + readable-stream@3.6.2: | |
| 821 | + dependencies: | |
| 822 | + inherits: 2.0.4 | |
| 823 | + string_decoder: 1.3.0 | |
| 824 | + util-deprecate: 1.0.2 | |
| 825 | + | |
| 826 | + safe-buffer@5.2.1: {} | |
| 827 | + | |
| 828 | + scrypt-js@3.0.1: {} | |
| 829 | + | |
| 830 | + snarkjs@0.7.6: | |
| 831 | + dependencies: | |
| 832 | + '@iden3/binfileutils': 0.0.12 | |
| 833 | + '@noble/hashes': 1.8.0 | |
| 834 | + bfj: 7.1.0 | |
| 835 | + circom_runtime: 0.1.28 | |
| 836 | + ejs: 3.1.10 | |
| 837 | + fastfile: 0.0.20 | |
| 838 | + ffjavascript: 0.3.1 | |
| 839 | + logplease: 1.2.15 | |
| 840 | + r1csfile: 0.0.48 | |
| 841 | + | |
| 842 | + source-map@0.6.1: | |
| 843 | + optional: true | |
| 844 | + | |
| 845 | + static-eval@2.1.1: | |
| 846 | + dependencies: | |
| 847 | + escodegen: 2.1.0 | |
| 848 | + | |
| 849 | + string_decoder@1.3.0: | |
| 850 | + dependencies: | |
| 851 | + safe-buffer: 5.2.1 | |
| 852 | + | |
| 853 | + tryer@1.0.1: {} | |
| 854 | + | |
| 855 | + underscore@1.13.6: {} | |
| 856 | + | |
| 857 | + util-deprecate@1.0.2: {} | |
| 858 | + | |
| 859 | + wasmbuilder@0.0.16: {} | |
| 860 | + | |
| 861 | + wasmcurves@0.2.2: | |
| 862 | + dependencies: | |
| 863 | + wasmbuilder: 0.0.16 | |
| 864 | + | |
| 865 | + web-worker@1.2.0: {} | |
| 866 | + | |
| 867 | + ws@8.18.0: {} | |
| new file mode 100644 | |||
| @@ -0,0 +1,867 @@ | |||
| 1 | +lockfileVersion: '9.0' | ||
| 2 | + | ||
| 3 | +settings: | ||
| 4 | + autoInstallPeers: true | ||
| 5 | + excludeLinksFromLockfile: false | ||
| 6 | + | ||
| 7 | +importers: | ||
| 8 | + | ||
| 9 | + .: | ||
| 10 | + dependencies: | ||
| 11 | + circomlib: | ||
| 12 | + specifier: ^2.0.5 | ||
| 13 | + version: 2.0.5 | ||
| 14 | + circomlibjs: | ||
| 15 | + specifier: ^0.1.7 | ||
| 16 | + version: 0.1.7 | ||
| 17 | + snarkjs: | ||
| 18 | + specifier: ^0.7.6 | ||
| 19 | + version: 0.7.6 | ||
| 20 | + | ||
| 21 | +packages: | ||
| 22 | + | ||
| 23 | + '@ethersproject/abi@5.8.0': | ||
| 24 | + resolution: {integrity: sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==} | ||
| 25 | + | ||
| 26 | + '@ethersproject/abstract-provider@5.8.0': | ||
| 27 | + resolution: {integrity: sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==} | ||
| 28 | + | ||
| 29 | + '@ethersproject/abstract-signer@5.8.0': | ||
| 30 | + resolution: {integrity: sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==} | ||
| 31 | + | ||
| 32 | + '@ethersproject/address@5.8.0': | ||
| 33 | + resolution: {integrity: sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==} | ||
| 34 | + | ||
| 35 | + '@ethersproject/base64@5.8.0': | ||
| 36 | + resolution: {integrity: sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==} | ||
| 37 | + | ||
| 38 | + '@ethersproject/basex@5.8.0': | ||
| 39 | + resolution: {integrity: sha512-PIgTszMlDRmNwW9nhS6iqtVfdTAKosA7llYXNmGPw4YAI1PUyMv28988wAb41/gHF/WqGdoLv0erHaRcHRKW2Q==} | ||
| 40 | + | ||
| 41 | + '@ethersproject/bignumber@5.8.0': | ||
| 42 | + resolution: {integrity: sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==} | ||
| 43 | + | ||
| 44 | + '@ethersproject/bytes@5.8.0': | ||
| 45 | + resolution: {integrity: sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==} | ||
| 46 | + | ||
| 47 | + '@ethersproject/constants@5.8.0': | ||
| 48 | + resolution: {integrity: sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==} | ||
| 49 | + | ||
| 50 | + '@ethersproject/contracts@5.8.0': | ||
| 51 | + resolution: {integrity: sha512-0eFjGz9GtuAi6MZwhb4uvUM216F38xiuR0yYCjKJpNfSEy4HUM8hvqqBj9Jmm0IUz8l0xKEhWwLIhPgxNY0yvQ==} | ||
| 52 | + | ||
| 53 | + '@ethersproject/hash@5.8.0': | ||
| 54 | + resolution: {integrity: sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==} | ||
| 55 | + | ||
| 56 | + '@ethersproject/hdnode@5.8.0': | ||
| 57 | + resolution: {integrity: sha512-4bK1VF6E83/3/Im0ERnnUeWOY3P1BZml4ZD3wcH8Ys0/d1h1xaFt6Zc+Dh9zXf9TapGro0T4wvO71UTCp3/uoA==} | ||
| 58 | + | ||
| 59 | + '@ethersproject/json-wallets@5.8.0': | ||
| 60 | + resolution: {integrity: sha512-HxblNck8FVUtNxS3VTEYJAcwiKYsBIF77W15HufqlBF9gGfhmYOJtYZp8fSDZtn9y5EaXTE87zDwzxRoTFk11w==} | ||
| 61 | + | ||
| 62 | + '@ethersproject/keccak256@5.8.0': | ||
| 63 | + resolution: {integrity: sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==} | ||
| 64 | + | ||
| 65 | + '@ethersproject/logger@5.8.0': | ||
| 66 | + resolution: {integrity: sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==} | ||
| 67 | + | ||
| 68 | + '@ethersproject/networks@5.8.0': | ||
| 69 | + resolution: {integrity: sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==} | ||
| 70 | + | ||
| 71 | + '@ethersproject/pbkdf2@5.8.0': | ||
| 72 | + resolution: {integrity: sha512-wuHiv97BrzCmfEaPbUFpMjlVg/IDkZThp9Ri88BpjRleg4iePJaj2SW8AIyE8cXn5V1tuAaMj6lzvsGJkGWskg==} | ||
| 73 | + | ||
| 74 | + '@ethersproject/properties@5.8.0': | ||
| 75 | + resolution: {integrity: sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==} | ||
| 76 | + | ||
| 77 | + '@ethersproject/providers@5.8.0': | ||
| 78 | + resolution: {integrity: sha512-3Il3oTzEx3o6kzcg9ZzbE+oCZYyY+3Zh83sKkn4s1DZfTUjIegHnN2Cm0kbn9YFy45FDVcuCLLONhU7ny0SsCw==} | ||
| 79 | + | ||
| 80 | + '@ethersproject/random@5.8.0': | ||
| 81 | + resolution: {integrity: sha512-E4I5TDl7SVqyg4/kkA/qTfuLWAQGXmSOgYyO01So8hLfwgKvYK5snIlzxJMk72IFdG/7oh8yuSqY2KX7MMwg+A==} | ||
| 82 | + | ||
| 83 | + '@ethersproject/rlp@5.8.0': | ||
| 84 | + resolution: {integrity: sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==} | ||
| 85 | + | ||
| 86 | + '@ethersproject/sha2@5.8.0': | ||
| 87 | + resolution: {integrity: sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A==} | ||
| 88 | + | ||
| 89 | + '@ethersproject/signing-key@5.8.0': | ||
| 90 | + resolution: {integrity: sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==} | ||
| 91 | + | ||
| 92 | + '@ethersproject/solidity@5.8.0': | ||
| 93 | + resolution: {integrity: sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA==} | ||
| 94 | + | ||
| 95 | + '@ethersproject/strings@5.8.0': | ||
| 96 | + resolution: {integrity: sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==} | ||
| 97 | + | ||
| 98 | + '@ethersproject/transactions@5.8.0': | ||
| 99 | + resolution: {integrity: sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==} | ||
| 100 | + | ||
| 101 | + '@ethersproject/units@5.8.0': | ||
| 102 | + resolution: {integrity: sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==} | ||
| 103 | + | ||
| 104 | + '@ethersproject/wallet@5.8.0': | ||
| 105 | + resolution: {integrity: sha512-G+jnzmgg6UxurVKRKvw27h0kvG75YKXZKdlLYmAHeF32TGUzHkOFd7Zn6QHOTYRFWnfjtSSFjBowKo7vfrXzPA==} | ||
| 106 | + | ||
| 107 | + '@ethersproject/web@5.8.0': | ||
| 108 | + resolution: {integrity: sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==} | ||
| 109 | + | ||
| 110 | + '@ethersproject/wordlists@5.8.0': | ||
| 111 | + resolution: {integrity: sha512-2df9bbXicZws2Sb5S6ET493uJ0Z84Fjr3pC4tu/qlnZERibZCeUVuqdtt+7Tv9xxhUxHoIekIA7avrKUWHrezg==} | ||
| 112 | + | ||
| 113 | + '@iden3/bigarray@0.0.2': | ||
| 114 | + resolution: {integrity: sha512-Xzdyxqm1bOFF6pdIsiHLLl3HkSLjbhqJHVyqaTxXt3RqXBEnmsUmEW47H7VOi/ak7TdkRpNkxjyK5Zbkm+y52g==} | ||
| 115 | + | ||
| 116 | + '@iden3/binfileutils@0.0.12': | ||
| 117 | + resolution: {integrity: sha512-naAmzuDufRIcoNfQ1d99d7hGHufLA3wZSibtr4dMe6ZeiOPV1KwOZWTJ1YVz4HbaWlpDuzVU72dS4ATQS4PXBQ==} | ||
| 118 | + | ||
| 119 | + '@noble/hashes@1.8.0': | ||
| 120 | + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} | ||
| 121 | + engines: {node: ^14.21.3 || >=16} | ||
| 122 | + | ||
| 123 | + aes-js@3.0.0: | ||
| 124 | + resolution: {integrity: sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==} | ||
| 125 | + | ||
| 126 | + async@3.2.6: | ||
| 127 | + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} | ||
| 128 | + | ||
| 129 | + b4a@1.9.0: | ||
| 130 | + resolution: {integrity: sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==} | ||
| 131 | + peerDependencies: | ||
| 132 | + react-native-b4a: '*' | ||
| 133 | + peerDependenciesMeta: | ||
| 134 | + react-native-b4a: | ||
| 135 | + optional: true | ||
| 136 | + | ||
| 137 | + balanced-match@1.0.2: | ||
| 138 | + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} | ||
| 139 | + | ||
| 140 | + bech32@1.1.4: | ||
| 141 | + resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} | ||
| 142 | + | ||
| 143 | + bfj@7.1.0: | ||
| 144 | + resolution: {integrity: sha512-I6MMLkn+anzNdCUp9hMRyui1HaNEUCco50lxbvNS4+EyXg8lN3nJ48PjPWtbH8UVS9CuMoaKE9U2V3l29DaRQw==} | ||
| 145 | + engines: {node: '>= 8.0.0'} | ||
| 146 | + | ||
| 147 | + blake-hash@2.0.0: | ||
| 148 | + resolution: {integrity: sha512-Igj8YowDu1PRkRsxZA7NVkdFNxH5rKv5cpLxQ0CVXSIA77pVYwCPRQJ2sMew/oneUpfuYRyjG6r8SmmmnbZb1w==} | ||
| 149 | + engines: {node: '>= 10'} | ||
| 150 | + | ||
| 151 | + blake2b-wasm@2.4.0: | ||
| 152 | + resolution: {integrity: sha512-S1kwmW2ZhZFFFOghcx73+ZajEfKBqhP82JMssxtLVMxlaPea1p9uoLiUZ5WYyHn0KddwbLc+0vh4wR0KBNoT5w==} | ||
| 153 | + | ||
| 154 | + blake2b@2.1.4: | ||
| 155 | + resolution: {integrity: sha512-AyBuuJNI64gIvwx13qiICz6H6hpmjvYS5DGkG6jbXMOT8Z3WUJ3V1X0FlhIoT1b/5JtHE3ki+xjtMvu1nn+t9A==} | ||
| 156 | + | ||
| 157 | + bluebird@3.7.2: | ||
| 158 | + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} | ||
| 159 | + | ||
| 160 | + bn.js@4.12.5: | ||
| 161 | + resolution: {integrity: sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==} | ||
| 162 | + | ||
| 163 | + bn.js@5.2.5: | ||
| 164 | + resolution: {integrity: sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg==} | ||
| 165 | + | ||
| 166 | + brace-expansion@2.1.7: | ||
| 167 | + resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==} | ||
| 168 | + | ||
| 169 | + brorand@1.1.0: | ||
| 170 | + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} | ||
| 171 | + | ||
| 172 | + check-types@11.2.3: | ||
| 173 | + resolution: {integrity: sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==} | ||
| 174 | + | ||
| 175 | + circom_runtime@0.1.28: | ||
| 176 | + resolution: {integrity: sha512-ACagpQ7zBRLKDl5xRZ4KpmYIcZDUjOiNRuxvXLqhnnlLSVY1Dbvh73TI853nqoR0oEbihtWmMSjgc5f+pXf/jQ==} | ||
| 177 | + hasBin: true | ||
| 178 | + | ||
| 179 | + circomlib@2.0.5: | ||
| 180 | + resolution: {integrity: sha512-O7NQ8OS+J4eshBuoy36z/TwQU0YHw8W3zxZcs4hVwpEll3e4hDm3mgkIPqItN8FDeLEKZFK3YeT/+k8TiLF3/A==} | ||
| 181 | + | ||
| 182 | + circomlibjs@0.1.7: | ||
| 183 | + resolution: {integrity: sha512-GRAUoAlKAsiiTa+PA725G9RmEmJJRc8tRFxw/zKktUxlQISGznT4hH4ESvW8FNTsrGg/nNd06sGP/Wlx0LUHVg==} | ||
| 184 | + | ||
| 185 | + ejs@3.1.10: | ||
| 186 | + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} | ||
| 187 | + engines: {node: '>=0.10.0'} | ||
| 188 | + hasBin: true | ||
| 189 | + | ||
| 190 | + elliptic@6.6.1: | ||
| 191 | + resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} | ||
| 192 | + | ||
| 193 | + escodegen@2.1.0: | ||
| 194 | + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} | ||
| 195 | + engines: {node: '>=6.0'} | ||
| 196 | + hasBin: true | ||
| 197 | + | ||
| 198 | + esprima@1.2.5: | ||
| 199 | + resolution: {integrity: sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==} | ||
| 200 | + engines: {node: '>=0.4.0'} | ||
| 201 | + hasBin: true | ||
| 202 | + | ||
| 203 | + esprima@4.0.1: | ||
| 204 | + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} | ||
| 205 | + engines: {node: '>=4'} | ||
| 206 | + hasBin: true | ||
| 207 | + | ||
| 208 | + estraverse@5.3.0: | ||
| 209 | + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} | ||
| 210 | + engines: {node: '>=4.0'} | ||
| 211 | + | ||
| 212 | + esutils@2.0.3: | ||
| 213 | + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} | ||
| 214 | + engines: {node: '>=0.10.0'} | ||
| 215 | + | ||
| 216 | + ethers@5.8.0: | ||
| 217 | + resolution: {integrity: sha512-DUq+7fHrCg1aPDFCHx6UIPb3nmt2XMpM7Y/g2gLhsl3lIBqeAfOJIl1qEvRf2uq3BiKxmh6Fh5pfp2ieyek7Kg==} | ||
| 218 | + | ||
| 219 | + fastfile@0.0.20: | ||
| 220 | + resolution: {integrity: sha512-r5ZDbgImvVWCP0lA/cGNgQcZqR+aYdFx3u+CtJqUE510pBUVGMn4ulL/iRTI4tACTYsNJ736uzFxEBXesPAktA==} | ||
| 221 | + | ||
| 222 | + ffjavascript@0.2.63: | ||
| 223 | + resolution: {integrity: sha512-dBgdsfGks58b66JnUZeZpGxdMIDQ4QsD3VYlRJyFVrKQHb2kJy4R2gufx5oetrTxXPT+aEjg0dOvOLg1N0on4A==} | ||
| 224 | + | ||
| 225 | + ffjavascript@0.3.0: | ||
| 226 | + resolution: {integrity: sha512-l7sR5kmU3gRwDy8g0Z2tYBXy5ttmafRPFOqY7S6af5cq51JqJWt5eQ/lSR/rs2wQNbDYaYlQr5O+OSUf/oMLoQ==} | ||
| 227 | + | ||
| 228 | + ffjavascript@0.3.1: | ||
| 229 | + resolution: {integrity: sha512-4PbK1WYodQtuF47D4pRI5KUg3Q392vuP5WjE1THSnceHdXwU3ijaoS0OqxTzLknCtz4Z2TtABzkBdBdMn3B/Aw==} | ||
| 230 | + | ||
| 231 | + filelist@1.0.6: | ||
| 232 | + resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} | ||
| 233 | + | ||
| 234 | + hash.js@1.1.7: | ||
| 235 | + resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} | ||
| 236 | + | ||
| 237 | + hmac-drbg@1.0.1: | ||
| 238 | + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} | ||
| 239 | + | ||
| 240 | + hoopy@0.1.4: | ||
| 241 | + resolution: {integrity: sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==} | ||
| 242 | + engines: {node: '>= 6.0.0'} | ||
| 243 | + | ||
| 244 | + inherits@2.0.4: | ||
| 245 | + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} | ||
| 246 | + | ||
| 247 | + jake@10.9.4: | ||
| 248 | + resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} | ||
| 249 | + engines: {node: '>=10'} | ||
| 250 | + hasBin: true | ||
| 251 | + | ||
| 252 | + js-sha3@0.8.0: | ||
| 253 | + resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} | ||
| 254 | + | ||
| 255 | + jsonpath@1.3.0: | ||
| 256 | + resolution: {integrity: sha512-0kjkYHJBkAy50Z5QzArZ7udmvxrJzkpKYW27fiF//BrMY7TQibYLl+FYIXN2BiYmwMIVzSfD8aDRj6IzgBX2/w==} | ||
| 257 | + | ||
| 258 | + logplease@1.2.15: | ||
| 259 | + resolution: {integrity: sha512-jLlHnlsPSJjpwUfcNyUxXCl33AYg2cHhIf9QhGL2T4iPT0XPB+xP1LRKFPgIg1M/sg9kAJvy94w9CzBNrfnstA==} | ||
| 260 | + | ||
| 261 | + minimalistic-assert@1.0.1: | ||
| 262 | + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} | ||
| 263 | + | ||
| 264 | + minimalistic-crypto-utils@1.0.1: | ||
| 265 | + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} | ||
| 266 | + | ||
| 267 | + minimatch@5.1.9: | ||
| 268 | + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} | ||
| 269 | + engines: {node: '>=10'} | ||
| 270 | + | ||
| 271 | + nanoassert@2.0.0: | ||
| 272 | + resolution: {integrity: sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==} | ||
| 273 | + | ||
| 274 | + node-addon-api@3.2.1: | ||
| 275 | + resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} | ||
| 276 | + | ||
| 277 | + node-gyp-build@4.8.4: | ||
| 278 | + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} | ||
| 279 | + hasBin: true | ||
| 280 | + | ||
| 281 | + picocolors@1.1.1: | ||
| 282 | + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} | ||
| 283 | + | ||
| 284 | + r1csfile@0.0.48: | ||
| 285 | + resolution: {integrity: sha512-kHRkKUJNaor31l05f2+RFzvcH5XSa7OfEfd/l4hzjte6NL6fjRkSMfZ4BjySW9wmfdwPOtq3mXurzPvPGEf5Tw==} | ||
| 286 | + | ||
| 287 | + readable-stream@3.6.2: | ||
| 288 | + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} | ||
| 289 | + engines: {node: '>= 6'} | ||
| 290 | + | ||
| 291 | + safe-buffer@5.2.1: | ||
| 292 | + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} | ||
| 293 | + | ||
| 294 | + scrypt-js@3.0.1: | ||
| 295 | + resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} | ||
| 296 | + | ||
| 297 | + snarkjs@0.7.6: | ||
| 298 | + resolution: {integrity: sha512-4uH1xA5JzVU5jaaWS2fXej3+RC6L5Erhr6INTJtUA27du4Elbh4VXCeeRjB4QiwL6N6y7SNKePw5prTxyEf4Zg==} | ||
| 299 | + hasBin: true | ||
| 300 | + | ||
| 301 | + source-map@0.6.1: | ||
| 302 | + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} | ||
| 303 | + engines: {node: '>=0.10.0'} | ||
| 304 | + | ||
| 305 | + static-eval@2.1.1: | ||
| 306 | + resolution: {integrity: sha512-MgWpQ/ZjGieSVB3eOJVs4OA2LT/q1vx98KPCTTQPzq/aLr0YUXTsgryTXr4SLfR0ZfUUCiedM9n/ABeDIyy4mA==} | ||
| 307 | + | ||
| 308 | + string_decoder@1.3.0: | ||
| 309 | + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} | ||
| 310 | + | ||
| 311 | + tryer@1.0.1: | ||
| 312 | + resolution: {integrity: sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==} | ||
| 313 | + | ||
| 314 | + underscore@1.13.6: | ||
| 315 | + resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==} | ||
| 316 | + | ||
| 317 | + util-deprecate@1.0.2: | ||
| 318 | + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} | ||
| 319 | + | ||
| 320 | + wasmbuilder@0.0.16: | ||
| 321 | + resolution: {integrity: sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==} | ||
| 322 | + | ||
| 323 | + wasmcurves@0.2.2: | ||
| 324 | + resolution: {integrity: sha512-JRY908NkmKjFl4ytnTu5ED6AwPD+8VJ9oc94kdq7h5bIwbj0L4TDJ69mG+2aLs2SoCmGfqIesMWTEJjtYsoQXQ==} | ||
| 325 | + | ||
| 326 | + web-worker@1.2.0: | ||
| 327 | + resolution: {integrity: sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==} | ||
| 328 | + | ||
| 329 | + ws@8.18.0: | ||
| 330 | + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} | ||
| 331 | + engines: {node: '>=10.0.0'} | ||
| 332 | + peerDependencies: | ||
| 333 | + bufferutil: ^4.0.1 | ||
| 334 | + utf-8-validate: '>=5.0.2' | ||
| 335 | + peerDependenciesMeta: | ||
| 336 | + bufferutil: | ||
| 337 | + optional: true | ||
| 338 | + utf-8-validate: | ||
| 339 | + optional: true | ||
| 340 | + | ||
| 341 | +snapshots: | ||
| 342 | + | ||
| 343 | + '@ethersproject/abi@5.8.0': | ||
| 344 | + dependencies: | ||
| 345 | + '@ethersproject/address': 5.8.0 | ||
| 346 | + '@ethersproject/bignumber': 5.8.0 | ||
| 347 | + '@ethersproject/bytes': 5.8.0 | ||
| 348 | + '@ethersproject/constants': 5.8.0 | ||
| 349 | + '@ethersproject/hash': 5.8.0 | ||
| 350 | + '@ethersproject/keccak256': 5.8.0 | ||
| 351 | + '@ethersproject/logger': 5.8.0 | ||
| 352 | + '@ethersproject/properties': 5.8.0 | ||
| 353 | + '@ethersproject/strings': 5.8.0 | ||
| 354 | + | ||
| 355 | + '@ethersproject/abstract-provider@5.8.0': | ||
| 356 | + dependencies: | ||
| 357 | + '@ethersproject/bignumber': 5.8.0 | ||
| 358 | + '@ethersproject/bytes': 5.8.0 | ||
| 359 | + '@ethersproject/logger': 5.8.0 | ||
| 360 | + '@ethersproject/networks': 5.8.0 | ||
| 361 | + '@ethersproject/properties': 5.8.0 | ||
| 362 | + '@ethersproject/transactions': 5.8.0 | ||
| 363 | + '@ethersproject/web': 5.8.0 | ||
| 364 | + | ||
| 365 | + '@ethersproject/abstract-signer@5.8.0': | ||
| 366 | + dependencies: | ||
| 367 | + '@ethersproject/abstract-provider': 5.8.0 | ||
| 368 | + '@ethersproject/bignumber': 5.8.0 | ||
| 369 | + '@ethersproject/bytes': 5.8.0 | ||
| 370 | + '@ethersproject/logger': 5.8.0 | ||
| 371 | + '@ethersproject/properties': 5.8.0 | ||
| 372 | + | ||
| 373 | + '@ethersproject/address@5.8.0': | ||
| 374 | + dependencies: | ||
| 375 | + '@ethersproject/bignumber': 5.8.0 | ||
| 376 | + '@ethersproject/bytes': 5.8.0 | ||
| 377 | + '@ethersproject/keccak256': 5.8.0 | ||
| 378 | + '@ethersproject/logger': 5.8.0 | ||
| 379 | + '@ethersproject/rlp': 5.8.0 | ||
| 380 | + | ||
| 381 | + '@ethersproject/base64@5.8.0': | ||
| 382 | + dependencies: | ||
| 383 | + '@ethersproject/bytes': 5.8.0 | ||
| 384 | + | ||
| 385 | + '@ethersproject/basex@5.8.0': | ||
| 386 | + dependencies: | ||
| 387 | + '@ethersproject/bytes': 5.8.0 | ||
| 388 | + '@ethersproject/properties': 5.8.0 | ||
| 389 | + | ||
| 390 | + '@ethersproject/bignumber@5.8.0': | ||
| 391 | + dependencies: | ||
| 392 | + '@ethersproject/bytes': 5.8.0 | ||
| 393 | + '@ethersproject/logger': 5.8.0 | ||
| 394 | + bn.js: 5.2.5 | ||
| 395 | + | ||
| 396 | + '@ethersproject/bytes@5.8.0': | ||
| 397 | + dependencies: | ||
| 398 | + '@ethersproject/logger': 5.8.0 | ||
| 399 | + | ||
| 400 | + '@ethersproject/constants@5.8.0': | ||
| 401 | + dependencies: | ||
| 402 | + '@ethersproject/bignumber': 5.8.0 | ||
| 403 | + | ||
| 404 | + '@ethersproject/contracts@5.8.0': | ||
| 405 | + dependencies: | ||
| 406 | + '@ethersproject/abi': 5.8.0 | ||
| 407 | + '@ethersproject/abstract-provider': 5.8.0 | ||
| 408 | + '@ethersproject/abstract-signer': 5.8.0 | ||
| 409 | + '@ethersproject/address': 5.8.0 | ||
| 410 | + '@ethersproject/bignumber': 5.8.0 | ||
| 411 | + '@ethersproject/bytes': 5.8.0 | ||
| 412 | + '@ethersproject/constants': 5.8.0 | ||
| 413 | + '@ethersproject/logger': 5.8.0 | ||
| 414 | + '@ethersproject/properties': 5.8.0 | ||
| 415 | + '@ethersproject/transactions': 5.8.0 | ||
| 416 | + | ||
| 417 | + '@ethersproject/hash@5.8.0': | ||
| 418 | + dependencies: | ||
| 419 | + '@ethersproject/abstract-signer': 5.8.0 | ||
| 420 | + '@ethersproject/address': 5.8.0 | ||
| 421 | + '@ethersproject/base64': 5.8.0 | ||
| 422 | + '@ethersproject/bignumber': 5.8.0 | ||
| 423 | + '@ethersproject/bytes': 5.8.0 | ||
| 424 | + '@ethersproject/keccak256': 5.8.0 | ||
| 425 | + '@ethersproject/logger': 5.8.0 | ||
| 426 | + '@ethersproject/properties': 5.8.0 | ||
| 427 | + '@ethersproject/strings': 5.8.0 | ||
| 428 | + | ||
| 429 | + '@ethersproject/hdnode@5.8.0': | ||
| 430 | + dependencies: | ||
| 431 | + '@ethersproject/abstract-signer': 5.8.0 | ||
| 432 | + '@ethersproject/basex': 5.8.0 | ||
| 433 | + '@ethersproject/bignumber': 5.8.0 | ||
| 434 | + '@ethersproject/bytes': 5.8.0 | ||
| 435 | + '@ethersproject/logger': 5.8.0 | ||
| 436 | + '@ethersproject/pbkdf2': 5.8.0 | ||
| 437 | + '@ethersproject/properties': 5.8.0 | ||
| 438 | + '@ethersproject/sha2': 5.8.0 | ||
| 439 | + '@ethersproject/signing-key': 5.8.0 | ||
| 440 | + '@ethersproject/strings': 5.8.0 | ||
| 441 | + '@ethersproject/transactions': 5.8.0 | ||
| 442 | + '@ethersproject/wordlists': 5.8.0 | ||
| 443 | + | ||
| 444 | + '@ethersproject/json-wallets@5.8.0': | ||
| 445 | + dependencies: | ||
| 446 | + '@ethersproject/abstract-signer': 5.8.0 | ||
| 447 | + '@ethersproject/address': 5.8.0 | ||
| 448 | + '@ethersproject/bytes': 5.8.0 | ||
| 449 | + '@ethersproject/hdnode': 5.8.0 | ||
| 450 | + '@ethersproject/keccak256': 5.8.0 | ||
| 451 | + '@ethersproject/logger': 5.8.0 | ||
| 452 | + '@ethersproject/pbkdf2': 5.8.0 | ||
| 453 | + '@ethersproject/properties': 5.8.0 | ||
| 454 | + '@ethersproject/random': 5.8.0 | ||
| 455 | + '@ethersproject/strings': 5.8.0 | ||
| 456 | + '@ethersproject/transactions': 5.8.0 | ||
| 457 | + aes-js: 3.0.0 | ||
| 458 | + scrypt-js: 3.0.1 | ||
| 459 | + | ||
| 460 | + '@ethersproject/keccak256@5.8.0': | ||
| 461 | + dependencies: | ||
| 462 | + '@ethersproject/bytes': 5.8.0 | ||
| 463 | + js-sha3: 0.8.0 | ||
| 464 | + | ||
| 465 | + '@ethersproject/logger@5.8.0': {} | ||
| 466 | + | ||
| 467 | + '@ethersproject/networks@5.8.0': | ||
| 468 | + dependencies: | ||
| 469 | + '@ethersproject/logger': 5.8.0 | ||
| 470 | + | ||
| 471 | + '@ethersproject/pbkdf2@5.8.0': | ||
| 472 | + dependencies: | ||
| 473 | + '@ethersproject/bytes': 5.8.0 | ||
| 474 | + '@ethersproject/sha2': 5.8.0 | ||
| 475 | + | ||
| 476 | + '@ethersproject/properties@5.8.0': | ||
| 477 | + dependencies: | ||
| 478 | + '@ethersproject/logger': 5.8.0 | ||
| 479 | + | ||
| 480 | + '@ethersproject/providers@5.8.0': | ||
| 481 | + dependencies: | ||
| 482 | + '@ethersproject/abstract-provider': 5.8.0 | ||
| 483 | + '@ethersproject/abstract-signer': 5.8.0 | ||
| 484 | + '@ethersproject/address': 5.8.0 | ||
| 485 | + '@ethersproject/base64': 5.8.0 | ||
| 486 | + '@ethersproject/basex': 5.8.0 | ||
| 487 | + '@ethersproject/bignumber': 5.8.0 | ||
| 488 | + '@ethersproject/bytes': 5.8.0 | ||
| 489 | + '@ethersproject/constants': 5.8.0 | ||
| 490 | + '@ethersproject/hash': 5.8.0 | ||
| 491 | + '@ethersproject/logger': 5.8.0 | ||
| 492 | + '@ethersproject/networks': 5.8.0 | ||
| 493 | + '@ethersproject/properties': 5.8.0 | ||
| 494 | + '@ethersproject/random': 5.8.0 | ||
| 495 | + '@ethersproject/rlp': 5.8.0 | ||
| 496 | + '@ethersproject/sha2': 5.8.0 | ||
| 497 | + '@ethersproject/strings': 5.8.0 | ||
| 498 | + '@ethersproject/transactions': 5.8.0 | ||
| 499 | + '@ethersproject/web': 5.8.0 | ||
| 500 | + bech32: 1.1.4 | ||
| 501 | + ws: 8.18.0 | ||
| 502 | + transitivePeerDependencies: | ||
| 503 | + - bufferutil | ||
| 504 | + - utf-8-validate | ||
| 505 | + | ||
| 506 | + '@ethersproject/random@5.8.0': | ||
| 507 | + dependencies: | ||
| 508 | + '@ethersproject/bytes': 5.8.0 | ||
| 509 | + '@ethersproject/logger': 5.8.0 | ||
| 510 | + | ||
| 511 | + '@ethersproject/rlp@5.8.0': | ||
| 512 | + dependencies: | ||
| 513 | + '@ethersproject/bytes': 5.8.0 | ||
| 514 | + '@ethersproject/logger': 5.8.0 | ||
| 515 | + | ||
| 516 | + '@ethersproject/sha2@5.8.0': | ||
| 517 | + dependencies: | ||
| 518 | + '@ethersproject/bytes': 5.8.0 | ||
| 519 | + '@ethersproject/logger': 5.8.0 | ||
| 520 | + hash.js: 1.1.7 | ||
| 521 | + | ||
| 522 | + '@ethersproject/signing-key@5.8.0': | ||
| 523 | + dependencies: | ||
| 524 | + '@ethersproject/bytes': 5.8.0 | ||
| 525 | + '@ethersproject/logger': 5.8.0 | ||
| 526 | + '@ethersproject/properties': 5.8.0 | ||
| 527 | + bn.js: 5.2.5 | ||
| 528 | + elliptic: 6.6.1 | ||
| 529 | + hash.js: 1.1.7 | ||
| 530 | + | ||
| 531 | + '@ethersproject/solidity@5.8.0': | ||
| 532 | + dependencies: | ||
| 533 | + '@ethersproject/bignumber': 5.8.0 | ||
| 534 | + '@ethersproject/bytes': 5.8.0 | ||
| 535 | + '@ethersproject/keccak256': 5.8.0 | ||
| 536 | + '@ethersproject/logger': 5.8.0 | ||
| 537 | + '@ethersproject/sha2': 5.8.0 | ||
| 538 | + '@ethersproject/strings': 5.8.0 | ||
| 539 | + | ||
| 540 | + '@ethersproject/strings@5.8.0': | ||
| 541 | + dependencies: | ||
| 542 | + '@ethersproject/bytes': 5.8.0 | ||
| 543 | + '@ethersproject/constants': 5.8.0 | ||
| 544 | + '@ethersproject/logger': 5.8.0 | ||
| 545 | + | ||
| 546 | + '@ethersproject/transactions@5.8.0': | ||
| 547 | + dependencies: | ||
| 548 | + '@ethersproject/address': 5.8.0 | ||
| 549 | + '@ethersproject/bignumber': 5.8.0 | ||
| 550 | + '@ethersproject/bytes': 5.8.0 | ||
| 551 | + '@ethersproject/constants': 5.8.0 | ||
| 552 | + '@ethersproject/keccak256': 5.8.0 | ||
| 553 | + '@ethersproject/logger': 5.8.0 | ||
| 554 | + '@ethersproject/properties': 5.8.0 | ||
| 555 | + '@ethersproject/rlp': 5.8.0 | ||
| 556 | + '@ethersproject/signing-key': 5.8.0 | ||
| 557 | + | ||
| 558 | + '@ethersproject/units@5.8.0': | ||
| 559 | + dependencies: | ||
| 560 | + '@ethersproject/bignumber': 5.8.0 | ||
| 561 | + '@ethersproject/constants': 5.8.0 | ||
| 562 | + '@ethersproject/logger': 5.8.0 | ||
| 563 | + | ||
| 564 | + '@ethersproject/wallet@5.8.0': | ||
| 565 | + dependencies: | ||
| 566 | + '@ethersproject/abstract-provider': 5.8.0 | ||
| 567 | + '@ethersproject/abstract-signer': 5.8.0 | ||
| 568 | + '@ethersproject/address': 5.8.0 | ||
| 569 | + '@ethersproject/bignumber': 5.8.0 | ||
| 570 | + '@ethersproject/bytes': 5.8.0 | ||
| 571 | + '@ethersproject/hash': 5.8.0 | ||
| 572 | + '@ethersproject/hdnode': 5.8.0 | ||
| 573 | + '@ethersproject/json-wallets': 5.8.0 | ||
| 574 | + '@ethersproject/keccak256': 5.8.0 | ||
| 575 | + '@ethersproject/logger': 5.8.0 | ||
| 576 | + '@ethersproject/properties': 5.8.0 | ||
| 577 | + '@ethersproject/random': 5.8.0 | ||
| 578 | + '@ethersproject/signing-key': 5.8.0 | ||
| 579 | + '@ethersproject/transactions': 5.8.0 | ||
| 580 | + '@ethersproject/wordlists': 5.8.0 | ||
| 581 | + | ||
| 582 | + '@ethersproject/web@5.8.0': | ||
| 583 | + dependencies: | ||
| 584 | + '@ethersproject/base64': 5.8.0 | ||
| 585 | + '@ethersproject/bytes': 5.8.0 | ||
| 586 | + '@ethersproject/logger': 5.8.0 | ||
| 587 | + '@ethersproject/properties': 5.8.0 | ||
| 588 | + '@ethersproject/strings': 5.8.0 | ||
| 589 | + | ||
| 590 | + '@ethersproject/wordlists@5.8.0': | ||
| 591 | + dependencies: | ||
| 592 | + '@ethersproject/bytes': 5.8.0 | ||
| 593 | + '@ethersproject/hash': 5.8.0 | ||
| 594 | + '@ethersproject/logger': 5.8.0 | ||
| 595 | + '@ethersproject/properties': 5.8.0 | ||
| 596 | + '@ethersproject/strings': 5.8.0 | ||
| 597 | + | ||
| 598 | + '@iden3/bigarray@0.0.2': {} | ||
| 599 | + | ||
| 600 | + '@iden3/binfileutils@0.0.12': | ||
| 601 | + dependencies: | ||
| 602 | + fastfile: 0.0.20 | ||
| 603 | + ffjavascript: 0.3.1 | ||
| 604 | + | ||
| 605 | + '@noble/hashes@1.8.0': {} | ||
| 606 | + | ||
| 607 | + aes-js@3.0.0: {} | ||
| 608 | + | ||
| 609 | + async@3.2.6: {} | ||
| 610 | + | ||
| 611 | + b4a@1.9.0: {} | ||
| 612 | + | ||
| 613 | + balanced-match@1.0.2: {} | ||
| 614 | + | ||
| 615 | + bech32@1.1.4: {} | ||
| 616 | + | ||
| 617 | + bfj@7.1.0: | ||
| 618 | + dependencies: | ||
| 619 | + bluebird: 3.7.2 | ||
| 620 | + check-types: 11.2.3 | ||
| 621 | + hoopy: 0.1.4 | ||
| 622 | + jsonpath: 1.3.0 | ||
| 623 | + tryer: 1.0.1 | ||
| 624 | + | ||
| 625 | + blake-hash@2.0.0: | ||
| 626 | + dependencies: | ||
| 627 | + node-addon-api: 3.2.1 | ||
| 628 | + node-gyp-build: 4.8.4 | ||
| 629 | + readable-stream: 3.6.2 | ||
| 630 | + | ||
| 631 | + blake2b-wasm@2.4.0: | ||
| 632 | + dependencies: | ||
| 633 | + b4a: 1.9.0 | ||
| 634 | + nanoassert: 2.0.0 | ||
| 635 | + transitivePeerDependencies: | ||
| 636 | + - react-native-b4a | ||
| 637 | + | ||
| 638 | + blake2b@2.1.4: | ||
| 639 | + dependencies: | ||
| 640 | + blake2b-wasm: 2.4.0 | ||
| 641 | + nanoassert: 2.0.0 | ||
| 642 | + transitivePeerDependencies: | ||
| 643 | + - react-native-b4a | ||
| 644 | + | ||
| 645 | + bluebird@3.7.2: {} | ||
| 646 | + | ||
| 647 | + bn.js@4.12.5: {} | ||
| 648 | + | ||
| 649 | + bn.js@5.2.5: {} | ||
| 650 | + | ||
| 651 | + brace-expansion@2.1.7: | ||
| 652 | + dependencies: | ||
| 653 | + balanced-match: 1.0.2 | ||
| 654 | + | ||
| 655 | + brorand@1.1.0: {} | ||
| 656 | + | ||
| 657 | + check-types@11.2.3: {} | ||
| 658 | + | ||
| 659 | + circom_runtime@0.1.28: | ||
| 660 | + dependencies: | ||
| 661 | + ffjavascript: 0.3.1 | ||
| 662 | + | ||
| 663 | + circomlib@2.0.5: {} | ||
| 664 | + | ||
| 665 | + circomlibjs@0.1.7: | ||
| 666 | + dependencies: | ||
| 667 | + blake-hash: 2.0.0 | ||
| 668 | + blake2b: 2.1.4 | ||
| 669 | + ethers: 5.8.0 | ||
| 670 | + ffjavascript: 0.2.63 | ||
| 671 | + transitivePeerDependencies: | ||
| 672 | + - bufferutil | ||
| 673 | + - react-native-b4a | ||
| 674 | + - utf-8-validate | ||
| 675 | + | ||
| 676 | + ejs@3.1.10: | ||
| 677 | + dependencies: | ||
| 678 | + jake: 10.9.4 | ||
| 679 | + | ||
| 680 | + elliptic@6.6.1: | ||
| 681 | + dependencies: | ||
| 682 | + bn.js: 4.12.5 | ||
| 683 | + brorand: 1.1.0 | ||
| 684 | + hash.js: 1.1.7 | ||
| 685 | + hmac-drbg: 1.0.1 | ||
| 686 | + inherits: 2.0.4 | ||
| 687 | + minimalistic-assert: 1.0.1 | ||
| 688 | + minimalistic-crypto-utils: 1.0.1 | ||
| 689 | + | ||
| 690 | + escodegen@2.1.0: | ||
| 691 | + dependencies: | ||
| 692 | + esprima: 4.0.1 | ||
| 693 | + estraverse: 5.3.0 | ||
| 694 | + esutils: 2.0.3 | ||
| 695 | + optionalDependencies: | ||
| 696 | + source-map: 0.6.1 | ||
| 697 | + | ||
| 698 | + esprima@1.2.5: {} | ||
| 699 | + | ||
| 700 | + esprima@4.0.1: {} | ||
| 701 | + | ||
| 702 | + estraverse@5.3.0: {} | ||
| 703 | + | ||
| 704 | + esutils@2.0.3: {} | ||
| 705 | + | ||
| 706 | + ethers@5.8.0: | ||
| 707 | + dependencies: | ||
| 708 | + '@ethersproject/abi': 5.8.0 | ||
| 709 | + '@ethersproject/abstract-provider': 5.8.0 | ||
| 710 | + '@ethersproject/abstract-signer': 5.8.0 | ||
| 711 | + '@ethersproject/address': 5.8.0 | ||
| 712 | + '@ethersproject/base64': 5.8.0 | ||
| 713 | + '@ethersproject/basex': 5.8.0 | ||
| 714 | + '@ethersproject/bignumber': 5.8.0 | ||
| 715 | + '@ethersproject/bytes': 5.8.0 | ||
| 716 | + '@ethersproject/constants': 5.8.0 | ||
| 717 | + '@ethersproject/contracts': 5.8.0 | ||
| 718 | + '@ethersproject/hash': 5.8.0 | ||
| 719 | + '@ethersproject/hdnode': 5.8.0 | ||
| 720 | + '@ethersproject/json-wallets': 5.8.0 | ||
| 721 | + '@ethersproject/keccak256': 5.8.0 | ||
| 722 | + '@ethersproject/logger': 5.8.0 | ||
| 723 | + '@ethersproject/networks': 5.8.0 | ||
| 724 | + '@ethersproject/pbkdf2': 5.8.0 | ||
| 725 | + '@ethersproject/properties': 5.8.0 | ||
| 726 | + '@ethersproject/providers': 5.8.0 | ||
| 727 | + '@ethersproject/random': 5.8.0 | ||
| 728 | + '@ethersproject/rlp': 5.8.0 | ||
| 729 | + '@ethersproject/sha2': 5.8.0 | ||
| 730 | + '@ethersproject/signing-key': 5.8.0 | ||
| 731 | + '@ethersproject/solidity': 5.8.0 | ||
| 732 | + '@ethersproject/strings': 5.8.0 | ||
| 733 | + '@ethersproject/transactions': 5.8.0 | ||
| 734 | + '@ethersproject/units': 5.8.0 | ||
| 735 | + '@ethersproject/wallet': 5.8.0 | ||
| 736 | + '@ethersproject/web': 5.8.0 | ||
| 737 | + '@ethersproject/wordlists': 5.8.0 | ||
| 738 | + transitivePeerDependencies: | ||
| 739 | + - bufferutil | ||
| 740 | + - utf-8-validate | ||
| 741 | + | ||
| 742 | + fastfile@0.0.20: {} | ||
| 743 | + | ||
| 744 | + ffjavascript@0.2.63: | ||
| 745 | + dependencies: | ||
| 746 | + wasmbuilder: 0.0.16 | ||
| 747 | + wasmcurves: 0.2.2 | ||
| 748 | + web-worker: 1.2.0 | ||
| 749 | + | ||
| 750 | + ffjavascript@0.3.0: | ||
| 751 | + dependencies: | ||
| 752 | + wasmbuilder: 0.0.16 | ||
| 753 | + wasmcurves: 0.2.2 | ||
| 754 | + web-worker: 1.2.0 | ||
| 755 | + | ||
| 756 | + ffjavascript@0.3.1: | ||
| 757 | + dependencies: | ||
| 758 | + wasmbuilder: 0.0.16 | ||
| 759 | + wasmcurves: 0.2.2 | ||
| 760 | + web-worker: 1.2.0 | ||
| 761 | + | ||
| 762 | + filelist@1.0.6: | ||
| 763 | + dependencies: | ||
| 764 | + minimatch: 5.1.9 | ||
| 765 | + | ||
| 766 | + hash.js@1.1.7: | ||
| 767 | + dependencies: | ||
| 768 | + inherits: 2.0.4 | ||
| 769 | + minimalistic-assert: 1.0.1 | ||
| 770 | + | ||
| 771 | + hmac-drbg@1.0.1: | ||
| 772 | + dependencies: | ||
| 773 | + hash.js: 1.1.7 | ||
| 774 | + minimalistic-assert: 1.0.1 | ||
| 775 | + minimalistic-crypto-utils: 1.0.1 | ||
| 776 | + | ||
| 777 | + hoopy@0.1.4: {} | ||
| 778 | + | ||
| 779 | + inherits@2.0.4: {} | ||
| 780 | + | ||
| 781 | + jake@10.9.4: | ||
| 782 | + dependencies: | ||
| 783 | + async: 3.2.6 | ||
| 784 | + filelist: 1.0.6 | ||
| 785 | + picocolors: 1.1.1 | ||
| 786 | + | ||
| 787 | + js-sha3@0.8.0: {} | ||
| 788 | + | ||
| 789 | + jsonpath@1.3.0: | ||
| 790 | + dependencies: | ||
| 791 | + esprima: 1.2.5 | ||
| 792 | + static-eval: 2.1.1 | ||
| 793 | + underscore: 1.13.6 | ||
| 794 | + | ||
| 795 | + logplease@1.2.15: {} | ||
| 796 | + | ||
| 797 | + minimalistic-assert@1.0.1: {} | ||
| 798 | + | ||
| 799 | + minimalistic-crypto-utils@1.0.1: {} | ||
| 800 | + | ||
| 801 | + minimatch@5.1.9: | ||
| 802 | + dependencies: | ||
| 803 | + brace-expansion: 2.1.7 | ||
| 804 | + | ||
| 805 | + nanoassert@2.0.0: {} | ||
| 806 | + | ||
| 807 | + node-addon-api@3.2.1: {} | ||
| 808 | + | ||
| 809 | + node-gyp-build@4.8.4: {} | ||
| 810 | + | ||
| 811 | + picocolors@1.1.1: {} | ||
| 812 | + | ||
| 813 | + r1csfile@0.0.48: | ||
| 814 | + dependencies: | ||
| 815 | + '@iden3/bigarray': 0.0.2 | ||
| 816 | + '@iden3/binfileutils': 0.0.12 | ||
| 817 | + fastfile: 0.0.20 | ||
| 818 | + ffjavascript: 0.3.0 | ||
| 819 | + | ||
| 820 | + readable-stream@3.6.2: | ||
| 821 | + dependencies: | ||
| 822 | + inherits: 2.0.4 | ||
| 823 | + string_decoder: 1.3.0 | ||
| 824 | + util-deprecate: 1.0.2 | ||
| 825 | + | ||
| 826 | + safe-buffer@5.2.1: {} | ||
| 827 | + | ||
| 828 | + scrypt-js@3.0.1: {} | ||
| 829 | + | ||
| 830 | + snarkjs@0.7.6: | ||
| 831 | + dependencies: | ||
| 832 | + '@iden3/binfileutils': 0.0.12 | ||
| 833 | + '@noble/hashes': 1.8.0 | ||
| 834 | + bfj: 7.1.0 | ||
| 835 | + circom_runtime: 0.1.28 | ||
| 836 | + ejs: 3.1.10 | ||
| 837 | + fastfile: 0.0.20 | ||
| 838 | + ffjavascript: 0.3.1 | ||
| 839 | + logplease: 1.2.15 | ||
| 840 | + r1csfile: 0.0.48 | ||
| 841 | + | ||
| 842 | + source-map@0.6.1: | ||
| 843 | + optional: true | ||
| 844 | + | ||
| 845 | + static-eval@2.1.1: | ||
| 846 | + dependencies: | ||
| 847 | + escodegen: 2.1.0 | ||
| 848 | + | ||
| 849 | + string_decoder@1.3.0: | ||
| 850 | + dependencies: | ||
| 851 | + safe-buffer: 5.2.1 | ||
| 852 | + | ||
| 853 | + tryer@1.0.1: {} | ||
| 854 | + | ||
| 855 | + underscore@1.13.6: {} | ||
| 856 | + | ||
| 857 | + util-deprecate@1.0.2: {} | ||
| 858 | + | ||
| 859 | + wasmbuilder@0.0.16: {} | ||
| 860 | + | ||
| 861 | + wasmcurves@0.2.2: | ||
| 862 | + dependencies: | ||
| 863 | + wasmbuilder: 0.0.16 | ||
| 864 | + | ||
| 865 | + web-worker@1.2.0: {} | ||
| 866 | + | ||
| 867 | + ws@8.18.0: {} | ||
added
zk/scripts/fixture.mjs +41 -0 | new file mode 100644 | ||
| @@ -0,0 +1,41 @@ | ||
| 1 | +// Regenerates test/fixtures/proofs.json: real Groth16 proofs the Solidity tests | |
| 2 | +// replay against a fresh pool. | |
| 3 | +// | |
| 4 | +// Three notes with fixed values are deposited in order. Two claims are proved: | |
| 5 | +// note 0 against the root after one deposit (an old root that must stay valid), | |
| 6 | +// and note 2 against the root after all three (a leaf whose sibling is empty | |
| 7 | +// and whose parent is a right child, so both path directions are exercised). | |
| 8 | +// | |
| 9 | +// Rerun this after `pnpm setup`: the proofs are only valid for the zkey they | |
| 10 | +// were made with. | |
| 11 | +import { writeFileSync } from "node:fs"; | |
| 12 | +import { fileURLToPath } from "node:url"; | |
| 13 | +import { noteHashes, prove } from "../lib.mjs"; | |
| 14 | + | |
| 15 | +const OUT = fileURLToPath(new URL("../../test/fixtures/proofs.json", import.meta.url)); | |
| 16 | + | |
| 17 | +// Must match RECIPIENT in test/LoveZK.t.sol. | |
| 18 | +const RECIPIENT = "0x00000000000000000000000000000000000B0B01"; | |
| 19 | + | |
| 20 | +const notes = [1n, 2n, 3n].map((i) => ({ nullifier: 1000n + i, secret: 2000n + i })); | |
| 21 | +const commitments = []; | |
| 22 | +for (const note of notes) commitments.push((await noteHashes(note)).commitment); | |
| 23 | + | |
| 24 | +const claims = []; | |
| 25 | +for (const { index, deposits } of [ | |
| 26 | + { index: 0, deposits: 1 }, | |
| 27 | + { index: 2, deposits: 3 }, | |
| 28 | +]) { | |
| 29 | + const { pA, pB, pC, root, nullifierHash } = await prove({ | |
| 30 | + note: notes[index], | |
| 31 | + commitments: commitments.slice(0, deposits), | |
| 32 | + recipient: RECIPIENT, | |
| 33 | + }); | |
| 34 | + claims.push({ index, deposits, root, nullifierHash, pA, pB, pC }); | |
| 35 | +} | |
| 36 | + | |
| 37 | +writeFileSync(OUT, JSON.stringify({ recipient: RECIPIENT, commitments, claims }, null, 2) + "\n"); | |
| 38 | +console.log(`wrote ${OUT}`); | |
| 39 | + | |
| 40 | +// snarkjs keeps worker threads alive. | |
| 41 | +process.exit(0); | |
| new file mode 100644 | |||
| @@ -0,0 +1,41 @@ | |||
| 1 | +// Regenerates test/fixtures/proofs.json: real Groth16 proofs the Solidity tests | ||
| 2 | +// replay against a fresh pool. | ||
| 3 | +// | ||
| 4 | +// Three notes with fixed values are deposited in order. Two claims are proved: | ||
| 5 | +// note 0 against the root after one deposit (an old root that must stay valid), | ||
| 6 | +// and note 2 against the root after all three (a leaf whose sibling is empty | ||
| 7 | +// and whose parent is a right child, so both path directions are exercised). | ||
| 8 | +// | ||
| 9 | +// Rerun this after `pnpm setup`: the proofs are only valid for the zkey they | ||
| 10 | +// were made with. | ||
| 11 | +import { writeFileSync } from "node:fs"; | ||
| 12 | +import { fileURLToPath } from "node:url"; | ||
| 13 | +import { noteHashes, prove } from "../lib.mjs"; | ||
| 14 | + | ||
| 15 | +const OUT = fileURLToPath(new URL("../../test/fixtures/proofs.json", import.meta.url)); | ||
| 16 | + | ||
| 17 | +// Must match RECIPIENT in test/LoveZK.t.sol. | ||
| 18 | +const RECIPIENT = "0x00000000000000000000000000000000000B0B01"; | ||
| 19 | + | ||
| 20 | +const notes = [1n, 2n, 3n].map((i) => ({ nullifier: 1000n + i, secret: 2000n + i })); | ||
| 21 | +const commitments = []; | ||
| 22 | +for (const note of notes) commitments.push((await noteHashes(note)).commitment); | ||
| 23 | + | ||
| 24 | +const claims = []; | ||
| 25 | +for (const { index, deposits } of [ | ||
| 26 | + { index: 0, deposits: 1 }, | ||
| 27 | + { index: 2, deposits: 3 }, | ||
| 28 | +]) { | ||
| 29 | + const { pA, pB, pC, root, nullifierHash } = await prove({ | ||
| 30 | + note: notes[index], | ||
| 31 | + commitments: commitments.slice(0, deposits), | ||
| 32 | + recipient: RECIPIENT, | ||
| 33 | + }); | ||
| 34 | + claims.push({ index, deposits, root, nullifierHash, pA, pB, pC }); | ||
| 35 | +} | ||
| 36 | + | ||
| 37 | +writeFileSync(OUT, JSON.stringify({ recipient: RECIPIENT, commitments, claims }, null, 2) + "\n"); | ||
| 38 | +console.log(`wrote ${OUT}`); | ||
| 39 | + | ||
| 40 | +// snarkjs keeps worker threads alive. | ||
| 41 | +process.exit(0); | ||
added
zk/scripts/setup.sh +41 -0 | new file mode 100755 | ||
| @@ -0,0 +1,41 @@ | ||
| 1 | +#!/usr/bin/env bash | |
| 2 | +# Trusted setup for the withdraw circuit, with a single contributor. | |
| 3 | +# | |
| 4 | +# Two phases, one contribution each. Anyone who keeps the entropy from either | |
| 5 | +# contribution can forge proofs, so this is only as trustworthy as the machine | |
| 6 | +# it runs on. Fine for testnets; see docs/ZK.md before mainnet. | |
| 7 | +# | |
| 8 | +# Writes the artifacts the repo commits to zk/build and the verifier to | |
| 9 | +# src/Groth16Verifier.sol. Re-running produces a *different* verifier: commit | |
| 10 | +# the outputs together, and redeploy. | |
| 11 | +set -euo pipefail | |
| 12 | + | |
| 13 | +cd "$(dirname "$0")/.." | |
| 14 | + | |
| 15 | +POWER=13 # 2^13 = 8192 constraints; the circuit has 6133 | |
| 16 | +TMP=build/tmp | |
| 17 | +SNARKJS="pnpm exec snarkjs" | |
| 18 | + | |
| 19 | +rm -rf "$TMP" | |
| 20 | +mkdir -p "$TMP" | |
| 21 | + | |
| 22 | +pnpm compile | |
| 23 | + | |
| 24 | +# Phase 1: powers of tau, circuit-independent. | |
| 25 | +$SNARKJS powersoftau new bn128 "$POWER" "$TMP/pot_0000.ptau" | |
| 26 | +$SNARKJS powersoftau contribute "$TMP/pot_0000.ptau" "$TMP/pot_0001.ptau" \ | |
| 27 | + --name="phase 1" -e="$(openssl rand -hex 64)" | |
| 28 | +$SNARKJS powersoftau prepare phase2 "$TMP/pot_0001.ptau" "$TMP/pot_final.ptau" | |
| 29 | + | |
| 30 | +# Phase 2: circuit-specific. | |
| 31 | +$SNARKJS groth16 setup "$TMP/withdraw.r1cs" "$TMP/pot_final.ptau" "$TMP/withdraw_0000.zkey" | |
| 32 | +$SNARKJS zkey contribute "$TMP/withdraw_0000.zkey" build/withdraw.zkey \ | |
| 33 | + --name="phase 2" -e="$(openssl rand -hex 64)" | |
| 34 | +$SNARKJS zkey verify "$TMP/withdraw.r1cs" "$TMP/pot_final.ptau" build/withdraw.zkey | |
| 35 | + | |
| 36 | +$SNARKJS zkey export verificationkey build/withdraw.zkey build/verification_key.json | |
| 37 | +$SNARKJS zkey export solidityverifier build/withdraw.zkey ../src/Groth16Verifier.sol | |
| 38 | + | |
| 39 | +cp "$TMP/withdraw_js/withdraw.wasm" build/withdraw.wasm | |
| 40 | + | |
| 41 | +rm -rf "$TMP" | |
| new file mode 100755 | |||
| @@ -0,0 +1,41 @@ | |||
| 1 | +#!/usr/bin/env bash | ||
| 2 | +# Trusted setup for the withdraw circuit, with a single contributor. | ||
| 3 | +# | ||
| 4 | +# Two phases, one contribution each. Anyone who keeps the entropy from either | ||
| 5 | +# contribution can forge proofs, so this is only as trustworthy as the machine | ||
| 6 | +# it runs on. Fine for testnets; see docs/ZK.md before mainnet. | ||
| 7 | +# | ||
| 8 | +# Writes the artifacts the repo commits to zk/build and the verifier to | ||
| 9 | +# src/Groth16Verifier.sol. Re-running produces a *different* verifier: commit | ||
| 10 | +# the outputs together, and redeploy. | ||
| 11 | +set -euo pipefail | ||
| 12 | + | ||
| 13 | +cd "$(dirname "$0")/.." | ||
| 14 | + | ||
| 15 | +POWER=13 # 2^13 = 8192 constraints; the circuit has 6133 | ||
| 16 | +TMP=build/tmp | ||
| 17 | +SNARKJS="pnpm exec snarkjs" | ||
| 18 | + | ||
| 19 | +rm -rf "$TMP" | ||
| 20 | +mkdir -p "$TMP" | ||
| 21 | + | ||
| 22 | +pnpm compile | ||
| 23 | + | ||
| 24 | +# Phase 1: powers of tau, circuit-independent. | ||
| 25 | +$SNARKJS powersoftau new bn128 "$POWER" "$TMP/pot_0000.ptau" | ||
| 26 | +$SNARKJS powersoftau contribute "$TMP/pot_0000.ptau" "$TMP/pot_0001.ptau" \ | ||
| 27 | + --name="phase 1" -e="$(openssl rand -hex 64)" | ||
| 28 | +$SNARKJS powersoftau prepare phase2 "$TMP/pot_0001.ptau" "$TMP/pot_final.ptau" | ||
| 29 | + | ||
| 30 | +# Phase 2: circuit-specific. | ||
| 31 | +$SNARKJS groth16 setup "$TMP/withdraw.r1cs" "$TMP/pot_final.ptau" "$TMP/withdraw_0000.zkey" | ||
| 32 | +$SNARKJS zkey contribute "$TMP/withdraw_0000.zkey" build/withdraw.zkey \ | ||
| 33 | + --name="phase 2" -e="$(openssl rand -hex 64)" | ||
| 34 | +$SNARKJS zkey verify "$TMP/withdraw.r1cs" "$TMP/pot_final.ptau" build/withdraw.zkey | ||
| 35 | + | ||
| 36 | +$SNARKJS zkey export verificationkey build/withdraw.zkey build/verification_key.json | ||
| 37 | +$SNARKJS zkey export solidityverifier build/withdraw.zkey ../src/Groth16Verifier.sol | ||
| 38 | + | ||
| 39 | +cp "$TMP/withdraw_js/withdraw.wasm" build/withdraw.wasm | ||
| 40 | + | ||
| 41 | +rm -rf "$TMP" | ||