diff --git a/README.md b/README.md index a7708c07cc..9a74cc2e20 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,8 @@ Some of these packages are used to share development configuration between the d - [`@pcd/semaphore-identity-pcd`](packages/semaphore-identity-pcd): a 'self-evident' PCD, representing the public and private components of a Semaphore identity - [`@pcd/semaphore-signature-pcd`](packages/semaphore-signature-pcd): like `@pcd/semaphore-group-pcd`, but with a more specific purpose of using the semaphore protocol to 'sign' a particular string message on behalf of a particular revealed commitment id. - [`@pcd/webauthn-pcd`](packages/webauthn-pcd): a pcd that can be used to make claims about [WebAuthn](https://webauthn.guide/) attestations, i.e. signing a particular challenge with a fingerprint, Yubikey, or another [authorization gesture](https://www.w3.org/TR/webauthn-2/#authorization-gesture). +- [`@pcd/ethereum-ownership-pcd`](packages/ethereum-ownership-pcd): a pcd that can be used to claim that a Semaphore identity knows the private key of an ethereum address. +- [`@pcd/ethereum-group-pcd`](packages/ethereum-group-pcd): a pcd that can be used to claim that a Semaphore identity knows the private key of an ethereum address or public key that is part of a merkle group of addresses or public keys, without revealing the specific one. - ... more to come! #### shared code packages diff --git a/apps/consumer-client/package.json b/apps/consumer-client/package.json index 5c6ec12440..4f9e1bf3aa 100644 --- a/apps/consumer-client/package.json +++ b/apps/consumer-client/package.json @@ -11,6 +11,7 @@ "lint": "next lint" }, "dependencies": { + "@pcd/ethereum-group-pcd": "0.0.1", "@pcd/ethereum-ownership-pcd": "0.6.0", "@pcd/passport-interface": "0.6.0", "@pcd/pcd-types": "0.6.0", diff --git a/apps/consumer-client/pages/examples/add-pcd.tsx b/apps/consumer-client/pages/examples/add-pcd.tsx index 7e271dfdcf..48f7711e7c 100644 --- a/apps/consumer-client/pages/examples/add-pcd.tsx +++ b/apps/consumer-client/pages/examples/add-pcd.tsx @@ -1,18 +1,30 @@ +import { + EthereumGroupPCDPackage, + getRawPubKeyBuffer, + GroupType, +} from "@pcd/ethereum-group-pcd"; +import { EthereumOwnershipPCDPackage } from "@pcd/ethereum-ownership-pcd"; import { constructPassportPcdAddRequestUrl, constructPassportPcdProveAndAddRequestUrl, + openSignedZuzaluSignInPopup, + usePassportPopupMessages, } from "@pcd/passport-interface"; -import { ArgumentTypeName } from "@pcd/pcd-types"; +import { ArgumentTypeName, SerializedPCD } from "@pcd/pcd-types"; import { SemaphoreGroupPCDPackage } from "@pcd/semaphore-group-pcd"; import { SemaphoreIdentityPCDPackage } from "@pcd/semaphore-identity-pcd"; import { SemaphoreSignaturePCDPackage } from "@pcd/semaphore-signature-pcd"; import { WebAuthnPCDPackage } from "@pcd/webauthn-pcd"; +import { Poseidon, Tree } from "@personaelabs/spartan-ecdsa"; import { Identity } from "@semaphore-protocol/identity"; import { startRegistration } from "@simplewebauthn/browser"; import { generateRegistrationOptions, verifyRegistrationResponse, } from "@simplewebauthn/server"; +import { ethers } from "ethers"; +import JSONBig from "json-bigint"; +import { useEffect, useState } from "react"; import { HomeLink } from "../../components/Core"; import { ExampleContainer } from "../../components/ExamplePage"; import { ZUPASS_URL, ZUZALU_SEMAPHORE_GROUP_URL } from "../../src/constants"; @@ -62,11 +74,185 @@ export default function Page() { +
+
+ +
+
+ ); } +function AddEthAddrPCDButton() { + const [pcdStr] = usePassportPopupMessages(); + const [isActive, setIsActive] = useState(false); + + useEffect(() => { + if (!pcdStr) return; + if (!isActive) return; + + const parsed = JSON.parse(pcdStr) as SerializedPCD; + + const ethereum = (window as any).ethereum; + const provider = new ethers.providers.Web3Provider(ethereum); + if (!ethereum) { + alert("Please install MetaMask to use this dApp!"); + } + + (async function () { + await ethereum.request({ method: "eth_requestAccounts" }); + const pcd = await SemaphoreSignaturePCDPackage.deserialize(parsed.pcd); + const signature = await provider + .getSigner() + .signMessage(pcd.claim.identityCommitment); + + const popupUrl = window.location.origin + "/popup"; + + const proofUrl = constructPassportPcdProveAndAddRequestUrl< + typeof EthereumOwnershipPCDPackage + >(ZUPASS_URL, popupUrl, EthereumOwnershipPCDPackage.name, { + identity: { + argumentType: ArgumentTypeName.PCD, + pcdType: SemaphoreIdentityPCDPackage.name, + value: undefined, + userProvided: true, + description: + "The Semaphore Identity which you are proving owns the given Ethereum address.", + }, + ethereumAddress: { + argumentType: ArgumentTypeName.String, + value: await provider.getSigner().getAddress(), + }, + ethereumSignatureOfCommitment: { + argumentType: ArgumentTypeName.String, + value: signature, + }, + }); + + sendPassportRequest(proofUrl); + })(); + + setIsActive(false); + }, [pcdStr, isActive]); + + return ( + + ); +} + +async function zupassSignIn(originalSiteName: string) { + openSignedZuzaluSignInPopup( + ZUPASS_URL, + window.location.origin + "/popup", + originalSiteName + ); +} + +function AddEthGroupPCDButton() { + const [pcdStr] = usePassportPopupMessages(); + const [isActive, setIsActive] = useState(false); + + useEffect(() => { + if (!pcdStr) return; + if (!isActive) return; + + const parsed = JSON.parse(pcdStr) as SerializedPCD; + + const ethereum = (window as any).ethereum; + const provider = new ethers.providers.Web3Provider(ethereum); + if (!ethereum) { + alert("Please install MetaMask to use this dApp!"); + } + + (async function () { + await ethereum.request({ method: "eth_requestAccounts" }); + const pcd = await SemaphoreSignaturePCDPackage.deserialize(parsed.pcd); + + const msgHash = Buffer.from( + ethers.utils.hashMessage(pcd.claim.identityCommitment).slice(2), + "hex" + ); + const signatureOfIdentityCommitment = await provider + .getSigner() + .signMessage(pcd.claim.identityCommitment); + + const poseidon = new Poseidon(); + await poseidon.initWasm(); + const treeDepth = 20; // Provided circuits have tree depth = 20 + const pubKeyTree = new Tree(treeDepth, poseidon); + + // Add some public keys to the tree + for (const member of [ + "0x04b4d5188949bf70c4db5e965a9ea67b80407e8ee7fa3a260ccf86e9c0395fe82cba155fdff55829b3c862322aba402d00b563861b603879ee8ae211c34257d4ad", + "0x042d21e6aa2021a991a82d08591fa0528d0bebe4ac9a34d851a74507327d930dec217380bd602fe48a143bb21106ab274d6a51aff396f0e4f7e1e3a8a673d46d83", + ]) { + pubKeyTree.insert(poseidon.hashPubKey(getRawPubKeyBuffer(member))); + } + // Add the prover's public key to the tree + const proverPubkeyBuffer: Buffer = getRawPubKeyBuffer( + ethers.utils.recoverPublicKey(msgHash, signatureOfIdentityCommitment) + ); + pubKeyTree.insert(poseidon.hashPubKey(proverPubkeyBuffer)); + const pubKeyIndex = pubKeyTree.indexOf( + poseidon.hashPubKey(proverPubkeyBuffer) + ); // == 2 in this test + + // Prove membership of the prover's public key in the tree + const merkleProof = pubKeyTree.createProof(pubKeyIndex); + + const popupUrl = window.location.origin + "/popup"; + const proofUrl = constructPassportPcdProveAndAddRequestUrl< + typeof EthereumGroupPCDPackage + >(ZUPASS_URL, popupUrl, EthereumGroupPCDPackage.name, { + identity: { + argumentType: ArgumentTypeName.PCD, + pcdType: SemaphoreIdentityPCDPackage.name, + value: undefined, + userProvided: true, + description: + "The Semaphore Identity which you are signing the message.", + }, + groupType: { + argumentType: ArgumentTypeName.String, + value: GroupType.PUBLICKEY, + }, + signatureOfIdentityCommitment: { + argumentType: ArgumentTypeName.String, + value: signatureOfIdentityCommitment, + }, + merkleProof: { + argumentType: ArgumentTypeName.String, + value: JSONBig({ useNativeBigInt: true }).stringify(merkleProof), + }, + }); + + sendPassportRequest(proofUrl); + })(); + + setIsActive(false); + }, [pcdStr, isActive]); + + return ( + + ); +} + async function addGroupMembershipProofPCD() { const url = constructPassportPcdProveAndAddRequestUrl< typeof SemaphoreGroupPCDPackage diff --git a/apps/passport-client/src/pcdPackages.ts b/apps/passport-client/src/pcdPackages.ts index 103da848a0..dc13c95458 100644 --- a/apps/passport-client/src/pcdPackages.ts +++ b/apps/passport-client/src/pcdPackages.ts @@ -1,3 +1,4 @@ +import { EthereumGroupPCDPackage } from "@pcd/ethereum-group-pcd"; import { EthereumOwnershipPCDPackage } from "@pcd/ethereum-ownership-pcd"; import { HaLoNoncePCDPackage } from "@pcd/halo-nonce-pcd"; import { PCDPackage } from "@pcd/pcd-types"; @@ -41,6 +42,26 @@ async function loadPackages(): Promise { zkeyFilePath: "/semaphore-artifacts/16.zkey", }); + await EthereumGroupPCDPackage.init({ + wasmFilePath: "/semaphore-artifacts/16.wasm", + zkeyFilePath: "/semaphore-artifacts/16.zkey", + + // TODO: update these to point to pcd pass' static server + addrMembershipConfig: { + circuit: + "https://storage.googleapis.com/personae-proving-keys/membership/addr_membership.circuit", + witnessGenWasm: + "https://storage.googleapis.com/personae-proving-keys/membership/addr_membership.wasm", + }, + + pubkeyMembershipConfig: { + circuit: + "https://storage.googleapis.com/personae-proving-keys/membership/pubkey_membership.circuit", + witnessGenWasm: + "https://storage.googleapis.com/personae-proving-keys/membership/pubkey_membership.wasm", + }, + }); + await RLNPCDPackage.init({ wasmFilePath: SERVER_STATIC_URL + "rln-artifacts/16.wasm", zkeyFilePath: SERVER_STATIC_URL + "rln-artifacts/16.zkey", @@ -55,6 +76,7 @@ async function loadPackages(): Promise { SemaphoreIdentityPCDPackage, SemaphoreSignaturePCDPackage, EthereumOwnershipPCDPackage, + EthereumGroupPCDPackage, JubJubSignaturePCDPackage, RLNPCDPackage, WebAuthnPCDPackage, diff --git a/apps/passport-server/public/spartan-ecdsa-artifacts/addr_membership.circuit b/apps/passport-server/public/spartan-ecdsa-artifacts/addr_membership.circuit new file mode 100644 index 0000000000..8e9d5b2882 Binary files /dev/null and b/apps/passport-server/public/spartan-ecdsa-artifacts/addr_membership.circuit differ diff --git a/apps/passport-server/public/spartan-ecdsa-artifacts/addr_membership.wasm b/apps/passport-server/public/spartan-ecdsa-artifacts/addr_membership.wasm new file mode 100644 index 0000000000..8deb4b9a68 Binary files /dev/null and b/apps/passport-server/public/spartan-ecdsa-artifacts/addr_membership.wasm differ diff --git a/apps/passport-server/public/spartan-ecdsa-artifacts/pubkey_membership.circuit b/apps/passport-server/public/spartan-ecdsa-artifacts/pubkey_membership.circuit new file mode 100644 index 0000000000..1dbca38d26 Binary files /dev/null and b/apps/passport-server/public/spartan-ecdsa-artifacts/pubkey_membership.circuit differ diff --git a/apps/passport-server/public/spartan-ecdsa-artifacts/pubkey_membership.wasm b/apps/passport-server/public/spartan-ecdsa-artifacts/pubkey_membership.wasm new file mode 100644 index 0000000000..648dfea70a Binary files /dev/null and b/apps/passport-server/public/spartan-ecdsa-artifacts/pubkey_membership.wasm differ diff --git a/packages/ethereum-group-pcd/.eslintrc.js b/packages/ethereum-group-pcd/.eslintrc.js new file mode 100644 index 0000000000..dac5168f48 --- /dev/null +++ b/packages/ethereum-group-pcd/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + extends: ["@pcd/eslint-config-custom"], + root: true, +}; diff --git a/packages/ethereum-group-pcd/.gitignore b/packages/ethereum-group-pcd/.gitignore new file mode 100644 index 0000000000..5d074eaa74 --- /dev/null +++ b/packages/ethereum-group-pcd/.gitignore @@ -0,0 +1,5 @@ +*.js +*.d.ts +*.ts.map +!.eslintrc.js +dist diff --git a/packages/ethereum-group-pcd/CHANGELOG.md b/packages/ethereum-group-pcd/CHANGELOG.md new file mode 100644 index 0000000000..c192f89e83 --- /dev/null +++ b/packages/ethereum-group-pcd/CHANGELOG.md @@ -0,0 +1,7 @@ +# @pcd/ethereum-group-pcd + +## 0.0.1 + +### Patch Changes + +- Hello world diff --git a/packages/ethereum-group-pcd/README.md b/packages/ethereum-group-pcd/README.md new file mode 100644 index 0000000000..70c146a068 --- /dev/null +++ b/packages/ethereum-group-pcd/README.md @@ -0,0 +1,17 @@ +# `@pcd/ethereum-group-pcd` + +PCD which proves that a particular Semaphore Identity owns a particular ethereum address that is part of an address merkle set or public key merkle set, without revealing the ethereum address. + +It uses an optimized group ecdsa zk [circuit](https://github.com/personaelabs/spartan-ecdsa) written by Personae Labs. + +By pre-computing a set of Ethereum addresses that satisfy a particular property, one can prove that property about themselves without revealing their exact address. For example, proving that one: + +- owns a NounDao (punk, or etc) NFT at a particular block +- made a transaction before 2020 +- made at least 100 transactions +- owns at least 10 eth +- used tornado cash when it was cool +- has been validated by proof-of-humanity +- participated in a conference that issued a POAP + +If you always use one address, proving multiple properties about yourself will narrow the anonymity set. However, if you use multiple addresses, you can own multiple PCDs without narrowing the anonymity set. diff --git a/packages/ethereum-group-pcd/artifacts/16.json b/packages/ethereum-group-pcd/artifacts/16.json new file mode 100644 index 0000000000..3d6db34c6a --- /dev/null +++ b/packages/ethereum-group-pcd/artifacts/16.json @@ -0,0 +1,109 @@ +{ + "protocol": "groth16", + "curve": "bn128", + "nPublic": 4, + "vk_alpha_1": [ + "20491192805390485299153009773594534940189261866228447918068658471970481763042", + "9383485363053290200918347156157836566562967994039712273449902621266178545958", + "1" + ], + "vk_beta_2": [ + [ + "6375614351688725206403948262868962793625744043794305715222011528459656738731", + "4252822878758300859123897981450591353533073413197771768651442665752259397132" + ], + [ + "10505242626370262277552901082094356697409835680220590971873171140371331206856", + "21847035105528745403288232691147584728191162732299865338377159692350059136679" + ], + [ + "1", + "0" + ] + ], + "vk_gamma_2": [ + [ + "10857046999023057135944570762232829481370756359578518086990519993285655852781", + "11559732032986387107991004021392285783925812861821192530917403151452391805634" + ], + [ + "8495653923123431417604973247489272438418190587263600148770280649306958101930", + "4082367875863433681332203403145435568316851327593401208105741076214120093531" + ], + [ + "1", + "0" + ] + ], + "vk_delta_2": [ + [ + "16243966861079634958125511652590761846958471358623040426599000904006426210032", + "13406811599156507528361773763681356312643537981039994686313383243831956396116" + ], + [ + "15688083679237922164673518758181461582601853873216319711156397437601833996222", + "11781596534582143578120404722739278517564025497573071755253972265891888117374" + ], + [ + "1", + "0" + ] + ], + "vk_alphabeta_12": [ + [ + [ + "2029413683389138792403550203267699914886160938906632433982220835551125967885", + "21072700047562757817161031222997517981543347628379360635925549008442030252106" + ], + [ + "5940354580057074848093997050200682056184807770593307860589430076672439820312", + "12156638873931618554171829126792193045421052652279363021382169897324752428276" + ], + [ + "7898200236362823042373859371574133993780991612861777490112507062703164551277", + "7074218545237549455313236346927434013100842096812539264420499035217050630853" + ] + ], + [ + [ + "7077479683546002997211712695946002074877511277312570035766170199895071832130", + "10093483419865920389913245021038182291233451549023025229112148274109565435465" + ], + [ + "4595479056700221319381530156280926371456704509942304414423590385166031118820", + "19831328484489333784475432780421641293929726139240675179672856274388269393268" + ], + [ + "11934129596455521040620786944827826205713621633706285934057045369193958244500", + "8037395052364110730298837004334506829870972346962140206007064471173334027475" + ] + ] + ], + "IC": [ + [ + "1964404930528116823793003656764176108669615750422202377358993070935069307720", + "2137714996673694828207437580381836490878070731768805974506391024595988817424", + "1" + ], + [ + "19568893707760843340848992184233194433177372925415116053368211122719346671126", + "11639469568629189918046964192305250472192697612201524135560178632824282818614", + "1" + ], + [ + "5317268879687484957437879782519918549127939892210247573193613900261494313825", + "528174394975085006443543773707702838726735933116136102590448357278717993744", + "1" + ], + [ + "14865918005176722116473730206622066845866539143554731094374354951675249722731", + "3197770568483953664363740385883457803041685902965668289308665954510373380344", + "1" + ], + [ + "6863358721495494421022713667808247652425178970453300712435830652679038918987", + "15025816433373311798308762709072064417001390853103872064614174594927359131281", + "1" + ] + ] +} \ No newline at end of file diff --git a/packages/ethereum-group-pcd/artifacts/16.wasm b/packages/ethereum-group-pcd/artifacts/16.wasm new file mode 100644 index 0000000000..eeeac83f72 Binary files /dev/null and b/packages/ethereum-group-pcd/artifacts/16.wasm differ diff --git a/packages/ethereum-group-pcd/artifacts/16.zkey b/packages/ethereum-group-pcd/artifacts/16.zkey new file mode 100644 index 0000000000..681b8e9b66 Binary files /dev/null and b/packages/ethereum-group-pcd/artifacts/16.zkey differ diff --git a/packages/ethereum-group-pcd/artifacts/addr_membership.circuit b/packages/ethereum-group-pcd/artifacts/addr_membership.circuit new file mode 100644 index 0000000000..8e9d5b2882 Binary files /dev/null and b/packages/ethereum-group-pcd/artifacts/addr_membership.circuit differ diff --git a/packages/ethereum-group-pcd/artifacts/addr_membership.wasm b/packages/ethereum-group-pcd/artifacts/addr_membership.wasm new file mode 100644 index 0000000000..8deb4b9a68 Binary files /dev/null and b/packages/ethereum-group-pcd/artifacts/addr_membership.wasm differ diff --git a/packages/ethereum-group-pcd/artifacts/pubkey_membership.circuit b/packages/ethereum-group-pcd/artifacts/pubkey_membership.circuit new file mode 100644 index 0000000000..1dbca38d26 Binary files /dev/null and b/packages/ethereum-group-pcd/artifacts/pubkey_membership.circuit differ diff --git a/packages/ethereum-group-pcd/artifacts/pubkey_membership.wasm b/packages/ethereum-group-pcd/artifacts/pubkey_membership.wasm new file mode 100644 index 0000000000..648dfea70a Binary files /dev/null and b/packages/ethereum-group-pcd/artifacts/pubkey_membership.wasm differ diff --git a/packages/ethereum-group-pcd/package.json b/packages/ethereum-group-pcd/package.json new file mode 100644 index 0000000000..433521796f --- /dev/null +++ b/packages/ethereum-group-pcd/package.json @@ -0,0 +1,66 @@ +{ + "name": "@pcd/ethereum-group-pcd", + "version": "0.0.1", + "license": "GPL-3.0-or-later", + "main": "./dist/node/index.js", + "types": "./src/index.ts", + "files": [ + "./artifacts/*", + "./src/*", + "./dist/*", + "./README.md" + ], + "exports": { + ".": { + "node": { + "default": "./dist/node/index.js" + }, + "browser": { + "default": "./dist/browser/index.js" + } + } + }, + "scripts": { + "lint": "eslint \"**/*.ts{,x}\"", + "build": "tsup --platform=browser src/index.ts --out-dir ./dist/browser && tsup --platform=node src/index.ts --out-dir ./dist/node", + "dev": "concurrently \"yarn dev:browser\" \"yarn dev:node\"", + "dev:browser": "tsup --platform=browser src/index.ts --out-dir ./dist/browser --watch", + "dev:node": "tsup --platform=node src/index.ts --out-dir ./dist/browser --watch", + "typecheck": "yarn tsc --noEmit", + "test": "ts-mocha --exit test/**/*.spec.ts", + "prepublishOnly": "yarn build" + }, + "dependencies": { + "@pcd/passport-ui": "0.5.3", + "@pcd/pcd-types": "0.5.3", + "@pcd/semaphore-identity-pcd": "0.5.3", + "@pcd/semaphore-signature-pcd": "0.5.3", + "@personaelabs/spartan-ecdsa": "^2.1.4", + "@semaphore-protocol/group": "^3.10.0", + "@semaphore-protocol/identity": "^3.10.0", + "@semaphore-protocol/proof": "^3.10.0", + "ethers": "^5.7.2", + "js-sha256": "^0.9.0", + "json-bigint": "^1.0.0", + "react": "^18.2.0", + "styled-components": "^5.3.9" + }, + "devDependencies": { + "@pcd/eslint-config-custom": "0.5.2", + "@pcd/tsconfig": "0.5.2", + "@types/expect": "^24.3.0", + "@types/json-bigint": "^1.0.1", + "@types/mocha": "^10.0.1", + "@types/react": "^18.0.22", + "@types/react-dom": "^18.0.7", + "@types/styled-components": "^5.1.26", + "concurrently": "^8.2.0", + "eslint": "^7.32.0", + "mocha": "^10.2.0", + "ts-mocha": "^10.0.0", + "typescript": "^4.9.5" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/ethereum-group-pcd/src/CardBody.tsx b/packages/ethereum-group-pcd/src/CardBody.tsx new file mode 100644 index 0000000000..761d5fa096 --- /dev/null +++ b/packages/ethereum-group-pcd/src/CardBody.tsx @@ -0,0 +1,51 @@ +import { + FieldLabel, + HiddenText, + Separator, + Spacer, + TextContainer, +} from "@pcd/passport-ui"; +import { SemaphoreSignaturePCDPackage } from "@pcd/semaphore-signature-pcd"; +import { useEffect, useState } from "react"; +import styled from "styled-components"; +import { EthereumGroupPCD } from "./EthereumGroupPCD"; + +export function EthereumGroupCardBody({ pcd }: { pcd: EthereumGroupPCD }) { + const [identityCommitment, setIdentityCommitment] = + useState(""); + + useEffect(() => { + SemaphoreSignaturePCDPackage.deserialize(pcd.proof.signatureProof.pcd).then( + (pcd) => { + setIdentityCommitment(pcd.claim.identityCommitment); + } + ); + }, [pcd]); + + return ( + +

+ This PCD represents that a particular Semphore Identity owns an Ethereum + Address that is part of a merkle group of Ethereum public keys or + addresses. +

+ + + + Commitment + + + + Merkle Root + + {pcd.claim.publicInput.circuitPubInput.merkleRoot.toString(16)} + +
+ ); +} + +const Container = styled.div` + padding: 16px; + overflow: hidden; + width: 100%; +`; diff --git a/packages/ethereum-group-pcd/src/EthereumGroupPCD.ts b/packages/ethereum-group-pcd/src/EthereumGroupPCD.ts new file mode 100644 index 0000000000..e5c5e5dbf7 --- /dev/null +++ b/packages/ethereum-group-pcd/src/EthereumGroupPCD.ts @@ -0,0 +1,336 @@ +import { + ArgumentTypeName, + DisplayOptions, + PCD, + PCDArgument, + PCDPackage, + SerializedPCD, + StringArgument, +} from "@pcd/pcd-types"; +import { + SemaphoreIdentityPCD, + SemaphoreIdentityPCDPackage, + SemaphoreIdentityPCDTypeName, +} from "@pcd/semaphore-identity-pcd"; +import { + SemaphoreSignaturePCD, + SemaphoreSignaturePCDPackage, +} from "@pcd/semaphore-signature-pcd"; +import { + CircuitPubInput, + MembershipProver, + MembershipVerifier, + ProverConfig, + PublicInput, +} from "@personaelabs/spartan-ecdsa"; +import { ethers } from "ethers"; +import JSONBig from "json-bigint"; +import { v4 as uuid } from "uuid"; +import { EthereumGroupCardBody } from "./CardBody"; + +export const EthereumGroupPCDTypeName = "ethereum-group-pcd"; + +export interface EthereumGroupPCDInitArgs { + // TODO: how do we distribute these in-package, so that consumers + // of the package don't have to copy-paste these artifacts? + // TODO: how do we account for different versions of the same type + // of artifact? eg. this one is parameterized by group size. Should + // we pre-generate a bunch of artifacts per possible group size? + // Should we do code-gen? + zkeyFilePath: string; + wasmFilePath: string; + + addrMembershipConfig?: ProverConfig; + pubkeyMembershipConfig?: ProverConfig; +} + +export interface EthereumGroupPCDArgs { + identity: PCDArgument; + signatureOfIdentityCommitment: StringArgument; + merkleProof: StringArgument; + groupType: StringArgument; +} + +export interface EthereumGroupPCDClaim { + publicInput: PublicInput; + groupType: GroupType; +} + +export enum GroupType { + PUBLICKEY = "public_key", + ADDRESS = "address", +} + +export interface EthereumGroupPCDProof { + /** + * A signature of the serialized spartan-ecdsa group membership proof, using a semaphore identity. + */ + signatureProof: SerializedPCD; + + /** + * A hex string of the NIZK proof format defined in https://github.com/personaelabs/spartan-ecdsa + * + * In the group membership proof, the semaphore identity is used as the message to sign. + * + * https://github.com/personaelabs/spartan-ecdsa/blob/5dae5e1aa4eda726ddffc08eaec0144d003a98a0/packages/Spartan-secq/src/lib.rs#L491 + * + * https://github.com/personaelabs/spartan-ecdsa/blob/5dae5e1aa4eda726ddffc08eaec0144d003a98a0/packages/Spartan-secq/src/r1csproof.rs#L23 + */ + ethereumGroupProof: string; +} + +export class EthereumGroupPCD + implements PCD +{ + type = EthereumGroupPCDTypeName; + claim: EthereumGroupPCDClaim; + proof: EthereumGroupPCDProof; + id: string; + + public constructor( + id: string, + claim: EthereumGroupPCDClaim, + proof: EthereumGroupPCDProof + ) { + this.id = id; + this.claim = claim; + this.proof = proof; + } +} + +let addrMembershipConfig: ProverConfig = { + circuit: __dirname.concat("/../artifacts/addr_membership.circuit"), + witnessGenWasm: __dirname.concat("/../artifacts/addr_membership.wasm"), +}; +let pubkeyMembershipConfig: ProverConfig = { + circuit: __dirname.concat("/../artifacts/pubkey_membership.circuit"), + witnessGenWasm: __dirname.concat("/../artifacts/pubkey_membership.wasm"), +}; +let addrProver: MembershipProver; +let pubkeyProver: MembershipProver; +let addrVerifier: MembershipVerifier; +let pubkeyVerifier: MembershipVerifier; + +export async function init(args: EthereumGroupPCDInitArgs): Promise { + addrMembershipConfig = args.addrMembershipConfig ?? addrMembershipConfig; + pubkeyMembershipConfig = + args.pubkeyMembershipConfig ?? pubkeyMembershipConfig; + addrProver = new MembershipProver(addrMembershipConfig); + pubkeyProver = new MembershipProver(pubkeyMembershipConfig); + addrVerifier = new MembershipVerifier(addrMembershipConfig); + pubkeyVerifier = new MembershipVerifier(pubkeyMembershipConfig); + await Promise.all([ + addrProver.initWasm(), + pubkeyProver.initWasm(), + addrVerifier.initWasm(), + pubkeyVerifier.initWasm(), + ]); + return SemaphoreSignaturePCDPackage.init!(args); +} + +/** + * Utility function to get the raw public key buffer from a hex string. + * + * The offset that the hex representation of the public key starts at, without the 0x prefix and without the 04 encoding prefix. That's what the spartan-ecdsa circuit expects. + * + * https://github.com/indutny/elliptic/issues/86 + * + * https://dev.to/q9/finally-understanding-ethereum-accounts-1kpe + */ +export function getRawPubKeyBuffer(pubKey: string): Buffer { + if (pubKey.length !== 132) { + throw new Error( + `invalid public key length ${pubKey.length}. Expected 130 (hex string)` + ); + } + const hexPubkeyOffset = 2 + 2; + return Buffer.from(pubKey.slice(hexPubkeyOffset), "hex"); +} + +export async function prove( + args: EthereumGroupPCDArgs +): Promise { + if (args.identity.value === undefined) { + throw new Error(`missing argument identity`); + } + + if (args.signatureOfIdentityCommitment.value === undefined) { + throw new Error(`missing argument signatureOfIdentityCommitment`); + } + + if (args.merkleProof.value === undefined) { + throw new Error(`missing argument merkleProof`); + } + + if ( + ![GroupType.ADDRESS, GroupType.PUBLICKEY].includes( + args.groupType.value as any + ) + ) { + throw new Error( + `invalid group type ${args.groupType}. Expected ${GroupType.ADDRESS} or ${GroupType.PUBLICKEY} got ${args.groupType.value}}` + ); + } + + const deserializedIdentity = await SemaphoreIdentityPCDPackage.deserialize( + args.identity.value.pcd + ); + const message = deserializedIdentity.claim.identity.commitment.toString(); + const msgHash = ethers.utils.hashMessage(message); + + const prover = args.groupType.value === "address" ? addrProver : pubkeyProver; + const { proof, publicInput } = await prover.prove( + args.signatureOfIdentityCommitment.value, + Buffer.from(msgHash.slice(2), "hex"), + JSONBig({ useNativeBigInt: true }).parse(args.merkleProof.value) + ); + + const publicInputMsgHash = "0x" + publicInput.msgHash.toString("hex"); + + if (msgHash !== publicInputMsgHash) { + throw new Error( + `public input message hash ${publicInputMsgHash} does not match commitment ${message} hash ${msgHash} ` + ); + } + + const semaphoreSignature = await SemaphoreSignaturePCDPackage.prove({ + identity: { + argumentType: ArgumentTypeName.PCD, + pcdType: SemaphoreIdentityPCDTypeName, + value: args.identity.value, + }, + signedMessage: { + argumentType: ArgumentTypeName.String, + value: Buffer.from(proof).toString("hex"), + }, + }); + + return new EthereumGroupPCD( + uuid(), + { + publicInput: publicInput, + groupType: + args.groupType.value === "address" + ? GroupType.ADDRESS + : GroupType.PUBLICKEY, + }, + { + signatureProof: await SemaphoreSignaturePCDPackage.serialize( + semaphoreSignature + ), + ethereumGroupProof: Buffer.from(proof).toString("hex"), + } + ); +} + +export async function verify(pcd: EthereumGroupPCD): Promise { + const semaphoreSignature = await SemaphoreSignaturePCDPackage.deserialize( + pcd.proof.signatureProof.pcd + ); + const signatureProofValid = await SemaphoreSignaturePCDPackage.verify( + semaphoreSignature + ); + + // the semaphore signature of the group membership proof must be valid + if (!signatureProofValid) { + return false; + } + + // The message signed by the semaphore signature must be the same as the + // serialized ethereum group proof + if (semaphoreSignature.claim.signedMessage !== pcd.proof.ethereumGroupProof) { + return false; + } + + const deserializedSignatureProof = + await SemaphoreSignaturePCDPackage.deserialize( + pcd.proof.signatureProof.pcd + ); + + const deserializedIdentity = + deserializedSignatureProof.claim.identityCommitment; + const message = deserializedIdentity; + const msgHash = ethers.utils.hashMessage(message); + + const publicInputMsgHash = + "0x" + pcd.claim.publicInput.msgHash.toString("hex"); + + // The string that the ethereum group proof was signed over must the same + // as the semaphore identity commitment. (Their hashes are equal) + if (msgHash !== publicInputMsgHash) { + return false; + } + + const verifier = + pcd.claim.groupType === GroupType.ADDRESS ? addrVerifier : pubkeyVerifier; + const groupMembershipValid = await verifier.verify( + Buffer.from(pcd.proof.ethereumGroupProof, "hex"), + pcd.claim.publicInput.serialize() + ); + + if (!groupMembershipValid) { + return false; + } + + return true; +} + +export async function serialize( + pcd: EthereumGroupPCD +): Promise> { + return { + type: EthereumGroupPCDTypeName, + pcd: JSONBig({ useNativeBigInt: true }).stringify(pcd), + } as SerializedPCD; +} + +export async function deserialize( + serialized: string +): Promise { + const parsed = JSONBig({ useNativeBigInt: true }).parse(serialized); + const publicInput = new PublicInput( + parsed.claim.publicInput.r, + parsed.claim.publicInput.rV, + Buffer.from(parsed.claim.publicInput.msgHash), + new CircuitPubInput( + parsed.claim.publicInput.circuitPubInput.merkleRoot, + parsed.claim.publicInput.circuitPubInput.Tx, + parsed.claim.publicInput.circuitPubInput.Ty, + parsed.claim.publicInput.circuitPubInput.Ux, + parsed.claim.publicInput.circuitPubInput.Uy + ) + ); + parsed.claim.publicInput = publicInput; + return new EthereumGroupPCD(parsed.id, parsed.claim, parsed.proof); +} + +export function getDisplayOptions(pcd: EthereumGroupPCD): DisplayOptions { + return { + header: + "Ethereum Merkle Group " + + pcd.claim.publicInput.circuitPubInput.merkleRoot + .toString(16) + .substring(0, 12), + displayName: "eth-group-" + pcd.id.substring(0, 4), + }; +} + +/** + * PCD-conforming wrapper for an ethereum group membership proof using the spartan-ecdsa circuit. + * You can find documentation of spartan-ecdsa here: https://github.com/personaelabs/spartan-ecdsa + */ +export const EthereumGroupPCDPackage: PCDPackage< + EthereumGroupPCDClaim, + EthereumGroupPCDProof, + EthereumGroupPCDArgs, + EthereumGroupPCDInitArgs +> = { + name: EthereumGroupPCDTypeName, + renderCardBody: EthereumGroupCardBody, + getDisplayOptions, + init, + prove, + verify, + serialize, + deserialize, +}; diff --git a/packages/ethereum-group-pcd/src/index.ts b/packages/ethereum-group-pcd/src/index.ts new file mode 100644 index 0000000000..3245738f7b --- /dev/null +++ b/packages/ethereum-group-pcd/src/index.ts @@ -0,0 +1 @@ +export * from "./EthereumGroupPCD"; diff --git a/packages/ethereum-group-pcd/test/EthereumGroupPCD.spec.ts b/packages/ethereum-group-pcd/test/EthereumGroupPCD.spec.ts new file mode 100644 index 0000000000..476d9cc06b --- /dev/null +++ b/packages/ethereum-group-pcd/test/EthereumGroupPCD.spec.ts @@ -0,0 +1,310 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { ArgumentTypeName } from "@pcd/pcd-types"; +import { + SemaphoreIdentityPCD, + SemaphoreIdentityPCDPackage, + SemaphoreIdentityPCDTypeName, +} from "@pcd/semaphore-identity-pcd"; +import { Poseidon, Tree } from "@personaelabs/spartan-ecdsa"; +import { Identity } from "@semaphore-protocol/identity"; +import assert from "assert"; +import { ethers } from "ethers"; +import JSONBig from "json-bigint"; +import "mocha"; +import * as path from "path"; +import { + EthereumGroupPCD, + EthereumGroupPCDPackage, + getRawPubKeyBuffer, + GroupType, +} from "../src/EthereumGroupPCD"; + +const zkeyFilePath: string = path.join(__dirname, "../artifacts/16.zkey"); +const wasmFilePath: string = path.join(__dirname, "../artifacts/16.wasm"); + +async function groupProof( + identity: SemaphoreIdentityPCD, + wallet: ethers.Wallet, + groupType: GroupType = GroupType.PUBLICKEY +) { + const signatureOfIdentityCommitment = await wallet.signMessage( + identity.claim.identity.commitment.toString() + ); + + const msgHash = Buffer.from( + ethers.utils + .hashMessage(identity.claim.identity.commitment.toString()) + .slice(2), + "hex" + ); + + const poseidon = new Poseidon(); + await poseidon.initWasm(); + const treeDepth = 20; // Provided circuits have tree depth = 20 + const tree = new Tree(treeDepth, poseidon); + + // Add some IDs to the tree before the prover's public key + const randM = Math.floor(Math.random() * 10) + 1; + for (let i = 0; i < randM; i++) { + const otherWallet = ethers.Wallet.createRandom(); + tree.insert( + groupType == GroupType.ADDRESS + ? BigInt(otherWallet.address) + : poseidon.hashPubKey(getRawPubKeyBuffer(otherWallet.publicKey)) + ); + } + // Add the prover's ID to the tree + const proverPubkeyBuffer: Buffer = getRawPubKeyBuffer(wallet.publicKey); + tree.insert( + groupType == GroupType.ADDRESS + ? BigInt(wallet.address) + : poseidon.hashPubKey(proverPubkeyBuffer) + ); + + // Add some IDs to the tree after the prover's public key + const randN = Math.floor(Math.random() * 10) + 1; + for (let i = 0; i < randN; i++) { + const otherWallet = ethers.Wallet.createRandom(); + tree.insert( + groupType == GroupType.ADDRESS + ? BigInt(otherWallet.address) + : poseidon.hashPubKey(getRawPubKeyBuffer(otherWallet.publicKey)) + ); + } + + // Get the index of the prover's public key in the tree + const idIndex = tree.indexOf( + groupType == GroupType.ADDRESS + ? BigInt(wallet.address) + : poseidon.hashPubKey(proverPubkeyBuffer) + ); + + // Prove membership of the prover's public key in the tree + const merkleProof = tree.createProof(idIndex); + return { + signatureOfIdentityCommitment, + msgHash, + merkleProof, + }; +} + +async function happyPathEthGroupPCD(groupType: GroupType) { + const identity = await SemaphoreIdentityPCDPackage.prove({ + identity: new Identity(), + }); + const serializedIdentity = await SemaphoreIdentityPCDPackage.serialize( + identity + ); + const wallet = ethers.Wallet.createRandom(); + const { signatureOfIdentityCommitment, merkleProof } = await groupProof( + identity, + wallet, + groupType + ); + + const ethGroupPCD = await EthereumGroupPCDPackage.prove({ + merkleProof: { + argumentType: ArgumentTypeName.String, + value: JSONBig({ useNativeBigInt: true }).stringify(merkleProof), + }, + identity: { + argumentType: ArgumentTypeName.PCD, + pcdType: SemaphoreIdentityPCDTypeName, + value: serializedIdentity, + }, + signatureOfIdentityCommitment: { + argumentType: ArgumentTypeName.String, + value: signatureOfIdentityCommitment, + }, + groupType: { + argumentType: ArgumentTypeName.String, + value: groupType, + }, + }); + + return ethGroupPCD; +} + +describe("Ethereum Group PCD", function () { + let ethGroupPCD: EthereumGroupPCD; + this.timeout(2 * 60 * 1000); + + this.beforeAll(async function () { + await EthereumGroupPCDPackage.init!({ + zkeyFilePath, + wasmFilePath, + }); + ethGroupPCD = await happyPathEthGroupPCD(GroupType.PUBLICKEY); + }); + + it("should work", async function () { + assert(await EthereumGroupPCDPackage.verify(ethGroupPCD)); + }); + + it("should work with address (slow ~40s)", async function () { + const ethGroupPCD = await happyPathEthGroupPCD(GroupType.ADDRESS); + assert(await EthereumGroupPCDPackage.verify(ethGroupPCD)); + }); + + it("serializes", async function () { + const newEthGroupPCD = await EthereumGroupPCDPackage.deserialize( + ( + await EthereumGroupPCDPackage.serialize(ethGroupPCD) + ).pcd + ); + assert(await EthereumGroupPCDPackage.verify(newEthGroupPCD)); + }); + + it("should not verify tampered inputs", async function () { + const newEthGroupPCD = await EthereumGroupPCDPackage.deserialize( + ( + await EthereumGroupPCDPackage.serialize(ethGroupPCD) + ).pcd + ); + assert(await EthereumGroupPCDPackage.verify(newEthGroupPCD)); + + // Tamper + newEthGroupPCD.claim.publicInput.circuitPubInput.merkleRoot = + newEthGroupPCD.claim.publicInput.circuitPubInput.merkleRoot + BigInt(1); + + assert(!(await EthereumGroupPCDPackage.verify(newEthGroupPCD))); + + // Untamper + newEthGroupPCD.claim.publicInput.circuitPubInput.merkleRoot = + newEthGroupPCD.claim.publicInput.circuitPubInput.merkleRoot - BigInt(1); + + assert(await EthereumGroupPCDPackage.verify(newEthGroupPCD)); + + // Tamper + newEthGroupPCD.claim.publicInput.circuitPubInput.Tx = + newEthGroupPCD.claim.publicInput.circuitPubInput.Tx + BigInt(1); + + assert(!(await EthereumGroupPCDPackage.verify(newEthGroupPCD))); + + // Untamper + newEthGroupPCD.claim.publicInput.circuitPubInput.Tx = + newEthGroupPCD.claim.publicInput.circuitPubInput.Tx - BigInt(1); + + assert(await EthereumGroupPCDPackage.verify(newEthGroupPCD)); + + // Tamper + newEthGroupPCD.claim.publicInput.circuitPubInput.Ty = + newEthGroupPCD.claim.publicInput.circuitPubInput.Ty + BigInt(1); + + assert(!(await EthereumGroupPCDPackage.verify(newEthGroupPCD))); + + // Untamper + newEthGroupPCD.claim.publicInput.circuitPubInput.Ty = + newEthGroupPCD.claim.publicInput.circuitPubInput.Ty - BigInt(1); + + assert(await EthereumGroupPCDPackage.verify(newEthGroupPCD)); + + // Tamper + newEthGroupPCD.claim.publicInput.circuitPubInput.Ux = + newEthGroupPCD.claim.publicInput.circuitPubInput.Ux + BigInt(1); + + assert(!(await EthereumGroupPCDPackage.verify(newEthGroupPCD))); + + // Untamper + newEthGroupPCD.claim.publicInput.circuitPubInput.Ux = + newEthGroupPCD.claim.publicInput.circuitPubInput.Ux - BigInt(1); + + assert(await EthereumGroupPCDPackage.verify(newEthGroupPCD)); + + // Tamper + newEthGroupPCD.claim.publicInput.circuitPubInput.Uy = + newEthGroupPCD.claim.publicInput.circuitPubInput.Uy + BigInt(1); + + assert(!(await EthereumGroupPCDPackage.verify(newEthGroupPCD))); + + // Untamper + newEthGroupPCD.claim.publicInput.circuitPubInput.Uy = + newEthGroupPCD.claim.publicInput.circuitPubInput.Uy - BigInt(1); + + assert(await EthereumGroupPCDPackage.verify(newEthGroupPCD)); + }); + + it("should not be able create a PCD with a different identity", async function () { + const identity1 = await SemaphoreIdentityPCDPackage.prove({ + identity: new Identity(), + }); + const wallet = ethers.Wallet.createRandom(); + const { signatureOfIdentityCommitment, merkleProof } = await groupProof( + identity1, + wallet + ); + + const identity2 = await SemaphoreIdentityPCDPackage.prove({ + identity: new Identity(), + }); + const serializedIdentity2 = await SemaphoreIdentityPCDPackage.serialize( + identity2 + ); + + await assert.rejects( + async () => + // This will output an error in the console that looks like this: + // ERROR: 4 Error in template PubKeyMembership_149 line: 45 + await EthereumGroupPCDPackage.prove({ + merkleProof: { + argumentType: ArgumentTypeName.String, + value: JSONBig({ useNativeBigInt: true }).stringify(merkleProof), + }, + identity: { + argumentType: ArgumentTypeName.PCD, + pcdType: SemaphoreIdentityPCDTypeName, + value: serializedIdentity2, + }, + groupType: { + argumentType: ArgumentTypeName.String, + value: GroupType.PUBLICKEY, + }, + signatureOfIdentityCommitment: { + argumentType: ArgumentTypeName.String, + value: signatureOfIdentityCommitment, + }, + }) + ); + }); + + it("should not be able to create a PCD with tampered merkle root", async function () { + const identity = await SemaphoreIdentityPCDPackage.prove({ + identity: new Identity(), + }); + const serializedIdentity = await SemaphoreIdentityPCDPackage.serialize( + identity + ); + const wallet = ethers.Wallet.createRandom(); + const { merkleProof, signatureOfIdentityCommitment } = await groupProof( + identity, + wallet + ); + + // Tamper with the merkle root + merkleProof.root = merkleProof.root + BigInt(1); + + await assert.rejects(async () => { + // This will output an error in the console that looks like this: + // ERROR: 4 Error in template PubKeyMembership_149 line: 45 + await EthereumGroupPCDPackage.prove({ + identity: { + argumentType: ArgumentTypeName.PCD, + pcdType: SemaphoreIdentityPCDTypeName, + value: serializedIdentity, + }, + groupType: { + argumentType: ArgumentTypeName.String, + value: GroupType.PUBLICKEY, + }, + signatureOfIdentityCommitment: { + argumentType: ArgumentTypeName.String, + value: signatureOfIdentityCommitment, + }, + merkleProof: { + argumentType: ArgumentTypeName.String, + value: JSONBig({ useNativeBigInt: true }).stringify(merkleProof), + }, + }); + }); + }); +}); diff --git a/packages/ethereum-group-pcd/tsconfig.json b/packages/ethereum-group-pcd/tsconfig.json new file mode 100644 index 0000000000..8796e9cd58 --- /dev/null +++ b/packages/ethereum-group-pcd/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@pcd/tsconfig/ts-library.json", + "include": [".", "./test"], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/packages/ethereum-ownership-pcd/package.json b/packages/ethereum-ownership-pcd/package.json index a329f249e7..21a927fcdf 100644 --- a/packages/ethereum-ownership-pcd/package.json +++ b/packages/ethereum-ownership-pcd/package.json @@ -23,7 +23,9 @@ "scripts": { "lint": "eslint \"**/*.ts{,x}\"", "build": "tsup --platform=browser src/index.ts --out-dir ./dist/browser && tsup --platform=node src/index.ts --out-dir ./dist/node", - "dev": "tsup --platform=browser src/index.ts --out-dir ./dist/browser --watch", + "dev": "concurrently \"yarn dev:browser\" \"yarn dev:node\"", + "dev:browser": "tsup --platform=browser src/index.ts --out-dir ./dist/browser --watch", + "dev:node": "tsup --platform=node src/index.ts --out-dir ./dist/browser --watch", "typecheck": "yarn tsc --noEmit", "test": "ts-mocha --config ../../.mocharc.js --exit test/**/*.spec.ts", "prepublishOnly": "yarn build" diff --git a/packages/ethereum-ownership-pcd/src/CardBody.tsx b/packages/ethereum-ownership-pcd/src/CardBody.tsx index 193e2c98ca..dbc75c93a7 100644 --- a/packages/ethereum-ownership-pcd/src/CardBody.tsx +++ b/packages/ethereum-ownership-pcd/src/CardBody.tsx @@ -10,7 +10,7 @@ import { useEffect, useState } from "react"; import styled from "styled-components"; import { EthereumOwnershipPCD } from "./EthereumOwnershipPCD"; -export function SemaphoreIdentityCardBody({ +export function EthereumOwnershipCardBody({ pcd, }: { pcd: EthereumOwnershipPCD; diff --git a/packages/ethereum-ownership-pcd/src/EthereumOwnershipPCD.ts b/packages/ethereum-ownership-pcd/src/EthereumOwnershipPCD.ts index f7d07715d8..fcbdf56620 100644 --- a/packages/ethereum-ownership-pcd/src/EthereumOwnershipPCD.ts +++ b/packages/ethereum-ownership-pcd/src/EthereumOwnershipPCD.ts @@ -17,30 +17,9 @@ import { SemaphoreSignaturePCDPackage, } from "@pcd/semaphore-signature-pcd"; import { ethers } from "ethers"; -import { sha256 } from "js-sha256"; import JSONBig from "json-bigint"; import { v4 as uuid } from "uuid"; -import { SemaphoreIdentityCardBody as EthereumOwnershipCardBody } from "./CardBody"; - -/** - * All signature PCDs are 'namespaced' to this pseudo-random nullifier, - * so that they cannot be reused by malicious actors across different - * applications. - */ -export const STATIC_ETH_PCD_NULLIFIER = generateMessageHash( - "hardcoded-nullifier" -); - -/** - * Hashes a message to be signed with sha256 and fits it into a baby jub jub field element. - * @param signal The initial message. - * @returns The outputted hash, fed in as a signal to the Semaphore proof. - */ -export function generateMessageHash(signal: string): bigint { - // right shift to fit into a field element, which is 254 bits long - // shift by 8 ensures we have a 253 bit element - return BigInt("0x" + sha256(signal)) >> BigInt(8); -} +import { EthereumOwnershipCardBody } from "./CardBody"; export const EthereumOwnershipPCDTypeName = "ethereum-ownership-pcd"; @@ -55,9 +34,6 @@ export interface EthereumOwnershipPCDInitArgs { wasmFilePath: string; } -// We hardcode the externalNullifer to also be your identityCommitment -// so that your nullifier for specific groups is not revealed when -// a SemaphoreSignaturePCD is requested from a consumer application. export interface EthereumOwnershipPCDArgs { identity: PCDArgument; ethereumAddress: StringArgument; @@ -121,10 +97,11 @@ export async function prove( const deserializedIdentity = await SemaphoreIdentityPCDPackage.deserialize( args.identity.value.pcd ); + const message = deserializedIdentity.claim.identity.commitment.toString(); const address = ethers.utils.getAddress( ethers.utils.verifyMessage( - deserializedIdentity.claim.identity.commitment.toString(), + message, args.ethereumSignatureOfCommitment.value ) ); diff --git a/packages/ethereum-ownership-pcd/test/EthereumOwnershipPCD.spec.ts b/packages/ethereum-ownership-pcd/test/EthereumOwnershipPCD.spec.ts index c9c56ea15a..f67b38c4e6 100644 --- a/packages/ethereum-ownership-pcd/test/EthereumOwnershipPCD.spec.ts +++ b/packages/ethereum-ownership-pcd/test/EthereumOwnershipPCD.spec.ts @@ -25,7 +25,7 @@ describe("Ethereum ownership PCD", function () { }); it("should work", async function () { - const wallet = ethers.Wallet.createRandom(null); + const wallet = ethers.Wallet.createRandom(); const identity = await SemaphoreIdentityPCDPackage.prove({ identity: new Identity(), }); @@ -52,11 +52,11 @@ describe("Ethereum ownership PCD", function () { }, }); - await EthereumOwnershipPCDPackage.verify(ethereumPCD); + assert(await EthereumOwnershipPCDPackage.verify(ethereumPCD)); }); it("should not be able create a PCD from an invalid signature", async function () { - const wallet = ethers.Wallet.createRandom(null); + const wallet = ethers.Wallet.createRandom(); const identity = await SemaphoreIdentityPCDPackage.prove({ identity: new Identity(), }); @@ -92,38 +92,43 @@ describe("Ethereum ownership PCD", function () { }); it("should not be able create a PCD where identity does not match identity pcd", async function () { - const wallet = ethers.Wallet.createRandom(null); + const wallet = ethers.Wallet.createRandom(); const identity = await SemaphoreIdentityPCDPackage.prove({ identity: new Identity(), }); - const serializedIdentity = await SemaphoreIdentityPCDPackage.serialize( - identity - ); const signatureOfIdentityCommitment = await wallet.signMessage( identity.claim.identity.commitment.toString() ); - assert.rejects(() => - EthereumOwnershipPCDPackage.prove({ - ethereumAddress: { - argumentType: ArgumentTypeName.String, - value: wallet.address, - }, - ethereumSignatureOfCommitment: { - argumentType: ArgumentTypeName.String, - value: signatureOfIdentityCommitment, - }, - identity: { - argumentType: ArgumentTypeName.PCD, - pcdType: SemaphoreIdentityPCDTypeName, - value: serializedIdentity, - }, - }) + const identity2 = await SemaphoreIdentityPCDPackage.prove({ + identity: new Identity(), + }); + const serializedIdentity2 = await SemaphoreIdentityPCDPackage.serialize( + identity2 + ); + + await assert.rejects( + async () => + await EthereumOwnershipPCDPackage.prove({ + ethereumAddress: { + argumentType: ArgumentTypeName.String, + value: wallet.address, + }, + ethereumSignatureOfCommitment: { + argumentType: ArgumentTypeName.String, + value: signatureOfIdentityCommitment, + }, + identity: { + argumentType: ArgumentTypeName.PCD, + pcdType: SemaphoreIdentityPCDTypeName, + value: serializedIdentity2, + }, + }) ); }); it("should not be able verify a PCD whose Ethereum address was tampered with", async function () { - const wallet = ethers.Wallet.createRandom(null); + const wallet = ethers.Wallet.createRandom(); const identity = await SemaphoreIdentityPCDPackage.prove({ identity: new Identity(), }); @@ -158,6 +163,6 @@ describe("Ethereum ownership PCD", function () { pcd.claim.ethereumAddress = mangledAddress; - assert.rejects(EthereumOwnershipPCDPackage.verify(pcd)); + assert(!(await EthereumOwnershipPCDPackage.verify(pcd))); }); }); diff --git a/yarn.lock b/yarn.lock index 59e01ba012..36926f0272 100644 --- a/yarn.lock +++ b/yarn.lock @@ -806,6 +806,11 @@ crc-32 "^1.2.0" ethereumjs-util "^7.1.5" +"@ethereumjs/rlp@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" + integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== + "@ethereumjs/tx@3.3.2": version "3.3.2" resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.3.2.tgz#348d4624bf248aaab6c44fec2ae67265efe3db00" @@ -814,6 +819,15 @@ "@ethereumjs/common" "^2.5.0" ethereumjs-util "^7.1.2" +"@ethereumjs/util@^8.0.3": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-8.1.0.tgz#299df97fb6b034e0577ce9f94c7d9d1004409ed4" + integrity sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA== + dependencies: + "@ethereumjs/rlp" "^4.0.1" + ethereum-cryptography "^2.0.0" + micro-ftch "^0.3.1" + "@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.6.3", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" @@ -1640,6 +1654,18 @@ resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.10.tgz#44dd9eea943ed14a1012edd5011b8e905f5e6fc4" integrity sha512-g2+tU63yTWmcVQKDGY0MV1PjjqgZtwM4rB1oVVi/v0brdZAcrcTV+04agKzWtvWroyFz6IqtT0MoZJA7PNyLVw== +"@noble/curves@1.1.0", "@noble/curves@~1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" + integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA== + dependencies: + "@noble/hashes" "1.3.1" + +"@noble/hashes@1.3.1", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" + integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -2463,6 +2489,26 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.8.0.tgz#fe2aa90e6df050a11cd57f5c0f47b0641fd2cad3" integrity sha512-TYh1MRcm4JnvpqtqOwT9WYaBYY4KERHdToxs/suDTLviGRsQkIjS5yYROTYTSJQUnYLOn/TuOh5GoMwfLSU+Ew== +"@pcd/eslint-config-custom@0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@pcd/eslint-config-custom/-/eslint-config-custom-0.5.2.tgz#b1c6da66dd1ee6ce05540afcf6fc84e1635550e2" + integrity sha512-GwqmjzSoyR6XUKfnvIWjw/xPD01XLhgh/sB0H258eHvS1RubCqorfOLCpyOZFzglPGaEG8ylp+lGvh+/NO1frw== + dependencies: + eslint "^7.23.0" + eslint-config-next "13.0.0" + eslint-config-prettier "^8.3.0" + eslint-config-turbo latest + eslint-plugin-react "7.31.8" + +"@pcd/passport-ui@0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@pcd/passport-ui/-/passport-ui-0.5.3.tgz#5441afc6dba40e8cfe32ab4da7d026abf3617aab" + integrity sha512-Pwq5CIF/JjmbMibQVzYr0laT8KX04tN3AqQgcEkRAij6pBt3HMJw//fD/U4f/184y/lqx+k3zosmhg5+vQ8/yQ== + dependencies: + "@pcd/pcd-types" "0.5.3" + react "^18.2.0" + styled-components "^5.3.9" + "@pcd/passport-ui@^0.4.1": version "0.4.2" resolved "https://registry.yarnpkg.com/@pcd/passport-ui/-/passport-ui-0.4.2.tgz#ed12bc67eef713cf1b1d5b6a39c825067c0fe226" @@ -2487,6 +2533,26 @@ "@types/react" "^18.0.22" typescript "^4.9.5" +"@pcd/pcd-types@0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@pcd/pcd-types/-/pcd-types-0.5.3.tgz#e40664b9cc0474767e5a9399c773f98421e2aa4a" + integrity sha512-5BQ3LCgNMn6gkoqmP4YxceObenkyNWG+bVp0hQSz4eCp2P6hGsHhRtt2oKSp5LnnLaWHss3wyqBaTC6EfuG9ow== + +"@pcd/semaphore-identity-pcd@0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@pcd/semaphore-identity-pcd/-/semaphore-identity-pcd-0.5.3.tgz#cae4013881a3e396e2567d3c27540dc0389f7172" + integrity sha512-rkSEqmDOmfpY4ygVg8qdRsmK/LRPvy0uBruHFsaPK3XCa1FNGfhVUnhbvdAZSFiZDfguRbkmqMfsHR5DXZ+HZA== + dependencies: + "@pcd/passport-ui" "0.5.3" + "@pcd/pcd-types" "0.5.3" + "@semaphore-protocol/group" "^3.10.0" + "@semaphore-protocol/identity" "^3.10.0" + "@semaphore-protocol/proof" "^3.10.0" + json-bigint "^1.0.0" + react "^18.2.0" + styled-components "^5.3.9" + uuid "^9.0.0" + "@pcd/semaphore-identity-pcd@^0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@pcd/semaphore-identity-pcd/-/semaphore-identity-pcd-0.1.0.tgz#1a17c5d5eaa99bc8b96b2ebefa106d4140600b9b" @@ -2499,11 +2565,33 @@ json-bigint "^1.0.0" uuid "^9.0.0" +"@pcd/semaphore-signature-pcd@0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@pcd/semaphore-signature-pcd/-/semaphore-signature-pcd-0.5.3.tgz#8f869f0af3fb5607db3f2054bb27936ec5a15b93" + integrity sha512-LWU300/kQ1R9Jxmav81a3QV1hXYeLjdcpg3N68YEAnKPyQ+l8Jqr4L4LVxNtFfiBMWUmPSR5dyWzD2cHoUDchQ== + dependencies: + "@pcd/passport-ui" "0.5.3" + "@pcd/pcd-types" "0.5.3" + "@pcd/semaphore-identity-pcd" "0.5.3" + "@semaphore-protocol/group" "^3.10.0" + "@semaphore-protocol/identity" "^3.10.0" + "@semaphore-protocol/proof" "^3.10.0" + js-sha256 "^0.9.0" + json-bigint "^1.0.0" + react "^18.2.0" + styled-components "^5.3.9" + uuid "^9.0.0" + "@pcd/tsconfig@0.4.2": version "0.4.2" resolved "https://registry.yarnpkg.com/@pcd/tsconfig/-/tsconfig-0.4.2.tgz#ea0121cf14f5f5d4a9e59cec864d9d0d1666a02e" integrity sha512-bxeNIxvg+kHvrUn2bT6cnUYxr9lumOol9KUUM6SlHXOFaNylEwNx3h9i089yHCIYx5Pc8NLeU2uHatwWPlstsg== +"@pcd/tsconfig@0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@pcd/tsconfig/-/tsconfig-0.5.2.tgz#afd1da532f8b3a54c3ea309736290fd2a4e91d2c" + integrity sha512-bi7S+dt2HGZMVHmY5DbRXrkbSvkghF1P21H+ctIBY6htUp8Z8Zu98zK5AJBesx4kdgCES8A5Os6ai17nhnbc2w== + "@peculiar/asn1-android@^2.3.3": version "2.3.6" resolved "https://registry.yarnpkg.com/@peculiar/asn1-android/-/asn1-android-2.3.6.tgz#20363c23bc5b9a91f7ffd80d7c3842dccff8c20b" @@ -2553,6 +2641,16 @@ pvtsutils "^1.3.2" tslib "^2.4.0" +"@personaelabs/spartan-ecdsa@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@personaelabs/spartan-ecdsa/-/spartan-ecdsa-2.1.4.tgz#d566706e46b040fee69095c89bbd9929956f92f5" + integrity sha512-nqArRr1m5bjQOKKPaf+tSumfQ8sfIH021fhsmsy8epccp95/gM955VsgUSNSHcLTddHPrO5lI14oDOsXALPGYQ== + dependencies: + "@ethereumjs/util" "^8.0.3" + "@zk-kit/incremental-merkle-tree" "^1.0.0" + elliptic "^6.5.4" + snarkjs "^0.5.0" + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -2606,10 +2704,10 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== -"@remix-run/router@1.7.1": - version "1.7.1" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.1.tgz#fea7ac35ae4014637c130011f59428f618730498" - integrity sha512-bgVQM4ZJ2u2CM8k1ey70o1ePFXsEzYVZoWghh6WjM8p59jQ7HxzbHW4SbnWFG7V9ig9chLawQxDTZ3xzOF8MkQ== +"@remix-run/router@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.2.tgz#cba1cf0a04bc04cb66027c51fa600e9cbc388bc8" + integrity sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A== "@rollbar/react@^0.11.1": version "0.11.2" @@ -2641,6 +2739,28 @@ resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.5.1.tgz#254521c188b49e8b2d4cc048b475fb2b38737fec" integrity sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA== +"@scure/base@~1.1.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" + integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== + +"@scure/bip32@1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.1.tgz#7248aea723667f98160f593d621c47e208ccbb10" + integrity sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A== + dependencies: + "@noble/curves" "~1.1.0" + "@noble/hashes" "~1.3.1" + "@scure/base" "~1.1.0" + +"@scure/bip39@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.1.tgz#5cee8978656b272a917b7871c981e0541ad6ac2a" + integrity sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg== + dependencies: + "@noble/hashes" "~1.3.0" + "@scure/base" "~1.1.0" + "@semaphore-protocol/group@^3.1.0", "@semaphore-protocol/group@^3.10.0": version "3.10.1" resolved "https://registry.yarnpkg.com/@semaphore-protocol/group/-/group-3.10.1.tgz#c8aa42780379579d2887744fee49e25e782ea8c4" @@ -3618,7 +3738,7 @@ resolved "https://registry.yarnpkg.com/@zk-kit/incremental-merkle-tree/-/incremental-merkle-tree-0.4.3.tgz#197f72102d35dd9c546a6a432896d6d6f6de05f4" integrity sha512-2qHfrJXtPx8/UmF0wFAUr4VqCLr3J/P859fk/e3fwKLUnf3baeIUAO6inY4wrh0NGy4bzpKUWYjDph0yTbPz6A== -"@zk-kit/incremental-merkle-tree@1.1.0": +"@zk-kit/incremental-merkle-tree@1.1.0", "@zk-kit/incremental-merkle-tree@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@zk-kit/incremental-merkle-tree/-/incremental-merkle-tree-1.1.0.tgz#6b0ccce56f9e05ccf73f7ea4fa746d5ef7d24b1d" integrity sha512-WnNR/GQse3lX8zOHMU8zwhgX8u3qPoul8w4GjJ0WDHq+VGJimo7EGheRZ/ILeBQabnlzAerdv3vBqYBehBeoKA== @@ -4737,6 +4857,13 @@ circom_runtime@0.1.20: dependencies: ffjavascript "0.2.55" +circom_runtime@0.1.21: + version "0.1.21" + resolved "https://registry.yarnpkg.com/circom_runtime/-/circom_runtime-0.1.21.tgz#0ee93bb798b5afb8ecec30725ed14d94587a999b" + integrity sha512-qTkud630B/GK8y76hnOaaS1aNuF6prfV0dTrkeRsiJKnlP1ryQbP2FWLgDOPqn6aKyaPlam+Z+DTbBhkEzh8dA== + dependencies: + ffjavascript "0.2.56" + "circomlib@git+https://github.com/weijiekoh/circomlib.git#24ed08eee0bb613b8c0135d66c1013bd9f78d50a": version "0.5.2" resolved "git+https://github.com/weijiekoh/circomlib.git#24ed08eee0bb613b8c0135d66c1013bd9f78d50a" @@ -5398,9 +5525,9 @@ ejs@^3.0.1, ejs@^3.1.6: jake "^10.8.5" electron-to-chromium@^1.4.431: - version "1.4.461" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.461.tgz#6b14af66042732bf883ab63a4d82cac8f35eb252" - integrity sha512-1JkvV2sgEGTDXjdsaQCeSwYYuhLRphRpc+g6EHTFELJXEiznLt3/0pZ9JuAOQ5p2rI3YxKTbivtvajirIfhrEQ== + version "1.4.462" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.462.tgz#4faf5072bb5f55269d35ca9dc7475e7bf91b1ac3" + integrity sha512-ux2LqN9JKRBDKXMT+78jtiBLPiXf+rLtYlsrOg5Qn7uv6Cbg7+9JyIalE3wcqkOdB2wPCUYNWAuL7suKRMHe9w== elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.4: version "6.5.4" @@ -5675,11 +5802,11 @@ eslint-config-prettier@^8.3.0: integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA== eslint-config-turbo@latest: - version "1.10.7" - resolved "https://registry.yarnpkg.com/eslint-config-turbo/-/eslint-config-turbo-1.10.7.tgz#6e353944b04f20f964f6cc7a9b82e65b474eb222" - integrity sha512-0yHt5UlXVph8S4SOvP6gYehLvYjJj6XFKTYOG/WUQbjlcF0OU4pOT1a1juqmmBPWYlvJ0evt7v+RekY4tOopPQ== + version "1.10.8" + resolved "https://registry.yarnpkg.com/eslint-config-turbo/-/eslint-config-turbo-1.10.8.tgz#d1360d3fb0e824e050f6ce1adc98605081125435" + integrity sha512-fDJTyM04N9XUcvYFlM4r7Byvjx27TuO68iOnMNjtKF7vBtrJuBBBGtNM0aQIWGg0oTqjjcNTNQw9sQK+IcA5Gw== dependencies: - eslint-plugin-turbo "1.10.7" + eslint-plugin-turbo "1.10.8" eslint-import-resolver-node@^0.3.6, eslint-import-resolver-node@^0.3.7: version "0.3.7" @@ -5797,10 +5924,10 @@ eslint-plugin-react@^7.31.7, eslint-plugin-react@^7.32.2: semver "^6.3.0" string.prototype.matchall "^4.0.8" -eslint-plugin-turbo@1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/eslint-plugin-turbo/-/eslint-plugin-turbo-1.10.7.tgz#393619177155e324040e6f2c3393410a394f3339" - integrity sha512-YikBHc75DY9VV1vAFUIBekHLQlxqVT5zTNibK8zBQInCUhF7PvyPJc0xXw5FSz8EYtt4uOV3r0Km3CmFRclS4Q== +eslint-plugin-turbo@1.10.8: + version "1.10.8" + resolved "https://registry.yarnpkg.com/eslint-plugin-turbo/-/eslint-plugin-turbo-1.10.8.tgz#2a83c660b1b40d0cafc810088bfaee3c64293bf6" + integrity sha512-0ULCoR0Zj/ec4mjmfeYhDa5OtqCvSgDkQdSD/tqLUSHM0GzcUrNvGPclVmsoCb5kmawzeqtlqnS2FKILc862qw== dependencies: dotenv "16.0.3" @@ -5990,6 +6117,16 @@ ethereum-cryptography@^0.1.3: secp256k1 "^4.0.1" setimmediate "^1.0.5" +ethereum-cryptography@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz#18fa7108622e56481157a5cb7c01c0c6a672eb67" + integrity sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug== + dependencies: + "@noble/curves" "1.1.0" + "@noble/hashes" "1.3.1" + "@scure/bip32" "1.3.1" + "@scure/bip39" "1.2.1" + ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.1, ethereumjs-util@^7.1.2, ethereumjs-util@^7.1.5: version "7.1.5" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" @@ -6326,6 +6463,15 @@ ffjavascript@0.2.55: wasmcurves "0.1.0" web-worker "^1.2.0" +ffjavascript@0.2.56: + version "0.2.56" + resolved "https://registry.yarnpkg.com/ffjavascript/-/ffjavascript-0.2.56.tgz#3509f98fcbd3e44ea93cd23519071b76d6eae433" + integrity sha512-em6G5Lrj7ucIqj4TYEgyoHs/j99Urwwqa4+YxEVY2hggnpRimVj+noX5pZQTxI1pvtiekZI4rG65JBf0xraXrg== + dependencies: + wasmbuilder "0.0.16" + wasmcurves "0.2.0" + web-worker "^1.2.0" + ffjavascript@^0.2.38, ffjavascript@^0.2.39, ffjavascript@^0.2.48, ffjavascript@^0.2.57: version "0.2.59" resolved "https://registry.yarnpkg.com/ffjavascript/-/ffjavascript-0.2.59.tgz#b2f836082587fab333dfb181b909a188f80036f3" @@ -8445,6 +8591,11 @@ methods@^1.1.2, methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== +micro-ftch@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/micro-ftch/-/micro-ftch-0.3.1.tgz#6cb83388de4c1f279a034fb0cf96dfc050853c5f" + integrity sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg== + micromatch@^4.0.2, micromatch@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" @@ -9812,6 +9963,16 @@ r1csfile@0.0.40: fastfile "0.0.20" ffjavascript "0.2.55" +r1csfile@0.0.41: + version "0.0.41" + resolved "https://registry.yarnpkg.com/r1csfile/-/r1csfile-0.0.41.tgz#e3d2709d36923156dd1fc2db9858987b30c92948" + integrity sha512-Q1WDF3u1vYeAwjHo4YuddkA8Aq0TulbKjmGm99+Atn13Lf5fTsMZBnBV9T741w8iSyPFG6Uh6sapQby77sREqA== + dependencies: + "@iden3/bigarray" "0.0.2" + "@iden3/binfileutils" "0.0.11" + fastfile "0.0.20" + ffjavascript "0.2.56" + ramda@^0.27.0: version "0.27.2" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.2.tgz#84463226f7f36dc33592f6f4ed6374c48306c3f1" @@ -9887,19 +10048,19 @@ react-qr-reader@^3.0.0-beta-1: rollup "^2.67.2" react-router-dom@^6.9.0: - version "6.14.1" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.14.1.tgz#0ad7ba7abdf75baa61169d49f096f0494907a36f" - integrity sha512-ssF6M5UkQjHK70fgukCJyjlda0Dgono2QGwqGvuk7D+EDGHdacEN3Yke2LTMjkrpHuFwBfDFsEjGVXBDmL+bWw== + version "6.14.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.14.2.tgz#88f520118b91aa60233bd08dbd3fdcaea3a68488" + integrity sha512-5pWX0jdKR48XFZBuJqHosX3AAHjRAzygouMTyimnBPOLdY3WjzUSKhus2FVMihUFWzeLebDgr4r8UeQFAct7Bg== dependencies: - "@remix-run/router" "1.7.1" - react-router "6.14.1" + "@remix-run/router" "1.7.2" + react-router "6.14.2" -react-router@6.14.1: - version "6.14.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.14.1.tgz#5e82bcdabf21add859dc04b1859f91066b3a5810" - integrity sha512-U4PfgvG55LdvbQjg5Y9QRWyVxIdO1LlpYT7x+tMAxd9/vmiPuJhIwdxZuIQLN/9e3O4KFDHYfR9gzGeYMasW8g== +react-router@6.14.2: + version "6.14.2" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.14.2.tgz#1f60994d8c369de7b8ba7a78d8f7ec23df76b300" + integrity sha512-09Zss2dE2z+T1D03IheqAFtK4UzQyX8nFPWx6jkwdYzGLXd5ie06A6ezS2fO6zJfEb/SpG6UocN2O1hfD+2urQ== dependencies: - "@remix-run/router" "1.7.1" + "@remix-run/router" "1.7.2" react@18.2.0, react@^18.2.0: version "18.2.0" @@ -10594,6 +10755,22 @@ snarkjs@^0.4.22: logplease "^1.2.15" r1csfile "0.0.40" +snarkjs@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/snarkjs/-/snarkjs-0.5.0.tgz#cf26bf1d3835eb16b4b330a438bad9824837d6b0" + integrity sha512-KWz8mZ2Y+6wvn6GGkQo6/ZlKwETdAGohd40Lzpwp5TUZCn6N6O4Az1SuX1rw/qREGL6Im+ycb19suCFE8/xaKA== + dependencies: + "@iden3/binfileutils" "0.0.11" + bfj "^7.0.2" + blake2b-wasm "^2.4.0" + circom_runtime "0.1.21" + ejs "^3.1.6" + fastfile "0.0.20" + ffjavascript "0.2.56" + js-sha3 "^0.8.0" + logplease "^1.2.15" + r1csfile "0.0.41" + socks-proxy-agent@5, socks-proxy-agent@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz#032fb583048a29ebffec2e6a73fca0761f48177e" @@ -11378,47 +11555,47 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -turbo-darwin-64@1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.10.7.tgz#bf270105effd337c91ea1c81ae3a1678c0c95023" - integrity sha512-N2MNuhwrl6g7vGuz4y3fFG2aR1oCs0UZ5HKl8KSTn/VC2y2YIuLGedQ3OVbo0TfEvygAlF3QGAAKKtOCmGPNKA== +turbo-darwin-64@1.10.8: + version "1.10.8" + resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.10.8.tgz#62d8f47e42fccb9ddadb69c13c4fad510f9c0585" + integrity sha512-FOK3qrLZE2Yq7/2DkAnAzghisGQroZJs85Rui3IXM/2e7rTtBADmU9w36d4k0Yw7RHEiOo8U4eAYUl52OWRwJQ== -turbo-darwin-arm64@1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.10.7.tgz#29aa580dfda9a17bcbcce86c84e9144ceacbfaab" - integrity sha512-WbJkvjU+6qkngp7K4EsswOriO3xrNQag7YEGRtfLoDdMTk4O4QTeU6sfg2dKfDsBpTidTvEDwgIYJhYVGzrz9Q== +turbo-darwin-arm64@1.10.8: + version "1.10.8" + resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.10.8.tgz#537db8024d2a43a5579ac43632fb7c3b18c6cbaf" + integrity sha512-8mbgH8oBycusa8RnbHlvrpHxfZsgNrk6CXMu/KJECpajYT3nSOMK2Rrs+422HqLDTVUw4GAqmTr26nUx8yJoyA== -turbo-linux-64@1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.10.7.tgz#25bb73f2c9644d187fac87804eaf8d1c8fffcb69" - integrity sha512-x1CF2CDP1pDz/J8/B2T0hnmmOQI2+y11JGIzNP0KtwxDM7rmeg3DDTtDM/9PwGqfPotN9iVGgMiMvBuMFbsLhg== +turbo-linux-64@1.10.8: + version "1.10.8" + resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.10.8.tgz#58f0fa8bbd1d148443bc787c4458f7821b2650e4" + integrity sha512-eJ1ND3LuILw28gd+9f3Ews7Eika9WOxp+/PxJI+EPHseTrbLMLYqSPAunmZdOx840Pq0Sk5j4Nik7NCzuCWXkg== -turbo-linux-arm64@1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.10.7.tgz#dac37b50d37d1168a7bda5333499482a78eb78fe" - integrity sha512-JtnBmaBSYbs7peJPkXzXxsRGSGBmBEIb6/kC8RRmyvPAMyqF8wIex0pttsI+9plghREiGPtRWv/lfQEPRlXnNQ== +turbo-linux-arm64@1.10.8: + version "1.10.8" + resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.10.8.tgz#1fbd620fea6fcf778fb55c7d15ffa15243e31ea0" + integrity sha512-3+pVaOzGP/5GFvQakxuHDMsj43Y6bmaq5/84tvgGL0FgtKpsQvBfdaDs12HX5cb/zUnd2/jdQPNiGJwVeC/McA== -turbo-windows-64@1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.10.7.tgz#3c31915cec88395aaffb457532e39e33bd1407b4" - integrity sha512-7A/4CByoHdolWS8dg3DPm99owfu1aY/W0V0+KxFd0o2JQMTQtoBgIMSvZesXaWM57z3OLsietFivDLQPuzE75w== +turbo-windows-64@1.10.8: + version "1.10.8" + resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.10.8.tgz#69de1c5149201a7dfcf3afae4e40331bee7a6a0c" + integrity sha512-LdryI+ZQsVrW4hWZw5G5vJz0syjWxyc0tnieZRefy+d9Ti1du/qCYLP0KQRgL9Yuh1klbH/tzmx70upGARgWKQ== -turbo-windows-arm64@1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.10.7.tgz#6f86f3bb5cc1faf7c61def19f1c98807335b39c0" - integrity sha512-D36K/3b6+hqm9IBAymnuVgyePktwQ+F0lSXr2B9JfAdFPBktSqGmp50JNC7pahxhnuCLj0Vdpe9RqfnJw5zATA== +turbo-windows-arm64@1.10.8: + version "1.10.8" + resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.10.8.tgz#9bb290f3c50c0e5ea0185c6e16fac39eb5385c21" + integrity sha512-whHnhM84KIa2Ly/fcw2Ujw2Rr/9wh8ynAdZ9bdvZoZKAbOr3tXKft0tmy50jQ6IsNr6Cj0XD4cuSTKhvqoGtYA== turbo@^1.8.5: - version "1.10.7" - resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.10.7.tgz#af0cf5963b2121577f264c31a7a196f0e8b00510" - integrity sha512-xm0MPM28TWx1e6TNC3wokfE5eaDqlfi0G24kmeHupDUZt5Wd0OzHFENEHMPqEaNKJ0I+AMObL6nbSZonZBV2HA== + version "1.10.8" + resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.10.8.tgz#132a3269580378be1c869c51572f790b8e79adec" + integrity sha512-lmPKkeRMC/3gjTVxICt93A8zAzjGjbZINdekjzivn4g/rOjpHVNuOuVANU5L4H4R1bzQr8FFvZNQeQaElOjz/Q== optionalDependencies: - turbo-darwin-64 "1.10.7" - turbo-darwin-arm64 "1.10.7" - turbo-linux-64 "1.10.7" - turbo-linux-arm64 "1.10.7" - turbo-windows-64 "1.10.7" - turbo-windows-arm64 "1.10.7" + turbo-darwin-64 "1.10.8" + turbo-darwin-arm64 "1.10.8" + turbo-linux-64 "1.10.8" + turbo-linux-arm64 "1.10.8" + turbo-windows-64 "1.10.8" + turbo-windows-arm64 "1.10.8" tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" @@ -11757,6 +11934,13 @@ wasmcurves@0.1.0: big-integer "^1.6.42" blakejs "^1.1.0" +wasmcurves@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/wasmcurves/-/wasmcurves-0.2.0.tgz#ccfc5a7d3778b6e0768b82a9336c80054f9bc0cf" + integrity sha512-3e2rbxdujOwaod657gxgmdhZNn+i1qKdHO3Y/bK+8E7bV8ttV/fu5FO4/WLBACF375cK0QDLOP+65Na63qYuWA== + dependencies: + wasmbuilder "0.0.16" + wasmcurves@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/wasmcurves/-/wasmcurves-0.2.1.tgz#416d15432a9c6a7b79ef6000eab1e8e7302624ad"