diff --git a/.env.example b/.env.example index 45192856a..fe81d3d32 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,10 @@ DEFAULT_NETWORK=hardhat -TREE_DEPTH=20 -ALL_SNARK_ARTIFACTS=true +TREE_DEPTH=10 REPORT_GAS=false BACKEND_PRIVATE_KEY= INFURA_API_KEY= COINMARKETCAP_API_KEY= ETHERSCAN_API_KEY= +DEFENDER_KEY= +DEFENDER_SECRET= +CREATE2_SALT=1234 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index fb592714d..30bb17b98 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -10,25 +10,13 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: - node-version: 16.x - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn config get cacheFolder)" - - - name: Restore yarn cache - uses: actions/cache@v3 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- + node-version: 20 + cache: yarn - name: Install dependencies run: yarn diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index f1d3a5133..56cc0e4e4 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -6,33 +6,20 @@ on: - main env: - TREE_DEPTH: 20 - ALL_SNARK_ARTIFACTS: false + TREE_DEPTH: 10 jobs: style: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: - node-version: 16.x - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn config get cacheFolder)" - - - name: Restore yarn cache - uses: actions/cache@v3 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- + node-version: 20 + cache: yarn - name: Install dependencies run: yarn @@ -56,25 +43,13 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: - node-version: 16.x - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn config get cacheFolder)" - - - name: Restore yarn cache - uses: actions/cache@v3 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- + node-version: 20 + cache: yarn - name: Install dependencies run: yarn @@ -90,29 +65,25 @@ jobs: strategy: matrix: type: + - circuits - libraries - contracts steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: - node-version: 16.x + node-version: 20 + cache: yarn - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn config get cacheFolder)" + # https://github.com/iden3/circuits/blob/8fffb6609ecad0b7bcda19bb908bdb544bdb3cf7/.github/workflows/main.yml#L18-L22 + - name: Setup Circom deps + run: sudo apt-get update && sudo apt-get install -y wget nlohmann-json3-dev libgmp-dev nasm g++ build-essential - - name: Restore yarn cache - uses: actions/cache@v3 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- + - name: Setup Circom + run: wget https://github.com/iden3/circom/releases/latest/download/circom-linux-amd64 && sudo mv ./circom-linux-amd64 /usr/bin/circom && sudo chmod +x /usr/bin/circom - name: Install dependencies run: yarn @@ -120,10 +91,11 @@ jobs: - name: Build libraries run: yarn build:libraries - - name: Test contracts and libraries + - name: Test libraries, contracts and circuits run: yarn test:${{ matrix.type }} - name: Coveralls + if: matrix.type != 'circuits' uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 9339ef1f0..c6d666c5c 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -4,33 +4,20 @@ on: pull_request: env: - TREE_DEPTH: 20 - ALL_SNARK_ARTIFACTS: false + TREE_DEPTH: 10 jobs: style: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: - node-version: 16.x - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn config get cacheFolder)" - - - name: Restore yarn cache - uses: actions/cache@v3 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- + node-version: 20 + cache: yarn - name: Install dependencies run: yarn @@ -54,25 +41,20 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: - node-version: 16.x + node-version: 20 + cache: yarn - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn config get cacheFolder)" + # https://github.com/iden3/circuits/blob/8fffb6609ecad0b7bcda19bb908bdb544bdb3cf7/.github/workflows/main.yml#L18-L22 + - name: Setup Circom deps + run: sudo apt-get update && sudo apt-get install -y wget nlohmann-json3-dev libgmp-dev nasm g++ build-essential - - name: Restore yarn cache - uses: actions/cache@v3 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- + - name: Setup Circom + run: wget https://github.com/iden3/circom/releases/latest/download/circom-linux-amd64 && sudo mv ./circom-linux-amd64 /usr/bin/circom && sudo chmod +x /usr/bin/circom - name: Install dependencies run: yarn @@ -83,5 +65,5 @@ jobs: - name: Build subgraph run: yarn build:subgraph - - name: Test contracts, libraries and subgraph + - name: Test contracts, libraries, circuits and subgraph run: yarn test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 22e4d190c..d2d89c282 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,27 +13,15 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 16.x - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn config get cacheFolder)" - - - name: Restore yarn cache - uses: actions/cache@v3 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- + node-version: 20 + cache: yarn - name: Install dependencies run: yarn diff --git a/.gitignore b/.gitignore index 3ba4537bc..53dc72094 100644 --- a/.gitignore +++ b/.gitignore @@ -73,9 +73,6 @@ dist artifacts cache typechain-types -packages/contracts/deployed-contracts/undefined.json -packages/contracts/deployed-contracts/hardhat.json -packages/contracts/deployed-contracts/localhost.json # Stores VSCode versions used for testing VSCode extensions .vscode-test diff --git a/.prettierignore b/.prettierignore index 1b01a7f17..6fb9c59a7 100644 --- a/.prettierignore +++ b/.prettierignore @@ -12,15 +12,6 @@ coverage.json artifacts cache typechain-types -packages/contracts/deployed-contracts/undefined.json -packages/contracts/deployed-contracts/hardhat.json -packages/contracts/deployed-contracts/localhost.json - -# circuits -circuits - -# contracts -Verifier*.sol # production dist diff --git a/.yarn/plugins/@yarnpkg/plugin-version.cjs.REMOVED.git-id b/.yarn/plugins/@yarnpkg/plugin-version.cjs.REMOVED.git-id deleted file mode 100644 index 25a0235b9..000000000 --- a/.yarn/plugins/@yarnpkg/plugin-version.cjs.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -87de4f440a77841135f97a187e09140c6d4e6ae2 \ No newline at end of file diff --git a/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs b/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs deleted file mode 100644 index b9044a014..000000000 --- a/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-disable */ -//prettier-ignore -module.exports = { -name: "@yarnpkg/plugin-workspace-tools", -factory: function (require) { -var plugin=(()=>{var wr=Object.create,me=Object.defineProperty,Sr=Object.defineProperties,vr=Object.getOwnPropertyDescriptor,Hr=Object.getOwnPropertyDescriptors,$r=Object.getOwnPropertyNames,et=Object.getOwnPropertySymbols,kr=Object.getPrototypeOf,tt=Object.prototype.hasOwnProperty,Tr=Object.prototype.propertyIsEnumerable;var rt=(e,t,r)=>t in e?me(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,B=(e,t)=>{for(var r in t||(t={}))tt.call(t,r)&&rt(e,r,t[r]);if(et)for(var r of et(t))Tr.call(t,r)&&rt(e,r,t[r]);return e},Q=(e,t)=>Sr(e,Hr(t)),Lr=e=>me(e,"__esModule",{value:!0});var K=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),Or=(e,t)=>{for(var r in t)me(e,r,{get:t[r],enumerable:!0})},Nr=(e,t,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of $r(t))!tt.call(e,n)&&n!=="default"&&me(e,n,{get:()=>t[n],enumerable:!(r=vr(t,n))||r.enumerable});return e},X=e=>Nr(Lr(me(e!=null?wr(kr(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var $e=K(te=>{"use strict";te.isInteger=e=>typeof e=="number"?Number.isInteger(e):typeof e=="string"&&e.trim()!==""?Number.isInteger(Number(e)):!1;te.find=(e,t)=>e.nodes.find(r=>r.type===t);te.exceedsLimit=(e,t,r=1,n)=>n===!1||!te.isInteger(e)||!te.isInteger(t)?!1:(Number(t)-Number(e))/Number(r)>=n;te.escapeNode=(e,t=0,r)=>{let n=e.nodes[t];!n||(r&&n.type===r||n.type==="open"||n.type==="close")&&n.escaped!==!0&&(n.value="\\"+n.value,n.escaped=!0)};te.encloseBrace=e=>e.type!=="brace"?!1:e.commas>>0+e.ranges>>0==0?(e.invalid=!0,!0):!1;te.isInvalidBrace=e=>e.type!=="brace"?!1:e.invalid===!0||e.dollar?!0:e.commas>>0+e.ranges>>0==0||e.open!==!0||e.close!==!0?(e.invalid=!0,!0):!1;te.isOpenOrClose=e=>e.type==="open"||e.type==="close"?!0:e.open===!0||e.close===!0;te.reduce=e=>e.reduce((t,r)=>(r.type==="text"&&t.push(r.value),r.type==="range"&&(r.type="text"),t),[]);te.flatten=(...e)=>{let t=[],r=n=>{for(let s=0;s{"use strict";var it=$e();at.exports=(e,t={})=>{let r=(n,s={})=>{let a=t.escapeInvalid&&it.isInvalidBrace(s),i=n.invalid===!0&&t.escapeInvalid===!0,o="";if(n.value)return(a||i)&&it.isOpenOrClose(n)?"\\"+n.value:n.value;if(n.value)return n.value;if(n.nodes)for(let h of n.nodes)o+=r(h);return o};return r(e)}});var ct=K((os,ot)=>{"use strict";ot.exports=function(e){return typeof e=="number"?e-e==0:typeof e=="string"&&e.trim()!==""?Number.isFinite?Number.isFinite(+e):isFinite(+e):!1}});var At=K((cs,ut)=>{"use strict";var lt=ct(),pe=(e,t,r)=>{if(lt(e)===!1)throw new TypeError("toRegexRange: expected the first argument to be a number");if(t===void 0||e===t)return String(e);if(lt(t)===!1)throw new TypeError("toRegexRange: expected the second argument to be a number.");let n=B({relaxZeros:!0},r);typeof n.strictZeros=="boolean"&&(n.relaxZeros=n.strictZeros===!1);let s=String(n.relaxZeros),a=String(n.shorthand),i=String(n.capture),o=String(n.wrap),h=e+":"+t+"="+s+a+i+o;if(pe.cache.hasOwnProperty(h))return pe.cache[h].result;let g=Math.min(e,t),f=Math.max(e,t);if(Math.abs(g-f)===1){let R=e+"|"+t;return n.capture?`(${R})`:n.wrap===!1?R:`(?:${R})`}let A=ft(e)||ft(t),p={min:e,max:t,a:g,b:f},k=[],y=[];if(A&&(p.isPadded=A,p.maxLen=String(p.max).length),g<0){let R=f<0?Math.abs(f):1;y=pt(R,Math.abs(g),p,n),g=p.a=0}return f>=0&&(k=pt(g,f,p,n)),p.negatives=y,p.positives=k,p.result=Ir(y,k,n),n.capture===!0?p.result=`(${p.result})`:n.wrap!==!1&&k.length+y.length>1&&(p.result=`(?:${p.result})`),pe.cache[h]=p,p.result};function Ir(e,t,r){let n=Pe(e,t,"-",!1,r)||[],s=Pe(t,e,"",!1,r)||[],a=Pe(e,t,"-?",!0,r)||[];return n.concat(a).concat(s).join("|")}function Mr(e,t){let r=1,n=1,s=ht(e,r),a=new Set([t]);for(;e<=s&&s<=t;)a.add(s),r+=1,s=ht(e,r);for(s=dt(t+1,n)-1;e1&&o.count.pop(),o.count.push(f.count[0]),o.string=o.pattern+gt(o.count),i=g+1;continue}r.isPadded&&(A=Gr(g,r,n)),f.string=A+f.pattern+gt(f.count),a.push(f),i=g+1,o=f}return a}function Pe(e,t,r,n,s){let a=[];for(let i of e){let{string:o}=i;!n&&!mt(t,"string",o)&&a.push(r+o),n&&mt(t,"string",o)&&a.push(r+o)}return a}function Pr(e,t){let r=[];for(let n=0;nt?1:t>e?-1:0}function mt(e,t,r){return e.some(n=>n[t]===r)}function ht(e,t){return Number(String(e).slice(0,-t)+"9".repeat(t))}function dt(e,t){return e-e%Math.pow(10,t)}function gt(e){let[t=0,r=""]=e;return r||t>1?`{${t+(r?","+r:"")}}`:""}function Dr(e,t,r){return`[${e}${t-e==1?"":"-"}${t}]`}function ft(e){return/^-?(0+)\d/.test(e)}function Gr(e,t,r){if(!t.isPadded)return e;let n=Math.abs(t.maxLen-String(e).length),s=r.relaxZeros!==!1;switch(n){case 0:return"";case 1:return s?"0?":"0";case 2:return s?"0{0,2}":"00";default:return s?`0{0,${n}}`:`0{${n}}`}}pe.cache={};pe.clearCache=()=>pe.cache={};ut.exports=pe});var Ge=K((us,Rt)=>{"use strict";var qr=require("util"),yt=At(),bt=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),Kr=e=>t=>e===!0?Number(t):String(t),De=e=>typeof e=="number"||typeof e=="string"&&e!=="",Re=e=>Number.isInteger(+e),Ue=e=>{let t=`${e}`,r=-1;if(t[0]==="-"&&(t=t.slice(1)),t==="0")return!1;for(;t[++r]==="0";);return r>0},Wr=(e,t,r)=>typeof e=="string"||typeof t=="string"?!0:r.stringify===!0,jr=(e,t,r)=>{if(t>0){let n=e[0]==="-"?"-":"";n&&(e=e.slice(1)),e=n+e.padStart(n?t-1:t,"0")}return r===!1?String(e):e},_t=(e,t)=>{let r=e[0]==="-"?"-":"";for(r&&(e=e.slice(1),t--);e.length{e.negatives.sort((i,o)=>io?1:0),e.positives.sort((i,o)=>io?1:0);let r=t.capture?"":"?:",n="",s="",a;return e.positives.length&&(n=e.positives.join("|")),e.negatives.length&&(s=`-(${r}${e.negatives.join("|")})`),n&&s?a=`${n}|${s}`:a=n||s,t.wrap?`(${r}${a})`:a},Et=(e,t,r,n)=>{if(r)return yt(e,t,B({wrap:!1},n));let s=String.fromCharCode(e);if(e===t)return s;let a=String.fromCharCode(t);return`[${s}-${a}]`},xt=(e,t,r)=>{if(Array.isArray(e)){let n=r.wrap===!0,s=r.capture?"":"?:";return n?`(${s}${e.join("|")})`:e.join("|")}return yt(e,t,r)},Ct=(...e)=>new RangeError("Invalid range arguments: "+qr.inspect(...e)),wt=(e,t,r)=>{if(r.strictRanges===!0)throw Ct([e,t]);return[]},Qr=(e,t)=>{if(t.strictRanges===!0)throw new TypeError(`Expected step "${e}" to be a number`);return[]},Xr=(e,t,r=1,n={})=>{let s=Number(e),a=Number(t);if(!Number.isInteger(s)||!Number.isInteger(a)){if(n.strictRanges===!0)throw Ct([e,t]);return[]}s===0&&(s=0),a===0&&(a=0);let i=s>a,o=String(e),h=String(t),g=String(r);r=Math.max(Math.abs(r),1);let f=Ue(o)||Ue(h)||Ue(g),A=f?Math.max(o.length,h.length,g.length):0,p=f===!1&&Wr(e,t,n)===!1,k=n.transform||Kr(p);if(n.toRegex&&r===1)return Et(_t(e,A),_t(t,A),!0,n);let y={negatives:[],positives:[]},R=T=>y[T<0?"negatives":"positives"].push(Math.abs(T)),_=[],x=0;for(;i?s>=a:s<=a;)n.toRegex===!0&&r>1?R(s):_.push(jr(k(s,x),A,p)),s=i?s-r:s+r,x++;return n.toRegex===!0?r>1?Fr(y,n):xt(_,null,B({wrap:!1},n)):_},Zr=(e,t,r=1,n={})=>{if(!Re(e)&&e.length>1||!Re(t)&&t.length>1)return wt(e,t,n);let s=n.transform||(p=>String.fromCharCode(p)),a=`${e}`.charCodeAt(0),i=`${t}`.charCodeAt(0),o=a>i,h=Math.min(a,i),g=Math.max(a,i);if(n.toRegex&&r===1)return Et(h,g,!1,n);let f=[],A=0;for(;o?a>=i:a<=i;)f.push(s(a,A)),a=o?a-r:a+r,A++;return n.toRegex===!0?xt(f,null,{wrap:!1,options:n}):f},Te=(e,t,r,n={})=>{if(t==null&&De(e))return[e];if(!De(e)||!De(t))return wt(e,t,n);if(typeof r=="function")return Te(e,t,1,{transform:r});if(bt(r))return Te(e,t,0,r);let s=B({},n);return s.capture===!0&&(s.wrap=!0),r=r||s.step||1,Re(r)?Re(e)&&Re(t)?Xr(e,t,r,s):Zr(e,t,Math.max(Math.abs(r),1),s):r!=null&&!bt(r)?Qr(r,s):Te(e,t,1,r)};Rt.exports=Te});var Ht=K((ls,St)=>{"use strict";var Yr=Ge(),vt=$e(),zr=(e,t={})=>{let r=(n,s={})=>{let a=vt.isInvalidBrace(s),i=n.invalid===!0&&t.escapeInvalid===!0,o=a===!0||i===!0,h=t.escapeInvalid===!0?"\\":"",g="";if(n.isOpen===!0||n.isClose===!0)return h+n.value;if(n.type==="open")return o?h+n.value:"(";if(n.type==="close")return o?h+n.value:")";if(n.type==="comma")return n.prev.type==="comma"?"":o?n.value:"|";if(n.value)return n.value;if(n.nodes&&n.ranges>0){let f=vt.reduce(n.nodes),A=Yr(...f,Q(B({},t),{wrap:!1,toRegex:!0}));if(A.length!==0)return f.length>1&&A.length>1?`(${A})`:A}if(n.nodes)for(let f of n.nodes)g+=r(f,n);return g};return r(e)};St.exports=zr});var Tt=K((ps,$t)=>{"use strict";var Vr=Ge(),kt=ke(),he=$e(),fe=(e="",t="",r=!1)=>{let n=[];if(e=[].concat(e),t=[].concat(t),!t.length)return e;if(!e.length)return r?he.flatten(t).map(s=>`{${s}}`):t;for(let s of e)if(Array.isArray(s))for(let a of s)n.push(fe(a,t,r));else for(let a of t)r===!0&&typeof a=="string"&&(a=`{${a}}`),n.push(Array.isArray(a)?fe(s,a,r):s+a);return he.flatten(n)},Jr=(e,t={})=>{let r=t.rangeLimit===void 0?1e3:t.rangeLimit,n=(s,a={})=>{s.queue=[];let i=a,o=a.queue;for(;i.type!=="brace"&&i.type!=="root"&&i.parent;)i=i.parent,o=i.queue;if(s.invalid||s.dollar){o.push(fe(o.pop(),kt(s,t)));return}if(s.type==="brace"&&s.invalid!==!0&&s.nodes.length===2){o.push(fe(o.pop(),["{}"]));return}if(s.nodes&&s.ranges>0){let A=he.reduce(s.nodes);if(he.exceedsLimit(...A,t.step,r))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let p=Vr(...A,t);p.length===0&&(p=kt(s,t)),o.push(fe(o.pop(),p)),s.nodes=[];return}let h=he.encloseBrace(s),g=s.queue,f=s;for(;f.type!=="brace"&&f.type!=="root"&&f.parent;)f=f.parent,g=f.queue;for(let A=0;A{"use strict";Lt.exports={MAX_LENGTH:1024*64,CHAR_0:"0",CHAR_9:"9",CHAR_UPPERCASE_A:"A",CHAR_LOWERCASE_A:"a",CHAR_UPPERCASE_Z:"Z",CHAR_LOWERCASE_Z:"z",CHAR_LEFT_PARENTHESES:"(",CHAR_RIGHT_PARENTHESES:")",CHAR_ASTERISK:"*",CHAR_AMPERSAND:"&",CHAR_AT:"@",CHAR_BACKSLASH:"\\",CHAR_BACKTICK:"`",CHAR_CARRIAGE_RETURN:"\r",CHAR_CIRCUMFLEX_ACCENT:"^",CHAR_COLON:":",CHAR_COMMA:",",CHAR_DOLLAR:"$",CHAR_DOT:".",CHAR_DOUBLE_QUOTE:'"',CHAR_EQUAL:"=",CHAR_EXCLAMATION_MARK:"!",CHAR_FORM_FEED:"\f",CHAR_FORWARD_SLASH:"/",CHAR_HASH:"#",CHAR_HYPHEN_MINUS:"-",CHAR_LEFT_ANGLE_BRACKET:"<",CHAR_LEFT_CURLY_BRACE:"{",CHAR_LEFT_SQUARE_BRACKET:"[",CHAR_LINE_FEED:` -`,CHAR_NO_BREAK_SPACE:"\xA0",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:" ",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\uFEFF"}});var Pt=K((hs,Nt)=>{"use strict";var en=ke(),{MAX_LENGTH:It,CHAR_BACKSLASH:qe,CHAR_BACKTICK:tn,CHAR_COMMA:rn,CHAR_DOT:nn,CHAR_LEFT_PARENTHESES:sn,CHAR_RIGHT_PARENTHESES:an,CHAR_LEFT_CURLY_BRACE:on,CHAR_RIGHT_CURLY_BRACE:cn,CHAR_LEFT_SQUARE_BRACKET:Bt,CHAR_RIGHT_SQUARE_BRACKET:Mt,CHAR_DOUBLE_QUOTE:un,CHAR_SINGLE_QUOTE:ln,CHAR_NO_BREAK_SPACE:pn,CHAR_ZERO_WIDTH_NOBREAK_SPACE:fn}=Ot(),hn=(e,t={})=>{if(typeof e!="string")throw new TypeError("Expected a string");let r=t||{},n=typeof r.maxLength=="number"?Math.min(It,r.maxLength):It;if(e.length>n)throw new SyntaxError(`Input length (${e.length}), exceeds max characters (${n})`);let s={type:"root",input:e,nodes:[]},a=[s],i=s,o=s,h=0,g=e.length,f=0,A=0,p,k={},y=()=>e[f++],R=_=>{if(_.type==="text"&&o.type==="dot"&&(o.type="text"),o&&o.type==="text"&&_.type==="text"){o.value+=_.value;return}return i.nodes.push(_),_.parent=i,_.prev=o,o=_,_};for(R({type:"bos"});f0){if(i.ranges>0){i.ranges=0;let _=i.nodes.shift();i.nodes=[_,{type:"text",value:en(i)}]}R({type:"comma",value:p}),i.commas++;continue}if(p===nn&&A>0&&i.commas===0){let _=i.nodes;if(A===0||_.length===0){R({type:"text",value:p});continue}if(o.type==="dot"){if(i.range=[],o.value+=p,o.type="range",i.nodes.length!==3&&i.nodes.length!==5){i.invalid=!0,i.ranges=0,o.type="text";continue}i.ranges++,i.args=[];continue}if(o.type==="range"){_.pop();let x=_[_.length-1];x.value+=o.value+p,o=x,i.ranges--;continue}R({type:"dot",value:p});continue}R({type:"text",value:p})}do if(i=a.pop(),i.type!=="root"){i.nodes.forEach(T=>{T.nodes||(T.type==="open"&&(T.isOpen=!0),T.type==="close"&&(T.isClose=!0),T.nodes||(T.type="text"),T.invalid=!0)});let _=a[a.length-1],x=_.nodes.indexOf(i);_.nodes.splice(x,1,...i.nodes)}while(a.length>0);return R({type:"eos"}),s};Nt.exports=hn});var Gt=K((ds,Dt)=>{"use strict";var Ut=ke(),dn=Ht(),gn=Tt(),mn=Pt(),V=(e,t={})=>{let r=[];if(Array.isArray(e))for(let n of e){let s=V.create(n,t);Array.isArray(s)?r.push(...s):r.push(s)}else r=[].concat(V.create(e,t));return t&&t.expand===!0&&t.nodupes===!0&&(r=[...new Set(r)]),r};V.parse=(e,t={})=>mn(e,t);V.stringify=(e,t={})=>typeof e=="string"?Ut(V.parse(e,t),t):Ut(e,t);V.compile=(e,t={})=>(typeof e=="string"&&(e=V.parse(e,t)),dn(e,t));V.expand=(e,t={})=>{typeof e=="string"&&(e=V.parse(e,t));let r=gn(e,t);return t.noempty===!0&&(r=r.filter(Boolean)),t.nodupes===!0&&(r=[...new Set(r)]),r};V.create=(e,t={})=>e===""||e.length<3?[e]:t.expand!==!0?V.compile(e,t):V.expand(e,t);Dt.exports=V});var ye=K((gs,qt)=>{"use strict";var An=require("path"),ie="\\\\/",Kt=`[^${ie}]`,ce="\\.",Rn="\\+",yn="\\?",Le="\\/",bn="(?=.)",Wt="[^/]",Ke=`(?:${Le}|$)`,jt=`(?:^|${Le})`,We=`${ce}{1,2}${Ke}`,_n=`(?!${ce})`,En=`(?!${jt}${We})`,xn=`(?!${ce}{0,1}${Ke})`,Cn=`(?!${We})`,wn=`[^.${Le}]`,Sn=`${Wt}*?`,Ft={DOT_LITERAL:ce,PLUS_LITERAL:Rn,QMARK_LITERAL:yn,SLASH_LITERAL:Le,ONE_CHAR:bn,QMARK:Wt,END_ANCHOR:Ke,DOTS_SLASH:We,NO_DOT:_n,NO_DOTS:En,NO_DOT_SLASH:xn,NO_DOTS_SLASH:Cn,QMARK_NO_DOT:wn,STAR:Sn,START_ANCHOR:jt},vn=Q(B({},Ft),{SLASH_LITERAL:`[${ie}]`,QMARK:Kt,STAR:`${Kt}*?`,DOTS_SLASH:`${ce}{1,2}(?:[${ie}]|$)`,NO_DOT:`(?!${ce})`,NO_DOTS:`(?!(?:^|[${ie}])${ce}{1,2}(?:[${ie}]|$))`,NO_DOT_SLASH:`(?!${ce}{0,1}(?:[${ie}]|$))`,NO_DOTS_SLASH:`(?!${ce}{1,2}(?:[${ie}]|$))`,QMARK_NO_DOT:`[^.${ie}]`,START_ANCHOR:`(?:^|[${ie}])`,END_ANCHOR:`(?:[${ie}]|$)`}),Hn={alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"};qt.exports={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:Hn,REGEX_BACKSLASH:/\\(?![*+?^${}(|)[\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\].,$*+?^{}()|\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\?)((\W)(\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\[.*?[^\\]\]|\\(?=.))/g,REPLACEMENTS:{"***":"*","**/**":"**","**/**/**":"**"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:An.sep,extglobChars(e){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${e.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}},globChars(e){return e===!0?vn:Ft}}});var be=K(Z=>{"use strict";var $n=require("path"),kn=process.platform==="win32",{REGEX_BACKSLASH:Tn,REGEX_REMOVE_BACKSLASH:Ln,REGEX_SPECIAL_CHARS:On,REGEX_SPECIAL_CHARS_GLOBAL:Nn}=ye();Z.isObject=e=>e!==null&&typeof e=="object"&&!Array.isArray(e);Z.hasRegexChars=e=>On.test(e);Z.isRegexChar=e=>e.length===1&&Z.hasRegexChars(e);Z.escapeRegex=e=>e.replace(Nn,"\\$1");Z.toPosixSlashes=e=>e.replace(Tn,"/");Z.removeBackslashes=e=>e.replace(Ln,t=>t==="\\"?"":t);Z.supportsLookbehinds=()=>{let e=process.version.slice(1).split(".").map(Number);return e.length===3&&e[0]>=9||e[0]===8&&e[1]>=10};Z.isWindows=e=>e&&typeof e.windows=="boolean"?e.windows:kn===!0||$n.sep==="\\";Z.escapeLast=(e,t,r)=>{let n=e.lastIndexOf(t,r);return n===-1?e:e[n-1]==="\\"?Z.escapeLast(e,t,n-1):`${e.slice(0,n)}\\${e.slice(n)}`};Z.removePrefix=(e,t={})=>{let r=e;return r.startsWith("./")&&(r=r.slice(2),t.prefix="./"),r};Z.wrapOutput=(e,t={},r={})=>{let n=r.contains?"":"^",s=r.contains?"":"$",a=`${n}(?:${e})${s}`;return t.negated===!0&&(a=`(?:^(?!${a}).*$)`),a}});var er=K((As,Qt)=>{"use strict";var Xt=be(),{CHAR_ASTERISK:je,CHAR_AT:In,CHAR_BACKWARD_SLASH:_e,CHAR_COMMA:Bn,CHAR_DOT:Fe,CHAR_EXCLAMATION_MARK:Qe,CHAR_FORWARD_SLASH:Zt,CHAR_LEFT_CURLY_BRACE:Xe,CHAR_LEFT_PARENTHESES:Ze,CHAR_LEFT_SQUARE_BRACKET:Mn,CHAR_PLUS:Pn,CHAR_QUESTION_MARK:Yt,CHAR_RIGHT_CURLY_BRACE:Dn,CHAR_RIGHT_PARENTHESES:zt,CHAR_RIGHT_SQUARE_BRACKET:Un}=ye(),Vt=e=>e===Zt||e===_e,Jt=e=>{e.isPrefix!==!0&&(e.depth=e.isGlobstar?Infinity:1)},Gn=(e,t)=>{let r=t||{},n=e.length-1,s=r.parts===!0||r.scanToEnd===!0,a=[],i=[],o=[],h=e,g=-1,f=0,A=0,p=!1,k=!1,y=!1,R=!1,_=!1,x=!1,T=!1,O=!1,W=!1,G=!1,ne=0,E,b,C={value:"",depth:0,isGlob:!1},M=()=>g>=n,l=()=>h.charCodeAt(g+1),H=()=>(E=b,h.charCodeAt(++g));for(;g0&&(j=h.slice(0,f),h=h.slice(f),A-=f),w&&y===!0&&A>0?(w=h.slice(0,A),c=h.slice(A)):y===!0?(w="",c=h):w=h,w&&w!==""&&w!=="/"&&w!==h&&Vt(w.charCodeAt(w.length-1))&&(w=w.slice(0,-1)),r.unescape===!0&&(c&&(c=Xt.removeBackslashes(c)),w&&T===!0&&(w=Xt.removeBackslashes(w)));let u={prefix:j,input:e,start:f,base:w,glob:c,isBrace:p,isBracket:k,isGlob:y,isExtglob:R,isGlobstar:_,negated:O,negatedExtglob:W};if(r.tokens===!0&&(u.maxDepth=0,Vt(b)||i.push(C),u.tokens=i),r.parts===!0||r.tokens===!0){let I;for(let $=0;${"use strict";var Oe=ye(),J=be(),{MAX_LENGTH:Ne,POSIX_REGEX_SOURCE:qn,REGEX_NON_SPECIAL_CHARS:Kn,REGEX_SPECIAL_CHARS_BACKREF:Wn,REPLACEMENTS:rr}=Oe,jn=(e,t)=>{if(typeof t.expandRange=="function")return t.expandRange(...e,t);e.sort();let r=`[${e.join("-")}]`;try{new RegExp(r)}catch(n){return e.map(s=>J.escapeRegex(s)).join("..")}return r},de=(e,t)=>`Missing ${e}: "${t}" - use "\\\\${t}" to match literal characters`,nr=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");e=rr[e]||e;let r=B({},t),n=typeof r.maxLength=="number"?Math.min(Ne,r.maxLength):Ne,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);let a={type:"bos",value:"",output:r.prepend||""},i=[a],o=r.capture?"":"?:",h=J.isWindows(t),g=Oe.globChars(h),f=Oe.extglobChars(g),{DOT_LITERAL:A,PLUS_LITERAL:p,SLASH_LITERAL:k,ONE_CHAR:y,DOTS_SLASH:R,NO_DOT:_,NO_DOT_SLASH:x,NO_DOTS_SLASH:T,QMARK:O,QMARK_NO_DOT:W,STAR:G,START_ANCHOR:ne}=g,E=m=>`(${o}(?:(?!${ne}${m.dot?R:A}).)*?)`,b=r.dot?"":_,C=r.dot?O:W,M=r.bash===!0?E(r):G;r.capture&&(M=`(${M})`),typeof r.noext=="boolean"&&(r.noextglob=r.noext);let l={input:e,index:-1,start:0,dot:r.dot===!0,consumed:"",output:"",prefix:"",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:i};e=J.removePrefix(e,l),s=e.length;let H=[],w=[],j=[],c=a,u,I=()=>l.index===s-1,$=l.peek=(m=1)=>e[l.index+m],ee=l.advance=()=>e[++l.index]||"",se=()=>e.slice(l.index+1),z=(m="",L=0)=>{l.consumed+=m,l.index+=L},Ce=m=>{l.output+=m.output!=null?m.output:m.value,z(m.value)},xr=()=>{let m=1;for(;$()==="!"&&($(2)!=="("||$(3)==="?");)ee(),l.start++,m++;return m%2==0?!1:(l.negated=!0,l.start++,!0)},we=m=>{l[m]++,j.push(m)},ue=m=>{l[m]--,j.pop()},v=m=>{if(c.type==="globstar"){let L=l.braces>0&&(m.type==="comma"||m.type==="brace"),d=m.extglob===!0||H.length&&(m.type==="pipe"||m.type==="paren");m.type!=="slash"&&m.type!=="paren"&&!L&&!d&&(l.output=l.output.slice(0,-c.output.length),c.type="star",c.value="*",c.output=M,l.output+=c.output)}if(H.length&&m.type!=="paren"&&(H[H.length-1].inner+=m.value),(m.value||m.output)&&Ce(m),c&&c.type==="text"&&m.type==="text"){c.value+=m.value,c.output=(c.output||"")+m.value;return}m.prev=c,i.push(m),c=m},Se=(m,L)=>{let d=Q(B({},f[L]),{conditions:1,inner:""});d.prev=c,d.parens=l.parens,d.output=l.output;let S=(r.capture?"(":"")+d.open;we("parens"),v({type:m,value:L,output:l.output?"":y}),v({type:"paren",extglob:!0,value:ee(),output:S}),H.push(d)},Cr=m=>{let L=m.close+(r.capture?")":""),d;if(m.type==="negate"){let S=M;m.inner&&m.inner.length>1&&m.inner.includes("/")&&(S=E(r)),(S!==M||I()||/^\)+$/.test(se()))&&(L=m.close=`)$))${S}`),m.inner.includes("*")&&(d=se())&&/^\.[^\\/.]+$/.test(d)&&(L=m.close=`)${d})${S})`),m.prev.type==="bos"&&(l.negatedExtglob=!0)}v({type:"paren",extglob:!0,value:u,output:L}),ue("parens")};if(r.fastpaths!==!1&&!/(^[*!]|[/()[\]{}"])/.test(e)){let m=!1,L=e.replace(Wn,(d,S,P,F,q,Me)=>F==="\\"?(m=!0,d):F==="?"?S?S+F+(q?O.repeat(q.length):""):Me===0?C+(q?O.repeat(q.length):""):O.repeat(P.length):F==="."?A.repeat(P.length):F==="*"?S?S+F+(q?M:""):M:S?d:`\\${d}`);return m===!0&&(r.unescape===!0?L=L.replace(/\\/g,""):L=L.replace(/\\+/g,d=>d.length%2==0?"\\\\":d?"\\":"")),L===e&&r.contains===!0?(l.output=e,l):(l.output=J.wrapOutput(L,l,t),l)}for(;!I();){if(u=ee(),u==="\0")continue;if(u==="\\"){let d=$();if(d==="/"&&r.bash!==!0||d==="."||d===";")continue;if(!d){u+="\\",v({type:"text",value:u});continue}let S=/^\\+/.exec(se()),P=0;if(S&&S[0].length>2&&(P=S[0].length,l.index+=P,P%2!=0&&(u+="\\")),r.unescape===!0?u=ee():u+=ee(),l.brackets===0){v({type:"text",value:u});continue}}if(l.brackets>0&&(u!=="]"||c.value==="["||c.value==="[^")){if(r.posix!==!1&&u===":"){let d=c.value.slice(1);if(d.includes("[")&&(c.posix=!0,d.includes(":"))){let S=c.value.lastIndexOf("["),P=c.value.slice(0,S),F=c.value.slice(S+2),q=qn[F];if(q){c.value=P+q,l.backtrack=!0,ee(),!a.output&&i.indexOf(c)===1&&(a.output=y);continue}}}(u==="["&&$()!==":"||u==="-"&&$()==="]")&&(u=`\\${u}`),u==="]"&&(c.value==="["||c.value==="[^")&&(u=`\\${u}`),r.posix===!0&&u==="!"&&c.value==="["&&(u="^"),c.value+=u,Ce({value:u});continue}if(l.quotes===1&&u!=='"'){u=J.escapeRegex(u),c.value+=u,Ce({value:u});continue}if(u==='"'){l.quotes=l.quotes===1?0:1,r.keepQuotes===!0&&v({type:"text",value:u});continue}if(u==="("){we("parens"),v({type:"paren",value:u});continue}if(u===")"){if(l.parens===0&&r.strictBrackets===!0)throw new SyntaxError(de("opening","("));let d=H[H.length-1];if(d&&l.parens===d.parens+1){Cr(H.pop());continue}v({type:"paren",value:u,output:l.parens?")":"\\)"}),ue("parens");continue}if(u==="["){if(r.nobracket===!0||!se().includes("]")){if(r.nobracket!==!0&&r.strictBrackets===!0)throw new SyntaxError(de("closing","]"));u=`\\${u}`}else we("brackets");v({type:"bracket",value:u});continue}if(u==="]"){if(r.nobracket===!0||c&&c.type==="bracket"&&c.value.length===1){v({type:"text",value:u,output:`\\${u}`});continue}if(l.brackets===0){if(r.strictBrackets===!0)throw new SyntaxError(de("opening","["));v({type:"text",value:u,output:`\\${u}`});continue}ue("brackets");let d=c.value.slice(1);if(c.posix!==!0&&d[0]==="^"&&!d.includes("/")&&(u=`/${u}`),c.value+=u,Ce({value:u}),r.literalBrackets===!1||J.hasRegexChars(d))continue;let S=J.escapeRegex(c.value);if(l.output=l.output.slice(0,-c.value.length),r.literalBrackets===!0){l.output+=S,c.value=S;continue}c.value=`(${o}${S}|${c.value})`,l.output+=c.value;continue}if(u==="{"&&r.nobrace!==!0){we("braces");let d={type:"brace",value:u,output:"(",outputIndex:l.output.length,tokensIndex:l.tokens.length};w.push(d),v(d);continue}if(u==="}"){let d=w[w.length-1];if(r.nobrace===!0||!d){v({type:"text",value:u,output:u});continue}let S=")";if(d.dots===!0){let P=i.slice(),F=[];for(let q=P.length-1;q>=0&&(i.pop(),P[q].type!=="brace");q--)P[q].type!=="dots"&&F.unshift(P[q].value);S=jn(F,r),l.backtrack=!0}if(d.comma!==!0&&d.dots!==!0){let P=l.output.slice(0,d.outputIndex),F=l.tokens.slice(d.tokensIndex);d.value=d.output="\\{",u=S="\\}",l.output=P;for(let q of F)l.output+=q.output||q.value}v({type:"brace",value:u,output:S}),ue("braces"),w.pop();continue}if(u==="|"){H.length>0&&H[H.length-1].conditions++,v({type:"text",value:u});continue}if(u===","){let d=u,S=w[w.length-1];S&&j[j.length-1]==="braces"&&(S.comma=!0,d="|"),v({type:"comma",value:u,output:d});continue}if(u==="/"){if(c.type==="dot"&&l.index===l.start+1){l.start=l.index+1,l.consumed="",l.output="",i.pop(),c=a;continue}v({type:"slash",value:u,output:k});continue}if(u==="."){if(l.braces>0&&c.type==="dot"){c.value==="."&&(c.output=A);let d=w[w.length-1];c.type="dots",c.output+=u,c.value+=u,d.dots=!0;continue}if(l.braces+l.parens===0&&c.type!=="bos"&&c.type!=="slash"){v({type:"text",value:u,output:A});continue}v({type:"dot",value:u,output:A});continue}if(u==="?"){if(!(c&&c.value==="(")&&r.noextglob!==!0&&$()==="("&&$(2)!=="?"){Se("qmark",u);continue}if(c&&c.type==="paren"){let S=$(),P=u;if(S==="<"&&!J.supportsLookbehinds())throw new Error("Node.js v10 or higher is required for regex lookbehinds");(c.value==="("&&!/[!=<:]/.test(S)||S==="<"&&!/<([!=]|\w+>)/.test(se()))&&(P=`\\${u}`),v({type:"text",value:u,output:P});continue}if(r.dot!==!0&&(c.type==="slash"||c.type==="bos")){v({type:"qmark",value:u,output:W});continue}v({type:"qmark",value:u,output:O});continue}if(u==="!"){if(r.noextglob!==!0&&$()==="("&&($(2)!=="?"||!/[!=<:]/.test($(3)))){Se("negate",u);continue}if(r.nonegate!==!0&&l.index===0){xr();continue}}if(u==="+"){if(r.noextglob!==!0&&$()==="("&&$(2)!=="?"){Se("plus",u);continue}if(c&&c.value==="("||r.regex===!1){v({type:"plus",value:u,output:p});continue}if(c&&(c.type==="bracket"||c.type==="paren"||c.type==="brace")||l.parens>0){v({type:"plus",value:u});continue}v({type:"plus",value:p});continue}if(u==="@"){if(r.noextglob!==!0&&$()==="("&&$(2)!=="?"){v({type:"at",extglob:!0,value:u,output:""});continue}v({type:"text",value:u});continue}if(u!=="*"){(u==="$"||u==="^")&&(u=`\\${u}`);let d=Kn.exec(se());d&&(u+=d[0],l.index+=d[0].length),v({type:"text",value:u});continue}if(c&&(c.type==="globstar"||c.star===!0)){c.type="star",c.star=!0,c.value+=u,c.output=M,l.backtrack=!0,l.globstar=!0,z(u);continue}let m=se();if(r.noextglob!==!0&&/^\([^?]/.test(m)){Se("star",u);continue}if(c.type==="star"){if(r.noglobstar===!0){z(u);continue}let d=c.prev,S=d.prev,P=d.type==="slash"||d.type==="bos",F=S&&(S.type==="star"||S.type==="globstar");if(r.bash===!0&&(!P||m[0]&&m[0]!=="/")){v({type:"star",value:u,output:""});continue}let q=l.braces>0&&(d.type==="comma"||d.type==="brace"),Me=H.length&&(d.type==="pipe"||d.type==="paren");if(!P&&d.type!=="paren"&&!q&&!Me){v({type:"star",value:u,output:""});continue}for(;m.slice(0,3)==="/**";){let ve=e[l.index+4];if(ve&&ve!=="/")break;m=m.slice(3),z("/**",3)}if(d.type==="bos"&&I()){c.type="globstar",c.value+=u,c.output=E(r),l.output=c.output,l.globstar=!0,z(u);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&!F&&I()){l.output=l.output.slice(0,-(d.output+c.output).length),d.output=`(?:${d.output}`,c.type="globstar",c.output=E(r)+(r.strictSlashes?")":"|$)"),c.value+=u,l.globstar=!0,l.output+=d.output+c.output,z(u);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&m[0]==="/"){let ve=m[1]!==void 0?"|$":"";l.output=l.output.slice(0,-(d.output+c.output).length),d.output=`(?:${d.output}`,c.type="globstar",c.output=`${E(r)}${k}|${k}${ve})`,c.value+=u,l.output+=d.output+c.output,l.globstar=!0,z(u+ee()),v({type:"slash",value:"/",output:""});continue}if(d.type==="bos"&&m[0]==="/"){c.type="globstar",c.value+=u,c.output=`(?:^|${k}|${E(r)}${k})`,l.output=c.output,l.globstar=!0,z(u+ee()),v({type:"slash",value:"/",output:""});continue}l.output=l.output.slice(0,-c.output.length),c.type="globstar",c.output=E(r),c.value+=u,l.output+=c.output,l.globstar=!0,z(u);continue}let L={type:"star",value:u,output:M};if(r.bash===!0){L.output=".*?",(c.type==="bos"||c.type==="slash")&&(L.output=b+L.output),v(L);continue}if(c&&(c.type==="bracket"||c.type==="paren")&&r.regex===!0){L.output=u,v(L);continue}(l.index===l.start||c.type==="slash"||c.type==="dot")&&(c.type==="dot"?(l.output+=x,c.output+=x):r.dot===!0?(l.output+=T,c.output+=T):(l.output+=b,c.output+=b),$()!=="*"&&(l.output+=y,c.output+=y)),v(L)}for(;l.brackets>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing","]"));l.output=J.escapeLast(l.output,"["),ue("brackets")}for(;l.parens>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing",")"));l.output=J.escapeLast(l.output,"("),ue("parens")}for(;l.braces>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing","}"));l.output=J.escapeLast(l.output,"{"),ue("braces")}if(r.strictSlashes!==!0&&(c.type==="star"||c.type==="bracket")&&v({type:"maybe_slash",value:"",output:`${k}?`}),l.backtrack===!0){l.output="";for(let m of l.tokens)l.output+=m.output!=null?m.output:m.value,m.suffix&&(l.output+=m.suffix)}return l};nr.fastpaths=(e,t)=>{let r=B({},t),n=typeof r.maxLength=="number"?Math.min(Ne,r.maxLength):Ne,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);e=rr[e]||e;let a=J.isWindows(t),{DOT_LITERAL:i,SLASH_LITERAL:o,ONE_CHAR:h,DOTS_SLASH:g,NO_DOT:f,NO_DOTS:A,NO_DOTS_SLASH:p,STAR:k,START_ANCHOR:y}=Oe.globChars(a),R=r.dot?A:f,_=r.dot?p:f,x=r.capture?"":"?:",T={negated:!1,prefix:""},O=r.bash===!0?".*?":k;r.capture&&(O=`(${O})`);let W=b=>b.noglobstar===!0?O:`(${x}(?:(?!${y}${b.dot?g:i}).)*?)`,G=b=>{switch(b){case"*":return`${R}${h}${O}`;case".*":return`${i}${h}${O}`;case"*.*":return`${R}${O}${i}${h}${O}`;case"*/*":return`${R}${O}${o}${h}${_}${O}`;case"**":return R+W(r);case"**/*":return`(?:${R}${W(r)}${o})?${_}${h}${O}`;case"**/*.*":return`(?:${R}${W(r)}${o})?${_}${O}${i}${h}${O}`;case"**/.*":return`(?:${R}${W(r)}${o})?${i}${h}${O}`;default:{let C=/^(.*?)\.(\w+)$/.exec(b);if(!C)return;let M=G(C[1]);return M?M+i+C[2]:void 0}}},ne=J.removePrefix(e,T),E=G(ne);return E&&r.strictSlashes!==!0&&(E+=`${o}?`),E};tr.exports=nr});var ir=K((ys,ar)=>{"use strict";var Fn=require("path"),Qn=er(),Ye=sr(),ze=be(),Xn=ye(),Zn=e=>e&&typeof e=="object"&&!Array.isArray(e),D=(e,t,r=!1)=>{if(Array.isArray(e)){let f=e.map(p=>D(p,t,r));return p=>{for(let k of f){let y=k(p);if(y)return y}return!1}}let n=Zn(e)&&e.tokens&&e.input;if(e===""||typeof e!="string"&&!n)throw new TypeError("Expected pattern to be a non-empty string");let s=t||{},a=ze.isWindows(t),i=n?D.compileRe(e,t):D.makeRe(e,t,!1,!0),o=i.state;delete i.state;let h=()=>!1;if(s.ignore){let f=Q(B({},t),{ignore:null,onMatch:null,onResult:null});h=D(s.ignore,f,r)}let g=(f,A=!1)=>{let{isMatch:p,match:k,output:y}=D.test(f,i,t,{glob:e,posix:a}),R={glob:e,state:o,regex:i,posix:a,input:f,output:y,match:k,isMatch:p};return typeof s.onResult=="function"&&s.onResult(R),p===!1?(R.isMatch=!1,A?R:!1):h(f)?(typeof s.onIgnore=="function"&&s.onIgnore(R),R.isMatch=!1,A?R:!1):(typeof s.onMatch=="function"&&s.onMatch(R),A?R:!0)};return r&&(g.state=o),g};D.test=(e,t,r,{glob:n,posix:s}={})=>{if(typeof e!="string")throw new TypeError("Expected input to be a string");if(e==="")return{isMatch:!1,output:""};let a=r||{},i=a.format||(s?ze.toPosixSlashes:null),o=e===n,h=o&&i?i(e):e;return o===!1&&(h=i?i(e):e,o=h===n),(o===!1||a.capture===!0)&&(a.matchBase===!0||a.basename===!0?o=D.matchBase(e,t,r,s):o=t.exec(h)),{isMatch:Boolean(o),match:o,output:h}};D.matchBase=(e,t,r,n=ze.isWindows(r))=>(t instanceof RegExp?t:D.makeRe(t,r)).test(Fn.basename(e));D.isMatch=(e,t,r)=>D(t,r)(e);D.parse=(e,t)=>Array.isArray(e)?e.map(r=>D.parse(r,t)):Ye(e,Q(B({},t),{fastpaths:!1}));D.scan=(e,t)=>Qn(e,t);D.compileRe=(e,t,r=!1,n=!1)=>{if(r===!0)return e.output;let s=t||{},a=s.contains?"":"^",i=s.contains?"":"$",o=`${a}(?:${e.output})${i}`;e&&e.negated===!0&&(o=`^(?!${o}).*$`);let h=D.toRegex(o,t);return n===!0&&(h.state=e),h};D.makeRe=(e,t={},r=!1,n=!1)=>{if(!e||typeof e!="string")throw new TypeError("Expected a non-empty string");let s={negated:!1,fastpaths:!0};return t.fastpaths!==!1&&(e[0]==="."||e[0]==="*")&&(s.output=Ye.fastpaths(e,t)),s.output||(s=Ye(e,t)),D.compileRe(s,t,r,n)};D.toRegex=(e,t)=>{try{let r=t||{};return new RegExp(e,r.flags||(r.nocase?"i":""))}catch(r){if(t&&t.debug===!0)throw r;return/$^/}};D.constants=Xn;ar.exports=D});var cr=K((bs,or)=>{"use strict";or.exports=ir()});var hr=K((_s,ur)=>{"use strict";var lr=require("util"),pr=Gt(),oe=cr(),Ve=be(),fr=e=>e===""||e==="./",N=(e,t,r)=>{t=[].concat(t),e=[].concat(e);let n=new Set,s=new Set,a=new Set,i=0,o=f=>{a.add(f.output),r&&r.onResult&&r.onResult(f)};for(let f=0;f!n.has(f));if(r&&g.length===0){if(r.failglob===!0)throw new Error(`No matches found for "${t.join(", ")}"`);if(r.nonull===!0||r.nullglob===!0)return r.unescape?t.map(f=>f.replace(/\\/g,"")):t}return g};N.match=N;N.matcher=(e,t)=>oe(e,t);N.isMatch=(e,t,r)=>oe(t,r)(e);N.any=N.isMatch;N.not=(e,t,r={})=>{t=[].concat(t).map(String);let n=new Set,s=[],a=o=>{r.onResult&&r.onResult(o),s.push(o.output)},i=N(e,t,Q(B({},r),{onResult:a}));for(let o of s)i.includes(o)||n.add(o);return[...n]};N.contains=(e,t,r)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${lr.inspect(e)}"`);if(Array.isArray(t))return t.some(n=>N.contains(e,n,r));if(typeof t=="string"){if(fr(e)||fr(t))return!1;if(e.includes(t)||e.startsWith("./")&&e.slice(2).includes(t))return!0}return N.isMatch(e,t,Q(B({},r),{contains:!0}))};N.matchKeys=(e,t,r)=>{if(!Ve.isObject(e))throw new TypeError("Expected the first argument to be an object");let n=N(Object.keys(e),t,r),s={};for(let a of n)s[a]=e[a];return s};N.some=(e,t,r)=>{let n=[].concat(e);for(let s of[].concat(t)){let a=oe(String(s),r);if(n.some(i=>a(i)))return!0}return!1};N.every=(e,t,r)=>{let n=[].concat(e);for(let s of[].concat(t)){let a=oe(String(s),r);if(!n.every(i=>a(i)))return!1}return!0};N.all=(e,t,r)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${lr.inspect(e)}"`);return[].concat(t).every(n=>oe(n,r)(e))};N.capture=(e,t,r)=>{let n=Ve.isWindows(r),a=oe.makeRe(String(e),Q(B({},r),{capture:!0})).exec(n?Ve.toPosixSlashes(t):t);if(a)return a.slice(1).map(i=>i===void 0?"":i)};N.makeRe=(...e)=>oe.makeRe(...e);N.scan=(...e)=>oe.scan(...e);N.parse=(e,t)=>{let r=[];for(let n of[].concat(e||[]))for(let s of pr(String(n),t))r.push(oe.parse(s,t));return r};N.braces=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");return t&&t.nobrace===!0||!/\{.*\}/.test(e)?[e]:pr(e,t)};N.braceExpand=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");return N.braces(e,Q(B({},t),{expand:!0}))};ur.exports=N});var gr=K((Es,dr)=>{"use strict";dr.exports=(e,...t)=>new Promise(r=>{r(e(...t))})});var Ar=K((xs,Je)=>{"use strict";var Yn=gr(),mr=e=>{if(e<1)throw new TypeError("Expected `concurrency` to be a number from 1 and up");let t=[],r=0,n=()=>{r--,t.length>0&&t.shift()()},s=(o,h,...g)=>{r++;let f=Yn(o,...g);h(f),f.then(n,n)},a=(o,h,...g)=>{rnew Promise(g=>a(o,g,...h));return Object.defineProperties(i,{activeCount:{get:()=>r},pendingCount:{get:()=>t.length}}),i};Je.exports=mr;Je.exports.default=mr});var Vn={};Or(Vn,{default:()=>es});var He=X(require("@yarnpkg/cli")),ae=X(require("@yarnpkg/core")),nt=X(require("@yarnpkg/core")),le=X(require("clipanion")),Ae=class extends He.BaseCommand{constructor(){super(...arguments);this.json=le.Option.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.production=le.Option.Boolean("--production",!1,{description:"Only install regular dependencies by omitting dev dependencies"});this.all=le.Option.Boolean("-A,--all",!1,{description:"Install the entire project"});this.workspaces=le.Option.Rest()}async execute(){let t=await ae.Configuration.find(this.context.cwd,this.context.plugins),{project:r,workspace:n}=await ae.Project.find(t,this.context.cwd),s=await ae.Cache.find(t);await r.restoreInstallState({restoreResolutions:!1});let a;if(this.all)a=new Set(r.workspaces);else if(this.workspaces.length===0){if(!n)throw new He.WorkspaceRequiredError(r.cwd,this.context.cwd);a=new Set([n])}else a=new Set(this.workspaces.map(o=>r.getWorkspaceByIdent(nt.structUtils.parseIdent(o))));for(let o of a)for(let h of this.production?["dependencies"]:ae.Manifest.hardDependencies)for(let g of o.manifest.getForScope(h).values()){let f=r.tryWorkspaceByDescriptor(g);f!==null&&a.add(f)}for(let o of r.workspaces)a.has(o)?this.production&&o.manifest.devDependencies.clear():(o.manifest.installConfig=o.manifest.installConfig||{},o.manifest.installConfig.selfReferences=!1,o.manifest.dependencies.clear(),o.manifest.devDependencies.clear(),o.manifest.peerDependencies.clear(),o.manifest.scripts.clear());return(await ae.StreamReport.start({configuration:t,json:this.json,stdout:this.context.stdout,includeLogs:!0},async o=>{await r.install({cache:s,report:o,persistProject:!1})})).exitCode()}};Ae.paths=[["workspaces","focus"]],Ae.usage=le.Command.Usage({category:"Workspace-related commands",description:"install a single workspace and its dependencies",details:"\n This command will run an install as if the specified workspaces (and all other workspaces they depend on) were the only ones in the project. If no workspaces are explicitly listed, the active one will be assumed.\n\n Note that this command is only very moderately useful when using zero-installs, since the cache will contain all the packages anyway - meaning that the only difference between a full install and a focused install would just be a few extra lines in the `.pnp.cjs` file, at the cost of introducing an extra complexity.\n\n If the `-A,--all` flag is set, the entire project will be installed. Combine with `--production` to replicate the old `yarn install --production`.\n "});var st=Ae;var Ie=X(require("@yarnpkg/cli")),ge=X(require("@yarnpkg/core")),Ee=X(require("@yarnpkg/core")),Y=X(require("@yarnpkg/core")),Rr=X(require("@yarnpkg/plugin-git")),U=X(require("clipanion")),Be=X(hr()),yr=X(require("os")),br=X(Ar()),re=X(require("typanion")),xe=class extends Ie.BaseCommand{constructor(){super(...arguments);this.recursive=U.Option.Boolean("-R,--recursive",!1,{description:"Find packages via dependencies/devDependencies instead of using the workspaces field"});this.from=U.Option.Array("--from",[],{description:"An array of glob pattern idents from which to base any recursion"});this.all=U.Option.Boolean("-A,--all",!1,{description:"Run the command on all workspaces of a project"});this.verbose=U.Option.Boolean("-v,--verbose",!1,{description:"Prefix each output line with the name of the originating workspace"});this.parallel=U.Option.Boolean("-p,--parallel",!1,{description:"Run the commands in parallel"});this.interlaced=U.Option.Boolean("-i,--interlaced",!1,{description:"Print the output of commands in real-time instead of buffering it"});this.jobs=U.Option.String("-j,--jobs",{description:"The maximum number of parallel tasks that the execution will be limited to; or `unlimited`",validator:re.isOneOf([re.isEnum(["unlimited"]),re.applyCascade(re.isNumber(),[re.isInteger(),re.isAtLeast(1)])])});this.topological=U.Option.Boolean("-t,--topological",!1,{description:"Run the command after all workspaces it depends on (regular) have finished"});this.topologicalDev=U.Option.Boolean("--topological-dev",!1,{description:"Run the command after all workspaces it depends on (regular + dev) have finished"});this.include=U.Option.Array("--include",[],{description:"An array of glob pattern idents; only matching workspaces will be traversed"});this.exclude=U.Option.Array("--exclude",[],{description:"An array of glob pattern idents; matching workspaces won't be traversed"});this.publicOnly=U.Option.Boolean("--no-private",{description:"Avoid running the command on private workspaces"});this.since=U.Option.String("--since",{description:"Only include workspaces that have been changed since the specified ref.",tolerateBoolean:!0});this.commandName=U.Option.String();this.args=U.Option.Proxy()}async execute(){let t=await ge.Configuration.find(this.context.cwd,this.context.plugins),{project:r,workspace:n}=await ge.Project.find(t,this.context.cwd);if(!this.all&&!n)throw new Ie.WorkspaceRequiredError(r.cwd,this.context.cwd);await r.restoreInstallState();let s=this.cli.process([this.commandName,...this.args]),a=s.path.length===1&&s.path[0]==="run"&&typeof s.scriptName!="undefined"?s.scriptName:null;if(s.path.length===0)throw new U.UsageError("Invalid subcommand name for iteration - use the 'run' keyword if you wish to execute a script");let i=this.all?r.topLevelWorkspace:n,o=this.since?Array.from(await Rr.gitUtils.fetchChangedWorkspaces({ref:this.since,project:r})):[i,...this.from.length>0?i.getRecursiveWorkspaceChildren():[]],h=E=>Be.default.isMatch(Y.structUtils.stringifyIdent(E.locator),this.from),g=this.from.length>0?o.filter(h):o,f=new Set([...g,...g.map(E=>[...this.recursive?this.since?E.getRecursiveWorkspaceDependents():E.getRecursiveWorkspaceDependencies():E.getRecursiveWorkspaceChildren()]).flat()]),A=[],p=!1;if(a==null?void 0:a.includes(":")){for(let E of r.workspaces)if(E.manifest.scripts.has(a)&&(p=!p,p===!1))break}for(let E of f)a&&!E.manifest.scripts.has(a)&&!p&&!(await ge.scriptUtils.getWorkspaceAccessibleBinaries(E)).has(a)||a===process.env.npm_lifecycle_event&&E.cwd===n.cwd||this.include.length>0&&!Be.default.isMatch(Y.structUtils.stringifyIdent(E.locator),this.include)||this.exclude.length>0&&Be.default.isMatch(Y.structUtils.stringifyIdent(E.locator),this.exclude)||this.publicOnly&&E.manifest.private===!0||A.push(E);let k=this.parallel?this.jobs==="unlimited"?Infinity:this.jobs||Math.max(1,(0,yr.cpus)().length/2):1,y=k===1?!1:this.parallel,R=y?this.interlaced:!0,_=(0,br.default)(k),x=new Map,T=new Set,O=0,W=null,G=!1,ne=await Ee.StreamReport.start({configuration:t,stdout:this.context.stdout},async E=>{let b=async(C,{commandIndex:M})=>{if(G)return-1;!y&&this.verbose&&M>1&&E.reportSeparator();let l=zn(C,{configuration:t,verbose:this.verbose,commandIndex:M}),[H,w]=_r(E,{prefix:l,interlaced:R}),[j,c]=_r(E,{prefix:l,interlaced:R});try{this.verbose&&E.reportInfo(null,`${l} Process started`);let u=Date.now(),I=await this.cli.run([this.commandName,...this.args],{cwd:C.cwd,stdout:H,stderr:j})||0;H.end(),j.end(),await w,await c;let $=Date.now();if(this.verbose){let ee=t.get("enableTimers")?`, completed in ${Y.formatUtils.pretty(t,$-u,Y.formatUtils.Type.DURATION)}`:"";E.reportInfo(null,`${l} Process exited (exit code ${I})${ee}`)}return I===130&&(G=!0,W=I),I}catch(u){throw H.end(),j.end(),await w,await c,u}};for(let C of A)x.set(C.anchoredLocator.locatorHash,C);for(;x.size>0&&!E.hasErrors();){let C=[];for(let[H,w]of x){if(T.has(w.anchoredDescriptor.descriptorHash))continue;let j=!0;if(this.topological||this.topologicalDev){let c=this.topologicalDev?new Map([...w.manifest.dependencies,...w.manifest.devDependencies]):w.manifest.dependencies;for(let u of c.values()){let I=r.tryWorkspaceByDescriptor(u);if(j=I===null||!x.has(I.anchoredLocator.locatorHash),!j)break}}if(!!j&&(T.add(w.anchoredDescriptor.descriptorHash),C.push(_(async()=>{let c=await b(w,{commandIndex:++O});return x.delete(H),T.delete(w.anchoredDescriptor.descriptorHash),c})),!y))break}if(C.length===0){let H=Array.from(x.values()).map(w=>Y.structUtils.prettyLocator(t,w.anchoredLocator)).join(", ");E.reportError(Ee.MessageName.CYCLIC_DEPENDENCIES,`Dependency cycle detected (${H})`);return}let l=(await Promise.all(C)).find(H=>H!==0);W===null&&(W=typeof l!="undefined"?1:W),(this.topological||this.topologicalDev)&&typeof l!="undefined"&&E.reportError(Ee.MessageName.UNNAMED,"The command failed for workspaces that are depended upon by other workspaces; can't satisfy the dependency graph")}});return W!==null?W:ne.exitCode()}};xe.paths=[["workspaces","foreach"]],xe.usage=U.Command.Usage({category:"Workspace-related commands",description:"run a command on all workspaces",details:"\n This command will run a given sub-command on current and all its descendant workspaces. Various flags can alter the exact behavior of the command:\n\n - If `-p,--parallel` is set, the commands will be ran in parallel; they'll by default be limited to a number of parallel tasks roughly equal to half your core number, but that can be overridden via `-j,--jobs`, or disabled by setting `-j unlimited`.\n\n - If `-p,--parallel` and `-i,--interlaced` are both set, Yarn will print the lines from the output as it receives them. If `-i,--interlaced` wasn't set, it would instead buffer the output from each process and print the resulting buffers only after their source processes have exited.\n\n - If `-t,--topological` is set, Yarn will only run the command after all workspaces that it depends on through the `dependencies` field have successfully finished executing. If `--topological-dev` is set, both the `dependencies` and `devDependencies` fields will be considered when figuring out the wait points.\n\n - If `-A,--all` is set, Yarn will run the command on all the workspaces of a project. By default yarn runs the command only on current and all its descendant workspaces.\n\n - If `-R,--recursive` is set, Yarn will find workspaces to run the command on by recursively evaluating `dependencies` and `devDependencies` fields, instead of looking at the `workspaces` fields.\n\n - If `--from` is set, Yarn will use the packages matching the 'from' glob as the starting point for any recursive search.\n\n - If `--since` is set, Yarn will only run the command on workspaces that have been modified since the specified ref. By default Yarn will use the refs specified by the `changesetBaseRefs` configuration option.\n\n - The command may apply to only some workspaces through the use of `--include` which acts as a whitelist. The `--exclude` flag will do the opposite and will be a list of packages that mustn't execute the script. Both flags accept glob patterns (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n\n Adding the `-v,--verbose` flag will cause Yarn to print more information; in particular the name of the workspace that generated the output will be printed at the front of each line.\n\n If the command is `run` and the script being run does not exist the child workspace will be skipped without error.\n ",examples:[["Publish current and all descendant packages","yarn workspaces foreach npm publish --tolerate-republish"],["Run build script on current and all descendant packages","yarn workspaces foreach run build"],["Run build script on current and all descendant packages in parallel, building package dependencies first","yarn workspaces foreach -pt run build"],["Run build script on several packages and all their dependencies, building dependencies first","yarn workspaces foreach -ptR --from '{workspace-a,workspace-b}' run build"]]});var Er=xe;function _r(e,{prefix:t,interlaced:r}){let n=e.createStreamReporter(t),s=new Y.miscUtils.DefaultStream;s.pipe(n,{end:!1}),s.on("finish",()=>{n.end()});let a=new Promise(o=>{n.on("finish",()=>{o(s.active)})});if(r)return[s,a];let i=new Y.miscUtils.BufferStream;return i.pipe(s,{end:!1}),i.on("finish",()=>{s.end()}),[i,a]}function zn(e,{configuration:t,commandIndex:r,verbose:n}){if(!n)return null;let s=Y.structUtils.convertToIdent(e.locator),i=`[${Y.structUtils.stringifyIdent(s)}]:`,o=["#2E86AB","#A23B72","#F18F01","#C73E1D","#CCE2A3"],h=o[r%o.length];return Y.formatUtils.pretty(t,i,h)}var Jn={commands:[st,Er]},es=Jn;return Vn;})(); -/*! - * fill-range - * - * Copyright (c) 2014-present, Jon Schlinkert. - * Licensed under the MIT License. - */ -/*! - * is-number - * - * Copyright (c) 2014-present, Jon Schlinkert. - * Released under the MIT License. - */ -/*! - * to-regex-range - * - * Copyright (c) 2015-present, Jon Schlinkert. - * Released under the MIT License. - */ -return plugin; -} -}; diff --git a/.yarn/releases/yarn-3.2.1.cjs.REMOVED.git-id b/.yarn/releases/yarn-3.2.1.cjs.REMOVED.git-id deleted file mode 100644 index ee9f521b7..000000000 --- a/.yarn/releases/yarn-3.2.1.cjs.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -b3cadff6efb37a12712d12c2553ec703dbcaa4dd \ No newline at end of file diff --git a/.yarn/releases/yarn-4.1.0.cjs.REMOVED.git-id b/.yarn/releases/yarn-4.1.0.cjs.REMOVED.git-id new file mode 100644 index 000000000..430c13f99 --- /dev/null +++ b/.yarn/releases/yarn-4.1.0.cjs.REMOVED.git-id @@ -0,0 +1 @@ +738adce5914a0e193f2e1255e4dcf7042256a1c1 \ No newline at end of file diff --git a/.yarnrc.yml b/.yarnrc.yml index 738e42059..2ef358b8e 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -1,11 +1,9 @@ checksumBehavior: update -nodeLinker: node-modules +compressionLevel: mixed + +enableGlobalCache: false -plugins: - - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs - spec: "@yarnpkg/plugin-workspace-tools" - - path: .yarn/plugins/@yarnpkg/plugin-version.cjs - spec: "@yarnpkg/plugin-version" +nodeLinker: node-modules -yarnPath: .yarn/releases/yarn-3.2.1.cjs +yarnPath: .yarn/releases/yarn-4.1.0.cjs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 37e4ba1b4..e7d158cc3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,7 @@ Pull requests are great if you want to add a feature or fix a bug. Here's a quic 6. Commit your changes. -7. Push to your fork and submit a pull request on our `dev` branch. Please provide us with some explanation of why you made the changes you made. For new features make sure to explain a standard use case to us. +7. Push to your fork and submit a pull request on our `main` branch. Please provide us with some explanation of why you made the changes you made. For new features make sure to explain a standard use case to us. ## CI (Github Actions) Tests @@ -93,7 +93,6 @@ Just as in the subject, use the imperative, present tense: "change" not "changed ### Branch rules - There must be a `main` branch, used only for the releases. -- There must be a `dev` branch, used to merge all the branches under it. - Avoid long descriptive names for long-lived branches. - Use kebab-case (no CamelCase). - Use grouping tokens (words) at the beginning of your branch names (in a similar way to the `type` of commit). diff --git a/LICENSE b/LICENSE index bdb9b1e08..8ef16f7a5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Ethereum Foundation +Copyright (c) 2024 Ethereum Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index e20d60c17..c745b958b 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,25 @@ The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/ Version Downloads + + + + @semaphore-protocol/core + + + + + + NPM version + + + + + + Downloads + + + @@ -214,6 +233,28 @@ The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/ + + + + @semaphore-protocol/utils + + + (docs) + + + + + + NPM version + + + + + + Downloads + + + diff --git a/apps/docs/docusaurus.config.ts b/apps/docs/docusaurus.config.ts index 12ffed701..03eed1744 100644 --- a/apps/docs/docusaurus.config.ts +++ b/apps/docs/docusaurus.config.ts @@ -55,8 +55,7 @@ const config: Config = { routeBasePath: "/", sidebarPath: require.resolve("./sidebars.js"), editUrl: "https://github.com/semaphore-protocol/semaphore/edit/main/apps/docs", - includeCurrentVersion: false, - lastVersion: "V3" + includeCurrentVersion: false }, theme: { customCss: [require.resolve("./src/css/custom.scss")] @@ -66,9 +65,9 @@ const config: Config = { ], themeConfig: { announcementBar: { - id: "semaphore-v4-alpha", + id: "semaphore-v4-beta", content: - 'Semaphore V4-alpha is out 🎉 Try it out and let us know for any feedback on Discord or Github!', + 'Semaphore V4-beta is out 🎉 Try it out and let us know for any feedback on Discord or Github!', backgroundColor: "#dde6fc", textColor: "#000000" }, diff --git a/apps/docs/i18n/en/code.json b/apps/docs/i18n/en/code.json index ec21e5b52..6bb72177c 100644 --- a/apps/docs/i18n/en/code.json +++ b/apps/docs/i18n/en/code.json @@ -392,6 +392,6 @@ "message": "X (Twitter)" }, "footer.copyright": { - "message": "Copyright © 2023 Ethereum Foundation" + "message": "Copyright © 2024 Ethereum Foundation" } } diff --git a/apps/docs/i18n/es/code.json b/apps/docs/i18n/es/code.json index e4e7c9aa6..c6b70db62 100644 --- a/apps/docs/i18n/es/code.json +++ b/apps/docs/i18n/es/code.json @@ -392,6 +392,6 @@ "message": "X (Twitter)" }, "footer.copyright": { - "message": "Copyright © 2023 Ethereum Foundation" + "message": "Copyright © 2024 Ethereum Foundation" } } diff --git a/apps/docs/versioned_docs/version-V3/faq.md b/apps/docs/versioned_docs/version-V3/faq.md index 53039c979..ada399b4b 100644 --- a/apps/docs/versioned_docs/version-V3/faq.md +++ b/apps/docs/versioned_docs/version-V3/faq.md @@ -30,7 +30,7 @@ Finally, the nullifier hash is just the hash of the identity nullifier and the e In the case of a voting application, if you have a group and you want all members of this group to vote only once, you can use the id of the group as an external nullifier. When a user votes the first time, you can save the hash of their identity nullifier and the group id (i.e. the nullifier hash) and prevent double-voting by checking if that hash already exists. -See the [Semaphore circuits](https://docs.semaphore.pse.dev/technical-reference/circuits) for more technical information, or the [Semaphore boilerplate](https://github.com/semaphore-protocol/boilerplate) for a real use-case. +See the [Semaphore circuits](https://docs.semaphore.pse.dev/technical-reference/circuits) for more technical information, or the [Semaphore boilerplate](https://github.com/semaphore-protocol/boilerplate/tree/version/3) for a real use-case. ## Why should I prevent proofs from being verified twice? @@ -44,11 +44,11 @@ You can find some applications that are using Semaphore in [this blog post](http ## How can I start a project using Semaphore? -There are three ways you can start using Semaphore in your project: using the [Semaphore CLI](https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli), using the [Semaphore boilerplate](https://github.com/semaphore-protocol/boilerplate) as a template or forking it, or installing the Semaphore packages manually. +There are three ways you can start using Semaphore in your project: using the [Semaphore CLI](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/cli), using the [Semaphore boilerplate](https://github.com/semaphore-protocol/boilerplate/tree/version/3) as a template or forking it, or installing the Semaphore packages manually. ### Semaphore CLI -To create a new project you could use `npx` or install the [Semaphore CLI](https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli) globally using `npm` and then create the new project using the `semaphore create` command. See the [Quick Setup](https://docs.semaphore.pse.dev/quick-setup) for more information. +To create a new project you could use `npx` or install the [Semaphore CLI](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/cli) globally using `npm` and then create the new project using the `semaphore create` command. See the [Quick Setup](https://docs.semaphore.pse.dev/quick-setup) for more information. There are three supported templates right now: `contracts-hardhat`, `monorepo-ethers` and `monorepo-subgraph`. @@ -65,7 +65,7 @@ The Semaphore CLI can also be used to get group data from a supported network. T ### Semaphore boilerplate -To create a project, you could also use the [Semaphore boilerplate](https://github.com/semaphore-protocol/boilerplate). You could fork it or use it as a template. +To create a project, you could also use the [Semaphore boilerplate](https://github.com/semaphore-protocol/boilerplate/tree/version/3). You could fork it or use it as a template. The Semaphore CLI templates and the Semaphore boilerplate contain the same code, which is a feedback application where you can create an identity, join a group, and send your feedback anonymously. They are almost the same, the only difference is that the templates use plain CSS so you can decide the CSS framework or library you want to use and the boilerplate uses [ChakraUI](https://chakra-ui.com/) by default. diff --git a/apps/docs/versioned_docs/version-V3/glossary.md b/apps/docs/versioned_docs/version-V3/glossary.md index 197914690..515be65f5 100644 --- a/apps/docs/versioned_docs/version-V3/glossary.md +++ b/apps/docs/versioned_docs/version-V3/glossary.md @@ -39,7 +39,7 @@ For more information, see [Merkle tree in Wikipedia](https://en.wikipedia.org/wi A value used to prevent double entry or double signalling. -See [Circuit nullifier hash](/technical-reference/circuits/#nullifier-hash). +See [Circuit nullifier hash](/V3/technical-reference/circuits/#nullifier-hash). ## Relay diff --git a/apps/docs/versioned_docs/version-V3/guides/fetching-data.mdx b/apps/docs/versioned_docs/version-V3/guides/fetching-data.mdx index 4229785f5..12f2b59cf 100644 --- a/apps/docs/versioned_docs/version-V3/guides/fetching-data.mdx +++ b/apps/docs/versioned_docs/version-V3/guides/fetching-data.mdx @@ -8,9 +8,9 @@ import TabItem from "@theme/TabItem" # Semaphore data -To fetch on-chain data from the [Semaphore.sol](https://github.com/semaphore-protocol/semaphore/blob/main/packages/contracts/contracts/Semaphore.sol) contract, you can use the [@semaphore-protocol/data](https://github.com/semaphore-protocol/semaphore/tree/main/packages/data) library. +To fetch on-chain data from the [Semaphore.sol](https://github.com/semaphore-protocol/semaphore/blob/v3.15.2/packages/contracts/contracts/Semaphore.sol) contract, you can use the [@semaphore-protocol/data](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/data) library. -There are two ways to do this, using [`SemaphoreSubgraph`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/data/src/subgraph.ts) or [`SemaphoreEthers`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/data/src/ethers.ts). The `SemaphoreSubgraph` class uses the [Semaphore subgraph](https://github.com/semaphore-protocol/subgraph), which uses [The Graph Protocol](https://thegraph.com/) under the hood, and the `SemaphoreEthers` class uses [Ethers](https://github.com/ethers-io/ethers.js/). +There are two ways to do this, using [`SemaphoreSubgraph`](https://github.com/semaphore-protocol/semaphore/blob/v3.15.2/packages/data/src/subgraph.ts) or [`SemaphoreEthers`](https://github.com/semaphore-protocol/semaphore/blob/v3.15.2/packages/data/src/ethers.ts). The `SemaphoreSubgraph` class uses the [Semaphore subgraph](https://github.com/semaphore-protocol/subgraph), which uses [The Graph Protocol](https://thegraph.com/) under the hood, and the `SemaphoreEthers` class uses [Ethers](https://github.com/ethers-io/ethers.js/). - [**Fetch data using SemaphoreSubgraph**](#fetch-data-using-semaphoresubgraph) - [**Fetch data using SemaphoreEthers**](#fetch-data-using-semaphoreethers) @@ -50,7 +50,7 @@ pnpm add @semaphore-protocol/data@^3 ## Fetch data using SemaphoreSubgraph -To fetch data using the Semaphore subgraph you can use the [`SemaphoreSubgraph`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/data/src/subgraph.ts) class from the [@semaphore-protocol/data](https://github.com/semaphore-protocol/semaphore/tree/main/packages/data) package. +To fetch data using the Semaphore subgraph you can use the [`SemaphoreSubgraph`](https://github.com/semaphore-protocol/semaphore/blob/v3.15.2/packages/data/src/subgraph.ts) class from the [@semaphore-protocol/data](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/data) package. ```typescript import { SemaphoreSubgraph } from "@semaphore-protocol/data" @@ -117,7 +117,7 @@ const group = new Group(groupId, 20, members) ## Fetch data using SemaphoreEthers -To fetch data using Ethers you can use the [`SemaphoreEthers`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/data/src/ethers.ts) class from the [@semaphore-protocol/data](https://github.com/semaphore-protocol/semaphore/tree/main/packages/data) package. +To fetch data using Ethers you can use the [`SemaphoreEthers`](https://github.com/semaphore-protocol/semaphore/blob/v3.15.2/packages/data/src/ethers.ts) class from the [@semaphore-protocol/data](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/data) package. ```typescript import { SemaphoreEthers } from "@semaphore-protocol/data" diff --git a/apps/docs/versioned_docs/version-V3/guides/groups.mdx b/apps/docs/versioned_docs/version-V3/guides/groups.mdx index 0ef138c2b..ea2f9a556 100644 --- a/apps/docs/versioned_docs/version-V3/guides/groups.mdx +++ b/apps/docs/versioned_docs/version-V3/guides/groups.mdx @@ -8,14 +8,14 @@ import TabItem from "@theme/TabItem" # Semaphore groups -A [Semaphore group](/glossary/#semaphore-group) contains [identity commitments](/glossary/#identity-commitment) of group members. +A [Semaphore group](/V3/glossary/#semaphore-group) contains [identity commitments](/V3/glossary/#identity-commitment) of group members. Example uses of groups include the following: - poll question that attendees join to rate an event, - ballot that members join to vote on a proposal, - whistleblowers who are verified employees of an organization. -A Semaphore group is an [incremental Merkle tree](/glossary/#incremental-merkle-tree), and group members (i.e., [identity commitments](/glossary/#identity-commitments)) are tree leaves. +A Semaphore group is an [incremental Merkle tree](/V3/glossary/#incremental-merkle-tree), and group members (i.e., [identity commitments](/V3/glossary/#identity-commitments)) are tree leaves. Semaphore groups set the following three parameters: - **Group id**: a unique identifier for the group; @@ -35,7 +35,7 @@ Learn how to work with groups. ### Create a group -Use the [`@semaphore-protocol/group`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/group) library `Group` class to create an off-chain group with the following parameters: +Use the [`@semaphore-protocol/group`](https://github.com/semaphore-protocol/semaphore/blob/v3.15.2/packages/group) library `Group` class to create an off-chain group with the following parameters: - `Group id`: a unique identifier for the group; - `Tree depth`: (_default `20`_) the maximum number of members a group can contain (`max size = 2 ^ tree depth`). @@ -142,13 +142,13 @@ Given that the node isn't removed, and the length of the `group.members` array d ## On-chain groups -The [`SemaphoreGroups`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/contracts/contracts/base/SemaphoreGroups.sol) contract uses the [`IncrementalBinaryTree`](https://github.com/privacy-scaling-explorations/zk-kit/blob/main/packages/incremental-merkle-tree.sol/contracts/IncrementalBinaryTree.sol) library and provides methods to create and manage groups. +The [`SemaphoreGroups`](https://github.com/semaphore-protocol/semaphore/blob/v3.15.2/packages/contracts/contracts/base/SemaphoreGroups.sol) contract uses the [`IncrementalBinaryTree`](https://github.com/privacy-scaling-explorations/zk-kit/blob/main/packages/incremental-merkle-tree.sol/contracts/IncrementalBinaryTree.sol) library and provides methods to create and manage groups. :::info -You can import `SemaphoreGroups.sol` and other Semaphore contracts from the [`@semaphore-protocol/contracts`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/contracts) NPM module. +You can import `SemaphoreGroups.sol` and other Semaphore contracts from the [`@semaphore-protocol/contracts`](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/contracts) NPM module. ::: -Alternatively, you can use an already deployed [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/contracts/contracts/Semaphore.sol) contract and use its group external functions. +Alternatively, you can use an already deployed [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/v3.15.2/packages/contracts/contracts/Semaphore.sol) contract and use its group external functions. :::caution `Semaphore.sol` does not check if a member with a specific identity commitment already exists in a group. This check must be done off-chain. diff --git a/apps/docs/versioned_docs/version-V3/guides/identities.mdx b/apps/docs/versioned_docs/version-V3/guides/identities.mdx index bf7e36c97..32cf29f7c 100644 --- a/apps/docs/versioned_docs/version-V3/guides/identities.mdx +++ b/apps/docs/versioned_docs/version-V3/guides/identities.mdx @@ -8,7 +8,7 @@ import TabItem from "@theme/TabItem" # Semaphore identities -In order to join a [Semaphore group](/glossary#semaphore-group), a user must first create a [Semaphore identity](/glossary#semaphore-identity). +In order to join a [Semaphore group](/V3/glossary#semaphore-group), a user must first create a [Semaphore identity](/V3/glossary#semaphore-identity). A Semaphore identity contains two values generated with the identity: - Identity trapdoor @@ -19,7 +19,7 @@ To prevent fraud, the owner should keep both values secret. ## Create identities -In your code, use the [`@semaphore-protocol/identity`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/identity) library to create a Semaphore identity _deterministically_ (from the hash of a message) or _randomly_. +In your code, use the [`@semaphore-protocol/identity`](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/identity) library to create a Semaphore identity _deterministically_ (from the hash of a message) or _randomly_. - [**Create random identities**](#create-random-identities) - [**Create deterministic identities**](#create-deterministic-identities) diff --git a/apps/docs/versioned_docs/version-V3/guides/proofs.mdx b/apps/docs/versioned_docs/version-V3/guides/proofs.mdx index 88b4b86cd..5de3e4f34 100644 --- a/apps/docs/versioned_docs/version-V3/guides/proofs.mdx +++ b/apps/docs/versioned_docs/version-V3/guides/proofs.mdx @@ -8,7 +8,7 @@ import TabItem from "@theme/TabItem" # Semaphore proofs -Once a user joins their [Semaphore identity](/glossary#semaphore-identity) to a [Semaphore group](/glossary#semaphore-group), the user can signal anonymously with a zero-knowledge proof that proves the following: +Once a user joins their [Semaphore identity](/V3/glossary#semaphore-identity) to a [Semaphore group](/V3/glossary#semaphore-group), the user can signal anonymously with a zero-knowledge proof that proves the following: - the user is a member of the group, - the same user created the signal and the proof. @@ -21,14 +21,14 @@ Developers can use Semaphore for the following: ## Generate a proof off-chain -Use the [`@semaphore-protocol/proof`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/proof) library to generate an off-chain proof. +Use the [`@semaphore-protocol/proof`](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/proof) library to generate an off-chain proof. To generate a proof, pass the following parameters to the `generateProof` function: - `identity`: the Semaphore identity of the user broadcasting the signal and generating the proof; - `group`: the group to which the user belongs; - `externalNullifier`: the value that prevents double-signaling; - `signal`: the signal the user wants to send anonymously; -- `snarkArtifacts`: the `zkey` and `wasm` [trusted setup files](/glossary/#trusted-setup-files). +- `snarkArtifacts`: the `zkey` and `wasm` [trusted setup files](/V3/glossary/#trusted-setup-files). #### Install library: @@ -63,9 +63,9 @@ pnpm add @semaphore-protocol/proof@^3 -In the voting system use case, once all the voters have joined their [identities](/guides/identities#create-an-identity) to the ballot [group](/guides/groups), +In the voting system use case, once all the voters have joined their [identities](/V3/guides/identities#create-an-identity) to the ballot [group](/V3/guides/groups), a voter can generate a proof to vote for a proposal. -In the call to `generateProof`, the voting system passes the unique ballot ID (the [Merkle tree](/glossary/#merkle-tree/) root of the group) as the +In the call to `generateProof`, the voting system passes the unique ballot ID (the [Merkle tree](/V3/glossary/#merkle-tree/) root of the group) as the `externalNullifier` to prevent the voter signaling more than once for the ballot. The following code sample shows how to use `generateProof` to generate the voting proof: @@ -91,7 +91,7 @@ const fullProof = await generateProof(identity, group, externalNullifier, signal ## Verify a proof off-chain -Use the [`@semaphore-protocol/proof`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/proof) library to verify a Semaphore proof off-chain. +Use the [`@semaphore-protocol/proof`](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/proof) library to verify a Semaphore proof off-chain. To verify a proof, pass the following to the `verifyProof` function: - `fullProof`: the Semaphore proof; @@ -109,10 +109,10 @@ await verifyProof(fullProof, 20) // true or false. ## Verify a proof on-chain -Use the [`Semaphore.sol`](/technical-reference/contracts#semaphoresol) contract to verify proofs on-chain. +Use the [`Semaphore.sol`](/V3/technical-reference/contracts#semaphoresol) contract to verify proofs on-chain. :::info -See our [deployed contracts](/deployed-contracts) to find the addresses for your network. +See our [deployed contracts](/V3/deployed-contracts) to find the addresses for your network. :::: To verify Semaphore proofs in your contract, import `ISemaphore.sol`, pass it the `Semaphore.sol` address and call the `verifyProof` method with following parameters: @@ -125,5 +125,5 @@ To verify Semaphore proofs in your contract, import `ISemaphore.sol`, pass it th - `proof`: a [Solidity-compatible Semaphore proof](#generate-a-solidity-compatible-proof). :::info -You can import `ISemaphore.sol` and other Semaphore contracts from the [`@semaphore-protocol/contracts`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/contracts) NPM module. +You can import `ISemaphore.sol` and other Semaphore contracts from the [`@semaphore-protocol/contracts`](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/contracts) NPM module. ::: diff --git a/apps/docs/versioned_docs/version-V3/quick-setup.mdx b/apps/docs/versioned_docs/version-V3/quick-setup.mdx index 3f66908ce..5a96e5d80 100644 --- a/apps/docs/versioned_docs/version-V3/quick-setup.mdx +++ b/apps/docs/versioned_docs/version-V3/quick-setup.mdx @@ -21,11 +21,11 @@ semaphore create my-app --template monorepo-ethers ``` :::info -The supported templates are: [`contracts-hardhat`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli-template-contracts-hardhat), [`monorepo-ethers`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli-template-monorepo-ethers), [`monorepo-subgraph`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli-template-monorepo-subgraph). +The supported templates are: [`contracts-hardhat`](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/cli-template-contracts-hardhat), [`monorepo-ethers`](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/cli-template-monorepo-ethers), [`monorepo-subgraph`](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/cli-template-monorepo-subgraph). ::: :::info -The [`semaphore CLI`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli) can also be used to get group data from a supported network (e.g `semaphore get-groups --network arbitrum-goerli`). +The [`semaphore CLI`](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/cli) can also be used to get group data from a supported network (e.g `semaphore get-groups --network arbitrum-goerli`). ::: To start working on your project, install the dependencies: @@ -293,7 +293,7 @@ In the project root folder: :::note - Check the Semaphore contract addresses [here](/deployed-contracts). + Check the Semaphore contract addresses [here](/V3/deployed-contracts). ::: :::caution diff --git a/apps/docs/versioned_docs/version-V3/subgraph.md b/apps/docs/versioned_docs/version-V3/subgraph.md index 113f1f5ec..f064040a6 100644 --- a/apps/docs/versioned_docs/version-V3/subgraph.md +++ b/apps/docs/versioned_docs/version-V3/subgraph.md @@ -6,7 +6,7 @@ sidebar_position: 6 [The Graph](https://thegraph.com/) is a protocol for indexing networks like Ethereum and IPFS. Site owners publish _subgraphs_ that expose site data for anyone to query. -Semaphore's subgraph allows you to retrieve data from the [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/contracts/Semaphore.sol) smart contract. +Semaphore's subgraph allows you to retrieve data from the [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/v3.15.2/packages/contracts/Semaphore.sol) smart contract. :::tip The Graph protocol uses the [GraphQL](https://graphql.org/) query language. For examples, see the [GraphQL API documentation](https://thegraph.com/docs/developer/graphql-api). Visit the [subgraph repository](https://github.com/semaphore-protocol/subgraph) to see the list of Semaphore subgraphs. diff --git a/apps/docs/versioned_docs/version-V3/technical-reference/circuits.md b/apps/docs/versioned_docs/version-V3/technical-reference/circuits.md index 405f72574..25d45d9c1 100644 --- a/apps/docs/versioned_docs/version-V3/technical-reference/circuits.md +++ b/apps/docs/versioned_docs/version-V3/technical-reference/circuits.md @@ -4,13 +4,13 @@ sidebar_position: 2 # Circuits -The [Semaphore circuit](https://github.com/semaphore-protocol/semaphore/tree/main/packages/circuits) is the heart of the protocol and consists of three parts: +The [Semaphore circuit](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/circuits) is the heart of the protocol and consists of three parts: -- [**Proof of membership**](/technical-reference/circuits#proof-of-membership) -- [**Nullifier hash**](/technical-reference/circuits#nullifier-hash) -- [**Signal**](/technical-reference/circuits#signal) +- [**Proof of membership**](/V3/technical-reference/circuits#proof-of-membership) +- [**Nullifier hash**](/V3/technical-reference/circuits#nullifier-hash) +- [**Signal**](/V3/technical-reference/circuits#signal) -![Semaphore circuit](https://github.com/semaphore-protocol/semaphore/raw/main/packages/circuits/scheme.png) +![Semaphore circuit](https://github.com/semaphore-protocol/semaphore/raw/v3.15.2/packages/circuits/scheme.png) The diagram above shows how the input signals are used in the Semaphore circuit and how the outputs are calculated. diff --git a/apps/docs/versioned_docs/version-V3/technical-reference/contracts.md b/apps/docs/versioned_docs/version-V3/technical-reference/contracts.md index b1c4c30f7..a6513f340 100644 --- a/apps/docs/versioned_docs/version-V3/technical-reference/contracts.md +++ b/apps/docs/versioned_docs/version-V3/technical-reference/contracts.md @@ -6,25 +6,25 @@ sidebar_position: 3 Semaphore includes two types of contracts: -- [**Base contracts**](/technical-reference/contracts#base-contracts) -- [**Extension contracts**](/technical-reference/contracts#extension-contracts) +- [**Base contracts**](/V3/technical-reference/contracts#base-contracts) +- [**Extension contracts**](/V3/technical-reference/contracts#extension-contracts) -And [**Semaphore.sol**](/technical-reference/contracts#semaphoresol), the main contract deployed on the networks supported by Semaphore. +And [**Semaphore.sol**](/V3/technical-reference/contracts#semaphoresol), the main contract deployed on the networks supported by Semaphore. :::info To use Semaphore contracts and interfaces in your project, -install the [`@semaphore-protocol/contracts`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/contracts) NPM package. +install the [`@semaphore-protocol/contracts`](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/contracts) NPM package. ::: ## Base contracts Semaphore provides the following base contracts: -- [`SemaphoreVerifier.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/contracts/contracts/base/SemaphoreVerifier.sol): contains a function to verify Semaphore proofs; -- [`SemaphoreGroups.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/contracts/contracts/base/SemaphoreGroups.sol): contains the functions to create groups and add/remove/update members. +- [`SemaphoreVerifier.sol`](https://github.com/semaphore-protocol/semaphore/blob/v3.15.2/packages/contracts/contracts/base/SemaphoreVerifier.sol): contains a function to verify Semaphore proofs; +- [`SemaphoreGroups.sol`](https://github.com/semaphore-protocol/semaphore/blob/v3.15.2/packages/contracts/contracts/base/SemaphoreGroups.sol): contains the functions to create groups and add/remove/update members. These contracts are closely related to the protocol. -You can use them in your contract or you can use [**Semaphore.sol**](/technical-reference/contracts#semaphoresol), which integrates them for you. +You can use them in your contract or you can use [**Semaphore.sol**](/V3/technical-reference/contracts#semaphoresol), which integrates them for you. :::info While some DApps may use on-chain groups, others may prefer to use off-chain groups, saving only their tree roots in the contract. @@ -32,20 +32,20 @@ While some DApps may use on-chain groups, others may prefer to use off-chain gro ## Extension contracts -- [`SemaphoreVoting.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/contracts/contracts/extensions/SemaphoreVoting.sol): voting contract that contains the essential functions to create polls, add voters, and anonymously cast votes; -- [`SemaphoreWhistleblowing.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/contracts/contracts/extensions/SemaphoreWhistleblowing.sol): whistleblowing contract that contains the essential functions to create entities (for example: non-profit organizations), add whistleblowers, and anonymously publish leaks. +- [`SemaphoreVoting.sol`](https://github.com/semaphore-protocol/semaphore/blob/v3.15.2/packages/contracts/contracts/extensions/SemaphoreVoting.sol): voting contract that contains the essential functions to create polls, add voters, and anonymously cast votes; +- [`SemaphoreWhistleblowing.sol`](https://github.com/semaphore-protocol/semaphore/blob/v3.15.2/packages/contracts/contracts/extensions/SemaphoreWhistleblowing.sol): whistleblowing contract that contains the essential functions to create entities (for example: non-profit organizations), add whistleblowers, and anonymously publish leaks. These contracts extend the protocol to provide application logic for specific use-cases. More extensions will be added in the future. ## Semaphore.sol -[`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/contracts/contracts/Semaphore.sol) is based on the base contracts. It integrates them and additionally provides: +[`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/v3.15.2/packages/contracts/contracts/Semaphore.sol) is based on the base contracts. It integrates them and additionally provides: - a system to allow only admins (i.e. Ethereum accounts or smart contracts) to manage groups; -- a mechanism to save the [nullifier hashes](/technical-reference/circuits#nullifier-hash) of each group and prevent double-signaling; +- a mechanism to save the [nullifier hashes](/V3/technical-reference/circuits#nullifier-hash) of each group and prevent double-signaling; - a mechanism to allow Semaphore proofs generated with old Merkle roots to be verified for a certain period of time defined by the group admin. :::info -See our [deployed contracts](/deployed-contracts) to find the addresses for your network. +See our [deployed contracts](/V3/deployed-contracts) to find the addresses for your network. :::: diff --git a/apps/docs/versioned_docs/version-V3/troubleshooting.mdx b/apps/docs/versioned_docs/version-V3/troubleshooting.mdx index a0e2cc0e2..26aa734ac 100644 --- a/apps/docs/versioned_docs/version-V3/troubleshooting.mdx +++ b/apps/docs/versioned_docs/version-V3/troubleshooting.mdx @@ -11,7 +11,7 @@ If these suggestions do not work, feel free to ask in the [Semaphore Discussions ## Using Semaphore in the frontend -Semaphore works with any JavaScript frontend framework, but the [`@semaphore-protocol/proof`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/proof) package is using [snarkjs](https://github.com/iden3/snarkjs), which uses Node.js modules which are not compatible with frontend frameworks and there are some changes that we need to do to make it work on the client side. +Semaphore works with any JavaScript frontend framework, but the [`@semaphore-protocol/proof`](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/proof) package is using [snarkjs](https://github.com/iden3/snarkjs), which uses Node.js modules which are not compatible with frontend frameworks and there are some changes that we need to do to make it work on the client side. ### Semaphore with Nextjs @@ -229,10 +229,10 @@ Your `tsconfig.json` file would be something like this: When you create a group and the transaction is reverted, make sure that the group id you are using does not exist on the network you are using. -To check that, you can use the [Semaphore CLI](https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli) with the command `get-groups` and the network you are using and then, make sure that your group id is not part of that list. You can also use the [Semaphore explorer](https://explorer.semaphore.pse.dev/). +To check that, you can use the [Semaphore CLI](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/cli) with the command `get-groups` and the network you are using and then, make sure that your group id is not part of that list. You can also use the [Semaphore explorer](https://explorer.semaphore.pse.dev/). ## Semaphore Proofs ### Transaction reverted when using the same external nullifier -When you generate a proof using the same external nullifier you used to verify a proof before, the transaction will be reverted because that external nullifier was already used. If you want to send and verify several proofs from the same identity, you should use a different external nullifier each time you generate a proof. \ No newline at end of file +When you generate a proof using the same external nullifier you used to verify a proof before, the transaction will be reverted because that external nullifier was already used. If you want to send and verify several proofs from the same identity, you should use a different external nullifier each time you generate a proof. diff --git a/apps/docs/versioned_docs/version-V3/what-is-semaphore.md b/apps/docs/versioned_docs/version-V3/what-is-semaphore.md index 650cdc9d8..ef0c2b9f8 100644 --- a/apps/docs/versioned_docs/version-V3/what-is-semaphore.md +++ b/apps/docs/versioned_docs/version-V3/what-is-semaphore.md @@ -7,7 +7,7 @@ slug: / ## Overview -[Semaphore](https://github.com/semaphore-protocol/semaphore) is a [zero-knowledge](https://z.cash/technology/zksnarks) protocol that allows you to cast a signal (for example, a vote or endorsement) as a provable group member without revealing your identity. +[Semaphore](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2) is a [zero-knowledge](https://z.cash/technology/zksnarks) protocol that allows you to cast a signal (for example, a vote or endorsement) as a provable group member without revealing your identity. Additionally, it provides a simple mechanism to prevent double-signaling. Use cases include private voting, whistleblowing, anonymous DAOs and mixers. @@ -15,9 +15,9 @@ Use cases include private voting, whistleblowing, anonymous DAOs and mixers. With Semaphore, you can allow your users to do the following: -1. [Create a Semaphore identity](/guides/identities/). -2. [Add their Semaphore identity to a group (i.e. _Merkle tree_)](/guides/groups/). -3. [Send a verifiable, anonymous signal (e.g a vote or endorsement)](/guides/proofs/). +1. [Create a Semaphore identity](/V3/guides/identities/). +2. [Add their Semaphore identity to a group (i.e. _Merkle tree_)](/V3/guides/groups/). +3. [Send a verifiable, anonymous signal (e.g a vote or endorsement)](/V3/guides/proofs/). When a user broadcasts a signal (for example: a vote), Semaphore zero-knowledge proofs can ensure that the user has joined the group and hasn't already cast a signal with their nullifier. @@ -33,14 +33,14 @@ Semaphore is designed to be a simple and generic _privacy layer_ for decentraliz ## About the code -The core of the protocol is the [circuit logic](https://github.com/semaphore-protocol/semaphore/tree/main/packages/circuits/scheme.png). +The core of the protocol is the [circuit logic](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/circuits/scheme.png). In addition to circuits, -Semaphore provides [Solidity contracts](https://github.com/semaphore-protocol/semaphore/tree/main/packages/contracts) -and [JavaScript libraries](https://github.com/semaphore-protocol/semaphore#-packages) that allow developers to generate zero-knowledge proofs and verify them with minimal effort. +Semaphore provides [Solidity contracts](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2/packages/contracts) +and [JavaScript libraries](https://github.com/semaphore-protocol/semaphore/tree/v3.15.2#-packages) that allow developers to generate zero-knowledge proofs and verify them with minimal effort. ### Trusted Setup Ceremony -The [secure parameters](/glossary#trusted-setup-files) for generating valid proofs with Semaphore circuits were generated in a [Trusted Setup Ceremony](https://storage.googleapis.com/trustedsetup-a86f4.appspot.com/semaphore/semaphore_top_index.html) that was completed with over 300 participants on [29 March 2022](https://etherscan.io/tx/0xec6dbe68883c7593c2bea82f55af18b3aeb5cc146e026d0083a9b3faa9aa0b65#eventlog). +The [secure parameters](/V3/glossary#trusted-setup-files) for generating valid proofs with Semaphore circuits were generated in a [Trusted Setup Ceremony](https://storage.googleapis.com/trustedsetup-a86f4.appspot.com/semaphore/semaphore_top_index.html) that was completed with over 300 participants on [29 March 2022](https://etherscan.io/tx/0xec6dbe68883c7593c2bea82f55af18b3aeb5cc146e026d0083a9b3faa9aa0b65#eventlog). ### Audits diff --git a/apps/docs/versioned_docs/version-V4-alpha/troubleshooting.md b/apps/docs/versioned_docs/version-V4-alpha/troubleshooting.md deleted file mode 100644 index cf80626d2..000000000 --- a/apps/docs/versioned_docs/version-V4-alpha/troubleshooting.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -sidebar_position: 9 ---- - -import Tabs from "@theme/Tabs" -import TabItem from "@theme/TabItem" - -# Troubleshooting - -If these suggestions do not work, feel free to ask for more help and support on [Github Discussions](https://github.com/semaphore-protocol/semaphore/discussions) or [Discord](https://semaphore.pse.dev/discord) ("dev-chat" channel). - -## Creating a Group - -When you create a group and the transaction is reverted, make sure that the group id you are using does not exist on the network you are using. - -To check that, you can use the [Semaphore CLI](https://github.com/semaphore-protocol/semaphore/tree/feat/semaphore-v4/packages/cli) with the command `get-groups` and the network you are using and then, make sure that your group id is not part of that list. You can also use the [Semaphore explorer](https://explorer.semaphore.pse.dev/). - -## Transaction reverted when using the same nullifier - -When you generate a proof using the same [scope](/V4-alpha/glossary#scope) you used to validate a proof before, the transaction will be reverted because that scope (and thus the [nullifier](/V4-alpha/glossary#nullifier)) has already been used. If you want to send and validate several proofs from the same identity, you need to use a different scope for each time you generate a proof. diff --git a/apps/docs/versioned_docs/version-V4-alpha/credits.md b/apps/docs/versioned_docs/version-V4-beta/credits.md similarity index 100% rename from apps/docs/versioned_docs/version-V4-alpha/credits.md rename to apps/docs/versioned_docs/version-V4-beta/credits.md diff --git a/apps/docs/versioned_docs/version-V4-alpha/deployed-contracts.mdx b/apps/docs/versioned_docs/version-V4-beta/deployed-contracts.mdx similarity index 100% rename from apps/docs/versioned_docs/version-V4-alpha/deployed-contracts.mdx rename to apps/docs/versioned_docs/version-V4-beta/deployed-contracts.mdx diff --git a/apps/docs/versioned_docs/version-V4-alpha/faq.md b/apps/docs/versioned_docs/version-V4-beta/faq.md similarity index 62% rename from apps/docs/versioned_docs/version-V4-alpha/faq.md rename to apps/docs/versioned_docs/version-V4-beta/faq.md index c4692320a..9a56b5534 100644 --- a/apps/docs/versioned_docs/version-V4-alpha/faq.md +++ b/apps/docs/versioned_docs/version-V4-beta/faq.md @@ -16,13 +16,13 @@ For example, in an anonymous voting application a valid proof could be reused to ## What is the difference between the "nullifier" and "scope"? -The [scope](/V4-alpha/glossary#scope) is used like a topic on which users can generate a valid proof only once. The scope is a public value and every one can see what the scope of a proof is. +The [scope](/glossary#scope) is used like a topic on which users can generate a valid proof only once. The scope is a public value and every one can see what the scope of a proof is. -The [nullifier](/V4-alpha/glossary#nullifier) is the hash of the private key of the identity and the scope, and it is used to check if the same proof with that specific scope has already been generated by the same user. The nullifier is also a public value and it is what is actually stored to prevent, for example, double-voting. +The [nullifier](/glossary#nullifier) is the hash of the private key of the identity and the scope, and it is used to check if the same proof with that specific scope has already been generated by the same user. The nullifier is also a public value and it is what is actually stored to prevent, for example, double-voting. In the case of a voting application, if you have a group and you want all members of this group to vote only once, you can use the id of the group as the scope. When a user votes the first time, you can store the hash of voter's private key and the group id (i.e., the nullifier) and prevent double-voting by checking if that hash already exists. -See the [Semaphore circuits](/V4-alpha/technical-reference/circuits) for more technical information, or the [Semaphore boilerplate](https://github.com/semaphore-protocol/boilerplate/tree/version/4) for a real use-case. +See the [Semaphore circuits](/technical-reference/circuits) for more technical information, or the [Semaphore boilerplate](https://github.com/semaphore-protocol/boilerplate/tree/main) for a real use-case. ## Where can I find examples of applications using Semaphore? @@ -30,7 +30,7 @@ You can find a complete list of applications that are using Semaphore on the [Se ## How can I start a project using Semaphore? -There are three ways you can start using Semaphore in your project: using the [CLI](https://github.com/semaphore-protocol/semaphore/tree/feat/semaphore-v4/packages/cli), using the [boilerplate](https://github.com/semaphore-protocol/boilerplate/tree/version/4) as a template or forking it, or installing the Semaphore [packages](/V4-alpha/guides/identities) manually. +There are three ways you can start using Semaphore in your project: using the [CLI](https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli), using the [boilerplate](https://github.com/semaphore-protocol/boilerplate/tree/main) as a template or forking it, or installing the Semaphore [packages](/guides/identities) manually. ## How can I contribute to the protocol? diff --git a/apps/docs/versioned_docs/version-V4-alpha/getting-started.mdx b/apps/docs/versioned_docs/version-V4-beta/getting-started.mdx similarity index 88% rename from apps/docs/versioned_docs/version-V4-alpha/getting-started.mdx rename to apps/docs/versioned_docs/version-V4-beta/getting-started.mdx index f1f131cc1..9cd9549cd 100644 --- a/apps/docs/versioned_docs/version-V4-alpha/getting-started.mdx +++ b/apps/docs/versioned_docs/version-V4-beta/getting-started.mdx @@ -21,11 +21,11 @@ semaphore create my-app --template monorepo-ethers ``` :::info -The supported templates are: [`contracts-hardhat`](https://github.com/semaphore-protocol/semaphore/tree/feat/semaphore-v4/packages/cli-template-contracts-hardhat), [`monorepo-ethers`](https://github.com/semaphore-protocol/semaphore/tree/feat/semaphore-v4/packages/cli-template-monorepo-ethers), [`monorepo-subgraph`](https://github.com/semaphore-protocol/semaphore/tree/feat/semaphore-v4/packages/cli-template-monorepo-subgraph). +The supported templates are: [`contracts-hardhat`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli-template-contracts-hardhat), [`monorepo-ethers`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli-template-monorepo-ethers), [`monorepo-subgraph`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli-template-monorepo-subgraph). ::: :::info -The [`semaphore CLI`](https://github.com/semaphore-protocol/semaphore/tree/feat/semaphore-v4/packages/cli) can also be used to get group data from a supported network (e.g. `semaphore get-groups --network sepolia`). +The [`semaphore CLI`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli) can also be used to get group data from a supported network (e.g. `semaphore get-groups --network sepolia`). ::: To start working on your project, install the dependencies: @@ -289,7 +289,7 @@ In the project root folder: :::note - Check the Semaphore contract addresses [here](/V4-alpha/deployed-contracts). + Check the Semaphore contract addresses [here](/deployed-contracts). ::: :::caution @@ -332,5 +332,5 @@ pnpm dev :::info -If you want to see the code of a comprehensive application built on top of Semaphore see the [boilerplate](https://github.com/semaphore-protocol/boilerplate/tree/version/4). For more info about the core libraries, keep reading the next guides. +If you want to see the code of a comprehensive application built on top of Semaphore see the [boilerplate](https://github.com/semaphore-protocol/boilerplate/tree/main). For more info about the core libraries, keep reading the next guides. ::: diff --git a/apps/docs/versioned_docs/version-V4-alpha/glossary.md b/apps/docs/versioned_docs/version-V4-beta/glossary.md similarity index 92% rename from apps/docs/versioned_docs/version-V4-alpha/glossary.md rename to apps/docs/versioned_docs/version-V4-beta/glossary.md index 099f12cdf..0d0f0679a 100644 --- a/apps/docs/versioned_docs/version-V4-alpha/glossary.md +++ b/apps/docs/versioned_docs/version-V4-beta/glossary.md @@ -46,4 +46,4 @@ A trusted setup in the context of zero-knowledge proofs, particularly zk-SNARKs, ## Trusted setup files -The secure, verifiable parameters generated by Semaphore's trusted setup ceremony. Semaphore uses the trusted setup files to generate and verify valid zero-knowledge proofs. The [Semaphore circuit](/V4-alpha/technical-reference/circuits) includes a parameter to set the tree's maximum depth (MAX_DEPTH). During the trusted setup, parameters are specifically generated for each circuit instance, aligning with their designated MAX_DEPTH (from 1 to 32). +The secure, verifiable parameters generated by Semaphore's trusted setup ceremony. Semaphore uses the trusted setup files to generate and verify valid zero-knowledge proofs. The [Semaphore circuit](/technical-reference/circuits) includes a parameter to set the tree's maximum depth (MAX_DEPTH). During the trusted setup, parameters are specifically generated for each circuit instance, aligning with their designated MAX_DEPTH (from 1 to 32). diff --git a/apps/docs/versioned_docs/version-V4-alpha/guides/_category_.json b/apps/docs/versioned_docs/version-V4-beta/guides/_category_.json similarity index 100% rename from apps/docs/versioned_docs/version-V4-alpha/guides/_category_.json rename to apps/docs/versioned_docs/version-V4-beta/guides/_category_.json diff --git a/apps/docs/versioned_docs/version-V4-alpha/guides/groups.mdx b/apps/docs/versioned_docs/version-V4-beta/guides/groups.mdx similarity index 84% rename from apps/docs/versioned_docs/version-V4-alpha/guides/groups.mdx rename to apps/docs/versioned_docs/version-V4-beta/guides/groups.mdx index 493f59cec..862e380ba 100644 --- a/apps/docs/versioned_docs/version-V4-alpha/guides/groups.mdx +++ b/apps/docs/versioned_docs/version-V4-beta/guides/groups.mdx @@ -8,7 +8,7 @@ import TabItem from "@theme/TabItem" # Semaphore groups -A [Semaphore group](/V4-alpha/glossary/#group) contains [identity commitments](/V4-alpha/glossary/#commitment) of group members. +A [Semaphore group](/glossary/#group) contains [identity commitments](/glossary/#commitment) of group members. Example uses of groups include the following: - poll question that attendees join to rate an event, @@ -23,7 +23,7 @@ Merkle Tree) [Solidity](https://github.com/privacy-scaling-explorations/zk-kit/t ## Off-chain groups -Use the [`@semaphore-protocol/group`](https://github.com/semaphore-protocol/semaphore/blob/feat/semaphore-v4/packages/group) package to manage off-chain groups. +Use the [`@semaphore-protocol/group`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/group) package to manage off-chain groups. ### Install package @@ -58,6 +58,10 @@ pnpm add @semaphore-protocol/group +:::info +Semaphore also provides `@semaphore-protocol/core`, which includes the functions of the following core packages: `@semaphore-protocol/identity`, `@semaphore-protocol/group`, `@semaphore-protocol/proof`. +::: + ### Create a group To create a group instantiate `Group` without any parameters. For example: @@ -123,7 +127,7 @@ Given that the member isn't removed, the number of members (i.e., `group.size` o ### Generate a Merkle proof -Semaphore groups are [Merkle trees](/V4-alpha/glossary#merkle-tree), and it is therefore possible to calculate the Merkle proof of a group member (i.e., tree leaf) by passing the index of the member to the `generateMerkleProof`. For example: +Semaphore groups are [Merkle trees](/glossary#merkle-tree), and it is therefore possible to calculate the Merkle proof of a group member (i.e., tree leaf) by passing the index of the member to the `generateMerkleProof`. For example: ```ts group.generateMerkleProof(0) @@ -131,9 +135,9 @@ group.generateMerkleProof(0) ## On-chain groups -Semaphore provides [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/feat/semaphore-v4/packages/contracts/contracts/Semaphore.sol), a contract designed for managing on-chain groups ([deployed](/V4-alpha/deployed-contracts) on major testnets). +Semaphore provides [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/contracts/contracts/Semaphore.sol), a contract designed for managing on-chain groups ([deployed](/deployed-contracts) on major testnets). -Use the [`@semaphore-protocol/contracts`](https://github.com/semaphore-protocol/semaphore/blob/feat/semaphore-v4/packages/contracts) package to import the `ISemaphore.sol` interface in your contract and start using its functions. +Use the [`@semaphore-protocol/contracts`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/contracts) package to import the `ISemaphore.sol` interface in your contract and start using its functions. ### Install package @@ -235,6 +239,6 @@ function removeMember(uint256 identityCommitment, uint256[] calldata merkleProof ``` :::info -If you want to see an example of a working contract, have a look at the [`contracts-hardhat`](https://github.com/semaphore-protocol/semaphore/tree/feat/semaphore-v4/packages/cli-template-contracts-hardhat) CLI template. You can also create a project with that template by running `semaphore create my-app --template contracts-hardhat`. +If you want to see an example of a working contract, have a look at the [`contracts-hardhat`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli-template-contracts-hardhat) CLI template. You can also create a project with that template by running `semaphore create my-app --template contracts-hardhat`. ::: diff --git a/apps/docs/versioned_docs/version-V4-alpha/guides/identities.mdx b/apps/docs/versioned_docs/version-V4-beta/guides/identities.mdx similarity index 86% rename from apps/docs/versioned_docs/version-V4-alpha/guides/identities.mdx rename to apps/docs/versioned_docs/version-V4-beta/guides/identities.mdx index 418fb6a7f..a03b952d3 100644 --- a/apps/docs/versioned_docs/version-V4-alpha/guides/identities.mdx +++ b/apps/docs/versioned_docs/version-V4-beta/guides/identities.mdx @@ -8,7 +8,7 @@ import TabItem from "@theme/TabItem" # Semaphore identities -In order to join a [Semaphore group](/V4-alpha/glossary#group), a user must first create a [Semaphore identity](/V4-alpha/glossary#identity). +In order to join a [Semaphore group](/glossary#group), a user must first create a [Semaphore identity](/glossary#identity). A Semaphore identity contains three values generated with the identity: - Private key @@ -20,7 +20,7 @@ To prevent fraud, the owner should keep their private key secret. ## Install package -In your code, use the [`@semaphore-protocol/identity`](https://github.com/semaphore-protocol/semaphore/tree/feat/semaphore-v4/packages/identity) package to manage Semaphore identites. +In your code, use the [`@semaphore-protocol/identity`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/identity) package to manage Semaphore identites. +:::info +Semaphore also provides `@semaphore-protocol/core`, which includes the functions of the following core packages: `@semaphore-protocol/identity`, `@semaphore-protocol/group`, `@semaphore-protocol/proof`. +::: + ## Create identities ### Create random identities diff --git a/apps/docs/versioned_docs/version-V4-alpha/guides/proofs.mdx b/apps/docs/versioned_docs/version-V4-beta/guides/proofs.mdx similarity index 57% rename from apps/docs/versioned_docs/version-V4-alpha/guides/proofs.mdx rename to apps/docs/versioned_docs/version-V4-beta/guides/proofs.mdx index fbc7a56cd..2ded52f34 100644 --- a/apps/docs/versioned_docs/version-V4-alpha/guides/proofs.mdx +++ b/apps/docs/versioned_docs/version-V4-beta/guides/proofs.mdx @@ -8,16 +8,16 @@ import TabItem from "@theme/TabItem" # Semaphore proofs -Once a user joins a [Semaphore group](/V4-alpha/glossary#group) with their [Semaphore identity](/V4-alpha/glossary#identity), the user can send their anonymous [message](/V4-alpha/glossary#message) with a zero-knowledge proof that proves the following: +Once a user joins a [Semaphore group](/glossary#group) with their [Semaphore identity](/glossary#identity), the user can send their anonymous [message](/glossary#message) with a zero-knowledge proof that proves the following: - the user is a member of the group, - the same user created the message and the proof. -A unique [nullifier](/V4-alpha/glossary#nullifier) is also generated for each proof that can be used to check whether that proof has already been validated. +A unique [nullifier](/glossary#nullifier) is also generated for each proof that can be used to check whether that proof has already been validated. ## Install package -In your code, use the [`@semaphore-protocol/proof`](https://github.com/semaphore-protocol/semaphore/tree/feat/semaphore-v4/packages/proof) package to generate and verify a proof. +In your code, use the [`@semaphore-protocol/proof`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/proof) package to generate and verify a proof. +:::info +Semaphore also provides `@semaphore-protocol/core`, which includes the functions of the following core packages: `@semaphore-protocol/identity`, `@semaphore-protocol/group`, `@semaphore-protocol/proof`. +::: + ## Generate a proof ### 1. Create the identity In order for a user to generate a proof, it is necessary to create a Semaphore identity. If you do not know how to -create an identity, see the previous [guide](/V4-alpha/guides/identities) on identities. +create an identity, see the previous [guide](/guides/identities) on identities. ### 2. Create the group -Before generating a proof you also need to create a Semaphore group containing the commitment of the Semaphore identity of the user who will generate the proof. If you do not know how to create a group, see the previous [guide](/V4-alpha/guides/groups) on groups. +Before generating a proof you also need to create a Semaphore group containing the commitment of the Semaphore identity of the user who will generate the proof. If you do not know how to create a group, see the previous [guide](/guides/groups) on groups. -If your group is on-chain, you can use the [`@semaphore-protocol/data`](https://github.com/semaphore-protocol/semaphore/tree/feat/semaphore-v4/packages/data) library to fetch the group members and re-create the off-chain group. For example: +If your group is on-chain, you can use the [`@semaphore-protocol/data`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/data) library to fetch the group members and re-create the off-chain group. For example: ```ts import { SemaphoreSubgraph } from "@semaphore-protocol/data" @@ -76,7 +80,7 @@ const group = new Group(members) ### 3. Choose the scope -Each proof requires a [scope](/V4-alpha/glossary#scope), on which each user may only generate one valid proof. The scope, together with the user's private key, is used to generate the nullifier, which is the value you can actually use to check whether a proof with that scope has already been generated by that user. In a voting application where double-voting must be prevented, the scope could be the ballot id, or the Merkle root of the group. +Each proof requires a [scope](/glossary#scope), on which each user may only generate one valid proof. The scope, together with the user's private key, is used to generate the nullifier, which is the value you can actually use to check whether a proof with that scope has already been generated by that user. In a voting application where double-voting must be prevented, the scope could be the ballot id, or the Merkle root of the group. ### 4. Generate the anomymous message @@ -101,7 +105,7 @@ import { verifyProof } from "@semaphore-protocol/proof" await verifyProof(proof) // true or false. ``` -If you want to validate a proof on-chain, you can use [`@semaphore-protocol/contracts`](https://github.com/semaphore-protocol/semaphore/blob/feat/semaphore-v4/packages/contracts) and the [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/feat/semaphore-v4/packages/contracts/contracts/Semaphore.sol) contract, as explained in the previous [guide](/V4-alpha/guides/groups#install-package-1), and use the `validateProof` function. For example: +If you want to validate a proof on-chain, you can use [`@semaphore-protocol/contracts`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/contracts) and the [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/contracts/contracts/Semaphore.sol) contract, as explained in the previous [guide](/guides/groups#install-package-1), and use the `validateProof` function. For example: ```solidity function validateProof(ISemaphore.SemaphoreProof calldata proof) external { diff --git a/apps/docs/versioned_docs/version-V4-alpha/resources.mdx b/apps/docs/versioned_docs/version-V4-beta/resources.mdx similarity index 100% rename from apps/docs/versioned_docs/version-V4-alpha/resources.mdx rename to apps/docs/versioned_docs/version-V4-beta/resources.mdx diff --git a/apps/docs/versioned_docs/version-V4-alpha/subgraph.mdx b/apps/docs/versioned_docs/version-V4-beta/subgraph.mdx similarity index 76% rename from apps/docs/versioned_docs/version-V4-alpha/subgraph.mdx rename to apps/docs/versioned_docs/version-V4-beta/subgraph.mdx index ea6f5c07c..ba8a66e1c 100644 --- a/apps/docs/versioned_docs/version-V4-alpha/subgraph.mdx +++ b/apps/docs/versioned_docs/version-V4-beta/subgraph.mdx @@ -8,13 +8,13 @@ import RemoteCode from '@site/src/components/RemoteCode'; [The Graph](https://thegraph.com/) is a protocol for indexing networks like Ethereum and IPFS. Site owners publish _subgraphs_ that expose site data for anyone to query. -Semaphore's subgraph allows you to retrieve data from the [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/feat/semaphore-v4/packages/contracts/contracts/Semaphore.sol) smart contract. +Semaphore's subgraph allows you to retrieve data from the [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/contracts/contracts/Semaphore.sol) smart contract. :::tip -The Graph protocol uses the [GraphQL](https://graphql.org/) query language. For examples, see the [GraphQL API documentation](https://thegraph.com/docs/developer/graphql-api). Visit the [Semaphore subgraph](https://github.com/semaphore-protocol/semaphore/tree/feat/semaphore-v4/apps/subgraph) to see the list of networks supported by Semaphore and its URLs. +The Graph protocol uses the [GraphQL](https://graphql.org/) query language. For examples, see the [GraphQL API documentation](https://thegraph.com/docs/developer/graphql-api). Visit the [Semaphore subgraph](https://github.com/semaphore-protocol/semaphore/tree/main/apps/subgraph) to see the list of networks supported by Semaphore and its URLs. ::: ## Schema - diff --git a/apps/docs/versioned_docs/version-V4-alpha/technical-reference/_category_.json b/apps/docs/versioned_docs/version-V4-beta/technical-reference/_category_.json similarity index 100% rename from apps/docs/versioned_docs/version-V4-alpha/technical-reference/_category_.json rename to apps/docs/versioned_docs/version-V4-beta/technical-reference/_category_.json diff --git a/apps/docs/versioned_docs/version-V4-alpha/technical-reference/circuits.md b/apps/docs/versioned_docs/version-V4-beta/technical-reference/circuits.md similarity index 91% rename from apps/docs/versioned_docs/version-V4-alpha/technical-reference/circuits.md rename to apps/docs/versioned_docs/version-V4-beta/technical-reference/circuits.md index 8961fa7d9..2ff9a1e00 100644 --- a/apps/docs/versioned_docs/version-V4-alpha/technical-reference/circuits.md +++ b/apps/docs/versioned_docs/version-V4-beta/technical-reference/circuits.md @@ -4,13 +4,13 @@ sidebar_position: 2 # Circuits -The [Semaphore circuit](https://github.com/semaphore-protocol/semaphore/tree/feat/semaphore-v4/packages/circuits/semaphore.circom) is the heart of the protocol and consists of three parts: +The [Semaphore circuit](https://github.com/semaphore-protocol/semaphore/tree/main/packages/circuits/semaphore.circom) is the heart of the protocol and consists of three parts: - [Proof of membership](#proof-of-membership) - [Nullifier](#nullifier) - [Message](#message) -![Semaphore circuit](https://github.com/semaphore-protocol/semaphore/raw/feat/semaphore-v4/packages/circuits/scheme.png) +![Semaphore circuit](https://github.com/semaphore-protocol/semaphore/raw/main/packages/circuits/scheme.png) The diagram above shows how the input signals are used in the Semaphore circuit and how the outputs are calculated. diff --git a/apps/docs/versioned_docs/version-V4-alpha/technical-reference/contracts.md b/apps/docs/versioned_docs/version-V4-beta/technical-reference/contracts.md similarity index 80% rename from apps/docs/versioned_docs/version-V4-alpha/technical-reference/contracts.md rename to apps/docs/versioned_docs/version-V4-beta/technical-reference/contracts.md index a41783598..f9ba7ca94 100644 --- a/apps/docs/versioned_docs/version-V4-alpha/technical-reference/contracts.md +++ b/apps/docs/versioned_docs/version-V4-beta/technical-reference/contracts.md @@ -7,20 +7,20 @@ sidebar_position: 3 Semaphore contracts are designed with minimal yet essential code, enabling developers to efficiently manage on-chain groups and verify or validate zero-knowledge proofs. There are three contracts: -- [`SemaphoreVerifier.sol`](https://github.com/semaphore-protocol/semaphore/blob/feat/semaphore-v4/packages/contracts/contracts/base/SemaphoreVerifier.sol) -- [`SemaphoreGroups.sol`](https://github.com/semaphore-protocol/semaphore/blob/feat/semaphore-v4/packages/contracts/contracts/base/SemaphoreGroups.sol) -- [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/feat/semaphore-v4/packages/contracts/contracts/Semaphore.sol) +- [`SemaphoreVerifier.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/contracts/contracts/base/SemaphoreVerifier.sol) +- [`SemaphoreGroups.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/contracts/contracts/base/SemaphoreGroups.sol) +- [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/packages/contracts/contracts/Semaphore.sol) :::info To use Semaphore contracts and interfaces in your project, -install the [`@semaphore-protocol/contracts`](https://github.com/semaphore-protocol/semaphore/tree/feat/semaphore-v4/packages/contracts) NPM package. +install the [`@semaphore-protocol/contracts`](https://github.com/semaphore-protocol/semaphore/tree/main/packages/contracts) NPM package. ::: ## SemaphoreVerifier.sol `SemaphoreVerifier.sol` is an extended version of the Groth16 verifier generated by default with [SnarkJS](https://github.com/iden3/snarkjs). It contains a function for verifying proofs and a list of verification keys parameters. -Since the Semaphore circuit is compiled with a `MAX_DEPTH` range from 1 to 32 during the [trusted setup](/V4-alpha/glossary#trusted-setup), the verifier must contain the parameters of the verification keys of each instance. +Since the Semaphore circuit is compiled with a `MAX_DEPTH` range from 1 to 32 during the [trusted setup](/glossary#trusted-setup), the verifier must contain the parameters of the verification keys of each instance. ## SemaphoreGroups.sol @@ -37,5 +37,5 @@ The `verifyProof` function contains code for checking whether a Semaphore proof The `validateProof` function first checks whether a proof with the same nullifier has already been validated, and then verifies the proof with the `verifyProof` function and saves the nullifier. This function also creates a log with the group id and the proof, which can then additionally be verified off-chain. :::info -Semaphore contracts are deployed on the main testnets and Arbitrum One. See the [deployed contracts](/V4-alpha/deployed-contracts) to check the addresses. +Semaphore contracts are deployed on the main testnets and Arbitrum One. See the [deployed contracts](/deployed-contracts) to check the addresses. :::: diff --git a/apps/docs/versioned_docs/version-V4-beta/troubleshooting.md b/apps/docs/versioned_docs/version-V4-beta/troubleshooting.md new file mode 100644 index 000000000..c87a02bfb --- /dev/null +++ b/apps/docs/versioned_docs/version-V4-beta/troubleshooting.md @@ -0,0 +1,20 @@ +--- +sidebar_position: 9 +--- + +import Tabs from "@theme/Tabs" +import TabItem from "@theme/TabItem" + +# Troubleshooting + +If these suggestions do not work, feel free to ask for more help and support on [Github Discussions](https://github.com/semaphore-protocol/semaphore/discussions) or [Discord](https://semaphore.pse.dev/discord) ("dev-chat" channel). + +## Creating a Group + +When you create a group and the transaction is reverted, make sure that the group id you are using does not exist on the network you are using. + +To check that, you can use the [Semaphore CLI](https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli) with the command `get-groups` and the network you are using and then, make sure that your group id is not part of that list. You can also use the [Semaphore explorer](https://explorer.semaphore.pse.dev/). + +## Transaction reverted when using the same nullifier + +When you generate a proof using the same [scope](/glossary#scope) you used to validate a proof before, the transaction will be reverted because that scope (and thus the [nullifier](/glossary#nullifier)) has already been used. If you want to send and validate several proofs from the same identity, you need to use a different scope for each time you generate a proof. diff --git a/apps/docs/versioned_docs/version-V4-alpha/what-is-semaphore.md b/apps/docs/versioned_docs/version-V4-beta/what-is-semaphore.md similarity index 80% rename from apps/docs/versioned_docs/version-V4-alpha/what-is-semaphore.md rename to apps/docs/versioned_docs/version-V4-beta/what-is-semaphore.md index bbd45869e..22f52d63e 100644 --- a/apps/docs/versioned_docs/version-V4-alpha/what-is-semaphore.md +++ b/apps/docs/versioned_docs/version-V4-beta/what-is-semaphore.md @@ -7,7 +7,7 @@ slug: / ## Overview -[Semaphore](https://github.com/semaphore-protocol/semaphore/tree/feat/semaphore-v4) is a [zero-knowledge](https://z.cash/technology/zksnarks) protocol that allows you to cast a message (for example, a vote or endorsement) as a provable group member without revealing your identity. +[Semaphore](https://github.com/semaphore-protocol/semaphore/tree/main) is a [zero-knowledge](https://z.cash/technology/zksnarks) protocol that allows you to cast a message (for example, a vote or endorsement) as a provable group member without revealing your identity. Additionally, it provides a simple mechanism to prevent double-signaling. Use cases include private voting, whistleblowing, anonymous DAOs and mixers. @@ -15,9 +15,9 @@ Use cases include private voting, whistleblowing, anonymous DAOs and mixers. With Semaphore, you can allow your users to do the following: -1. [Create a Semaphore identity](/V4-alpha/guides/identities/). -2. [Add their Semaphore identity to a group (i.e. _Merkle tree_)](/V4-alpha/guides/groups/). -3. [Send a verifiable, anonymous message (e.g a vote or endorsement)](/V4-alpha/guides/proofs/). +1. [Create a Semaphore identity](/guides/identities/). +2. [Add their Semaphore identity to a group (i.e. _Merkle tree_)](/guides/groups/). +3. [Send a verifiable, anonymous message (e.g a vote or endorsement)](/guides/proofs/). When a user broadcasts a message, Semaphore zero-knowledge proofs can ensure that the user has joined the group and hasn't already cast a message with their nullifier. @@ -33,10 +33,10 @@ Semaphore is designed to be a simple and generic _privacy layer_ for decentraliz ## About the code -The core of the protocol is the [circuit logic](https://github.com/semaphore-protocol/semaphore/tree/feat/semaphore-v4/packages/circuits/semaphore.circom). +The core of the protocol is the [circuit logic](https://github.com/semaphore-protocol/semaphore/tree/main/packages/circuits/semaphore.circom). In addition to circuits, -Semaphore provides [Solidity contracts](https://github.com/semaphore-protocol/semaphore/tree/feat/semaphore-v4/packages/contracts) -and [JavaScript libraries](https://github.com/semaphore-protocol/semaphore/tree/feat/semaphore-v4#-packages) that allow developers to generate zero-knowledge proofs and verify them with minimal effort. +Semaphore provides [Solidity contracts](https://github.com/semaphore-protocol/semaphore/tree/main/packages/contracts) +and [JavaScript libraries](https://github.com/semaphore-protocol/semaphore/tree/main#-packages) that allow developers to generate zero-knowledge proofs and verify them with minimal effort. ### Audits diff --git a/apps/docs/versioned_sidebars/version-V4-alpha-sidebars.json b/apps/docs/versioned_sidebars/version-V4-beta-sidebars.json similarity index 100% rename from apps/docs/versioned_sidebars/version-V4-alpha-sidebars.json rename to apps/docs/versioned_sidebars/version-V4-beta-sidebars.json diff --git a/apps/docs/versions.json b/apps/docs/versions.json index a2dee460f..b69f062ec 100644 --- a/apps/docs/versions.json +++ b/apps/docs/versions.json @@ -1 +1 @@ -["V4-alpha", "V3", "V2", "V1"] +["V4-beta", "V3", "V2", "V1"] diff --git a/apps/subgraph/.gitignore b/apps/subgraph/.gitignore index 8cafc1d73..fde3d1349 100644 --- a/apps/subgraph/.gitignore +++ b/apps/subgraph/.gitignore @@ -6,3 +6,5 @@ subgraph.yaml # Tests /tests/.bin + +/data diff --git a/apps/subgraph/README.md b/apps/subgraph/README.md index 0fa25361f..64c4ac966 100644 --- a/apps/subgraph/README.md +++ b/apps/subgraph/README.md @@ -42,13 +42,13 @@ ## Networks -| Semaphore version | Sepolia | Goerli | Mumbai | Optimism Goerli | Arbitrum Goerli | Arbitrum One | -| ----------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -| v2.0 | N/A | N/A | N/A | N/A | N/A | [semaphore-protocol/arbitrum](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum) | -| v2.5 | N/A | [semaphore-protocol/goerli](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/goerli) | N/A | N/A | N/A | N/A | -| v2.6 | N/A | [semaphore-protocol/goerli-5259d3](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/goerli-5259d3) | N/A | N/A | N/A | [semaphore-protocol/arbitrum-86337c](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum-86337c) | -| v3.0 - v3.1 | N/A | [semaphore-protocol/goerli-89490c](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/goerli-89490c) | N/A | N/A | N/A | [semaphore-protocol/arbitrum-72dca3](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum-72dca3) | -| >= v3.2 | [semaphore-sepolia](https://api.studio.thegraph.com/query/14377/semaphore-sepolia/v3.6.1) | [semaphore-goerli](https://api.studio.thegraph.com/query/14377/semaphore-goerli/v3.6.1) | [semaphore-mumbai](https://api.studio.thegraph.com/query/14377/semaphore-mumbai/v3.6.1) | [semaphore-optimism-goerli](https://api.studio.thegraph.com/query/14377/semaphore-optimism-goerli/v3.6.1) | [semaphore-arbitrum-goerli](https://api.studio.thegraph.com/query/14377/semaphore-arbitrum-goerli/v3.6.1) | [semaphore-arbitrum](https://api.studio.thegraph.com/query/14377/semaphore-arbitrum/v3.6.1) | +| Semaphore version | Sepolia | Mumbai | Optimism Sepolia | Arbitrum Sepolia | Arbitrum One | +| ----------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ---------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------- | +| v2.0 | N/A | N/A | N/A | N/A | [semaphore-protocol/arbitrum](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum) | +| v2.5 | N/A | N/A | N/A | N/A | N/A | +| v2.6 | N/A | N/A | N/A | N/A | [semaphore-protocol/arbitrum-86337c](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum-86337c) | +| v3.0 - v3.1 | N/A | N/A | N/A | N/A | [semaphore-protocol/arbitrum-72dca3](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum-72dca3) | +| >= v3.2 | [semaphore-sepolia](https://api.studio.thegraph.com/query/14377/semaphore-sepolia/v3.6.1) | [semaphore-mumbai](https://api.studio.thegraph.com/query/14377/semaphore-mumbai/v3.6.1) | N/A | N/A | [semaphore-arbitrum](https://api.studio.thegraph.com/query/14377/semaphore-arbitrum/v3.6.1) | ## 🛠 Install @@ -111,15 +111,15 @@ yarn deploy Start services required for TheGraph node by running: ```bash -docker compose -f docker-compose-graph.yml up +docker compose up ``` Start a local Hardhat node and deploy the [Semaphore contract](https://github.com/semaphore-protocol/semaphore/tree/main/packages/contracts): ```bash # CWD = /semaphore/packages/contracts -yarn start -yarn deploy:semaphore --network localhost +yarn start --hostname 0.0.0.0 +yarn deploy --network localhost ``` Create the `subgraph.yaml` file for your local network and create/deploy your subgraph: diff --git a/apps/subgraph/abis/Semaphore.json b/apps/subgraph/abis/Semaphore.json index 5a0524159..24e2af252 100644 --- a/apps/subgraph/abis/Semaphore.json +++ b/apps/subgraph/abis/Semaphore.json @@ -14,6 +14,26 @@ "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [], + "name": "LeafAlreadyExists", + "type": "error" + }, + { + "inputs": [], + "name": "LeafCannotBeZero", + "type": "error" + }, + { + "inputs": [], + "name": "LeafDoesNotExist", + "type": "error" + }, + { + "inputs": [], + "name": "LeafGreaterThanSnarkScalarField", + "type": "error" + }, { "inputs": [], "name": "Semaphore__CallerIsNotTheGroupAdmin", @@ -29,6 +49,16 @@ "name": "Semaphore__GroupDoesNotExist", "type": "error" }, + { + "inputs": [], + "name": "Semaphore__GroupHasNoMembers", + "type": "error" + }, + { + "inputs": [], + "name": "Semaphore__InvalidProof", + "type": "error" + }, { "inputs": [], "name": "Semaphore__MerkleTreeDepthIsNotSupported", @@ -46,7 +76,12 @@ }, { "inputs": [], - "name": "Semaphore__YouAreUsingTheSameNillifierTwice", + "name": "Semaphore__YouAreUsingTheSameNullifierTwice", + "type": "error" + }, + { + "inputs": [], + "name": "WrongSiblingNodes", "type": "error" }, { @@ -82,18 +117,6 @@ "internalType": "uint256", "name": "groupId", "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "merkleTreeDepth", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "zeroValue", - "type": "uint256" } ], "name": "GroupCreated", @@ -233,31 +256,74 @@ "type": "uint256" }, { - "indexed": true, + "indexed": false, + "internalType": "uint256", + "name": "startIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "identityCommitments", + "type": "uint256[]" + }, + { + "indexed": false, "internalType": "uint256", "name": "merkleTreeRoot", "type": "uint256" + } + ], + "name": "MembersAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "groupId", + "type": "uint256" }, { "indexed": false, "internalType": "uint256", - "name": "nullifierHash", + "name": "merkleTreeDepth", "type": "uint256" }, { "indexed": true, "internalType": "uint256", - "name": "externalNullifier", + "name": "merkleTreeRoot", "type": "uint256" }, { "indexed": false, "internalType": "uint256", - "name": "signal", + "name": "nullifier", "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "message", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "scope", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[8]", + "name": "points", + "type": "uint256[8]" } ], - "name": "ProofVerified", + "name": "ProofValidated", "type": "event" }, { @@ -303,11 +369,6 @@ "name": "groupId", "type": "uint256" }, - { - "internalType": "uint256", - "name": "merkleTreeDepth", - "type": "uint256" - }, { "internalType": "address", "name": "admin", @@ -331,11 +392,6 @@ "name": "groupId", "type": "uint256" }, - { - "internalType": "uint256", - "name": "merkleTreeDepth", - "type": "uint256" - }, { "internalType": "address", "name": "admin", @@ -347,6 +403,25 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + } + ], + "name": "getGroupAdmin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -393,7 +468,7 @@ "type": "uint256" } ], - "name": "getNumberOfMerkleTreeLeaves", + "name": "getMerkleTreeSize", "outputs": [ { "internalType": "uint256", @@ -415,16 +490,35 @@ "name": "groups", "outputs": [ { - "internalType": "address", - "name": "admin", - "type": "address" + "internalType": "uint256", + "name": "merkleTreeDuration", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" }, { "internalType": "uint256", - "name": "merkleTreeDuration", + "name": "identityCommitment", "type": "uint256" } ], + "name": "hasMember", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], "stateMutability": "view", "type": "function" }, @@ -439,16 +533,35 @@ "internalType": "uint256", "name": "identityCommitment", "type": "uint256" + } + ], + "name": "indexOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" }, { - "internalType": "uint256[]", - "name": "proofSiblings", - "type": "uint256[]" + "internalType": "uint256", + "name": "identityCommitment", + "type": "uint256" }, { - "internalType": "uint8[]", - "name": "proofPathIndices", - "type": "uint8[]" + "internalType": "uint256[]", + "name": "merkleProofSiblings", + "type": "uint256[]" } ], "name": "removeMember", @@ -511,16 +624,61 @@ }, { "internalType": "uint256[]", - "name": "proofSiblings", + "name": "merkleProofSiblings", "type": "uint256[]" + } + ], + "name": "updateMember", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" }, { - "internalType": "uint8[]", - "name": "proofPathIndices", - "type": "uint8[]" + "components": [ + { + "internalType": "uint256", + "name": "merkleTreeDepth", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "merkleTreeRoot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nullifier", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "message", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "scope", + "type": "uint256" + }, + { + "internalType": "uint256[8]", + "name": "points", + "type": "uint256[8]" + } + ], + "internalType": "struct ISemaphore.SemaphoreProof", + "name": "proof", + "type": "tuple" } ], - "name": "updateMember", + "name": "validateProof", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -546,79 +704,113 @@ "type": "uint256" }, { - "internalType": "uint256", - "name": "merkleTreeRoot", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "signal", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "nullifierHash", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "externalNullifier", - "type": "uint256" - }, - { - "internalType": "uint256[8]", + "components": [ + { + "internalType": "uint256", + "name": "merkleTreeDepth", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "merkleTreeRoot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nullifier", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "message", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "scope", + "type": "uint256" + }, + { + "internalType": "uint256[8]", + "name": "points", + "type": "uint256[8]" + } + ], + "internalType": "struct ISemaphore.SemaphoreProof", "name": "proof", - "type": "uint256[8]" + "type": "tuple" } ], "name": "verifyProof", - "outputs": [], - "stateMutability": "nonpayable", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", "type": "function" } ], - "bytecode": "0x60806040523480156200001157600080fd5b506040516200214a3803806200214a833981810160405281019062000037919062000096565b80600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505062000124565b60008151905062000090816200010a565b92915050565b600060208284031215620000a957600080fd5b6000620000b9848285016200007f565b91505092915050565b6000620000cf82620000ea565b9050919050565b6000620000e382620000c2565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6200011581620000d6565b81146200012157600080fd5b50565b61201680620001346000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c80636389e1071161008c5780639c112141116100665780639c11214114610246578063dabc4d5114610262578063ec45622a14610292578063fcf0b6ec146102ae576100ea565b80636389e107146101c957806365e54f83146101f957806396324bd414610215576100ea565b80633bc778e3116100c85780633bc778e31461014557806343989f8514610161578063568ee8261461017d578063638480be14610199576100ea565b806304245371146100ef5780631783efc31461010b5780632b7ac3f314610127575b600080fd5b61010960048036038101906101049190611655565b6102ca565b005b610125600480360381019061012091906116ad565b610410565b005b61012f6104fc565b60405161013c9190611b24565b60405180910390f35b61015f600480360381019061015a91906118e6565b610522565b005b61017b6004803603810190610176919061179b565b6107cf565b005b61019760048036038101906101929190611619565b6108c3565b005b6101b360048036038101906101ae91906115f0565b610a22565b6040516101c09190611c5b565b60405180910390f35b6101e360048036038101906101de91906115f0565b610a41565b6040516101f09190611c5b565b60405180910390f35b610213600480360381019061020e9190611738565b610a60565b005b61022f600480360381019061022a91906115f0565b610b84565b60405161023d929190611afb565b60405180910390f35b610260600480360381019061025b91906116e9565b610bc8565b005b61027c600480360381019061027791906115f0565b610ced565b6040516102899190611c5b565b60405180910390f35b6102ac60048036038101906102a79190611836565b610d0c565b005b6102c860048036038101906102c391906116ad565b610e02565b005b826102d3610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461036d576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b838390508110156103cf576103c4858585848181106103b8577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020135610f23565b806001019050610370565b5060006103db85610ced565b905042600260008781526020019081526020016000206002016000838152602001908152602001600020819055505050505050565b81610419610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146104b3576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6104bd8383610f23565b60006104c884610ced565b9050426002600086815260200190815260200160002060020160008381526020019081526020016000208190555050505050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600061052d87610a41565b9050600081141561056a576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061057588610ced565b9050808714610648576000600260008a815260200190815260200160002060020160008981526020019081526020016000205490506000600260008b81526020019081526020016000206001015490506000821415610600576040517f4d32958600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b808261060c9190611dce565b421115610645576040517f9581a99000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505b60026000898152602001908152602001600020600301600086815260200190815260200160002060009054906101000a900460ff16156106b4576040517f948d067400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16639aca8f2a8887898888886040518763ffffffff1660e01b815260040161071996959493929190611d1b565b60006040518083038186803b15801561073157600080fd5b505afa158015610745573d6000803e3d6000fd5b505050506001600260008a8152602001908152602001600020600301600087815260200190815260200160002060006101000a81548160ff0219169083151502179055508387897f48950129900df26c2140187532df49c8af343c3daf74f1e99e562e1b2be07adc888a6040516107bd929190611c76565b60405180910390a45050505050505050565b856107d8610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610872576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61088087878787878761104a565b600061088b88610ced565b905042600260008a81526020019081526020016000206002016000838152602001908152602001600020819055505050505050505050565b816108cc610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610966576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b816002600085815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff166109da610f1b565b73ffffffffffffffffffffffffffffffffffffffff16847f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a4505050565b6000806000838152602001908152602001600020600201549050919050565b6000806000838152602001908152602001600020600001549050919050565b826010811080610a705750602081115b15610aa7576040517fecf64f1200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610ab18585611172565b826002600087815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160026000878152602001908152602001600020600101819055508273ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff16867f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a45050505050565b60026020528060005260406000206000915090508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060010154905082565b816010811080610bd85750602081115b15610c0f576040517fecf64f1200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610c198484611172565b816002600086815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610e1060026000868152602001908152602001600020600101819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff16857f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a450505050565b6000806000838152602001908152602001600020600101549050919050565b86610d15610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610daf576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610dbe888888888888886112a3565b6000610dc989610ced565b905042600260008b8152602001908152602001600020600201600083815260200190815260200160002081905550505050505050505050565b81610e0b610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610ea5576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060026000858152602001908152602001600020600101549050826002600086815260200190815260200160002060010181905550837f264b2a8f6763c084235fe832ba903482b2ef1a521336881fc75b987c2dfd29c58285604051610f0d929190611c76565b60405180910390a250505050565b600033905090565b6000610f2e83610a41565b1415610f66576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008083815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__63168703fa9091836040518363ffffffff1660e01b8152600401610fb2929190611b3f565b60006040518083038186803b158015610fca57600080fd5b505af4158015610fde573d6000803e3d6000fd5b505050506000610fed83610ced565b905060006001610ffc85610a22565b6110069190611e7e565b9050837f19239b3f93cd10558aaf11423af70c77763bf54f52bcc75bfa74d4d13548cde982858560405161103c93929190611c9f565b60405180910390a250505050565b600061105587610a41565b141561108d576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008087815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__630629596f909187878787876040518763ffffffff1660e01b81526004016110e196959493929190611b68565b60006040518083038186803b1580156110f957600080fd5b505af415801561110d573d6000803e3d6000fd5b50505050600061111c87610ced565b9050600061112a84846113d0565b9050877f3108849c053c77b8073a11256dffb5ffd5b55e93e105a355e1c9061db890d87182898560405161116093929190611c9f565b60405180910390a25050505050505050565b600061117d83610a41565b146111b4576040517f8121725b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006008836040516020016111c99190611ae0565b6040516020818303038152906040528051906020012060001c901c905060008084815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__631095fbb4909184846040518463ffffffff1660e01b815260040161123493929190611bbf565b60006040518083038186803b15801561124c57600080fd5b505af4158015611260573d6000803e3d6000fd5b50505050827f0d000126c26c1bbe400fd2332187f75d58b69306f9ec47b408686189d3a008338383604051611296929190611c76565b60405180910390a2505050565b60006112ae88610a41565b14156112e6576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008088815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__63a547882790918888888888886040518863ffffffff1660e01b815260040161133c9796959493929190611bf6565b60006040518083038186803b15801561135457600080fd5b505af4158015611368573d6000803e3d6000fd5b50505050600061137788610ced565b9050600061138584846113d0565b9050887fea3588e4a2a0c93d6a0e69dfeaf7496f43ccccf02ad9ce0a5b7627cbca4b61b1828a8a866040516113bd9493929190611cd6565b60405180910390a2505050505050505050565b6000806000905060008484905090505b60008160ff1611156114f457600082118061145b5750600085856001846114079190611eb2565b60ff16818110611440577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020160208101906114559190611970565b60ff1614155b156114e85760028261146d9190611e24565b9150600185856001846114809190611eb2565b60ff168181106114b9577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020160208101906114ce9190611970565b60ff1614156114e7576001826114e49190611dce565b91505b5b806001900390506113e0565b508091505092915050565b60008135905061150e81611f9b565b92915050565b60008190508260206008028201111561152c57600080fd5b92915050565b60008083601f84011261154457600080fd5b8235905067ffffffffffffffff81111561155d57600080fd5b60208301915083602082028301111561157557600080fd5b9250929050565b60008083601f84011261158e57600080fd5b8235905067ffffffffffffffff8111156115a757600080fd5b6020830191508360208202830111156115bf57600080fd5b9250929050565b6000813590506115d581611fb2565b92915050565b6000813590506115ea81611fc9565b92915050565b60006020828403121561160257600080fd5b6000611610848285016115c6565b91505092915050565b6000806040838503121561162c57600080fd5b600061163a858286016115c6565b925050602061164b858286016114ff565b9150509250929050565b60008060006040848603121561166a57600080fd5b6000611678868287016115c6565b935050602084013567ffffffffffffffff81111561169557600080fd5b6116a186828701611532565b92509250509250925092565b600080604083850312156116c057600080fd5b60006116ce858286016115c6565b92505060206116df858286016115c6565b9150509250929050565b6000806000606084860312156116fe57600080fd5b600061170c868287016115c6565b935050602061171d868287016115c6565b925050604061172e868287016114ff565b9150509250925092565b6000806000806080858703121561174e57600080fd5b600061175c878288016115c6565b945050602061176d878288016115c6565b935050604061177e878288016114ff565b925050606061178f878288016115c6565b91505092959194509250565b600080600080600080608087890312156117b457600080fd5b60006117c289828a016115c6565b96505060206117d389828a016115c6565b955050604087013567ffffffffffffffff8111156117f057600080fd5b6117fc89828a01611532565b9450945050606087013567ffffffffffffffff81111561181b57600080fd5b61182789828a0161157c565b92509250509295509295509295565b600080600080600080600060a0888a03121561185157600080fd5b600061185f8a828b016115c6565b97505060206118708a828b016115c6565b96505060406118818a828b016115c6565b955050606088013567ffffffffffffffff81111561189e57600080fd5b6118aa8a828b01611532565b9450945050608088013567ffffffffffffffff8111156118c957600080fd5b6118d58a828b0161157c565b925092505092959891949750929550565b6000806000806000806101a0878903121561190057600080fd5b600061190e89828a016115c6565b965050602061191f89828a016115c6565b955050604061193089828a016115c6565b945050606061194189828a016115c6565b935050608061195289828a016115c6565b92505060a061196389828a01611514565b9150509295509295509295565b60006020828403121561198257600080fd5b6000611990848285016115db565b91505092915050565b60006119a58383611ad1565b60208301905092915050565b6119ba81611ee6565b82525050565b6119cd6101008383611f53565b5050565b60006119dd8385611d95565b93507f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff831115611a0c57600080fd5b602083029250611a1d838584611f53565b82840190509392505050565b6000611a358385611da6565b9350611a4082611d7e565b8060005b85811015611a7957611a568284611db7565b611a608882611999565b9750611a6b83611d88565b925050600181019050611a44565b5085925050509392505050565b611a8f81611f2f565b82525050565b8082525050565b611aa581611f18565b82525050565b611ab481611f18565b82525050565b611acb611ac682611f18565b611f62565b82525050565b611ada81611f22565b82525050565b6000611aec8284611aba565b60208201915081905092915050565b6000604082019050611b1060008301856119b1565b611b1d6020830184611a9c565b9392505050565b6000602082019050611b396000830184611a86565b92915050565b6000604082019050611b546000830185611a95565b611b616020830184611aab565b9392505050565b6000608082019050611b7d6000830189611a95565b611b8a6020830188611aab565b8181036040830152611b9d8186886119d1565b90508181036060830152611bb2818486611a29565b9050979650505050505050565b6000606082019050611bd46000830186611a95565b611be16020830185611aab565b611bee6040830184611aab565b949350505050565b600060a082019050611c0b600083018a611a95565b611c186020830189611aab565b611c256040830188611aab565b8181036060830152611c388186886119d1565b90508181036080830152611c4d818486611a29565b905098975050505050505050565b6000602082019050611c706000830184611a9c565b92915050565b6000604082019050611c8b6000830185611a9c565b611c986020830184611a9c565b9392505050565b6000606082019050611cb46000830186611a9c565b611cc16020830185611a9c565b611cce6040830184611a9c565b949350505050565b6000608082019050611ceb6000830187611a9c565b611cf86020830186611a9c565b611d056040830185611a9c565b611d126060830184611a9c565b95945050505050565b60006101a082019050611d316000830189611a9c565b611d3e6020830188611a9c565b611d4b6040830187611a9c565b611d586060830186611a9c565b611d6560808301856119c0565b611d73610180830184611a9c565b979650505050505050565b6000819050919050565b6000602082019050919050565b600082825260208201905092915050565b600082825260208201905092915050565b6000611dc660208401846115db565b905092915050565b6000611dd982611f18565b9150611de483611f18565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115611e1957611e18611f6c565b5b828201905092915050565b6000611e2f82611f18565b9150611e3a83611f18565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615611e7357611e72611f6c565b5b828202905092915050565b6000611e8982611f18565b9150611e9483611f18565b925082821015611ea757611ea6611f6c565b5b828203905092915050565b6000611ebd82611f22565b9150611ec883611f22565b925082821015611edb57611eda611f6c565b5b828203905092915050565b6000611ef182611ef8565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b6000611f3a82611f41565b9050919050565b6000611f4c82611ef8565b9050919050565b82818337600083830152505050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b611fa481611ee6565b8114611faf57600080fd5b50565b611fbb81611f18565b8114611fc657600080fd5b50565b611fd281611f22565b8114611fdd57600080fd5b5056fea2646970667358221220a6777edc645a16d8f81beacbb5ec133f19452c578127e38e7f40d5540508d08464736f6c63430008040033", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100ea5760003560e01c80636389e1071161008c5780639c112141116100665780639c11214114610246578063dabc4d5114610262578063ec45622a14610292578063fcf0b6ec146102ae576100ea565b80636389e107146101c957806365e54f83146101f957806396324bd414610215576100ea565b80633bc778e3116100c85780633bc778e31461014557806343989f8514610161578063568ee8261461017d578063638480be14610199576100ea565b806304245371146100ef5780631783efc31461010b5780632b7ac3f314610127575b600080fd5b61010960048036038101906101049190611655565b6102ca565b005b610125600480360381019061012091906116ad565b610410565b005b61012f6104fc565b60405161013c9190611b24565b60405180910390f35b61015f600480360381019061015a91906118e6565b610522565b005b61017b6004803603810190610176919061179b565b6107cf565b005b61019760048036038101906101929190611619565b6108c3565b005b6101b360048036038101906101ae91906115f0565b610a22565b6040516101c09190611c5b565b60405180910390f35b6101e360048036038101906101de91906115f0565b610a41565b6040516101f09190611c5b565b60405180910390f35b610213600480360381019061020e9190611738565b610a60565b005b61022f600480360381019061022a91906115f0565b610b84565b60405161023d929190611afb565b60405180910390f35b610260600480360381019061025b91906116e9565b610bc8565b005b61027c600480360381019061027791906115f0565b610ced565b6040516102899190611c5b565b60405180910390f35b6102ac60048036038101906102a79190611836565b610d0c565b005b6102c860048036038101906102c391906116ad565b610e02565b005b826102d3610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461036d576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b838390508110156103cf576103c4858585848181106103b8577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020135610f23565b806001019050610370565b5060006103db85610ced565b905042600260008781526020019081526020016000206002016000838152602001908152602001600020819055505050505050565b81610419610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146104b3576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6104bd8383610f23565b60006104c884610ced565b9050426002600086815260200190815260200160002060020160008381526020019081526020016000208190555050505050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600061052d87610a41565b9050600081141561056a576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061057588610ced565b9050808714610648576000600260008a815260200190815260200160002060020160008981526020019081526020016000205490506000600260008b81526020019081526020016000206001015490506000821415610600576040517f4d32958600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b808261060c9190611dce565b421115610645576040517f9581a99000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505b60026000898152602001908152602001600020600301600086815260200190815260200160002060009054906101000a900460ff16156106b4576040517f948d067400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16639aca8f2a8887898888886040518763ffffffff1660e01b815260040161071996959493929190611d1b565b60006040518083038186803b15801561073157600080fd5b505afa158015610745573d6000803e3d6000fd5b505050506001600260008a8152602001908152602001600020600301600087815260200190815260200160002060006101000a81548160ff0219169083151502179055508387897f48950129900df26c2140187532df49c8af343c3daf74f1e99e562e1b2be07adc888a6040516107bd929190611c76565b60405180910390a45050505050505050565b856107d8610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610872576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61088087878787878761104a565b600061088b88610ced565b905042600260008a81526020019081526020016000206002016000838152602001908152602001600020819055505050505050505050565b816108cc610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610966576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b816002600085815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff166109da610f1b565b73ffffffffffffffffffffffffffffffffffffffff16847f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a4505050565b6000806000838152602001908152602001600020600201549050919050565b6000806000838152602001908152602001600020600001549050919050565b826010811080610a705750602081115b15610aa7576040517fecf64f1200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610ab18585611172565b826002600087815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160026000878152602001908152602001600020600101819055508273ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff16867f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a45050505050565b60026020528060005260406000206000915090508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060010154905082565b816010811080610bd85750602081115b15610c0f576040517fecf64f1200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610c198484611172565b816002600086815260200190815260200160002060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610e1060026000868152602001908152602001600020600101819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff16857f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a450505050565b6000806000838152602001908152602001600020600101549050919050565b86610d15610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610daf576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610dbe888888888888886112a3565b6000610dc989610ced565b905042600260008b8152602001908152602001600020600201600083815260200190815260200160002081905550505050505050505050565b81610e0b610f1b565b73ffffffffffffffffffffffffffffffffffffffff166002600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610ea5576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060026000858152602001908152602001600020600101549050826002600086815260200190815260200160002060010181905550837f264b2a8f6763c084235fe832ba903482b2ef1a521336881fc75b987c2dfd29c58285604051610f0d929190611c76565b60405180910390a250505050565b600033905090565b6000610f2e83610a41565b1415610f66576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008083815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__63168703fa9091836040518363ffffffff1660e01b8152600401610fb2929190611b3f565b60006040518083038186803b158015610fca57600080fd5b505af4158015610fde573d6000803e3d6000fd5b505050506000610fed83610ced565b905060006001610ffc85610a22565b6110069190611e7e565b9050837f19239b3f93cd10558aaf11423af70c77763bf54f52bcc75bfa74d4d13548cde982858560405161103c93929190611c9f565b60405180910390a250505050565b600061105587610a41565b141561108d576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008087815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__630629596f909187878787876040518763ffffffff1660e01b81526004016110e196959493929190611b68565b60006040518083038186803b1580156110f957600080fd5b505af415801561110d573d6000803e3d6000fd5b50505050600061111c87610ced565b9050600061112a84846113d0565b9050877f3108849c053c77b8073a11256dffb5ffd5b55e93e105a355e1c9061db890d87182898560405161116093929190611c9f565b60405180910390a25050505050505050565b600061117d83610a41565b146111b4576040517f8121725b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006008836040516020016111c99190611ae0565b6040516020818303038152906040528051906020012060001c901c905060008084815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__631095fbb4909184846040518463ffffffff1660e01b815260040161123493929190611bbf565b60006040518083038186803b15801561124c57600080fd5b505af4158015611260573d6000803e3d6000fd5b50505050827f0d000126c26c1bbe400fd2332187f75d58b69306f9ec47b408686189d3a008338383604051611296929190611c76565b60405180910390a2505050565b60006112ae88610a41565b14156112e6576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008088815260200190815260200160002073__$0c6eb7207c37accf1552a1c47686411ac0$__63a547882790918888888888886040518863ffffffff1660e01b815260040161133c9796959493929190611bf6565b60006040518083038186803b15801561135457600080fd5b505af4158015611368573d6000803e3d6000fd5b50505050600061137788610ced565b9050600061138584846113d0565b9050887fea3588e4a2a0c93d6a0e69dfeaf7496f43ccccf02ad9ce0a5b7627cbca4b61b1828a8a866040516113bd9493929190611cd6565b60405180910390a2505050505050505050565b6000806000905060008484905090505b60008160ff1611156114f457600082118061145b5750600085856001846114079190611eb2565b60ff16818110611440577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020160208101906114559190611970565b60ff1614155b156114e85760028261146d9190611e24565b9150600185856001846114809190611eb2565b60ff168181106114b9577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90506020020160208101906114ce9190611970565b60ff1614156114e7576001826114e49190611dce565b91505b5b806001900390506113e0565b508091505092915050565b60008135905061150e81611f9b565b92915050565b60008190508260206008028201111561152c57600080fd5b92915050565b60008083601f84011261154457600080fd5b8235905067ffffffffffffffff81111561155d57600080fd5b60208301915083602082028301111561157557600080fd5b9250929050565b60008083601f84011261158e57600080fd5b8235905067ffffffffffffffff8111156115a757600080fd5b6020830191508360208202830111156115bf57600080fd5b9250929050565b6000813590506115d581611fb2565b92915050565b6000813590506115ea81611fc9565b92915050565b60006020828403121561160257600080fd5b6000611610848285016115c6565b91505092915050565b6000806040838503121561162c57600080fd5b600061163a858286016115c6565b925050602061164b858286016114ff565b9150509250929050565b60008060006040848603121561166a57600080fd5b6000611678868287016115c6565b935050602084013567ffffffffffffffff81111561169557600080fd5b6116a186828701611532565b92509250509250925092565b600080604083850312156116c057600080fd5b60006116ce858286016115c6565b92505060206116df858286016115c6565b9150509250929050565b6000806000606084860312156116fe57600080fd5b600061170c868287016115c6565b935050602061171d868287016115c6565b925050604061172e868287016114ff565b9150509250925092565b6000806000806080858703121561174e57600080fd5b600061175c878288016115c6565b945050602061176d878288016115c6565b935050604061177e878288016114ff565b925050606061178f878288016115c6565b91505092959194509250565b600080600080600080608087890312156117b457600080fd5b60006117c289828a016115c6565b96505060206117d389828a016115c6565b955050604087013567ffffffffffffffff8111156117f057600080fd5b6117fc89828a01611532565b9450945050606087013567ffffffffffffffff81111561181b57600080fd5b61182789828a0161157c565b92509250509295509295509295565b600080600080600080600060a0888a03121561185157600080fd5b600061185f8a828b016115c6565b97505060206118708a828b016115c6565b96505060406118818a828b016115c6565b955050606088013567ffffffffffffffff81111561189e57600080fd5b6118aa8a828b01611532565b9450945050608088013567ffffffffffffffff8111156118c957600080fd5b6118d58a828b0161157c565b925092505092959891949750929550565b6000806000806000806101a0878903121561190057600080fd5b600061190e89828a016115c6565b965050602061191f89828a016115c6565b955050604061193089828a016115c6565b945050606061194189828a016115c6565b935050608061195289828a016115c6565b92505060a061196389828a01611514565b9150509295509295509295565b60006020828403121561198257600080fd5b6000611990848285016115db565b91505092915050565b60006119a58383611ad1565b60208301905092915050565b6119ba81611ee6565b82525050565b6119cd6101008383611f53565b5050565b60006119dd8385611d95565b93507f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff831115611a0c57600080fd5b602083029250611a1d838584611f53565b82840190509392505050565b6000611a358385611da6565b9350611a4082611d7e565b8060005b85811015611a7957611a568284611db7565b611a608882611999565b9750611a6b83611d88565b925050600181019050611a44565b5085925050509392505050565b611a8f81611f2f565b82525050565b8082525050565b611aa581611f18565b82525050565b611ab481611f18565b82525050565b611acb611ac682611f18565b611f62565b82525050565b611ada81611f22565b82525050565b6000611aec8284611aba565b60208201915081905092915050565b6000604082019050611b1060008301856119b1565b611b1d6020830184611a9c565b9392505050565b6000602082019050611b396000830184611a86565b92915050565b6000604082019050611b546000830185611a95565b611b616020830184611aab565b9392505050565b6000608082019050611b7d6000830189611a95565b611b8a6020830188611aab565b8181036040830152611b9d8186886119d1565b90508181036060830152611bb2818486611a29565b9050979650505050505050565b6000606082019050611bd46000830186611a95565b611be16020830185611aab565b611bee6040830184611aab565b949350505050565b600060a082019050611c0b600083018a611a95565b611c186020830189611aab565b611c256040830188611aab565b8181036060830152611c388186886119d1565b90508181036080830152611c4d818486611a29565b905098975050505050505050565b6000602082019050611c706000830184611a9c565b92915050565b6000604082019050611c8b6000830185611a9c565b611c986020830184611a9c565b9392505050565b6000606082019050611cb46000830186611a9c565b611cc16020830185611a9c565b611cce6040830184611a9c565b949350505050565b6000608082019050611ceb6000830187611a9c565b611cf86020830186611a9c565b611d056040830185611a9c565b611d126060830184611a9c565b95945050505050565b60006101a082019050611d316000830189611a9c565b611d3e6020830188611a9c565b611d4b6040830187611a9c565b611d586060830186611a9c565b611d6560808301856119c0565b611d73610180830184611a9c565b979650505050505050565b6000819050919050565b6000602082019050919050565b600082825260208201905092915050565b600082825260208201905092915050565b6000611dc660208401846115db565b905092915050565b6000611dd982611f18565b9150611de483611f18565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115611e1957611e18611f6c565b5b828201905092915050565b6000611e2f82611f18565b9150611e3a83611f18565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615611e7357611e72611f6c565b5b828202905092915050565b6000611e8982611f18565b9150611e9483611f18565b925082821015611ea757611ea6611f6c565b5b828203905092915050565b6000611ebd82611f22565b9150611ec883611f22565b925082821015611edb57611eda611f6c565b5b828203905092915050565b6000611ef182611ef8565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b6000611f3a82611f41565b9050919050565b6000611f4c82611ef8565b9050919050565b82818337600083830152505050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b611fa481611ee6565b8114611faf57600080fd5b50565b611fbb81611f18565b8114611fc657600080fd5b50565b611fd281611f22565b8114611fdd57600080fd5b5056fea2646970667358221220a6777edc645a16d8f81beacbb5ec133f19452c578127e38e7f40d5540508d08464736f6c63430008040033", + "bytecode": "0x60806040523480156200001157600080fd5b5060405162003559380380620035598339818101604052810190620000379190620000fd565b80600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550506200012f565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620000b18262000084565b9050919050565b6000620000c582620000a4565b9050919050565b620000d781620000b8565b8114620000e357600080fd5b50565b600081519050620000f781620000cc565b92915050565b6000602082840312156200011657620001156200007f565b5b60006200012684828501620000e6565b91505092915050565b61341a806200013f6000396000f3fe608060405234801561001057600080fd5b50600436106101165760003560e01c80637ee35a0c116100a2578063b180517911610071578063b180517914610315578063c96e71fb14610331578063d0d898dd1461034d578063dabc4d5114610369578063fcf0b6ec1461039957610116565b80637ee35a0c1461025557806390509d441461028557806396324bd4146102b5578063a9961c94146102e557610116565b80634178c4d5116100e95780634178c4d5146101a1578063456f4188146101bd578063568ee826146101ed5780636389e107146102095780636cdd32fe1461023957610116565b8063042453711461011b57806306dd8485146101375780631783efc3146101675780632b7ac3f314610183575b600080fd5b61013560048036038101906101309190612756565b6103b5565b005b610151600480360381019061014c91906127b6565b6103ff565b60405161015e9190612805565b60405180910390f35b610181600480360381019061017c91906127b6565b61042d565b005b61018b610475565b604051610198919061289f565b60405180910390f35b6101bb60048036038101906101b691906128ba565b61049b565b005b6101d760048036038101906101d29190612967565b6104e9565b6040516101e491906129c3565b60405180910390f35b61020760048036038101906102029190612a1c565b61092e565b005b610223600480360381019061021e9190612a5c565b61093c565b6040516102309190612805565b60405180910390f35b610253600480360381019061024e9190612a89565b61095b565b005b61026f600480360381019061026a9190612a5c565b6109a7565b60405161027c9190612805565b60405180910390f35b61029f600480360381019061029a91906127b6565b6109c6565b6040516102ac91906129c3565b60405180910390f35b6102cf60048036038101906102ca9190612a5c565b6109f4565b6040516102dc9190612805565b60405180910390f35b6102ff60048036038101906102fa9190612a5c565b610a12565b60405161030c9190612b0c565b60405180910390f35b61032f600480360381019061032a9190612b27565b610a4f565b005b61034b60048036038101906103469190612a1c565b610a79565b005b61036760048036038101906103629190612967565b610aa4565b005b610383600480360381019061037e9190612a5c565b610c8e565b6040516103909190612805565b60405180910390f35b6103b360048036038101906103ae91906127b6565b610cb1565b005b6103c0838383610e5b565b60006103cb84610c8e565b9050426003600086815260200190815260200160002060010160008381526020019081526020016000208190555050505050565b600061042582600080868152602001908152602001600020610ed790919063ffffffff16565b905092915050565b6104378282610f03565b600061044283610c8e565b90504260036000858152602001908152602001600020600101600083815260200190815260200160002081905550505050565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6104a885858585856110af565b60006104b386610c8e565b90504260036000888152602001908152602001600020600101600083815260200190815260200160002081905550505050505050565b600082600073ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610585576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018360000135108061059c5750600c8360000135115b156105d3576040517fecf64f1200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006105de856109a7565b90506000810361061a576040517fc8b02e0100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061062586610c8e565b9050808560200135146106ff57600060036000888152602001908152602001600020600101600087602001358152602001908152602001600020549050600060036000898152602001908152602001600020600001549050600082036106b7576040517f4d32958600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80826106c39190612ba9565b4211156106fc576040517f9581a99000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a23f019960405180604001604052808860a00160006008811061075f5761075e612bdd565b5b602002013581526020018860a0016001600881106107805761077f612bdd565b5b6020020135815250604051806040016040528060405180604001604052808b60a0016002600881106107b5576107b4612bdd565b5b602002013581526020018b60a0016003600881106107d6576107d5612bdd565b5b6020020135815250815260200160405180604001604052808b60a00160046008811061080557610804612bdd565b5b602002013581526020018b60a00160056008811061082657610825612bdd565b5b602002013581525081525060405180604001604052808a60a00160066008811061085357610852612bdd565b5b602002013581526020018a60a00160076008811061087457610873612bdd565b5b602002013581525060405180608001604052808b6020013581526020018b6040013581526020016108a88c60600135611281565b81526020016108ba8c60800135611281565b8152508a600001356040518663ffffffff1660e01b81526004016108e2959493929190612e39565b602060405180830381865afa1580156108ff573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109239190612ebb565b935050505092915050565b61093882826112b8565b5050565b6000806000838152602001908152602001600020600101549050919050565b6109678484848461149e565b600061097285610c8e565b905042600360008781526020019081526020016000206001016000838152602001908152602001600020819055505050505050565b6000806000838152602001908152602001600020600001549050919050565b60006109ec8260008086815260200190815260200160002061166b90919063ffffffff16565b905092915050565b60036020528060005260406000206000915090508060000154905081565b60006001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b610a59838361168e565b806003600085815260200190815260200160002060000181905550505050565b610a83828261168e565b610e1060036000848152602001908152602001600020600001819055505050565b81600073ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610b3e576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6003600084815260200190815260200160002060020160008360400135815260200190815260200160002060009054906101000a900460ff1615610bae576040517f208b15e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610bb883836104e9565b610bee576040517f4aa6bc4000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60016003600085815260200190815260200160002060020160008460400135815260200190815260200160002060006101000a81548160ff02191690831515021790555081608001358260200135847f0c32e14cfe81a05d371c248d22de6b7ae849e981b76a1f8842e7b6da73fc405a8560000135866040013587606001358860a001604051610c819493929190612f02565b60405180910390a4505050565b6000610caa600080848152602001908152602001600020611806565b9050919050565b81600073ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610d4b576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b823373ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610de4576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060036000868152602001908152602001600020600001549050836003600087815260200190815260200160002060000181905550847f264b2a8f6763c084235fe832ba903482b2ef1a521336881fc75b987c2dfd29c58286604051610e4c929190612f48565b60405180910390a25050505050565b6000610e66846109a7565b90506000610e9084846000808981526020019081526020016000206118299092919063ffffffff16565b9050847f61e5e8054e3daf084a0c6c646c065e8bf5e7ca4d5567bda942309bd1652f349d83868685604051610ec89493929190612fe3565b60405180910390a25050505050565b6000600183600301600084815260200190815260200160002054610efb9190613023565b905092915050565b81600073ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610f9d576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b823373ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614611036576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000611041856109a7565b9050600061106985600080898152602001908152602001600020611e2690919063ffffffff16565b9050857f19239b3f93cd10558aaf11423af70c77763bf54f52bcc75bfa74d4d13548cde983878460405161109f93929190613057565b60405180910390a2505050505050565b84600073ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603611149576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b853373ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146111e2576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000611208876000808b8152602001908152602001600020610ed790919063ffffffff16565b90506000611236888888886000808f81526020019081526020016000206120a290949392919063ffffffff16565b9050887fea3588e4a2a0c93d6a0e69dfeaf7496f43ccccf02ad9ce0a5b7627cbca4b61b1838a8a8560405161126e949392919061308e565b60405180910390a2505050505050505050565b600060088260405160200161129691906130f4565b6040516020818303038152906040528051906020012060001c901c9050919050565b81600073ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603611352576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b823373ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146113eb576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b826001600086815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16857f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a450505050565b83600073ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603611538576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b843373ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146115d1576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006115f7866000808a8152602001908152602001600020610ed790919063ffffffff16565b905060006116238787876000808d8152602001908152602001600020612697909392919063ffffffff16565b9050877f3108849c053c77b8073a11256dffb5ffd5b55e93e105a355e1c9061db890d87183898460405161165993929190613057565b60405180910390a25050505050505050565b600080836003016000848152602001908152602001600020541415905092915050565b600073ffffffffffffffffffffffffffffffffffffffff166001600084815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614611727576040517f8121725b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806001600084815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550817ff0adfb94eab6daf835deb69c5738fe636150c3dfd08094a76f39b963dc8cb05a60405160405180910390a28073ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff16837f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a45050565b600081600201600083600101548152602001908152602001600020549050919050565b600080600090505b838390508110156119b4577f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f00000018484838181106118705761186f612bdd565b5b90506020020135106118ae576040517fc380a82e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008484838181106118c3576118c2612bdd565b5b9050602002013503611901576040517f29691be200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6119248585858481811061191857611917612bdd565b5b9050602002013561166b565b1561195b576040517f258a195a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806001866000015461196d9190612ba9565b6119779190612ba9565b8560030160008686858181106119905761198f612bdd565b5b90506020020135815260200190815260200160002081905550806001019050611831565b506060838380806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f8201169050808301925050505050505090505b838390508560000154611a0f9190612ba9565b85600101546002611a209190613242565b1015611a47576001856001016000828254611a3b9190612ba9565b925050819055506119fc565b6000856000015490506000858590508760000154611a659190612ba9565b90506000600183901c90506000600180600185611a829190613023565b901c611a8e9190612ba9565b905060005b8960010154811015611da45760008383611aad9190613023565b905060008167ffffffffffffffff811115611acb57611aca61328d565b5b604051908082528060200260200182016040528015611af95781602001602082028036833780820191505090505b50905060005b82811015611ccb5760008088600160028a86611b1b9190612ba9565b611b2591906132bc565b611b2f9190612ba9565b1015611b81578a8a600160028b87611b479190612ba9565b611b5191906132bc565b611b5b9190612ba9565b611b659190613023565b81518110611b7657611b75612bdd565b5b602002602001015191505b8960028985611b909190612ba9565b611b9a91906132bc565b1015611bbd578e6002016000878152602001908152602001600020549050611bfd565b8a8a60028a86611bcd9190612ba9565b611bd791906132bc565b611be19190613023565b81518110611bf257611bf1612bdd565b5b602002602001015190505b6000808314611c995773__$75f79a42d9bcbdbb69ad79ebd80f556f39$__63561558fe6040518060400160405280858152602001868152506040518263ffffffff1660e01b8152600401611c519190613387565b602060405180830381865af4158015611c6e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c9291906133b7565b9050611c9d565b8190505b80858581518110611cb157611cb0612bdd565b5b602002602001018181525050836001019350505050611aff565b50600180871603611d1c578760018951611ce59190613023565b81518110611cf657611cf5612bdd565b5b60200260200101518c600201600085815260200190815260200160002081905550611d69565b600188511115611d68578760028951611d359190613023565b81518110611d4657611d45612bdd565b5b60200260200101518c6002016000858152602001908152602001600020819055505b5b849650600185901c9450809750839550600180600186611d899190613023565b901c611d959190612ba9565b93508260010192505050611a93565b5087879050896000016000828254611dbc9190612ba9565b9250508190555084600081518110611dd757611dd6612bdd565b5b60200260200101518960020160008b6001015481526020019081526020016000208190555084600081518110611e1057611e0f612bdd565b5b6020026020010151955050505050509392505050565b60007f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f00000018210611e81576040517fc380a82e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008203611ebb576040517f29691be200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611ec5838361166b565b15611efc576040517f258a195a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5b60018360000154611f0e9190612ba9565b83600101546002611f1f9190613242565b1015611f46576001836001016000828254611f3a9190612ba9565b92505081905550611efd565b600083600001549050600083905060005b856001015481101561203e576001808285901c16036120185773__$75f79a42d9bcbdbb69ad79ebd80f556f39$__63561558fe6040518060400160405280896002016000868152602001908152602001600020548152602001858152506040518263ffffffff1660e01b8152600401611fd09190613387565b602060405180830381865af4158015611fed573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061201191906133b7565b9150612033565b81866002016000838152602001908152602001600020819055505b806001019050611f57565b5060018560000160008282546120549190612ba9565b92505081905550808560020160008760010154815260200190815260200160002081905550846000015485600301600086815260200190815260200160002081905550809250505092915050565b60007f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f000000184106120fd576040517fc380a82e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612107868661166b565b61213d576040517f7204756c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600084141580156121545750612153868561166b565b5b1561218b576040517f258a195a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600083839050036121c8576040517f3fa930c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006121d48787610ed7565b905060008590506000879050600060018a600001546121f39190613023565b90506000805b8b600101548110156125dc576001808288901c16036123dd577f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f000000189898481811061224657612245612bdd565b5b9050602002013510612284576040517fc380a82e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73__$75f79a42d9bcbdbb69ad79ebd80f556f39$__63561558fe60405180604001604052808c8c878181106122bc576122bb612bdd565b5b905060200201358152602001888152506040518263ffffffff1660e01b81526004016122e89190613387565b602060405180830381865af4158015612305573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061232991906133b7565b945073__$75f79a42d9bcbdbb69ad79ebd80f556f39$__63561558fe60405180604001604052808c8c8781811061236357612362612bdd565b5b905060200201358152602001878152506040518263ffffffff1660e01b815260040161238f9190613387565b602060405180830381865af41580156123ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123d091906133b7565b93508160010191506125d1565b8083901c8187901c146125b5577f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f000000189898481811061241e5761241d612bdd565b5b905060200201351061245c576040517fc380a82e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73__$75f79a42d9bcbdbb69ad79ebd80f556f39$__63561558fe60405180604001604052808881526020018c8c8781811061249a57612499612bdd565b5b905060200201358152506040518263ffffffff1660e01b81526004016124c09190613387565b602060405180830381865af41580156124dd573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061250191906133b7565b945073__$75f79a42d9bcbdbb69ad79ebd80f556f39$__63561558fe60405180604001604052808781526020018c8c8781811061254157612540612bdd565b5b905060200201358152506040518263ffffffff1660e01b81526004016125679190613387565b602060405180830381865af4158015612584573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125a891906133b7565b93508160010191506125d0565b848c6002016000848152602001908152602001600020819055505b5b8060010190506121f9565b506125e68b611806565b831461261e576040517f3fa930c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b838b60020160008d600101548152602001908152602001600020819055508a60030160008b8152602001908152602001600020548b60030160008b81526020019081526020016000208190555060008b60030160008c815260200190815260200160002081905550839550505050505095945050505050565b60006126a78585600086866120a2565b9050949350505050565b600080fd5b600080fd5b6000819050919050565b6126ce816126bb565b81146126d957600080fd5b50565b6000813590506126eb816126c5565b92915050565b600080fd5b600080fd5b600080fd5b60008083601f840112612716576127156126f1565b5b8235905067ffffffffffffffff811115612733576127326126f6565b5b60208301915083602082028301111561274f5761274e6126fb565b5b9250929050565b60008060006040848603121561276f5761276e6126b1565b5b600061277d868287016126dc565b935050602084013567ffffffffffffffff81111561279e5761279d6126b6565b5b6127aa86828701612700565b92509250509250925092565b600080604083850312156127cd576127cc6126b1565b5b60006127db858286016126dc565b92505060206127ec858286016126dc565b9150509250929050565b6127ff816126bb565b82525050565b600060208201905061281a60008301846127f6565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600061286561286061285b84612820565b612840565b612820565b9050919050565b60006128778261284a565b9050919050565b60006128898261286c565b9050919050565b6128998161287e565b82525050565b60006020820190506128b46000830184612890565b92915050565b6000806000806000608086880312156128d6576128d56126b1565b5b60006128e4888289016126dc565b95505060206128f5888289016126dc565b9450506040612906888289016126dc565b935050606086013567ffffffffffffffff811115612927576129266126b6565b5b61293388828901612700565b92509250509295509295909350565b600080fd5b60006101a0828403121561295e5761295d612942565b5b81905092915050565b6000806101c0838503121561297f5761297e6126b1565b5b600061298d858286016126dc565b925050602061299e85828601612947565b9150509250929050565b60008115159050919050565b6129bd816129a8565b82525050565b60006020820190506129d860008301846129b4565b92915050565b60006129e982612820565b9050919050565b6129f9816129de565b8114612a0457600080fd5b50565b600081359050612a16816129f0565b92915050565b60008060408385031215612a3357612a326126b1565b5b6000612a41858286016126dc565b9250506020612a5285828601612a07565b9150509250929050565b600060208284031215612a7257612a716126b1565b5b6000612a80848285016126dc565b91505092915050565b60008060008060608587031215612aa357612aa26126b1565b5b6000612ab1878288016126dc565b9450506020612ac2878288016126dc565b935050604085013567ffffffffffffffff811115612ae357612ae26126b6565b5b612aef87828801612700565b925092505092959194509250565b612b06816129de565b82525050565b6000602082019050612b216000830184612afd565b92915050565b600080600060608486031215612b4057612b3f6126b1565b5b6000612b4e868287016126dc565b9350506020612b5f86828701612a07565b9250506040612b70868287016126dc565b9150509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000612bb4826126bb565b9150612bbf836126bb565b9250828201905080821115612bd757612bd6612b7a565b5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060029050919050565b600081905092915050565b6000819050919050565b612c35816126bb565b82525050565b6000612c478383612c2c565b60208301905092915050565b6000602082019050919050565b612c6981612c0c565b612c738184612c17565b9250612c7e82612c22565b8060005b83811015612caf578151612c968782612c3b565b9650612ca183612c53565b925050600181019050612c82565b505050505050565b600060029050919050565b600081905092915050565b6000819050919050565b600081905092915050565b612ceb81612c0c565b612cf58184612cd7565b9250612d0082612c22565b8060005b83811015612d31578151612d188782612c3b565b9650612d2383612c53565b925050600181019050612d04565b505050505050565b6000612d458383612ce2565b60408301905092915050565b6000602082019050919050565b612d6781612cb7565b612d718184612cc2565b9250612d7c82612ccd565b8060005b83811015612dad578151612d948782612d39565b9650612d9f83612d51565b925050600181019050612d80565b505050505050565b600060049050919050565b600081905092915050565b6000819050919050565b6000602082019050919050565b612deb81612db5565b612df58184612dc0565b9250612e0082612dcb565b8060005b83811015612e31578151612e188782612c3b565b9650612e2383612dd5565b925050600181019050612e04565b505050505050565b60006101a082019050612e4f6000830188612c60565b612e5c6040830187612d5e565b612e6960c0830186612c60565b612e77610100830185612de2565b612e856101808301846127f6565b9695505050505050565b612e98816129a8565b8114612ea357600080fd5b50565b600081519050612eb581612e8f565b92915050565b600060208284031215612ed157612ed06126b1565b5b6000612edf84828501612ea6565b91505092915050565b82818337505050565b612efe6101008383612ee8565b5050565b600061016082019050612f1860008301876127f6565b612f2560208301866127f6565b612f3260408301856127f6565b612f3f6060830184612ef1565b95945050505050565b6000604082019050612f5d60008301856127f6565b612f6a60208301846127f6565b9392505050565b600082825260208201905092915050565b600080fd5b6000612f938385612f71565b93507f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff831115612fc657612fc5612f82565b5b602083029250612fd7838584612ee8565b82840190509392505050565b6000606082019050612ff860008301876127f6565b818103602083015261300b818587612f87565b905061301a60408301846127f6565b95945050505050565b600061302e826126bb565b9150613039836126bb565b925082820390508181111561305157613050612b7a565b5b92915050565b600060608201905061306c60008301866127f6565b61307960208301856127f6565b61308660408301846127f6565b949350505050565b60006080820190506130a360008301876127f6565b6130b060208301866127f6565b6130bd60408301856127f6565b6130ca60608301846127f6565b95945050505050565b6000819050919050565b6130ee6130e9826126bb565b6130d3565b82525050565b600061310082846130dd565b60208201915081905092915050565b60008160011c9050919050565b6000808291508390505b60018511156131665780860481111561314257613141612b7a565b5b60018516156131515780820291505b808102905061315f8561310f565b9450613126565b94509492505050565b60008261317f576001905061323b565b8161318d576000905061323b565b81600181146131a357600281146131ad576131dc565b600191505061323b565b60ff8411156131bf576131be612b7a565b5b8360020a9150848211156131d6576131d5612b7a565b5b5061323b565b5060208310610133831016604e8410600b84101617156132115782820a90508381111561320c5761320b612b7a565b5b61323b565b61321e848484600161311c565b9250905081840481111561323557613234612b7a565b5b81810290505b9392505050565b600061324d826126bb565b9150613258836126bb565b92506132857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff848461316f565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006132c7826126bb565b91506132d2836126bb565b92508282026132e0816126bb565b915082820484148315176132f7576132f6612b7a565b5b5092915050565b600081905092915050565b613312816126bb565b82525050565b60006133248383613309565b60208301905092915050565b61333981612c0c565b61334381846132fe565b925061334e82612c22565b8060005b8381101561337f5781516133668782613318565b965061337183612c53565b925050600181019050613352565b505050505050565b600060408201905061339c6000830184613330565b92915050565b6000815190506133b1816126c5565b92915050565b6000602082840312156133cd576133cc6126b1565b5b60006133db848285016133a2565b9150509291505056fea26469706673582212203293c3b6e3ca144799c7f25fa4dac9e509e5056ef370283b9f694d76cd9eeb5a64736f6c63430008170033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106101165760003560e01c80637ee35a0c116100a2578063b180517911610071578063b180517914610315578063c96e71fb14610331578063d0d898dd1461034d578063dabc4d5114610369578063fcf0b6ec1461039957610116565b80637ee35a0c1461025557806390509d441461028557806396324bd4146102b5578063a9961c94146102e557610116565b80634178c4d5116100e95780634178c4d5146101a1578063456f4188146101bd578063568ee826146101ed5780636389e107146102095780636cdd32fe1461023957610116565b8063042453711461011b57806306dd8485146101375780631783efc3146101675780632b7ac3f314610183575b600080fd5b61013560048036038101906101309190612756565b6103b5565b005b610151600480360381019061014c91906127b6565b6103ff565b60405161015e9190612805565b60405180910390f35b610181600480360381019061017c91906127b6565b61042d565b005b61018b610475565b604051610198919061289f565b60405180910390f35b6101bb60048036038101906101b691906128ba565b61049b565b005b6101d760048036038101906101d29190612967565b6104e9565b6040516101e491906129c3565b60405180910390f35b61020760048036038101906102029190612a1c565b61092e565b005b610223600480360381019061021e9190612a5c565b61093c565b6040516102309190612805565b60405180910390f35b610253600480360381019061024e9190612a89565b61095b565b005b61026f600480360381019061026a9190612a5c565b6109a7565b60405161027c9190612805565b60405180910390f35b61029f600480360381019061029a91906127b6565b6109c6565b6040516102ac91906129c3565b60405180910390f35b6102cf60048036038101906102ca9190612a5c565b6109f4565b6040516102dc9190612805565b60405180910390f35b6102ff60048036038101906102fa9190612a5c565b610a12565b60405161030c9190612b0c565b60405180910390f35b61032f600480360381019061032a9190612b27565b610a4f565b005b61034b60048036038101906103469190612a1c565b610a79565b005b61036760048036038101906103629190612967565b610aa4565b005b610383600480360381019061037e9190612a5c565b610c8e565b6040516103909190612805565b60405180910390f35b6103b360048036038101906103ae91906127b6565b610cb1565b005b6103c0838383610e5b565b60006103cb84610c8e565b9050426003600086815260200190815260200160002060010160008381526020019081526020016000208190555050505050565b600061042582600080868152602001908152602001600020610ed790919063ffffffff16565b905092915050565b6104378282610f03565b600061044283610c8e565b90504260036000858152602001908152602001600020600101600083815260200190815260200160002081905550505050565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6104a885858585856110af565b60006104b386610c8e565b90504260036000888152602001908152602001600020600101600083815260200190815260200160002081905550505050505050565b600082600073ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610585576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018360000135108061059c5750600c8360000135115b156105d3576040517fecf64f1200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006105de856109a7565b90506000810361061a576040517fc8b02e0100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061062586610c8e565b9050808560200135146106ff57600060036000888152602001908152602001600020600101600087602001358152602001908152602001600020549050600060036000898152602001908152602001600020600001549050600082036106b7576040517f4d32958600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80826106c39190612ba9565b4211156106fc576040517f9581a99000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a23f019960405180604001604052808860a00160006008811061075f5761075e612bdd565b5b602002013581526020018860a0016001600881106107805761077f612bdd565b5b6020020135815250604051806040016040528060405180604001604052808b60a0016002600881106107b5576107b4612bdd565b5b602002013581526020018b60a0016003600881106107d6576107d5612bdd565b5b6020020135815250815260200160405180604001604052808b60a00160046008811061080557610804612bdd565b5b602002013581526020018b60a00160056008811061082657610825612bdd565b5b602002013581525081525060405180604001604052808a60a00160066008811061085357610852612bdd565b5b602002013581526020018a60a00160076008811061087457610873612bdd565b5b602002013581525060405180608001604052808b6020013581526020018b6040013581526020016108a88c60600135611281565b81526020016108ba8c60800135611281565b8152508a600001356040518663ffffffff1660e01b81526004016108e2959493929190612e39565b602060405180830381865afa1580156108ff573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109239190612ebb565b935050505092915050565b61093882826112b8565b5050565b6000806000838152602001908152602001600020600101549050919050565b6109678484848461149e565b600061097285610c8e565b905042600360008781526020019081526020016000206001016000838152602001908152602001600020819055505050505050565b6000806000838152602001908152602001600020600001549050919050565b60006109ec8260008086815260200190815260200160002061166b90919063ffffffff16565b905092915050565b60036020528060005260406000206000915090508060000154905081565b60006001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b610a59838361168e565b806003600085815260200190815260200160002060000181905550505050565b610a83828261168e565b610e1060036000848152602001908152602001600020600001819055505050565b81600073ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610b3e576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6003600084815260200190815260200160002060020160008360400135815260200190815260200160002060009054906101000a900460ff1615610bae576040517f208b15e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610bb883836104e9565b610bee576040517f4aa6bc4000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60016003600085815260200190815260200160002060020160008460400135815260200190815260200160002060006101000a81548160ff02191690831515021790555081608001358260200135847f0c32e14cfe81a05d371c248d22de6b7ae849e981b76a1f8842e7b6da73fc405a8560000135866040013587606001358860a001604051610c819493929190612f02565b60405180910390a4505050565b6000610caa600080848152602001908152602001600020611806565b9050919050565b81600073ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610d4b576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b823373ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610de4576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060036000868152602001908152602001600020600001549050836003600087815260200190815260200160002060000181905550847f264b2a8f6763c084235fe832ba903482b2ef1a521336881fc75b987c2dfd29c58286604051610e4c929190612f48565b60405180910390a25050505050565b6000610e66846109a7565b90506000610e9084846000808981526020019081526020016000206118299092919063ffffffff16565b9050847f61e5e8054e3daf084a0c6c646c065e8bf5e7ca4d5567bda942309bd1652f349d83868685604051610ec89493929190612fe3565b60405180910390a25050505050565b6000600183600301600084815260200190815260200160002054610efb9190613023565b905092915050565b81600073ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610f9d576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b823373ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614611036576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000611041856109a7565b9050600061106985600080898152602001908152602001600020611e2690919063ffffffff16565b9050857f19239b3f93cd10558aaf11423af70c77763bf54f52bcc75bfa74d4d13548cde983878460405161109f93929190613057565b60405180910390a2505050505050565b84600073ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603611149576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b853373ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146111e2576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000611208876000808b8152602001908152602001600020610ed790919063ffffffff16565b90506000611236888888886000808f81526020019081526020016000206120a290949392919063ffffffff16565b9050887fea3588e4a2a0c93d6a0e69dfeaf7496f43ccccf02ad9ce0a5b7627cbca4b61b1838a8a8560405161126e949392919061308e565b60405180910390a2505050505050505050565b600060088260405160200161129691906130f4565b6040516020818303038152906040528051906020012060001c901c9050919050565b81600073ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603611352576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b823373ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146113eb576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b826001600086815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16857f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a450505050565b83600073ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603611538576040517f029f057900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b843373ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146115d1576040517fbb9bf27800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006115f7866000808a8152602001908152602001600020610ed790919063ffffffff16565b905060006116238787876000808d8152602001908152602001600020612697909392919063ffffffff16565b9050877f3108849c053c77b8073a11256dffb5ffd5b55e93e105a355e1c9061db890d87183898460405161165993929190613057565b60405180910390a25050505050505050565b600080836003016000848152602001908152602001600020541415905092915050565b600073ffffffffffffffffffffffffffffffffffffffff166001600084815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614611727576040517f8121725b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806001600084815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550817ff0adfb94eab6daf835deb69c5738fe636150c3dfd08094a76f39b963dc8cb05a60405160405180910390a28073ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff16837f0ba83579a0e79193ef649b9f5a8759d35af086ba62a3e207b52e4a8ae30d49e360405160405180910390a45050565b600081600201600083600101548152602001908152602001600020549050919050565b600080600090505b838390508110156119b4577f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f00000018484838181106118705761186f612bdd565b5b90506020020135106118ae576040517fc380a82e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008484838181106118c3576118c2612bdd565b5b9050602002013503611901576040517f29691be200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6119248585858481811061191857611917612bdd565b5b9050602002013561166b565b1561195b576040517f258a195a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806001866000015461196d9190612ba9565b6119779190612ba9565b8560030160008686858181106119905761198f612bdd565b5b90506020020135815260200190815260200160002081905550806001019050611831565b506060838380806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f8201169050808301925050505050505090505b838390508560000154611a0f9190612ba9565b85600101546002611a209190613242565b1015611a47576001856001016000828254611a3b9190612ba9565b925050819055506119fc565b6000856000015490506000858590508760000154611a659190612ba9565b90506000600183901c90506000600180600185611a829190613023565b901c611a8e9190612ba9565b905060005b8960010154811015611da45760008383611aad9190613023565b905060008167ffffffffffffffff811115611acb57611aca61328d565b5b604051908082528060200260200182016040528015611af95781602001602082028036833780820191505090505b50905060005b82811015611ccb5760008088600160028a86611b1b9190612ba9565b611b2591906132bc565b611b2f9190612ba9565b1015611b81578a8a600160028b87611b479190612ba9565b611b5191906132bc565b611b5b9190612ba9565b611b659190613023565b81518110611b7657611b75612bdd565b5b602002602001015191505b8960028985611b909190612ba9565b611b9a91906132bc565b1015611bbd578e6002016000878152602001908152602001600020549050611bfd565b8a8a60028a86611bcd9190612ba9565b611bd791906132bc565b611be19190613023565b81518110611bf257611bf1612bdd565b5b602002602001015190505b6000808314611c995773__$75f79a42d9bcbdbb69ad79ebd80f556f39$__63561558fe6040518060400160405280858152602001868152506040518263ffffffff1660e01b8152600401611c519190613387565b602060405180830381865af4158015611c6e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c9291906133b7565b9050611c9d565b8190505b80858581518110611cb157611cb0612bdd565b5b602002602001018181525050836001019350505050611aff565b50600180871603611d1c578760018951611ce59190613023565b81518110611cf657611cf5612bdd565b5b60200260200101518c600201600085815260200190815260200160002081905550611d69565b600188511115611d68578760028951611d359190613023565b81518110611d4657611d45612bdd565b5b60200260200101518c6002016000858152602001908152602001600020819055505b5b849650600185901c9450809750839550600180600186611d899190613023565b901c611d959190612ba9565b93508260010192505050611a93565b5087879050896000016000828254611dbc9190612ba9565b9250508190555084600081518110611dd757611dd6612bdd565b5b60200260200101518960020160008b6001015481526020019081526020016000208190555084600081518110611e1057611e0f612bdd565b5b6020026020010151955050505050509392505050565b60007f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f00000018210611e81576040517fc380a82e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008203611ebb576040517f29691be200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611ec5838361166b565b15611efc576040517f258a195a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5b60018360000154611f0e9190612ba9565b83600101546002611f1f9190613242565b1015611f46576001836001016000828254611f3a9190612ba9565b92505081905550611efd565b600083600001549050600083905060005b856001015481101561203e576001808285901c16036120185773__$75f79a42d9bcbdbb69ad79ebd80f556f39$__63561558fe6040518060400160405280896002016000868152602001908152602001600020548152602001858152506040518263ffffffff1660e01b8152600401611fd09190613387565b602060405180830381865af4158015611fed573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061201191906133b7565b9150612033565b81866002016000838152602001908152602001600020819055505b806001019050611f57565b5060018560000160008282546120549190612ba9565b92505081905550808560020160008760010154815260200190815260200160002081905550846000015485600301600086815260200190815260200160002081905550809250505092915050565b60007f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f000000184106120fd576040517fc380a82e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612107868661166b565b61213d576040517f7204756c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600084141580156121545750612153868561166b565b5b1561218b576040517f258a195a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600083839050036121c8576040517f3fa930c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006121d48787610ed7565b905060008590506000879050600060018a600001546121f39190613023565b90506000805b8b600101548110156125dc576001808288901c16036123dd577f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f000000189898481811061224657612245612bdd565b5b9050602002013510612284576040517fc380a82e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73__$75f79a42d9bcbdbb69ad79ebd80f556f39$__63561558fe60405180604001604052808c8c878181106122bc576122bb612bdd565b5b905060200201358152602001888152506040518263ffffffff1660e01b81526004016122e89190613387565b602060405180830381865af4158015612305573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061232991906133b7565b945073__$75f79a42d9bcbdbb69ad79ebd80f556f39$__63561558fe60405180604001604052808c8c8781811061236357612362612bdd565b5b905060200201358152602001878152506040518263ffffffff1660e01b815260040161238f9190613387565b602060405180830381865af41580156123ac573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123d091906133b7565b93508160010191506125d1565b8083901c8187901c146125b5577f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f000000189898481811061241e5761241d612bdd565b5b905060200201351061245c576040517fc380a82e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73__$75f79a42d9bcbdbb69ad79ebd80f556f39$__63561558fe60405180604001604052808881526020018c8c8781811061249a57612499612bdd565b5b905060200201358152506040518263ffffffff1660e01b81526004016124c09190613387565b602060405180830381865af41580156124dd573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061250191906133b7565b945073__$75f79a42d9bcbdbb69ad79ebd80f556f39$__63561558fe60405180604001604052808781526020018c8c8781811061254157612540612bdd565b5b905060200201358152506040518263ffffffff1660e01b81526004016125679190613387565b602060405180830381865af4158015612584573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125a891906133b7565b93508160010191506125d0565b848c6002016000848152602001908152602001600020819055505b5b8060010190506121f9565b506125e68b611806565b831461261e576040517f3fa930c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b838b60020160008d600101548152602001908152602001600020819055508a60030160008b8152602001908152602001600020548b60030160008b81526020019081526020016000208190555060008b60030160008c815260200190815260200160002081905550839550505050505095945050505050565b60006126a78585600086866120a2565b9050949350505050565b600080fd5b600080fd5b6000819050919050565b6126ce816126bb565b81146126d957600080fd5b50565b6000813590506126eb816126c5565b92915050565b600080fd5b600080fd5b600080fd5b60008083601f840112612716576127156126f1565b5b8235905067ffffffffffffffff811115612733576127326126f6565b5b60208301915083602082028301111561274f5761274e6126fb565b5b9250929050565b60008060006040848603121561276f5761276e6126b1565b5b600061277d868287016126dc565b935050602084013567ffffffffffffffff81111561279e5761279d6126b6565b5b6127aa86828701612700565b92509250509250925092565b600080604083850312156127cd576127cc6126b1565b5b60006127db858286016126dc565b92505060206127ec858286016126dc565b9150509250929050565b6127ff816126bb565b82525050565b600060208201905061281a60008301846127f6565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600061286561286061285b84612820565b612840565b612820565b9050919050565b60006128778261284a565b9050919050565b60006128898261286c565b9050919050565b6128998161287e565b82525050565b60006020820190506128b46000830184612890565b92915050565b6000806000806000608086880312156128d6576128d56126b1565b5b60006128e4888289016126dc565b95505060206128f5888289016126dc565b9450506040612906888289016126dc565b935050606086013567ffffffffffffffff811115612927576129266126b6565b5b61293388828901612700565b92509250509295509295909350565b600080fd5b60006101a0828403121561295e5761295d612942565b5b81905092915050565b6000806101c0838503121561297f5761297e6126b1565b5b600061298d858286016126dc565b925050602061299e85828601612947565b9150509250929050565b60008115159050919050565b6129bd816129a8565b82525050565b60006020820190506129d860008301846129b4565b92915050565b60006129e982612820565b9050919050565b6129f9816129de565b8114612a0457600080fd5b50565b600081359050612a16816129f0565b92915050565b60008060408385031215612a3357612a326126b1565b5b6000612a41858286016126dc565b9250506020612a5285828601612a07565b9150509250929050565b600060208284031215612a7257612a716126b1565b5b6000612a80848285016126dc565b91505092915050565b60008060008060608587031215612aa357612aa26126b1565b5b6000612ab1878288016126dc565b9450506020612ac2878288016126dc565b935050604085013567ffffffffffffffff811115612ae357612ae26126b6565b5b612aef87828801612700565b925092505092959194509250565b612b06816129de565b82525050565b6000602082019050612b216000830184612afd565b92915050565b600080600060608486031215612b4057612b3f6126b1565b5b6000612b4e868287016126dc565b9350506020612b5f86828701612a07565b9250506040612b70868287016126dc565b9150509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000612bb4826126bb565b9150612bbf836126bb565b9250828201905080821115612bd757612bd6612b7a565b5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060029050919050565b600081905092915050565b6000819050919050565b612c35816126bb565b82525050565b6000612c478383612c2c565b60208301905092915050565b6000602082019050919050565b612c6981612c0c565b612c738184612c17565b9250612c7e82612c22565b8060005b83811015612caf578151612c968782612c3b565b9650612ca183612c53565b925050600181019050612c82565b505050505050565b600060029050919050565b600081905092915050565b6000819050919050565b600081905092915050565b612ceb81612c0c565b612cf58184612cd7565b9250612d0082612c22565b8060005b83811015612d31578151612d188782612c3b565b9650612d2383612c53565b925050600181019050612d04565b505050505050565b6000612d458383612ce2565b60408301905092915050565b6000602082019050919050565b612d6781612cb7565b612d718184612cc2565b9250612d7c82612ccd565b8060005b83811015612dad578151612d948782612d39565b9650612d9f83612d51565b925050600181019050612d80565b505050505050565b600060049050919050565b600081905092915050565b6000819050919050565b6000602082019050919050565b612deb81612db5565b612df58184612dc0565b9250612e0082612dcb565b8060005b83811015612e31578151612e188782612c3b565b9650612e2383612dd5565b925050600181019050612e04565b505050505050565b60006101a082019050612e4f6000830188612c60565b612e5c6040830187612d5e565b612e6960c0830186612c60565b612e77610100830185612de2565b612e856101808301846127f6565b9695505050505050565b612e98816129a8565b8114612ea357600080fd5b50565b600081519050612eb581612e8f565b92915050565b600060208284031215612ed157612ed06126b1565b5b6000612edf84828501612ea6565b91505092915050565b82818337505050565b612efe6101008383612ee8565b5050565b600061016082019050612f1860008301876127f6565b612f2560208301866127f6565b612f3260408301856127f6565b612f3f6060830184612ef1565b95945050505050565b6000604082019050612f5d60008301856127f6565b612f6a60208301846127f6565b9392505050565b600082825260208201905092915050565b600080fd5b6000612f938385612f71565b93507f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff831115612fc657612fc5612f82565b5b602083029250612fd7838584612ee8565b82840190509392505050565b6000606082019050612ff860008301876127f6565b818103602083015261300b818587612f87565b905061301a60408301846127f6565b95945050505050565b600061302e826126bb565b9150613039836126bb565b925082820390508181111561305157613050612b7a565b5b92915050565b600060608201905061306c60008301866127f6565b61307960208301856127f6565b61308660408301846127f6565b949350505050565b60006080820190506130a360008301876127f6565b6130b060208301866127f6565b6130bd60408301856127f6565b6130ca60608301846127f6565b95945050505050565b6000819050919050565b6130ee6130e9826126bb565b6130d3565b82525050565b600061310082846130dd565b60208201915081905092915050565b60008160011c9050919050565b6000808291508390505b60018511156131665780860481111561314257613141612b7a565b5b60018516156131515780820291505b808102905061315f8561310f565b9450613126565b94509492505050565b60008261317f576001905061323b565b8161318d576000905061323b565b81600181146131a357600281146131ad576131dc565b600191505061323b565b60ff8411156131bf576131be612b7a565b5b8360020a9150848211156131d6576131d5612b7a565b5b5061323b565b5060208310610133831016604e8410600b84101617156132115782820a90508381111561320c5761320b612b7a565b5b61323b565b61321e848484600161311c565b9250905081840481111561323557613234612b7a565b5b81810290505b9392505050565b600061324d826126bb565b9150613258836126bb565b92506132857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff848461316f565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006132c7826126bb565b91506132d2836126bb565b92508282026132e0816126bb565b915082820484148315176132f7576132f6612b7a565b5b5092915050565b600081905092915050565b613312816126bb565b82525050565b60006133248383613309565b60208301905092915050565b61333981612c0c565b61334381846132fe565b925061334e82612c22565b8060005b8381101561337f5781516133668782613318565b965061337183612c53565b925050600181019050613352565b505050505050565b600060408201905061339c6000830184613330565b92915050565b6000815190506133b1816126c5565b92915050565b6000602082840312156133cd576133cc6126b1565b5b60006133db848285016133a2565b9150509291505056fea26469706673582212203293c3b6e3ca144799c7f25fa4dac9e509e5056ef370283b9f694d76cd9eeb5a64736f6c63430008170033", "linkReferences": { - "@zk-kit/incremental-merkle-tree.sol/IncrementalBinaryTree.sol": { - "IncrementalBinaryTree": [ + "poseidon-solidity/PoseidonT3.sol": { + "PoseidonT3": [ + { + "length": 20, + "start": 7495 + }, + { + "length": 20, + "start": 8369 + }, { "length": 20, - "start": 4270 + "start": 9157 }, { "length": 20, - "start": 4565 + "start": 9324 }, { "length": 20, - "start": 4910 + "start": 9629 }, { "length": 20, - "start": 5166 + "start": 9796 } ] } }, "deployedLinkReferences": { - "@zk-kit/incremental-merkle-tree.sol/IncrementalBinaryTree.sol": { - "IncrementalBinaryTree": [ + "poseidon-solidity/PoseidonT3.sol": { + "PoseidonT3": [ + { + "length": 20, + "start": 7176 + }, + { + "length": 20, + "start": 8050 + }, { "length": 20, - "start": 3962 + "start": 8838 }, { "length": 20, - "start": 4257 + "start": 9005 }, { "length": 20, - "start": 4602 + "start": 9310 }, { "length": 20, - "start": 4858 + "start": 9477 } ] } diff --git a/apps/subgraph/docker-compose-graph.yml b/apps/subgraph/docker-compose.yml similarity index 58% rename from apps/subgraph/docker-compose-graph.yml rename to apps/subgraph/docker-compose.yml index 332efbc5b..e752dbb6d 100644 --- a/apps/subgraph/docker-compose-graph.yml +++ b/apps/subgraph/docker-compose.yml @@ -1,4 +1,6 @@ -version: "3.9" +# https://github.com/graphprotocol/graph-node/blob/master/docker/docker-compose.yml + +version: "3" services: graph-node: @@ -12,49 +14,35 @@ services: depends_on: - ipfs - postgres + extra_hosts: + - host.docker.internal:host-gateway environment: postgres_host: postgres postgres_user: graph-node postgres_pass: let-me-in postgres_db: graph-node - postgres_port: 5432 ipfs: "ipfs:5001" - ethereum: "localhost:http://host.docker.internal:8545" # Should use the `localhost` as network name in subgraph.yml - # ethereum: 'goerli:https://goerli.infura.io/v3/YOUR-API-KEY' + ethereum: "localhost:http://host.docker.internal:8545" GRAPH_LOG: info - GRAPH_ALLOW_NON_DETERMINISTIC_IPFS: 1 - networks: - - the-graph - ipfs: - image: ipfs/go-ipfs:v0.4.23 + image: ipfs/kubo:v0.14.0 ports: - "5001:5001" volumes: - - graph-ipfs:/data/ipfs - networks: - - the-graph - + - ./data/ipfs:/data/ipfs postgres: - image: postgres + image: postgres:14 ports: - - "5433:5432" - command: ["postgres", "-cshared_preload_libraries=pg_stat_statements"] + - "5432:5432" + command: ["postgres", "-cshared_preload_libraries=pg_stat_statements", "-cmax_connections=200"] environment: POSTGRES_USER: graph-node POSTGRES_PASSWORD: let-me-in POSTGRES_DB: graph-node + # FIXME: remove this env. var. which we shouldn't need. Introduced by + # , maybe as a + # workaround for https://github.com/docker/for-mac/issues/6270? + PGDATA: "/var/lib/postgresql/data" POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C" - networks: - - the-graph volumes: - - graph-postgres:/var/lib/postgresql/data - -networks: - the-graph: - internal: false - driver: bridge - -volumes: - graph-postgres: - graph-ipfs: + - ./data/postgres:/var/lib/postgresql/data diff --git a/apps/subgraph/networks.json b/apps/subgraph/networks.json index 2066652d3..5e359d952 100644 --- a/apps/subgraph/networks.json +++ b/apps/subgraph/networks.json @@ -1,43 +1,37 @@ { "sepolia": { "Semaphore": { - "address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131", - "startBlock": 3231111 - } - }, - "goerli": { - "Semaphore": { - "address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131", - "startBlock": 8777695 + "address": "0x5B8e7cC7bAC61A4b952d472b67056B2f260ba6dc", + "startBlock": 5150903 } }, "mumbai": { "Semaphore": { - "address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131", - "startBlock": 33995010 + "address": "", + "startBlock": 0 } }, - "optimism-goerli": { + "optimism-sepolia": { "Semaphore": { - "address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131", - "startBlock": 7632846 + "address": "", + "startBlock": 0 } }, - "arbitrum-goerli": { + "arbitrum-sepolia": { "Semaphore": { - "address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131", - "startBlock": 15174410 + "address": "", + "startBlock": 0 } }, "arbitrum-one": { "Semaphore": { - "address": "0xc60E0Ee1a2770d5F619858C641f14FC4a6401520", - "startBlock": 77278430 + "address": "", + "startBlock": 0 } }, "localhost": { "Semaphore": { - "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", + "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", "startBlock": 0 } } diff --git a/apps/subgraph/package.json b/apps/subgraph/package.json index 1cff22ed5..c35857955 100644 --- a/apps/subgraph/package.json +++ b/apps/subgraph/package.json @@ -5,18 +5,19 @@ "license": "MIT", "private": true, "scripts": { - "codegen": "node scripts/generateSubgraph.js ${0} && graph codegen", + "codegen": "node scripts/generate-subgraph.js ${0} && graph codegen", "build": "graph build", "auth": "graph auth --studio", "deploy": "graph deploy --node https://api.studio.thegraph.com/deploy/ ${0}", + "start-ipfs": "node scripts/start-ipfs.js", "create-local": "graph create --node http://localhost:8020/ semaphore", "remove-local": "graph remove --node http://localhost:8020/ semaphore", "deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 semaphore", "test": "graph test Semaphore -v 0.5.0" }, "dependencies": { - "@graphprotocol/graph-cli": "0.56.0", - "@graphprotocol/graph-ts": "^0.31.0" + "@graphprotocol/graph-cli": "0.67.0", + "@graphprotocol/graph-ts": "0.32.0" }, "devDependencies": { "@types/mustache": "^4.2.2", diff --git a/apps/subgraph/schema.graphql b/apps/subgraph/schema.graphql index f8d238b2b..67a3e8a64 100644 --- a/apps/subgraph/schema.graphql +++ b/apps/subgraph/schema.graphql @@ -1,35 +1,36 @@ type MerkleTree @entity { id: ID! - depth: BigInt! + depth: Int! root: BigInt - zeroValue: BigInt! - numberOfLeaves: Int! + size: Int! group: Group! } type Group @entity { id: ID! - merkleTree: MerkleTree! timestamp: BigInt! + merkleTree: MerkleTree! admin: Bytes members: [Member!] @derivedFrom(field: "group") - verifiedProofs: [VerifiedProof!] @derivedFrom(field: "group") + validatedProofs: [ValidatedProof!] @derivedFrom(field: "group") } type Member @entity { id: ID! - identityCommitment: BigInt! timestamp: BigInt! + identityCommitment: BigInt! index: Int! group: Group! } -type VerifiedProof @entity { +type ValidatedProof @entity { id: ID! - signal: BigInt! - merkleTreeRoot: BigInt! - nullifierHash: BigInt! - externalNullifier: BigInt! timestamp: BigInt! + message: BigInt! + scope: BigInt! + merkleTreeRoot: BigInt! + merkleTreeDepth: Int! + nullifier: BigInt! + points: [BigInt!]! group: Group! } diff --git a/apps/subgraph/scripts/generateSubgraph.js b/apps/subgraph/scripts/generate-subgraph.js similarity index 100% rename from apps/subgraph/scripts/generateSubgraph.js rename to apps/subgraph/scripts/generate-subgraph.js diff --git a/apps/subgraph/src/semaphore.ts b/apps/subgraph/src/semaphore.ts index 1bc564369..97880fa6e 100644 --- a/apps/subgraph/src/semaphore.ts +++ b/apps/subgraph/src/semaphore.ts @@ -1,13 +1,14 @@ -import { ByteArray, log } from "@graphprotocol/graph-ts" +import { BigInt, ByteArray, log } from "@graphprotocol/graph-ts" import { GroupAdminUpdated, GroupCreated, MemberAdded, MemberRemoved, MemberUpdated, - ProofVerified + MembersAdded, + ProofValidated } from "../generated/Semaphore/Semaphore" -import { Member, Group, VerifiedProof, MerkleTree } from "../generated/schema" +import { Group, Member, MerkleTree, ValidatedProof } from "../generated/schema" import { concat, hash } from "./utils" /** @@ -22,9 +23,8 @@ export function createGroup(event: GroupCreated): void { log.info("Creating group '{}'", [group.id]) - merkleTree.depth = event.params.merkleTreeDepth - merkleTree.zeroValue = event.params.zeroValue - merkleTree.numberOfLeaves = 0 + merkleTree.depth = 0 + merkleTree.size = 0 merkleTree.group = group.id group.timestamp = event.block.timestamp @@ -76,12 +76,12 @@ export function addMember(event: MemberAdded): void { member.group = merkleTree.group member.identityCommitment = event.params.identityCommitment member.timestamp = event.block.timestamp - member.index = merkleTree.numberOfLeaves + member.index = merkleTree.size member.save() merkleTree.root = event.params.merkleTreeRoot - merkleTree.numberOfLeaves += 1 + merkleTree.size += 1 merkleTree.save() @@ -138,7 +138,7 @@ export function removeMember(event: MemberRemoved): void { if (member) { log.info("Removing member '{}' from the onchain group '{}'", [member.id, merkleTree.group]) - member.identityCommitment = merkleTree.zeroValue + member.identityCommitment = BigInt.fromI32(0) member.save() @@ -152,39 +152,83 @@ export function removeMember(event: MemberRemoved): void { } /** - * Adds a verified proof in a group. - * @param event Ethereum event emitted when a proof has been verified. + * Adds N members to a group. + * @param event Ethereum event emitted when many members are added to a group. */ -export function addVerifiedProof(event: ProofVerified): void { - log.debug(`ProofVerified event block {}`, [event.block.number.toString()]) +export function addMembers(event: MembersAdded): void { + log.debug(`MembersAdded event block {}`, [event.block.number.toString()]) + + const merkleTree = MerkleTree.load(event.params.groupId.toString()) + + // eslint-disable-next-line prefer-destructuring + const identityCommitments = event.params.identityCommitments + // eslint-disable-next-line prefer-destructuring + const startIndex = event.params.startIndex + + if (merkleTree) { + for (let i = 0; i < identityCommitments.length; i += 1) { + const identityCommitment = identityCommitments[i] + + const memberId = hash( + concat(ByteArray.fromI32(startIndex.toI32() + i), ByteArray.fromBigInt(event.params.groupId)) + ) + const member = new Member(memberId) + + log.info("Adding member '{}' in the onchain group '{}'", [member.id, merkleTree.group]) + + member.group = merkleTree.group + member.identityCommitment = identityCommitment + member.timestamp = event.block.timestamp + member.index = startIndex.toI32() + i + + member.save() + + log.info("Member '{}' of the onchain group '{}' has been added", [member.id, merkleTree.id]) + } + + merkleTree.root = event.params.merkleTreeRoot + merkleTree.size += identityCommitments.length + + merkleTree.save() + } +} + +/** + * Adds a validated proof in a group. + * @param event Ethereum event emitted when a proof has been validated. + */ +export function addValidatedProof(event: ProofValidated): void { + log.debug(`ProofValidated event block {}`, [event.block.number.toString()]) const group = Group.load(event.params.groupId.toString()) if (group) { - const verifiedProofId = hash( - concat(ByteArray.fromBigInt(event.params.nullifierHash), ByteArray.fromBigInt(event.params.groupId)) + const validatedProofId = hash( + concat(ByteArray.fromBigInt(event.params.nullifier), ByteArray.fromBigInt(event.params.groupId)) ) - const verifiedProof = new VerifiedProof(verifiedProofId) + const validatedProof = new ValidatedProof(validatedProofId) - log.info("Adding verified proof with signal '{}' in the onchain group '{}'", [ - event.params.signal.toHexString(), + log.info("Adding validated proof with message '{}' in the onchain group '{}'", [ + event.params.message.toHexString(), group.id ]) - verifiedProof.group = group.id - verifiedProof.signal = event.params.signal - verifiedProof.merkleTreeRoot = event.params.merkleTreeRoot - verifiedProof.externalNullifier = event.params.externalNullifier - verifiedProof.nullifierHash = event.params.nullifierHash - verifiedProof.timestamp = event.block.timestamp + validatedProof.group = group.id + validatedProof.message = event.params.message + validatedProof.merkleTreeRoot = event.params.merkleTreeRoot + validatedProof.merkleTreeDepth = event.params.merkleTreeDepth.toI32() + validatedProof.scope = event.params.scope + validatedProof.nullifier = event.params.nullifier + validatedProof.points = event.params.points + validatedProof.timestamp = event.block.timestamp - verifiedProof.save() + validatedProof.save() group.save() - log.info("Verified proof with signal '{}' in the onchain group '{}' has been added", [ - event.params.signal.toHexString(), + log.info("Validated proof with message '{}' in the onchain group '{}' has been added", [ + event.params.message.toHexString(), group.id ]) } diff --git a/apps/subgraph/subgraph.template.yaml b/apps/subgraph/subgraph.template.yaml index 434a461a3..1301a7611 100644 --- a/apps/subgraph/subgraph.template.yaml +++ b/apps/subgraph/subgraph.template.yaml @@ -20,7 +20,7 @@ dataSources: - name: Semaphore file: ./abis/Semaphore.json eventHandlers: - - event: GroupCreated(indexed uint256,uint256,uint256) + - event: GroupCreated(indexed uint256) handler: createGroup - event: GroupAdminUpdated(indexed uint256,indexed address,indexed address) handler: updateGroupAdmin @@ -30,6 +30,8 @@ dataSources: handler: updateMember - event: MemberRemoved(indexed uint256,uint256,uint256,uint256) handler: removeMember - - event: ProofVerified(indexed uint256,indexed uint256,uint256,indexed uint256,uint256) - handler: addVerifiedProof + - event: MembersAdded(indexed uint256,uint256,uint256[],uint256) + handler: addMembers + - event: ProofValidated(indexed uint256,uint256,indexed uint256,uint256,uint256,indexed uint256,uint256[8]) + handler: addValidatedProof file: ./src/semaphore.ts diff --git a/apps/subgraph/tests/semaphore-utils.ts b/apps/subgraph/tests/semaphore-utils.ts index 38b963f1f..32229771c 100644 --- a/apps/subgraph/tests/semaphore-utils.ts +++ b/apps/subgraph/tests/semaphore-utils.ts @@ -1,5 +1,5 @@ +import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts" import { newMockEvent } from "matchstick-as" -import { ethereum, BigInt, Address } from "@graphprotocol/graph-ts" import { GroupAdminUpdated, GroupCreated, @@ -7,21 +7,16 @@ import { MemberAdded, MemberRemoved, MemberUpdated, - ProofVerified + MembersAdded, + ProofValidated } from "../generated/Semaphore/Semaphore" -export function createGroupCreatedEvent(groupId: BigInt, merkleTreeDepth: BigInt, zeroValue: BigInt): GroupCreated { +export function createGroupCreatedEvent(groupId: BigInt): GroupCreated { const groupCreatedEvent = changetype(newMockEvent()) groupCreatedEvent.parameters = [] groupCreatedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId))) - groupCreatedEvent.parameters.push( - new ethereum.EventParam("merkleTreeDepth", ethereum.Value.fromUnsignedBigInt(merkleTreeDepth)) - ) - groupCreatedEvent.parameters.push( - new ethereum.EventParam("zeroValue", ethereum.Value.fromUnsignedBigInt(zeroValue)) - ) return groupCreatedEvent } @@ -132,29 +127,58 @@ export function createMemberUpdatedEvent( return memberUpdatedEvent } +export function createMembersAddedEvent( + groupId: BigInt, + startIndex: BigInt, + identityCommitments: BigInt[], + merkleTreeRoot: BigInt +): MembersAdded { + const membersAddedEvent = changetype(newMockEvent()) + + membersAddedEvent.parameters = [] + + membersAddedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId))) + membersAddedEvent.parameters.push( + new ethereum.EventParam("startIndex", ethereum.Value.fromUnsignedBigInt(startIndex)) + ) + membersAddedEvent.parameters.push( + new ethereum.EventParam("identityCommitments", ethereum.Value.fromUnsignedBigIntArray(identityCommitments)) + ) + membersAddedEvent.parameters.push( + new ethereum.EventParam("merkleTreeRoot", ethereum.Value.fromUnsignedBigInt(merkleTreeRoot)) + ) + + return membersAddedEvent +} + export function createProofVerifiedEvent( groupId: BigInt, + merkleTreeDepth: BigInt, merkleTreeRoot: BigInt, - externalNullifier: BigInt, - nullifierHash: BigInt, - signal: BigInt -): ProofVerified { - const proofVerifiedEvent = changetype(newMockEvent()) + nullifier: BigInt, + message: BigInt, + scope: BigInt, + points: BigInt[] +): ProofValidated { + const proofValidatedEvent = changetype(newMockEvent()) - proofVerifiedEvent.parameters = [] + proofValidatedEvent.parameters = [] - proofVerifiedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId))) - proofVerifiedEvent.parameters.push( + proofValidatedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId))) + proofValidatedEvent.parameters.push( + new ethereum.EventParam("merkleTreeDepth", ethereum.Value.fromUnsignedBigInt(merkleTreeDepth)) + ) + proofValidatedEvent.parameters.push( new ethereum.EventParam("merkleTreeRoot", ethereum.Value.fromUnsignedBigInt(merkleTreeRoot)) ) - proofVerifiedEvent.parameters.push( - new ethereum.EventParam("nullifierHash", ethereum.Value.fromUnsignedBigInt(nullifierHash)) + proofValidatedEvent.parameters.push( + new ethereum.EventParam("nullifier", ethereum.Value.fromUnsignedBigInt(nullifier)) ) - proofVerifiedEvent.parameters.push( - new ethereum.EventParam("externalNullifier", ethereum.Value.fromUnsignedBigInt(externalNullifier)) + proofValidatedEvent.parameters.push(new ethereum.EventParam("message", ethereum.Value.fromUnsignedBigInt(message))) + proofValidatedEvent.parameters.push(new ethereum.EventParam("scope", ethereum.Value.fromUnsignedBigInt(scope))) + proofValidatedEvent.parameters.push( + new ethereum.EventParam("points", ethereum.Value.fromUnsignedBigIntArray(points)) ) - proofVerifiedEvent.parameters.push(new ethereum.EventParam("signal", ethereum.Value.fromUnsignedBigInt(signal))) - - return proofVerifiedEvent + return proofValidatedEvent } diff --git a/apps/subgraph/tests/semaphore.test.ts b/apps/subgraph/tests/semaphore.test.ts index 64e6d2ef4..f883453c7 100644 --- a/apps/subgraph/tests/semaphore.test.ts +++ b/apps/subgraph/tests/semaphore.test.ts @@ -1,8 +1,10 @@ +/* eslint-disable jest/expect-expect */ import { Address, BigInt, ByteArray } from "@graphprotocol/graph-ts" import { afterAll, assert, clearStore, describe, test } from "matchstick-as/assembly/index" import { addMember, - addVerifiedProof, + addMembers, + addValidatedProof, createGroup, removeMember, updateGroupAdmin, @@ -15,6 +17,7 @@ import { createMemberAddedEvent, createMemberRemovedEvent, createMemberUpdatedEvent, + createMembersAddedEvent, createProofVerifiedEvent } from "./semaphore-utils" @@ -27,12 +30,10 @@ describe("Semaphore subgraph", () => { describe("# createGroup", () => { test("Should have created a group", () => { const groupId = BigInt.fromI32(234) - const merkleTreeDepth = BigInt.fromI32(20) - const zeroValue = BigInt.fromI32(0) const oldAdmin = Address.fromString("0x0000000000000000000000000000000000000000") const newAdmin = Address.fromString("0x0000000000000000000000000000000000000001") - const event1 = createGroupCreatedEvent(groupId, merkleTreeDepth, zeroValue) + const event1 = createGroupCreatedEvent(groupId) const event2 = createGroupAdminUpdatedEvent(groupId, oldAdmin, newAdmin) createGroup(event1) @@ -44,9 +45,8 @@ describe("Semaphore subgraph", () => { assert.fieldEquals("Group", groupId.toString(), "admin", "0x0000000000000000000000000000000000000001") assert.fieldEquals("Group", groupId.toString(), "merkleTree", groupId.toString()) - assert.fieldEquals("MerkleTree", groupId.toString(), "depth", "20") - assert.fieldEquals("MerkleTree", groupId.toString(), "zeroValue", "0") - assert.fieldEquals("MerkleTree", groupId.toString(), "numberOfLeaves", "0") + assert.fieldEquals("MerkleTree", groupId.toString(), "depth", "0") + assert.fieldEquals("MerkleTree", groupId.toString(), "size", "0") assert.fieldEquals("MerkleTree", groupId.toString(), "group", groupId.toString()) }) }) @@ -84,7 +84,7 @@ describe("Semaphore subgraph", () => { assert.fieldEquals("Member", id, "group", groupId.toString()) assert.fieldEquals("MerkleTree", groupId.toString(), "root", "999") - assert.fieldEquals("MerkleTree", groupId.toString(), "numberOfLeaves", "1") + assert.fieldEquals("MerkleTree", groupId.toString(), "size", "1") }) }) @@ -131,26 +131,61 @@ describe("Semaphore subgraph", () => { }) }) + describe("# addMembers", () => { + test("Should have added many group members at once", () => { + const groupId = BigInt.fromI32(234) + const startIndex = BigInt.fromI32(1) + const identityCommitments = [BigInt.fromI32(123), BigInt.fromI32(124)] + const merkleTreeRoot = BigInt.fromI32(999) + const id = hash(concat(ByteArray.fromBigInt(startIndex), ByteArray.fromBigInt(groupId))) + + const event = createMembersAddedEvent(groupId, startIndex, identityCommitments, merkleTreeRoot) + + addMembers(event) + + assert.entityCount("Member", 3) + + assert.fieldEquals("Member", id, "index", "1") + assert.fieldEquals("Member", id, "identityCommitment", "123") + assert.fieldEquals("Member", id, "group", groupId.toString()) + + assert.fieldEquals("MerkleTree", groupId.toString(), "root", "999") + assert.fieldEquals("MerkleTree", groupId.toString(), "size", "3") + }) + }) + describe("# addVerifiedProof", () => { test("Should have added a proof", () => { const groupId = BigInt.fromI32(234) + const merkleTreeDepth = BigInt.fromI32(32) const merkleTreeRoot = BigInt.fromI32(1001) - const externalNullifier = BigInt.fromI32(1) - const nullifierHash = BigInt.fromI32(666) - const signal = BigInt.fromI32(2) - const id = hash(concat(ByteArray.fromBigInt(nullifierHash), ByteArray.fromBigInt(groupId))) + const nullifier = BigInt.fromI32(666) + const message = BigInt.fromI32(2) + const scope = BigInt.fromI32(1) + const points = [BigInt.fromI32(1), BigInt.fromI32(2)] + const id = hash(concat(ByteArray.fromBigInt(nullifier), ByteArray.fromBigInt(groupId))) - const event = createProofVerifiedEvent(groupId, merkleTreeRoot, externalNullifier, nullifierHash, signal) + const event = createProofVerifiedEvent( + groupId, + merkleTreeDepth, + merkleTreeRoot, + nullifier, + message, + scope, + points + ) - addVerifiedProof(event) + addValidatedProof(event) - assert.entityCount("VerifiedProof", 1) + assert.entityCount("ValidatedProof", 1) - assert.fieldEquals("VerifiedProof", id, "merkleTreeRoot", "1001") - assert.fieldEquals("VerifiedProof", id, "externalNullifier", "1") - assert.fieldEquals("VerifiedProof", id, "nullifierHash", "666") - assert.fieldEquals("VerifiedProof", id, "signal", "2") - assert.fieldEquals("VerifiedProof", id, "group", groupId.toString()) + assert.fieldEquals("ValidatedProof", id, "group", groupId.toString()) + assert.fieldEquals("ValidatedProof", id, "merkleTreeRoot", "1001") + assert.fieldEquals("ValidatedProof", id, "merkleTreeDepth", "32") + assert.fieldEquals("ValidatedProof", id, "scope", "1") + assert.fieldEquals("ValidatedProof", id, "nullifier", "666") + assert.fieldEquals("ValidatedProof", id, "message", "2") + assert.fieldEquals("ValidatedProof", id, "points", `[${points.join(", ")}]`) }) }) }) diff --git a/apps/website/src/components/Footer.tsx b/apps/website/src/components/Footer.tsx index 1063e94ca..11a119b3b 100644 --- a/apps/website/src/components/Footer.tsx +++ b/apps/website/src/components/Footer.tsx @@ -88,7 +88,7 @@ export default function Footer() { - Copyright © 2023 Ethereum Foundation + Copyright © 2024 Ethereum Foundation diff --git a/apps/website/src/components/Navbar.tsx b/apps/website/src/components/Navbar.tsx index e54f7edb5..a01ece0b8 100644 --- a/apps/website/src/components/Navbar.tsx +++ b/apps/website/src/components/Navbar.tsx @@ -103,7 +103,7 @@ export default function Navbar() { - Copyright © 2023 Ethereum Foundation + Copyright © 2024 Ethereum Foundation diff --git a/babel.config.json b/babel.config.json deleted file mode 100644 index 62475f2ef..000000000 --- a/babel.config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": [["@babel/preset-env", { "targets": { "node": "current" } }], "@babel/preset-typescript"] -} diff --git a/jest.config.ts b/jest.config.ts index f85f06e01..d4db960fe 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,23 +1,27 @@ import fs from "fs" -import type { Config } from "@jest/types" +import type { Config } from "jest" + +const exclude = ["circuits", "contracts"] const projects: any = fs .readdirSync("./packages", { withFileTypes: true }) .filter((directory) => directory.isDirectory()) + .filter((directory) => !exclude.includes(directory.name)) .map(({ name }) => ({ + preset: "ts-jest", rootDir: `packages/${name}`, displayName: name, setupFiles: ["dotenv/config"], moduleNameMapper: { - "@semaphore-protocol/(.*)": "/../$1/src/index.ts" // Interdependency packages. + "@semaphore-protocol/(.*)/(.*)": "/../$1/src/$2.ts", + "@semaphore-protocol/(.*)": "/../$1/src/index.ts" } })) -export default async (): Promise => ({ +const config: Config = { projects, verbose: true, coverageDirectory: "./coverage/libraries", - collectCoverageFrom: ["/src/**/*.ts", "!/src/**/index.ts", "!/src/**/*.d.ts"], coverageThreshold: { global: { branches: 90, @@ -26,4 +30,6 @@ export default async (): Promise => ({ statements: 95 } } -}) +} + +export default config diff --git a/package.json b/package.json index 7f5851bac..d098dfed4 100644 --- a/package.json +++ b/package.json @@ -7,25 +7,27 @@ "bugs": "https://github.com/semaphore-protocol/semaphore/issues", "private": true, "scripts": { - "build:libraries": "yarn workspaces foreach -t --no-private run build", - "build:subgraph": "yarn workspace semaphore-subgraph codegen goerli && yarn workspace semaphore-subgraph build", + "build:libraries": "yarn workspaces foreach -A -t --no-private run build", + "build:subgraph": "yarn workspace semaphore-subgraph codegen sepolia && yarn workspace semaphore-subgraph build", "compile:contracts": "yarn workspace semaphore-contracts compile", - "download:snark-artifacts": "rimraf snark-artifacts && ts-node scripts/download-snark-artifacts.ts", - "remove:template-files": "ts-node scripts/remove-template-files.ts", - "test": "yarn test:libraries && yarn test:contracts && yarn test:subgraph", + "test": "yarn test:libraries && yarn test:contracts && yarn test:circuits && yarn test:subgraph", "test:libraries": "jest --coverage", + "test:library": "jest packages/${0}", "test:subgraph": "yarn workspace semaphore-subgraph test", "test:contracts": "yarn workspace semaphore-contracts test:coverage", + "test:circuits": "yarn workspace @semaphore-protocol/circuits test", "lint": "eslint . --ext .js,.ts,.tsx && yarn workspace semaphore-contracts lint", "prettier": "prettier -c .", "prettier:write": "prettier -w .", "docs": "typedoc --cname js.semaphore.pse.dev --githubPages true", - "version:bump": "yarn workspaces foreach --no-private version -d ${0} && yarn version apply --all && git commit -am \"chore: v${0}\" && git tag v${0}", - "version:publish": "yarn build:libraries && yarn remove:template-files && yarn workspaces foreach --no-private npm publish --tolerate-republish --access public", + "version:bump": "yarn workspaces foreach -A --no-private version -d ${0} && yarn version apply --all && git commit -am \"chore: v${0}\" && git tag v${0}", + "version:publish": "yarn build:libraries && yarn clean:cli-templates && yarn workspaces foreach -A --no-private npm publish --tolerate-republish --access public", "version:release": "changelogithub", + "clean": "ts-node scripts/clean-apps.ts && ts-node scripts/clean-packages.ts && yarn clean:cli-templates && rimraf node_modules", + "clean:cli-templates": "ts-node scripts/clean-cli-templates.ts", "commit": "cz", "precommit": "lint-staged", - "postinstall": "yarn download:snark-artifacts && husky install" + "postinstall": "husky install" }, "keywords": [ "ethereum", @@ -45,24 +47,19 @@ "packages/*", "packages/contracts/contracts" ], - "packageManager": "yarn@3.2.1", + "packageManager": "yarn@4.1.0", "devDependencies": { - "@babel/core": "^7.16.7", - "@babel/preset-env": "^7.16.8", - "@babel/preset-typescript": "^7.17.12", "@commitlint/cli": "^16.0.2", "@commitlint/config-conventional": "^16.0.0", - "@rollup/plugin-typescript": "^8.3.0", + "@rollup/plugin-typescript": "^11.1.6", "@types/circomlibjs": "^0.1.4", "@types/download": "^8.0.1", "@types/glob": "^7.2.0", - "@types/jest": "^27.4.0", - "@types/node": "^17.0.9", + "@types/jest": "^29.5.12", + "@types/node": "^20", "@types/rimraf": "^3.0.2", - "@types/snarkjs": "^0.7.5", "@typescript-eslint/eslint-plugin": "^5.9.1", "@typescript-eslint/parser": "^5.9.1", - "babel-jest": "^27.4.6", "changelogithub": "0.12.7", "commitizen": "^4.2.4", "cz-conventional-changelog": "^3.3.0", @@ -72,21 +69,23 @@ "eslint-config-airbnb-typescript": "^16.1.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.2", - "eslint-plugin-jest": "^25.7.0", + "eslint-plugin-jest": "^27.8.0", "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "husky": "^8.0.3", - "jest": "^27.4.1", - "jest-config": "^27.4.7", + "jest": "^29.7.0", + "jest-config": "^29.7.0", "lint-staged": "^12.1.7", "prettier": "^2.5.1", - "rimraf": "^3.0.2", - "rollup": "^2.64.0", - "ts-node": "^10.4.0", - "tslib": "^2.3.1", - "typedoc": "^0.25.1", - "typescript": "^4.7.0" + "rimraf": "^5.0.5", + "rollup": "^4.9.6", + "snarkjs": "^0.7.2", + "ts-jest": "^29.1.2", + "ts-node": "^10.9.2", + "tslib": "^2.6.2", + "typedoc": "^0.25.7", + "typescript": "^5.3.3" }, "config": { "commitizen": { diff --git a/packages/circuits/.gitignore b/packages/circuits/.gitignore new file mode 100644 index 000000000..317889d51 --- /dev/null +++ b/packages/circuits/.gitignore @@ -0,0 +1,3 @@ +ptau +main +test diff --git a/packages/circuits/.mocharc.json b/packages/circuits/.mocharc.json new file mode 100644 index 000000000..0319b9322 --- /dev/null +++ b/packages/circuits/.mocharc.json @@ -0,0 +1,7 @@ +{ + "extension": ["ts"], + "require": "ts-node/register", + "spec": "./tests/*.test.ts", + "timeout": 100000, + "exit": true +} diff --git a/packages/circuits/LICENSE b/packages/circuits/LICENSE new file mode 100644 index 000000000..8ef16f7a5 --- /dev/null +++ b/packages/circuits/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Ethereum Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/circuits/README.md b/packages/circuits/README.md index b212fb956..86d20ac77 100644 --- a/packages/circuits/README.md +++ b/packages/circuits/README.md @@ -2,15 +2,21 @@

Semaphore circuits

-

Semaphore circuits to create and verify zero-knowledge proofs.

+

Semaphore circuits to generate and verify zero-knowledge proofs.

- - Github license + + NPM license + + + NPM version + + + Downloads

diff --git a/packages/circuits/circomkit.json b/packages/circuits/circomkit.json new file mode 100644 index 000000000..3e275c84d --- /dev/null +++ b/packages/circuits/circomkit.json @@ -0,0 +1,17 @@ +{ + "protocol": "groth16", + "prime": "bn128", + "version": "2.1.5", + "circuits": "./circuits.json", + "dirPtau": "./ptau", + "dirCircuits": "./", + "dirInputs": "./inputs", + "dirBuild": "./build", + "optimization": 2, + "inspect": true, + "include": ["../../node_modules/circomlib/circuits", "../../node_modules/@zk-kit/circuits/circom"], + "groth16numContributions": 1, + "groth16askForEntropy": false, + "logLevel": "INFO", + "verbose": true +} diff --git a/packages/circuits/circuits.json b/packages/circuits/circuits.json new file mode 100644 index 000000000..e4f3ab025 --- /dev/null +++ b/packages/circuits/circuits.json @@ -0,0 +1,8 @@ +{ + "semaphore": { + "file": "semaphore", + "template": "Semaphore", + "pubs": ["message", "scope"], + "params": [10] + } +} diff --git a/packages/circuits/package.json b/packages/circuits/package.json index e015df878..30be0321b 100644 --- a/packages/circuits/package.json +++ b/packages/circuits/package.json @@ -1,7 +1,38 @@ { - "name": "circuits", - "private": true, + "name": "@semaphore-protocol/circuits", + "version": "4.0.0-alpha.8", + "description": "Semaphore Circom circuits to generate zero-knowledge proofs.", + "license": "MIT", + "files": [ + "**/*.circom", + "!main", + "!test", + "LICENSE", + "README.md" + ], + "repository": "https://github.com/semaphore-protocol/semaphore", + "homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/data", + "bugs": { + "url": "https://github.com/semaphore-protocol/semaphore.git/issues" + }, + "scripts": { + "compile": "circomkit compile semaphore", + "setup": "circomkit setup semaphore", + "test": "mocha" + }, + "publishConfig": { + "access": "public" + }, "dependencies": { - "circomlib": "^2.0.2" + "@zk-kit/circuits": "0.2.4", + "circomlib": "2.0.5" + }, + "devDependencies": { + "@types/mocha": "^10.0.6", + "@zk-kit/eddsa-poseidon": "0.3.1", + "@zk-kit/imt": "^2.0.0-beta.1", + "circomkit": "^0.0.19", + "mocha": "^10.2.0", + "poseidon-lite": "^0.2.0" } } diff --git a/packages/circuits/scheme.png b/packages/circuits/scheme.png index 78c2c705a..841f3feac 100644 Binary files a/packages/circuits/scheme.png and b/packages/circuits/scheme.png differ diff --git a/packages/circuits/semaphore.circom b/packages/circuits/semaphore.circom index 3b6845709..30526ee01 100644 --- a/packages/circuits/semaphore.circom +++ b/packages/circuits/semaphore.circom @@ -1,90 +1,67 @@ -pragma circom 2.0.0; - -include "../node_modules/circomlib/circuits/poseidon.circom"; -include "./tree.circom"; - -template CalculateSecret() { - signal input identityNullifier; - signal input identityTrapdoor; - - signal output out; - - component poseidon = Poseidon(2); - - poseidon.inputs[0] <== identityNullifier; - poseidon.inputs[1] <== identityTrapdoor; - - out <== poseidon.out; -} - -template CalculateIdentityCommitment() { +pragma circom 2.1.5; + +include "babyjub.circom"; +include "poseidon.circom"; +include "binary-merkle-root.circom"; + +// The Semaphore circuit can be divided into 3 main parts. +// The first part involves the generation of the Semaphore identity, +// i.e. the public key and its hash, which is called the commitment +// and is used as a public value. +// In the second part, it is verified whether or not the identity commitment is part +// of the Merkle tree, i.e. the Semaphore group. That is, a proof of membership is verified. +// The third part covers the generation of a nullifier, i.e. the hash of the scope of the proof +// and the secret used to derive the public key (secret scalar). The nullifier is used to prevent the same +// proof from being verified twice. +// The circuit lastly includes the message, which is an arbitrary anonymous value defined by +// the user, or the hash of that value. +template Semaphore(MAX_DEPTH) { + // Input signals. + // The input signals are all private except 'message' and 'scope'. + // The secret is the scalar generated from the EdDSA private key. + // Using the secret scalar instead of the private key allows this circuit + // to skip steps 1, 2, 3 in the generation of the public key defined here: + // https://www.rfc-editor.org/rfc/rfc8032#section-5.1.5, making the circuit + // more efficient and simple. + // See the Semaphore identity package to know more about how the identity is generated: + // https://github.com/semaphore-protocol/semaphore/tree/main/packages/identity. signal input secret; - - signal output out; - - component poseidon = Poseidon(1); - - poseidon.inputs[0] <== secret; - - out <== poseidon.out; -} - -template CalculateNullifierHash() { - signal input externalNullifier; - signal input identityNullifier; - - signal output out; - - component poseidon = Poseidon(2); - - poseidon.inputs[0] <== externalNullifier; - poseidon.inputs[1] <== identityNullifier; - - out <== poseidon.out; -} - -// The current Semaphore smart contracts require nLevels <= 32 and nLevels >= 16. -template Semaphore(nLevels) { - signal input identityNullifier; - signal input identityTrapdoor; - signal input treePathIndices[nLevels]; - signal input treeSiblings[nLevels]; - - signal input signalHash; - signal input externalNullifier; - - signal output root; - signal output nullifierHash; - - component calculateSecret = CalculateSecret(); - calculateSecret.identityNullifier <== identityNullifier; - calculateSecret.identityTrapdoor <== identityTrapdoor; - - signal secret; - secret <== calculateSecret.out; - - component calculateIdentityCommitment = CalculateIdentityCommitment(); - calculateIdentityCommitment.secret <== secret; - - component calculateNullifierHash = CalculateNullifierHash(); - calculateNullifierHash.externalNullifier <== externalNullifier; - calculateNullifierHash.identityNullifier <== identityNullifier; - - component inclusionProof = MerkleTreeInclusionProof(nLevels); - inclusionProof.leaf <== calculateIdentityCommitment.out; - - for (var i = 0; i < nLevels; i++) { - inclusionProof.siblings[i] <== treeSiblings[i]; - inclusionProof.pathIndices[i] <== treePathIndices[i]; - } - - root <== inclusionProof.root; - - // Dummy square to prevent tampering signalHash. - signal signalHashSquared; - signalHashSquared <== signalHash * signalHash; - - nullifierHash <== calculateNullifierHash.out; + signal input merkleProofLength, merkleProofIndices[MAX_DEPTH], merkleProofSiblings[MAX_DEPTH]; + signal input message; + signal input scope; + + // Output signals. + // The output signals are all public. + signal output merkleRoot, nullifier; + + // Identity generation. + // The circuit derives the EdDSA public key from a secret using + // Baby Jubjub (https://eips.ethereum.org/EIPS/eip-2494), + // which is basically nothing more than a point with two coordinates. + // It then calculates the hash of the public key, which is used + // as the commitment, i.e. the public value of the Semaphore identity. + var Ax, Ay; + (Ax, Ay) = BabyPbk()(secret); + + var identityCommitment = Poseidon(2)([Ax, Ay]); + + // Proof of membership verification. + // The Merkle root passed as output must be equal to that calculated within + // the circuit through the inputs of the Merkle proof. + // See https://github.com/privacy-scaling-explorations/zk-kit/blob/main/packages/circuits/circom/binary-merkle-root.circom + // to know more about how the 'BinaryMerkleRoot' template works. + merkleRoot <== BinaryMerkleRoot(MAX_DEPTH)(identityCommitment, merkleProofLength, merkleProofIndices, merkleProofSiblings); + + // Nullifier generation. + // The nullifier is a value that essentially identifies the proof generated in a specific scope + // and by a specific identity, so that externally anyone can check if another proof with the same + // nullifier has already been generated. This mechanism can be particularly useful in cases + // where one wants to prevent double-spending or double-voting, for example. + nullifier <== Poseidon(2)([scope, secret]); + + // The message is not really used within the circuit. + // The square applied to it is a way to force Circom's compiler to add a constraint and + // prevent its value from being changed by an attacker. + // More information here: https://geometryresearch.xyz/notebook/groth16-malleability. + signal dummySquare <== message * message; } - -component main {public [signalHash, externalNullifier]} = Semaphore(20); diff --git a/packages/circuits/tests/common.ts b/packages/circuits/tests/common.ts new file mode 100644 index 000000000..20f153fce --- /dev/null +++ b/packages/circuits/tests/common.ts @@ -0,0 +1,12 @@ +import { Circomkit } from "circomkit" +import { readFileSync } from "fs" +import path from "path" + +const configFilePath = path.join(__dirname, "../circomkit.json") +const config = JSON.parse(readFileSync(configFilePath, "utf-8")) + +// eslint-disable-next-line import/prefer-default-export +export const circomkit = new Circomkit({ + ...config, + verbose: false +}) diff --git a/packages/circuits/tests/semaphore.test.ts b/packages/circuits/tests/semaphore.test.ts new file mode 100644 index 000000000..06f008474 --- /dev/null +++ b/packages/circuits/tests/semaphore.test.ts @@ -0,0 +1,72 @@ +import { derivePublicKey, deriveSecretScalar } from "@zk-kit/eddsa-poseidon" +import { LeanIMT } from "@zk-kit/imt" +import { WitnessTester } from "circomkit" +import { poseidon2 } from "poseidon-lite" +import { circomkit } from "./common" + +describe("semaphore", () => { + let circuit: WitnessTester< + ["secret", "merkleProofLength", "merkleProofIndices", "merkleProofSiblings", "scope", "message"], + ["nullifier", "merkleRoot"] + > + + const MAX_DEPTH = 20 + + const scope = 32 + const message = 43 + + const secret = 1 + const publicKey = derivePublicKey(secret) + + const leaf = poseidon2(publicKey) + + const tree = new LeanIMT((a, b) => poseidon2([a, b])) + + tree.insert(leaf) + + for (let i = 1; i < 4; i += 1) { + tree.insert(BigInt(i)) + } + + const { siblings: merkleProofSiblings, index } = tree.generateProof(0) + + // The index must be converted to a list of indices, 1 for each tree level. + // The circuit tree depth is 20, so the number of siblings must be 20, even if + // the tree depth is actually 3. The missing siblings can be set to 0, as they + // won't be used to calculate the root in the circuit. + const merkleProofIndices: number[] = [] + + for (let i = 0; i < MAX_DEPTH; i += 1) { + merkleProofIndices.push((index >> i) & 1) + + if (merkleProofSiblings[i] === undefined) { + merkleProofSiblings[i] = BigInt(0) + } + } + + const INPUT = { + secret: deriveSecretScalar(secret), + merkleProofLength: tree.depth, + merkleProofIndices, + merkleProofSiblings, + scope, + message + } + + const OUTPUT = { + nullifier: poseidon2([scope, deriveSecretScalar(secret)]), + merkleRoot: tree.root + } + + before(async () => { + circuit = await circomkit.WitnessTester("semaphore", { + file: "semaphore", + template: "Semaphore", + params: [MAX_DEPTH] + }) + }) + + it("Should calculate the root and the nullifier correctly", async () => { + await circuit.expectPass(INPUT, OUTPUT) + }) +}) diff --git a/packages/circuits/tree.circom b/packages/circuits/tree.circom deleted file mode 100644 index 6f5951dbe..000000000 --- a/packages/circuits/tree.circom +++ /dev/null @@ -1,40 +0,0 @@ -pragma circom 2.0.0; - -include "../node_modules/circomlib/circuits/poseidon.circom"; -include "../node_modules/circomlib/circuits/mux1.circom"; - -template MerkleTreeInclusionProof(nLevels) { - signal input leaf; - signal input pathIndices[nLevels]; - signal input siblings[nLevels]; - - signal output root; - - component poseidons[nLevels]; - component mux[nLevels]; - - signal hashes[nLevels + 1]; - hashes[0] <== leaf; - - for (var i = 0; i < nLevels; i++) { - pathIndices[i] * (1 - pathIndices[i]) === 0; - - poseidons[i] = Poseidon(2); - mux[i] = MultiMux1(2); - - mux[i].c[0][0] <== hashes[i]; - mux[i].c[0][1] <== siblings[i]; - - mux[i].c[1][0] <== siblings[i]; - mux[i].c[1][1] <== hashes[i]; - - mux[i].s <== pathIndices[i]; - - poseidons[i].inputs[0] <== mux[i].out[0]; - poseidons[i].inputs[1] <== mux[i].out[1]; - - hashes[i + 1] <== poseidons[i].out; - } - - root <== hashes[nLevels]; -} diff --git a/packages/cli-template-contracts-hardhat/.editorconfig b/packages/cli-template-contracts-hardhat/.editorconfig new file mode 100644 index 000000000..c94bae0f3 --- /dev/null +++ b/packages/cli-template-contracts-hardhat/.editorconfig @@ -0,0 +1,13 @@ +#root = true + +[*] +indent_style = space +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 120 +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false diff --git a/packages/cli-template-contracts-hardhat/.env.example b/packages/cli-template-contracts-hardhat/.env.example index df8d3b757..43e4291c9 100644 --- a/packages/cli-template-contracts-hardhat/.env.example +++ b/packages/cli-template-contracts-hardhat/.env.example @@ -1,4 +1,5 @@ INFURA_API_KEY= ETHEREUM_PRIVATE_KEY= REPORT_GAS=false +ETHERSCAN_API_KEY= COINMARKETCAP_API_KEY= diff --git a/packages/cli-template-contracts-hardhat/.eslintignore b/packages/cli-template-contracts-hardhat/.eslintignore new file mode 100644 index 000000000..c8c27e9cf --- /dev/null +++ b/packages/cli-template-contracts-hardhat/.eslintignore @@ -0,0 +1,23 @@ +# dependencies +node_modules +package-lock.json +yarn.lock +.yarn + +# testing +coverage +coverage.json + +# hardhat +artifacts +cache +typechain-types + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/packages/cli-template-monorepo-ethers/.eslintrc.json b/packages/cli-template-contracts-hardhat/.eslintrc.json similarity index 55% rename from packages/cli-template-monorepo-ethers/.eslintrc.json rename to packages/cli-template-contracts-hardhat/.eslintrc.json index 43bdfc312..29d2ebadb 100644 --- a/packages/cli-template-monorepo-ethers/.eslintrc.json +++ b/packages/cli-template-contracts-hardhat/.eslintrc.json @@ -8,29 +8,17 @@ "parserOptions": { "ecmaVersion": 6, "sourceType": "module", - "project": ["./**/tsconfig.json"] + "project": ["./tsconfig.json"] }, "plugins": ["@typescript-eslint"], "rules": { "no-underscore-dangle": "off", - "no-alert": "off", - "no-nested-ternary": "off", "import/no-extraneous-dependencies": "off", - "import/extensions": "off", - "import/no-relative-packages": "off", - "no-await-in-loop": "off", "no-bitwise": "off", + "no-await-in-loop": "off", "no-restricted-syntax": "off", - "no-console": ["warn", { "allow": ["info", "warn", "error"] }], + "no-console": ["warn", { "allow": ["info", "warn", "error", "log"] }], "@typescript-eslint/lines-between-class-members": "off", - "no-param-reassign": "off", - "@typescript-eslint/naming-convention": [ - "error", - { - "selector": "variable", - "format": ["camelCase", "PascalCase", "UPPER_CASE", "snake_case"], - "leadingUnderscore": "allow" - } - ] + "no-param-reassign": "off" } } diff --git a/packages/cli-template-contracts-hardhat/.gitignore b/packages/cli-template-contracts-hardhat/.gitignore index 85d361b91..15809cf04 100644 --- a/packages/cli-template-contracts-hardhat/.gitignore +++ b/packages/cli-template-contracts-hardhat/.gitignore @@ -1,10 +1,71 @@ node_modules .env -coverage -coverage.json -typechain -typechain-types # Hardhat files -cache -artifacts \ No newline at end of file +/cache +/artifacts + +# TypeChain files +/typechain +/typechain-types + +# solidity-coverage files +/coverage +/coverage.json + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# IDE +.vscode +.idea + +# Dependency directories +node_modules/ + +# Output of 'npm pack' +*.tgz + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Yarn Integrity file +.yarn-integrity + +# vercel +.vercel + +# dotenv environment variable files +.env + +# Optional npm cache directory +.npm +.DS_Store + +# yarn v3 +.pnp.* +.pnp.js +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions diff --git a/packages/cli-template-contracts-hardhat/.prettierignore b/packages/cli-template-contracts-hardhat/.prettierignore new file mode 100644 index 000000000..a80eb0656 --- /dev/null +++ b/packages/cli-template-contracts-hardhat/.prettierignore @@ -0,0 +1,29 @@ +# dependencies +node_modules +package-lock.json +yarn.lock +.yarn + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# testing +coverage +coverage.json + +# hardhat +artifacts +cache +typechain-types + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + diff --git a/packages/cli-template-contracts-hardhat/.prettierrc.json b/packages/cli-template-contracts-hardhat/.prettierrc.json new file mode 100644 index 000000000..27140f2b4 --- /dev/null +++ b/packages/cli-template-contracts-hardhat/.prettierrc.json @@ -0,0 +1,5 @@ +{ + "semi": false, + "arrowParens": "always", + "trailingComma": "none" +} diff --git a/packages/cli-template-contracts-hardhat/.solhint.json b/packages/cli-template-contracts-hardhat/.solhint.json new file mode 100644 index 000000000..5de4e27c1 --- /dev/null +++ b/packages/cli-template-contracts-hardhat/.solhint.json @@ -0,0 +1,3 @@ +{ + "extends": "solhint:default" +} diff --git a/packages/cli-template-contracts-hardhat/.yarn/releases/yarn-4.1.0.cjs.REMOVED.git-id b/packages/cli-template-contracts-hardhat/.yarn/releases/yarn-4.1.0.cjs.REMOVED.git-id new file mode 100644 index 000000000..430c13f99 --- /dev/null +++ b/packages/cli-template-contracts-hardhat/.yarn/releases/yarn-4.1.0.cjs.REMOVED.git-id @@ -0,0 +1 @@ +738adce5914a0e193f2e1255e4dcf7042256a1c1 \ No newline at end of file diff --git a/packages/cli-template-contracts-hardhat/.yarnrc.yml b/packages/cli-template-contracts-hardhat/.yarnrc.yml new file mode 100644 index 000000000..bd95ab27f --- /dev/null +++ b/packages/cli-template-contracts-hardhat/.yarnrc.yml @@ -0,0 +1,7 @@ +compressionLevel: mixed + +enableGlobalCache: false + +nodeLinker: node-modules + +yarnPath: .yarn/releases/yarn-4.1.0.cjs diff --git a/packages/cli-template-contracts-hardhat/README.md b/packages/cli-template-contracts-hardhat/README.md index a2e1a66f1..a0f4aec8a 100644 --- a/packages/cli-template-contracts-hardhat/README.md +++ b/packages/cli-template-contracts-hardhat/README.md @@ -2,15 +2,23 @@ This project demonstrates a basic Semaphore use case. It comes with a sample contract, a test for that contract and a sample task that deploys that contract. +## Install + +### Install dependencies + +```bash +yarn +``` + ## Usage -### Compile +### Compile contracts ```bash yarn compile ``` -### Testing +### Test contracts ```bash yarn test @@ -28,7 +36,7 @@ Or a test gas report: yarn test:report-gas ``` -### Deploy +### Deploy contracts 1. Copy the `.env.example` file as `.env`. @@ -44,7 +52,7 @@ cp .env.example .env 3. And deploy your contract. ```bash -yarn deploy --semaphore --group --network goerli +yarn deploy --semaphore --group --network sepolia ``` > **Note** @@ -52,3 +60,23 @@ yarn deploy --semaphore --group --network goerli > **Warning** > The group id is a number! + +### Code quality and formatting + +Run [ESLint](https://eslint.org/) and [solhint](https://github.com/protofire/solhint) to analyze the code and catch bugs: + +```bash +yarn lint +``` + +Run [Prettier](https://prettier.io/) to check formatting rules: + +```bash +yarn prettier +``` + +Or to automatically format the code: + +```bash +yarn prettier:write +``` diff --git a/packages/cli-template-contracts-hardhat/contracts/Feedback.sol b/packages/cli-template-contracts-hardhat/contracts/Feedback.sol index 0c846db5a..0a418c4fe 100644 --- a/packages/cli-template-contracts-hardhat/contracts/Feedback.sol +++ b/packages/cli-template-contracts-hardhat/contracts/Feedback.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.23; import "@semaphore-protocol/contracts/interfaces/ISemaphore.sol"; @@ -12,7 +12,7 @@ contract Feedback { semaphore = ISemaphore(semaphoreAddress); groupId = _groupId; - semaphore.createGroup(groupId, 20, address(this)); + semaphore.createGroup(groupId, address(this)); } function joinGroup(uint256 identityCommitment) external { @@ -20,11 +20,21 @@ contract Feedback { } function sendFeedback( - uint256 feedback, + uint256 merkleTreeDepth, uint256 merkleTreeRoot, - uint256 nullifierHash, - uint256[8] calldata proof + uint256 nullifier, + uint256 feedback, + uint256[8] calldata points ) external { - semaphore.verifyProof(groupId, merkleTreeRoot, feedback, nullifierHash, groupId, proof); + ISemaphore.SemaphoreProof memory proof = ISemaphore.SemaphoreProof( + merkleTreeDepth, + merkleTreeRoot, + nullifier, + feedback, + groupId, + points + ); + + semaphore.validateProof(groupId, proof); } } diff --git a/packages/cli-template-contracts-hardhat/hardhat.config.ts b/packages/cli-template-contracts-hardhat/hardhat.config.ts index 984204e34..c429c5358 100644 --- a/packages/cli-template-contracts-hardhat/hardhat.config.ts +++ b/packages/cli-template-contracts-hardhat/hardhat.config.ts @@ -1,17 +1,10 @@ -import "@nomiclabs/hardhat-ethers" -import "@nomicfoundation/hardhat-chai-matchers" -import "@semaphore-protocol/hardhat" -import "@typechain/hardhat" -import { config as dotenvConfig } from "dotenv" -import "hardhat-gas-reporter" import { HardhatUserConfig } from "hardhat/config" import { NetworksUserConfig } from "hardhat/types" -import "solidity-coverage" -import { config } from "./package.json" +import "@nomicfoundation/hardhat-toolbox" +import "dotenv/config" +import "@semaphore-protocol/hardhat" import "./tasks/deploy" -dotenvConfig() - function getNetworks(): NetworksUserConfig { if (!process.env.INFURA_API_KEY || !process.env.ETHEREUM_PRIVATE_KEY) { return {} @@ -21,11 +14,6 @@ function getNetworks(): NetworksUserConfig { const infuraApiKey = process.env.INFURA_API_KEY return { - goerli: { - url: `https://goerli.infura.io/v3/${infuraApiKey}`, - chainId: 5, - accounts - }, sepolia: { url: `https://sepolia.infura.io/v3/${infuraApiKey}`, chainId: 11155111, @@ -36,27 +24,26 @@ function getNetworks(): NetworksUserConfig { chainId: 80001, accounts }, - "optimism-goerli": { - url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`, - chainId: 420, + "optimism-sepolia": { + url: `https://optimism-sepolia.infura.io/v3/${infuraApiKey}`, + chainId: 11155420, + accounts + }, + "arbitrum-sepolia": { + url: "https://sepolia-rollup.arbitrum.io/rpc", + chainId: 421614, accounts }, arbitrum: { - url: `https://arbitrum-mainnet.infura.io/v3/${infuraApiKey}`, + url: "https://arb1.arbitrum.io/rpc", chainId: 42161, accounts } } } -const hardhatConfig: HardhatUserConfig = { - solidity: config.solidity, - paths: { - sources: config.paths.contracts, - tests: config.paths.tests, - cache: config.paths.cache, - artifacts: config.paths.build.contracts - }, +const config: HardhatUserConfig = { + solidity: "0.8.23", networks: { hardhat: { chainId: 1337 @@ -69,9 +56,14 @@ const hardhatConfig: HardhatUserConfig = { coinmarketcap: process.env.COINMARKETCAP_API_KEY }, typechain: { - outDir: config.paths.build.typechain, - target: "ethers-v5" + target: "ethers-v6" + }, + etherscan: { + apiKey: process.env.ETHERSCAN_API_KEY + }, + sourcify: { + enabled: true } } -export default hardhatConfig +export default config diff --git a/packages/cli-template-contracts-hardhat/package.json b/packages/cli-template-contracts-hardhat/package.json index d7093fb79..d00672738 100644 --- a/packages/cli-template-contracts-hardhat/package.json +++ b/packages/cli-template-contracts-hardhat/package.json @@ -1,78 +1,78 @@ { "name": "@semaphore-protocol/cli-template-contracts-hardhat", - "version": "3.15.2", + "version": "4.0.0-alpha.8", "description": "Semaphore Hardhat template.", "license": "Unlicense", "files": [ "files.tgz", "contracts/", - "scripts/", "tasks/", "test/", + ".editorconfig", ".env.example", + ".eslintignore", + ".eslintrc.json", + ".prettierignore", + ".prettierrc.json", "hardhat.config.ts", "tsconfig.json", - "README.md" + "README.md", + ".yarn/", + ".yarnrc.yml" ], "publishConfig": { "access": "public" }, "scripts": { - "dev": "hardhat node & yarn compile && yarn deploy --network localhost", + "dev": "hardhat node & yarn deploy --network localhost", "compile": "hardhat compile", - "download:snark-artifacts": "hardhat run scripts/download-snark-artifacts.ts", - "deploy": "yarn compile && hardhat deploy", - "test": "hardhat run scripts/download-snark-artifacts.ts && hardhat test", + "deploy": "hardhat compile && hardhat deploy", + "verify": "hardhat verify", + "test": "hardhat test", "test:report-gas": "REPORT_GAS=true hardhat test", "test:coverage": "hardhat coverage", "typechain": "hardhat typechain", - "prepublish": "tar -czf files.tgz .gitignore" + "prepublish": "tar -czf files.tgz .gitignore", + "lint": "eslint . --ext .js,.ts && solhint 'contracts/**/*.sol'", + "prettier": "prettier -c --plugin=prettier-plugin-solidity .", + "prettier:write": "prettier -w --plugin=prettier-plugin-solidity ." }, "devDependencies": { - "@ethersproject/abi": "^5.4.7", - "@ethersproject/providers": "^5.4.7", - "@nomicfoundation/hardhat-chai-matchers": "^1.0.0", + "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", + "@nomicfoundation/hardhat-ethers": "^3.0.0", "@nomicfoundation/hardhat-network-helpers": "^1.0.0", - "@nomicfoundation/hardhat-toolbox": "^2.0.0", - "@nomiclabs/hardhat-ethers": "^2.0.0", - "@nomiclabs/hardhat-etherscan": "^3.0.0", - "@semaphore-protocol/group": "3.15.2", - "@semaphore-protocol/hardhat": "3.15.2", - "@semaphore-protocol/identity": "3.15.2", - "@semaphore-protocol/proof": "3.15.2", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", + "@nomicfoundation/hardhat-toolbox": "^4.0.0", + "@nomicfoundation/hardhat-verify": "^2.0.0", + "@semaphore-protocol/core": "4.0.0-alpha.8", + "@semaphore-protocol/hardhat": "4.0.0-alpha.8", + "@typechain/ethers-v6": "^0.5.0", + "@typechain/hardhat": "^9.0.0", "@types/chai": "^4.2.0", - "@types/download": "^8.0.1", - "@types/mocha": "^9.1.0", - "@types/node": ">=12.0.0", + "@types/eslint": "^8", + "@types/mocha": ">=9.1.0", + "@types/node": "^20.11.20", + "@typescript-eslint/eslint-plugin": "^7.0.2", + "@typescript-eslint/parser": "^7.0.2", "chai": "^4.2.0", - "dotenv": "^16.0.3", - "download": "^8.0.0", - "ethers": "^5.4.7", - "hardhat": "^2.11.0", + "dotenv": "^16.4.5", + "eslint": "^8.57.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.1.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.29.1", + "ethers": "^6.4.0", + "hardhat": "^2.20.1", "hardhat-gas-reporter": "^1.0.8", + "prettier": "^3.2.5", + "prettier-plugin-solidity": "^1.3.1", + "solhint": "^4.1.1", "solidity-coverage": "^0.8.1", - "ts-node": ">=8.0.0", - "typechain": "^8.1.0", - "typescript": ">=4.5.0" + "ts-node": "^10.9.2", + "typechain": "^8.3.0", + "typescript": "^5.3.3" }, "dependencies": { - "@semaphore-protocol/contracts": "3.15.2" + "@semaphore-protocol/contracts": "4.0.0-alpha.8" }, - "config": { - "solidity": { - "version": "0.8.4" - }, - "paths": { - "contracts": "./contracts", - "tests": "./test", - "cache": "./cache", - "build": { - "snark-artifacts": "./build/snark-artifacts", - "contracts": "./build/contracts", - "typechain": "./build/typechain" - } - } - } + "packageManager": "yarn@4.1.0" } diff --git a/packages/cli-template-contracts-hardhat/scripts/download-snark-artifacts.ts b/packages/cli-template-contracts-hardhat/scripts/download-snark-artifacts.ts deleted file mode 100644 index 6051397e5..000000000 --- a/packages/cli-template-contracts-hardhat/scripts/download-snark-artifacts.ts +++ /dev/null @@ -1,24 +0,0 @@ -import download from "download" -import fs from "fs" -import { config } from "../package.json" - -async function main() { - const snarkArtifactsPath = config.paths.build["snark-artifacts"] - const url = `http://www.trusted-setup-pse.org/semaphore/${20}` - - if (!fs.existsSync(snarkArtifactsPath)) { - fs.mkdirSync(snarkArtifactsPath, { recursive: true }) - } - - if (!fs.existsSync(`${snarkArtifactsPath}/semaphore.zkey`)) { - await download(`${url}/semaphore.wasm`, snarkArtifactsPath) - await download(`${url}/semaphore.zkey`, snarkArtifactsPath) - } -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error) - process.exit(1) - }) diff --git a/packages/cli-template-contracts-hardhat/tasks/deploy.ts b/packages/cli-template-contracts-hardhat/tasks/deploy.ts index 85b43377e..0dd175e40 100644 --- a/packages/cli-template-contracts-hardhat/tasks/deploy.ts +++ b/packages/cli-template-contracts-hardhat/tasks/deploy.ts @@ -10,7 +10,7 @@ task("deploy", "Deploy a Feedback contract") logs }) - semaphoreAddress = semaphore.address + semaphoreAddress = await semaphore.getAddress() } if (!groupId) { @@ -21,10 +21,8 @@ task("deploy", "Deploy a Feedback contract") const feedbackContract = await FeedbackFactory.deploy(semaphoreAddress, groupId) - await feedbackContract.deployed() - if (logs) { - console.info(`Feedback contract has been deployed to: ${feedbackContract.address}`) + console.info(`Feedback contract has been deployed to: ${await feedbackContract.getAddress()}`) } return feedbackContract diff --git a/packages/cli-template-contracts-hardhat/test/Feedback.ts b/packages/cli-template-contracts-hardhat/test/Feedback.ts index 08227e215..7733e3b86 100644 --- a/packages/cli-template-contracts-hardhat/test/Feedback.ts +++ b/packages/cli-template-contracts-hardhat/test/Feedback.ts @@ -1,38 +1,41 @@ -import { Group } from "@semaphore-protocol/group" -import { Identity } from "@semaphore-protocol/identity" -import { generateProof } from "@semaphore-protocol/proof" +import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers" +import { Group, Identity, generateProof } from "@semaphore-protocol/core" import { expect } from "chai" -import { formatBytes32String } from "ethers/lib/utils" +import { encodeBytes32String } from "ethers" import { run } from "hardhat" // @ts-ignore: typechain folder will be generated after contracts compilation -import { Feedback } from "../build/typechain" -import { config } from "../package.json" +// eslint-disable-next-line +import { Feedback, ISemaphore } from "../typechain-types" describe("Feedback", () => { - let feedbackContract: Feedback - let semaphoreContract: string + async function deployFeedbackFixture() { + const groupId = "42" - const groupId = "42" - const group = new Group(groupId) - const users: Identity[] = [] - - before(async () => { const { semaphore } = await run("deploy:semaphore", { logs: false }) - feedbackContract = await run("deploy", { logs: false, group: groupId, semaphore: semaphore.address }) - semaphoreContract = semaphore + const semaphoreContract: ISemaphore = semaphore - users.push(new Identity()) - users.push(new Identity()) - }) + const feedbackContract: Feedback = await run("deploy", { + logs: false, + group: groupId, + semaphore: await semaphoreContract.getAddress() + }) + + return { semaphoreContract, feedbackContract, groupId } + } describe("# joinGroup", () => { it("Should allow users to join the group", async () => { - for await (const [i, user] of users.entries()) { - const transaction = feedbackContract.joinGroup(user.commitment) + const { semaphoreContract, feedbackContract, groupId } = await loadFixture(deployFeedbackFixture) + + const users = [new Identity(), new Identity()] + + const group = new Group() + for (const [i, user] of users.entries()) { + const transaction = await feedbackContract.joinGroup(user.commitment) group.addMember(user.commitment) await expect(transaction) @@ -43,27 +46,40 @@ describe("Feedback", () => { }) describe("# sendFeedback", () => { - const wasmFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.wasm` - const zkeyFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.zkey` - it("Should allow users to send feedback anonymously", async () => { - const feedback = formatBytes32String("Hello World") + const { semaphoreContract, feedbackContract, groupId } = await loadFixture(deployFeedbackFixture) + + const users = [new Identity(), new Identity()] + const group = new Group() + + for (const user of users) { + await feedbackContract.joinGroup(user.commitment) + group.addMember(user.commitment) + } + + const feedback = encodeBytes32String("Hello World") - const fullProof = await generateProof(users[1], group, groupId, feedback, { - wasmFilePath, - zkeyFilePath - }) + const proof = await generateProof(users[1], group, feedback, groupId) const transaction = feedbackContract.sendFeedback( + proof.merkleTreeDepth, + proof.merkleTreeRoot, + proof.nullifier, feedback, - fullProof.merkleTreeRoot, - fullProof.nullifierHash, - fullProof.proof + proof.points ) await expect(transaction) - .to.emit(semaphoreContract, "ProofVerified") - .withArgs(groupId, fullProof.merkleTreeRoot, fullProof.nullifierHash, groupId, fullProof.signal) + .to.emit(semaphoreContract, "ProofValidated") + .withArgs( + groupId, + proof.merkleTreeDepth, + proof.merkleTreeRoot, + proof.nullifier, + proof.message, + groupId, + proof.points + ) }) }) }) diff --git a/packages/cli-template-contracts-hardhat/tsconfig.json b/packages/cli-template-contracts-hardhat/tsconfig.json index 5a3f0f784..a702753ac 100644 --- a/packages/cli-template-contracts-hardhat/tsconfig.json +++ b/packages/cli-template-contracts-hardhat/tsconfig.json @@ -1,15 +1,13 @@ { "compilerOptions": { - "moduleResolution": "Node", - "noImplicitAny": true, - "resolveJsonModule": true, - "target": "ES2018", - "module": "CommonJS", - "strict": true, + "target": "es2020", + "module": "commonjs", "esModuleInterop": true, - "outDir": "dist", - "typeRoots": ["node_modules/@types", "types"] + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true }, - "include": ["scripts/**/*", "tasks/**/*", "test/**/*", "build/typechain/**/*", "types/**/*"], + "include": ["scripts/**/*", "tasks/**/*", "test/**/*", "typechain-types/**/*"], "files": ["./hardhat.config.ts"] } diff --git a/packages/cli-template-monorepo-ethers/.env.example b/packages/cli-template-monorepo-ethers/.env.example index 050dd4d74..57a673d86 100644 --- a/packages/cli-template-monorepo-ethers/.env.example +++ b/packages/cli-template-monorepo-ethers/.env.example @@ -1,10 +1,6 @@ DEFAULT_NETWORK=localhost INFURA_API_KEY= ETHEREUM_PRIVATE_KEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 -FEEDBACK_CONTRACT_ADDRESS=0x5fc8d32690cc91d4c39d9d3abcbd16989f875707 -SEMAPHORE_CONTRACT_ADDRESS=0xdc64a140aa3e981100a9beca4e685f962f0cf6c9 -OPENZEPPELIN_AUTOTASK_WEBHOOK= -GROUP_ID=42 REPORT_GAS=false COINMARKETCAP_API_KEY= ETHERSCAN_API_KEY= diff --git a/packages/cli-template-monorepo-ethers/.eslintignore b/packages/cli-template-monorepo-ethers/.eslintignore deleted file mode 100644 index 902d0695f..000000000 --- a/packages/cli-template-monorepo-ethers/.eslintignore +++ /dev/null @@ -1,45 +0,0 @@ -# dependencies -node_modules -package-lock.json -yarn.lock -.yarn - -# testing -coverage -coverage.json - -# docs -docs - -# types -types - -# production -dist -build -cache -contract-artifacts - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Next.js -.next/ -out/ - -# The Graph -generated - -# Auto Generated PWA files -**/public/sw.js -**/public/workbox-*.js -**/public/worker-*.js -**/public/sw.js.map -**/public/workbox-*.js.map -**/public/worker-*.js.map diff --git a/packages/cli-template-monorepo-ethers/.gitignore b/packages/cli-template-monorepo-ethers/.gitignore index bad39850b..4aa5d3d55 100644 --- a/packages/cli-template-monorepo-ethers/.gitignore +++ b/packages/cli-template-monorepo-ethers/.gitignore @@ -20,26 +20,12 @@ pids .vscode .idea -# Cargo -target - -# Testing -coverage -coverage.json -*.lcov - # Dependency directories node_modules/ -# TypeScript cache -*.tsbuildinfo - # Output of 'npm pack' *.tgz -# Optional eslint cache -.eslintcache - # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ @@ -49,31 +35,15 @@ node_modules/ # Yarn Integrity file .yarn-integrity -# Generate output -dist -build -cache - -# Next.js -.next/ -out/ - # vercel .vercel -# typescript -next-env.d.ts - # dotenv environment variable files .env -.env.development.local -.env.test.local -.env.production.local -.env.local # Optional npm cache directory .npm -*.DS_Store +.DS_Store # yarn v3 .pnp.* @@ -84,39 +54,3 @@ next-env.d.ts !.yarn/releases !.yarn/sdks !.yarn/versions - -# The Graph -generated - -# Auto Generated PWA files -**/public/sw.js -**/public/workbox-*.js -**/public/worker-*.js -**/public/sw.js.map -**/public/workbox-*.js.map -**/public/worker-*.js.map - -#amplify-do-not-edit-begin -amplify/\#current-cloud-backend -amplify/.config/local-* -amplify/logs -amplify/mock-data -amplify/mock-api-resources -amplify/backend/amplify-meta.json -amplify/backend/.temp -build/ -dist/ -node_modules/ -aws-exports.js -awsconfiguration.json -amplifyconfiguration.json -amplifyconfiguration.dart -amplify-build-config.json -amplify-gradle-config.json -amplifytools.xcconfig -.secret-* -**.sample -#amplify-do-not-edit-end - -# Others -files.tgz diff --git a/packages/cli-template-monorepo-ethers/.prettierignore b/packages/cli-template-monorepo-ethers/.prettierignore index c6d5fdfbe..a82da8179 100644 --- a/packages/cli-template-monorepo-ethers/.prettierignore +++ b/packages/cli-template-monorepo-ethers/.prettierignore @@ -14,9 +14,13 @@ docs # production dist build -cache contract-artifacts +# Hardhat files +artifacts +cache +typechain-types + # github .github/ISSUE_TEMPLATE diff --git a/packages/cli-template-monorepo-ethers/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs b/packages/cli-template-monorepo-ethers/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs deleted file mode 100644 index b9044a014..000000000 --- a/packages/cli-template-monorepo-ethers/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-disable */ -//prettier-ignore -module.exports = { -name: "@yarnpkg/plugin-workspace-tools", -factory: function (require) { -var plugin=(()=>{var wr=Object.create,me=Object.defineProperty,Sr=Object.defineProperties,vr=Object.getOwnPropertyDescriptor,Hr=Object.getOwnPropertyDescriptors,$r=Object.getOwnPropertyNames,et=Object.getOwnPropertySymbols,kr=Object.getPrototypeOf,tt=Object.prototype.hasOwnProperty,Tr=Object.prototype.propertyIsEnumerable;var rt=(e,t,r)=>t in e?me(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,B=(e,t)=>{for(var r in t||(t={}))tt.call(t,r)&&rt(e,r,t[r]);if(et)for(var r of et(t))Tr.call(t,r)&&rt(e,r,t[r]);return e},Q=(e,t)=>Sr(e,Hr(t)),Lr=e=>me(e,"__esModule",{value:!0});var K=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),Or=(e,t)=>{for(var r in t)me(e,r,{get:t[r],enumerable:!0})},Nr=(e,t,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of $r(t))!tt.call(e,n)&&n!=="default"&&me(e,n,{get:()=>t[n],enumerable:!(r=vr(t,n))||r.enumerable});return e},X=e=>Nr(Lr(me(e!=null?wr(kr(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var $e=K(te=>{"use strict";te.isInteger=e=>typeof e=="number"?Number.isInteger(e):typeof e=="string"&&e.trim()!==""?Number.isInteger(Number(e)):!1;te.find=(e,t)=>e.nodes.find(r=>r.type===t);te.exceedsLimit=(e,t,r=1,n)=>n===!1||!te.isInteger(e)||!te.isInteger(t)?!1:(Number(t)-Number(e))/Number(r)>=n;te.escapeNode=(e,t=0,r)=>{let n=e.nodes[t];!n||(r&&n.type===r||n.type==="open"||n.type==="close")&&n.escaped!==!0&&(n.value="\\"+n.value,n.escaped=!0)};te.encloseBrace=e=>e.type!=="brace"?!1:e.commas>>0+e.ranges>>0==0?(e.invalid=!0,!0):!1;te.isInvalidBrace=e=>e.type!=="brace"?!1:e.invalid===!0||e.dollar?!0:e.commas>>0+e.ranges>>0==0||e.open!==!0||e.close!==!0?(e.invalid=!0,!0):!1;te.isOpenOrClose=e=>e.type==="open"||e.type==="close"?!0:e.open===!0||e.close===!0;te.reduce=e=>e.reduce((t,r)=>(r.type==="text"&&t.push(r.value),r.type==="range"&&(r.type="text"),t),[]);te.flatten=(...e)=>{let t=[],r=n=>{for(let s=0;s{"use strict";var it=$e();at.exports=(e,t={})=>{let r=(n,s={})=>{let a=t.escapeInvalid&&it.isInvalidBrace(s),i=n.invalid===!0&&t.escapeInvalid===!0,o="";if(n.value)return(a||i)&&it.isOpenOrClose(n)?"\\"+n.value:n.value;if(n.value)return n.value;if(n.nodes)for(let h of n.nodes)o+=r(h);return o};return r(e)}});var ct=K((os,ot)=>{"use strict";ot.exports=function(e){return typeof e=="number"?e-e==0:typeof e=="string"&&e.trim()!==""?Number.isFinite?Number.isFinite(+e):isFinite(+e):!1}});var At=K((cs,ut)=>{"use strict";var lt=ct(),pe=(e,t,r)=>{if(lt(e)===!1)throw new TypeError("toRegexRange: expected the first argument to be a number");if(t===void 0||e===t)return String(e);if(lt(t)===!1)throw new TypeError("toRegexRange: expected the second argument to be a number.");let n=B({relaxZeros:!0},r);typeof n.strictZeros=="boolean"&&(n.relaxZeros=n.strictZeros===!1);let s=String(n.relaxZeros),a=String(n.shorthand),i=String(n.capture),o=String(n.wrap),h=e+":"+t+"="+s+a+i+o;if(pe.cache.hasOwnProperty(h))return pe.cache[h].result;let g=Math.min(e,t),f=Math.max(e,t);if(Math.abs(g-f)===1){let R=e+"|"+t;return n.capture?`(${R})`:n.wrap===!1?R:`(?:${R})`}let A=ft(e)||ft(t),p={min:e,max:t,a:g,b:f},k=[],y=[];if(A&&(p.isPadded=A,p.maxLen=String(p.max).length),g<0){let R=f<0?Math.abs(f):1;y=pt(R,Math.abs(g),p,n),g=p.a=0}return f>=0&&(k=pt(g,f,p,n)),p.negatives=y,p.positives=k,p.result=Ir(y,k,n),n.capture===!0?p.result=`(${p.result})`:n.wrap!==!1&&k.length+y.length>1&&(p.result=`(?:${p.result})`),pe.cache[h]=p,p.result};function Ir(e,t,r){let n=Pe(e,t,"-",!1,r)||[],s=Pe(t,e,"",!1,r)||[],a=Pe(e,t,"-?",!0,r)||[];return n.concat(a).concat(s).join("|")}function Mr(e,t){let r=1,n=1,s=ht(e,r),a=new Set([t]);for(;e<=s&&s<=t;)a.add(s),r+=1,s=ht(e,r);for(s=dt(t+1,n)-1;e1&&o.count.pop(),o.count.push(f.count[0]),o.string=o.pattern+gt(o.count),i=g+1;continue}r.isPadded&&(A=Gr(g,r,n)),f.string=A+f.pattern+gt(f.count),a.push(f),i=g+1,o=f}return a}function Pe(e,t,r,n,s){let a=[];for(let i of e){let{string:o}=i;!n&&!mt(t,"string",o)&&a.push(r+o),n&&mt(t,"string",o)&&a.push(r+o)}return a}function Pr(e,t){let r=[];for(let n=0;nt?1:t>e?-1:0}function mt(e,t,r){return e.some(n=>n[t]===r)}function ht(e,t){return Number(String(e).slice(0,-t)+"9".repeat(t))}function dt(e,t){return e-e%Math.pow(10,t)}function gt(e){let[t=0,r=""]=e;return r||t>1?`{${t+(r?","+r:"")}}`:""}function Dr(e,t,r){return`[${e}${t-e==1?"":"-"}${t}]`}function ft(e){return/^-?(0+)\d/.test(e)}function Gr(e,t,r){if(!t.isPadded)return e;let n=Math.abs(t.maxLen-String(e).length),s=r.relaxZeros!==!1;switch(n){case 0:return"";case 1:return s?"0?":"0";case 2:return s?"0{0,2}":"00";default:return s?`0{0,${n}}`:`0{${n}}`}}pe.cache={};pe.clearCache=()=>pe.cache={};ut.exports=pe});var Ge=K((us,Rt)=>{"use strict";var qr=require("util"),yt=At(),bt=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),Kr=e=>t=>e===!0?Number(t):String(t),De=e=>typeof e=="number"||typeof e=="string"&&e!=="",Re=e=>Number.isInteger(+e),Ue=e=>{let t=`${e}`,r=-1;if(t[0]==="-"&&(t=t.slice(1)),t==="0")return!1;for(;t[++r]==="0";);return r>0},Wr=(e,t,r)=>typeof e=="string"||typeof t=="string"?!0:r.stringify===!0,jr=(e,t,r)=>{if(t>0){let n=e[0]==="-"?"-":"";n&&(e=e.slice(1)),e=n+e.padStart(n?t-1:t,"0")}return r===!1?String(e):e},_t=(e,t)=>{let r=e[0]==="-"?"-":"";for(r&&(e=e.slice(1),t--);e.length{e.negatives.sort((i,o)=>io?1:0),e.positives.sort((i,o)=>io?1:0);let r=t.capture?"":"?:",n="",s="",a;return e.positives.length&&(n=e.positives.join("|")),e.negatives.length&&(s=`-(${r}${e.negatives.join("|")})`),n&&s?a=`${n}|${s}`:a=n||s,t.wrap?`(${r}${a})`:a},Et=(e,t,r,n)=>{if(r)return yt(e,t,B({wrap:!1},n));let s=String.fromCharCode(e);if(e===t)return s;let a=String.fromCharCode(t);return`[${s}-${a}]`},xt=(e,t,r)=>{if(Array.isArray(e)){let n=r.wrap===!0,s=r.capture?"":"?:";return n?`(${s}${e.join("|")})`:e.join("|")}return yt(e,t,r)},Ct=(...e)=>new RangeError("Invalid range arguments: "+qr.inspect(...e)),wt=(e,t,r)=>{if(r.strictRanges===!0)throw Ct([e,t]);return[]},Qr=(e,t)=>{if(t.strictRanges===!0)throw new TypeError(`Expected step "${e}" to be a number`);return[]},Xr=(e,t,r=1,n={})=>{let s=Number(e),a=Number(t);if(!Number.isInteger(s)||!Number.isInteger(a)){if(n.strictRanges===!0)throw Ct([e,t]);return[]}s===0&&(s=0),a===0&&(a=0);let i=s>a,o=String(e),h=String(t),g=String(r);r=Math.max(Math.abs(r),1);let f=Ue(o)||Ue(h)||Ue(g),A=f?Math.max(o.length,h.length,g.length):0,p=f===!1&&Wr(e,t,n)===!1,k=n.transform||Kr(p);if(n.toRegex&&r===1)return Et(_t(e,A),_t(t,A),!0,n);let y={negatives:[],positives:[]},R=T=>y[T<0?"negatives":"positives"].push(Math.abs(T)),_=[],x=0;for(;i?s>=a:s<=a;)n.toRegex===!0&&r>1?R(s):_.push(jr(k(s,x),A,p)),s=i?s-r:s+r,x++;return n.toRegex===!0?r>1?Fr(y,n):xt(_,null,B({wrap:!1},n)):_},Zr=(e,t,r=1,n={})=>{if(!Re(e)&&e.length>1||!Re(t)&&t.length>1)return wt(e,t,n);let s=n.transform||(p=>String.fromCharCode(p)),a=`${e}`.charCodeAt(0),i=`${t}`.charCodeAt(0),o=a>i,h=Math.min(a,i),g=Math.max(a,i);if(n.toRegex&&r===1)return Et(h,g,!1,n);let f=[],A=0;for(;o?a>=i:a<=i;)f.push(s(a,A)),a=o?a-r:a+r,A++;return n.toRegex===!0?xt(f,null,{wrap:!1,options:n}):f},Te=(e,t,r,n={})=>{if(t==null&&De(e))return[e];if(!De(e)||!De(t))return wt(e,t,n);if(typeof r=="function")return Te(e,t,1,{transform:r});if(bt(r))return Te(e,t,0,r);let s=B({},n);return s.capture===!0&&(s.wrap=!0),r=r||s.step||1,Re(r)?Re(e)&&Re(t)?Xr(e,t,r,s):Zr(e,t,Math.max(Math.abs(r),1),s):r!=null&&!bt(r)?Qr(r,s):Te(e,t,1,r)};Rt.exports=Te});var Ht=K((ls,St)=>{"use strict";var Yr=Ge(),vt=$e(),zr=(e,t={})=>{let r=(n,s={})=>{let a=vt.isInvalidBrace(s),i=n.invalid===!0&&t.escapeInvalid===!0,o=a===!0||i===!0,h=t.escapeInvalid===!0?"\\":"",g="";if(n.isOpen===!0||n.isClose===!0)return h+n.value;if(n.type==="open")return o?h+n.value:"(";if(n.type==="close")return o?h+n.value:")";if(n.type==="comma")return n.prev.type==="comma"?"":o?n.value:"|";if(n.value)return n.value;if(n.nodes&&n.ranges>0){let f=vt.reduce(n.nodes),A=Yr(...f,Q(B({},t),{wrap:!1,toRegex:!0}));if(A.length!==0)return f.length>1&&A.length>1?`(${A})`:A}if(n.nodes)for(let f of n.nodes)g+=r(f,n);return g};return r(e)};St.exports=zr});var Tt=K((ps,$t)=>{"use strict";var Vr=Ge(),kt=ke(),he=$e(),fe=(e="",t="",r=!1)=>{let n=[];if(e=[].concat(e),t=[].concat(t),!t.length)return e;if(!e.length)return r?he.flatten(t).map(s=>`{${s}}`):t;for(let s of e)if(Array.isArray(s))for(let a of s)n.push(fe(a,t,r));else for(let a of t)r===!0&&typeof a=="string"&&(a=`{${a}}`),n.push(Array.isArray(a)?fe(s,a,r):s+a);return he.flatten(n)},Jr=(e,t={})=>{let r=t.rangeLimit===void 0?1e3:t.rangeLimit,n=(s,a={})=>{s.queue=[];let i=a,o=a.queue;for(;i.type!=="brace"&&i.type!=="root"&&i.parent;)i=i.parent,o=i.queue;if(s.invalid||s.dollar){o.push(fe(o.pop(),kt(s,t)));return}if(s.type==="brace"&&s.invalid!==!0&&s.nodes.length===2){o.push(fe(o.pop(),["{}"]));return}if(s.nodes&&s.ranges>0){let A=he.reduce(s.nodes);if(he.exceedsLimit(...A,t.step,r))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let p=Vr(...A,t);p.length===0&&(p=kt(s,t)),o.push(fe(o.pop(),p)),s.nodes=[];return}let h=he.encloseBrace(s),g=s.queue,f=s;for(;f.type!=="brace"&&f.type!=="root"&&f.parent;)f=f.parent,g=f.queue;for(let A=0;A{"use strict";Lt.exports={MAX_LENGTH:1024*64,CHAR_0:"0",CHAR_9:"9",CHAR_UPPERCASE_A:"A",CHAR_LOWERCASE_A:"a",CHAR_UPPERCASE_Z:"Z",CHAR_LOWERCASE_Z:"z",CHAR_LEFT_PARENTHESES:"(",CHAR_RIGHT_PARENTHESES:")",CHAR_ASTERISK:"*",CHAR_AMPERSAND:"&",CHAR_AT:"@",CHAR_BACKSLASH:"\\",CHAR_BACKTICK:"`",CHAR_CARRIAGE_RETURN:"\r",CHAR_CIRCUMFLEX_ACCENT:"^",CHAR_COLON:":",CHAR_COMMA:",",CHAR_DOLLAR:"$",CHAR_DOT:".",CHAR_DOUBLE_QUOTE:'"',CHAR_EQUAL:"=",CHAR_EXCLAMATION_MARK:"!",CHAR_FORM_FEED:"\f",CHAR_FORWARD_SLASH:"/",CHAR_HASH:"#",CHAR_HYPHEN_MINUS:"-",CHAR_LEFT_ANGLE_BRACKET:"<",CHAR_LEFT_CURLY_BRACE:"{",CHAR_LEFT_SQUARE_BRACKET:"[",CHAR_LINE_FEED:` -`,CHAR_NO_BREAK_SPACE:"\xA0",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:" ",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\uFEFF"}});var Pt=K((hs,Nt)=>{"use strict";var en=ke(),{MAX_LENGTH:It,CHAR_BACKSLASH:qe,CHAR_BACKTICK:tn,CHAR_COMMA:rn,CHAR_DOT:nn,CHAR_LEFT_PARENTHESES:sn,CHAR_RIGHT_PARENTHESES:an,CHAR_LEFT_CURLY_BRACE:on,CHAR_RIGHT_CURLY_BRACE:cn,CHAR_LEFT_SQUARE_BRACKET:Bt,CHAR_RIGHT_SQUARE_BRACKET:Mt,CHAR_DOUBLE_QUOTE:un,CHAR_SINGLE_QUOTE:ln,CHAR_NO_BREAK_SPACE:pn,CHAR_ZERO_WIDTH_NOBREAK_SPACE:fn}=Ot(),hn=(e,t={})=>{if(typeof e!="string")throw new TypeError("Expected a string");let r=t||{},n=typeof r.maxLength=="number"?Math.min(It,r.maxLength):It;if(e.length>n)throw new SyntaxError(`Input length (${e.length}), exceeds max characters (${n})`);let s={type:"root",input:e,nodes:[]},a=[s],i=s,o=s,h=0,g=e.length,f=0,A=0,p,k={},y=()=>e[f++],R=_=>{if(_.type==="text"&&o.type==="dot"&&(o.type="text"),o&&o.type==="text"&&_.type==="text"){o.value+=_.value;return}return i.nodes.push(_),_.parent=i,_.prev=o,o=_,_};for(R({type:"bos"});f0){if(i.ranges>0){i.ranges=0;let _=i.nodes.shift();i.nodes=[_,{type:"text",value:en(i)}]}R({type:"comma",value:p}),i.commas++;continue}if(p===nn&&A>0&&i.commas===0){let _=i.nodes;if(A===0||_.length===0){R({type:"text",value:p});continue}if(o.type==="dot"){if(i.range=[],o.value+=p,o.type="range",i.nodes.length!==3&&i.nodes.length!==5){i.invalid=!0,i.ranges=0,o.type="text";continue}i.ranges++,i.args=[];continue}if(o.type==="range"){_.pop();let x=_[_.length-1];x.value+=o.value+p,o=x,i.ranges--;continue}R({type:"dot",value:p});continue}R({type:"text",value:p})}do if(i=a.pop(),i.type!=="root"){i.nodes.forEach(T=>{T.nodes||(T.type==="open"&&(T.isOpen=!0),T.type==="close"&&(T.isClose=!0),T.nodes||(T.type="text"),T.invalid=!0)});let _=a[a.length-1],x=_.nodes.indexOf(i);_.nodes.splice(x,1,...i.nodes)}while(a.length>0);return R({type:"eos"}),s};Nt.exports=hn});var Gt=K((ds,Dt)=>{"use strict";var Ut=ke(),dn=Ht(),gn=Tt(),mn=Pt(),V=(e,t={})=>{let r=[];if(Array.isArray(e))for(let n of e){let s=V.create(n,t);Array.isArray(s)?r.push(...s):r.push(s)}else r=[].concat(V.create(e,t));return t&&t.expand===!0&&t.nodupes===!0&&(r=[...new Set(r)]),r};V.parse=(e,t={})=>mn(e,t);V.stringify=(e,t={})=>typeof e=="string"?Ut(V.parse(e,t),t):Ut(e,t);V.compile=(e,t={})=>(typeof e=="string"&&(e=V.parse(e,t)),dn(e,t));V.expand=(e,t={})=>{typeof e=="string"&&(e=V.parse(e,t));let r=gn(e,t);return t.noempty===!0&&(r=r.filter(Boolean)),t.nodupes===!0&&(r=[...new Set(r)]),r};V.create=(e,t={})=>e===""||e.length<3?[e]:t.expand!==!0?V.compile(e,t):V.expand(e,t);Dt.exports=V});var ye=K((gs,qt)=>{"use strict";var An=require("path"),ie="\\\\/",Kt=`[^${ie}]`,ce="\\.",Rn="\\+",yn="\\?",Le="\\/",bn="(?=.)",Wt="[^/]",Ke=`(?:${Le}|$)`,jt=`(?:^|${Le})`,We=`${ce}{1,2}${Ke}`,_n=`(?!${ce})`,En=`(?!${jt}${We})`,xn=`(?!${ce}{0,1}${Ke})`,Cn=`(?!${We})`,wn=`[^.${Le}]`,Sn=`${Wt}*?`,Ft={DOT_LITERAL:ce,PLUS_LITERAL:Rn,QMARK_LITERAL:yn,SLASH_LITERAL:Le,ONE_CHAR:bn,QMARK:Wt,END_ANCHOR:Ke,DOTS_SLASH:We,NO_DOT:_n,NO_DOTS:En,NO_DOT_SLASH:xn,NO_DOTS_SLASH:Cn,QMARK_NO_DOT:wn,STAR:Sn,START_ANCHOR:jt},vn=Q(B({},Ft),{SLASH_LITERAL:`[${ie}]`,QMARK:Kt,STAR:`${Kt}*?`,DOTS_SLASH:`${ce}{1,2}(?:[${ie}]|$)`,NO_DOT:`(?!${ce})`,NO_DOTS:`(?!(?:^|[${ie}])${ce}{1,2}(?:[${ie}]|$))`,NO_DOT_SLASH:`(?!${ce}{0,1}(?:[${ie}]|$))`,NO_DOTS_SLASH:`(?!${ce}{1,2}(?:[${ie}]|$))`,QMARK_NO_DOT:`[^.${ie}]`,START_ANCHOR:`(?:^|[${ie}])`,END_ANCHOR:`(?:[${ie}]|$)`}),Hn={alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"};qt.exports={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:Hn,REGEX_BACKSLASH:/\\(?![*+?^${}(|)[\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\].,$*+?^{}()|\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\?)((\W)(\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\[.*?[^\\]\]|\\(?=.))/g,REPLACEMENTS:{"***":"*","**/**":"**","**/**/**":"**"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:An.sep,extglobChars(e){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${e.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}},globChars(e){return e===!0?vn:Ft}}});var be=K(Z=>{"use strict";var $n=require("path"),kn=process.platform==="win32",{REGEX_BACKSLASH:Tn,REGEX_REMOVE_BACKSLASH:Ln,REGEX_SPECIAL_CHARS:On,REGEX_SPECIAL_CHARS_GLOBAL:Nn}=ye();Z.isObject=e=>e!==null&&typeof e=="object"&&!Array.isArray(e);Z.hasRegexChars=e=>On.test(e);Z.isRegexChar=e=>e.length===1&&Z.hasRegexChars(e);Z.escapeRegex=e=>e.replace(Nn,"\\$1");Z.toPosixSlashes=e=>e.replace(Tn,"/");Z.removeBackslashes=e=>e.replace(Ln,t=>t==="\\"?"":t);Z.supportsLookbehinds=()=>{let e=process.version.slice(1).split(".").map(Number);return e.length===3&&e[0]>=9||e[0]===8&&e[1]>=10};Z.isWindows=e=>e&&typeof e.windows=="boolean"?e.windows:kn===!0||$n.sep==="\\";Z.escapeLast=(e,t,r)=>{let n=e.lastIndexOf(t,r);return n===-1?e:e[n-1]==="\\"?Z.escapeLast(e,t,n-1):`${e.slice(0,n)}\\${e.slice(n)}`};Z.removePrefix=(e,t={})=>{let r=e;return r.startsWith("./")&&(r=r.slice(2),t.prefix="./"),r};Z.wrapOutput=(e,t={},r={})=>{let n=r.contains?"":"^",s=r.contains?"":"$",a=`${n}(?:${e})${s}`;return t.negated===!0&&(a=`(?:^(?!${a}).*$)`),a}});var er=K((As,Qt)=>{"use strict";var Xt=be(),{CHAR_ASTERISK:je,CHAR_AT:In,CHAR_BACKWARD_SLASH:_e,CHAR_COMMA:Bn,CHAR_DOT:Fe,CHAR_EXCLAMATION_MARK:Qe,CHAR_FORWARD_SLASH:Zt,CHAR_LEFT_CURLY_BRACE:Xe,CHAR_LEFT_PARENTHESES:Ze,CHAR_LEFT_SQUARE_BRACKET:Mn,CHAR_PLUS:Pn,CHAR_QUESTION_MARK:Yt,CHAR_RIGHT_CURLY_BRACE:Dn,CHAR_RIGHT_PARENTHESES:zt,CHAR_RIGHT_SQUARE_BRACKET:Un}=ye(),Vt=e=>e===Zt||e===_e,Jt=e=>{e.isPrefix!==!0&&(e.depth=e.isGlobstar?Infinity:1)},Gn=(e,t)=>{let r=t||{},n=e.length-1,s=r.parts===!0||r.scanToEnd===!0,a=[],i=[],o=[],h=e,g=-1,f=0,A=0,p=!1,k=!1,y=!1,R=!1,_=!1,x=!1,T=!1,O=!1,W=!1,G=!1,ne=0,E,b,C={value:"",depth:0,isGlob:!1},M=()=>g>=n,l=()=>h.charCodeAt(g+1),H=()=>(E=b,h.charCodeAt(++g));for(;g0&&(j=h.slice(0,f),h=h.slice(f),A-=f),w&&y===!0&&A>0?(w=h.slice(0,A),c=h.slice(A)):y===!0?(w="",c=h):w=h,w&&w!==""&&w!=="/"&&w!==h&&Vt(w.charCodeAt(w.length-1))&&(w=w.slice(0,-1)),r.unescape===!0&&(c&&(c=Xt.removeBackslashes(c)),w&&T===!0&&(w=Xt.removeBackslashes(w)));let u={prefix:j,input:e,start:f,base:w,glob:c,isBrace:p,isBracket:k,isGlob:y,isExtglob:R,isGlobstar:_,negated:O,negatedExtglob:W};if(r.tokens===!0&&(u.maxDepth=0,Vt(b)||i.push(C),u.tokens=i),r.parts===!0||r.tokens===!0){let I;for(let $=0;${"use strict";var Oe=ye(),J=be(),{MAX_LENGTH:Ne,POSIX_REGEX_SOURCE:qn,REGEX_NON_SPECIAL_CHARS:Kn,REGEX_SPECIAL_CHARS_BACKREF:Wn,REPLACEMENTS:rr}=Oe,jn=(e,t)=>{if(typeof t.expandRange=="function")return t.expandRange(...e,t);e.sort();let r=`[${e.join("-")}]`;try{new RegExp(r)}catch(n){return e.map(s=>J.escapeRegex(s)).join("..")}return r},de=(e,t)=>`Missing ${e}: "${t}" - use "\\\\${t}" to match literal characters`,nr=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");e=rr[e]||e;let r=B({},t),n=typeof r.maxLength=="number"?Math.min(Ne,r.maxLength):Ne,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);let a={type:"bos",value:"",output:r.prepend||""},i=[a],o=r.capture?"":"?:",h=J.isWindows(t),g=Oe.globChars(h),f=Oe.extglobChars(g),{DOT_LITERAL:A,PLUS_LITERAL:p,SLASH_LITERAL:k,ONE_CHAR:y,DOTS_SLASH:R,NO_DOT:_,NO_DOT_SLASH:x,NO_DOTS_SLASH:T,QMARK:O,QMARK_NO_DOT:W,STAR:G,START_ANCHOR:ne}=g,E=m=>`(${o}(?:(?!${ne}${m.dot?R:A}).)*?)`,b=r.dot?"":_,C=r.dot?O:W,M=r.bash===!0?E(r):G;r.capture&&(M=`(${M})`),typeof r.noext=="boolean"&&(r.noextglob=r.noext);let l={input:e,index:-1,start:0,dot:r.dot===!0,consumed:"",output:"",prefix:"",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:i};e=J.removePrefix(e,l),s=e.length;let H=[],w=[],j=[],c=a,u,I=()=>l.index===s-1,$=l.peek=(m=1)=>e[l.index+m],ee=l.advance=()=>e[++l.index]||"",se=()=>e.slice(l.index+1),z=(m="",L=0)=>{l.consumed+=m,l.index+=L},Ce=m=>{l.output+=m.output!=null?m.output:m.value,z(m.value)},xr=()=>{let m=1;for(;$()==="!"&&($(2)!=="("||$(3)==="?");)ee(),l.start++,m++;return m%2==0?!1:(l.negated=!0,l.start++,!0)},we=m=>{l[m]++,j.push(m)},ue=m=>{l[m]--,j.pop()},v=m=>{if(c.type==="globstar"){let L=l.braces>0&&(m.type==="comma"||m.type==="brace"),d=m.extglob===!0||H.length&&(m.type==="pipe"||m.type==="paren");m.type!=="slash"&&m.type!=="paren"&&!L&&!d&&(l.output=l.output.slice(0,-c.output.length),c.type="star",c.value="*",c.output=M,l.output+=c.output)}if(H.length&&m.type!=="paren"&&(H[H.length-1].inner+=m.value),(m.value||m.output)&&Ce(m),c&&c.type==="text"&&m.type==="text"){c.value+=m.value,c.output=(c.output||"")+m.value;return}m.prev=c,i.push(m),c=m},Se=(m,L)=>{let d=Q(B({},f[L]),{conditions:1,inner:""});d.prev=c,d.parens=l.parens,d.output=l.output;let S=(r.capture?"(":"")+d.open;we("parens"),v({type:m,value:L,output:l.output?"":y}),v({type:"paren",extglob:!0,value:ee(),output:S}),H.push(d)},Cr=m=>{let L=m.close+(r.capture?")":""),d;if(m.type==="negate"){let S=M;m.inner&&m.inner.length>1&&m.inner.includes("/")&&(S=E(r)),(S!==M||I()||/^\)+$/.test(se()))&&(L=m.close=`)$))${S}`),m.inner.includes("*")&&(d=se())&&/^\.[^\\/.]+$/.test(d)&&(L=m.close=`)${d})${S})`),m.prev.type==="bos"&&(l.negatedExtglob=!0)}v({type:"paren",extglob:!0,value:u,output:L}),ue("parens")};if(r.fastpaths!==!1&&!/(^[*!]|[/()[\]{}"])/.test(e)){let m=!1,L=e.replace(Wn,(d,S,P,F,q,Me)=>F==="\\"?(m=!0,d):F==="?"?S?S+F+(q?O.repeat(q.length):""):Me===0?C+(q?O.repeat(q.length):""):O.repeat(P.length):F==="."?A.repeat(P.length):F==="*"?S?S+F+(q?M:""):M:S?d:`\\${d}`);return m===!0&&(r.unescape===!0?L=L.replace(/\\/g,""):L=L.replace(/\\+/g,d=>d.length%2==0?"\\\\":d?"\\":"")),L===e&&r.contains===!0?(l.output=e,l):(l.output=J.wrapOutput(L,l,t),l)}for(;!I();){if(u=ee(),u==="\0")continue;if(u==="\\"){let d=$();if(d==="/"&&r.bash!==!0||d==="."||d===";")continue;if(!d){u+="\\",v({type:"text",value:u});continue}let S=/^\\+/.exec(se()),P=0;if(S&&S[0].length>2&&(P=S[0].length,l.index+=P,P%2!=0&&(u+="\\")),r.unescape===!0?u=ee():u+=ee(),l.brackets===0){v({type:"text",value:u});continue}}if(l.brackets>0&&(u!=="]"||c.value==="["||c.value==="[^")){if(r.posix!==!1&&u===":"){let d=c.value.slice(1);if(d.includes("[")&&(c.posix=!0,d.includes(":"))){let S=c.value.lastIndexOf("["),P=c.value.slice(0,S),F=c.value.slice(S+2),q=qn[F];if(q){c.value=P+q,l.backtrack=!0,ee(),!a.output&&i.indexOf(c)===1&&(a.output=y);continue}}}(u==="["&&$()!==":"||u==="-"&&$()==="]")&&(u=`\\${u}`),u==="]"&&(c.value==="["||c.value==="[^")&&(u=`\\${u}`),r.posix===!0&&u==="!"&&c.value==="["&&(u="^"),c.value+=u,Ce({value:u});continue}if(l.quotes===1&&u!=='"'){u=J.escapeRegex(u),c.value+=u,Ce({value:u});continue}if(u==='"'){l.quotes=l.quotes===1?0:1,r.keepQuotes===!0&&v({type:"text",value:u});continue}if(u==="("){we("parens"),v({type:"paren",value:u});continue}if(u===")"){if(l.parens===0&&r.strictBrackets===!0)throw new SyntaxError(de("opening","("));let d=H[H.length-1];if(d&&l.parens===d.parens+1){Cr(H.pop());continue}v({type:"paren",value:u,output:l.parens?")":"\\)"}),ue("parens");continue}if(u==="["){if(r.nobracket===!0||!se().includes("]")){if(r.nobracket!==!0&&r.strictBrackets===!0)throw new SyntaxError(de("closing","]"));u=`\\${u}`}else we("brackets");v({type:"bracket",value:u});continue}if(u==="]"){if(r.nobracket===!0||c&&c.type==="bracket"&&c.value.length===1){v({type:"text",value:u,output:`\\${u}`});continue}if(l.brackets===0){if(r.strictBrackets===!0)throw new SyntaxError(de("opening","["));v({type:"text",value:u,output:`\\${u}`});continue}ue("brackets");let d=c.value.slice(1);if(c.posix!==!0&&d[0]==="^"&&!d.includes("/")&&(u=`/${u}`),c.value+=u,Ce({value:u}),r.literalBrackets===!1||J.hasRegexChars(d))continue;let S=J.escapeRegex(c.value);if(l.output=l.output.slice(0,-c.value.length),r.literalBrackets===!0){l.output+=S,c.value=S;continue}c.value=`(${o}${S}|${c.value})`,l.output+=c.value;continue}if(u==="{"&&r.nobrace!==!0){we("braces");let d={type:"brace",value:u,output:"(",outputIndex:l.output.length,tokensIndex:l.tokens.length};w.push(d),v(d);continue}if(u==="}"){let d=w[w.length-1];if(r.nobrace===!0||!d){v({type:"text",value:u,output:u});continue}let S=")";if(d.dots===!0){let P=i.slice(),F=[];for(let q=P.length-1;q>=0&&(i.pop(),P[q].type!=="brace");q--)P[q].type!=="dots"&&F.unshift(P[q].value);S=jn(F,r),l.backtrack=!0}if(d.comma!==!0&&d.dots!==!0){let P=l.output.slice(0,d.outputIndex),F=l.tokens.slice(d.tokensIndex);d.value=d.output="\\{",u=S="\\}",l.output=P;for(let q of F)l.output+=q.output||q.value}v({type:"brace",value:u,output:S}),ue("braces"),w.pop();continue}if(u==="|"){H.length>0&&H[H.length-1].conditions++,v({type:"text",value:u});continue}if(u===","){let d=u,S=w[w.length-1];S&&j[j.length-1]==="braces"&&(S.comma=!0,d="|"),v({type:"comma",value:u,output:d});continue}if(u==="/"){if(c.type==="dot"&&l.index===l.start+1){l.start=l.index+1,l.consumed="",l.output="",i.pop(),c=a;continue}v({type:"slash",value:u,output:k});continue}if(u==="."){if(l.braces>0&&c.type==="dot"){c.value==="."&&(c.output=A);let d=w[w.length-1];c.type="dots",c.output+=u,c.value+=u,d.dots=!0;continue}if(l.braces+l.parens===0&&c.type!=="bos"&&c.type!=="slash"){v({type:"text",value:u,output:A});continue}v({type:"dot",value:u,output:A});continue}if(u==="?"){if(!(c&&c.value==="(")&&r.noextglob!==!0&&$()==="("&&$(2)!=="?"){Se("qmark",u);continue}if(c&&c.type==="paren"){let S=$(),P=u;if(S==="<"&&!J.supportsLookbehinds())throw new Error("Node.js v10 or higher is required for regex lookbehinds");(c.value==="("&&!/[!=<:]/.test(S)||S==="<"&&!/<([!=]|\w+>)/.test(se()))&&(P=`\\${u}`),v({type:"text",value:u,output:P});continue}if(r.dot!==!0&&(c.type==="slash"||c.type==="bos")){v({type:"qmark",value:u,output:W});continue}v({type:"qmark",value:u,output:O});continue}if(u==="!"){if(r.noextglob!==!0&&$()==="("&&($(2)!=="?"||!/[!=<:]/.test($(3)))){Se("negate",u);continue}if(r.nonegate!==!0&&l.index===0){xr();continue}}if(u==="+"){if(r.noextglob!==!0&&$()==="("&&$(2)!=="?"){Se("plus",u);continue}if(c&&c.value==="("||r.regex===!1){v({type:"plus",value:u,output:p});continue}if(c&&(c.type==="bracket"||c.type==="paren"||c.type==="brace")||l.parens>0){v({type:"plus",value:u});continue}v({type:"plus",value:p});continue}if(u==="@"){if(r.noextglob!==!0&&$()==="("&&$(2)!=="?"){v({type:"at",extglob:!0,value:u,output:""});continue}v({type:"text",value:u});continue}if(u!=="*"){(u==="$"||u==="^")&&(u=`\\${u}`);let d=Kn.exec(se());d&&(u+=d[0],l.index+=d[0].length),v({type:"text",value:u});continue}if(c&&(c.type==="globstar"||c.star===!0)){c.type="star",c.star=!0,c.value+=u,c.output=M,l.backtrack=!0,l.globstar=!0,z(u);continue}let m=se();if(r.noextglob!==!0&&/^\([^?]/.test(m)){Se("star",u);continue}if(c.type==="star"){if(r.noglobstar===!0){z(u);continue}let d=c.prev,S=d.prev,P=d.type==="slash"||d.type==="bos",F=S&&(S.type==="star"||S.type==="globstar");if(r.bash===!0&&(!P||m[0]&&m[0]!=="/")){v({type:"star",value:u,output:""});continue}let q=l.braces>0&&(d.type==="comma"||d.type==="brace"),Me=H.length&&(d.type==="pipe"||d.type==="paren");if(!P&&d.type!=="paren"&&!q&&!Me){v({type:"star",value:u,output:""});continue}for(;m.slice(0,3)==="/**";){let ve=e[l.index+4];if(ve&&ve!=="/")break;m=m.slice(3),z("/**",3)}if(d.type==="bos"&&I()){c.type="globstar",c.value+=u,c.output=E(r),l.output=c.output,l.globstar=!0,z(u);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&!F&&I()){l.output=l.output.slice(0,-(d.output+c.output).length),d.output=`(?:${d.output}`,c.type="globstar",c.output=E(r)+(r.strictSlashes?")":"|$)"),c.value+=u,l.globstar=!0,l.output+=d.output+c.output,z(u);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&m[0]==="/"){let ve=m[1]!==void 0?"|$":"";l.output=l.output.slice(0,-(d.output+c.output).length),d.output=`(?:${d.output}`,c.type="globstar",c.output=`${E(r)}${k}|${k}${ve})`,c.value+=u,l.output+=d.output+c.output,l.globstar=!0,z(u+ee()),v({type:"slash",value:"/",output:""});continue}if(d.type==="bos"&&m[0]==="/"){c.type="globstar",c.value+=u,c.output=`(?:^|${k}|${E(r)}${k})`,l.output=c.output,l.globstar=!0,z(u+ee()),v({type:"slash",value:"/",output:""});continue}l.output=l.output.slice(0,-c.output.length),c.type="globstar",c.output=E(r),c.value+=u,l.output+=c.output,l.globstar=!0,z(u);continue}let L={type:"star",value:u,output:M};if(r.bash===!0){L.output=".*?",(c.type==="bos"||c.type==="slash")&&(L.output=b+L.output),v(L);continue}if(c&&(c.type==="bracket"||c.type==="paren")&&r.regex===!0){L.output=u,v(L);continue}(l.index===l.start||c.type==="slash"||c.type==="dot")&&(c.type==="dot"?(l.output+=x,c.output+=x):r.dot===!0?(l.output+=T,c.output+=T):(l.output+=b,c.output+=b),$()!=="*"&&(l.output+=y,c.output+=y)),v(L)}for(;l.brackets>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing","]"));l.output=J.escapeLast(l.output,"["),ue("brackets")}for(;l.parens>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing",")"));l.output=J.escapeLast(l.output,"("),ue("parens")}for(;l.braces>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing","}"));l.output=J.escapeLast(l.output,"{"),ue("braces")}if(r.strictSlashes!==!0&&(c.type==="star"||c.type==="bracket")&&v({type:"maybe_slash",value:"",output:`${k}?`}),l.backtrack===!0){l.output="";for(let m of l.tokens)l.output+=m.output!=null?m.output:m.value,m.suffix&&(l.output+=m.suffix)}return l};nr.fastpaths=(e,t)=>{let r=B({},t),n=typeof r.maxLength=="number"?Math.min(Ne,r.maxLength):Ne,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);e=rr[e]||e;let a=J.isWindows(t),{DOT_LITERAL:i,SLASH_LITERAL:o,ONE_CHAR:h,DOTS_SLASH:g,NO_DOT:f,NO_DOTS:A,NO_DOTS_SLASH:p,STAR:k,START_ANCHOR:y}=Oe.globChars(a),R=r.dot?A:f,_=r.dot?p:f,x=r.capture?"":"?:",T={negated:!1,prefix:""},O=r.bash===!0?".*?":k;r.capture&&(O=`(${O})`);let W=b=>b.noglobstar===!0?O:`(${x}(?:(?!${y}${b.dot?g:i}).)*?)`,G=b=>{switch(b){case"*":return`${R}${h}${O}`;case".*":return`${i}${h}${O}`;case"*.*":return`${R}${O}${i}${h}${O}`;case"*/*":return`${R}${O}${o}${h}${_}${O}`;case"**":return R+W(r);case"**/*":return`(?:${R}${W(r)}${o})?${_}${h}${O}`;case"**/*.*":return`(?:${R}${W(r)}${o})?${_}${O}${i}${h}${O}`;case"**/.*":return`(?:${R}${W(r)}${o})?${i}${h}${O}`;default:{let C=/^(.*?)\.(\w+)$/.exec(b);if(!C)return;let M=G(C[1]);return M?M+i+C[2]:void 0}}},ne=J.removePrefix(e,T),E=G(ne);return E&&r.strictSlashes!==!0&&(E+=`${o}?`),E};tr.exports=nr});var ir=K((ys,ar)=>{"use strict";var Fn=require("path"),Qn=er(),Ye=sr(),ze=be(),Xn=ye(),Zn=e=>e&&typeof e=="object"&&!Array.isArray(e),D=(e,t,r=!1)=>{if(Array.isArray(e)){let f=e.map(p=>D(p,t,r));return p=>{for(let k of f){let y=k(p);if(y)return y}return!1}}let n=Zn(e)&&e.tokens&&e.input;if(e===""||typeof e!="string"&&!n)throw new TypeError("Expected pattern to be a non-empty string");let s=t||{},a=ze.isWindows(t),i=n?D.compileRe(e,t):D.makeRe(e,t,!1,!0),o=i.state;delete i.state;let h=()=>!1;if(s.ignore){let f=Q(B({},t),{ignore:null,onMatch:null,onResult:null});h=D(s.ignore,f,r)}let g=(f,A=!1)=>{let{isMatch:p,match:k,output:y}=D.test(f,i,t,{glob:e,posix:a}),R={glob:e,state:o,regex:i,posix:a,input:f,output:y,match:k,isMatch:p};return typeof s.onResult=="function"&&s.onResult(R),p===!1?(R.isMatch=!1,A?R:!1):h(f)?(typeof s.onIgnore=="function"&&s.onIgnore(R),R.isMatch=!1,A?R:!1):(typeof s.onMatch=="function"&&s.onMatch(R),A?R:!0)};return r&&(g.state=o),g};D.test=(e,t,r,{glob:n,posix:s}={})=>{if(typeof e!="string")throw new TypeError("Expected input to be a string");if(e==="")return{isMatch:!1,output:""};let a=r||{},i=a.format||(s?ze.toPosixSlashes:null),o=e===n,h=o&&i?i(e):e;return o===!1&&(h=i?i(e):e,o=h===n),(o===!1||a.capture===!0)&&(a.matchBase===!0||a.basename===!0?o=D.matchBase(e,t,r,s):o=t.exec(h)),{isMatch:Boolean(o),match:o,output:h}};D.matchBase=(e,t,r,n=ze.isWindows(r))=>(t instanceof RegExp?t:D.makeRe(t,r)).test(Fn.basename(e));D.isMatch=(e,t,r)=>D(t,r)(e);D.parse=(e,t)=>Array.isArray(e)?e.map(r=>D.parse(r,t)):Ye(e,Q(B({},t),{fastpaths:!1}));D.scan=(e,t)=>Qn(e,t);D.compileRe=(e,t,r=!1,n=!1)=>{if(r===!0)return e.output;let s=t||{},a=s.contains?"":"^",i=s.contains?"":"$",o=`${a}(?:${e.output})${i}`;e&&e.negated===!0&&(o=`^(?!${o}).*$`);let h=D.toRegex(o,t);return n===!0&&(h.state=e),h};D.makeRe=(e,t={},r=!1,n=!1)=>{if(!e||typeof e!="string")throw new TypeError("Expected a non-empty string");let s={negated:!1,fastpaths:!0};return t.fastpaths!==!1&&(e[0]==="."||e[0]==="*")&&(s.output=Ye.fastpaths(e,t)),s.output||(s=Ye(e,t)),D.compileRe(s,t,r,n)};D.toRegex=(e,t)=>{try{let r=t||{};return new RegExp(e,r.flags||(r.nocase?"i":""))}catch(r){if(t&&t.debug===!0)throw r;return/$^/}};D.constants=Xn;ar.exports=D});var cr=K((bs,or)=>{"use strict";or.exports=ir()});var hr=K((_s,ur)=>{"use strict";var lr=require("util"),pr=Gt(),oe=cr(),Ve=be(),fr=e=>e===""||e==="./",N=(e,t,r)=>{t=[].concat(t),e=[].concat(e);let n=new Set,s=new Set,a=new Set,i=0,o=f=>{a.add(f.output),r&&r.onResult&&r.onResult(f)};for(let f=0;f!n.has(f));if(r&&g.length===0){if(r.failglob===!0)throw new Error(`No matches found for "${t.join(", ")}"`);if(r.nonull===!0||r.nullglob===!0)return r.unescape?t.map(f=>f.replace(/\\/g,"")):t}return g};N.match=N;N.matcher=(e,t)=>oe(e,t);N.isMatch=(e,t,r)=>oe(t,r)(e);N.any=N.isMatch;N.not=(e,t,r={})=>{t=[].concat(t).map(String);let n=new Set,s=[],a=o=>{r.onResult&&r.onResult(o),s.push(o.output)},i=N(e,t,Q(B({},r),{onResult:a}));for(let o of s)i.includes(o)||n.add(o);return[...n]};N.contains=(e,t,r)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${lr.inspect(e)}"`);if(Array.isArray(t))return t.some(n=>N.contains(e,n,r));if(typeof t=="string"){if(fr(e)||fr(t))return!1;if(e.includes(t)||e.startsWith("./")&&e.slice(2).includes(t))return!0}return N.isMatch(e,t,Q(B({},r),{contains:!0}))};N.matchKeys=(e,t,r)=>{if(!Ve.isObject(e))throw new TypeError("Expected the first argument to be an object");let n=N(Object.keys(e),t,r),s={};for(let a of n)s[a]=e[a];return s};N.some=(e,t,r)=>{let n=[].concat(e);for(let s of[].concat(t)){let a=oe(String(s),r);if(n.some(i=>a(i)))return!0}return!1};N.every=(e,t,r)=>{let n=[].concat(e);for(let s of[].concat(t)){let a=oe(String(s),r);if(!n.every(i=>a(i)))return!1}return!0};N.all=(e,t,r)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${lr.inspect(e)}"`);return[].concat(t).every(n=>oe(n,r)(e))};N.capture=(e,t,r)=>{let n=Ve.isWindows(r),a=oe.makeRe(String(e),Q(B({},r),{capture:!0})).exec(n?Ve.toPosixSlashes(t):t);if(a)return a.slice(1).map(i=>i===void 0?"":i)};N.makeRe=(...e)=>oe.makeRe(...e);N.scan=(...e)=>oe.scan(...e);N.parse=(e,t)=>{let r=[];for(let n of[].concat(e||[]))for(let s of pr(String(n),t))r.push(oe.parse(s,t));return r};N.braces=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");return t&&t.nobrace===!0||!/\{.*\}/.test(e)?[e]:pr(e,t)};N.braceExpand=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");return N.braces(e,Q(B({},t),{expand:!0}))};ur.exports=N});var gr=K((Es,dr)=>{"use strict";dr.exports=(e,...t)=>new Promise(r=>{r(e(...t))})});var Ar=K((xs,Je)=>{"use strict";var Yn=gr(),mr=e=>{if(e<1)throw new TypeError("Expected `concurrency` to be a number from 1 and up");let t=[],r=0,n=()=>{r--,t.length>0&&t.shift()()},s=(o,h,...g)=>{r++;let f=Yn(o,...g);h(f),f.then(n,n)},a=(o,h,...g)=>{rnew Promise(g=>a(o,g,...h));return Object.defineProperties(i,{activeCount:{get:()=>r},pendingCount:{get:()=>t.length}}),i};Je.exports=mr;Je.exports.default=mr});var Vn={};Or(Vn,{default:()=>es});var He=X(require("@yarnpkg/cli")),ae=X(require("@yarnpkg/core")),nt=X(require("@yarnpkg/core")),le=X(require("clipanion")),Ae=class extends He.BaseCommand{constructor(){super(...arguments);this.json=le.Option.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.production=le.Option.Boolean("--production",!1,{description:"Only install regular dependencies by omitting dev dependencies"});this.all=le.Option.Boolean("-A,--all",!1,{description:"Install the entire project"});this.workspaces=le.Option.Rest()}async execute(){let t=await ae.Configuration.find(this.context.cwd,this.context.plugins),{project:r,workspace:n}=await ae.Project.find(t,this.context.cwd),s=await ae.Cache.find(t);await r.restoreInstallState({restoreResolutions:!1});let a;if(this.all)a=new Set(r.workspaces);else if(this.workspaces.length===0){if(!n)throw new He.WorkspaceRequiredError(r.cwd,this.context.cwd);a=new Set([n])}else a=new Set(this.workspaces.map(o=>r.getWorkspaceByIdent(nt.structUtils.parseIdent(o))));for(let o of a)for(let h of this.production?["dependencies"]:ae.Manifest.hardDependencies)for(let g of o.manifest.getForScope(h).values()){let f=r.tryWorkspaceByDescriptor(g);f!==null&&a.add(f)}for(let o of r.workspaces)a.has(o)?this.production&&o.manifest.devDependencies.clear():(o.manifest.installConfig=o.manifest.installConfig||{},o.manifest.installConfig.selfReferences=!1,o.manifest.dependencies.clear(),o.manifest.devDependencies.clear(),o.manifest.peerDependencies.clear(),o.manifest.scripts.clear());return(await ae.StreamReport.start({configuration:t,json:this.json,stdout:this.context.stdout,includeLogs:!0},async o=>{await r.install({cache:s,report:o,persistProject:!1})})).exitCode()}};Ae.paths=[["workspaces","focus"]],Ae.usage=le.Command.Usage({category:"Workspace-related commands",description:"install a single workspace and its dependencies",details:"\n This command will run an install as if the specified workspaces (and all other workspaces they depend on) were the only ones in the project. If no workspaces are explicitly listed, the active one will be assumed.\n\n Note that this command is only very moderately useful when using zero-installs, since the cache will contain all the packages anyway - meaning that the only difference between a full install and a focused install would just be a few extra lines in the `.pnp.cjs` file, at the cost of introducing an extra complexity.\n\n If the `-A,--all` flag is set, the entire project will be installed. Combine with `--production` to replicate the old `yarn install --production`.\n "});var st=Ae;var Ie=X(require("@yarnpkg/cli")),ge=X(require("@yarnpkg/core")),Ee=X(require("@yarnpkg/core")),Y=X(require("@yarnpkg/core")),Rr=X(require("@yarnpkg/plugin-git")),U=X(require("clipanion")),Be=X(hr()),yr=X(require("os")),br=X(Ar()),re=X(require("typanion")),xe=class extends Ie.BaseCommand{constructor(){super(...arguments);this.recursive=U.Option.Boolean("-R,--recursive",!1,{description:"Find packages via dependencies/devDependencies instead of using the workspaces field"});this.from=U.Option.Array("--from",[],{description:"An array of glob pattern idents from which to base any recursion"});this.all=U.Option.Boolean("-A,--all",!1,{description:"Run the command on all workspaces of a project"});this.verbose=U.Option.Boolean("-v,--verbose",!1,{description:"Prefix each output line with the name of the originating workspace"});this.parallel=U.Option.Boolean("-p,--parallel",!1,{description:"Run the commands in parallel"});this.interlaced=U.Option.Boolean("-i,--interlaced",!1,{description:"Print the output of commands in real-time instead of buffering it"});this.jobs=U.Option.String("-j,--jobs",{description:"The maximum number of parallel tasks that the execution will be limited to; or `unlimited`",validator:re.isOneOf([re.isEnum(["unlimited"]),re.applyCascade(re.isNumber(),[re.isInteger(),re.isAtLeast(1)])])});this.topological=U.Option.Boolean("-t,--topological",!1,{description:"Run the command after all workspaces it depends on (regular) have finished"});this.topologicalDev=U.Option.Boolean("--topological-dev",!1,{description:"Run the command after all workspaces it depends on (regular + dev) have finished"});this.include=U.Option.Array("--include",[],{description:"An array of glob pattern idents; only matching workspaces will be traversed"});this.exclude=U.Option.Array("--exclude",[],{description:"An array of glob pattern idents; matching workspaces won't be traversed"});this.publicOnly=U.Option.Boolean("--no-private",{description:"Avoid running the command on private workspaces"});this.since=U.Option.String("--since",{description:"Only include workspaces that have been changed since the specified ref.",tolerateBoolean:!0});this.commandName=U.Option.String();this.args=U.Option.Proxy()}async execute(){let t=await ge.Configuration.find(this.context.cwd,this.context.plugins),{project:r,workspace:n}=await ge.Project.find(t,this.context.cwd);if(!this.all&&!n)throw new Ie.WorkspaceRequiredError(r.cwd,this.context.cwd);await r.restoreInstallState();let s=this.cli.process([this.commandName,...this.args]),a=s.path.length===1&&s.path[0]==="run"&&typeof s.scriptName!="undefined"?s.scriptName:null;if(s.path.length===0)throw new U.UsageError("Invalid subcommand name for iteration - use the 'run' keyword if you wish to execute a script");let i=this.all?r.topLevelWorkspace:n,o=this.since?Array.from(await Rr.gitUtils.fetchChangedWorkspaces({ref:this.since,project:r})):[i,...this.from.length>0?i.getRecursiveWorkspaceChildren():[]],h=E=>Be.default.isMatch(Y.structUtils.stringifyIdent(E.locator),this.from),g=this.from.length>0?o.filter(h):o,f=new Set([...g,...g.map(E=>[...this.recursive?this.since?E.getRecursiveWorkspaceDependents():E.getRecursiveWorkspaceDependencies():E.getRecursiveWorkspaceChildren()]).flat()]),A=[],p=!1;if(a==null?void 0:a.includes(":")){for(let E of r.workspaces)if(E.manifest.scripts.has(a)&&(p=!p,p===!1))break}for(let E of f)a&&!E.manifest.scripts.has(a)&&!p&&!(await ge.scriptUtils.getWorkspaceAccessibleBinaries(E)).has(a)||a===process.env.npm_lifecycle_event&&E.cwd===n.cwd||this.include.length>0&&!Be.default.isMatch(Y.structUtils.stringifyIdent(E.locator),this.include)||this.exclude.length>0&&Be.default.isMatch(Y.structUtils.stringifyIdent(E.locator),this.exclude)||this.publicOnly&&E.manifest.private===!0||A.push(E);let k=this.parallel?this.jobs==="unlimited"?Infinity:this.jobs||Math.max(1,(0,yr.cpus)().length/2):1,y=k===1?!1:this.parallel,R=y?this.interlaced:!0,_=(0,br.default)(k),x=new Map,T=new Set,O=0,W=null,G=!1,ne=await Ee.StreamReport.start({configuration:t,stdout:this.context.stdout},async E=>{let b=async(C,{commandIndex:M})=>{if(G)return-1;!y&&this.verbose&&M>1&&E.reportSeparator();let l=zn(C,{configuration:t,verbose:this.verbose,commandIndex:M}),[H,w]=_r(E,{prefix:l,interlaced:R}),[j,c]=_r(E,{prefix:l,interlaced:R});try{this.verbose&&E.reportInfo(null,`${l} Process started`);let u=Date.now(),I=await this.cli.run([this.commandName,...this.args],{cwd:C.cwd,stdout:H,stderr:j})||0;H.end(),j.end(),await w,await c;let $=Date.now();if(this.verbose){let ee=t.get("enableTimers")?`, completed in ${Y.formatUtils.pretty(t,$-u,Y.formatUtils.Type.DURATION)}`:"";E.reportInfo(null,`${l} Process exited (exit code ${I})${ee}`)}return I===130&&(G=!0,W=I),I}catch(u){throw H.end(),j.end(),await w,await c,u}};for(let C of A)x.set(C.anchoredLocator.locatorHash,C);for(;x.size>0&&!E.hasErrors();){let C=[];for(let[H,w]of x){if(T.has(w.anchoredDescriptor.descriptorHash))continue;let j=!0;if(this.topological||this.topologicalDev){let c=this.topologicalDev?new Map([...w.manifest.dependencies,...w.manifest.devDependencies]):w.manifest.dependencies;for(let u of c.values()){let I=r.tryWorkspaceByDescriptor(u);if(j=I===null||!x.has(I.anchoredLocator.locatorHash),!j)break}}if(!!j&&(T.add(w.anchoredDescriptor.descriptorHash),C.push(_(async()=>{let c=await b(w,{commandIndex:++O});return x.delete(H),T.delete(w.anchoredDescriptor.descriptorHash),c})),!y))break}if(C.length===0){let H=Array.from(x.values()).map(w=>Y.structUtils.prettyLocator(t,w.anchoredLocator)).join(", ");E.reportError(Ee.MessageName.CYCLIC_DEPENDENCIES,`Dependency cycle detected (${H})`);return}let l=(await Promise.all(C)).find(H=>H!==0);W===null&&(W=typeof l!="undefined"?1:W),(this.topological||this.topologicalDev)&&typeof l!="undefined"&&E.reportError(Ee.MessageName.UNNAMED,"The command failed for workspaces that are depended upon by other workspaces; can't satisfy the dependency graph")}});return W!==null?W:ne.exitCode()}};xe.paths=[["workspaces","foreach"]],xe.usage=U.Command.Usage({category:"Workspace-related commands",description:"run a command on all workspaces",details:"\n This command will run a given sub-command on current and all its descendant workspaces. Various flags can alter the exact behavior of the command:\n\n - If `-p,--parallel` is set, the commands will be ran in parallel; they'll by default be limited to a number of parallel tasks roughly equal to half your core number, but that can be overridden via `-j,--jobs`, or disabled by setting `-j unlimited`.\n\n - If `-p,--parallel` and `-i,--interlaced` are both set, Yarn will print the lines from the output as it receives them. If `-i,--interlaced` wasn't set, it would instead buffer the output from each process and print the resulting buffers only after their source processes have exited.\n\n - If `-t,--topological` is set, Yarn will only run the command after all workspaces that it depends on through the `dependencies` field have successfully finished executing. If `--topological-dev` is set, both the `dependencies` and `devDependencies` fields will be considered when figuring out the wait points.\n\n - If `-A,--all` is set, Yarn will run the command on all the workspaces of a project. By default yarn runs the command only on current and all its descendant workspaces.\n\n - If `-R,--recursive` is set, Yarn will find workspaces to run the command on by recursively evaluating `dependencies` and `devDependencies` fields, instead of looking at the `workspaces` fields.\n\n - If `--from` is set, Yarn will use the packages matching the 'from' glob as the starting point for any recursive search.\n\n - If `--since` is set, Yarn will only run the command on workspaces that have been modified since the specified ref. By default Yarn will use the refs specified by the `changesetBaseRefs` configuration option.\n\n - The command may apply to only some workspaces through the use of `--include` which acts as a whitelist. The `--exclude` flag will do the opposite and will be a list of packages that mustn't execute the script. Both flags accept glob patterns (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n\n Adding the `-v,--verbose` flag will cause Yarn to print more information; in particular the name of the workspace that generated the output will be printed at the front of each line.\n\n If the command is `run` and the script being run does not exist the child workspace will be skipped without error.\n ",examples:[["Publish current and all descendant packages","yarn workspaces foreach npm publish --tolerate-republish"],["Run build script on current and all descendant packages","yarn workspaces foreach run build"],["Run build script on current and all descendant packages in parallel, building package dependencies first","yarn workspaces foreach -pt run build"],["Run build script on several packages and all their dependencies, building dependencies first","yarn workspaces foreach -ptR --from '{workspace-a,workspace-b}' run build"]]});var Er=xe;function _r(e,{prefix:t,interlaced:r}){let n=e.createStreamReporter(t),s=new Y.miscUtils.DefaultStream;s.pipe(n,{end:!1}),s.on("finish",()=>{n.end()});let a=new Promise(o=>{n.on("finish",()=>{o(s.active)})});if(r)return[s,a];let i=new Y.miscUtils.BufferStream;return i.pipe(s,{end:!1}),i.on("finish",()=>{s.end()}),[i,a]}function zn(e,{configuration:t,commandIndex:r,verbose:n}){if(!n)return null;let s=Y.structUtils.convertToIdent(e.locator),i=`[${Y.structUtils.stringifyIdent(s)}]:`,o=["#2E86AB","#A23B72","#F18F01","#C73E1D","#CCE2A3"],h=o[r%o.length];return Y.formatUtils.pretty(t,i,h)}var Jn={commands:[st,Er]},es=Jn;return Vn;})(); -/*! - * fill-range - * - * Copyright (c) 2014-present, Jon Schlinkert. - * Licensed under the MIT License. - */ -/*! - * is-number - * - * Copyright (c) 2014-present, Jon Schlinkert. - * Released under the MIT License. - */ -/*! - * to-regex-range - * - * Copyright (c) 2015-present, Jon Schlinkert. - * Released under the MIT License. - */ -return plugin; -} -}; diff --git a/packages/cli-template-monorepo-ethers/.yarn/releases/yarn-3.2.1.cjs.REMOVED.git-id b/packages/cli-template-monorepo-ethers/.yarn/releases/yarn-3.2.1.cjs.REMOVED.git-id deleted file mode 100644 index ee9f521b7..000000000 --- a/packages/cli-template-monorepo-ethers/.yarn/releases/yarn-3.2.1.cjs.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -b3cadff6efb37a12712d12c2553ec703dbcaa4dd \ No newline at end of file diff --git a/packages/cli-template-monorepo-ethers/.yarn/releases/yarn-4.1.0.cjs.REMOVED.git-id b/packages/cli-template-monorepo-ethers/.yarn/releases/yarn-4.1.0.cjs.REMOVED.git-id new file mode 100644 index 000000000..430c13f99 --- /dev/null +++ b/packages/cli-template-monorepo-ethers/.yarn/releases/yarn-4.1.0.cjs.REMOVED.git-id @@ -0,0 +1 @@ +738adce5914a0e193f2e1255e4dcf7042256a1c1 \ No newline at end of file diff --git a/packages/cli-template-monorepo-ethers/.yarnrc.yml b/packages/cli-template-monorepo-ethers/.yarnrc.yml index 8d06f86a2..bd95ab27f 100644 --- a/packages/cli-template-monorepo-ethers/.yarnrc.yml +++ b/packages/cli-template-monorepo-ethers/.yarnrc.yml @@ -1,9 +1,7 @@ -checksumBehavior: update +compressionLevel: mixed -nodeLinker: node-modules +enableGlobalCache: false -plugins: - - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs - spec: "@yarnpkg/plugin-workspace-tools" +nodeLinker: node-modules -yarnPath: .yarn/releases/yarn-3.2.1.cjs +yarnPath: .yarn/releases/yarn-4.1.0.cjs diff --git a/packages/cli-template-monorepo-ethers/README.md b/packages/cli-template-monorepo-ethers/README.md index efc383bad..209f58a99 100644 --- a/packages/cli-template-monorepo-ethers/README.md +++ b/packages/cli-template-monorepo-ethers/README.md @@ -2,15 +2,15 @@ This project is a complete application that demonstrates a basic Semaphore use case. It comes with a sample contract, a test for that contract and a sample task that deploys that contract. It also contains a frontend to play around with the contract. -## 📜 Usage +## Install -Copy the `.env.example` file as `.env`: +### Install dependencies ```bash -cp .env.example .env +yarn ``` -and add your environment variables or run the app in a local network. +## 📜 Usage ### Local server @@ -25,12 +25,12 @@ yarn dev 1. Go to the `apps/contracts` directory and deploy your contract: ```bash -yarn deploy --semaphore --group --network arbitrum-goerli +yarn deploy --semaphore --group --network arbitrum-sepolia ``` 2. Update your `.env` file with your new contract address, the group id and the semaphore contract address. -3. Copy your contract artifacts from `apps/contracts/build/contracts/contracts` folder to `apps/web-app/contract-artifacts` folders manually. Or run `yarn copy:contract-artifacts` in the project root to do it automatically. +3. Copy your contract artifacts from `apps/contracts/artifacts/contracts/` folder to `apps/web-app/contract-artifacts` folder manually. > **Note** > Check the Semaphore contract addresses [here](https://docs.semaphore.pse.dev/deployed-contracts). @@ -40,7 +40,7 @@ yarn deploy --semaphore --group --network arbitru ### Code quality and formatting -Run [ESLint](https://eslint.org/) to analyze the code and catch bugs: +Run [ESLint](https://eslint.org/) and [solhint](https://github.com/protofire/solhint) to analyze the code and catch bugs: ```bash yarn lint @@ -52,7 +52,7 @@ Run [Prettier](https://prettier.io/) to check formatting rules: yarn prettier ``` -or to automatically format the code: +Or to automatically format the code: ```bash yarn prettier:write diff --git a/packages/cli-template-monorepo-ethers/apps/contracts/.eslintignore b/packages/cli-template-monorepo-ethers/apps/contracts/.eslintignore new file mode 100644 index 000000000..64601e738 --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/contracts/.eslintignore @@ -0,0 +1,18 @@ +# dependencies +node_modules + +# testing +coverage +coverage.json + +# hardhat +artifacts +cache +typechain-types + +# misc +.DS_Store +*.pem + + + diff --git a/packages/cli-template-monorepo-subgraph/.eslintrc.json b/packages/cli-template-monorepo-ethers/apps/contracts/.eslintrc.json similarity index 55% rename from packages/cli-template-monorepo-subgraph/.eslintrc.json rename to packages/cli-template-monorepo-ethers/apps/contracts/.eslintrc.json index 43bdfc312..4cf715c01 100644 --- a/packages/cli-template-monorepo-subgraph/.eslintrc.json +++ b/packages/cli-template-monorepo-ethers/apps/contracts/.eslintrc.json @@ -8,29 +8,17 @@ "parserOptions": { "ecmaVersion": 6, "sourceType": "module", - "project": ["./**/tsconfig.json"] + "project": ["./tsconfig.json", "./packages/**/tsconfig.json"] }, "plugins": ["@typescript-eslint"], "rules": { "no-underscore-dangle": "off", - "no-alert": "off", - "no-nested-ternary": "off", "import/no-extraneous-dependencies": "off", - "import/extensions": "off", - "import/no-relative-packages": "off", - "no-await-in-loop": "off", "no-bitwise": "off", + "no-await-in-loop": "off", "no-restricted-syntax": "off", - "no-console": ["warn", { "allow": ["info", "warn", "error"] }], + "no-console": ["warn", { "allow": ["info", "warn", "error", "log"] }], "@typescript-eslint/lines-between-class-members": "off", - "no-param-reassign": "off", - "@typescript-eslint/naming-convention": [ - "error", - { - "selector": "variable", - "format": ["camelCase", "PascalCase", "UPPER_CASE", "snake_case"], - "leadingUnderscore": "allow" - } - ] + "no-param-reassign": "off" } } diff --git a/packages/cli-template-monorepo-ethers/apps/contracts/.gitignore b/packages/cli-template-monorepo-ethers/apps/contracts/.gitignore new file mode 100644 index 000000000..e16e181ce --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/contracts/.gitignore @@ -0,0 +1,9 @@ +# Hardhat files +/cache +/artifacts + +# TypeChain files +/typechain +/typechain-types + + diff --git a/packages/cli-template-monorepo-ethers/apps/contracts/contracts/Feedback.sol b/packages/cli-template-monorepo-ethers/apps/contracts/contracts/Feedback.sol index 0c846db5a..0a418c4fe 100644 --- a/packages/cli-template-monorepo-ethers/apps/contracts/contracts/Feedback.sol +++ b/packages/cli-template-monorepo-ethers/apps/contracts/contracts/Feedback.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.23; import "@semaphore-protocol/contracts/interfaces/ISemaphore.sol"; @@ -12,7 +12,7 @@ contract Feedback { semaphore = ISemaphore(semaphoreAddress); groupId = _groupId; - semaphore.createGroup(groupId, 20, address(this)); + semaphore.createGroup(groupId, address(this)); } function joinGroup(uint256 identityCommitment) external { @@ -20,11 +20,21 @@ contract Feedback { } function sendFeedback( - uint256 feedback, + uint256 merkleTreeDepth, uint256 merkleTreeRoot, - uint256 nullifierHash, - uint256[8] calldata proof + uint256 nullifier, + uint256 feedback, + uint256[8] calldata points ) external { - semaphore.verifyProof(groupId, merkleTreeRoot, feedback, nullifierHash, groupId, proof); + ISemaphore.SemaphoreProof memory proof = ISemaphore.SemaphoreProof( + merkleTreeDepth, + merkleTreeRoot, + nullifier, + feedback, + groupId, + points + ); + + semaphore.validateProof(groupId, proof); } } diff --git a/packages/cli-template-monorepo-ethers/apps/contracts/hardhat.config.ts b/packages/cli-template-monorepo-ethers/apps/contracts/hardhat.config.ts index 4e862668c..f5b61eaee 100644 --- a/packages/cli-template-monorepo-ethers/apps/contracts/hardhat.config.ts +++ b/packages/cli-template-monorepo-ethers/apps/contracts/hardhat.config.ts @@ -1,15 +1,9 @@ -import "@nomicfoundation/hardhat-chai-matchers" -import "@nomiclabs/hardhat-ethers" -import "@nomiclabs/hardhat-etherscan" -import "@semaphore-protocol/hardhat" -import "@typechain/hardhat" -import { config as dotenvConfig } from "dotenv" -import "hardhat-gas-reporter" import { HardhatUserConfig } from "hardhat/config" import { NetworksUserConfig } from "hardhat/types" +import "@nomicfoundation/hardhat-toolbox" +import { config as dotenvConfig } from "dotenv" import { resolve } from "path" -import "solidity-coverage" -import { config } from "./package.json" +import "@semaphore-protocol/hardhat" import "./tasks/deploy" dotenvConfig({ path: resolve(__dirname, "../../.env") }) @@ -23,11 +17,6 @@ function getNetworks(): NetworksUserConfig { const infuraApiKey = process.env.INFURA_API_KEY return { - goerli: { - url: `https://goerli.infura.io/v3/${infuraApiKey}`, - chainId: 5, - accounts - }, sepolia: { url: `https://sepolia.infura.io/v3/${infuraApiKey}`, chainId: 11155111, @@ -38,14 +27,14 @@ function getNetworks(): NetworksUserConfig { chainId: 80001, accounts }, - "optimism-goerli": { - url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`, - chainId: 420, + "optimism-sepolia": { + url: `https://optimism-sepolia.infura.io/v3/${infuraApiKey}`, + chainId: 11155420, accounts }, - "arbitrum-goerli": { - url: "https://goerli-rollup.arbitrum.io/rpc", - chainId: 421613, + "arbitrum-sepolia": { + url: "https://sepolia-rollup.arbitrum.io/rpc", + chainId: 421614, accounts }, arbitrum: { @@ -56,14 +45,9 @@ function getNetworks(): NetworksUserConfig { } } -const hardhatConfig: HardhatUserConfig = { - solidity: config.solidity, - paths: { - sources: config.paths.contracts, - tests: config.paths.tests, - cache: config.paths.cache, - artifacts: config.paths.build.contracts - }, +const config: HardhatUserConfig = { + solidity: "0.8.23", + defaultNetwork: process.env.DEFAULT_NETWORK || "localhost", networks: { hardhat: { chainId: 1337 @@ -76,12 +60,14 @@ const hardhatConfig: HardhatUserConfig = { coinmarketcap: process.env.COINMARKETCAP_API_KEY }, typechain: { - outDir: config.paths.build.typechain, - target: "ethers-v5" + target: "ethers-v6" }, etherscan: { apiKey: process.env.ETHERSCAN_API_KEY + }, + sourcify: { + enabled: true } } -export default hardhatConfig +export default config diff --git a/packages/cli-template-monorepo-ethers/apps/contracts/package.json b/packages/cli-template-monorepo-ethers/apps/contracts/package.json index 4781efbdb..ffdcc3995 100644 --- a/packages/cli-template-monorepo-ethers/apps/contracts/package.json +++ b/packages/cli-template-monorepo-ethers/apps/contracts/package.json @@ -1,60 +1,54 @@ { "name": "monorepo-ethers-contracts", - "version": "1.0.0", - "private": true, - "main": "index.js", "scripts": { - "dev": "hardhat node & yarn compile && yarn deploy --network localhost", + "dev": "hardhat node & yarn deploy --network localhost", "compile": "hardhat compile", - "download:snark-artifacts": "hardhat run scripts/download-snark-artifacts.ts", "deploy": "yarn compile && hardhat deploy", - "test": "hardhat run scripts/download-snark-artifacts.ts && hardhat test", + "verify": "hardhat verify", + "test": "hardhat test", "test:report-gas": "REPORT_GAS=true hardhat test", "test:coverage": "hardhat coverage", "typechain": "hardhat typechain", - "lint": "solhint 'contracts/**/*.sol'" + "lint": "eslint . --ext .js,.ts && solhint 'contracts/**/*.sol'", + "prettier": "prettier -c --plugin=prettier-plugin-solidity .", + "prettier:write": "prettier -w --plugin=prettier-plugin-solidity ." }, "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^1.0.5", - "@nomiclabs/hardhat-ethers": "^2.0.0", - "@nomiclabs/hardhat-etherscan": "^3.1.7", - "@semaphore-protocol/group": "3.15.2", - "@semaphore-protocol/hardhat": "3.15.2", - "@semaphore-protocol/identity": "3.15.2", - "@semaphore-protocol/proof": "3.15.2", - "@typechain/ethers-v5": "^10.0.0", - "@typechain/hardhat": "^6.0.0", - "@types/chai": "^4.3.1", - "@types/download": "^8.0.1", - "@types/mocha": "^9.1.1", + "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", + "@nomicfoundation/hardhat-ethers": "^3.0.0", + "@nomicfoundation/hardhat-network-helpers": "^1.0.0", + "@nomicfoundation/hardhat-toolbox": "^4.0.0", + "@nomicfoundation/hardhat-verify": "^2.0.0", + "@semaphore-protocol/core": "4.0.0-alpha.8", + "@semaphore-protocol/hardhat": "4.0.0-alpha.8", + "@typechain/ethers-v6": "^0.5.0", + "@typechain/hardhat": "^9.0.0", + "@types/chai": "^4.2.0", + "@types/eslint": "^8", + "@types/mocha": ">=9.1.0", + "@types/node": "^20.11.20", + "@typescript-eslint/eslint-plugin": "^7.0.2", + "@typescript-eslint/parser": "^7.0.2", "chai": "^4.2.0", - "dotenv": "^14.3.2", - "download": "^8.0.0", - "ethers": "^5.0.0", - "hardhat": "^2.8.4", + "dotenv": "^16.4.5", + "eslint": "^8.57.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.1.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.29.1", + "ethers": "^6.4.0", + "hardhat": "^2.20.1", "hardhat-gas-reporter": "^1.0.8", - "prettier-plugin-solidity": "^1.0.0-beta.19", - "solhint": "^3.3.6", - "solhint-plugin-prettier": "^0.0.5", - "solidity-coverage": "^0.7.21", - "typechain": "^8.0.0" + "prettier": "^3.2.5", + "prettier-plugin-solidity": "^1.3.1", + "solhint": "^4.1.1", + "solidity-coverage": "^0.8.1", + "ts-node": "^10.9.2", + "typechain": "^8.3.0", + "typescript": "^5.3.3" }, "dependencies": { - "@semaphore-protocol/contracts": "3.15.2" + "@semaphore-protocol/contracts": "4.0.0-alpha.8" }, - "config": { - "solidity": { - "version": "0.8.4" - }, - "paths": { - "contracts": "./contracts", - "tests": "./test", - "cache": "./cache", - "build": { - "snark-artifacts": "./build/snark-artifacts", - "contracts": "./build/contracts", - "typechain": "./build/typechain" - } - } - } + "packageManager": "yarn@4.1.0" } diff --git a/packages/cli-template-monorepo-ethers/apps/contracts/scripts/download-snark-artifacts.ts b/packages/cli-template-monorepo-ethers/apps/contracts/scripts/download-snark-artifacts.ts deleted file mode 100644 index 6051397e5..000000000 --- a/packages/cli-template-monorepo-ethers/apps/contracts/scripts/download-snark-artifacts.ts +++ /dev/null @@ -1,24 +0,0 @@ -import download from "download" -import fs from "fs" -import { config } from "../package.json" - -async function main() { - const snarkArtifactsPath = config.paths.build["snark-artifacts"] - const url = `http://www.trusted-setup-pse.org/semaphore/${20}` - - if (!fs.existsSync(snarkArtifactsPath)) { - fs.mkdirSync(snarkArtifactsPath, { recursive: true }) - } - - if (!fs.existsSync(`${snarkArtifactsPath}/semaphore.zkey`)) { - await download(`${url}/semaphore.wasm`, snarkArtifactsPath) - await download(`${url}/semaphore.zkey`, snarkArtifactsPath) - } -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error) - process.exit(1) - }) diff --git a/packages/cli-template-monorepo-ethers/apps/contracts/tasks/deploy.ts b/packages/cli-template-monorepo-ethers/apps/contracts/tasks/deploy.ts index 85b43377e..0dd175e40 100644 --- a/packages/cli-template-monorepo-ethers/apps/contracts/tasks/deploy.ts +++ b/packages/cli-template-monorepo-ethers/apps/contracts/tasks/deploy.ts @@ -10,7 +10,7 @@ task("deploy", "Deploy a Feedback contract") logs }) - semaphoreAddress = semaphore.address + semaphoreAddress = await semaphore.getAddress() } if (!groupId) { @@ -21,10 +21,8 @@ task("deploy", "Deploy a Feedback contract") const feedbackContract = await FeedbackFactory.deploy(semaphoreAddress, groupId) - await feedbackContract.deployed() - if (logs) { - console.info(`Feedback contract has been deployed to: ${feedbackContract.address}`) + console.info(`Feedback contract has been deployed to: ${await feedbackContract.getAddress()}`) } return feedbackContract diff --git a/packages/cli-template-monorepo-ethers/apps/contracts/test/Feedback.ts b/packages/cli-template-monorepo-ethers/apps/contracts/test/Feedback.ts index 08227e215..7733e3b86 100644 --- a/packages/cli-template-monorepo-ethers/apps/contracts/test/Feedback.ts +++ b/packages/cli-template-monorepo-ethers/apps/contracts/test/Feedback.ts @@ -1,38 +1,41 @@ -import { Group } from "@semaphore-protocol/group" -import { Identity } from "@semaphore-protocol/identity" -import { generateProof } from "@semaphore-protocol/proof" +import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers" +import { Group, Identity, generateProof } from "@semaphore-protocol/core" import { expect } from "chai" -import { formatBytes32String } from "ethers/lib/utils" +import { encodeBytes32String } from "ethers" import { run } from "hardhat" // @ts-ignore: typechain folder will be generated after contracts compilation -import { Feedback } from "../build/typechain" -import { config } from "../package.json" +// eslint-disable-next-line +import { Feedback, ISemaphore } from "../typechain-types" describe("Feedback", () => { - let feedbackContract: Feedback - let semaphoreContract: string + async function deployFeedbackFixture() { + const groupId = "42" - const groupId = "42" - const group = new Group(groupId) - const users: Identity[] = [] - - before(async () => { const { semaphore } = await run("deploy:semaphore", { logs: false }) - feedbackContract = await run("deploy", { logs: false, group: groupId, semaphore: semaphore.address }) - semaphoreContract = semaphore + const semaphoreContract: ISemaphore = semaphore - users.push(new Identity()) - users.push(new Identity()) - }) + const feedbackContract: Feedback = await run("deploy", { + logs: false, + group: groupId, + semaphore: await semaphoreContract.getAddress() + }) + + return { semaphoreContract, feedbackContract, groupId } + } describe("# joinGroup", () => { it("Should allow users to join the group", async () => { - for await (const [i, user] of users.entries()) { - const transaction = feedbackContract.joinGroup(user.commitment) + const { semaphoreContract, feedbackContract, groupId } = await loadFixture(deployFeedbackFixture) + + const users = [new Identity(), new Identity()] + + const group = new Group() + for (const [i, user] of users.entries()) { + const transaction = await feedbackContract.joinGroup(user.commitment) group.addMember(user.commitment) await expect(transaction) @@ -43,27 +46,40 @@ describe("Feedback", () => { }) describe("# sendFeedback", () => { - const wasmFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.wasm` - const zkeyFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.zkey` - it("Should allow users to send feedback anonymously", async () => { - const feedback = formatBytes32String("Hello World") + const { semaphoreContract, feedbackContract, groupId } = await loadFixture(deployFeedbackFixture) + + const users = [new Identity(), new Identity()] + const group = new Group() + + for (const user of users) { + await feedbackContract.joinGroup(user.commitment) + group.addMember(user.commitment) + } + + const feedback = encodeBytes32String("Hello World") - const fullProof = await generateProof(users[1], group, groupId, feedback, { - wasmFilePath, - zkeyFilePath - }) + const proof = await generateProof(users[1], group, feedback, groupId) const transaction = feedbackContract.sendFeedback( + proof.merkleTreeDepth, + proof.merkleTreeRoot, + proof.nullifier, feedback, - fullProof.merkleTreeRoot, - fullProof.nullifierHash, - fullProof.proof + proof.points ) await expect(transaction) - .to.emit(semaphoreContract, "ProofVerified") - .withArgs(groupId, fullProof.merkleTreeRoot, fullProof.nullifierHash, groupId, fullProof.signal) + .to.emit(semaphoreContract, "ProofValidated") + .withArgs( + groupId, + proof.merkleTreeDepth, + proof.merkleTreeRoot, + proof.nullifier, + proof.message, + groupId, + proof.points + ) }) }) }) diff --git a/packages/cli-template-monorepo-ethers/apps/contracts/tsconfig.json b/packages/cli-template-monorepo-ethers/apps/contracts/tsconfig.json index 5a3f0f784..a702753ac 100644 --- a/packages/cli-template-monorepo-ethers/apps/contracts/tsconfig.json +++ b/packages/cli-template-monorepo-ethers/apps/contracts/tsconfig.json @@ -1,15 +1,13 @@ { "compilerOptions": { - "moduleResolution": "Node", - "noImplicitAny": true, - "resolveJsonModule": true, - "target": "ES2018", - "module": "CommonJS", - "strict": true, + "target": "es2020", + "module": "commonjs", "esModuleInterop": true, - "outDir": "dist", - "typeRoots": ["node_modules/@types", "types"] + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true }, - "include": ["scripts/**/*", "tasks/**/*", "test/**/*", "build/typechain/**/*", "types/**/*"], + "include": ["scripts/**/*", "tasks/**/*", "test/**/*", "typechain-types/**/*"], "files": ["./hardhat.config.ts"] } diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/.env.development b/packages/cli-template-monorepo-ethers/apps/web-app/.env.development new file mode 100644 index 000000000..afa315696 --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/web-app/.env.development @@ -0,0 +1,4 @@ +NEXT_PUBLIC_DEFAULT_NETWORK=localhost +NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 +NEXT_PUBLIC_SEMAPHORE_CONTRACT_ADDRESS=0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 +NEXT_PUBLIC_GROUP_ID=42 diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/.env.production b/packages/cli-template-monorepo-ethers/apps/web-app/.env.production new file mode 100644 index 000000000..1236cd0a8 --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/web-app/.env.production @@ -0,0 +1,5 @@ +NEXT_PUBLIC_DEFAULT_NETWORK=sepolia +NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS=0xC6446BB589E141fFACeaCd91953CbB0F2055C7e5 +NEXT_PUBLIC_SEMAPHORE_CONTRACT_ADDRESS=0x5B8e7cC7bAC61A4b952d472b67056B2f260ba6dc +NEXT_PUBLIC_OPENZEPPELIN_AUTOTASK_WEBHOOK=https://api.defender.openzeppelin.com/actions/20fce2ae-844b-4ec0-a6a2-90a3350a9d2c/runs/webhook/303216d1-fa7d-4fca-8c5b-7ba1ba544fc7/2T7i9xrkZA5j37hoaQLUuw +NEXT_PUBLIC_GROUP_ID=9 diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/.eslintrc.json b/packages/cli-template-monorepo-ethers/apps/web-app/.eslintrc.json new file mode 100644 index 000000000..1c2aa65d7 --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/web-app/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/.gitignore b/packages/cli-template-monorepo-ethers/apps/web-app/.gitignore new file mode 100644 index 000000000..d183c4f53 --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/web-app/.gitignore @@ -0,0 +1,9 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# next.js +/.next/ +/out/ + +# production +/build + diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/contract-artifacts/Feedback.json b/packages/cli-template-monorepo-ethers/apps/web-app/contract-artifacts/Feedback.json index 14c2f7e88..123448e9e 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/contract-artifacts/Feedback.json +++ b/packages/cli-template-monorepo-ethers/apps/web-app/contract-artifacts/Feedback.json @@ -62,7 +62,7 @@ "inputs": [ { "internalType": "uint256", - "name": "feedback", + "name": "merkleTreeDepth", "type": "uint256" }, { @@ -72,12 +72,17 @@ }, { "internalType": "uint256", - "name": "nullifierHash", + "name": "nullifier", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feedback", "type": "uint256" }, { "internalType": "uint256[8]", - "name": "proof", + "name": "points", "type": "uint256[8]" } ], @@ -87,8 +92,8 @@ "type": "function" } ], - "bytecode": "0x608060405234801561001057600080fd5b506040516106e13803806106e18339818101604052810190610032919061013c565b816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060018190555060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16639c1121416001546014306040518463ffffffff1660e01b81526004016100d9939291906101a5565b600060405180830381600087803b1580156100f357600080fd5b505af1158015610107573d6000803e3d6000fd5b505050505050610258565b6000815190506101218161022a565b92915050565b60008151905061013681610241565b92915050565b6000806040838503121561014f57600080fd5b600061015d85828601610112565b925050602061016e85828601610127565b9150509250929050565b610181816101dc565b82525050565b61019081610218565b82525050565b61019f8161020e565b82525050565b60006060820190506101ba6000830186610196565b6101c76020830185610187565b6101d46040830184610178565b949350505050565b60006101e7826101ee565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102238261020e565b9050919050565b610233816101dc565b811461023e57600080fd5b50565b61024a8161020e565b811461025557600080fd5b50565b61047a806102676000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d253414610051578063a0f44c921461006f578063d18ed1e91461008d578063eed02e4b146100a9575b600080fd5b6100596100c5565b604051610066919061030f565b60405180910390f35b6100776100e9565b604051610084919061032a565b60405180910390f35b6100a760048036038101906100a2919061027c565b6100ef565b005b6100c360048036038101906100be9190610253565b61018e565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16633bc778e3600154858786600154876040518763ffffffff1660e01b81526004016101569695949392919061036e565b600060405180830381600087803b15801561017057600080fd5b505af1158015610184573d6000803e3d6000fd5b5050505050505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b81526004016101eb929190610345565b600060405180830381600087803b15801561020557600080fd5b505af1158015610219573d6000803e3d6000fd5b5050505050565b60008190508260206008028201111561023857600080fd5b92915050565b60008135905061024d8161042d565b92915050565b60006020828403121561026557600080fd5b60006102738482850161023e565b91505092915050565b600080600080610160858703121561029357600080fd5b60006102a18782880161023e565b94505060206102b28782880161023e565b93505060406102c38782880161023e565b92505060606102d487828801610220565b91505092959194509250565b6102ed610100838361041e565b5050565b6102fa816103fa565b82525050565b610309816103f0565b82525050565b600060208201905061032460008301846102f1565b92915050565b600060208201905061033f6000830184610300565b92915050565b600060408201905061035a6000830185610300565b6103676020830184610300565b9392505050565b60006101a0820190506103846000830189610300565b6103916020830188610300565b61039e6040830187610300565b6103ab6060830186610300565b6103b86080830185610300565b6103c560a08301846102e0565b979650505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006104058261040c565b9050919050565b6000610417826103d0565b9050919050565b82818337600083830152505050565b610436816103f0565b811461044157600080fd5b5056fea26469706673582212204d8dc3161abc759242364c3a754a86e5eb8653092bcdf1e20bd6fcd368e1997664736f6c63430008040033", - "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d253414610051578063a0f44c921461006f578063d18ed1e91461008d578063eed02e4b146100a9575b600080fd5b6100596100c5565b604051610066919061030f565b60405180910390f35b6100776100e9565b604051610084919061032a565b60405180910390f35b6100a760048036038101906100a2919061027c565b6100ef565b005b6100c360048036038101906100be9190610253565b61018e565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16633bc778e3600154858786600154876040518763ffffffff1660e01b81526004016101569695949392919061036e565b600060405180830381600087803b15801561017057600080fd5b505af1158015610184573d6000803e3d6000fd5b5050505050505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b81526004016101eb929190610345565b600060405180830381600087803b15801561020557600080fd5b505af1158015610219573d6000803e3d6000fd5b5050505050565b60008190508260206008028201111561023857600080fd5b92915050565b60008135905061024d8161042d565b92915050565b60006020828403121561026557600080fd5b60006102738482850161023e565b91505092915050565b600080600080610160858703121561029357600080fd5b60006102a18782880161023e565b94505060206102b28782880161023e565b93505060406102c38782880161023e565b92505060606102d487828801610220565b91505092959194509250565b6102ed610100838361041e565b5050565b6102fa816103fa565b82525050565b610309816103f0565b82525050565b600060208201905061032460008301846102f1565b92915050565b600060208201905061033f6000830184610300565b92915050565b600060408201905061035a6000830185610300565b6103676020830184610300565b9392505050565b60006101a0820190506103846000830189610300565b6103916020830188610300565b61039e6040830187610300565b6103ab6060830186610300565b6103b86080830185610300565b6103c560a08301846102e0565b979650505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006104058261040c565b9050919050565b6000610417826103d0565b9050919050565b82818337600083830152505050565b610436816103f0565b811461044157600080fd5b5056fea26469706673582212204d8dc3161abc759242364c3a754a86e5eb8653092bcdf1e20bd6fcd368e1997664736f6c63430008040033", + "bytecode": "0x608060405234801561001057600080fd5b5060405161083f38038061083f833981810160405281019061003291906101a8565b816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060018190555060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c96e71fb600154306040518363ffffffff1660e01b81526004016100d6929190610206565b600060405180830381600087803b1580156100f057600080fd5b505af1158015610104573d6000803e3d6000fd5b50505050505061022f565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061013f82610114565b9050919050565b61014f81610134565b811461015a57600080fd5b50565b60008151905061016c81610146565b92915050565b6000819050919050565b61018581610172565b811461019057600080fd5b50565b6000815190506101a28161017c565b92915050565b600080604083850312156101bf576101be61010f565b5b60006101cd8582860161015d565b92505060206101de85828601610193565b9150509250929050565b6101f181610172565b82525050565b61020081610134565b82525050565b600060408201905061021b60008301856101e8565b61022860208301846101f7565b9392505050565b6106018061023e6000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d2534146100515780637b85d27a1461006f578063a0f44c921461008b578063eed02e4b146100a9575b600080fd5b6100596100c5565b6040516100669190610301565b60405180910390f35b6100896004803603810190610084919061037e565b6100e9565b005b6100936101ea565b6040516100a09190610409565b60405180910390f35b6100c360048036038101906100be9190610424565b6101f0565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60006040518060c001604052808781526020018681526020018581526020018481526020016001548152602001836008806020026040519081016040528092919082600860200280828437600081840152601f19601f820116905080830192505050505050815250905060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0d898dd600154836040518363ffffffff1660e01b81526004016101b0929190610578565b600060405180830381600087803b1580156101ca57600080fd5b505af11580156101de573d6000803e3d6000fd5b50505050505050505050565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b815260040161024d9291906105a2565b600060405180830381600087803b15801561026757600080fd5b505af115801561027b573d6000803e3d6000fd5b5050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102c76102c26102bd84610282565b6102a2565b610282565b9050919050565b60006102d9826102ac565b9050919050565b60006102eb826102ce565b9050919050565b6102fb816102e0565b82525050565b600060208201905061031660008301846102f2565b92915050565b600080fd5b6000819050919050565b61033481610321565b811461033f57600080fd5b50565b6000813590506103518161032b565b92915050565b600080fd5b60008190508260206008028201111561037857610377610357565b5b92915050565b6000806000806000610180868803121561039b5761039a61031c565b5b60006103a988828901610342565b95505060206103ba88828901610342565b94505060406103cb88828901610342565b93505060606103dc88828901610342565b92505060806103ed8882890161035c565b9150509295509295909350565b61040381610321565b82525050565b600060208201905061041e60008301846103fa565b92915050565b60006020828403121561043a5761043961031c565b5b600061044884828501610342565b91505092915050565b61045a81610321565b82525050565b600060089050919050565b600081905092915050565b6000819050919050565b600061048c8383610451565b60208301905092915050565b6000602082019050919050565b6104ae81610460565b6104b8818461046b565b92506104c382610476565b8060005b838110156104f45781516104db8782610480565b96506104e683610498565b9250506001810190506104c7565b505050505050565b6101a0820160008201516105136000850182610451565b5060208201516105266020850182610451565b5060408201516105396040850182610451565b50606082015161054c6060850182610451565b50608082015161055f6080850182610451565b5060a082015161057260a08501826104a5565b50505050565b60006101c08201905061058e60008301856103fa565b61059b60208301846104fc565b9392505050565b60006040820190506105b760008301856103fa565b6105c460208301846103fa565b939250505056fea2646970667358221220f33606b2d5ad7c0dfc5d22afb43476e1974ea7fd160e1f28203a3e433f29cb4964736f6c63430008170033", + "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d2534146100515780637b85d27a1461006f578063a0f44c921461008b578063eed02e4b146100a9575b600080fd5b6100596100c5565b6040516100669190610301565b60405180910390f35b6100896004803603810190610084919061037e565b6100e9565b005b6100936101ea565b6040516100a09190610409565b60405180910390f35b6100c360048036038101906100be9190610424565b6101f0565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60006040518060c001604052808781526020018681526020018581526020018481526020016001548152602001836008806020026040519081016040528092919082600860200280828437600081840152601f19601f820116905080830192505050505050815250905060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0d898dd600154836040518363ffffffff1660e01b81526004016101b0929190610578565b600060405180830381600087803b1580156101ca57600080fd5b505af11580156101de573d6000803e3d6000fd5b50505050505050505050565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b815260040161024d9291906105a2565b600060405180830381600087803b15801561026757600080fd5b505af115801561027b573d6000803e3d6000fd5b5050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102c76102c26102bd84610282565b6102a2565b610282565b9050919050565b60006102d9826102ac565b9050919050565b60006102eb826102ce565b9050919050565b6102fb816102e0565b82525050565b600060208201905061031660008301846102f2565b92915050565b600080fd5b6000819050919050565b61033481610321565b811461033f57600080fd5b50565b6000813590506103518161032b565b92915050565b600080fd5b60008190508260206008028201111561037857610377610357565b5b92915050565b6000806000806000610180868803121561039b5761039a61031c565b5b60006103a988828901610342565b95505060206103ba88828901610342565b94505060406103cb88828901610342565b93505060606103dc88828901610342565b92505060806103ed8882890161035c565b9150509295509295909350565b61040381610321565b82525050565b600060208201905061041e60008301846103fa565b92915050565b60006020828403121561043a5761043961031c565b5b600061044884828501610342565b91505092915050565b61045a81610321565b82525050565b600060089050919050565b600081905092915050565b6000819050919050565b600061048c8383610451565b60208301905092915050565b6000602082019050919050565b6104ae81610460565b6104b8818461046b565b92506104c382610476565b8060005b838110156104f45781516104db8782610480565b96506104e683610498565b9250506001810190506104c7565b505050505050565b6101a0820160008201516105136000850182610451565b5060208201516105266020850182610451565b5060408201516105396040850182610451565b50606082015161054c6060850182610451565b50608082015161055f6080850182610451565b5060a082015161057260a08501826104a5565b50505050565b60006101c08201905061058e60008301856103fa565b61059b60208301846104fc565b9392505050565b60006040820190506105b760008301856103fa565b6105c460208301846103fa565b939250505056fea2646970667358221220f33606b2d5ad7c0dfc5d22afb43476e1974ea7fd160e1f28203a3e433f29cb4964736f6c63430008170033", "linkReferences": {}, "deployedLinkReferences": {} } diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/next.config.js b/packages/cli-template-monorepo-ethers/apps/web-app/next.config.js deleted file mode 100644 index 4a0f3e9f8..000000000 --- a/packages/cli-template-monorepo-ethers/apps/web-app/next.config.js +++ /dev/null @@ -1,45 +0,0 @@ -/** @type {import('next').NextConfig} */ - -const fs = require("fs") -const withPWA = require("next-pwa") - -if (!fs.existsSync("./.env")) { - // eslint-disable-next-line global-require - require("dotenv").config({ path: "../../.env" }) -} - -const nextConfig = withPWA({ - dest: "public", - disable: process.env.NODE_ENV === "development" -})({ - eslint: { - ignoreDuringBuilds: true - }, - reactStrictMode: true, - swcMinify: true, - env: { - DEFAULT_NETWORK: process.env.DEFAULT_NETWORK, - INFURA_API_KEY: process.env.INFURA_API_KEY, - ETHEREUM_PRIVATE_KEY: process.env.ETHEREUM_PRIVATE_KEY, - FEEDBACK_CONTRACT_ADDRESS: process.env.FEEDBACK_CONTRACT_ADDRESS, - SEMAPHORE_CONTRACT_ADDRESS: process.env.SEMAPHORE_CONTRACT_ADDRESS - }, - publicRuntimeConfig: { - DEFAULT_NETWORK: process.env.DEFAULT_NETWORK, - FEEDBACK_CONTRACT_ADDRESS: process.env.FEEDBACK_CONTRACT_ADDRESS, - SEMAPHORE_CONTRACT_ADDRESS: process.env.SEMAPHORE_CONTRACT_ADDRESS, - OPENZEPPELIN_AUTOTASK_WEBHOOK: process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK, - GROUP_ID: process.env.GROUP_ID - }, - webpack: (config, { isServer }) => { - if (!isServer) { - config.resolve.fallback = { - fs: false - } - } - - return config - } -}) - -module.exports = nextConfig diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/next.config.mjs b/packages/cli-template-monorepo-ethers/apps/web-app/next.config.mjs new file mode 100644 index 000000000..2233f10f5 --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/web-app/next.config.mjs @@ -0,0 +1,21 @@ +/** @type {import('next').NextConfig} */ + +import withPWA from "next-pwa" +import fs from "fs" +import { config } from "dotenv" + +if (!fs.existsSync("./.env")) { + config({ path: "../../.env" }) +} + +const nextConfig = withPWA({ + dest: "public", + disable: process.env.NODE_ENV === "development" +})({ + env: { + INFURA_API_KEY: process.env.INFURA_API_KEY, + ETHEREUM_PRIVATE_KEY: process.env.ETHEREUM_PRIVATE_KEY + } +}) + +export default nextConfig diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/package.json b/packages/cli-template-monorepo-ethers/apps/web-app/package.json index 8a85d6c6f..9d8d5d0ac 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/package.json +++ b/packages/cli-template-monorepo-ethers/apps/web-app/package.json @@ -1,27 +1,29 @@ { "name": "monorepo-ethers-web-app", - "version": "1.0.0", + "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", - "export": "next export", - "start": "next start" + "start": "next start", + "lint": "next lint" }, "dependencies": { - "@next/font": "13.0.3", - "@semaphore-protocol/data": "3.15.2", - "@semaphore-protocol/group": "3.15.2", - "@semaphore-protocol/identity": "3.15.2", - "@semaphore-protocol/proof": "3.15.2", - "@types/react": "18.0.25", - "@types/react-dom": "18.0.8", - "dotenv": "^16.0.3", - "ethers": "^5.7.2", - "next": "13.0.3", + "@semaphore-protocol/core": "4.0.0-alpha.8", + "@semaphore-protocol/data": "4.0.0-alpha.8", + "ethers": "^6.11.1", + "next": "14.1.0", "next-pwa": "^5.6.0", - "react": "18.2.0", - "react-dom": "18.2.0", - "typescript": "^4.7.3" + "react": "^18", + "react-dom": "^18" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "dotenv": "^16.4.5", + "eslint": "^8", + "eslint-config-next": "14.1.0", + "typescript": "^5" } } diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/apple-icon.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/apple-icon.png new file mode 100644 index 000000000..ed4cb2bca Binary files /dev/null and b/packages/cli-template-monorepo-ethers/apps/web-app/public/apple-icon.png differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/apple-touch-icon.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/apple-touch-icon.png deleted file mode 100644 index 52a950aa1..000000000 Binary files a/packages/cli-template-monorepo-ethers/apps/web-app/public/apple-touch-icon.png and /dev/null differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/favicon-16x16.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/favicon-16x16.png deleted file mode 100644 index 6eff30957..000000000 Binary files a/packages/cli-template-monorepo-ethers/apps/web-app/public/favicon-16x16.png and /dev/null differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/favicon-32x32.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/favicon-32x32.png deleted file mode 100644 index ac7812ee1..000000000 Binary files a/packages/cli-template-monorepo-ethers/apps/web-app/public/favicon-32x32.png and /dev/null differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/favicon.ico b/packages/cli-template-monorepo-ethers/apps/web-app/public/favicon.ico deleted file mode 100644 index c695649f6..000000000 Binary files a/packages/cli-template-monorepo-ethers/apps/web-app/public/favicon.ico and /dev/null differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/icon-192x192.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/icon-192x192.png deleted file mode 100644 index 01cf07e55..000000000 Binary files a/packages/cli-template-monorepo-ethers/apps/web-app/public/icon-192x192.png and /dev/null differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/icon-256x256.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/icon-256x256.png deleted file mode 100644 index f318cb420..000000000 Binary files a/packages/cli-template-monorepo-ethers/apps/web-app/public/icon-256x256.png and /dev/null differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/icon-384x384.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/icon-384x384.png deleted file mode 100644 index 4eeb5d596..000000000 Binary files a/packages/cli-template-monorepo-ethers/apps/web-app/public/icon-384x384.png and /dev/null differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/icon-512x512.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/icon-512x512.png deleted file mode 100644 index d553ca2b2..000000000 Binary files a/packages/cli-template-monorepo-ethers/apps/web-app/public/icon-512x512.png and /dev/null differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/icon.svg b/packages/cli-template-monorepo-ethers/apps/web-app/public/icon.svg new file mode 100644 index 000000000..4d16a5525 --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/web-app/public/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/favicon-16x16.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/favicon-16x16.png new file mode 100644 index 000000000..1fd837b2f Binary files /dev/null and b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/favicon-16x16.png differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/favicon-32x32.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/favicon-32x32.png new file mode 100644 index 000000000..cc97d75ee Binary files /dev/null and b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/favicon-32x32.png differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/favicon.ico b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/favicon.ico new file mode 100644 index 000000000..dc8672670 Binary files /dev/null and b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/favicon.ico differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-128x128.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-128x128.png new file mode 100644 index 000000000..27e0c96f3 Binary files /dev/null and b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-128x128.png differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-144x144.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-144x144.png new file mode 100644 index 000000000..0054e006f Binary files /dev/null and b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-144x144.png differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-152x152.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-152x152.png new file mode 100644 index 000000000..9d1b190b1 Binary files /dev/null and b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-152x152.png differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-192x192.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-192x192.png new file mode 100644 index 000000000..9bd7da0fd Binary files /dev/null and b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-192x192.png differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-512x512.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-512x512.png new file mode 100644 index 000000000..ce0738332 Binary files /dev/null and b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-512x512.png differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-72x72.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-72x72.png new file mode 100644 index 000000000..6d799c7cb Binary files /dev/null and b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-72x72.png differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-96x96.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-96x96.png new file mode 100644 index 000000000..dda5492a6 Binary files /dev/null and b/packages/cli-template-monorepo-ethers/apps/web-app/public/images/icons/icon-96x96.png differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/manifest.json b/packages/cli-template-monorepo-ethers/apps/web-app/public/manifest.json index 3da3e4d06..76af03744 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/public/manifest.json +++ b/packages/cli-template-monorepo-ethers/apps/web-app/public/manifest.json @@ -1,29 +1,44 @@ { - "theme_color": "#ebedff", - "background_color": "#ebedff", + "name": "Semaphore Boilerplate", + "short_name": "Semaphore", + "theme_color": "#4771ea", + "background_color": "#00020D", "display": "standalone", "scope": "/", "start_url": "/", - "name": "Semaphore Boilerplate", - "short_name": "Semaphore", "icons": [ { - "src": "/icon-192x192.png", - "sizes": "192x192", + "src": "images/icons/icon-72x72.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "images/icons/icon-96x96.png", + "sizes": "96x96", "type": "image/png" }, { - "src": "/icon-256x256.png", - "sizes": "256x256", + "src": "images/icons/icon-128x128.png", + "sizes": "128x128", "type": "image/png" }, { - "src": "/icon-384x384.png", - "sizes": "384x384", + "src": "images/icons/icon-144x144.png", + "sizes": "144x144", + "type": "image/png" + }, + { + "src": "images/icons/icon-152x152.png", + "sizes": "152x152", + "type": "image/png" + }, + { + "src": "images/icons/icon-192x192.png", + "sizes": "192x192", "type": "image/png" }, { - "src": "/icon-512x512.png", + "src": "images/icons/icon-512x512.png", "sizes": "512x512", "type": "image/png" } diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/semaphore-icon.svg b/packages/cli-template-monorepo-ethers/apps/web-app/public/semaphore-icon.svg new file mode 100644 index 000000000..11db9b12b --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/web-app/public/semaphore-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/semaphore-logo.svg b/packages/cli-template-monorepo-ethers/apps/web-app/public/semaphore-logo.svg new file mode 100644 index 000000000..82ab73c3e --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/web-app/public/semaphore-logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/public/social-media.png b/packages/cli-template-monorepo-ethers/apps/web-app/public/social-media.png new file mode 100644 index 000000000..3ad281151 Binary files /dev/null and b/packages/cli-template-monorepo-ethers/apps/web-app/public/social-media.png differ diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/api/feedback/route.ts b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/api/feedback/route.ts new file mode 100644 index 000000000..795b2c5dd --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/api/feedback/route.ts @@ -0,0 +1,50 @@ +import { Contract, InfuraProvider, JsonRpcProvider, Wallet } from "ethers" +import { NextRequest } from "next/server" +import Feedback from "../../../../contract-artifacts/Feedback.json" + +export async function POST(req: NextRequest) { + if (typeof process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS !== "string") { + throw new Error("Please, define NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS in your .env file") + } + + if (typeof process.env.NEXT_PUBLIC_DEFAULT_NETWORK !== "string") { + throw new Error("Please, define NEXT_PUBLIC_DEFAULT_NETWORK in your .env file") + } + + if (typeof process.env.INFURA_API_KEY !== "string" && process.env.NEXT_PUBLIC_DEFAULT_NETWORK !== "localhost") { + throw new Error("Please, define INFURA_API_KEY in your .env file") + } + + if (typeof process.env.ETHEREUM_PRIVATE_KEY !== "string") { + throw new Error("Please, define ETHEREUM_PRIVATE_KEY in your .env file") + } + + const ethereumPrivateKey = process.env.ETHEREUM_PRIVATE_KEY + const ethereumNetwork = process.env.NEXT_PUBLIC_DEFAULT_NETWORK + const infuraApiKey = process.env.INFURA_API_KEY + const contractAddress = process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS + + const provider = + ethereumNetwork === "localhost" + ? new JsonRpcProvider("http://127.0.0.1:8545") + : new InfuraProvider(ethereumNetwork, infuraApiKey) + + const signer = new Wallet(ethereumPrivateKey, provider) + const contract = new Contract(contractAddress, Feedback.abi, signer) + + const { feedback, merkleTreeDepth, merkleTreeRoot, nullifier, points } = await req.json() + + try { + const transaction = await contract.sendFeedback(merkleTreeDepth, merkleTreeRoot, nullifier, feedback, points) + + await transaction.wait() + + return new Response("Success", { status: 200 }) + } catch (error: any) { + console.error(error) + + return new Response(`Server error: ${error}`, { + status: 500 + }) + } +} diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/api/join/route.ts b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/api/join/route.ts new file mode 100644 index 000000000..608055f9e --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/api/join/route.ts @@ -0,0 +1,50 @@ +import { Contract, InfuraProvider, JsonRpcProvider, Wallet } from "ethers" +import { NextRequest } from "next/server" +import Feedback from "../../../../contract-artifacts/Feedback.json" + +export async function POST(req: NextRequest) { + if (typeof process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS !== "string") { + throw new Error("Please, define NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS in your .env file") + } + + if (typeof process.env.NEXT_PUBLIC_DEFAULT_NETWORK !== "string") { + throw new Error("Please, define NEXT_PUBLIC_DEFAULT_NETWORK in your .env file") + } + + if (typeof process.env.INFURA_API_KEY !== "string" && process.env.NEXT_PUBLIC_DEFAULT_NETWORK !== "localhost") { + throw new Error("Please, define INFURA_API_KEY in your .env file") + } + + if (typeof process.env.ETHEREUM_PRIVATE_KEY !== "string") { + throw new Error("Please, define ETHEREUM_PRIVATE_KEY in your .env file") + } + + const ethereumPrivateKey = process.env.ETHEREUM_PRIVATE_KEY + const ethereumNetwork = process.env.NEXT_PUBLIC_DEFAULT_NETWORK + const infuraApiKey = process.env.INFURA_API_KEY + const contractAddress = process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS + + const provider = + ethereumNetwork === "localhost" + ? new JsonRpcProvider("http://127.0.0.1:8545") + : new InfuraProvider(ethereumNetwork, infuraApiKey) + + const signer = new Wallet(ethereumPrivateKey, provider) + const contract = new Contract(contractAddress, Feedback.abi, signer) + + const { identityCommitment } = await req.json() + + try { + const transaction = await contract.joinGroup(identityCommitment) + + await transaction.wait() + + return new Response("Success", { status: 200 }) + } catch (error: any) { + console.error(error) + + return new Response(`Server error: ${error}`, { + status: 500 + }) + } +} diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/styles/globals.css b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/globals.css similarity index 74% rename from packages/cli-template-monorepo-subgraph/apps/web-app/src/styles/globals.css rename to packages/cli-template-monorepo-ethers/apps/web-app/src/app/globals.css index 5b95a69a2..505ae8579 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/styles/globals.css +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/globals.css @@ -1,12 +1,17 @@ :root { --slate100: #f1f5f9; + --slate200: #e2e8f0; --slate300: #cbd5e1; --slate400: #94a3b8; + --slate500: #64748b; --slate700: #334155; --blue200: #bfdbfe; + --blue500: #3b82f6; --blue600: #2563eb; + --blue700: #1d4ed8; --blue800: #1e40af; --blue900: #1e3a8a; + --darkBlueBg: #00020d; } * { @@ -22,7 +27,8 @@ body { } body { - color: var(--slate700); + background-color: var(--darkBlueBg); + color: var(--slate200); } p { @@ -61,18 +67,18 @@ li { h2 { font-size: 2.25rem; - font-weight: 700; + font-weight: 500; margin-bottom: 1rem; } h3 { font-size: 1.125rem; - font-weight: 700; + font-weight: 500; } .divider { height: 1px; - background: var(--slate400); + background: var(--slate500); margin: 2rem 0; } @@ -83,25 +89,26 @@ h3 { } a { - color: var(--blue600); + color: var(--blue500); } .button { background-color: var(--blue800); width: 100%; padding: 0.8rem 1rem; - border-radius: 4px; + border: 1px; + border-radius: 100px; cursor: pointer; color: var(--slate100); font-size: 1.125rem; - font-weight: 700; + font-weight: 500; transition: all 200ms linear; margin-top: 1rem; margin-bottom: 1.5rem; opacity: 0.9; display: flex; - justify-content: space-between; - height: 3.5rem; + justify-content: center; + height: 3rem; align-items: center; } @@ -120,7 +127,7 @@ a { .button-stepper { cursor: pointer; - color: var(--blue900); + color: var(--blue600); font-size: 1.1rem; border: none; background: none; @@ -128,7 +135,7 @@ a { .button-link { cursor: pointer; - color: var(--slate700); + color: var(--slate300); font-size: 1.1rem; border: none; background: none; @@ -138,7 +145,7 @@ a { padding: 0.8rem; border-style: solid; border-width: 1px; - border-color: var(--slate300); + border-color: var(--slate500); border-radius: 4px; } @@ -149,17 +156,36 @@ a { margin: 0.5rem; } +.header { + display: flex; + justify-content: space-between; + padding: 1.5rem; +} + +.header-left { + font-size: 1.125rem; + font-weight: 500; + text-decoration: none; +} + +.header-right { + display: flex; + gap: 1.5rem; +} + .footer { - background-color: var(--blue200); + background-color: var(--darkBlueBg); padding: 1rem 0; - height: 3.5rem; - display: flex; justify-content: center; align-items: center; + font-weight: 500; +} - font-weight: 700; +.divider-footer { + height: 1px; + background: var(--slate500); } .loader { @@ -170,6 +196,7 @@ a { border-right: 2px solid transparent; animation: spin 1s linear infinite; z-index: 20; + margin-left: 1rem; } @keyframes spin { diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/groups.tsx b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/groups/page.tsx similarity index 69% rename from packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/groups.tsx rename to packages/cli-template-monorepo-ethers/apps/web-app/src/app/groups/page.tsx index d7c5c1ee8..7163f3157 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/groups.tsx +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/groups/page.tsx @@ -1,13 +1,12 @@ -import { Identity } from "@semaphore-protocol/identity" -import getNextConfig from "next/config" -import { useRouter } from "next/router" -import { useCallback, useContext, useEffect, useState } from "react" -import Feedback from "../../contract-artifacts/Feedback.json" -import Stepper from "../components/Stepper" -import LogsContext from "../context/LogsContext" -import SemaphoreContext from "../context/SemaphoreContext" +"use client" -const { publicRuntimeConfig: env } = getNextConfig() +import { Identity } from "@semaphore-protocol/core" +import { useRouter } from "next/navigation" +import { useCallback, useContext, useEffect, useState } from "react" +import Feedback from "../../../contract-artifacts/Feedback.json" +import Stepper from "@/components/Stepper" +import LogsContext from "@/context/LogsContext" +import SemaphoreContext from "@/context/SemaphoreContext" export default function GroupsPage() { const router = useRouter() @@ -17,14 +16,14 @@ export default function GroupsPage() { const [_identity, setIdentity] = useState() useEffect(() => { - const identityString = localStorage.getItem("identity") + const privateKey = localStorage.getItem("identity") - if (!identityString) { + if (!privateKey) { router.push("/") return } - setIdentity(new Identity(identityString)) + setIdentity(new Identity(privateKey)) }, []) useEffect(() => { @@ -43,13 +42,13 @@ export default function GroupsPage() { let response: any - if (env.OPENZEPPELIN_AUTOTASK_WEBHOOK) { - response = await fetch(env.OPENZEPPELIN_AUTOTASK_WEBHOOK, { + if (process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK) { + response = await fetch(process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ abi: Feedback.abi, - address: env.FEEDBACK_CONTRACT_ADDRESS, + address: process.env.FEEDBACK_CONTRACT_ADDRESS, functionName: "joinGroup", functionParameters: [_identity.commitment.toString()] }) @@ -67,7 +66,7 @@ export default function GroupsPage() { if (response.status === 200) { addUser(_identity.commitment.toString()) - setLogs(`You joined the Feedback group event 🎉 Share your feedback anonymously!`) + setLogs(`You have joined the Feedback group event 🎉 Share your feedback anonymously!`) } else { setLogs("Some error occurred, please try again!") } @@ -75,29 +74,36 @@ export default function GroupsPage() { setLoading(false) }, [_identity]) - const userHasJoined = useCallback((identity: Identity) => _users.includes(identity.commitment.toString()), [_users]) + const userHasJoined = useCallback((identity: Identity) => _users.includes(identity.commitment), [_users]) return ( <>

Groups

- Semaphore{" "} + Semaphore groups + {" "} + are{" "} + - groups + Lean incremental Merkle trees {" "} - are binary incremental Merkle trees in which each leaf contains an identity commitment for a user. - Groups can be abstracted to represent events, polls, or organizations. + in which each leaf contains an identity commitment for a user. Groups can be abstracted to represent + events, polls, or organizations.

-

Feedback users ({_users.length})

+

Group users ({_users.length})

diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/app/layout.tsx b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/layout.tsx new file mode 100644 index 000000000..e6a6d1aac --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/layout.tsx @@ -0,0 +1,40 @@ +import PageContainer from "@/components/PageContainer" +import type { Metadata } from "next" +import { Inter } from "next/font/google" +import "./globals.css" + +const inter = Inter({ subsets: ["latin"] }) + +export const metadata: Metadata = { + title: "Semaphore Demo", + description: "A zero-knowledge protocol for anonymous signaling on Ethereum.", + icons: { icon: "/icon.svg", apple: "/apple-icon.png" }, + metadataBase: new URL("https://demo.semaphore.pse.dev"), + openGraph: { + type: "website", + url: "https://demo.semaphore.pse.dev", + title: "Semaphore Demo", + description: "A zero-knowledge protocol for anonymous signaling on Ethereum.", + siteName: "Semaphore Demo", + images: [ + { + url: "https://demo.semaphore.pse.dev/social-media.png" + } + ] + }, + twitter: { card: "summary_large_image", images: "https://demo.semaphore.pse.dev/social-media.png" } +} + +export default function RootLayout({ + children +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + + {children} + + + ) +} diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/pages/index.tsx b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/page.tsx similarity index 60% rename from packages/cli-template-monorepo-ethers/apps/web-app/src/pages/index.tsx rename to packages/cli-template-monorepo-ethers/apps/web-app/src/app/page.tsx index a58772bf9..5e78b474d 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/pages/index.tsx +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/page.tsx @@ -1,5 +1,7 @@ -import { Identity } from "@semaphore-protocol/identity" -import { useRouter } from "next/router" +"use client" + +import { Identity } from "@semaphore-protocol/core" +import { useRouter } from "next/navigation" import { useCallback, useContext, useEffect, useState } from "react" import Stepper from "../components/Stepper" import LogsContext from "../context/LogsContext" @@ -10,14 +12,14 @@ export default function IdentitiesPage() { const [_identity, setIdentity] = useState() useEffect(() => { - const identityString = localStorage.getItem("identity") + const privateKey = localStorage.getItem("identity") - if (identityString) { - const identity = new Identity(identityString) + if (privateKey) { + const identity = new Identity(privateKey) setIdentity(identity) - setLogs("Your Semaphore identity was retrieved from the browser cache 👌🏽") + setLogs("Your Semaphore identity has been retrieved from the browser cache 👌🏽") } else { setLogs("Create your Semaphore identity 👆🏽") } @@ -28,9 +30,9 @@ export default function IdentitiesPage() { setIdentity(identity) - localStorage.setItem("identity", identity.toString()) + localStorage.setItem("identity", identity.privateKey.toString()) - setLogs("Your new Semaphore identity was just created 🎉") + setLogs("Your new Semaphore identity has just been created 🎉") }, []) return ( @@ -38,21 +40,24 @@ export default function IdentitiesPage() {

Identities

- Users interact with the protocol using a Semaphore{" "} + The identity of a user in the Semaphore protocol. A{" "} + + Semaphore identity + {" "} + consists of an{" "} - identity + EdDSA {" "} - (similar to Ethereum accounts). It contains three values: + public/private key pair and a commitment, used as the public identifier of the identity.

-
    -
  1. Trapdoor: private, known only by user
  2. -
  3. Nullifier: private, known only by user
  4. -
  5. Commitment: public
  6. -
@@ -68,8 +73,7 @@ export default function IdentitiesPage() { {_identity ? (
-

Trapdoor: {_identity.trapdoor.toString()}

-

Nullifier: {_identity.nullifier.toString()}

+

Private Key: {_identity.privateKey.toString()}

Commitment: {_identity.commitment.toString()}

diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/proofs.tsx b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/proofs/page.tsx similarity index 64% rename from packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/proofs.tsx rename to packages/cli-template-monorepo-ethers/apps/web-app/src/app/proofs/page.tsx index fd89a7705..20fa797aa 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/proofs.tsx +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/app/proofs/page.tsx @@ -1,16 +1,12 @@ -import { Group } from "@semaphore-protocol/group" -import { Identity } from "@semaphore-protocol/identity" -import { generateProof } from "@semaphore-protocol/proof" -import { BigNumber, utils } from "ethers" -import getNextConfig from "next/config" -import { useRouter } from "next/router" -import { useCallback, useContext, useEffect, useState } from "react" -import Feedback from "../../contract-artifacts/Feedback.json" -import Stepper from "../components/Stepper" -import LogsContext from "../context/LogsContext" -import SemaphoreContext from "../context/SemaphoreContext" +"use client" -const { publicRuntimeConfig: env } = getNextConfig() +import { Group, Identity, generateProof } from "@semaphore-protocol/core" +import { useRouter } from "next/navigation" +import { useCallback, useContext, useEffect, useState } from "react" +import Feedback from "../../../contract-artifacts/Feedback.json" +import Stepper from "../../components/Stepper" +import LogsContext from "../../context/LogsContext" +import SemaphoreContext from "../../context/SemaphoreContext" export default function ProofsPage() { const router = useRouter() @@ -20,14 +16,14 @@ export default function ProofsPage() { const [_identity, setIdentity] = useState() useEffect(() => { - const identityString = localStorage.getItem("identity") + const privateKey = localStorage.getItem("identity") - if (!identityString) { + if (!privateKey) { router.push("/") return } - setIdentity(new Identity(identityString)) + setIdentity(new Identity(privateKey)) }, []) useEffect(() => { @@ -41,6 +37,15 @@ export default function ProofsPage() { return } + if (typeof process.env.NEXT_PUBLIC_GROUP_ID !== "string") { + throw new Error("Please, define NEXT_PUBLIC_GROUP_ID in your .env file") + } + + if (_users && _users.length < 2) { + alert("No anonymity in a group of one!") + return + } + const feedback = prompt("Please enter your feedback:") if (feedback && _users) { @@ -49,30 +54,26 @@ export default function ProofsPage() { setLogs(`Posting your anonymous feedback...`) try { - const group = new Group(env.GROUP_ID) - - const signal = BigNumber.from(utils.formatBytes32String(feedback)).toString() - - group.addMembers(_users) + const group = new Group(_users) - const { proof, merkleTreeRoot, nullifierHash } = await generateProof( + const { points, merkleTreeDepth, merkleTreeRoot, nullifier, message } = await generateProof( _identity, group, - env.GROUP_ID, - signal + feedback, + process.env.NEXT_PUBLIC_GROUP_ID as string ) let response: any - if (env.OPENZEPPELIN_AUTOTASK_WEBHOOK) { - response = await fetch(env.OPENZEPPELIN_AUTOTASK_WEBHOOK, { + if (process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK) { + response = await fetch(process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ abi: Feedback.abi, - address: env.FEEDBACK_CONTRACT_ADDRESS, + address: process.env.FEEDBACK_CONTRACT_ADDRESS, functionName: "sendFeedback", - functionParameters: [signal, merkleTreeRoot, nullifierHash, proof] + functionParameters: [merkleTreeDepth, merkleTreeRoot, nullifier, message, points] }) }) } else { @@ -80,10 +81,11 @@ export default function ProofsPage() { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - feedback: signal, + feedback: message, + merkleTreeDepth, merkleTreeRoot, - nullifierHash, - proof + nullifier, + points }) }) } @@ -91,7 +93,7 @@ export default function ProofsPage() { if (response.status === 200) { addFeedback(feedback) - setLogs(`Your feedback was posted 🎉`) + setLogs(`Your feedback has been posted 🎉`) } else { setLogs("Some error occurred, please try again!") } @@ -103,7 +105,7 @@ export default function ProofsPage() { setLoading(false) } } - }, [_identity]) + }, [_identity, _users]) return ( <> @@ -112,20 +114,20 @@ export default function ProofsPage() {

Semaphore members can anonymously{" "} prove {" "} - that they are part of a group and that they are generating their own signals. Signals could be anonymous - votes, leaks, reviews, or feedback. + that they are part of a group and send their anonymous messages. Messages could be votes, leaks, + reviews, feedback, etc.

-

Feedback signals ({_feedback.length})

+

Feedback messages ({_feedback.length})

diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/components/PageContainer.tsx b/packages/cli-template-monorepo-ethers/apps/web-app/src/components/PageContainer.tsx new file mode 100644 index 000000000..854dd6816 --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/components/PageContainer.tsx @@ -0,0 +1,98 @@ +"use client" + +import LogsContext from "@/context/LogsContext" +import SemaphoreContext from "@/context/SemaphoreContext" +import useSemaphore from "@/hooks/useSemaphore" +import shortenString from "@/utils/shortenString" +import { SupportedNetwork } from "@semaphore-protocol/data" +import { usePathname } from "next/navigation" +import { useEffect, useState } from "react" +import Link from "next/link" + +export default function PageContainer({ + children +}: Readonly<{ + children: React.ReactNode +}>) { + const pathname = usePathname() + const semaphore = useSemaphore() + const [_logs, setLogs] = useState("") + + useEffect(() => { + semaphore.refreshUsers() + semaphore.refreshFeedback() + }, []) + + function getExplorerLink(network: SupportedNetwork, address: string) { + switch (network) { + case "sepolia": + return `https://sepolia.etherscan.io/address/${address}` + case "arbitrum-sepolia": + return `https://sepolia.arbiscan.io/address/${address}` + default: + return "" + } + } + + return ( +
+ + +
+ + + {children} + + +
+ +
+ +
+ {_logs.endsWith("...")} +

{_logs || `Current step: ${pathname}`}

+
+
+ ) +} diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/hooks/useSemaphore.ts b/packages/cli-template-monorepo-ethers/apps/web-app/src/hooks/useSemaphore.ts index 906299294..bc71ab433 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/hooks/useSemaphore.ts +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/hooks/useSemaphore.ts @@ -1,12 +1,12 @@ import { SemaphoreEthers } from "@semaphore-protocol/data" -import { BigNumber, utils } from "ethers" -import getNextConfig from "next/config" +import { decodeBytes32String, toBeHex } from "ethers" import { useCallback, useState } from "react" import { SemaphoreContextType } from "../context/SemaphoreContext" -const { publicRuntimeConfig: env } = getNextConfig() - -const ethereumNetwork = env.DEFAULT_NETWORK === "localhost" ? "http://localhost:8545" : env.DEFAULT_NETWORK +const ethereumNetwork = + process.env.NEXT_PUBLIC_DEFAULT_NETWORK === "localhost" + ? "http://127.0.0.1:8545" + : process.env.NEXT_PUBLIC_DEFAULT_NETWORK export default function useSemaphore(): SemaphoreContextType { const [_users, setUsers] = useState([]) @@ -14,12 +14,12 @@ export default function useSemaphore(): SemaphoreContextType { const refreshUsers = useCallback(async (): Promise => { const semaphore = new SemaphoreEthers(ethereumNetwork, { - address: env.SEMAPHORE_CONTRACT_ADDRESS + address: process.env.NEXT_PUBLIC_SEMAPHORE_CONTRACT_ADDRESS }) - const members = await semaphore.getGroupMembers(env.GROUP_ID) + const members = await semaphore.getGroupMembers(process.env.NEXT_PUBLIC_GROUP_ID as string) - setUsers(members) + setUsers(members.map((member) => member.toString())) }, []) const addUser = useCallback( @@ -31,12 +31,12 @@ export default function useSemaphore(): SemaphoreContextType { const refreshFeedback = useCallback(async (): Promise => { const semaphore = new SemaphoreEthers(ethereumNetwork, { - address: env.SEMAPHORE_CONTRACT_ADDRESS + address: process.env.NEXT_PUBLIC_SEMAPHORE_CONTRACT_ADDRESS }) - const proofs = await semaphore.getGroupVerifiedProofs(env.GROUP_ID) + const proofs = await semaphore.getGroupValidatedProofs(process.env.NEXT_PUBLIC_GROUP_ID as string) - setFeedback(proofs.map(({ signal }: any) => utils.parseBytes32String(BigNumber.from(signal).toHexString()))) + setFeedback(proofs.map(({ message }: any) => decodeBytes32String(toBeHex(message, 32)))) }, []) const addFeedback = useCallback( diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/pages/_app.tsx b/packages/cli-template-monorepo-ethers/apps/web-app/src/pages/_app.tsx deleted file mode 100644 index e48545cc7..000000000 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/pages/_app.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import "../styles/globals.css" -import type { AppProps } from "next/app" -import Head from "next/head" -import { useRouter } from "next/router" -import { useEffect, useState } from "react" -import LogsContext from "../context/LogsContext" -import SemaphoreContext from "../context/SemaphoreContext" -import useSemaphore from "../hooks/useSemaphore" -import { Inter } from "@next/font/google" - -const inter = Inter({ subsets: ["latin"] }) - -export default function App({ Component, pageProps }: AppProps) { - const router = useRouter() - const semaphore = useSemaphore() - const [_logs, setLogs] = useState("") - - useEffect(() => { - semaphore.refreshUsers() - semaphore.refreshFeedback() - }, []) - - return ( -
- - Semaphore template - - - - - - - - -
-
-
- - - - - -
-
- -
- {_logs.endsWith("...")} -

{_logs || `Current step: ${router.route}`}

-
-
-
- ) -} diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/pages/api/feedback.ts b/packages/cli-template-monorepo-ethers/apps/web-app/src/pages/api/feedback.ts deleted file mode 100644 index 0e65056e5..000000000 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/pages/api/feedback.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Contract, providers, Wallet } from "ethers" -import type { NextApiRequest, NextApiResponse } from "next" -import Feedback from "../../../contract-artifacts/Feedback.json" - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - if (typeof process.env.FEEDBACK_CONTRACT_ADDRESS !== "string") { - throw new Error("Please, define FEEDBACK_CONTRACT_ADDRESS in your .env file") - } - - if (typeof process.env.DEFAULT_NETWORK !== "string") { - throw new Error("Please, define DEFAULT_NETWORK in your .env file") - } - - if (typeof process.env.INFURA_API_KEY !== "string") { - throw new Error("Please, define INFURA_API_KEY in your .env file") - } - - if (typeof process.env.ETHEREUM_PRIVATE_KEY !== "string") { - throw new Error("Please, define ETHEREUM_PRIVATE_KEY in your .env file") - } - - const ethereumPrivateKey = process.env.ETHEREUM_PRIVATE_KEY - const ethereumNetwork = process.env.DEFAULT_NETWORK - const infuraApiKey = process.env.INFURA_API_KEY - const contractAddress = process.env.FEEDBACK_CONTRACT_ADDRESS - - const provider = - ethereumNetwork === "localhost" - ? new providers.JsonRpcProvider() - : new providers.InfuraProvider(ethereumNetwork, infuraApiKey) - - const signer = new Wallet(ethereumPrivateKey, provider) - const contract = new Contract(contractAddress, Feedback.abi, signer) - - const { feedback, merkleTreeRoot, nullifierHash, proof } = req.body - - try { - const transaction = await contract.sendFeedback(feedback, merkleTreeRoot, nullifierHash, proof) - - await transaction.wait() - - res.status(200).end() - } catch (error: any) { - console.error(error) - - res.status(500).end() - } -} diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/pages/api/join.ts b/packages/cli-template-monorepo-ethers/apps/web-app/src/pages/api/join.ts deleted file mode 100644 index 826865f16..000000000 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/pages/api/join.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Contract, providers, Wallet } from "ethers" -import type { NextApiRequest, NextApiResponse } from "next" -import Feedback from "../../../contract-artifacts/Feedback.json" - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - if (typeof process.env.FEEDBACK_CONTRACT_ADDRESS !== "string") { - throw new Error("Please, define FEEDBACK_CONTRACT_ADDRESS in your .env file") - } - - if (typeof process.env.DEFAULT_NETWORK !== "string") { - throw new Error("Please, define DEFAULT_NETWORK in your .env file") - } - - if (typeof process.env.INFURA_API_KEY !== "string") { - throw new Error("Please, define INFURA_API_KEY in your .env file") - } - - if (typeof process.env.ETHEREUM_PRIVATE_KEY !== "string") { - throw new Error("Please, define ETHEREUM_PRIVATE_KEY in your .env file") - } - - const ethereumPrivateKey = process.env.ETHEREUM_PRIVATE_KEY - const ethereumNetwork = process.env.DEFAULT_NETWORK - const infuraApiKey = process.env.INFURA_API_KEY - const contractAddress = process.env.FEEDBACK_CONTRACT_ADDRESS - - const provider = - ethereumNetwork === "localhost" - ? new providers.JsonRpcProvider() - : new providers.InfuraProvider(ethereumNetwork, infuraApiKey) - - const signer = new Wallet(ethereumPrivateKey, provider) - const contract = new Contract(contractAddress, Feedback.abi, signer) - - const { identityCommitment } = req.body - - try { - const transaction = await contract.joinGroup(identityCommitment) - - await transaction.wait() - - res.status(200).end() - } catch (error: any) { - console.error(error) - - res.status(500).end() - } -} diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/utils/shortenString.ts b/packages/cli-template-monorepo-ethers/apps/web-app/src/utils/shortenString.ts new file mode 100644 index 000000000..97de81b0b --- /dev/null +++ b/packages/cli-template-monorepo-ethers/apps/web-app/src/utils/shortenString.ts @@ -0,0 +1,3 @@ +export default function shortenString(s: string, l: [number, number]) { + return `${s.slice(0, l[0])}...${s.slice(-l[1])}` +} diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/tsconfig.json b/packages/cli-template-monorepo-ethers/apps/web-app/tsconfig.json index 2018a3356..68bce00b1 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/tsconfig.json +++ b/packages/cli-template-monorepo-ethers/apps/web-app/tsconfig.json @@ -1,20 +1,26 @@ { "compilerOptions": { - "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, - "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", - "incremental": true + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } }, - "include": ["next.config.js", "next-env.d.ts", "**/*.ts", "**/*.tsx"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] } diff --git a/packages/cli-template-monorepo-ethers/package.json b/packages/cli-template-monorepo-ethers/package.json index cb1d08d6f..efa44fc78 100644 --- a/packages/cli-template-monorepo-ethers/package.json +++ b/packages/cli-template-monorepo-ethers/package.json @@ -1,44 +1,30 @@ { "name": "@semaphore-protocol/cli-template-monorepo-ethers", - "version": "3.15.2", + "version": "4.0.0-alpha.8", "description": "Semaphore Hardhat + Next.js + SemaphoreEthers template.", "license": "Unlicense", "files": [ "files.tgz", - "scripts/", ".editorconfig", ".env.example", - ".eslintignore", - ".eslintrc.json", ".prettierignore", ".prettierrc.json", - "tsconfig.json", "README.md" ], "scripts": { - "dev": "yarn workspaces foreach -pi run dev", + "dev": "yarn workspaces foreach -A -pi run dev", "dev:web-app": "yarn workspace monorepo-ethers-web-app dev", "dev:contracts": "yarn workspace monorepo-ethers-contracts dev", - "lint": "eslint . --ext .js,.ts", - "prettier": "prettier -c .", - "prettier:write": "prettier -w .", - "copy:contract-artifacts": "ts-node scripts/copy-contract-artifacts.ts", + "lint": "yarn workspaces foreach -A -pi run lint", + "prettier": "prettier -c . && yarn workspaces foreach -A -pi run prettier", + "prettier:write": "prettier -w . && yarn workspaces foreach -A -pi run prettier:write", "prepublish": "tar -czf files.tgz .gitignore .yarn .yarnrc.yml apps" }, "workspaces": [ "apps/*" ], + "packageManager": "yarn@4.1.0", "devDependencies": { - "@types/node": "^17.0.9", - "@typescript-eslint/eslint-plugin": "^5.9.1", - "@typescript-eslint/parser": "^5.9.1", - "eslint": "^8.2.0", - "eslint-config-airbnb-base": "15.0.0", - "eslint-config-airbnb-typescript": "^16.1.0", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-import": "^2.25.2", - "prettier": "^2.5.1", - "ts-node": "^10.8.1", - "typescript": "^4.7.3" + "prettier": "^3.2.5" } } diff --git a/packages/cli-template-monorepo-ethers/scripts/copy-contract-artifacts.ts b/packages/cli-template-monorepo-ethers/scripts/copy-contract-artifacts.ts deleted file mode 100644 index 7faf8eefe..000000000 --- a/packages/cli-template-monorepo-ethers/scripts/copy-contract-artifacts.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as fs from "fs" - -async function main() { - const contractArtifactsPath = "apps/contracts/build/contracts/contracts/Feedback.sol" - const webAppArtifactsPath = "apps/web-app/contract-artifacts" - - await fs.promises.copyFile(`${contractArtifactsPath}/Feedback.json`, `${webAppArtifactsPath}/Feedback.json`) -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error) - process.exit(1) - }) diff --git a/packages/cli-template-monorepo-ethers/tsconfig.json b/packages/cli-template-monorepo-ethers/tsconfig.json deleted file mode 100644 index 6ae2d24c4..000000000 --- a/packages/cli-template-monorepo-ethers/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "include": ["scripts/**/*"] -} diff --git a/packages/cli-template-monorepo-subgraph/.env.example b/packages/cli-template-monorepo-subgraph/.env.example index 050dd4d74..57a673d86 100644 --- a/packages/cli-template-monorepo-subgraph/.env.example +++ b/packages/cli-template-monorepo-subgraph/.env.example @@ -1,10 +1,6 @@ DEFAULT_NETWORK=localhost INFURA_API_KEY= ETHEREUM_PRIVATE_KEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 -FEEDBACK_CONTRACT_ADDRESS=0x5fc8d32690cc91d4c39d9d3abcbd16989f875707 -SEMAPHORE_CONTRACT_ADDRESS=0xdc64a140aa3e981100a9beca4e685f962f0cf6c9 -OPENZEPPELIN_AUTOTASK_WEBHOOK= -GROUP_ID=42 REPORT_GAS=false COINMARKETCAP_API_KEY= ETHERSCAN_API_KEY= diff --git a/packages/cli-template-monorepo-subgraph/.eslintignore b/packages/cli-template-monorepo-subgraph/.eslintignore deleted file mode 100644 index 902d0695f..000000000 --- a/packages/cli-template-monorepo-subgraph/.eslintignore +++ /dev/null @@ -1,45 +0,0 @@ -# dependencies -node_modules -package-lock.json -yarn.lock -.yarn - -# testing -coverage -coverage.json - -# docs -docs - -# types -types - -# production -dist -build -cache -contract-artifacts - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Next.js -.next/ -out/ - -# The Graph -generated - -# Auto Generated PWA files -**/public/sw.js -**/public/workbox-*.js -**/public/worker-*.js -**/public/sw.js.map -**/public/workbox-*.js.map -**/public/worker-*.js.map diff --git a/packages/cli-template-monorepo-subgraph/.gitignore b/packages/cli-template-monorepo-subgraph/.gitignore index bad39850b..4aa5d3d55 100644 --- a/packages/cli-template-monorepo-subgraph/.gitignore +++ b/packages/cli-template-monorepo-subgraph/.gitignore @@ -20,26 +20,12 @@ pids .vscode .idea -# Cargo -target - -# Testing -coverage -coverage.json -*.lcov - # Dependency directories node_modules/ -# TypeScript cache -*.tsbuildinfo - # Output of 'npm pack' *.tgz -# Optional eslint cache -.eslintcache - # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ @@ -49,31 +35,15 @@ node_modules/ # Yarn Integrity file .yarn-integrity -# Generate output -dist -build -cache - -# Next.js -.next/ -out/ - # vercel .vercel -# typescript -next-env.d.ts - # dotenv environment variable files .env -.env.development.local -.env.test.local -.env.production.local -.env.local # Optional npm cache directory .npm -*.DS_Store +.DS_Store # yarn v3 .pnp.* @@ -84,39 +54,3 @@ next-env.d.ts !.yarn/releases !.yarn/sdks !.yarn/versions - -# The Graph -generated - -# Auto Generated PWA files -**/public/sw.js -**/public/workbox-*.js -**/public/worker-*.js -**/public/sw.js.map -**/public/workbox-*.js.map -**/public/worker-*.js.map - -#amplify-do-not-edit-begin -amplify/\#current-cloud-backend -amplify/.config/local-* -amplify/logs -amplify/mock-data -amplify/mock-api-resources -amplify/backend/amplify-meta.json -amplify/backend/.temp -build/ -dist/ -node_modules/ -aws-exports.js -awsconfiguration.json -amplifyconfiguration.json -amplifyconfiguration.dart -amplify-build-config.json -amplify-gradle-config.json -amplifytools.xcconfig -.secret-* -**.sample -#amplify-do-not-edit-end - -# Others -files.tgz diff --git a/packages/cli-template-monorepo-subgraph/.prettierignore b/packages/cli-template-monorepo-subgraph/.prettierignore index c6d5fdfbe..a82da8179 100644 --- a/packages/cli-template-monorepo-subgraph/.prettierignore +++ b/packages/cli-template-monorepo-subgraph/.prettierignore @@ -14,9 +14,13 @@ docs # production dist build -cache contract-artifacts +# Hardhat files +artifacts +cache +typechain-types + # github .github/ISSUE_TEMPLATE diff --git a/packages/cli-template-monorepo-subgraph/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs b/packages/cli-template-monorepo-subgraph/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs deleted file mode 100644 index b9044a014..000000000 --- a/packages/cli-template-monorepo-subgraph/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-disable */ -//prettier-ignore -module.exports = { -name: "@yarnpkg/plugin-workspace-tools", -factory: function (require) { -var plugin=(()=>{var wr=Object.create,me=Object.defineProperty,Sr=Object.defineProperties,vr=Object.getOwnPropertyDescriptor,Hr=Object.getOwnPropertyDescriptors,$r=Object.getOwnPropertyNames,et=Object.getOwnPropertySymbols,kr=Object.getPrototypeOf,tt=Object.prototype.hasOwnProperty,Tr=Object.prototype.propertyIsEnumerable;var rt=(e,t,r)=>t in e?me(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,B=(e,t)=>{for(var r in t||(t={}))tt.call(t,r)&&rt(e,r,t[r]);if(et)for(var r of et(t))Tr.call(t,r)&&rt(e,r,t[r]);return e},Q=(e,t)=>Sr(e,Hr(t)),Lr=e=>me(e,"__esModule",{value:!0});var K=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),Or=(e,t)=>{for(var r in t)me(e,r,{get:t[r],enumerable:!0})},Nr=(e,t,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of $r(t))!tt.call(e,n)&&n!=="default"&&me(e,n,{get:()=>t[n],enumerable:!(r=vr(t,n))||r.enumerable});return e},X=e=>Nr(Lr(me(e!=null?wr(kr(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var $e=K(te=>{"use strict";te.isInteger=e=>typeof e=="number"?Number.isInteger(e):typeof e=="string"&&e.trim()!==""?Number.isInteger(Number(e)):!1;te.find=(e,t)=>e.nodes.find(r=>r.type===t);te.exceedsLimit=(e,t,r=1,n)=>n===!1||!te.isInteger(e)||!te.isInteger(t)?!1:(Number(t)-Number(e))/Number(r)>=n;te.escapeNode=(e,t=0,r)=>{let n=e.nodes[t];!n||(r&&n.type===r||n.type==="open"||n.type==="close")&&n.escaped!==!0&&(n.value="\\"+n.value,n.escaped=!0)};te.encloseBrace=e=>e.type!=="brace"?!1:e.commas>>0+e.ranges>>0==0?(e.invalid=!0,!0):!1;te.isInvalidBrace=e=>e.type!=="brace"?!1:e.invalid===!0||e.dollar?!0:e.commas>>0+e.ranges>>0==0||e.open!==!0||e.close!==!0?(e.invalid=!0,!0):!1;te.isOpenOrClose=e=>e.type==="open"||e.type==="close"?!0:e.open===!0||e.close===!0;te.reduce=e=>e.reduce((t,r)=>(r.type==="text"&&t.push(r.value),r.type==="range"&&(r.type="text"),t),[]);te.flatten=(...e)=>{let t=[],r=n=>{for(let s=0;s{"use strict";var it=$e();at.exports=(e,t={})=>{let r=(n,s={})=>{let a=t.escapeInvalid&&it.isInvalidBrace(s),i=n.invalid===!0&&t.escapeInvalid===!0,o="";if(n.value)return(a||i)&&it.isOpenOrClose(n)?"\\"+n.value:n.value;if(n.value)return n.value;if(n.nodes)for(let h of n.nodes)o+=r(h);return o};return r(e)}});var ct=K((os,ot)=>{"use strict";ot.exports=function(e){return typeof e=="number"?e-e==0:typeof e=="string"&&e.trim()!==""?Number.isFinite?Number.isFinite(+e):isFinite(+e):!1}});var At=K((cs,ut)=>{"use strict";var lt=ct(),pe=(e,t,r)=>{if(lt(e)===!1)throw new TypeError("toRegexRange: expected the first argument to be a number");if(t===void 0||e===t)return String(e);if(lt(t)===!1)throw new TypeError("toRegexRange: expected the second argument to be a number.");let n=B({relaxZeros:!0},r);typeof n.strictZeros=="boolean"&&(n.relaxZeros=n.strictZeros===!1);let s=String(n.relaxZeros),a=String(n.shorthand),i=String(n.capture),o=String(n.wrap),h=e+":"+t+"="+s+a+i+o;if(pe.cache.hasOwnProperty(h))return pe.cache[h].result;let g=Math.min(e,t),f=Math.max(e,t);if(Math.abs(g-f)===1){let R=e+"|"+t;return n.capture?`(${R})`:n.wrap===!1?R:`(?:${R})`}let A=ft(e)||ft(t),p={min:e,max:t,a:g,b:f},k=[],y=[];if(A&&(p.isPadded=A,p.maxLen=String(p.max).length),g<0){let R=f<0?Math.abs(f):1;y=pt(R,Math.abs(g),p,n),g=p.a=0}return f>=0&&(k=pt(g,f,p,n)),p.negatives=y,p.positives=k,p.result=Ir(y,k,n),n.capture===!0?p.result=`(${p.result})`:n.wrap!==!1&&k.length+y.length>1&&(p.result=`(?:${p.result})`),pe.cache[h]=p,p.result};function Ir(e,t,r){let n=Pe(e,t,"-",!1,r)||[],s=Pe(t,e,"",!1,r)||[],a=Pe(e,t,"-?",!0,r)||[];return n.concat(a).concat(s).join("|")}function Mr(e,t){let r=1,n=1,s=ht(e,r),a=new Set([t]);for(;e<=s&&s<=t;)a.add(s),r+=1,s=ht(e,r);for(s=dt(t+1,n)-1;e1&&o.count.pop(),o.count.push(f.count[0]),o.string=o.pattern+gt(o.count),i=g+1;continue}r.isPadded&&(A=Gr(g,r,n)),f.string=A+f.pattern+gt(f.count),a.push(f),i=g+1,o=f}return a}function Pe(e,t,r,n,s){let a=[];for(let i of e){let{string:o}=i;!n&&!mt(t,"string",o)&&a.push(r+o),n&&mt(t,"string",o)&&a.push(r+o)}return a}function Pr(e,t){let r=[];for(let n=0;nt?1:t>e?-1:0}function mt(e,t,r){return e.some(n=>n[t]===r)}function ht(e,t){return Number(String(e).slice(0,-t)+"9".repeat(t))}function dt(e,t){return e-e%Math.pow(10,t)}function gt(e){let[t=0,r=""]=e;return r||t>1?`{${t+(r?","+r:"")}}`:""}function Dr(e,t,r){return`[${e}${t-e==1?"":"-"}${t}]`}function ft(e){return/^-?(0+)\d/.test(e)}function Gr(e,t,r){if(!t.isPadded)return e;let n=Math.abs(t.maxLen-String(e).length),s=r.relaxZeros!==!1;switch(n){case 0:return"";case 1:return s?"0?":"0";case 2:return s?"0{0,2}":"00";default:return s?`0{0,${n}}`:`0{${n}}`}}pe.cache={};pe.clearCache=()=>pe.cache={};ut.exports=pe});var Ge=K((us,Rt)=>{"use strict";var qr=require("util"),yt=At(),bt=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),Kr=e=>t=>e===!0?Number(t):String(t),De=e=>typeof e=="number"||typeof e=="string"&&e!=="",Re=e=>Number.isInteger(+e),Ue=e=>{let t=`${e}`,r=-1;if(t[0]==="-"&&(t=t.slice(1)),t==="0")return!1;for(;t[++r]==="0";);return r>0},Wr=(e,t,r)=>typeof e=="string"||typeof t=="string"?!0:r.stringify===!0,jr=(e,t,r)=>{if(t>0){let n=e[0]==="-"?"-":"";n&&(e=e.slice(1)),e=n+e.padStart(n?t-1:t,"0")}return r===!1?String(e):e},_t=(e,t)=>{let r=e[0]==="-"?"-":"";for(r&&(e=e.slice(1),t--);e.length{e.negatives.sort((i,o)=>io?1:0),e.positives.sort((i,o)=>io?1:0);let r=t.capture?"":"?:",n="",s="",a;return e.positives.length&&(n=e.positives.join("|")),e.negatives.length&&(s=`-(${r}${e.negatives.join("|")})`),n&&s?a=`${n}|${s}`:a=n||s,t.wrap?`(${r}${a})`:a},Et=(e,t,r,n)=>{if(r)return yt(e,t,B({wrap:!1},n));let s=String.fromCharCode(e);if(e===t)return s;let a=String.fromCharCode(t);return`[${s}-${a}]`},xt=(e,t,r)=>{if(Array.isArray(e)){let n=r.wrap===!0,s=r.capture?"":"?:";return n?`(${s}${e.join("|")})`:e.join("|")}return yt(e,t,r)},Ct=(...e)=>new RangeError("Invalid range arguments: "+qr.inspect(...e)),wt=(e,t,r)=>{if(r.strictRanges===!0)throw Ct([e,t]);return[]},Qr=(e,t)=>{if(t.strictRanges===!0)throw new TypeError(`Expected step "${e}" to be a number`);return[]},Xr=(e,t,r=1,n={})=>{let s=Number(e),a=Number(t);if(!Number.isInteger(s)||!Number.isInteger(a)){if(n.strictRanges===!0)throw Ct([e,t]);return[]}s===0&&(s=0),a===0&&(a=0);let i=s>a,o=String(e),h=String(t),g=String(r);r=Math.max(Math.abs(r),1);let f=Ue(o)||Ue(h)||Ue(g),A=f?Math.max(o.length,h.length,g.length):0,p=f===!1&&Wr(e,t,n)===!1,k=n.transform||Kr(p);if(n.toRegex&&r===1)return Et(_t(e,A),_t(t,A),!0,n);let y={negatives:[],positives:[]},R=T=>y[T<0?"negatives":"positives"].push(Math.abs(T)),_=[],x=0;for(;i?s>=a:s<=a;)n.toRegex===!0&&r>1?R(s):_.push(jr(k(s,x),A,p)),s=i?s-r:s+r,x++;return n.toRegex===!0?r>1?Fr(y,n):xt(_,null,B({wrap:!1},n)):_},Zr=(e,t,r=1,n={})=>{if(!Re(e)&&e.length>1||!Re(t)&&t.length>1)return wt(e,t,n);let s=n.transform||(p=>String.fromCharCode(p)),a=`${e}`.charCodeAt(0),i=`${t}`.charCodeAt(0),o=a>i,h=Math.min(a,i),g=Math.max(a,i);if(n.toRegex&&r===1)return Et(h,g,!1,n);let f=[],A=0;for(;o?a>=i:a<=i;)f.push(s(a,A)),a=o?a-r:a+r,A++;return n.toRegex===!0?xt(f,null,{wrap:!1,options:n}):f},Te=(e,t,r,n={})=>{if(t==null&&De(e))return[e];if(!De(e)||!De(t))return wt(e,t,n);if(typeof r=="function")return Te(e,t,1,{transform:r});if(bt(r))return Te(e,t,0,r);let s=B({},n);return s.capture===!0&&(s.wrap=!0),r=r||s.step||1,Re(r)?Re(e)&&Re(t)?Xr(e,t,r,s):Zr(e,t,Math.max(Math.abs(r),1),s):r!=null&&!bt(r)?Qr(r,s):Te(e,t,1,r)};Rt.exports=Te});var Ht=K((ls,St)=>{"use strict";var Yr=Ge(),vt=$e(),zr=(e,t={})=>{let r=(n,s={})=>{let a=vt.isInvalidBrace(s),i=n.invalid===!0&&t.escapeInvalid===!0,o=a===!0||i===!0,h=t.escapeInvalid===!0?"\\":"",g="";if(n.isOpen===!0||n.isClose===!0)return h+n.value;if(n.type==="open")return o?h+n.value:"(";if(n.type==="close")return o?h+n.value:")";if(n.type==="comma")return n.prev.type==="comma"?"":o?n.value:"|";if(n.value)return n.value;if(n.nodes&&n.ranges>0){let f=vt.reduce(n.nodes),A=Yr(...f,Q(B({},t),{wrap:!1,toRegex:!0}));if(A.length!==0)return f.length>1&&A.length>1?`(${A})`:A}if(n.nodes)for(let f of n.nodes)g+=r(f,n);return g};return r(e)};St.exports=zr});var Tt=K((ps,$t)=>{"use strict";var Vr=Ge(),kt=ke(),he=$e(),fe=(e="",t="",r=!1)=>{let n=[];if(e=[].concat(e),t=[].concat(t),!t.length)return e;if(!e.length)return r?he.flatten(t).map(s=>`{${s}}`):t;for(let s of e)if(Array.isArray(s))for(let a of s)n.push(fe(a,t,r));else for(let a of t)r===!0&&typeof a=="string"&&(a=`{${a}}`),n.push(Array.isArray(a)?fe(s,a,r):s+a);return he.flatten(n)},Jr=(e,t={})=>{let r=t.rangeLimit===void 0?1e3:t.rangeLimit,n=(s,a={})=>{s.queue=[];let i=a,o=a.queue;for(;i.type!=="brace"&&i.type!=="root"&&i.parent;)i=i.parent,o=i.queue;if(s.invalid||s.dollar){o.push(fe(o.pop(),kt(s,t)));return}if(s.type==="brace"&&s.invalid!==!0&&s.nodes.length===2){o.push(fe(o.pop(),["{}"]));return}if(s.nodes&&s.ranges>0){let A=he.reduce(s.nodes);if(he.exceedsLimit(...A,t.step,r))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let p=Vr(...A,t);p.length===0&&(p=kt(s,t)),o.push(fe(o.pop(),p)),s.nodes=[];return}let h=he.encloseBrace(s),g=s.queue,f=s;for(;f.type!=="brace"&&f.type!=="root"&&f.parent;)f=f.parent,g=f.queue;for(let A=0;A{"use strict";Lt.exports={MAX_LENGTH:1024*64,CHAR_0:"0",CHAR_9:"9",CHAR_UPPERCASE_A:"A",CHAR_LOWERCASE_A:"a",CHAR_UPPERCASE_Z:"Z",CHAR_LOWERCASE_Z:"z",CHAR_LEFT_PARENTHESES:"(",CHAR_RIGHT_PARENTHESES:")",CHAR_ASTERISK:"*",CHAR_AMPERSAND:"&",CHAR_AT:"@",CHAR_BACKSLASH:"\\",CHAR_BACKTICK:"`",CHAR_CARRIAGE_RETURN:"\r",CHAR_CIRCUMFLEX_ACCENT:"^",CHAR_COLON:":",CHAR_COMMA:",",CHAR_DOLLAR:"$",CHAR_DOT:".",CHAR_DOUBLE_QUOTE:'"',CHAR_EQUAL:"=",CHAR_EXCLAMATION_MARK:"!",CHAR_FORM_FEED:"\f",CHAR_FORWARD_SLASH:"/",CHAR_HASH:"#",CHAR_HYPHEN_MINUS:"-",CHAR_LEFT_ANGLE_BRACKET:"<",CHAR_LEFT_CURLY_BRACE:"{",CHAR_LEFT_SQUARE_BRACKET:"[",CHAR_LINE_FEED:` -`,CHAR_NO_BREAK_SPACE:"\xA0",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:" ",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\uFEFF"}});var Pt=K((hs,Nt)=>{"use strict";var en=ke(),{MAX_LENGTH:It,CHAR_BACKSLASH:qe,CHAR_BACKTICK:tn,CHAR_COMMA:rn,CHAR_DOT:nn,CHAR_LEFT_PARENTHESES:sn,CHAR_RIGHT_PARENTHESES:an,CHAR_LEFT_CURLY_BRACE:on,CHAR_RIGHT_CURLY_BRACE:cn,CHAR_LEFT_SQUARE_BRACKET:Bt,CHAR_RIGHT_SQUARE_BRACKET:Mt,CHAR_DOUBLE_QUOTE:un,CHAR_SINGLE_QUOTE:ln,CHAR_NO_BREAK_SPACE:pn,CHAR_ZERO_WIDTH_NOBREAK_SPACE:fn}=Ot(),hn=(e,t={})=>{if(typeof e!="string")throw new TypeError("Expected a string");let r=t||{},n=typeof r.maxLength=="number"?Math.min(It,r.maxLength):It;if(e.length>n)throw new SyntaxError(`Input length (${e.length}), exceeds max characters (${n})`);let s={type:"root",input:e,nodes:[]},a=[s],i=s,o=s,h=0,g=e.length,f=0,A=0,p,k={},y=()=>e[f++],R=_=>{if(_.type==="text"&&o.type==="dot"&&(o.type="text"),o&&o.type==="text"&&_.type==="text"){o.value+=_.value;return}return i.nodes.push(_),_.parent=i,_.prev=o,o=_,_};for(R({type:"bos"});f0){if(i.ranges>0){i.ranges=0;let _=i.nodes.shift();i.nodes=[_,{type:"text",value:en(i)}]}R({type:"comma",value:p}),i.commas++;continue}if(p===nn&&A>0&&i.commas===0){let _=i.nodes;if(A===0||_.length===0){R({type:"text",value:p});continue}if(o.type==="dot"){if(i.range=[],o.value+=p,o.type="range",i.nodes.length!==3&&i.nodes.length!==5){i.invalid=!0,i.ranges=0,o.type="text";continue}i.ranges++,i.args=[];continue}if(o.type==="range"){_.pop();let x=_[_.length-1];x.value+=o.value+p,o=x,i.ranges--;continue}R({type:"dot",value:p});continue}R({type:"text",value:p})}do if(i=a.pop(),i.type!=="root"){i.nodes.forEach(T=>{T.nodes||(T.type==="open"&&(T.isOpen=!0),T.type==="close"&&(T.isClose=!0),T.nodes||(T.type="text"),T.invalid=!0)});let _=a[a.length-1],x=_.nodes.indexOf(i);_.nodes.splice(x,1,...i.nodes)}while(a.length>0);return R({type:"eos"}),s};Nt.exports=hn});var Gt=K((ds,Dt)=>{"use strict";var Ut=ke(),dn=Ht(),gn=Tt(),mn=Pt(),V=(e,t={})=>{let r=[];if(Array.isArray(e))for(let n of e){let s=V.create(n,t);Array.isArray(s)?r.push(...s):r.push(s)}else r=[].concat(V.create(e,t));return t&&t.expand===!0&&t.nodupes===!0&&(r=[...new Set(r)]),r};V.parse=(e,t={})=>mn(e,t);V.stringify=(e,t={})=>typeof e=="string"?Ut(V.parse(e,t),t):Ut(e,t);V.compile=(e,t={})=>(typeof e=="string"&&(e=V.parse(e,t)),dn(e,t));V.expand=(e,t={})=>{typeof e=="string"&&(e=V.parse(e,t));let r=gn(e,t);return t.noempty===!0&&(r=r.filter(Boolean)),t.nodupes===!0&&(r=[...new Set(r)]),r};V.create=(e,t={})=>e===""||e.length<3?[e]:t.expand!==!0?V.compile(e,t):V.expand(e,t);Dt.exports=V});var ye=K((gs,qt)=>{"use strict";var An=require("path"),ie="\\\\/",Kt=`[^${ie}]`,ce="\\.",Rn="\\+",yn="\\?",Le="\\/",bn="(?=.)",Wt="[^/]",Ke=`(?:${Le}|$)`,jt=`(?:^|${Le})`,We=`${ce}{1,2}${Ke}`,_n=`(?!${ce})`,En=`(?!${jt}${We})`,xn=`(?!${ce}{0,1}${Ke})`,Cn=`(?!${We})`,wn=`[^.${Le}]`,Sn=`${Wt}*?`,Ft={DOT_LITERAL:ce,PLUS_LITERAL:Rn,QMARK_LITERAL:yn,SLASH_LITERAL:Le,ONE_CHAR:bn,QMARK:Wt,END_ANCHOR:Ke,DOTS_SLASH:We,NO_DOT:_n,NO_DOTS:En,NO_DOT_SLASH:xn,NO_DOTS_SLASH:Cn,QMARK_NO_DOT:wn,STAR:Sn,START_ANCHOR:jt},vn=Q(B({},Ft),{SLASH_LITERAL:`[${ie}]`,QMARK:Kt,STAR:`${Kt}*?`,DOTS_SLASH:`${ce}{1,2}(?:[${ie}]|$)`,NO_DOT:`(?!${ce})`,NO_DOTS:`(?!(?:^|[${ie}])${ce}{1,2}(?:[${ie}]|$))`,NO_DOT_SLASH:`(?!${ce}{0,1}(?:[${ie}]|$))`,NO_DOTS_SLASH:`(?!${ce}{1,2}(?:[${ie}]|$))`,QMARK_NO_DOT:`[^.${ie}]`,START_ANCHOR:`(?:^|[${ie}])`,END_ANCHOR:`(?:[${ie}]|$)`}),Hn={alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"};qt.exports={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:Hn,REGEX_BACKSLASH:/\\(?![*+?^${}(|)[\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\].,$*+?^{}()|\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\?)((\W)(\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\[.*?[^\\]\]|\\(?=.))/g,REPLACEMENTS:{"***":"*","**/**":"**","**/**/**":"**"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:An.sep,extglobChars(e){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${e.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}},globChars(e){return e===!0?vn:Ft}}});var be=K(Z=>{"use strict";var $n=require("path"),kn=process.platform==="win32",{REGEX_BACKSLASH:Tn,REGEX_REMOVE_BACKSLASH:Ln,REGEX_SPECIAL_CHARS:On,REGEX_SPECIAL_CHARS_GLOBAL:Nn}=ye();Z.isObject=e=>e!==null&&typeof e=="object"&&!Array.isArray(e);Z.hasRegexChars=e=>On.test(e);Z.isRegexChar=e=>e.length===1&&Z.hasRegexChars(e);Z.escapeRegex=e=>e.replace(Nn,"\\$1");Z.toPosixSlashes=e=>e.replace(Tn,"/");Z.removeBackslashes=e=>e.replace(Ln,t=>t==="\\"?"":t);Z.supportsLookbehinds=()=>{let e=process.version.slice(1).split(".").map(Number);return e.length===3&&e[0]>=9||e[0]===8&&e[1]>=10};Z.isWindows=e=>e&&typeof e.windows=="boolean"?e.windows:kn===!0||$n.sep==="\\";Z.escapeLast=(e,t,r)=>{let n=e.lastIndexOf(t,r);return n===-1?e:e[n-1]==="\\"?Z.escapeLast(e,t,n-1):`${e.slice(0,n)}\\${e.slice(n)}`};Z.removePrefix=(e,t={})=>{let r=e;return r.startsWith("./")&&(r=r.slice(2),t.prefix="./"),r};Z.wrapOutput=(e,t={},r={})=>{let n=r.contains?"":"^",s=r.contains?"":"$",a=`${n}(?:${e})${s}`;return t.negated===!0&&(a=`(?:^(?!${a}).*$)`),a}});var er=K((As,Qt)=>{"use strict";var Xt=be(),{CHAR_ASTERISK:je,CHAR_AT:In,CHAR_BACKWARD_SLASH:_e,CHAR_COMMA:Bn,CHAR_DOT:Fe,CHAR_EXCLAMATION_MARK:Qe,CHAR_FORWARD_SLASH:Zt,CHAR_LEFT_CURLY_BRACE:Xe,CHAR_LEFT_PARENTHESES:Ze,CHAR_LEFT_SQUARE_BRACKET:Mn,CHAR_PLUS:Pn,CHAR_QUESTION_MARK:Yt,CHAR_RIGHT_CURLY_BRACE:Dn,CHAR_RIGHT_PARENTHESES:zt,CHAR_RIGHT_SQUARE_BRACKET:Un}=ye(),Vt=e=>e===Zt||e===_e,Jt=e=>{e.isPrefix!==!0&&(e.depth=e.isGlobstar?Infinity:1)},Gn=(e,t)=>{let r=t||{},n=e.length-1,s=r.parts===!0||r.scanToEnd===!0,a=[],i=[],o=[],h=e,g=-1,f=0,A=0,p=!1,k=!1,y=!1,R=!1,_=!1,x=!1,T=!1,O=!1,W=!1,G=!1,ne=0,E,b,C={value:"",depth:0,isGlob:!1},M=()=>g>=n,l=()=>h.charCodeAt(g+1),H=()=>(E=b,h.charCodeAt(++g));for(;g0&&(j=h.slice(0,f),h=h.slice(f),A-=f),w&&y===!0&&A>0?(w=h.slice(0,A),c=h.slice(A)):y===!0?(w="",c=h):w=h,w&&w!==""&&w!=="/"&&w!==h&&Vt(w.charCodeAt(w.length-1))&&(w=w.slice(0,-1)),r.unescape===!0&&(c&&(c=Xt.removeBackslashes(c)),w&&T===!0&&(w=Xt.removeBackslashes(w)));let u={prefix:j,input:e,start:f,base:w,glob:c,isBrace:p,isBracket:k,isGlob:y,isExtglob:R,isGlobstar:_,negated:O,negatedExtglob:W};if(r.tokens===!0&&(u.maxDepth=0,Vt(b)||i.push(C),u.tokens=i),r.parts===!0||r.tokens===!0){let I;for(let $=0;${"use strict";var Oe=ye(),J=be(),{MAX_LENGTH:Ne,POSIX_REGEX_SOURCE:qn,REGEX_NON_SPECIAL_CHARS:Kn,REGEX_SPECIAL_CHARS_BACKREF:Wn,REPLACEMENTS:rr}=Oe,jn=(e,t)=>{if(typeof t.expandRange=="function")return t.expandRange(...e,t);e.sort();let r=`[${e.join("-")}]`;try{new RegExp(r)}catch(n){return e.map(s=>J.escapeRegex(s)).join("..")}return r},de=(e,t)=>`Missing ${e}: "${t}" - use "\\\\${t}" to match literal characters`,nr=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");e=rr[e]||e;let r=B({},t),n=typeof r.maxLength=="number"?Math.min(Ne,r.maxLength):Ne,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);let a={type:"bos",value:"",output:r.prepend||""},i=[a],o=r.capture?"":"?:",h=J.isWindows(t),g=Oe.globChars(h),f=Oe.extglobChars(g),{DOT_LITERAL:A,PLUS_LITERAL:p,SLASH_LITERAL:k,ONE_CHAR:y,DOTS_SLASH:R,NO_DOT:_,NO_DOT_SLASH:x,NO_DOTS_SLASH:T,QMARK:O,QMARK_NO_DOT:W,STAR:G,START_ANCHOR:ne}=g,E=m=>`(${o}(?:(?!${ne}${m.dot?R:A}).)*?)`,b=r.dot?"":_,C=r.dot?O:W,M=r.bash===!0?E(r):G;r.capture&&(M=`(${M})`),typeof r.noext=="boolean"&&(r.noextglob=r.noext);let l={input:e,index:-1,start:0,dot:r.dot===!0,consumed:"",output:"",prefix:"",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:i};e=J.removePrefix(e,l),s=e.length;let H=[],w=[],j=[],c=a,u,I=()=>l.index===s-1,$=l.peek=(m=1)=>e[l.index+m],ee=l.advance=()=>e[++l.index]||"",se=()=>e.slice(l.index+1),z=(m="",L=0)=>{l.consumed+=m,l.index+=L},Ce=m=>{l.output+=m.output!=null?m.output:m.value,z(m.value)},xr=()=>{let m=1;for(;$()==="!"&&($(2)!=="("||$(3)==="?");)ee(),l.start++,m++;return m%2==0?!1:(l.negated=!0,l.start++,!0)},we=m=>{l[m]++,j.push(m)},ue=m=>{l[m]--,j.pop()},v=m=>{if(c.type==="globstar"){let L=l.braces>0&&(m.type==="comma"||m.type==="brace"),d=m.extglob===!0||H.length&&(m.type==="pipe"||m.type==="paren");m.type!=="slash"&&m.type!=="paren"&&!L&&!d&&(l.output=l.output.slice(0,-c.output.length),c.type="star",c.value="*",c.output=M,l.output+=c.output)}if(H.length&&m.type!=="paren"&&(H[H.length-1].inner+=m.value),(m.value||m.output)&&Ce(m),c&&c.type==="text"&&m.type==="text"){c.value+=m.value,c.output=(c.output||"")+m.value;return}m.prev=c,i.push(m),c=m},Se=(m,L)=>{let d=Q(B({},f[L]),{conditions:1,inner:""});d.prev=c,d.parens=l.parens,d.output=l.output;let S=(r.capture?"(":"")+d.open;we("parens"),v({type:m,value:L,output:l.output?"":y}),v({type:"paren",extglob:!0,value:ee(),output:S}),H.push(d)},Cr=m=>{let L=m.close+(r.capture?")":""),d;if(m.type==="negate"){let S=M;m.inner&&m.inner.length>1&&m.inner.includes("/")&&(S=E(r)),(S!==M||I()||/^\)+$/.test(se()))&&(L=m.close=`)$))${S}`),m.inner.includes("*")&&(d=se())&&/^\.[^\\/.]+$/.test(d)&&(L=m.close=`)${d})${S})`),m.prev.type==="bos"&&(l.negatedExtglob=!0)}v({type:"paren",extglob:!0,value:u,output:L}),ue("parens")};if(r.fastpaths!==!1&&!/(^[*!]|[/()[\]{}"])/.test(e)){let m=!1,L=e.replace(Wn,(d,S,P,F,q,Me)=>F==="\\"?(m=!0,d):F==="?"?S?S+F+(q?O.repeat(q.length):""):Me===0?C+(q?O.repeat(q.length):""):O.repeat(P.length):F==="."?A.repeat(P.length):F==="*"?S?S+F+(q?M:""):M:S?d:`\\${d}`);return m===!0&&(r.unescape===!0?L=L.replace(/\\/g,""):L=L.replace(/\\+/g,d=>d.length%2==0?"\\\\":d?"\\":"")),L===e&&r.contains===!0?(l.output=e,l):(l.output=J.wrapOutput(L,l,t),l)}for(;!I();){if(u=ee(),u==="\0")continue;if(u==="\\"){let d=$();if(d==="/"&&r.bash!==!0||d==="."||d===";")continue;if(!d){u+="\\",v({type:"text",value:u});continue}let S=/^\\+/.exec(se()),P=0;if(S&&S[0].length>2&&(P=S[0].length,l.index+=P,P%2!=0&&(u+="\\")),r.unescape===!0?u=ee():u+=ee(),l.brackets===0){v({type:"text",value:u});continue}}if(l.brackets>0&&(u!=="]"||c.value==="["||c.value==="[^")){if(r.posix!==!1&&u===":"){let d=c.value.slice(1);if(d.includes("[")&&(c.posix=!0,d.includes(":"))){let S=c.value.lastIndexOf("["),P=c.value.slice(0,S),F=c.value.slice(S+2),q=qn[F];if(q){c.value=P+q,l.backtrack=!0,ee(),!a.output&&i.indexOf(c)===1&&(a.output=y);continue}}}(u==="["&&$()!==":"||u==="-"&&$()==="]")&&(u=`\\${u}`),u==="]"&&(c.value==="["||c.value==="[^")&&(u=`\\${u}`),r.posix===!0&&u==="!"&&c.value==="["&&(u="^"),c.value+=u,Ce({value:u});continue}if(l.quotes===1&&u!=='"'){u=J.escapeRegex(u),c.value+=u,Ce({value:u});continue}if(u==='"'){l.quotes=l.quotes===1?0:1,r.keepQuotes===!0&&v({type:"text",value:u});continue}if(u==="("){we("parens"),v({type:"paren",value:u});continue}if(u===")"){if(l.parens===0&&r.strictBrackets===!0)throw new SyntaxError(de("opening","("));let d=H[H.length-1];if(d&&l.parens===d.parens+1){Cr(H.pop());continue}v({type:"paren",value:u,output:l.parens?")":"\\)"}),ue("parens");continue}if(u==="["){if(r.nobracket===!0||!se().includes("]")){if(r.nobracket!==!0&&r.strictBrackets===!0)throw new SyntaxError(de("closing","]"));u=`\\${u}`}else we("brackets");v({type:"bracket",value:u});continue}if(u==="]"){if(r.nobracket===!0||c&&c.type==="bracket"&&c.value.length===1){v({type:"text",value:u,output:`\\${u}`});continue}if(l.brackets===0){if(r.strictBrackets===!0)throw new SyntaxError(de("opening","["));v({type:"text",value:u,output:`\\${u}`});continue}ue("brackets");let d=c.value.slice(1);if(c.posix!==!0&&d[0]==="^"&&!d.includes("/")&&(u=`/${u}`),c.value+=u,Ce({value:u}),r.literalBrackets===!1||J.hasRegexChars(d))continue;let S=J.escapeRegex(c.value);if(l.output=l.output.slice(0,-c.value.length),r.literalBrackets===!0){l.output+=S,c.value=S;continue}c.value=`(${o}${S}|${c.value})`,l.output+=c.value;continue}if(u==="{"&&r.nobrace!==!0){we("braces");let d={type:"brace",value:u,output:"(",outputIndex:l.output.length,tokensIndex:l.tokens.length};w.push(d),v(d);continue}if(u==="}"){let d=w[w.length-1];if(r.nobrace===!0||!d){v({type:"text",value:u,output:u});continue}let S=")";if(d.dots===!0){let P=i.slice(),F=[];for(let q=P.length-1;q>=0&&(i.pop(),P[q].type!=="brace");q--)P[q].type!=="dots"&&F.unshift(P[q].value);S=jn(F,r),l.backtrack=!0}if(d.comma!==!0&&d.dots!==!0){let P=l.output.slice(0,d.outputIndex),F=l.tokens.slice(d.tokensIndex);d.value=d.output="\\{",u=S="\\}",l.output=P;for(let q of F)l.output+=q.output||q.value}v({type:"brace",value:u,output:S}),ue("braces"),w.pop();continue}if(u==="|"){H.length>0&&H[H.length-1].conditions++,v({type:"text",value:u});continue}if(u===","){let d=u,S=w[w.length-1];S&&j[j.length-1]==="braces"&&(S.comma=!0,d="|"),v({type:"comma",value:u,output:d});continue}if(u==="/"){if(c.type==="dot"&&l.index===l.start+1){l.start=l.index+1,l.consumed="",l.output="",i.pop(),c=a;continue}v({type:"slash",value:u,output:k});continue}if(u==="."){if(l.braces>0&&c.type==="dot"){c.value==="."&&(c.output=A);let d=w[w.length-1];c.type="dots",c.output+=u,c.value+=u,d.dots=!0;continue}if(l.braces+l.parens===0&&c.type!=="bos"&&c.type!=="slash"){v({type:"text",value:u,output:A});continue}v({type:"dot",value:u,output:A});continue}if(u==="?"){if(!(c&&c.value==="(")&&r.noextglob!==!0&&$()==="("&&$(2)!=="?"){Se("qmark",u);continue}if(c&&c.type==="paren"){let S=$(),P=u;if(S==="<"&&!J.supportsLookbehinds())throw new Error("Node.js v10 or higher is required for regex lookbehinds");(c.value==="("&&!/[!=<:]/.test(S)||S==="<"&&!/<([!=]|\w+>)/.test(se()))&&(P=`\\${u}`),v({type:"text",value:u,output:P});continue}if(r.dot!==!0&&(c.type==="slash"||c.type==="bos")){v({type:"qmark",value:u,output:W});continue}v({type:"qmark",value:u,output:O});continue}if(u==="!"){if(r.noextglob!==!0&&$()==="("&&($(2)!=="?"||!/[!=<:]/.test($(3)))){Se("negate",u);continue}if(r.nonegate!==!0&&l.index===0){xr();continue}}if(u==="+"){if(r.noextglob!==!0&&$()==="("&&$(2)!=="?"){Se("plus",u);continue}if(c&&c.value==="("||r.regex===!1){v({type:"plus",value:u,output:p});continue}if(c&&(c.type==="bracket"||c.type==="paren"||c.type==="brace")||l.parens>0){v({type:"plus",value:u});continue}v({type:"plus",value:p});continue}if(u==="@"){if(r.noextglob!==!0&&$()==="("&&$(2)!=="?"){v({type:"at",extglob:!0,value:u,output:""});continue}v({type:"text",value:u});continue}if(u!=="*"){(u==="$"||u==="^")&&(u=`\\${u}`);let d=Kn.exec(se());d&&(u+=d[0],l.index+=d[0].length),v({type:"text",value:u});continue}if(c&&(c.type==="globstar"||c.star===!0)){c.type="star",c.star=!0,c.value+=u,c.output=M,l.backtrack=!0,l.globstar=!0,z(u);continue}let m=se();if(r.noextglob!==!0&&/^\([^?]/.test(m)){Se("star",u);continue}if(c.type==="star"){if(r.noglobstar===!0){z(u);continue}let d=c.prev,S=d.prev,P=d.type==="slash"||d.type==="bos",F=S&&(S.type==="star"||S.type==="globstar");if(r.bash===!0&&(!P||m[0]&&m[0]!=="/")){v({type:"star",value:u,output:""});continue}let q=l.braces>0&&(d.type==="comma"||d.type==="brace"),Me=H.length&&(d.type==="pipe"||d.type==="paren");if(!P&&d.type!=="paren"&&!q&&!Me){v({type:"star",value:u,output:""});continue}for(;m.slice(0,3)==="/**";){let ve=e[l.index+4];if(ve&&ve!=="/")break;m=m.slice(3),z("/**",3)}if(d.type==="bos"&&I()){c.type="globstar",c.value+=u,c.output=E(r),l.output=c.output,l.globstar=!0,z(u);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&!F&&I()){l.output=l.output.slice(0,-(d.output+c.output).length),d.output=`(?:${d.output}`,c.type="globstar",c.output=E(r)+(r.strictSlashes?")":"|$)"),c.value+=u,l.globstar=!0,l.output+=d.output+c.output,z(u);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&m[0]==="/"){let ve=m[1]!==void 0?"|$":"";l.output=l.output.slice(0,-(d.output+c.output).length),d.output=`(?:${d.output}`,c.type="globstar",c.output=`${E(r)}${k}|${k}${ve})`,c.value+=u,l.output+=d.output+c.output,l.globstar=!0,z(u+ee()),v({type:"slash",value:"/",output:""});continue}if(d.type==="bos"&&m[0]==="/"){c.type="globstar",c.value+=u,c.output=`(?:^|${k}|${E(r)}${k})`,l.output=c.output,l.globstar=!0,z(u+ee()),v({type:"slash",value:"/",output:""});continue}l.output=l.output.slice(0,-c.output.length),c.type="globstar",c.output=E(r),c.value+=u,l.output+=c.output,l.globstar=!0,z(u);continue}let L={type:"star",value:u,output:M};if(r.bash===!0){L.output=".*?",(c.type==="bos"||c.type==="slash")&&(L.output=b+L.output),v(L);continue}if(c&&(c.type==="bracket"||c.type==="paren")&&r.regex===!0){L.output=u,v(L);continue}(l.index===l.start||c.type==="slash"||c.type==="dot")&&(c.type==="dot"?(l.output+=x,c.output+=x):r.dot===!0?(l.output+=T,c.output+=T):(l.output+=b,c.output+=b),$()!=="*"&&(l.output+=y,c.output+=y)),v(L)}for(;l.brackets>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing","]"));l.output=J.escapeLast(l.output,"["),ue("brackets")}for(;l.parens>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing",")"));l.output=J.escapeLast(l.output,"("),ue("parens")}for(;l.braces>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing","}"));l.output=J.escapeLast(l.output,"{"),ue("braces")}if(r.strictSlashes!==!0&&(c.type==="star"||c.type==="bracket")&&v({type:"maybe_slash",value:"",output:`${k}?`}),l.backtrack===!0){l.output="";for(let m of l.tokens)l.output+=m.output!=null?m.output:m.value,m.suffix&&(l.output+=m.suffix)}return l};nr.fastpaths=(e,t)=>{let r=B({},t),n=typeof r.maxLength=="number"?Math.min(Ne,r.maxLength):Ne,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);e=rr[e]||e;let a=J.isWindows(t),{DOT_LITERAL:i,SLASH_LITERAL:o,ONE_CHAR:h,DOTS_SLASH:g,NO_DOT:f,NO_DOTS:A,NO_DOTS_SLASH:p,STAR:k,START_ANCHOR:y}=Oe.globChars(a),R=r.dot?A:f,_=r.dot?p:f,x=r.capture?"":"?:",T={negated:!1,prefix:""},O=r.bash===!0?".*?":k;r.capture&&(O=`(${O})`);let W=b=>b.noglobstar===!0?O:`(${x}(?:(?!${y}${b.dot?g:i}).)*?)`,G=b=>{switch(b){case"*":return`${R}${h}${O}`;case".*":return`${i}${h}${O}`;case"*.*":return`${R}${O}${i}${h}${O}`;case"*/*":return`${R}${O}${o}${h}${_}${O}`;case"**":return R+W(r);case"**/*":return`(?:${R}${W(r)}${o})?${_}${h}${O}`;case"**/*.*":return`(?:${R}${W(r)}${o})?${_}${O}${i}${h}${O}`;case"**/.*":return`(?:${R}${W(r)}${o})?${i}${h}${O}`;default:{let C=/^(.*?)\.(\w+)$/.exec(b);if(!C)return;let M=G(C[1]);return M?M+i+C[2]:void 0}}},ne=J.removePrefix(e,T),E=G(ne);return E&&r.strictSlashes!==!0&&(E+=`${o}?`),E};tr.exports=nr});var ir=K((ys,ar)=>{"use strict";var Fn=require("path"),Qn=er(),Ye=sr(),ze=be(),Xn=ye(),Zn=e=>e&&typeof e=="object"&&!Array.isArray(e),D=(e,t,r=!1)=>{if(Array.isArray(e)){let f=e.map(p=>D(p,t,r));return p=>{for(let k of f){let y=k(p);if(y)return y}return!1}}let n=Zn(e)&&e.tokens&&e.input;if(e===""||typeof e!="string"&&!n)throw new TypeError("Expected pattern to be a non-empty string");let s=t||{},a=ze.isWindows(t),i=n?D.compileRe(e,t):D.makeRe(e,t,!1,!0),o=i.state;delete i.state;let h=()=>!1;if(s.ignore){let f=Q(B({},t),{ignore:null,onMatch:null,onResult:null});h=D(s.ignore,f,r)}let g=(f,A=!1)=>{let{isMatch:p,match:k,output:y}=D.test(f,i,t,{glob:e,posix:a}),R={glob:e,state:o,regex:i,posix:a,input:f,output:y,match:k,isMatch:p};return typeof s.onResult=="function"&&s.onResult(R),p===!1?(R.isMatch=!1,A?R:!1):h(f)?(typeof s.onIgnore=="function"&&s.onIgnore(R),R.isMatch=!1,A?R:!1):(typeof s.onMatch=="function"&&s.onMatch(R),A?R:!0)};return r&&(g.state=o),g};D.test=(e,t,r,{glob:n,posix:s}={})=>{if(typeof e!="string")throw new TypeError("Expected input to be a string");if(e==="")return{isMatch:!1,output:""};let a=r||{},i=a.format||(s?ze.toPosixSlashes:null),o=e===n,h=o&&i?i(e):e;return o===!1&&(h=i?i(e):e,o=h===n),(o===!1||a.capture===!0)&&(a.matchBase===!0||a.basename===!0?o=D.matchBase(e,t,r,s):o=t.exec(h)),{isMatch:Boolean(o),match:o,output:h}};D.matchBase=(e,t,r,n=ze.isWindows(r))=>(t instanceof RegExp?t:D.makeRe(t,r)).test(Fn.basename(e));D.isMatch=(e,t,r)=>D(t,r)(e);D.parse=(e,t)=>Array.isArray(e)?e.map(r=>D.parse(r,t)):Ye(e,Q(B({},t),{fastpaths:!1}));D.scan=(e,t)=>Qn(e,t);D.compileRe=(e,t,r=!1,n=!1)=>{if(r===!0)return e.output;let s=t||{},a=s.contains?"":"^",i=s.contains?"":"$",o=`${a}(?:${e.output})${i}`;e&&e.negated===!0&&(o=`^(?!${o}).*$`);let h=D.toRegex(o,t);return n===!0&&(h.state=e),h};D.makeRe=(e,t={},r=!1,n=!1)=>{if(!e||typeof e!="string")throw new TypeError("Expected a non-empty string");let s={negated:!1,fastpaths:!0};return t.fastpaths!==!1&&(e[0]==="."||e[0]==="*")&&(s.output=Ye.fastpaths(e,t)),s.output||(s=Ye(e,t)),D.compileRe(s,t,r,n)};D.toRegex=(e,t)=>{try{let r=t||{};return new RegExp(e,r.flags||(r.nocase?"i":""))}catch(r){if(t&&t.debug===!0)throw r;return/$^/}};D.constants=Xn;ar.exports=D});var cr=K((bs,or)=>{"use strict";or.exports=ir()});var hr=K((_s,ur)=>{"use strict";var lr=require("util"),pr=Gt(),oe=cr(),Ve=be(),fr=e=>e===""||e==="./",N=(e,t,r)=>{t=[].concat(t),e=[].concat(e);let n=new Set,s=new Set,a=new Set,i=0,o=f=>{a.add(f.output),r&&r.onResult&&r.onResult(f)};for(let f=0;f!n.has(f));if(r&&g.length===0){if(r.failglob===!0)throw new Error(`No matches found for "${t.join(", ")}"`);if(r.nonull===!0||r.nullglob===!0)return r.unescape?t.map(f=>f.replace(/\\/g,"")):t}return g};N.match=N;N.matcher=(e,t)=>oe(e,t);N.isMatch=(e,t,r)=>oe(t,r)(e);N.any=N.isMatch;N.not=(e,t,r={})=>{t=[].concat(t).map(String);let n=new Set,s=[],a=o=>{r.onResult&&r.onResult(o),s.push(o.output)},i=N(e,t,Q(B({},r),{onResult:a}));for(let o of s)i.includes(o)||n.add(o);return[...n]};N.contains=(e,t,r)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${lr.inspect(e)}"`);if(Array.isArray(t))return t.some(n=>N.contains(e,n,r));if(typeof t=="string"){if(fr(e)||fr(t))return!1;if(e.includes(t)||e.startsWith("./")&&e.slice(2).includes(t))return!0}return N.isMatch(e,t,Q(B({},r),{contains:!0}))};N.matchKeys=(e,t,r)=>{if(!Ve.isObject(e))throw new TypeError("Expected the first argument to be an object");let n=N(Object.keys(e),t,r),s={};for(let a of n)s[a]=e[a];return s};N.some=(e,t,r)=>{let n=[].concat(e);for(let s of[].concat(t)){let a=oe(String(s),r);if(n.some(i=>a(i)))return!0}return!1};N.every=(e,t,r)=>{let n=[].concat(e);for(let s of[].concat(t)){let a=oe(String(s),r);if(!n.every(i=>a(i)))return!1}return!0};N.all=(e,t,r)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${lr.inspect(e)}"`);return[].concat(t).every(n=>oe(n,r)(e))};N.capture=(e,t,r)=>{let n=Ve.isWindows(r),a=oe.makeRe(String(e),Q(B({},r),{capture:!0})).exec(n?Ve.toPosixSlashes(t):t);if(a)return a.slice(1).map(i=>i===void 0?"":i)};N.makeRe=(...e)=>oe.makeRe(...e);N.scan=(...e)=>oe.scan(...e);N.parse=(e,t)=>{let r=[];for(let n of[].concat(e||[]))for(let s of pr(String(n),t))r.push(oe.parse(s,t));return r};N.braces=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");return t&&t.nobrace===!0||!/\{.*\}/.test(e)?[e]:pr(e,t)};N.braceExpand=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");return N.braces(e,Q(B({},t),{expand:!0}))};ur.exports=N});var gr=K((Es,dr)=>{"use strict";dr.exports=(e,...t)=>new Promise(r=>{r(e(...t))})});var Ar=K((xs,Je)=>{"use strict";var Yn=gr(),mr=e=>{if(e<1)throw new TypeError("Expected `concurrency` to be a number from 1 and up");let t=[],r=0,n=()=>{r--,t.length>0&&t.shift()()},s=(o,h,...g)=>{r++;let f=Yn(o,...g);h(f),f.then(n,n)},a=(o,h,...g)=>{rnew Promise(g=>a(o,g,...h));return Object.defineProperties(i,{activeCount:{get:()=>r},pendingCount:{get:()=>t.length}}),i};Je.exports=mr;Je.exports.default=mr});var Vn={};Or(Vn,{default:()=>es});var He=X(require("@yarnpkg/cli")),ae=X(require("@yarnpkg/core")),nt=X(require("@yarnpkg/core")),le=X(require("clipanion")),Ae=class extends He.BaseCommand{constructor(){super(...arguments);this.json=le.Option.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.production=le.Option.Boolean("--production",!1,{description:"Only install regular dependencies by omitting dev dependencies"});this.all=le.Option.Boolean("-A,--all",!1,{description:"Install the entire project"});this.workspaces=le.Option.Rest()}async execute(){let t=await ae.Configuration.find(this.context.cwd,this.context.plugins),{project:r,workspace:n}=await ae.Project.find(t,this.context.cwd),s=await ae.Cache.find(t);await r.restoreInstallState({restoreResolutions:!1});let a;if(this.all)a=new Set(r.workspaces);else if(this.workspaces.length===0){if(!n)throw new He.WorkspaceRequiredError(r.cwd,this.context.cwd);a=new Set([n])}else a=new Set(this.workspaces.map(o=>r.getWorkspaceByIdent(nt.structUtils.parseIdent(o))));for(let o of a)for(let h of this.production?["dependencies"]:ae.Manifest.hardDependencies)for(let g of o.manifest.getForScope(h).values()){let f=r.tryWorkspaceByDescriptor(g);f!==null&&a.add(f)}for(let o of r.workspaces)a.has(o)?this.production&&o.manifest.devDependencies.clear():(o.manifest.installConfig=o.manifest.installConfig||{},o.manifest.installConfig.selfReferences=!1,o.manifest.dependencies.clear(),o.manifest.devDependencies.clear(),o.manifest.peerDependencies.clear(),o.manifest.scripts.clear());return(await ae.StreamReport.start({configuration:t,json:this.json,stdout:this.context.stdout,includeLogs:!0},async o=>{await r.install({cache:s,report:o,persistProject:!1})})).exitCode()}};Ae.paths=[["workspaces","focus"]],Ae.usage=le.Command.Usage({category:"Workspace-related commands",description:"install a single workspace and its dependencies",details:"\n This command will run an install as if the specified workspaces (and all other workspaces they depend on) were the only ones in the project. If no workspaces are explicitly listed, the active one will be assumed.\n\n Note that this command is only very moderately useful when using zero-installs, since the cache will contain all the packages anyway - meaning that the only difference between a full install and a focused install would just be a few extra lines in the `.pnp.cjs` file, at the cost of introducing an extra complexity.\n\n If the `-A,--all` flag is set, the entire project will be installed. Combine with `--production` to replicate the old `yarn install --production`.\n "});var st=Ae;var Ie=X(require("@yarnpkg/cli")),ge=X(require("@yarnpkg/core")),Ee=X(require("@yarnpkg/core")),Y=X(require("@yarnpkg/core")),Rr=X(require("@yarnpkg/plugin-git")),U=X(require("clipanion")),Be=X(hr()),yr=X(require("os")),br=X(Ar()),re=X(require("typanion")),xe=class extends Ie.BaseCommand{constructor(){super(...arguments);this.recursive=U.Option.Boolean("-R,--recursive",!1,{description:"Find packages via dependencies/devDependencies instead of using the workspaces field"});this.from=U.Option.Array("--from",[],{description:"An array of glob pattern idents from which to base any recursion"});this.all=U.Option.Boolean("-A,--all",!1,{description:"Run the command on all workspaces of a project"});this.verbose=U.Option.Boolean("-v,--verbose",!1,{description:"Prefix each output line with the name of the originating workspace"});this.parallel=U.Option.Boolean("-p,--parallel",!1,{description:"Run the commands in parallel"});this.interlaced=U.Option.Boolean("-i,--interlaced",!1,{description:"Print the output of commands in real-time instead of buffering it"});this.jobs=U.Option.String("-j,--jobs",{description:"The maximum number of parallel tasks that the execution will be limited to; or `unlimited`",validator:re.isOneOf([re.isEnum(["unlimited"]),re.applyCascade(re.isNumber(),[re.isInteger(),re.isAtLeast(1)])])});this.topological=U.Option.Boolean("-t,--topological",!1,{description:"Run the command after all workspaces it depends on (regular) have finished"});this.topologicalDev=U.Option.Boolean("--topological-dev",!1,{description:"Run the command after all workspaces it depends on (regular + dev) have finished"});this.include=U.Option.Array("--include",[],{description:"An array of glob pattern idents; only matching workspaces will be traversed"});this.exclude=U.Option.Array("--exclude",[],{description:"An array of glob pattern idents; matching workspaces won't be traversed"});this.publicOnly=U.Option.Boolean("--no-private",{description:"Avoid running the command on private workspaces"});this.since=U.Option.String("--since",{description:"Only include workspaces that have been changed since the specified ref.",tolerateBoolean:!0});this.commandName=U.Option.String();this.args=U.Option.Proxy()}async execute(){let t=await ge.Configuration.find(this.context.cwd,this.context.plugins),{project:r,workspace:n}=await ge.Project.find(t,this.context.cwd);if(!this.all&&!n)throw new Ie.WorkspaceRequiredError(r.cwd,this.context.cwd);await r.restoreInstallState();let s=this.cli.process([this.commandName,...this.args]),a=s.path.length===1&&s.path[0]==="run"&&typeof s.scriptName!="undefined"?s.scriptName:null;if(s.path.length===0)throw new U.UsageError("Invalid subcommand name for iteration - use the 'run' keyword if you wish to execute a script");let i=this.all?r.topLevelWorkspace:n,o=this.since?Array.from(await Rr.gitUtils.fetchChangedWorkspaces({ref:this.since,project:r})):[i,...this.from.length>0?i.getRecursiveWorkspaceChildren():[]],h=E=>Be.default.isMatch(Y.structUtils.stringifyIdent(E.locator),this.from),g=this.from.length>0?o.filter(h):o,f=new Set([...g,...g.map(E=>[...this.recursive?this.since?E.getRecursiveWorkspaceDependents():E.getRecursiveWorkspaceDependencies():E.getRecursiveWorkspaceChildren()]).flat()]),A=[],p=!1;if(a==null?void 0:a.includes(":")){for(let E of r.workspaces)if(E.manifest.scripts.has(a)&&(p=!p,p===!1))break}for(let E of f)a&&!E.manifest.scripts.has(a)&&!p&&!(await ge.scriptUtils.getWorkspaceAccessibleBinaries(E)).has(a)||a===process.env.npm_lifecycle_event&&E.cwd===n.cwd||this.include.length>0&&!Be.default.isMatch(Y.structUtils.stringifyIdent(E.locator),this.include)||this.exclude.length>0&&Be.default.isMatch(Y.structUtils.stringifyIdent(E.locator),this.exclude)||this.publicOnly&&E.manifest.private===!0||A.push(E);let k=this.parallel?this.jobs==="unlimited"?Infinity:this.jobs||Math.max(1,(0,yr.cpus)().length/2):1,y=k===1?!1:this.parallel,R=y?this.interlaced:!0,_=(0,br.default)(k),x=new Map,T=new Set,O=0,W=null,G=!1,ne=await Ee.StreamReport.start({configuration:t,stdout:this.context.stdout},async E=>{let b=async(C,{commandIndex:M})=>{if(G)return-1;!y&&this.verbose&&M>1&&E.reportSeparator();let l=zn(C,{configuration:t,verbose:this.verbose,commandIndex:M}),[H,w]=_r(E,{prefix:l,interlaced:R}),[j,c]=_r(E,{prefix:l,interlaced:R});try{this.verbose&&E.reportInfo(null,`${l} Process started`);let u=Date.now(),I=await this.cli.run([this.commandName,...this.args],{cwd:C.cwd,stdout:H,stderr:j})||0;H.end(),j.end(),await w,await c;let $=Date.now();if(this.verbose){let ee=t.get("enableTimers")?`, completed in ${Y.formatUtils.pretty(t,$-u,Y.formatUtils.Type.DURATION)}`:"";E.reportInfo(null,`${l} Process exited (exit code ${I})${ee}`)}return I===130&&(G=!0,W=I),I}catch(u){throw H.end(),j.end(),await w,await c,u}};for(let C of A)x.set(C.anchoredLocator.locatorHash,C);for(;x.size>0&&!E.hasErrors();){let C=[];for(let[H,w]of x){if(T.has(w.anchoredDescriptor.descriptorHash))continue;let j=!0;if(this.topological||this.topologicalDev){let c=this.topologicalDev?new Map([...w.manifest.dependencies,...w.manifest.devDependencies]):w.manifest.dependencies;for(let u of c.values()){let I=r.tryWorkspaceByDescriptor(u);if(j=I===null||!x.has(I.anchoredLocator.locatorHash),!j)break}}if(!!j&&(T.add(w.anchoredDescriptor.descriptorHash),C.push(_(async()=>{let c=await b(w,{commandIndex:++O});return x.delete(H),T.delete(w.anchoredDescriptor.descriptorHash),c})),!y))break}if(C.length===0){let H=Array.from(x.values()).map(w=>Y.structUtils.prettyLocator(t,w.anchoredLocator)).join(", ");E.reportError(Ee.MessageName.CYCLIC_DEPENDENCIES,`Dependency cycle detected (${H})`);return}let l=(await Promise.all(C)).find(H=>H!==0);W===null&&(W=typeof l!="undefined"?1:W),(this.topological||this.topologicalDev)&&typeof l!="undefined"&&E.reportError(Ee.MessageName.UNNAMED,"The command failed for workspaces that are depended upon by other workspaces; can't satisfy the dependency graph")}});return W!==null?W:ne.exitCode()}};xe.paths=[["workspaces","foreach"]],xe.usage=U.Command.Usage({category:"Workspace-related commands",description:"run a command on all workspaces",details:"\n This command will run a given sub-command on current and all its descendant workspaces. Various flags can alter the exact behavior of the command:\n\n - If `-p,--parallel` is set, the commands will be ran in parallel; they'll by default be limited to a number of parallel tasks roughly equal to half your core number, but that can be overridden via `-j,--jobs`, or disabled by setting `-j unlimited`.\n\n - If `-p,--parallel` and `-i,--interlaced` are both set, Yarn will print the lines from the output as it receives them. If `-i,--interlaced` wasn't set, it would instead buffer the output from each process and print the resulting buffers only after their source processes have exited.\n\n - If `-t,--topological` is set, Yarn will only run the command after all workspaces that it depends on through the `dependencies` field have successfully finished executing. If `--topological-dev` is set, both the `dependencies` and `devDependencies` fields will be considered when figuring out the wait points.\n\n - If `-A,--all` is set, Yarn will run the command on all the workspaces of a project. By default yarn runs the command only on current and all its descendant workspaces.\n\n - If `-R,--recursive` is set, Yarn will find workspaces to run the command on by recursively evaluating `dependencies` and `devDependencies` fields, instead of looking at the `workspaces` fields.\n\n - If `--from` is set, Yarn will use the packages matching the 'from' glob as the starting point for any recursive search.\n\n - If `--since` is set, Yarn will only run the command on workspaces that have been modified since the specified ref. By default Yarn will use the refs specified by the `changesetBaseRefs` configuration option.\n\n - The command may apply to only some workspaces through the use of `--include` which acts as a whitelist. The `--exclude` flag will do the opposite and will be a list of packages that mustn't execute the script. Both flags accept glob patterns (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n\n Adding the `-v,--verbose` flag will cause Yarn to print more information; in particular the name of the workspace that generated the output will be printed at the front of each line.\n\n If the command is `run` and the script being run does not exist the child workspace will be skipped without error.\n ",examples:[["Publish current and all descendant packages","yarn workspaces foreach npm publish --tolerate-republish"],["Run build script on current and all descendant packages","yarn workspaces foreach run build"],["Run build script on current and all descendant packages in parallel, building package dependencies first","yarn workspaces foreach -pt run build"],["Run build script on several packages and all their dependencies, building dependencies first","yarn workspaces foreach -ptR --from '{workspace-a,workspace-b}' run build"]]});var Er=xe;function _r(e,{prefix:t,interlaced:r}){let n=e.createStreamReporter(t),s=new Y.miscUtils.DefaultStream;s.pipe(n,{end:!1}),s.on("finish",()=>{n.end()});let a=new Promise(o=>{n.on("finish",()=>{o(s.active)})});if(r)return[s,a];let i=new Y.miscUtils.BufferStream;return i.pipe(s,{end:!1}),i.on("finish",()=>{s.end()}),[i,a]}function zn(e,{configuration:t,commandIndex:r,verbose:n}){if(!n)return null;let s=Y.structUtils.convertToIdent(e.locator),i=`[${Y.structUtils.stringifyIdent(s)}]:`,o=["#2E86AB","#A23B72","#F18F01","#C73E1D","#CCE2A3"],h=o[r%o.length];return Y.formatUtils.pretty(t,i,h)}var Jn={commands:[st,Er]},es=Jn;return Vn;})(); -/*! - * fill-range - * - * Copyright (c) 2014-present, Jon Schlinkert. - * Licensed under the MIT License. - */ -/*! - * is-number - * - * Copyright (c) 2014-present, Jon Schlinkert. - * Released under the MIT License. - */ -/*! - * to-regex-range - * - * Copyright (c) 2015-present, Jon Schlinkert. - * Released under the MIT License. - */ -return plugin; -} -}; diff --git a/packages/cli-template-monorepo-subgraph/.yarn/releases/yarn-3.2.1.cjs.REMOVED.git-id b/packages/cli-template-monorepo-subgraph/.yarn/releases/yarn-3.2.1.cjs.REMOVED.git-id deleted file mode 100644 index ee9f521b7..000000000 --- a/packages/cli-template-monorepo-subgraph/.yarn/releases/yarn-3.2.1.cjs.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -b3cadff6efb37a12712d12c2553ec703dbcaa4dd \ No newline at end of file diff --git a/packages/cli-template-monorepo-subgraph/.yarn/releases/yarn-4.1.0.cjs.REMOVED.git-id b/packages/cli-template-monorepo-subgraph/.yarn/releases/yarn-4.1.0.cjs.REMOVED.git-id new file mode 100644 index 000000000..430c13f99 --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/.yarn/releases/yarn-4.1.0.cjs.REMOVED.git-id @@ -0,0 +1 @@ +738adce5914a0e193f2e1255e4dcf7042256a1c1 \ No newline at end of file diff --git a/packages/cli-template-monorepo-subgraph/.yarnrc.yml b/packages/cli-template-monorepo-subgraph/.yarnrc.yml index 8d06f86a2..bd95ab27f 100644 --- a/packages/cli-template-monorepo-subgraph/.yarnrc.yml +++ b/packages/cli-template-monorepo-subgraph/.yarnrc.yml @@ -1,9 +1,7 @@ -checksumBehavior: update +compressionLevel: mixed -nodeLinker: node-modules +enableGlobalCache: false -plugins: - - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs - spec: "@yarnpkg/plugin-workspace-tools" +nodeLinker: node-modules -yarnPath: .yarn/releases/yarn-3.2.1.cjs +yarnPath: .yarn/releases/yarn-4.1.0.cjs diff --git a/packages/cli-template-monorepo-subgraph/README.md b/packages/cli-template-monorepo-subgraph/README.md index efc383bad..4d7b1b3ee 100644 --- a/packages/cli-template-monorepo-subgraph/README.md +++ b/packages/cli-template-monorepo-subgraph/README.md @@ -1,16 +1,16 @@ -# Semaphore Hardhat + Next.js + SemaphoreEthers template +# Semaphore Hardhat + Next.js + SemaphoreSubgraph template This project is a complete application that demonstrates a basic Semaphore use case. It comes with a sample contract, a test for that contract and a sample task that deploys that contract. It also contains a frontend to play around with the contract. -## 📜 Usage +## Install -Copy the `.env.example` file as `.env`: +### Install dependencies ```bash -cp .env.example .env +yarn ``` -and add your environment variables or run the app in a local network. +## 📜 Usage ### Local server @@ -25,12 +25,12 @@ yarn dev 1. Go to the `apps/contracts` directory and deploy your contract: ```bash -yarn deploy --semaphore --group --network arbitrum-goerli +yarn deploy --semaphore --group --network arbitrum-sepolia ``` 2. Update your `.env` file with your new contract address, the group id and the semaphore contract address. -3. Copy your contract artifacts from `apps/contracts/build/contracts/contracts` folder to `apps/web-app/contract-artifacts` folders manually. Or run `yarn copy:contract-artifacts` in the project root to do it automatically. +3. Copy your contract artifacts from `apps/contracts/artifacts/contracts/` folder to `apps/web-app/contract-artifacts` folder manually. > **Note** > Check the Semaphore contract addresses [here](https://docs.semaphore.pse.dev/deployed-contracts). @@ -40,7 +40,7 @@ yarn deploy --semaphore --group --network arbitru ### Code quality and formatting -Run [ESLint](https://eslint.org/) to analyze the code and catch bugs: +Run [ESLint](https://eslint.org/) and [solhint](https://github.com/protofire/solhint) to analyze the code and catch bugs: ```bash yarn lint @@ -52,7 +52,7 @@ Run [Prettier](https://prettier.io/) to check formatting rules: yarn prettier ``` -or to automatically format the code: +Or to automatically format the code: ```bash yarn prettier:write diff --git a/packages/cli-template-monorepo-subgraph/apps/contracts/.eslintignore b/packages/cli-template-monorepo-subgraph/apps/contracts/.eslintignore new file mode 100644 index 000000000..178c1b2a9 --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/contracts/.eslintignore @@ -0,0 +1,16 @@ +# dependencies +node_modules + +# testing +coverage +coverage.json + +# hardhat +artifacts +cache +typechain-types + +# misc +.DS_Store +*.pem + diff --git a/packages/cli-template-monorepo-subgraph/apps/contracts/.eslintrc.json b/packages/cli-template-monorepo-subgraph/apps/contracts/.eslintrc.json new file mode 100644 index 000000000..4cf715c01 --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/contracts/.eslintrc.json @@ -0,0 +1,24 @@ +{ + "root": true, + "env": { + "es6": true + }, + "extends": ["airbnb-base", "airbnb-typescript/base", "prettier"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module", + "project": ["./tsconfig.json", "./packages/**/tsconfig.json"] + }, + "plugins": ["@typescript-eslint"], + "rules": { + "no-underscore-dangle": "off", + "import/no-extraneous-dependencies": "off", + "no-bitwise": "off", + "no-await-in-loop": "off", + "no-restricted-syntax": "off", + "no-console": ["warn", { "allow": ["info", "warn", "error", "log"] }], + "@typescript-eslint/lines-between-class-members": "off", + "no-param-reassign": "off" + } +} diff --git a/packages/cli-template-monorepo-subgraph/apps/contracts/.gitignore b/packages/cli-template-monorepo-subgraph/apps/contracts/.gitignore new file mode 100644 index 000000000..21d9e568f --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/contracts/.gitignore @@ -0,0 +1,8 @@ +# Hardhat files +/cache +/artifacts + +# TypeChain files +/typechain +/typechain-types + diff --git a/packages/cli-template-monorepo-subgraph/apps/contracts/contracts/Feedback.sol b/packages/cli-template-monorepo-subgraph/apps/contracts/contracts/Feedback.sol index 0c846db5a..0a418c4fe 100644 --- a/packages/cli-template-monorepo-subgraph/apps/contracts/contracts/Feedback.sol +++ b/packages/cli-template-monorepo-subgraph/apps/contracts/contracts/Feedback.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.23; import "@semaphore-protocol/contracts/interfaces/ISemaphore.sol"; @@ -12,7 +12,7 @@ contract Feedback { semaphore = ISemaphore(semaphoreAddress); groupId = _groupId; - semaphore.createGroup(groupId, 20, address(this)); + semaphore.createGroup(groupId, address(this)); } function joinGroup(uint256 identityCommitment) external { @@ -20,11 +20,21 @@ contract Feedback { } function sendFeedback( - uint256 feedback, + uint256 merkleTreeDepth, uint256 merkleTreeRoot, - uint256 nullifierHash, - uint256[8] calldata proof + uint256 nullifier, + uint256 feedback, + uint256[8] calldata points ) external { - semaphore.verifyProof(groupId, merkleTreeRoot, feedback, nullifierHash, groupId, proof); + ISemaphore.SemaphoreProof memory proof = ISemaphore.SemaphoreProof( + merkleTreeDepth, + merkleTreeRoot, + nullifier, + feedback, + groupId, + points + ); + + semaphore.validateProof(groupId, proof); } } diff --git a/packages/cli-template-monorepo-subgraph/apps/contracts/hardhat.config.ts b/packages/cli-template-monorepo-subgraph/apps/contracts/hardhat.config.ts index 4e862668c..f5b61eaee 100644 --- a/packages/cli-template-monorepo-subgraph/apps/contracts/hardhat.config.ts +++ b/packages/cli-template-monorepo-subgraph/apps/contracts/hardhat.config.ts @@ -1,15 +1,9 @@ -import "@nomicfoundation/hardhat-chai-matchers" -import "@nomiclabs/hardhat-ethers" -import "@nomiclabs/hardhat-etherscan" -import "@semaphore-protocol/hardhat" -import "@typechain/hardhat" -import { config as dotenvConfig } from "dotenv" -import "hardhat-gas-reporter" import { HardhatUserConfig } from "hardhat/config" import { NetworksUserConfig } from "hardhat/types" +import "@nomicfoundation/hardhat-toolbox" +import { config as dotenvConfig } from "dotenv" import { resolve } from "path" -import "solidity-coverage" -import { config } from "./package.json" +import "@semaphore-protocol/hardhat" import "./tasks/deploy" dotenvConfig({ path: resolve(__dirname, "../../.env") }) @@ -23,11 +17,6 @@ function getNetworks(): NetworksUserConfig { const infuraApiKey = process.env.INFURA_API_KEY return { - goerli: { - url: `https://goerli.infura.io/v3/${infuraApiKey}`, - chainId: 5, - accounts - }, sepolia: { url: `https://sepolia.infura.io/v3/${infuraApiKey}`, chainId: 11155111, @@ -38,14 +27,14 @@ function getNetworks(): NetworksUserConfig { chainId: 80001, accounts }, - "optimism-goerli": { - url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`, - chainId: 420, + "optimism-sepolia": { + url: `https://optimism-sepolia.infura.io/v3/${infuraApiKey}`, + chainId: 11155420, accounts }, - "arbitrum-goerli": { - url: "https://goerli-rollup.arbitrum.io/rpc", - chainId: 421613, + "arbitrum-sepolia": { + url: "https://sepolia-rollup.arbitrum.io/rpc", + chainId: 421614, accounts }, arbitrum: { @@ -56,14 +45,9 @@ function getNetworks(): NetworksUserConfig { } } -const hardhatConfig: HardhatUserConfig = { - solidity: config.solidity, - paths: { - sources: config.paths.contracts, - tests: config.paths.tests, - cache: config.paths.cache, - artifacts: config.paths.build.contracts - }, +const config: HardhatUserConfig = { + solidity: "0.8.23", + defaultNetwork: process.env.DEFAULT_NETWORK || "localhost", networks: { hardhat: { chainId: 1337 @@ -76,12 +60,14 @@ const hardhatConfig: HardhatUserConfig = { coinmarketcap: process.env.COINMARKETCAP_API_KEY }, typechain: { - outDir: config.paths.build.typechain, - target: "ethers-v5" + target: "ethers-v6" }, etherscan: { apiKey: process.env.ETHERSCAN_API_KEY + }, + sourcify: { + enabled: true } } -export default hardhatConfig +export default config diff --git a/packages/cli-template-monorepo-subgraph/apps/contracts/package.json b/packages/cli-template-monorepo-subgraph/apps/contracts/package.json index 1ebbb356f..8635809a2 100644 --- a/packages/cli-template-monorepo-subgraph/apps/contracts/package.json +++ b/packages/cli-template-monorepo-subgraph/apps/contracts/package.json @@ -1,60 +1,54 @@ { "name": "monorepo-subgraph-contracts", - "version": "1.0.0", - "private": true, - "main": "index.js", "scripts": { - "dev": "hardhat node & yarn compile && yarn deploy --network localhost", + "dev": "hardhat node & yarn deploy --network localhost", "compile": "hardhat compile", - "download:snark-artifacts": "hardhat run scripts/download-snark-artifacts.ts", "deploy": "yarn compile && hardhat deploy", - "test": "hardhat run scripts/download-snark-artifacts.ts && hardhat test", + "verify": "hardhat verify", + "test": "hardhat test", "test:report-gas": "REPORT_GAS=true hardhat test", "test:coverage": "hardhat coverage", "typechain": "hardhat typechain", - "lint": "solhint 'contracts/**/*.sol'" + "lint": "eslint . --ext .js,.ts && solhint 'contracts/**/*.sol'", + "prettier": "prettier -c --plugin=prettier-plugin-solidity .", + "prettier:write": "prettier -w --plugin=prettier-plugin-solidity ." }, "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^1.0.5", - "@nomiclabs/hardhat-ethers": "^2.0.0", - "@nomiclabs/hardhat-etherscan": "^3.1.7", - "@semaphore-protocol/group": "3.15.2", - "@semaphore-protocol/hardhat": "3.15.2", - "@semaphore-protocol/identity": "3.15.2", - "@semaphore-protocol/proof": "3.15.2", - "@typechain/ethers-v5": "^10.0.0", - "@typechain/hardhat": "^6.0.0", - "@types/chai": "^4.3.1", - "@types/download": "^8.0.1", - "@types/mocha": "^9.1.1", + "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", + "@nomicfoundation/hardhat-ethers": "^3.0.0", + "@nomicfoundation/hardhat-network-helpers": "^1.0.0", + "@nomicfoundation/hardhat-toolbox": "^4.0.0", + "@nomicfoundation/hardhat-verify": "^2.0.0", + "@semaphore-protocol/core": "4.0.0-alpha.8", + "@semaphore-protocol/hardhat": "4.0.0-alpha.8", + "@typechain/ethers-v6": "^0.5.0", + "@typechain/hardhat": "^9.0.0", + "@types/chai": "^4.2.0", + "@types/eslint": "^8", + "@types/mocha": ">=9.1.0", + "@types/node": "^20.11.20", + "@typescript-eslint/eslint-plugin": "^7.0.2", + "@typescript-eslint/parser": "^7.0.2", "chai": "^4.2.0", - "dotenv": "^14.3.2", - "download": "^8.0.0", - "ethers": "^5.0.0", - "hardhat": "^2.8.4", + "dotenv": "^16.4.5", + "eslint": "^8.57.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.1.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.29.1", + "ethers": "^6.4.0", + "hardhat": "^2.20.1", "hardhat-gas-reporter": "^1.0.8", - "prettier-plugin-solidity": "^1.0.0-beta.19", - "solhint": "^3.3.6", - "solhint-plugin-prettier": "^0.0.5", - "solidity-coverage": "^0.7.21", - "typechain": "^8.0.0" + "prettier": "^3.2.5", + "prettier-plugin-solidity": "^1.3.1", + "solhint": "^4.1.1", + "solidity-coverage": "^0.8.1", + "ts-node": "^10.9.2", + "typechain": "^8.3.0", + "typescript": "^5.3.3" }, "dependencies": { - "@semaphore-protocol/contracts": "3.15.2" + "@semaphore-protocol/contracts": "4.0.0-alpha.8" }, - "config": { - "solidity": { - "version": "0.8.4" - }, - "paths": { - "contracts": "./contracts", - "tests": "./test", - "cache": "./cache", - "build": { - "snark-artifacts": "./build/snark-artifacts", - "contracts": "./build/contracts", - "typechain": "./build/typechain" - } - } - } + "packageManager": "yarn@4.1.0" } diff --git a/packages/cli-template-monorepo-subgraph/apps/contracts/scripts/download-snark-artifacts.ts b/packages/cli-template-monorepo-subgraph/apps/contracts/scripts/download-snark-artifacts.ts deleted file mode 100644 index 6051397e5..000000000 --- a/packages/cli-template-monorepo-subgraph/apps/contracts/scripts/download-snark-artifacts.ts +++ /dev/null @@ -1,24 +0,0 @@ -import download from "download" -import fs from "fs" -import { config } from "../package.json" - -async function main() { - const snarkArtifactsPath = config.paths.build["snark-artifacts"] - const url = `http://www.trusted-setup-pse.org/semaphore/${20}` - - if (!fs.existsSync(snarkArtifactsPath)) { - fs.mkdirSync(snarkArtifactsPath, { recursive: true }) - } - - if (!fs.existsSync(`${snarkArtifactsPath}/semaphore.zkey`)) { - await download(`${url}/semaphore.wasm`, snarkArtifactsPath) - await download(`${url}/semaphore.zkey`, snarkArtifactsPath) - } -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error) - process.exit(1) - }) diff --git a/packages/cli-template-monorepo-subgraph/apps/contracts/tasks/deploy.ts b/packages/cli-template-monorepo-subgraph/apps/contracts/tasks/deploy.ts index 85b43377e..0dd175e40 100644 --- a/packages/cli-template-monorepo-subgraph/apps/contracts/tasks/deploy.ts +++ b/packages/cli-template-monorepo-subgraph/apps/contracts/tasks/deploy.ts @@ -10,7 +10,7 @@ task("deploy", "Deploy a Feedback contract") logs }) - semaphoreAddress = semaphore.address + semaphoreAddress = await semaphore.getAddress() } if (!groupId) { @@ -21,10 +21,8 @@ task("deploy", "Deploy a Feedback contract") const feedbackContract = await FeedbackFactory.deploy(semaphoreAddress, groupId) - await feedbackContract.deployed() - if (logs) { - console.info(`Feedback contract has been deployed to: ${feedbackContract.address}`) + console.info(`Feedback contract has been deployed to: ${await feedbackContract.getAddress()}`) } return feedbackContract diff --git a/packages/cli-template-monorepo-subgraph/apps/contracts/test/Feedback.ts b/packages/cli-template-monorepo-subgraph/apps/contracts/test/Feedback.ts index dad8fb7da..7733e3b86 100644 --- a/packages/cli-template-monorepo-subgraph/apps/contracts/test/Feedback.ts +++ b/packages/cli-template-monorepo-subgraph/apps/contracts/test/Feedback.ts @@ -1,42 +1,41 @@ -import { Group } from "@semaphore-protocol/group" -import { Identity } from "@semaphore-protocol/identity" -import { generateProof } from "@semaphore-protocol/proof" +import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers" +import { Group, Identity, generateProof } from "@semaphore-protocol/core" import { expect } from "chai" -import { formatBytes32String } from "ethers/lib/utils" +import { encodeBytes32String } from "ethers" import { run } from "hardhat" // @ts-ignore: typechain folder will be generated after contracts compilation -import { Feedback } from "../build/typechain" -import { config } from "../package.json" +// eslint-disable-next-line +import { Feedback, ISemaphore } from "../typechain-types" describe("Feedback", () => { - let feedbackContract: Feedback - let semaphoreContract: string + async function deployFeedbackFixture() { + const groupId = "42" - const groupId = "42" - const group = new Group(groupId) - const users: Identity[] = [] - - before(async () => { const { semaphore } = await run("deploy:semaphore", { logs: false }) - feedbackContract = await run("deploy", { + const semaphoreContract: ISemaphore = semaphore + + const feedbackContract: Feedback = await run("deploy", { logs: false, group: groupId, - semaphore: semaphore.address + semaphore: await semaphoreContract.getAddress() }) - semaphoreContract = semaphore - users.push(new Identity()) - users.push(new Identity()) - }) + return { semaphoreContract, feedbackContract, groupId } + } describe("# joinGroup", () => { it("Should allow users to join the group", async () => { - for await (const [i, user] of users.entries()) { - const transaction = feedbackContract.joinGroup(user.commitment) + const { semaphoreContract, feedbackContract, groupId } = await loadFixture(deployFeedbackFixture) + const users = [new Identity(), new Identity()] + + const group = new Group() + + for (const [i, user] of users.entries()) { + const transaction = await feedbackContract.joinGroup(user.commitment) group.addMember(user.commitment) await expect(transaction) @@ -47,27 +46,40 @@ describe("Feedback", () => { }) describe("# sendFeedback", () => { - const wasmFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.wasm` - const zkeyFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.zkey` - it("Should allow users to send feedback anonymously", async () => { - const feedback = formatBytes32String("Hello World") + const { semaphoreContract, feedbackContract, groupId } = await loadFixture(deployFeedbackFixture) + + const users = [new Identity(), new Identity()] + const group = new Group() + + for (const user of users) { + await feedbackContract.joinGroup(user.commitment) + group.addMember(user.commitment) + } + + const feedback = encodeBytes32String("Hello World") - const fullProof = await generateProof(users[1], group, groupId, feedback, { - wasmFilePath, - zkeyFilePath - }) + const proof = await generateProof(users[1], group, feedback, groupId) const transaction = feedbackContract.sendFeedback( + proof.merkleTreeDepth, + proof.merkleTreeRoot, + proof.nullifier, feedback, - fullProof.merkleTreeRoot, - fullProof.nullifierHash, - fullProof.proof + proof.points ) await expect(transaction) - .to.emit(semaphoreContract, "ProofVerified") - .withArgs(groupId, fullProof.merkleTreeRoot, fullProof.nullifierHash, groupId, fullProof.signal) + .to.emit(semaphoreContract, "ProofValidated") + .withArgs( + groupId, + proof.merkleTreeDepth, + proof.merkleTreeRoot, + proof.nullifier, + proof.message, + groupId, + proof.points + ) }) }) }) diff --git a/packages/cli-template-monorepo-subgraph/apps/contracts/tsconfig.json b/packages/cli-template-monorepo-subgraph/apps/contracts/tsconfig.json index 5a3f0f784..a702753ac 100644 --- a/packages/cli-template-monorepo-subgraph/apps/contracts/tsconfig.json +++ b/packages/cli-template-monorepo-subgraph/apps/contracts/tsconfig.json @@ -1,15 +1,13 @@ { "compilerOptions": { - "moduleResolution": "Node", - "noImplicitAny": true, - "resolveJsonModule": true, - "target": "ES2018", - "module": "CommonJS", - "strict": true, + "target": "es2020", + "module": "commonjs", "esModuleInterop": true, - "outDir": "dist", - "typeRoots": ["node_modules/@types", "types"] + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true }, - "include": ["scripts/**/*", "tasks/**/*", "test/**/*", "build/typechain/**/*", "types/**/*"], + "include": ["scripts/**/*", "tasks/**/*", "test/**/*", "typechain-types/**/*"], "files": ["./hardhat.config.ts"] } diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/.env.development b/packages/cli-template-monorepo-subgraph/apps/web-app/.env.development new file mode 100644 index 000000000..afa315696 --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/.env.development @@ -0,0 +1,4 @@ +NEXT_PUBLIC_DEFAULT_NETWORK=localhost +NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 +NEXT_PUBLIC_SEMAPHORE_CONTRACT_ADDRESS=0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 +NEXT_PUBLIC_GROUP_ID=42 diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/.env.production b/packages/cli-template-monorepo-subgraph/apps/web-app/.env.production new file mode 100644 index 000000000..1236cd0a8 --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/.env.production @@ -0,0 +1,5 @@ +NEXT_PUBLIC_DEFAULT_NETWORK=sepolia +NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS=0xC6446BB589E141fFACeaCd91953CbB0F2055C7e5 +NEXT_PUBLIC_SEMAPHORE_CONTRACT_ADDRESS=0x5B8e7cC7bAC61A4b952d472b67056B2f260ba6dc +NEXT_PUBLIC_OPENZEPPELIN_AUTOTASK_WEBHOOK=https://api.defender.openzeppelin.com/actions/20fce2ae-844b-4ec0-a6a2-90a3350a9d2c/runs/webhook/303216d1-fa7d-4fca-8c5b-7ba1ba544fc7/2T7i9xrkZA5j37hoaQLUuw +NEXT_PUBLIC_GROUP_ID=9 diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/.eslintrc.json b/packages/cli-template-monorepo-subgraph/apps/web-app/.eslintrc.json new file mode 100644 index 000000000..1c2aa65d7 --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/.gitignore b/packages/cli-template-monorepo-subgraph/apps/web-app/.gitignore new file mode 100644 index 000000000..d183c4f53 --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/.gitignore @@ -0,0 +1,9 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# next.js +/.next/ +/out/ + +# production +/build + diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/contract-artifacts/Feedback.json b/packages/cli-template-monorepo-subgraph/apps/web-app/contract-artifacts/Feedback.json index 14c2f7e88..123448e9e 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/contract-artifacts/Feedback.json +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/contract-artifacts/Feedback.json @@ -62,7 +62,7 @@ "inputs": [ { "internalType": "uint256", - "name": "feedback", + "name": "merkleTreeDepth", "type": "uint256" }, { @@ -72,12 +72,17 @@ }, { "internalType": "uint256", - "name": "nullifierHash", + "name": "nullifier", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feedback", "type": "uint256" }, { "internalType": "uint256[8]", - "name": "proof", + "name": "points", "type": "uint256[8]" } ], @@ -87,8 +92,8 @@ "type": "function" } ], - "bytecode": "0x608060405234801561001057600080fd5b506040516106e13803806106e18339818101604052810190610032919061013c565b816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060018190555060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16639c1121416001546014306040518463ffffffff1660e01b81526004016100d9939291906101a5565b600060405180830381600087803b1580156100f357600080fd5b505af1158015610107573d6000803e3d6000fd5b505050505050610258565b6000815190506101218161022a565b92915050565b60008151905061013681610241565b92915050565b6000806040838503121561014f57600080fd5b600061015d85828601610112565b925050602061016e85828601610127565b9150509250929050565b610181816101dc565b82525050565b61019081610218565b82525050565b61019f8161020e565b82525050565b60006060820190506101ba6000830186610196565b6101c76020830185610187565b6101d46040830184610178565b949350505050565b60006101e7826101ee565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102238261020e565b9050919050565b610233816101dc565b811461023e57600080fd5b50565b61024a8161020e565b811461025557600080fd5b50565b61047a806102676000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d253414610051578063a0f44c921461006f578063d18ed1e91461008d578063eed02e4b146100a9575b600080fd5b6100596100c5565b604051610066919061030f565b60405180910390f35b6100776100e9565b604051610084919061032a565b60405180910390f35b6100a760048036038101906100a2919061027c565b6100ef565b005b6100c360048036038101906100be9190610253565b61018e565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16633bc778e3600154858786600154876040518763ffffffff1660e01b81526004016101569695949392919061036e565b600060405180830381600087803b15801561017057600080fd5b505af1158015610184573d6000803e3d6000fd5b5050505050505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b81526004016101eb929190610345565b600060405180830381600087803b15801561020557600080fd5b505af1158015610219573d6000803e3d6000fd5b5050505050565b60008190508260206008028201111561023857600080fd5b92915050565b60008135905061024d8161042d565b92915050565b60006020828403121561026557600080fd5b60006102738482850161023e565b91505092915050565b600080600080610160858703121561029357600080fd5b60006102a18782880161023e565b94505060206102b28782880161023e565b93505060406102c38782880161023e565b92505060606102d487828801610220565b91505092959194509250565b6102ed610100838361041e565b5050565b6102fa816103fa565b82525050565b610309816103f0565b82525050565b600060208201905061032460008301846102f1565b92915050565b600060208201905061033f6000830184610300565b92915050565b600060408201905061035a6000830185610300565b6103676020830184610300565b9392505050565b60006101a0820190506103846000830189610300565b6103916020830188610300565b61039e6040830187610300565b6103ab6060830186610300565b6103b86080830185610300565b6103c560a08301846102e0565b979650505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006104058261040c565b9050919050565b6000610417826103d0565b9050919050565b82818337600083830152505050565b610436816103f0565b811461044157600080fd5b5056fea26469706673582212204d8dc3161abc759242364c3a754a86e5eb8653092bcdf1e20bd6fcd368e1997664736f6c63430008040033", - "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d253414610051578063a0f44c921461006f578063d18ed1e91461008d578063eed02e4b146100a9575b600080fd5b6100596100c5565b604051610066919061030f565b60405180910390f35b6100776100e9565b604051610084919061032a565b60405180910390f35b6100a760048036038101906100a2919061027c565b6100ef565b005b6100c360048036038101906100be9190610253565b61018e565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16633bc778e3600154858786600154876040518763ffffffff1660e01b81526004016101569695949392919061036e565b600060405180830381600087803b15801561017057600080fd5b505af1158015610184573d6000803e3d6000fd5b5050505050505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b81526004016101eb929190610345565b600060405180830381600087803b15801561020557600080fd5b505af1158015610219573d6000803e3d6000fd5b5050505050565b60008190508260206008028201111561023857600080fd5b92915050565b60008135905061024d8161042d565b92915050565b60006020828403121561026557600080fd5b60006102738482850161023e565b91505092915050565b600080600080610160858703121561029357600080fd5b60006102a18782880161023e565b94505060206102b28782880161023e565b93505060406102c38782880161023e565b92505060606102d487828801610220565b91505092959194509250565b6102ed610100838361041e565b5050565b6102fa816103fa565b82525050565b610309816103f0565b82525050565b600060208201905061032460008301846102f1565b92915050565b600060208201905061033f6000830184610300565b92915050565b600060408201905061035a6000830185610300565b6103676020830184610300565b9392505050565b60006101a0820190506103846000830189610300565b6103916020830188610300565b61039e6040830187610300565b6103ab6060830186610300565b6103b86080830185610300565b6103c560a08301846102e0565b979650505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006104058261040c565b9050919050565b6000610417826103d0565b9050919050565b82818337600083830152505050565b610436816103f0565b811461044157600080fd5b5056fea26469706673582212204d8dc3161abc759242364c3a754a86e5eb8653092bcdf1e20bd6fcd368e1997664736f6c63430008040033", + "bytecode": "0x608060405234801561001057600080fd5b5060405161083f38038061083f833981810160405281019061003291906101a8565b816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060018190555060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c96e71fb600154306040518363ffffffff1660e01b81526004016100d6929190610206565b600060405180830381600087803b1580156100f057600080fd5b505af1158015610104573d6000803e3d6000fd5b50505050505061022f565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061013f82610114565b9050919050565b61014f81610134565b811461015a57600080fd5b50565b60008151905061016c81610146565b92915050565b6000819050919050565b61018581610172565b811461019057600080fd5b50565b6000815190506101a28161017c565b92915050565b600080604083850312156101bf576101be61010f565b5b60006101cd8582860161015d565b92505060206101de85828601610193565b9150509250929050565b6101f181610172565b82525050565b61020081610134565b82525050565b600060408201905061021b60008301856101e8565b61022860208301846101f7565b9392505050565b6106018061023e6000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d2534146100515780637b85d27a1461006f578063a0f44c921461008b578063eed02e4b146100a9575b600080fd5b6100596100c5565b6040516100669190610301565b60405180910390f35b6100896004803603810190610084919061037e565b6100e9565b005b6100936101ea565b6040516100a09190610409565b60405180910390f35b6100c360048036038101906100be9190610424565b6101f0565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60006040518060c001604052808781526020018681526020018581526020018481526020016001548152602001836008806020026040519081016040528092919082600860200280828437600081840152601f19601f820116905080830192505050505050815250905060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0d898dd600154836040518363ffffffff1660e01b81526004016101b0929190610578565b600060405180830381600087803b1580156101ca57600080fd5b505af11580156101de573d6000803e3d6000fd5b50505050505050505050565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b815260040161024d9291906105a2565b600060405180830381600087803b15801561026757600080fd5b505af115801561027b573d6000803e3d6000fd5b5050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102c76102c26102bd84610282565b6102a2565b610282565b9050919050565b60006102d9826102ac565b9050919050565b60006102eb826102ce565b9050919050565b6102fb816102e0565b82525050565b600060208201905061031660008301846102f2565b92915050565b600080fd5b6000819050919050565b61033481610321565b811461033f57600080fd5b50565b6000813590506103518161032b565b92915050565b600080fd5b60008190508260206008028201111561037857610377610357565b5b92915050565b6000806000806000610180868803121561039b5761039a61031c565b5b60006103a988828901610342565b95505060206103ba88828901610342565b94505060406103cb88828901610342565b93505060606103dc88828901610342565b92505060806103ed8882890161035c565b9150509295509295909350565b61040381610321565b82525050565b600060208201905061041e60008301846103fa565b92915050565b60006020828403121561043a5761043961031c565b5b600061044884828501610342565b91505092915050565b61045a81610321565b82525050565b600060089050919050565b600081905092915050565b6000819050919050565b600061048c8383610451565b60208301905092915050565b6000602082019050919050565b6104ae81610460565b6104b8818461046b565b92506104c382610476565b8060005b838110156104f45781516104db8782610480565b96506104e683610498565b9250506001810190506104c7565b505050505050565b6101a0820160008201516105136000850182610451565b5060208201516105266020850182610451565b5060408201516105396040850182610451565b50606082015161054c6060850182610451565b50608082015161055f6080850182610451565b5060a082015161057260a08501826104a5565b50505050565b60006101c08201905061058e60008301856103fa565b61059b60208301846104fc565b9392505050565b60006040820190506105b760008301856103fa565b6105c460208301846103fa565b939250505056fea2646970667358221220f33606b2d5ad7c0dfc5d22afb43476e1974ea7fd160e1f28203a3e433f29cb4964736f6c63430008170033", + "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d2534146100515780637b85d27a1461006f578063a0f44c921461008b578063eed02e4b146100a9575b600080fd5b6100596100c5565b6040516100669190610301565b60405180910390f35b6100896004803603810190610084919061037e565b6100e9565b005b6100936101ea565b6040516100a09190610409565b60405180910390f35b6100c360048036038101906100be9190610424565b6101f0565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60006040518060c001604052808781526020018681526020018581526020018481526020016001548152602001836008806020026040519081016040528092919082600860200280828437600081840152601f19601f820116905080830192505050505050815250905060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0d898dd600154836040518363ffffffff1660e01b81526004016101b0929190610578565b600060405180830381600087803b1580156101ca57600080fd5b505af11580156101de573d6000803e3d6000fd5b50505050505050505050565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b815260040161024d9291906105a2565b600060405180830381600087803b15801561026757600080fd5b505af115801561027b573d6000803e3d6000fd5b5050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102c76102c26102bd84610282565b6102a2565b610282565b9050919050565b60006102d9826102ac565b9050919050565b60006102eb826102ce565b9050919050565b6102fb816102e0565b82525050565b600060208201905061031660008301846102f2565b92915050565b600080fd5b6000819050919050565b61033481610321565b811461033f57600080fd5b50565b6000813590506103518161032b565b92915050565b600080fd5b60008190508260206008028201111561037857610377610357565b5b92915050565b6000806000806000610180868803121561039b5761039a61031c565b5b60006103a988828901610342565b95505060206103ba88828901610342565b94505060406103cb88828901610342565b93505060606103dc88828901610342565b92505060806103ed8882890161035c565b9150509295509295909350565b61040381610321565b82525050565b600060208201905061041e60008301846103fa565b92915050565b60006020828403121561043a5761043961031c565b5b600061044884828501610342565b91505092915050565b61045a81610321565b82525050565b600060089050919050565b600081905092915050565b6000819050919050565b600061048c8383610451565b60208301905092915050565b6000602082019050919050565b6104ae81610460565b6104b8818461046b565b92506104c382610476565b8060005b838110156104f45781516104db8782610480565b96506104e683610498565b9250506001810190506104c7565b505050505050565b6101a0820160008201516105136000850182610451565b5060208201516105266020850182610451565b5060408201516105396040850182610451565b50606082015161054c6060850182610451565b50608082015161055f6080850182610451565b5060a082015161057260a08501826104a5565b50505050565b60006101c08201905061058e60008301856103fa565b61059b60208301846104fc565b9392505050565b60006040820190506105b760008301856103fa565b6105c460208301846103fa565b939250505056fea2646970667358221220f33606b2d5ad7c0dfc5d22afb43476e1974ea7fd160e1f28203a3e433f29cb4964736f6c63430008170033", "linkReferences": {}, "deployedLinkReferences": {} } diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/next.config.js b/packages/cli-template-monorepo-subgraph/apps/web-app/next.config.js deleted file mode 100644 index 4a0f3e9f8..000000000 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/next.config.js +++ /dev/null @@ -1,45 +0,0 @@ -/** @type {import('next').NextConfig} */ - -const fs = require("fs") -const withPWA = require("next-pwa") - -if (!fs.existsSync("./.env")) { - // eslint-disable-next-line global-require - require("dotenv").config({ path: "../../.env" }) -} - -const nextConfig = withPWA({ - dest: "public", - disable: process.env.NODE_ENV === "development" -})({ - eslint: { - ignoreDuringBuilds: true - }, - reactStrictMode: true, - swcMinify: true, - env: { - DEFAULT_NETWORK: process.env.DEFAULT_NETWORK, - INFURA_API_KEY: process.env.INFURA_API_KEY, - ETHEREUM_PRIVATE_KEY: process.env.ETHEREUM_PRIVATE_KEY, - FEEDBACK_CONTRACT_ADDRESS: process.env.FEEDBACK_CONTRACT_ADDRESS, - SEMAPHORE_CONTRACT_ADDRESS: process.env.SEMAPHORE_CONTRACT_ADDRESS - }, - publicRuntimeConfig: { - DEFAULT_NETWORK: process.env.DEFAULT_NETWORK, - FEEDBACK_CONTRACT_ADDRESS: process.env.FEEDBACK_CONTRACT_ADDRESS, - SEMAPHORE_CONTRACT_ADDRESS: process.env.SEMAPHORE_CONTRACT_ADDRESS, - OPENZEPPELIN_AUTOTASK_WEBHOOK: process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK, - GROUP_ID: process.env.GROUP_ID - }, - webpack: (config, { isServer }) => { - if (!isServer) { - config.resolve.fallback = { - fs: false - } - } - - return config - } -}) - -module.exports = nextConfig diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/next.config.mjs b/packages/cli-template-monorepo-subgraph/apps/web-app/next.config.mjs new file mode 100644 index 000000000..2233f10f5 --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/next.config.mjs @@ -0,0 +1,21 @@ +/** @type {import('next').NextConfig} */ + +import withPWA from "next-pwa" +import fs from "fs" +import { config } from "dotenv" + +if (!fs.existsSync("./.env")) { + config({ path: "../../.env" }) +} + +const nextConfig = withPWA({ + dest: "public", + disable: process.env.NODE_ENV === "development" +})({ + env: { + INFURA_API_KEY: process.env.INFURA_API_KEY, + ETHEREUM_PRIVATE_KEY: process.env.ETHEREUM_PRIVATE_KEY + } +}) + +export default nextConfig diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/package.json b/packages/cli-template-monorepo-subgraph/apps/web-app/package.json index 606b2d6d3..2a30aad94 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/package.json +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/package.json @@ -1,27 +1,29 @@ { "name": "monorepo-subgraph-web-app", - "version": "1.0.0", + "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", - "export": "next export", - "start": "next start" + "start": "next start", + "lint": "next lint" }, "dependencies": { - "@next/font": "13.0.3", - "@semaphore-protocol/data": "3.15.2", - "@semaphore-protocol/group": "3.15.2", - "@semaphore-protocol/identity": "3.15.2", - "@semaphore-protocol/proof": "3.15.2", - "@types/react": "18.0.25", - "@types/react-dom": "18.0.8", - "dotenv": "^16.0.3", - "ethers": "^5.7.2", - "next": "13.0.3", + "@semaphore-protocol/core": "4.0.0-alpha.8", + "@semaphore-protocol/data": "4.0.0-alpha.8", + "ethers": "^6.11.1", + "next": "14.1.0", "next-pwa": "^5.6.0", - "react": "18.2.0", - "react-dom": "18.2.0", - "typescript": "^4.7.3" + "react": "^18", + "react-dom": "^18" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "dotenv": "^16.4.5", + "eslint": "^8", + "eslint-config-next": "14.1.0", + "typescript": "^5" } } diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/apple-icon.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/apple-icon.png new file mode 100644 index 000000000..ed4cb2bca Binary files /dev/null and b/packages/cli-template-monorepo-subgraph/apps/web-app/public/apple-icon.png differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/apple-touch-icon.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/apple-touch-icon.png deleted file mode 100644 index 52a950aa1..000000000 Binary files a/packages/cli-template-monorepo-subgraph/apps/web-app/public/apple-touch-icon.png and /dev/null differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/favicon-16x16.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/favicon-16x16.png deleted file mode 100644 index 6eff30957..000000000 Binary files a/packages/cli-template-monorepo-subgraph/apps/web-app/public/favicon-16x16.png and /dev/null differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/favicon-32x32.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/favicon-32x32.png deleted file mode 100644 index ac7812ee1..000000000 Binary files a/packages/cli-template-monorepo-subgraph/apps/web-app/public/favicon-32x32.png and /dev/null differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/favicon.ico b/packages/cli-template-monorepo-subgraph/apps/web-app/public/favicon.ico deleted file mode 100644 index c695649f6..000000000 Binary files a/packages/cli-template-monorepo-subgraph/apps/web-app/public/favicon.ico and /dev/null differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/icon-192x192.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/icon-192x192.png deleted file mode 100644 index 01cf07e55..000000000 Binary files a/packages/cli-template-monorepo-subgraph/apps/web-app/public/icon-192x192.png and /dev/null differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/icon-256x256.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/icon-256x256.png deleted file mode 100644 index f318cb420..000000000 Binary files a/packages/cli-template-monorepo-subgraph/apps/web-app/public/icon-256x256.png and /dev/null differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/icon-384x384.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/icon-384x384.png deleted file mode 100644 index 4eeb5d596..000000000 Binary files a/packages/cli-template-monorepo-subgraph/apps/web-app/public/icon-384x384.png and /dev/null differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/icon-512x512.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/icon-512x512.png deleted file mode 100644 index d553ca2b2..000000000 Binary files a/packages/cli-template-monorepo-subgraph/apps/web-app/public/icon-512x512.png and /dev/null differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/icon.svg b/packages/cli-template-monorepo-subgraph/apps/web-app/public/icon.svg new file mode 100644 index 000000000..4d16a5525 --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/public/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/favicon-16x16.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/favicon-16x16.png new file mode 100644 index 000000000..1fd837b2f Binary files /dev/null and b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/favicon-16x16.png differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/favicon-32x32.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/favicon-32x32.png new file mode 100644 index 000000000..cc97d75ee Binary files /dev/null and b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/favicon-32x32.png differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/favicon.ico b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/favicon.ico new file mode 100644 index 000000000..dc8672670 Binary files /dev/null and b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/favicon.ico differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-128x128.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-128x128.png new file mode 100644 index 000000000..27e0c96f3 Binary files /dev/null and b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-128x128.png differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-144x144.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-144x144.png new file mode 100644 index 000000000..0054e006f Binary files /dev/null and b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-144x144.png differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-152x152.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-152x152.png new file mode 100644 index 000000000..9d1b190b1 Binary files /dev/null and b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-152x152.png differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-192x192.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-192x192.png new file mode 100644 index 000000000..9bd7da0fd Binary files /dev/null and b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-192x192.png differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-512x512.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-512x512.png new file mode 100644 index 000000000..ce0738332 Binary files /dev/null and b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-512x512.png differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-72x72.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-72x72.png new file mode 100644 index 000000000..6d799c7cb Binary files /dev/null and b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-72x72.png differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-96x96.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-96x96.png new file mode 100644 index 000000000..dda5492a6 Binary files /dev/null and b/packages/cli-template-monorepo-subgraph/apps/web-app/public/images/icons/icon-96x96.png differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/manifest.json b/packages/cli-template-monorepo-subgraph/apps/web-app/public/manifest.json index 3da3e4d06..76af03744 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/public/manifest.json +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/public/manifest.json @@ -1,29 +1,44 @@ { - "theme_color": "#ebedff", - "background_color": "#ebedff", + "name": "Semaphore Boilerplate", + "short_name": "Semaphore", + "theme_color": "#4771ea", + "background_color": "#00020D", "display": "standalone", "scope": "/", "start_url": "/", - "name": "Semaphore Boilerplate", - "short_name": "Semaphore", "icons": [ { - "src": "/icon-192x192.png", - "sizes": "192x192", + "src": "images/icons/icon-72x72.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "images/icons/icon-96x96.png", + "sizes": "96x96", "type": "image/png" }, { - "src": "/icon-256x256.png", - "sizes": "256x256", + "src": "images/icons/icon-128x128.png", + "sizes": "128x128", "type": "image/png" }, { - "src": "/icon-384x384.png", - "sizes": "384x384", + "src": "images/icons/icon-144x144.png", + "sizes": "144x144", + "type": "image/png" + }, + { + "src": "images/icons/icon-152x152.png", + "sizes": "152x152", + "type": "image/png" + }, + { + "src": "images/icons/icon-192x192.png", + "sizes": "192x192", "type": "image/png" }, { - "src": "/icon-512x512.png", + "src": "images/icons/icon-512x512.png", "sizes": "512x512", "type": "image/png" } diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/semaphore-icon.svg b/packages/cli-template-monorepo-subgraph/apps/web-app/public/semaphore-icon.svg new file mode 100644 index 000000000..11db9b12b --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/public/semaphore-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/semaphore-logo.svg b/packages/cli-template-monorepo-subgraph/apps/web-app/public/semaphore-logo.svg new file mode 100644 index 000000000..82ab73c3e --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/public/semaphore-logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/public/social-media.png b/packages/cli-template-monorepo-subgraph/apps/web-app/public/social-media.png new file mode 100644 index 000000000..3ad281151 Binary files /dev/null and b/packages/cli-template-monorepo-subgraph/apps/web-app/public/social-media.png differ diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/api/feedback/route.ts b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/api/feedback/route.ts new file mode 100644 index 000000000..795b2c5dd --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/api/feedback/route.ts @@ -0,0 +1,50 @@ +import { Contract, InfuraProvider, JsonRpcProvider, Wallet } from "ethers" +import { NextRequest } from "next/server" +import Feedback from "../../../../contract-artifacts/Feedback.json" + +export async function POST(req: NextRequest) { + if (typeof process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS !== "string") { + throw new Error("Please, define NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS in your .env file") + } + + if (typeof process.env.NEXT_PUBLIC_DEFAULT_NETWORK !== "string") { + throw new Error("Please, define NEXT_PUBLIC_DEFAULT_NETWORK in your .env file") + } + + if (typeof process.env.INFURA_API_KEY !== "string" && process.env.NEXT_PUBLIC_DEFAULT_NETWORK !== "localhost") { + throw new Error("Please, define INFURA_API_KEY in your .env file") + } + + if (typeof process.env.ETHEREUM_PRIVATE_KEY !== "string") { + throw new Error("Please, define ETHEREUM_PRIVATE_KEY in your .env file") + } + + const ethereumPrivateKey = process.env.ETHEREUM_PRIVATE_KEY + const ethereumNetwork = process.env.NEXT_PUBLIC_DEFAULT_NETWORK + const infuraApiKey = process.env.INFURA_API_KEY + const contractAddress = process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS + + const provider = + ethereumNetwork === "localhost" + ? new JsonRpcProvider("http://127.0.0.1:8545") + : new InfuraProvider(ethereumNetwork, infuraApiKey) + + const signer = new Wallet(ethereumPrivateKey, provider) + const contract = new Contract(contractAddress, Feedback.abi, signer) + + const { feedback, merkleTreeDepth, merkleTreeRoot, nullifier, points } = await req.json() + + try { + const transaction = await contract.sendFeedback(merkleTreeDepth, merkleTreeRoot, nullifier, feedback, points) + + await transaction.wait() + + return new Response("Success", { status: 200 }) + } catch (error: any) { + console.error(error) + + return new Response(`Server error: ${error}`, { + status: 500 + }) + } +} diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/api/join/route.ts b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/api/join/route.ts new file mode 100644 index 000000000..608055f9e --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/api/join/route.ts @@ -0,0 +1,50 @@ +import { Contract, InfuraProvider, JsonRpcProvider, Wallet } from "ethers" +import { NextRequest } from "next/server" +import Feedback from "../../../../contract-artifacts/Feedback.json" + +export async function POST(req: NextRequest) { + if (typeof process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS !== "string") { + throw new Error("Please, define NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS in your .env file") + } + + if (typeof process.env.NEXT_PUBLIC_DEFAULT_NETWORK !== "string") { + throw new Error("Please, define NEXT_PUBLIC_DEFAULT_NETWORK in your .env file") + } + + if (typeof process.env.INFURA_API_KEY !== "string" && process.env.NEXT_PUBLIC_DEFAULT_NETWORK !== "localhost") { + throw new Error("Please, define INFURA_API_KEY in your .env file") + } + + if (typeof process.env.ETHEREUM_PRIVATE_KEY !== "string") { + throw new Error("Please, define ETHEREUM_PRIVATE_KEY in your .env file") + } + + const ethereumPrivateKey = process.env.ETHEREUM_PRIVATE_KEY + const ethereumNetwork = process.env.NEXT_PUBLIC_DEFAULT_NETWORK + const infuraApiKey = process.env.INFURA_API_KEY + const contractAddress = process.env.NEXT_PUBLIC_FEEDBACK_CONTRACT_ADDRESS + + const provider = + ethereumNetwork === "localhost" + ? new JsonRpcProvider("http://127.0.0.1:8545") + : new InfuraProvider(ethereumNetwork, infuraApiKey) + + const signer = new Wallet(ethereumPrivateKey, provider) + const contract = new Contract(contractAddress, Feedback.abi, signer) + + const { identityCommitment } = await req.json() + + try { + const transaction = await contract.joinGroup(identityCommitment) + + await transaction.wait() + + return new Response("Success", { status: 200 }) + } catch (error: any) { + console.error(error) + + return new Response(`Server error: ${error}`, { + status: 500 + }) + } +} diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/styles/globals.css b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/globals.css similarity index 74% rename from packages/cli-template-monorepo-ethers/apps/web-app/src/styles/globals.css rename to packages/cli-template-monorepo-subgraph/apps/web-app/src/app/globals.css index 5b95a69a2..505ae8579 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/styles/globals.css +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/globals.css @@ -1,12 +1,17 @@ :root { --slate100: #f1f5f9; + --slate200: #e2e8f0; --slate300: #cbd5e1; --slate400: #94a3b8; + --slate500: #64748b; --slate700: #334155; --blue200: #bfdbfe; + --blue500: #3b82f6; --blue600: #2563eb; + --blue700: #1d4ed8; --blue800: #1e40af; --blue900: #1e3a8a; + --darkBlueBg: #00020d; } * { @@ -22,7 +27,8 @@ body { } body { - color: var(--slate700); + background-color: var(--darkBlueBg); + color: var(--slate200); } p { @@ -61,18 +67,18 @@ li { h2 { font-size: 2.25rem; - font-weight: 700; + font-weight: 500; margin-bottom: 1rem; } h3 { font-size: 1.125rem; - font-weight: 700; + font-weight: 500; } .divider { height: 1px; - background: var(--slate400); + background: var(--slate500); margin: 2rem 0; } @@ -83,25 +89,26 @@ h3 { } a { - color: var(--blue600); + color: var(--blue500); } .button { background-color: var(--blue800); width: 100%; padding: 0.8rem 1rem; - border-radius: 4px; + border: 1px; + border-radius: 100px; cursor: pointer; color: var(--slate100); font-size: 1.125rem; - font-weight: 700; + font-weight: 500; transition: all 200ms linear; margin-top: 1rem; margin-bottom: 1.5rem; opacity: 0.9; display: flex; - justify-content: space-between; - height: 3.5rem; + justify-content: center; + height: 3rem; align-items: center; } @@ -120,7 +127,7 @@ a { .button-stepper { cursor: pointer; - color: var(--blue900); + color: var(--blue600); font-size: 1.1rem; border: none; background: none; @@ -128,7 +135,7 @@ a { .button-link { cursor: pointer; - color: var(--slate700); + color: var(--slate300); font-size: 1.1rem; border: none; background: none; @@ -138,7 +145,7 @@ a { padding: 0.8rem; border-style: solid; border-width: 1px; - border-color: var(--slate300); + border-color: var(--slate500); border-radius: 4px; } @@ -149,17 +156,36 @@ a { margin: 0.5rem; } +.header { + display: flex; + justify-content: space-between; + padding: 1.5rem; +} + +.header-left { + font-size: 1.125rem; + font-weight: 500; + text-decoration: none; +} + +.header-right { + display: flex; + gap: 1.5rem; +} + .footer { - background-color: var(--blue200); + background-color: var(--darkBlueBg); padding: 1rem 0; - height: 3.5rem; - display: flex; justify-content: center; align-items: center; + font-weight: 500; +} - font-weight: 700; +.divider-footer { + height: 1px; + background: var(--slate500); } .loader { @@ -170,6 +196,7 @@ a { border-right: 2px solid transparent; animation: spin 1s linear infinite; z-index: 20; + margin-left: 1rem; } @keyframes spin { diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/pages/groups.tsx b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/groups/page.tsx similarity index 69% rename from packages/cli-template-monorepo-ethers/apps/web-app/src/pages/groups.tsx rename to packages/cli-template-monorepo-subgraph/apps/web-app/src/app/groups/page.tsx index d7c5c1ee8..7163f3157 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/pages/groups.tsx +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/groups/page.tsx @@ -1,13 +1,12 @@ -import { Identity } from "@semaphore-protocol/identity" -import getNextConfig from "next/config" -import { useRouter } from "next/router" -import { useCallback, useContext, useEffect, useState } from "react" -import Feedback from "../../contract-artifacts/Feedback.json" -import Stepper from "../components/Stepper" -import LogsContext from "../context/LogsContext" -import SemaphoreContext from "../context/SemaphoreContext" +"use client" -const { publicRuntimeConfig: env } = getNextConfig() +import { Identity } from "@semaphore-protocol/core" +import { useRouter } from "next/navigation" +import { useCallback, useContext, useEffect, useState } from "react" +import Feedback from "../../../contract-artifacts/Feedback.json" +import Stepper from "@/components/Stepper" +import LogsContext from "@/context/LogsContext" +import SemaphoreContext from "@/context/SemaphoreContext" export default function GroupsPage() { const router = useRouter() @@ -17,14 +16,14 @@ export default function GroupsPage() { const [_identity, setIdentity] = useState() useEffect(() => { - const identityString = localStorage.getItem("identity") + const privateKey = localStorage.getItem("identity") - if (!identityString) { + if (!privateKey) { router.push("/") return } - setIdentity(new Identity(identityString)) + setIdentity(new Identity(privateKey)) }, []) useEffect(() => { @@ -43,13 +42,13 @@ export default function GroupsPage() { let response: any - if (env.OPENZEPPELIN_AUTOTASK_WEBHOOK) { - response = await fetch(env.OPENZEPPELIN_AUTOTASK_WEBHOOK, { + if (process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK) { + response = await fetch(process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ abi: Feedback.abi, - address: env.FEEDBACK_CONTRACT_ADDRESS, + address: process.env.FEEDBACK_CONTRACT_ADDRESS, functionName: "joinGroup", functionParameters: [_identity.commitment.toString()] }) @@ -67,7 +66,7 @@ export default function GroupsPage() { if (response.status === 200) { addUser(_identity.commitment.toString()) - setLogs(`You joined the Feedback group event 🎉 Share your feedback anonymously!`) + setLogs(`You have joined the Feedback group event 🎉 Share your feedback anonymously!`) } else { setLogs("Some error occurred, please try again!") } @@ -75,29 +74,36 @@ export default function GroupsPage() { setLoading(false) }, [_identity]) - const userHasJoined = useCallback((identity: Identity) => _users.includes(identity.commitment.toString()), [_users]) + const userHasJoined = useCallback((identity: Identity) => _users.includes(identity.commitment), [_users]) return ( <>

Groups

- Semaphore{" "} + Semaphore groups + {" "} + are{" "} + - groups + Lean incremental Merkle trees {" "} - are binary incremental Merkle trees in which each leaf contains an identity commitment for a user. - Groups can be abstracted to represent events, polls, or organizations. + in which each leaf contains an identity commitment for a user. Groups can be abstracted to represent + events, polls, or organizations.

-

Feedback users ({_users.length})

+

Group users ({_users.length})

diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/layout.tsx b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/layout.tsx new file mode 100644 index 000000000..e6a6d1aac --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/layout.tsx @@ -0,0 +1,40 @@ +import PageContainer from "@/components/PageContainer" +import type { Metadata } from "next" +import { Inter } from "next/font/google" +import "./globals.css" + +const inter = Inter({ subsets: ["latin"] }) + +export const metadata: Metadata = { + title: "Semaphore Demo", + description: "A zero-knowledge protocol for anonymous signaling on Ethereum.", + icons: { icon: "/icon.svg", apple: "/apple-icon.png" }, + metadataBase: new URL("https://demo.semaphore.pse.dev"), + openGraph: { + type: "website", + url: "https://demo.semaphore.pse.dev", + title: "Semaphore Demo", + description: "A zero-knowledge protocol for anonymous signaling on Ethereum.", + siteName: "Semaphore Demo", + images: [ + { + url: "https://demo.semaphore.pse.dev/social-media.png" + } + ] + }, + twitter: { card: "summary_large_image", images: "https://demo.semaphore.pse.dev/social-media.png" } +} + +export default function RootLayout({ + children +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + + {children} + + + ) +} diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/index.tsx b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/page.tsx similarity index 60% rename from packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/index.tsx rename to packages/cli-template-monorepo-subgraph/apps/web-app/src/app/page.tsx index a58772bf9..5e78b474d 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/index.tsx +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/page.tsx @@ -1,5 +1,7 @@ -import { Identity } from "@semaphore-protocol/identity" -import { useRouter } from "next/router" +"use client" + +import { Identity } from "@semaphore-protocol/core" +import { useRouter } from "next/navigation" import { useCallback, useContext, useEffect, useState } from "react" import Stepper from "../components/Stepper" import LogsContext from "../context/LogsContext" @@ -10,14 +12,14 @@ export default function IdentitiesPage() { const [_identity, setIdentity] = useState() useEffect(() => { - const identityString = localStorage.getItem("identity") + const privateKey = localStorage.getItem("identity") - if (identityString) { - const identity = new Identity(identityString) + if (privateKey) { + const identity = new Identity(privateKey) setIdentity(identity) - setLogs("Your Semaphore identity was retrieved from the browser cache 👌🏽") + setLogs("Your Semaphore identity has been retrieved from the browser cache 👌🏽") } else { setLogs("Create your Semaphore identity 👆🏽") } @@ -28,9 +30,9 @@ export default function IdentitiesPage() { setIdentity(identity) - localStorage.setItem("identity", identity.toString()) + localStorage.setItem("identity", identity.privateKey.toString()) - setLogs("Your new Semaphore identity was just created 🎉") + setLogs("Your new Semaphore identity has just been created 🎉") }, []) return ( @@ -38,21 +40,24 @@ export default function IdentitiesPage() {

Identities

- Users interact with the protocol using a Semaphore{" "} + The identity of a user in the Semaphore protocol. A{" "} + + Semaphore identity + {" "} + consists of an{" "} - identity + EdDSA {" "} - (similar to Ethereum accounts). It contains three values: + public/private key pair and a commitment, used as the public identifier of the identity.

-
    -
  1. Trapdoor: private, known only by user
  2. -
  3. Nullifier: private, known only by user
  4. -
  5. Commitment: public
  6. -
@@ -68,8 +73,7 @@ export default function IdentitiesPage() { {_identity ? (
-

Trapdoor: {_identity.trapdoor.toString()}

-

Nullifier: {_identity.nullifier.toString()}

+

Private Key: {_identity.privateKey.toString()}

Commitment: {_identity.commitment.toString()}

diff --git a/packages/cli-template-monorepo-ethers/apps/web-app/src/pages/proofs.tsx b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/proofs/page.tsx similarity index 64% rename from packages/cli-template-monorepo-ethers/apps/web-app/src/pages/proofs.tsx rename to packages/cli-template-monorepo-subgraph/apps/web-app/src/app/proofs/page.tsx index fd89a7705..20fa797aa 100644 --- a/packages/cli-template-monorepo-ethers/apps/web-app/src/pages/proofs.tsx +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/app/proofs/page.tsx @@ -1,16 +1,12 @@ -import { Group } from "@semaphore-protocol/group" -import { Identity } from "@semaphore-protocol/identity" -import { generateProof } from "@semaphore-protocol/proof" -import { BigNumber, utils } from "ethers" -import getNextConfig from "next/config" -import { useRouter } from "next/router" -import { useCallback, useContext, useEffect, useState } from "react" -import Feedback from "../../contract-artifacts/Feedback.json" -import Stepper from "../components/Stepper" -import LogsContext from "../context/LogsContext" -import SemaphoreContext from "../context/SemaphoreContext" +"use client" -const { publicRuntimeConfig: env } = getNextConfig() +import { Group, Identity, generateProof } from "@semaphore-protocol/core" +import { useRouter } from "next/navigation" +import { useCallback, useContext, useEffect, useState } from "react" +import Feedback from "../../../contract-artifacts/Feedback.json" +import Stepper from "../../components/Stepper" +import LogsContext from "../../context/LogsContext" +import SemaphoreContext from "../../context/SemaphoreContext" export default function ProofsPage() { const router = useRouter() @@ -20,14 +16,14 @@ export default function ProofsPage() { const [_identity, setIdentity] = useState() useEffect(() => { - const identityString = localStorage.getItem("identity") + const privateKey = localStorage.getItem("identity") - if (!identityString) { + if (!privateKey) { router.push("/") return } - setIdentity(new Identity(identityString)) + setIdentity(new Identity(privateKey)) }, []) useEffect(() => { @@ -41,6 +37,15 @@ export default function ProofsPage() { return } + if (typeof process.env.NEXT_PUBLIC_GROUP_ID !== "string") { + throw new Error("Please, define NEXT_PUBLIC_GROUP_ID in your .env file") + } + + if (_users && _users.length < 2) { + alert("No anonymity in a group of one!") + return + } + const feedback = prompt("Please enter your feedback:") if (feedback && _users) { @@ -49,30 +54,26 @@ export default function ProofsPage() { setLogs(`Posting your anonymous feedback...`) try { - const group = new Group(env.GROUP_ID) - - const signal = BigNumber.from(utils.formatBytes32String(feedback)).toString() - - group.addMembers(_users) + const group = new Group(_users) - const { proof, merkleTreeRoot, nullifierHash } = await generateProof( + const { points, merkleTreeDepth, merkleTreeRoot, nullifier, message } = await generateProof( _identity, group, - env.GROUP_ID, - signal + feedback, + process.env.NEXT_PUBLIC_GROUP_ID as string ) let response: any - if (env.OPENZEPPELIN_AUTOTASK_WEBHOOK) { - response = await fetch(env.OPENZEPPELIN_AUTOTASK_WEBHOOK, { + if (process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK) { + response = await fetch(process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ abi: Feedback.abi, - address: env.FEEDBACK_CONTRACT_ADDRESS, + address: process.env.FEEDBACK_CONTRACT_ADDRESS, functionName: "sendFeedback", - functionParameters: [signal, merkleTreeRoot, nullifierHash, proof] + functionParameters: [merkleTreeDepth, merkleTreeRoot, nullifier, message, points] }) }) } else { @@ -80,10 +81,11 @@ export default function ProofsPage() { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - feedback: signal, + feedback: message, + merkleTreeDepth, merkleTreeRoot, - nullifierHash, - proof + nullifier, + points }) }) } @@ -91,7 +93,7 @@ export default function ProofsPage() { if (response.status === 200) { addFeedback(feedback) - setLogs(`Your feedback was posted 🎉`) + setLogs(`Your feedback has been posted 🎉`) } else { setLogs("Some error occurred, please try again!") } @@ -103,7 +105,7 @@ export default function ProofsPage() { setLoading(false) } } - }, [_identity]) + }, [_identity, _users]) return ( <> @@ -112,20 +114,20 @@ export default function ProofsPage() {

Semaphore members can anonymously{" "} prove {" "} - that they are part of a group and that they are generating their own signals. Signals could be anonymous - votes, leaks, reviews, or feedback. + that they are part of a group and send their anonymous messages. Messages could be votes, leaks, + reviews, feedback, etc.

-

Feedback signals ({_feedback.length})

+

Feedback messages ({_feedback.length})

diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/components/PageContainer.tsx b/packages/cli-template-monorepo-subgraph/apps/web-app/src/components/PageContainer.tsx new file mode 100644 index 000000000..854dd6816 --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/components/PageContainer.tsx @@ -0,0 +1,98 @@ +"use client" + +import LogsContext from "@/context/LogsContext" +import SemaphoreContext from "@/context/SemaphoreContext" +import useSemaphore from "@/hooks/useSemaphore" +import shortenString from "@/utils/shortenString" +import { SupportedNetwork } from "@semaphore-protocol/data" +import { usePathname } from "next/navigation" +import { useEffect, useState } from "react" +import Link from "next/link" + +export default function PageContainer({ + children +}: Readonly<{ + children: React.ReactNode +}>) { + const pathname = usePathname() + const semaphore = useSemaphore() + const [_logs, setLogs] = useState("") + + useEffect(() => { + semaphore.refreshUsers() + semaphore.refreshFeedback() + }, []) + + function getExplorerLink(network: SupportedNetwork, address: string) { + switch (network) { + case "sepolia": + return `https://sepolia.etherscan.io/address/${address}` + case "arbitrum-sepolia": + return `https://sepolia.arbiscan.io/address/${address}` + default: + return "" + } + } + + return ( +
+ + +
+ + + {children} + + +
+ +
+ +
+ {_logs.endsWith("...")} +

{_logs || `Current step: ${pathname}`}

+
+
+ ) +} diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/hooks/useSemaphore.ts b/packages/cli-template-monorepo-subgraph/apps/web-app/src/hooks/useSemaphore.ts index 2a4a2ccbb..7af264f82 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/hooks/useSemaphore.ts +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/hooks/useSemaphore.ts @@ -1,12 +1,12 @@ import { SemaphoreSubgraph } from "@semaphore-protocol/data" -import { BigNumber, utils } from "ethers" -import getNextConfig from "next/config" +import { decodeBytes32String, toBeHex } from "ethers" import { useCallback, useState } from "react" import { SemaphoreContextType } from "../context/SemaphoreContext" -const { publicRuntimeConfig: env } = getNextConfig() - -const ethereumNetwork = env.DEFAULT_NETWORK === "localhost" ? "http://localhost:8545" : env.DEFAULT_NETWORK +const ethereumNetwork = + process.env.NEXT_PUBLIC_DEFAULT_NETWORK === "localhost" + ? "http://127.0.0.1:8545" + : process.env.NEXT_PUBLIC_DEFAULT_NETWORK export default function useSemaphore(): SemaphoreContextType { const [_users, setUsers] = useState([]) @@ -15,7 +15,7 @@ export default function useSemaphore(): SemaphoreContextType { const refreshUsers = useCallback(async (): Promise => { const semaphore = new SemaphoreSubgraph(ethereumNetwork) - const group = await semaphore.getGroup(env.GROUP_ID, { members: true }) + const group = await semaphore.getGroup(process.env.NEXT_PUBLIC_GROUP_ID as string, { members: true }) setUsers(group.members!) }, []) @@ -30,15 +30,11 @@ export default function useSemaphore(): SemaphoreContextType { const refreshFeedback = useCallback(async (): Promise => { const semaphore = new SemaphoreSubgraph(ethereumNetwork) - const group = await semaphore.getGroup(env.GROUP_ID, { - verifiedProofs: true + const group = await semaphore.getGroup(process.env.NEXT_PUBLIC_GROUP_ID as string, { + validatedProofs: true }) - setFeedback( - group.verifiedProofs!.map(({ signal }: any) => - utils.parseBytes32String(BigNumber.from(signal).toHexString()) - ) - ) + setFeedback(group.validatedProofs!.map(({ message }: any) => decodeBytes32String(toBeHex(message, 32)))) }, []) const addFeedback = useCallback( diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/_app.tsx b/packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/_app.tsx deleted file mode 100644 index e48545cc7..000000000 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/_app.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import "../styles/globals.css" -import type { AppProps } from "next/app" -import Head from "next/head" -import { useRouter } from "next/router" -import { useEffect, useState } from "react" -import LogsContext from "../context/LogsContext" -import SemaphoreContext from "../context/SemaphoreContext" -import useSemaphore from "../hooks/useSemaphore" -import { Inter } from "@next/font/google" - -const inter = Inter({ subsets: ["latin"] }) - -export default function App({ Component, pageProps }: AppProps) { - const router = useRouter() - const semaphore = useSemaphore() - const [_logs, setLogs] = useState("") - - useEffect(() => { - semaphore.refreshUsers() - semaphore.refreshFeedback() - }, []) - - return ( -
- - Semaphore template - - - - - - - - -
-
-
- - - - - -
-
- -
- {_logs.endsWith("...")} -

{_logs || `Current step: ${router.route}`}

-
-
-
- ) -} diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/api/feedback.ts b/packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/api/feedback.ts deleted file mode 100644 index 0e65056e5..000000000 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/api/feedback.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Contract, providers, Wallet } from "ethers" -import type { NextApiRequest, NextApiResponse } from "next" -import Feedback from "../../../contract-artifacts/Feedback.json" - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - if (typeof process.env.FEEDBACK_CONTRACT_ADDRESS !== "string") { - throw new Error("Please, define FEEDBACK_CONTRACT_ADDRESS in your .env file") - } - - if (typeof process.env.DEFAULT_NETWORK !== "string") { - throw new Error("Please, define DEFAULT_NETWORK in your .env file") - } - - if (typeof process.env.INFURA_API_KEY !== "string") { - throw new Error("Please, define INFURA_API_KEY in your .env file") - } - - if (typeof process.env.ETHEREUM_PRIVATE_KEY !== "string") { - throw new Error("Please, define ETHEREUM_PRIVATE_KEY in your .env file") - } - - const ethereumPrivateKey = process.env.ETHEREUM_PRIVATE_KEY - const ethereumNetwork = process.env.DEFAULT_NETWORK - const infuraApiKey = process.env.INFURA_API_KEY - const contractAddress = process.env.FEEDBACK_CONTRACT_ADDRESS - - const provider = - ethereumNetwork === "localhost" - ? new providers.JsonRpcProvider() - : new providers.InfuraProvider(ethereumNetwork, infuraApiKey) - - const signer = new Wallet(ethereumPrivateKey, provider) - const contract = new Contract(contractAddress, Feedback.abi, signer) - - const { feedback, merkleTreeRoot, nullifierHash, proof } = req.body - - try { - const transaction = await contract.sendFeedback(feedback, merkleTreeRoot, nullifierHash, proof) - - await transaction.wait() - - res.status(200).end() - } catch (error: any) { - console.error(error) - - res.status(500).end() - } -} diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/api/join.ts b/packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/api/join.ts deleted file mode 100644 index 826865f16..000000000 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/src/pages/api/join.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Contract, providers, Wallet } from "ethers" -import type { NextApiRequest, NextApiResponse } from "next" -import Feedback from "../../../contract-artifacts/Feedback.json" - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - if (typeof process.env.FEEDBACK_CONTRACT_ADDRESS !== "string") { - throw new Error("Please, define FEEDBACK_CONTRACT_ADDRESS in your .env file") - } - - if (typeof process.env.DEFAULT_NETWORK !== "string") { - throw new Error("Please, define DEFAULT_NETWORK in your .env file") - } - - if (typeof process.env.INFURA_API_KEY !== "string") { - throw new Error("Please, define INFURA_API_KEY in your .env file") - } - - if (typeof process.env.ETHEREUM_PRIVATE_KEY !== "string") { - throw new Error("Please, define ETHEREUM_PRIVATE_KEY in your .env file") - } - - const ethereumPrivateKey = process.env.ETHEREUM_PRIVATE_KEY - const ethereumNetwork = process.env.DEFAULT_NETWORK - const infuraApiKey = process.env.INFURA_API_KEY - const contractAddress = process.env.FEEDBACK_CONTRACT_ADDRESS - - const provider = - ethereumNetwork === "localhost" - ? new providers.JsonRpcProvider() - : new providers.InfuraProvider(ethereumNetwork, infuraApiKey) - - const signer = new Wallet(ethereumPrivateKey, provider) - const contract = new Contract(contractAddress, Feedback.abi, signer) - - const { identityCommitment } = req.body - - try { - const transaction = await contract.joinGroup(identityCommitment) - - await transaction.wait() - - res.status(200).end() - } catch (error: any) { - console.error(error) - - res.status(500).end() - } -} diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/src/utils/shortenString.ts b/packages/cli-template-monorepo-subgraph/apps/web-app/src/utils/shortenString.ts new file mode 100644 index 000000000..97de81b0b --- /dev/null +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/src/utils/shortenString.ts @@ -0,0 +1,3 @@ +export default function shortenString(s: string, l: [number, number]) { + return `${s.slice(0, l[0])}...${s.slice(-l[1])}` +} diff --git a/packages/cli-template-monorepo-subgraph/apps/web-app/tsconfig.json b/packages/cli-template-monorepo-subgraph/apps/web-app/tsconfig.json index 2018a3356..68bce00b1 100644 --- a/packages/cli-template-monorepo-subgraph/apps/web-app/tsconfig.json +++ b/packages/cli-template-monorepo-subgraph/apps/web-app/tsconfig.json @@ -1,20 +1,26 @@ { "compilerOptions": { - "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, - "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", - "incremental": true + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } }, - "include": ["next.config.js", "next-env.d.ts", "**/*.ts", "**/*.tsx"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] } diff --git a/packages/cli-template-monorepo-subgraph/package.json b/packages/cli-template-monorepo-subgraph/package.json index c13530545..c416b8887 100644 --- a/packages/cli-template-monorepo-subgraph/package.json +++ b/packages/cli-template-monorepo-subgraph/package.json @@ -1,44 +1,30 @@ { "name": "@semaphore-protocol/cli-template-monorepo-subgraph", - "version": "3.15.2", + "version": "4.0.0-alpha.8", "description": "Semaphore Hardhat + Next.js + SemaphoreSubgraph template.", "license": "Unlicense", "files": [ "files.tgz", - "scripts/", ".editorconfig", ".env.example", - ".eslintignore", - ".eslintrc.json", ".prettierignore", ".prettierrc.json", - "tsconfig.json", "README.md" ], "scripts": { - "dev": "yarn workspaces foreach -pi run dev", - "dev:web-app": "yarn workspace monorepo-subgraph-web-app dev", - "dev:contracts": "yarn workspace monorepo-subgraph-contracts dev", - "lint": "eslint . --ext .js,.ts", - "prettier": "prettier -c .", - "prettier:write": "prettier -w .", - "copy:contract-artifacts": "ts-node scripts/copy-contract-artifacts.ts", + "dev": "yarn workspaces foreach -A -pi run dev", + "dev:web-app": "yarn workspace monorepo-ethers-web-app dev", + "dev:contracts": "yarn workspace monorepo-ethers-contracts dev", + "lint": "yarn workspaces foreach -A -pi run lint", + "prettier": "prettier -c . && yarn workspaces foreach -A -pi run prettier", + "prettier:write": "prettier -w . && yarn workspaces foreach -A -pi run prettier:write", "prepublish": "tar -czf files.tgz .gitignore .yarn .yarnrc.yml apps" }, "workspaces": [ "apps/*" ], + "packageManager": "yarn@4.1.0", "devDependencies": { - "@types/node": "^17.0.9", - "@typescript-eslint/eslint-plugin": "^5.9.1", - "@typescript-eslint/parser": "^5.9.1", - "eslint": "^8.2.0", - "eslint-config-airbnb-base": "15.0.0", - "eslint-config-airbnb-typescript": "^16.1.0", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-import": "^2.25.2", - "prettier": "^2.5.1", - "ts-node": "^10.8.1", - "typescript": "^4.7.3" + "prettier": "^3.2.5" } } diff --git a/packages/cli-template-monorepo-subgraph/scripts/copy-contract-artifacts.ts b/packages/cli-template-monorepo-subgraph/scripts/copy-contract-artifacts.ts deleted file mode 100644 index 7faf8eefe..000000000 --- a/packages/cli-template-monorepo-subgraph/scripts/copy-contract-artifacts.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as fs from "fs" - -async function main() { - const contractArtifactsPath = "apps/contracts/build/contracts/contracts/Feedback.sol" - const webAppArtifactsPath = "apps/web-app/contract-artifacts" - - await fs.promises.copyFile(`${contractArtifactsPath}/Feedback.json`, `${webAppArtifactsPath}/Feedback.json`) -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error) - process.exit(1) - }) diff --git a/packages/cli-template-monorepo-subgraph/tsconfig.json b/packages/cli-template-monorepo-subgraph/tsconfig.json deleted file mode 100644 index 6ae2d24c4..000000000 --- a/packages/cli-template-monorepo-subgraph/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "include": ["scripts/**/*"] -} diff --git a/packages/cli/LICENSE b/packages/cli/LICENSE index bdb9b1e08..8ef16f7a5 100644 --- a/packages/cli/LICENSE +++ b/packages/cli/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Ethereum Foundation +Copyright (c) 2024 Ethereum Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/cli/README.md b/packages/cli/README.md index 767b06669..5ce226ce7 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -10,7 +10,7 @@ - Github license + NPM license NPM version @@ -76,9 +76,9 @@ Options: Commands: create [options] [project-directory] Create a Semaphore project with a supported template. - get-groups [options] Get the list of groups from a supported network (e.g. goerli or arbitrum). - get-group [options] [group-id] Get the data of a group from a supported network (e.g. goerli or arbitrum). - get-members [options] [group-id] Get the members of a group from a supported network (e.g. goerli or arbitrum). - get-proofs [options] [group-id] Get the proofs of a group from a supported network (e.g. goerli or arbitrum). + get-groups [options] Get the list of groups from a supported network (e.g. sepolia or arbitrum). + get-group [options] [group-id] Get the data of a group from a supported network (e.g. sepolia or arbitrum). + get-members [options] [group-id] Get the members of a group from a supported network (e.g. sepolia or arbitrum). + get-proofs [options] [group-id] Get the proofs of a group from a supported network (e.g. sepolia or arbitrum). help [command] Display help for a specific command. ``` diff --git a/packages/cli/build.tsconfig.json b/packages/cli/build.tsconfig.json index 2599898e5..410f745ec 100644 --- a/packages/cli/build.tsconfig.json +++ b/packages/cli/build.tsconfig.json @@ -1,8 +1,9 @@ { "compilerOptions": { "baseUrl": ".", - "target": "es2020", + "target": "ES2020", "module": "ESNext", + "moduleResolution": "Bundler", "allowSyntheticDefaultImports": true }, "include": ["src"] diff --git a/packages/cli/package.json b/packages/cli/package.json index 8dfb5ae56..8638bd5dc 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@semaphore-protocol/cli", "type": "module", - "version": "3.15.2", + "version": "4.0.0-alpha.8", "description": "A command line tool to set up your Semaphore project and get group data.", "license": "MIT", "bin": { @@ -23,8 +23,7 @@ "node": ">=14.16" }, "scripts": { - "start": "ts-node --esm src/index.ts", - "build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript", + "start": "node --import 'data:text/javascript,import { register } from \"node:module\"; import { pathToFileURL } from \"node:url\"; register(\"ts-node/esm\", pathToFileURL(\"./\"));' ./src/index.ts", "build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript", "prepublishOnly": "yarn build" }, @@ -32,27 +31,25 @@ "access": "public" }, "devDependencies": { - "@types/clear": "^0.1.2", - "@types/figlet": "^1.5.5", - "@types/inquirer": "^9.0.3", - "@types/pacote": "^11.1.5", - "@types/semver": "^7.3.13", + "@types/figlet": "^1.5.8", + "@types/inquirer": "^9.0.7", + "@types/pacote": "^11.1.8", + "@types/semver": "^7.5.8", "rollup-plugin-cleanup": "^3.2.1", - "rollup-plugin-typescript2": "^0.31.2", - "ts-node": "^10.9.1" + "rollup-plugin-typescript2": "^0.36.0" }, "dependencies": { - "@semaphore-protocol/data": "3.15.2", - "axios": "^1.3.2", - "boxen": "^7.0.1", - "chalk": "^5.1.2", - "commander": "^9.4.1", + "@semaphore-protocol/data": "4.0.0-alpha.8", + "axios": "^1.6.7", + "boxen": "^7.1.1", + "chalk": "^5.3.0", + "commander": "^12.0.0", "decompress": "^4.2.1", - "figlet": "^1.5.2", - "inquirer": "^9.1.4", - "log-symbols": "^5.1.0", - "ora": "^6.1.2", - "pacote": "^15.1.1", - "semver": "^7.3.8" + "figlet": "^1.7.0", + "inquirer": "^9.2.15", + "log-symbols": "^6.0.0", + "ora": "^8.0.1", + "pacote": "^17.0.6", + "semver": "^7.6.0" } } diff --git a/packages/cli/rollup.config.ts b/packages/cli/rollup.config.ts index dc9a089b6..cbd36160e 100644 --- a/packages/cli/rollup.config.ts +++ b/packages/cli/rollup.config.ts @@ -9,7 +9,7 @@ const banner = `#!/usr/bin/env node * @module ${pkg.name} * @version ${pkg.version} * @file ${pkg.description} - * @copyright Ethereum Foundation 2022 + * @copyright Ethereum Foundation 2024 * @license ${pkg.license} * @see [Github]{@link ${pkg.homepage}} */ @@ -18,7 +18,7 @@ const banner = `#!/usr/bin/env node export default { input: "src/index.ts", output: [{ file: pkg.bin.semaphore, format: "es", banner }], - external: ["url", "fs", "path", ...Object.keys(pkg.dependencies)], + external: [...Object.keys(pkg.dependencies), "url", "fs", "path", "child_process"], plugins: [ (typescript as any)({ tsconfig: "./build.tsconfig.json", diff --git a/packages/cli/src/getGroupIds.ts b/packages/cli/src/getGroupIds.ts index cafc49f77..3197e971d 100644 --- a/packages/cli/src/getGroupIds.ts +++ b/packages/cli/src/getGroupIds.ts @@ -34,9 +34,5 @@ export default async function getGroupIds(network): Promise { return null } } - if (groupIds.length === 0) { - console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n") - return null - } return groupIds } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index c5a628989..8fc587860 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,11 +1,11 @@ import { GroupResponse, SemaphoreEthers, SemaphoreSubgraph, getSupportedNetworks } from "@semaphore-protocol/data" import chalk from "chalk" import { program } from "commander" +import decompress from "decompress" import figlet from "figlet" -import { existsSync, readFileSync, unlinkSync, copyFileSync } from "fs" +import { copyFileSync, existsSync, readFileSync, unlinkSync } from "fs" import logSymbols from "log-symbols" import pacote from "pacote" -import decompress from "decompress" import { dirname } from "path" import { fileURLToPath } from "url" import checkLatestVersion from "./checkLatestVersion.js" @@ -98,7 +98,7 @@ program console.info(`\n ${logSymbols.success}`, `Your project is ready!\n`) console.info(` Please, install your dependencies by running:\n`) console.info(` ${chalk.cyan("cd")} ${projectDirectory}`) - console.info(` ${chalk.cyan("npm i")}\n`) + console.info(` ${chalk.cyan("yarn install")}\n`) const { scripts } = JSON.parse(readFileSync(`${currentDirectory}/${projectDirectory}/package.json`, "utf8")) @@ -107,7 +107,7 @@ program console.info( `${Object.keys(scripts) - .map((s) => ` ${chalk.cyan(`npm run ${s}`)}`) + .map((s) => ` ${chalk.cyan(`yarn ${s}`)}`) .join("\n")}\n` ) @@ -117,7 +117,7 @@ program program .command("get-groups") - .description("Get the list of groups from a supported network (e.g. goerli or arbitrum).") + .description("Get the list of groups from a supported network (e.g. sepolia or arbitrum).") .option("-n, --network ", "Supported Ethereum network.") .allowExcessArguments(false) .action(async ({ network }) => { @@ -132,7 +132,7 @@ program const groupIds = await getGroupIds(network) - if (groupIds === null) { + if (!groupIds || groupIds.length === 0) { console.info(`\n ${logSymbols.info}`, `info: there are no groups on the '${network}' network\n`) return } @@ -146,7 +146,7 @@ program program .command("get-group") - .description("Get the data of a group from a supported network (e.g. goerli or arbitrum).") + .description("Get the data of a group from a supported network (e.g. sepolia or arbitrum).") .argument("[group-id]", "Identifier of the group.") .option("-n, --network ", "Supported Ethereum network.") .allowExcessArguments(false) @@ -163,7 +163,7 @@ program if (!groupId) { const groupIds = await getGroupIds(network) - if (groupIds === null) { + if (!groupIds || groupIds.length === 0) { console.info(`\n ${logSymbols.info}`, `info: there are no groups on the '${network}' network\n`) return } @@ -189,8 +189,6 @@ program group = await semaphoreEthers.getGroup(groupId) - group.admin = await semaphoreEthers.getGroupAdmin(groupId) - spinner.stop() } catch { spinner.stop() @@ -209,15 +207,14 @@ program content += ` ${chalk.bold("Merkle tree")}:\n` content += ` Root: ${group.merkleTree.root}\n` content += ` Depth: ${group.merkleTree.depth}\n` - content += ` Zero value: ${group.merkleTree.zeroValue}\n` - content += ` Number of leaves: ${group.merkleTree.numberOfLeaves}` + content += ` Size: ${group.merkleTree.size}` console.info(`\n${content}\n`) }) program .command("get-members") - .description("Get the members of a group from a supported network (e.g. goerli or arbitrum).") + .description("Get the members of a group from a supported network (e.g. sepolia or arbitrum).") .argument("[group-id]", "Identifier of the group.") .option("-n, --network ", "Supported Ethereum network.") .allowExcessArguments(false) @@ -234,7 +231,7 @@ program if (!groupId) { const groupIds = await getGroupIds(network) - if (groupIds === null) { + if (!groupIds || groupIds.length === 0) { console.info(`\n ${logSymbols.info}`, `info: there are no groups on the '${network}' network\n`) return } @@ -283,7 +280,7 @@ program program .command("get-proofs") - .description("Get the proofs of a group from a supported network (e.g. goerli or arbitrum).") + .description("Get the proofs of a group from a supported network (e.g. sepolia or arbitrum).") .argument("[group-id]", "Identifier of the group.") .option("-n, --network ", "Supported Ethereum network.") .allowExcessArguments(false) @@ -300,7 +297,7 @@ program if (!groupId) { const groupIds = await getGroupIds(network) - if (groupIds === null) { + if (!groupIds || groupIds.length === 0) { console.info(`\n ${logSymbols.info}`, `info: there are no groups on the '${network}' network\n`) return } @@ -308,7 +305,7 @@ program groupId = await getGroupId(groupIds) } - let verifiedProofs: any[] + let validatedProofs: any[] const spinner = new Spinner(`Fetching proofs of group ${groupId}`) @@ -317,15 +314,15 @@ program try { const semaphoreSubgraph = new SemaphoreSubgraph(network) - const group = await semaphoreSubgraph.getGroup(groupId, { verifiedProofs: true }) - verifiedProofs = group.verifiedProofs + const group = await semaphoreSubgraph.getGroup(groupId, { validatedProofs: true }) + validatedProofs = group.validatedProofs spinner.stop() } catch { try { const semaphoreEthers = new SemaphoreEthers(network) - verifiedProofs = await semaphoreEthers.getGroupVerifiedProofs(groupId) + validatedProofs = await semaphoreEthers.getGroupValidatedProofs(groupId) spinner.stop() } catch { @@ -335,15 +332,17 @@ program } } - if (verifiedProofs.length === 0) { + if (validatedProofs.length === 0) { console.info(`\n ${logSymbols.info}`, "info: there are no proofs in this group\n") return } - const content = `${chalk.bold("Proofs")} (${verifiedProofs.length}): \n${verifiedProofs + const content = `${chalk.bold("Proofs")} (${validatedProofs.length}): \n${validatedProofs .map( - ({ signal, merkleTreeRoot, externalNullifier, nullifierHash }: any, i: number) => - ` ${i}. signal: ${signal} \n merkleTreeRoot: ${merkleTreeRoot} \n externalNullifier: ${externalNullifier} \n nullifierHash: ${nullifierHash}` + ({ message, merkleTreeRoot, merkleTreeDepth, scope, nullifier, points }: any, i: number) => + ` ${i}. message: ${message} \n merkleTreeRoot: ${merkleTreeRoot} \n merkleTreeDepth: ${merkleTreeDepth} \n scope: ${scope} \n nullifier: ${nullifier} \n points: [${points.join( + ", " + )}]` ) .join("\n")}` diff --git a/packages/cli/src/spinner.ts b/packages/cli/src/spinner.ts index 943195219..b69771fc0 100644 --- a/packages/cli/src/spinner.ts +++ b/packages/cli/src/spinner.ts @@ -1,20 +1,17 @@ import ora, { Ora } from "ora" export default class Spinner { - private text: string private ora: Ora constructor(text: string) { - this.text = text + this.ora = ora({ + text, + indent: 1 + }) } start() { - console.info("") - - this.ora = ora({ - text: this.text, - indent: 1 - }).start() + this.ora.start() } stop() { diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 40f865a10..198db003c 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -1,9 +1,9 @@ { "compilerOptions": { "baseUrl": ".", - "target": "es2020", + "target": "ES2020", "module": "ESNext", - "moduleResolution": "Node16", + "moduleResolution": "Bundler", "allowSyntheticDefaultImports": true }, "include": ["src", "rollup.config.ts"] diff --git a/packages/contracts/.solhintignore b/packages/contracts/.solhintignore deleted file mode 100644 index 1ddf4d72f..000000000 --- a/packages/contracts/.solhintignore +++ /dev/null @@ -1 +0,0 @@ -contracts/base/Pairing.sol diff --git a/packages/contracts/contracts/LICENSE b/packages/contracts/contracts/LICENSE index bdb9b1e08..8ef16f7a5 100644 --- a/packages/contracts/contracts/LICENSE +++ b/packages/contracts/contracts/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Ethereum Foundation +Copyright (c) 2024 Ethereum Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/contracts/contracts/README.md b/packages/contracts/contracts/README.md index 982f0a781..4aeb00770 100644 --- a/packages/contracts/contracts/README.md +++ b/packages/contracts/contracts/README.md @@ -10,7 +10,7 @@ - Github license + NPM license NPM version @@ -107,14 +107,14 @@ yarn deploy:semaphore --semaphoreVerifier
> **Note** > Run `yarn deploy:semaphore --help` to see the complete list. -If you want to deploy your contract in a specific network you can set up the `DEFAULT_NETWORK` variable in your `.env` file with the name of one of our supported networks (hardhat, localhost, goerli, arbitrum). Or you can specify it as an option: +If you want to deploy your contract in a specific network you can set up the `DEFAULT_NETWORK` variable in your `.env` file with the name of one of our supported networks (hardhat, localhost, sepolia, arbitrum). Or you can specify it as an option: ```bash -yarn deploy:semaphore --network goerli yarn deploy:semaphore --network sepolia yarn deploy:semaphore --network mumbai -yarn deploy:semaphore --network optimism-goerli +yarn deploy:semaphore --network optimism-sepolia +yarn deploy:semaphore --network arbitrum-sepolia yarn deploy:semaphore --network arbitrum ``` -If you want to deploy contracts on Goerli or Arbitrum, remember to provide a valid private key and an Infura API in your `.env` file. +If you want to deploy contracts on Sepolia or Arbitrum, remember to provide a valid private key and an Infura API in your `.env` file. diff --git a/packages/contracts/contracts/Semaphore.sol b/packages/contracts/contracts/Semaphore.sol index f65d611fe..793228083 100644 --- a/packages/contracts/contracts/Semaphore.sol +++ b/packages/contracts/contracts/Semaphore.sol @@ -1,88 +1,52 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.4; +pragma solidity 0.8.23; -import "./interfaces/ISemaphore.sol"; -import "./interfaces/ISemaphoreVerifier.sol"; -import "./base/SemaphoreGroups.sol"; +import {ISemaphore} from "./interfaces/ISemaphore.sol"; +import {ISemaphoreVerifier} from "./interfaces/ISemaphoreVerifier.sol"; +import {SemaphoreGroups} from "./base/SemaphoreGroups.sol"; /// @title Semaphore /// @dev This contract uses the Semaphore base contracts to provide a complete service -/// to allow admins to create and manage groups and their members to generate Semaphore proofs -/// and verify them. Group admins can add, update or remove group members, and can be -/// an Ethereum account or a smart contract. This contract also assigns each new Merkle tree -/// generated with a new root a duration (or an expiry) within which the proofs generated with that root -/// can be validated. +/// to allow admins to create and manage groups and their members to verify Semaphore proofs +/// Group admins can add, update or remove group members, and can be an Ethereum account or a smart contract. +/// This contract also assigns each new Merkle tree generated with a new root a duration (or an expiry) +/// within which the proofs generated with that root can be validated. contract Semaphore is ISemaphore, SemaphoreGroups { ISemaphoreVerifier public verifier; /// @dev Gets a group id and returns the group parameters. mapping(uint256 => Group) public groups; - /// @dev Checks if the group admin is the transaction sender. - /// @param groupId: Id of the group. - modifier onlyGroupAdmin(uint256 groupId) { - if (groups[groupId].admin != _msgSender()) { - revert Semaphore__CallerIsNotTheGroupAdmin(); - } - _; - } - - /// @dev Checks if there is a verifier for the given tree depth. - /// @param merkleTreeDepth: Depth of the tree. - modifier onlySupportedMerkleTreeDepth(uint256 merkleTreeDepth) { - if (merkleTreeDepth < 16 || merkleTreeDepth > 32) { - revert Semaphore__MerkleTreeDepthIsNotSupported(); - } - _; - } - /// @dev Initializes the Semaphore verifier used to verify the user's ZK proofs. - /// @param _verifier: Semaphore verifier address. + /// @param _verifier: Semaphore verifier addresse. constructor(ISemaphoreVerifier _verifier) { verifier = _verifier; } - /// @dev See {ISemaphore-createGroup}. - function createGroup( - uint256 groupId, - uint256 merkleTreeDepth, - address admin - ) external override onlySupportedMerkleTreeDepth(merkleTreeDepth) { - _createGroup(groupId, merkleTreeDepth); + /// @dev See {SemaphoreGroups-_createGroup}. + function createGroup(uint256 groupId, address admin) external override { + _createGroup(groupId, admin); - groups[groupId].admin = admin; groups[groupId].merkleTreeDuration = 1 hours; - - emit GroupAdminUpdated(groupId, address(0), admin); } /// @dev See {ISemaphore-createGroup}. - function createGroup( - uint256 groupId, - uint256 merkleTreeDepth, - address admin, - uint256 merkleTreeDuration - ) external override onlySupportedMerkleTreeDepth(merkleTreeDepth) { - _createGroup(groupId, merkleTreeDepth); + function createGroup(uint256 groupId, address admin, uint256 merkleTreeDuration) external override { + _createGroup(groupId, admin); - groups[groupId].admin = admin; groups[groupId].merkleTreeDuration = merkleTreeDuration; - - emit GroupAdminUpdated(groupId, address(0), admin); } - /// @dev See {ISemaphore-updateGroupAdmin}. - function updateGroupAdmin(uint256 groupId, address newAdmin) external override onlyGroupAdmin(groupId) { - groups[groupId].admin = newAdmin; - - emit GroupAdminUpdated(groupId, _msgSender(), newAdmin); + /// @dev See {SemaphoreGroups-_updateGroupAdmin}. + function updateGroupAdmin(uint256 groupId, address newAdmin) external override { + _updateGroupAdmin(groupId, newAdmin); } /// @dev See {ISemaphore-updateGroupMerkleTreeDuration}. function updateGroupMerkleTreeDuration( uint256 groupId, uint256 newMerkleTreeDuration - ) external override onlyGroupAdmin(groupId) { + ) external override onlyExistingGroup(groupId) onlyGroupAdmin(groupId) { uint256 oldMerkleTreeDuration = groups[groupId].merkleTreeDuration; groups[groupId].merkleTreeDuration = newMerkleTreeDuration; @@ -90,8 +54,8 @@ contract Semaphore is ISemaphore, SemaphoreGroups { emit GroupMerkleTreeDurationUpdated(groupId, oldMerkleTreeDuration, newMerkleTreeDuration); } - /// @dev See {ISemaphore-addMember}. - function addMember(uint256 groupId, uint256 identityCommitment) external override onlyGroupAdmin(groupId) { + /// @dev See {SemaphoreGroups-_addMember}. + function addMember(uint256 groupId, uint256 identityCommitment) external override { _addMember(groupId, identityCommitment); uint256 merkleTreeRoot = getMerkleTreeRoot(groupId); @@ -99,74 +63,99 @@ contract Semaphore is ISemaphore, SemaphoreGroups { groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp; } - /// @dev See {ISemaphore-addMembers}. - function addMembers( - uint256 groupId, - uint256[] calldata identityCommitments - ) external override onlyGroupAdmin(groupId) { - for (uint256 i = 0; i < identityCommitments.length; ) { - _addMember(groupId, identityCommitments[i]); - - unchecked { - ++i; - } - } + /// @dev See {SemaphoreGroups-_addMembers}. + function addMembers(uint256 groupId, uint256[] calldata identityCommitments) external override { + _addMembers(groupId, identityCommitments); uint256 merkleTreeRoot = getMerkleTreeRoot(groupId); groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp; } - /// @dev See {ISemaphore-updateMember}. + /// @dev See {SemaphoreGroups-_updateMember}. function updateMember( uint256 groupId, uint256 identityCommitment, uint256 newIdentityCommitment, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) external override onlyGroupAdmin(groupId) { - _updateMember(groupId, identityCommitment, newIdentityCommitment, proofSiblings, proofPathIndices); + uint256[] calldata merkleProofSiblings + ) external override { + _updateMember(groupId, identityCommitment, newIdentityCommitment, merkleProofSiblings); uint256 merkleTreeRoot = getMerkleTreeRoot(groupId); groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp; } - /// @dev See {ISemaphore-removeMember}. + /// @dev See {SemaphoreGroups-_removeMember}. function removeMember( uint256 groupId, uint256 identityCommitment, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) external override onlyGroupAdmin(groupId) { - _removeMember(groupId, identityCommitment, proofSiblings, proofPathIndices); + uint256[] calldata merkleProofSiblings + ) external override { + _removeMember(groupId, identityCommitment, merkleProofSiblings); uint256 merkleTreeRoot = getMerkleTreeRoot(groupId); groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp; } + /// @dev See {ISemaphore-validateProof}. + function validateProof( + uint256 groupId, + SemaphoreProof calldata proof + ) external override onlyExistingGroup(groupId) { + // The function will revert if the nullifier that is part of the proof, + // was already used inside the group with id groupId. + if (groups[groupId].nullifiers[proof.nullifier]) { + revert Semaphore__YouAreUsingTheSameNullifierTwice(); + } + + // The function will revert if the proof is not verified successfully. + if (!verifyProof(groupId, proof)) { + revert Semaphore__InvalidProof(); + } + + // Saves the nullifier so that it cannot be used again to successfully verify a proof + // that is part of the group with id groupId. + groups[groupId].nullifiers[proof.nullifier] = true; + + emit ProofValidated( + groupId, + proof.merkleTreeDepth, + proof.merkleTreeRoot, + proof.nullifier, + proof.message, + proof.scope, + proof.points + ); + } + /// @dev See {ISemaphore-verifyProof}. function verifyProof( uint256 groupId, - uint256 merkleTreeRoot, - uint256 signal, - uint256 nullifierHash, - uint256 externalNullifier, - uint256[8] calldata proof - ) external override { - uint256 merkleTreeDepth = getMerkleTreeDepth(groupId); + SemaphoreProof calldata proof + ) public view override onlyExistingGroup(groupId) returns (bool) { + // The function will revert if the Merkle tree depth is not supported. + if (proof.merkleTreeDepth < 1 || proof.merkleTreeDepth > 12) { + revert Semaphore__MerkleTreeDepthIsNotSupported(); + } + + // Gets the number of leaves in the Incremental Merkle Tree that represents the group + // with id groupId which is the same as the number of members in the group groupId. + uint256 merkleTreeSize = getMerkleTreeSize(groupId); - if (merkleTreeDepth == 0) { - revert Semaphore__GroupDoesNotExist(); + // The function will revert if there are no members in the group. + if (merkleTreeSize == 0) { + revert Semaphore__GroupHasNoMembers(); } + // Gets the Merkle root of the Incremental Merkle Tree that represents the group with id groupId. uint256 currentMerkleTreeRoot = getMerkleTreeRoot(groupId); // A proof could have used an old Merkle tree root. // https://github.com/semaphore-protocol/semaphore/issues/98 - if (merkleTreeRoot != currentMerkleTreeRoot) { - uint256 merkleRootCreationDate = groups[groupId].merkleRootCreationDates[merkleTreeRoot]; + if (proof.merkleTreeRoot != currentMerkleTreeRoot) { + uint256 merkleRootCreationDate = groups[groupId].merkleRootCreationDates[proof.merkleTreeRoot]; uint256 merkleTreeDuration = groups[groupId].merkleTreeDuration; if (merkleRootCreationDate == 0) { @@ -178,14 +167,20 @@ contract Semaphore is ISemaphore, SemaphoreGroups { } } - if (groups[groupId].nullifierHashes[nullifierHash]) { - revert Semaphore__YouAreUsingTheSameNillifierTwice(); - } - - verifier.verifyProof(merkleTreeRoot, nullifierHash, signal, externalNullifier, proof, merkleTreeDepth); - - groups[groupId].nullifierHashes[nullifierHash] = true; + return + verifier.verifyProof( + [proof.points[0], proof.points[1]], + [[proof.points[2], proof.points[3]], [proof.points[4], proof.points[5]]], + [proof.points[6], proof.points[7]], + [proof.merkleTreeRoot, proof.nullifier, _hash(proof.message), _hash(proof.scope)], + proof.merkleTreeDepth + ); + } - emit ProofVerified(groupId, merkleTreeRoot, nullifierHash, externalNullifier, signal); + /// @dev Creates a keccak256 hash of a message compatible with the SNARK scalar modulus. + /// @param message: Message to be hashed. + /// @return Message digest. + function _hash(uint256 message) private pure returns (uint256) { + return uint256(keccak256(abi.encodePacked(message))) >> 8; } } diff --git a/packages/contracts/contracts/base/Pairing.sol b/packages/contracts/contracts/base/Pairing.sol deleted file mode 100644 index bdf428f27..000000000 --- a/packages/contracts/contracts/base/Pairing.sol +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2017 Christian Reitwiessner -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// -// The following Pairing library is a modified version adapted to Semaphore. -// -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -library Pairing { - error InvalidProof(); - - // The prime q in the base field F_q for G1 - uint256 constant BASE_MODULUS = 21888242871839275222246405745257275088696311157297823662689037894645226208583; - - // The prime modulus of the scalar field of G1. - uint256 constant SCALAR_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617; - - struct G1Point { - uint256 X; - uint256 Y; - } - - // Encoding of field elements is: X[0] * z + X[1] - struct G2Point { - uint256[2] X; - uint256[2] Y; - } - - /// @return the generator of G1 - function P1() public pure returns (G1Point memory) { - return G1Point(1, 2); - } - - /// @return the generator of G2 - function P2() public pure returns (G2Point memory) { - return - G2Point( - [ - 11559732032986387107991004021392285783925812861821192530917403151452391805634, - 10857046999023057135944570762232829481370756359578518086990519993285655852781 - ], - [ - 4082367875863433681332203403145435568316851327593401208105741076214120093531, - 8495653923123431417604973247489272438418190587263600148770280649306958101930 - ] - ); - } - - /// @return r the negation of p, i.e. p.addition(p.negate()) should be zero. - function negate(G1Point memory p) public pure returns (G1Point memory r) { - if (p.X == 0 && p.Y == 0) { - return G1Point(0, 0); - } - - // Validate input or revert - if (p.X >= BASE_MODULUS || p.Y >= BASE_MODULUS) { - revert InvalidProof(); - } - - // We know p.Y > 0 and p.Y < BASE_MODULUS. - return G1Point(p.X, BASE_MODULUS - p.Y); - } - - /// @return r the sum of two points of G1 - function addition(G1Point memory p1, G1Point memory p2) public view returns (G1Point memory r) { - // By EIP-196 all input is validated to be less than the BASE_MODULUS and form points - // on the curve. - uint256[4] memory input; - - input[0] = p1.X; - input[1] = p1.Y; - input[2] = p2.X; - input[3] = p2.Y; - - bool success; - - // solium-disable-next-line security/no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) - } - - if (!success) { - revert InvalidProof(); - } - } - - /// @return r the product of a point on G1 and a scalar, i.e. - /// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p. - function scalar_mul(G1Point memory p, uint256 s) public view returns (G1Point memory r) { - // By EIP-196 the values p.X and p.Y are verified to be less than the BASE_MODULUS and - // form a valid point on the curve. But the scalar is not verified, so we do that explicitly. - if (s >= SCALAR_MODULUS) { - revert InvalidProof(); - } - - uint256[3] memory input; - - input[0] = p.X; - input[1] = p.Y; - input[2] = s; - - bool success; - - // solium-disable-next-line security/no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) - } - - if (!success) { - revert InvalidProof(); - } - } - - /// Asserts the pairing check - /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 - /// For example pairing([P1(), P1().negate()], [P2(), P2()]) should succeed - function pairingCheck(G1Point[] memory p1, G2Point[] memory p2) public view { - // By EIP-197 all input is verified to be less than the BASE_MODULUS and form elements in their - // respective groups of the right order. - if (p1.length != p2.length) { - revert InvalidProof(); - } - - uint256 elements = p1.length; - uint256 inputSize = elements * 6; - uint256[] memory input = new uint256[](inputSize); - - for (uint256 i = 0; i < elements; i++) { - input[i * 6 + 0] = p1[i].X; - input[i * 6 + 1] = p1[i].Y; - input[i * 6 + 2] = p2[i].X[0]; - input[i * 6 + 3] = p2[i].X[1]; - input[i * 6 + 4] = p2[i].Y[0]; - input[i * 6 + 5] = p2[i].Y[1]; - } - - uint256[1] memory out; - bool success; - - // solium-disable-next-line security/no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) - } - - if (!success || out[0] != 1) { - revert InvalidProof(); - } - } -} diff --git a/packages/contracts/contracts/base/SemaphoreGroups.sol b/packages/contracts/contracts/base/SemaphoreGroups.sol index d8a889fe1..a82006f5c 100644 --- a/packages/contracts/contracts/base/SemaphoreGroups.sol +++ b/packages/contracts/contracts/base/SemaphoreGroups.sol @@ -1,106 +1,149 @@ //SPDX-License-Identifier: MIT -pragma solidity 0.8.4; +pragma solidity 0.8.23; -import "../interfaces/ISemaphoreGroups.sol"; -import "@zk-kit/incremental-merkle-tree.sol/IncrementalBinaryTree.sol"; -import "@openzeppelin/contracts/utils/Context.sol"; +import {ISemaphoreGroups} from "../interfaces/ISemaphoreGroups.sol"; +import {InternalLeanIMT, LeanIMTData} from "@zk-kit/imt.sol/internal/InternalLeanIMT.sol"; /// @title Semaphore groups contract. /// @dev This contract allows you to create groups, add, remove and update members. /// You can use getters to obtain informations about groups (root, depth, number of leaves). -abstract contract SemaphoreGroups is Context, ISemaphoreGroups { - using IncrementalBinaryTree for IncrementalTreeData; +abstract contract SemaphoreGroups is ISemaphoreGroups { + using InternalLeanIMT for LeanIMTData; - /// @dev Gets a group id and returns the tree data. - mapping(uint256 => IncrementalTreeData) internal merkleTrees; + /// @dev Gets a group id and returns its tree data. + /// The tree is an Incremental Merkle Tree + /// which is called Lean Incremental Merkle Tree. + mapping(uint256 => LeanIMTData) internal merkleTrees; - /// @dev Creates a new group by initializing the associated tree. + /// @dev Gets a group id and returns its admin. + /// The admin can be an Ethereum account or a smart contract. + mapping(uint256 => address) internal admins; + + /// @dev Checks if the group admin is the transaction sender. + /// @param groupId: Id of the group. + modifier onlyGroupAdmin(uint256 groupId) { + if (admins[groupId] != msg.sender) { + revert Semaphore__CallerIsNotTheGroupAdmin(); + } + _; + } + + /// @dev Checks if the group exists. + /// @param groupId: Id of the group. + modifier onlyExistingGroup(uint256 groupId) { + if (admins[groupId] == address(0)) { + revert Semaphore__GroupDoesNotExist(); + } + + _; + } + + /// @dev Creates a new group. Only the admin will be able to add or remove members. /// @param groupId: Id of the group. - /// @param merkleTreeDepth: Depth of the tree. - function _createGroup(uint256 groupId, uint256 merkleTreeDepth) internal virtual { - if (getMerkleTreeDepth(groupId) != 0) { + /// @param admin: Admin of the group. + function _createGroup(uint256 groupId, address admin) internal virtual { + if (admins[groupId] != address(0)) { revert Semaphore__GroupAlreadyExists(); } - // The zeroValue is an implicit member of the group, or an implicit leaf of the Merkle tree. - // Although there is a remote possibility that the preimage of - // the hash may be calculated, using this value we aim to minimize the risk. - uint256 zeroValue = uint256(keccak256(abi.encodePacked(groupId))) >> 8; + admins[groupId] = admin; - merkleTrees[groupId].init(merkleTreeDepth, zeroValue); + emit GroupCreated(groupId); + emit GroupAdminUpdated(groupId, address(0), admin); + } + + /// @dev Updates the group admin. + /// @param groupId: Id of the group. + /// @param newAdmin: New admin of the group. + function _updateGroupAdmin( + uint256 groupId, + address newAdmin + ) internal virtual onlyExistingGroup(groupId) onlyGroupAdmin(groupId) { + admins[groupId] = newAdmin; - emit GroupCreated(groupId, merkleTreeDepth, zeroValue); + emit GroupAdminUpdated(groupId, msg.sender, newAdmin); } /// @dev Adds an identity commitment to an existing group. /// @param groupId: Id of the group. /// @param identityCommitment: New identity commitment. - function _addMember(uint256 groupId, uint256 identityCommitment) internal virtual { - if (getMerkleTreeDepth(groupId) == 0) { - revert Semaphore__GroupDoesNotExist(); - } + function _addMember( + uint256 groupId, + uint256 identityCommitment + ) internal virtual onlyExistingGroup(groupId) onlyGroupAdmin(groupId) { + uint256 index = getMerkleTreeSize(groupId); + uint256 merkleTreeRoot = merkleTrees[groupId]._insert(identityCommitment); - merkleTrees[groupId].insert(identityCommitment); + emit MemberAdded(groupId, index, identityCommitment, merkleTreeRoot); + } - uint256 merkleTreeRoot = getMerkleTreeRoot(groupId); - uint256 index = getNumberOfMerkleTreeLeaves(groupId) - 1; + /// @dev Adds new members to an existing group. + /// @param groupId: Id of the group. + /// @param identityCommitments: New identity commitments. + function _addMembers(uint256 groupId, uint256[] calldata identityCommitments) internal virtual { + uint256 startIndex = getMerkleTreeSize(groupId); + uint256 merkleTreeRoot = merkleTrees[groupId]._insertMany(identityCommitments); - emit MemberAdded(groupId, index, identityCommitment, merkleTreeRoot); + emit MembersAdded(groupId, startIndex, identityCommitments, merkleTreeRoot); } /// @dev Updates an identity commitment of an existing group. A proof of membership is /// needed to check if the node to be updated is part of the tree. /// @param groupId: Id of the group. - /// @param identityCommitment: Existing identity commitment to be updated. + /// @param oldIdentityCommitment: Existing identity commitment to be updated. /// @param newIdentityCommitment: New identity commitment. - /// @param proofSiblings: Array of the sibling nodes of the proof of membership. - /// @param proofPathIndices: Path of the proof of membership. + /// @param merkleProofSiblings: Array of the sibling nodes of the proof of membership. function _updateMember( uint256 groupId, - uint256 identityCommitment, + uint256 oldIdentityCommitment, uint256 newIdentityCommitment, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) internal virtual { - if (getMerkleTreeDepth(groupId) == 0) { - revert Semaphore__GroupDoesNotExist(); - } - - merkleTrees[groupId].update(identityCommitment, newIdentityCommitment, proofSiblings, proofPathIndices); - - uint256 merkleTreeRoot = getMerkleTreeRoot(groupId); - uint256 index = proofPathIndicesToMemberIndex(proofPathIndices); - - emit MemberUpdated(groupId, index, identityCommitment, newIdentityCommitment, merkleTreeRoot); + uint256[] calldata merkleProofSiblings + ) internal virtual onlyExistingGroup(groupId) onlyGroupAdmin(groupId) { + uint256 index = merkleTrees[groupId]._indexOf(oldIdentityCommitment); + uint256 merkleTreeRoot = merkleTrees[groupId]._update( + oldIdentityCommitment, + newIdentityCommitment, + merkleProofSiblings + ); + + emit MemberUpdated(groupId, index, oldIdentityCommitment, newIdentityCommitment, merkleTreeRoot); } /// @dev Removes an identity commitment from an existing group. A proof of membership is /// needed to check if the node to be deleted is part of the tree. /// @param groupId: Id of the group. /// @param identityCommitment: Existing identity commitment to be removed. - /// @param proofSiblings: Array of the sibling nodes of the proof of membership. - /// @param proofPathIndices: Path of the proof of membership. + /// @param merkleProofSiblings: Array of the sibling nodes of the proof of membership. function _removeMember( uint256 groupId, uint256 identityCommitment, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) internal virtual { - if (getMerkleTreeDepth(groupId) == 0) { - revert Semaphore__GroupDoesNotExist(); - } - - merkleTrees[groupId].remove(identityCommitment, proofSiblings, proofPathIndices); + uint256[] calldata merkleProofSiblings + ) internal virtual onlyExistingGroup(groupId) onlyGroupAdmin(groupId) { + uint256 index = merkleTrees[groupId]._indexOf(identityCommitment); - uint256 merkleTreeRoot = getMerkleTreeRoot(groupId); - uint256 index = proofPathIndicesToMemberIndex(proofPathIndices); + uint256 merkleTreeRoot = merkleTrees[groupId]._remove(identityCommitment, merkleProofSiblings); emit MemberRemoved(groupId, index, identityCommitment, merkleTreeRoot); } + /// @dev See {ISemaphoreGroups-getGroupAdmin}. + function getGroupAdmin(uint256 groupId) public view virtual override returns (address) { + return admins[groupId]; + } + + /// @dev See {ISemaphoreGroups-hasMember}. + function hasMember(uint256 groupId, uint256 identityCommitment) public view virtual override returns (bool) { + return merkleTrees[groupId]._has(identityCommitment); + } + + /// @dev See {ISemaphoreGroups-indexOf}. + function indexOf(uint256 groupId, uint256 identityCommitment) public view virtual override returns (uint256) { + return merkleTrees[groupId]._indexOf(identityCommitment); + } + /// @dev See {ISemaphoreGroups-getMerkleTreeRoot}. function getMerkleTreeRoot(uint256 groupId) public view virtual override returns (uint256) { - return merkleTrees[groupId].root; + return merkleTrees[groupId]._root(); } /// @dev See {ISemaphoreGroups-getMerkleTreeDepth}. @@ -108,31 +151,8 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups { return merkleTrees[groupId].depth; } - /// @dev See {ISemaphoreGroups-getNumberOfMerkleTreeLeaves}. - function getNumberOfMerkleTreeLeaves(uint256 groupId) public view virtual override returns (uint256) { - return merkleTrees[groupId].numberOfLeaves; - } - - /// @dev Converts the path indices of a Merkle proof to the identity commitment index in the tree. - /// @param proofPathIndices: Path of the proof of membership. - /// @return Index of a group member. - function proofPathIndicesToMemberIndex(uint8[] calldata proofPathIndices) private pure returns (uint256) { - uint256 memberIndex = 0; - - for (uint8 i = uint8(proofPathIndices.length); i > 0; ) { - if (memberIndex > 0 || proofPathIndices[i - 1] != 0) { - memberIndex *= 2; - - if (proofPathIndices[i - 1] == 1) { - memberIndex += 1; - } - } - - unchecked { - --i; - } - } - - return memberIndex; + /// @dev See {ISemaphoreGroups-getMerkleTreeSize}. + function getMerkleTreeSize(uint256 groupId) public view virtual override returns (uint256) { + return merkleTrees[groupId].size; } } diff --git a/packages/contracts/contracts/base/SemaphoreVerifier.sol b/packages/contracts/contracts/base/SemaphoreVerifier.sol index 6addbafea..e74a85d7c 100644 --- a/packages/contracts/contracts/base/SemaphoreVerifier.sol +++ b/packages/contracts/contracts/base/SemaphoreVerifier.sol @@ -1,113 +1,373 @@ -//SPDX-License-Identifier: MIT -pragma solidity 0.8.4; +// SPDX-License-Identifier: MIT +// Part of this file was generated with [snarkJS](https://github.com/iden3/snarkjs). -import "../interfaces/ISemaphoreVerifier.sol"; +pragma solidity 0.8.23; -/// @title Semaphore verifier contract. -/// @notice Minimal code to allow users to verify their Semaphore proofs. -/// @dev This contract allows you to verify whether a Semaphore proof is correct. -/// It is a modified version of the Groth16 verifier template of SnarkJS -/// (https://github.com/iden3/snarkjs) adapted to Semaphore. The Pairing library -/// is external. -contract SemaphoreVerifier is ISemaphoreVerifier { - using Pairing for *; +contract SemaphoreVerifier { + // Scalar field size + uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + // Base field size + uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; - // prettier-ignore - // solhint-disable-next-line - uint256[2][7][17] VK_POINTS = [[[13406811599156507528361773763681356312643537981039994686313383243831956396116,16243966861079634958125511652590761846958471358623040426599000904006426210032],[11781596534582143578120404722739278517564025497573071755253972265891888117374,15688083679237922164673518758181461582601853873216319711156397437601833996222],[1964404930528116823793003656764176108669615750422202377358993070935069307720,2137714996673694828207437580381836490878070731768805974506391024595988817424],[19568893707760843340848992184233194433177372925415116053368211122719346671126,11639469568629189918046964192305250472192697612201524135560178632824282818614],[5317268879687484957437879782519918549127939892210247573193613900261494313825,528174394975085006443543773707702838726735933116136102590448357278717993744],[14865918005176722116473730206622066845866539143554731094374354951675249722731,3197770568483953664363740385883457803041685902965668289308665954510373380344],[6863358721495494421022713667808247652425178970453300712435830652679038918987,15025816433373311798308762709072064417001390853103872064614174594927359131281]],[[15629200772768268814959330350023920183087521275477047626405113853190187031523,13589689305661231568162336263197960570915890299814486885851912452076929115480],[11464919285924930973853174493551975632739604254498590354200272115844983493029,16004221700357242255845535848024178544616388017965468694776181247983831995562],[17789438292552571310739605737896030466581277887660997531707911256058650850910,4112657509505371631825493224748310061184972897405589115208158208294581472016],[3322052920119834475842380240689494113984887785733316517680891208549118967155,381029395779795399840019487059126246243641886087320875571067736504031557148],[8777645223617381095463415690983421308854368583891690388850387317049320450400,11923582117369144413749726090967341613266070909169947059497952692052020331958],[15493263571528401950994933073246603557158047091963487223668240334879173885581,6315532173951617115856055775098532808695228294437279844344466163873167020700],[3481637421055377106140197938175958155334313900824697193932986771017625492245,20088416136090515091300914661950097694450984520235647990572441134215240947932]],[[9218320951536642499143228327011901814587826948504871816273184688188019956292,19717684456458906358368865507225121991585492363133107109865920739019288468011],[16717590750910963405756115910371408378114896008824240863060392362901176601412,18221695645112467945186983098720611586049108689347006136423489099202471884089],[4691595252082380256698158158199364410440273386659834000993210659508747323919,9205801980459323513061837717352821162780471027241700646145937351740096374660],[16150531426263112884093068164597994126623437929929609532055221646496813246000,20245743178241899668170758952526381872637304119026868520579207157118516761827],[6063536446992770713985314309889717594240410784717230886576072989709763902848,18258781411255795973918859665416013869184055573057512603788635470145328981347],[10109932964756104512054045207253535333686585863745296080906925765480296575285,4174640428253153601540284363759502713687021920150940723252842152556151210349],[18049428534741480832385046397049175120355008065781483226058177421025493210952,591730261265040164434889324846001338201068482543108348317417391345612814922]],[[3995128789564535587814512245259203300137618476815456454931286633947953135662,15953239752392927777442331623182226063776310198012173504208557434319753428770],[20957319343912866335583737646657534123362052690050674068142580221965936605075,2523786679709693946058523307330825034772478122295850507521258983130425334580],[9877211178693075145402462781884120278654771727348087433632224794894486095150,19972682062587174829535281061580296764150591339640180868104711395548066529340],[6324578424031095537345184040149690238371517387586958921377481904541316423724,15513931720576048544404512239839508014664224085062729779520992909505663748296],[11371337652479737143800707796204655130812036287859296372695832558127430723628,11757275188600040111649009832378343123994225623498773406233261322165903848967],[13282496583564708104981015168203451877588903263486398132954741568835583461335,1746144324840370907926720490289700342734912534857331743685374514401176014195],[7993952462467372951144011615584426050192046712674662254138390197508963352374,5156942148925224345709309361345680948125600198010285179548841917923439945819]],[[18976133691706015337908381757202123182841901611067930614519324084182946094218,1382518990777992893805140303684642328066746531257780279226677247567004248173],[6627710380771660558660627878547223719795356903257079198333641681330388499309,21806956747910197517744499423107239699428979652113081469385876768212706694581],[19918517214839406678907482305035208173510172567546071380302965459737278553528,7151186077716310064777520690144511885696297127165278362082219441732663131220],[690581125971423619528508316402701520070153774868732534279095503611995849608,21271996888576045810415843612869789314680408477068973024786458305950370465558],[16461282535702132833442937829027913110152135149151199860671943445720775371319,2814052162479976678403678512565563275428791320557060777323643795017729081887],[4319780315499060392574138782191013129592543766464046592208884866569377437627,13920930439395002698339449999482247728129484070642079851312682993555105218086],[3554830803181375418665292545416227334138838284686406179598687755626325482686,5951609174746846070367113593675211691311013364421437923470787371738135276998]],[[3811592683283527904145155808200366192489850711742363953668998371801696238057,9032545080831535702239063467087720597970266046938395860207839433937324718536],[16308433125974933290258540904373317426123214107276055539769464205982500660715,12429982191499850873612518410809641163252887523090441166572590809691267943605],[9494885690931955877467315318223108618392113101843890678090902614660136056680,11783514256715757384821021009301806722951917744219075907912683963173706887379],[7562082660623781416745328104576133910743071878837764423695105915778139873834,17954307004260053757579194018551114133664721761483240877658498973152950708099],[19338184851116432029108109461622579541195083625346674255186169347975445785058,38361206266360048012365562393026952048730052530888439195454086987795985927],[21178537742782571863590222710872928190886000600239072595684369348717288330049,9786438258541172244884631831247223050494423968411444302812755467521949734320],[11330504221972341797183339350494223413034293674225690456356444509688810101433,1490009915387901405464437253469086864085891770312035292355706249426866485365]],[[9485639152672984144988597737758037391807993615552051606205480347442429414340,17626503110323089701269363177710295379967225765713250625279671011873619640598],[12391874700409435648975069978280047983726144854114915177376036190441913967689,18953587685067712486092665232725058638563458484886448540567142557894080640927],[21791720972262589799021600767292883644106575897307484548888696814333235336885,11092962469758788187888592619035811117815082357439060720677582048880121542623],[9418924955930663972575130074928583215922927562059194231976193350658171304436,16113558481826020406162261319744796072664750077095575593106901121115073101408],[20054934960262983176880675919444457578562219675808407582143519621873973120773,14877415271301547911435683263206245199959943680225555496786470669330176961657],[4215199263810110748751715719957184804379752373072771007598572158043965517488,5225943468606602818132879686778547605180105897615251160509064537462109826521],[6250242626034734280813142093008675407723196706248829741247204621913994561803,1472231555266678689888727724824566171966416459791722465278225775922487343641]],[[9830856103389248449121962275587399130605902703453384856543071762984116567573,11408965575174993375815840422438995549652812400401163392501956884932167624437],[11814906841949499037550820576929552248172160643991870665022770052632331265834,19969543376625663966419118899515353499678204573709836615846115182224340858492],[3047486363455933831148688762823238723024952519326207356549121929667745957778,20241836359289449005887237560564358543646542598344362915541027571505243817211],[5965631918800530319167124148627450454569264331058008407732200168631989208657,20463557477532480934514091877628554948892025887087712764683631108388998871350],[16605042322692983282732511249912403956057999815658038166796858627082222971215,12219061498275616585164456833410962809536084885494309093787669879221959361956],[1548998572074037722622224303222294716243074837074272552644853986075252666508,10393312002885367652301897874262367916506364670364584602554176742602334134772],[16180907689593358346406392015123900260925622357393826746385511046141256905390,12267326749885120640972074479210537480053065569337817484467225562817467244765]],[[15035335306919942325459417688135340085377315274625768597233474641923619728582,10090041889587324002759549286390619541526396451963494627957072069124011137562],[21342049717074059749518233491526445388158772701642182532370641230478027030319,10507786999799841055999967456762679569286329319056926475375760604262707147294],[19590996174696909242575628014943555633938195923520472786993379268302478708283,2673753072556442230312995111304911178679525806396134504594492458566941824354],[13411253172375451489380472831999887223592471057462692619008484995624281735092,17181767455563581254432161119660408482332423481128600038352147258951772423229],[19138864631164378176055647711995352935065134904103255748190268290992108588628,14282526277736365863821375748687709839392307698935143595732632710176778519757],[20183773658676161990469276414858234178608794783112866811307579993999118293429,5223464433544489066271184294750886227362580875255044558831927430970236355539],[12333466991139269670298178539679773509487545471126920233507132846828588847444,3787586478923104354547687861486563468235879611952775292288436085429794222238]],[[15718373132479769904443326381037437528372212185108294117696143473979328398658,43456740675249348549891878341522275183186932745162972528932808393415299552],[11236864934894600819960883124570686936554376109344998527334431594565774237827,4289247401578837038775845192875793775418122783738936298355403103074020081838],[18580370382199518848261939652153768394883698461842792002922164533882262019935,20516185953882700254387267244708111605796661864845495645678049276372075842359],[20041291712709610738573661974551517833120775539593003477018637287434210072702,6326630253906616820412999166182553773360987412889775567442543181359104720511],[13268971611130152315428629919012388924225656285593904211561391821918930327614,9247437189452353488017802041158840512956111558640958728149597697508914590433],[6267384495557139339708615182113725421733376438932580472141549274050146739549,1832264154031452148715318442722960696977572389206897240030908464579133134237],[16650684165487873559901140599157559153018449083939294496255590830891994564285,14140282729498011406186082176268025578697081678243955538935501306868500498994]],[[1723458149089715907994189658689343304709709060535625667210252753337752162173,4023016874169005249382064394379671330447496454371261692205411970999350949293],[7651670126664625790835334090273463062538865895183205964669372719235003083565,17710652158212212080502343565075513548898593397103675832636832371532093744857],[4247947150009812467217672970806328247513830308400387953244764907353849211641,14500381439127180474801393438175928191199696177607750163263715436006533630877],[21213779524495874664157797605662894019112036728653622806607467354233012380232,1429370857470083395421401524518861545167550347090873730934256398864585069083],[12465277751642747637430517396067173985821959773399832969105187923427872239200,4377704428607835904642653580543541241155601291484645500691968624389522190030],[11283027832501128633761619552392013253304972822086786857121687098087331014745,21463394238922953607096052056881931791797740737164052798044623278557203313720],[19687293493101130967741578773742597470558958652351513582962108464055656171331,4445165696525061401582979300506082669540223774145877762689724631935313716632]],[[745924679191739894055143748466112994378439645681039136007774787076115375124,13132169670125192016391258838554965176628317453468870968867717287446623320643],[2126777833939378028304266129616145667925849332481755567268747182629795296580,20909608709868730010029182074820840312550443752829480953667886902663547957991],[3388767735894417381503201756905214431625081913405504580464345986403824999889,21014112837214011009096825602791072748195337199912773858499588477762724153070],[10521317016331497094903116740581271122844131442882845700567581775404872949272,13201921794561774338466680421903602920184688290946713194187958007088351657367],[16170260722059932609965743383032703380650557609693540121262881902248073364496,6004983491336500911294872035126141746032033211872472427212274143945425740617],[10275615677574391293596971122111363003313434841806630200532546038183081960924,5955568702561336410725734958627459212680756023420452791680213386065159525989],[19059081014385850734732058652137664919364805650872154944590269874395511868415,19202365837673729366500417038229950532560250566916189579621883380623278182155]],[[4553625243522856553165922942982108474187282402890756796515747778282922584601,16835654219229187428071649241190746119082269636345872682107941472241044260584],[3272293478534046729728233267765357195255129499603632413158978822084188871854,873742823867191038535544062852920538566418819521732785500614249239215175476],[7856986171681248404396064225772749784181602218562773063185003409958949630985,11707218736744382138692483591389641607570557654489363179025201039696228471230],[2902255937308264958973169948617099471543255757887963647238093192858290079050,4092153880227661899721872164083575597602963673456107552146583620177664115673],[18380478859138320895837407377103009470968863533040661874531861881638854174636,14502773952184441371657781525836310753176308880224816843041318743809785835984],[2781117248053224106149213822307598926495461873135153638774638501111353469325,3500056595279027698683405880585654897391289317486204483344715855049598477604],[8880120765926282932795149634761705738498809569874317407549203808931092257005,19080036326648068547894941015038877788526324720587349784852594495705578761000]],[[7252337675475138150830402909353772156046809729627064992143762325769537840623,7601443214415704135008588588192028557655441716696726549510699770097979655628],[436607343827794507835462908831699962173244647704538949914686722631806931932,18500126298578278987997086114400065402270866280547473913420536595663876273004],[18427701611614193839908361166447988195308352665132182219164437649866377475111,5299493942596042045861137432338955179078182570752746487573709678936617478454],[4188155714164125069834512529839479682516489319499446390214266838952761728656,2720966082507704094346897998659841489771837229143573083003847010258396944787],[13256461570028177373135283778770729308216900804505379897951455548375840027026,10722074030307391322177899534114921764931623271723882054692012663305322382747],[9824147497244652955949696442395586567974424828238608972020527958186701134273,15755269950882650791869946186461432242513999576056199368058858215068920022191],[21172488506061181949536573476893375313339715931330476837156243346077173297265,13892434487977776248366965108031841947713544939953824768291380177301871559945]],[[10202326166286888893675634318107715186834588694714750762952081034135561546271,15028154694713144242204861571552635520290993855826554325002991692907421516918],[18486039841380105976272577521609866666900576498507352937328726490052296469859,12766289885372833812620582632847872978085960777075662988932200910695848591357],[1452272927738590248356371174422184656932731110936062990115610832462181634644,3608050114233210789542189629343107890943266759827387991788718454179833288695],[14798240452388909327945424685903532333765637883272751382037716636327236955001,10773894897711848209682368488916121016695006898681985691467605219098835500201],[17204267933132009093604099819536245144503489322639121825381131096467570698650,7704298975420304156332734115679983371345754866278811368869074990486717531131],[8060465662017324080560848316478407038163145149983639907596180500095598669247,20475082166427284188002500222093571716651248980245637602667562336751029856573],[7457566682692308112726332096733260585025339741083447785327706250123165087868,11904519443874922292602150685069370036383697877657723976244907400392778002614]],[[14930624777162656776068112402283260602512252179767747308433194885322661150422,13682963731073238132274278610660469286329368216526659590944079211949686450402],[18705481657148807016785305378773304476425591636333098330324049960258682574070,21315724107376627085778492378001676935454590984229146391746301404292016287653],[12628427235010608529869146871556870477182704310235373946877240509680742038961,15093298104438768585559335868663959710321348106117735180051519837845319121254],[6593907467779318957599440584793099005109789224774644007604434924706249001015,18549596630007199540674697114946251030815675677713256327810772799104711621483],[6271101737045248834759003849256661059806617144229427987717476992610974162336,355748132218964841305454070022507122319085542484477110563322753565651576458],[2116139772133141967317791473319540620104888687412078412336248003979594158546,4004400204967325849492155713520296687406035356901102254880522534085890616486],[4206647028595764233995379982714022410660284578620723510907006350595207905228,19380634286337609988098517090003334645113675227742745065381519159322795845003]],[[12315240965742683516581565369496371929586281338862761742109651525191835544242,18994803742708336446369128568423705404354655742604689352630273180469431952708],[18019403342409608922812569436317484250134945386869657285229378095251425778096,12707009780301102830224094192984906206920666691015255692741008594808694787917],[2592407181901686208061988776764501828311271519595797153264758207470081204331,11847594161160074962679125411562687287595382335410213641115001866587988494499],[3346927026869562921166545684451290646273836362895645367665514203662899621366,15758185693543979820528128025093553492246135914029575732836221618882836493143],[20528686657810499188368147206002308531447185877994439397529705707372170337045,18025396678079701612906003769476076600196287001844168390936182972248852818155],[9799815250059685769827017947834627563597884023490186073806184882963949644596,4998495094322372762314630336611134866447406022687118703953312157819349892603],[16176535527670849161173306151058200762642157343823553073439957507563856439772,21877331533292960470552563236986670222564955589137303622102707801351340670855]]]; + // Verification Key data + uint256 constant alphax = 20491192805390485299153009773594534940189261866228447918068658471970481763042; + uint256 constant alphay = 9383485363053290200918347156157836566562967994039712273449902621266178545958; + uint256 constant betax1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132; + uint256 constant betax2 = 6375614351688725206403948262868962793625744043794305715222011528459656738731; + uint256 constant betay1 = 21847035105528745403288232691147584728191162732299865338377159692350059136679; + uint256 constant betay2 = 10505242626370262277552901082094356697409835680220590971873171140371331206856; + uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + + // TODO: Add more variables when Semaphore supports tree depth > 12. + // Right now Semaphore supports tree depth 1-12. + + // Verification Key points. + // These values are taken from the verification key json file generated with snarkjs. + // It allows to use the same verifier to verify proofs for all the tree depths supported by Semaphore. + uint256[14][12] VK_POINTS = [ + [ + 563562783592406106461234396505774794044312891062077216951605541624542949349, + 16293410697967515504861065986355060225819302510590370360517024529684437085892, + 718666295291146285397081607986058864287612968465033527894198098928522749430, + 10971164449179097832515056670594147898894418081309026322805549037082528587660, + 16397970494781113622140409380710514277504568512202481522431528914560031562762, + 15987991045804318696151558137607070651882190737142560406194591976742796145195, + 19175554796060272870947844726785784415291967441071791144616572094703752022787, + 14708716857471570462880683190724447733812118518043273649838616424043548023127, + 5109703116431086866657790821388182053265093586387384935734011036204399393630, + 1612476077210311180626421664389270104678910550524363357381847966959374674423, + 16028984344157742576452936855797086856968817024956118691418341585143424903639, + 6905323418829609143361434077039383502192920586565275784934386988372483556465, + 5037234187699234788733084505200789125332575456459095878140727750253994537245, + 5152780378461098676484401284161685966625077837157208907964141131514955942959 + ], + [ + 6925326031648378844726759441690273332175949886518827719224741736678266471462, + 8087990171976409378525016197066789089596692733769545253942804888352233244331, + 10115242383701466910539784663900956956505212347433548599529944880163612117847, + 15618724583521291135890928658458637081479213437530496494788106479523123480134, + 19573269191775350978579562905764743103417752180580176229566975904395505694207, + 2381522187377974692257359562587606852360557473353020907100590706767276986457, + 3570865353993345507636525265023758855364727230473021611280650482974485897542, + 3026650225811133060402545160946181194870257210933662802529138266788423022235, + 10948020004642082805202152014062303498984706484599503419636897666877701726029, + 16425328653566008388486740080892552688012772956429074139489785673820307770702, + 20638502839000044414864636985783149953889799151008745227055624454042471377832, + 977321369609271151576827768083353705509746900519279122286818202637470656742, + 18662986393280647182646025762677321210586855801774577114215804961111542726195, + 9182414327249389553719378979750943052880001176853490206379257023646169811116 + ], + [ + 7699900854308622607636895140107457213471038801393300399043779958706267744510, + 7699563868952902688273782470023919811229667532027732585467624469008534197561, + 8605006500231713390564760089884965869867818332514275728271802101761117177706, + 15390497513810955087868026868634948499789494261604405008298272598684585331879, + 1172653041469601107707030355715854571110526375194334139771259673595104816259, + 139822645729727942447876327389754883087551266970976361358585992124005959663, + 807568760006422352594797418374693945705914727214443753238278163089338607471, + 19362805143026565781098954744725161233661862485826387437200176803022106919133, + 9457043262004277983171082100772969689099515860060732671699192102623198243288, + 19229090298682361289811638032020124933114889462217199961448135904896325661193, + 18883326163102164343877677909116984239805709949130729675634413304023108763212, + 2795333364660917174391473381893559091159208514832297547909172862364581446206, + 18012979848063967305324818138773222084850070233834350625943804711450349870116, + 11425098272720529401115253745902976848228234025625773037823969253767737500443 + ], + [ + 9243647856126339588200227750973237380809101852206502177952556679680468734813, + 2561989864654045354260435368824127763249975523256535586391379695256700207392, + 1308059969166897152269078839315249187046718124106168932020710683189419160756, + 18264861452415533737686303596832882667852638121244187636443702248458018416929, + 5735580005765404165535708760854311053102182143614810924129912232734875728422, + 12547270286864340931439010083179993915942911007015778927394042746771813715897, + 17861179987982979247789643870162154315308943006677455616368767345181299723498, + 12520843301982976970226379882714814545061255956743392202788539428471921045815, + 709419095293140562723620898844593712074413125125086760533949250121053456086, + 20254885558410038279405227446341995510955697359900050994091569463713580982280, + 8876829807505794597615690202974184696287415186921508358861381955321520402876, + 9692396410060123455009190221005078571838194435772371581970098240972280778466, + 11541729643712807778212647011796800767186857085220105751644556558522744760878, + 7117533920695574619512162711113223946930183977424569115798612426749467545080 + ], + [ + 12554869896570109663558999106053541183136083706372178136870048170147558661370, + 21307904757295729894500052152251151076177124351059746872940558537429181679313, + 13447792161027167738515744919555311619301071111923271126707713963292614473975, + 11194665041440536766843756304212299788817698936187605770131686430625103172681, + 7022760843802757109079393624924627417630030139055381384837279305453559861026, + 14368180091281110030022253036601349874026580767275461373222397498585380849175, + 20362090815079102760789516334841889611927869342884580401494938701237909313479, + 11683916255585393494577805363227207275174890005674166623110857790951330330248, + 21680925564107543632769644144771629045807478787600735681275069556336841549441, + 1554686039880869088226596836931999292372497423378849142389700108361111176287, + 9553151402225656007352139561853210183786116738154807824913092141416366824324, + 21774504332021003611111291017295441063483107704370481606906960187337904118400, + 3472743162059388238264946309663113383560193397376658519781344886918012396958, + 21850707327192737842691835772722844106916466129654852946865863108319178245521 + ], + [ + 6841243505372324564043525805425666596605335849634400049689614462868641599622, + 11706947284665470438085734275890584651957983546291282232304205898476084974953, + 8134420958061302592465696766566203845441024351721660066575866529624350946525, + 2674642993002069213220912300725195098434544703961492590713058622848310061455, + 14007544454490183581525584522132854274412440955138184221862170710084681738226, + 9427297140302346741340407818949601657384670126565058224960470754064287232598, + 9424279417196769259939653963004219272435595656090601392111859177294419276463, + 5159704841441138198509282351301933053602549958584575945916187562678590534936, + 5934282708091541939003757621608766449985810436411564642900129758520734711260, + 6572936900512215029112509161886160866328777917297795430264727434969553346474, + 17332317297867087005634642159909283377570778576256008720318658354552654813365, + 20469716791176463599463762213563135543375669917658488173798369718370224346075, + 11895216520141526042182770992519307655836464153974170586288146149131884093776, + 2345310584279491449819095573562431881564998414485162753036518957549727207513 + ], + [ + 16222168105567777760688113388791710300819703656136395550799888633073848798053, + 14347453588631116202352669855016330076170847581618919999243717038742915487177, + 1551977748690015855031859376084014992267998166583511614295554470740621685494, + 5558376366205739247464101507558623934779699722049069497418188353860561540238, + 15084362489402846752495176069529022447007294106313236924068931072406922960482, + 17446301099629553693162731281967866078816607293341610477850995414321505446665, + 15416633712259927998995598381829216728913683600537032862643171407716059372732, + 6910124994917236999728700744617337824847623550680442113266553962793201270115, + 3240338086312503075747637551773566329150860621223080774242357236897458479365, + 9706162939164863315053907226494918827393300584078535638283184502242772542378, + 3821371825593696440611637099905775310839636477646533943959149234595561019388, + 12220054708690507765048929995921680127285008492320123722886943219569231705313, + 16484014909498227505527128570845947338361237083477117662394990633734193572570, + 15639273550026538537711009501005182488432976272250651065072907504931158455474 + ], + [ + 8039261311473792027408130340265108867783988336829815266149766357424909552805, + 13742810756711208866393269789950822956196651488331544225304557369739035583007, + 5732605965679620193714116075516176231919315490749768120227762344555213912038, + 1426283383724925735279168010737048062664480092231550167387716017170422478271, + 7289543915982634578897897440956973302403422297539950338836810160596047457980, + 15177295033774438197517772642364420015492974487860823338193520175227862903717, + 5412509155867259433944542755241941469706034032614068890113478300647952353475, + 16755045912589178729548422106539306191579601567866216267685082373250783779841, + 844425885324371282218176954530597075058496766373014736097970493596574863875, + 12323478340077628423886836431146436403221716179764806398442271274194718652143, + 19468406721151512215981453048984639951787280312568894384216547824763527840472, + 6512636337641737270183934024333064400224464644359645491020358650071524340464, + 8198878215192784492258585011520207242810327647198332623948351347768018051267, + 8014960150646978739731831763393714179490854831922196242908407049327430863238 + ], + [ + 12816421563768297608770270654146762017343826741755988834647936785087288245221, + 4869230832458710492613139050501069397194236257402947715710705773326736170245, + 18301439700961740613282752163964601039402055729651579152511810295028351855917, + 11073344652802336528382437239976464979745376329678013840706100925574578891663, + 9107882721143487446664847981273782313316003453386555221878701631432129058401, + 9441695541513726822451572070540177043748046411169636568582927338148104680158, + 8987068930426301924419900826390508311526176863992306858982473479912439758010, + 1725109031239210601724581840155988791039497876559382612372874257775877974323, + 18516110346729610211387987894593386281257834088357741109524145966500785499183, + 114078214476076549461901405178268623018026929846201249476869510133510589766, + 15915940069177053049090451855582611051242699463952201407239272373068542013292, + 9373936080407194259749015269175580128112032582312467763484971165406599176646, + 4922802633190996456945600288106639549790810185209505074744770090776471062338, + 8883001810279320778962047313612748029483858862205749774979015238684570049088 + ], + [ + 10621957863293158040009666146187340406300006617270113507997717497098731431645, + 11544253490899949675932185200523769178642714850738952025947643180461452257027, + 19367886286197174023585178612664391272570357530876044259219178006444442279835, + 49583159539136224825737189586941489049516773623689603831917260806184061698, + 14718035764669896417897962215694711502145567157050183561348015124983050763912, + 16468329864686708461615593125586840110548434159129257976547738477143616225069, + 19316680422470456517825967239402234468963225932355881906076897312088976727808, + 3793008941035953637072065313624049788550692955904011087840457148221740889857, + 8994123619474927749524041243795236789619386871069943230512200348341409025802, + 2898455184193535874162795400065700362364113037155462125222703939894764399559, + 7006987736985681119096469806956612323700302986805047546691419062106874738669, + 6578526693577992862738197741211972861837397562647850241473856881198749597774, + 18253609658317686864081972526657430282945609846265497350129963610508303097865, + 20602454323920106770928127673050173555696255204782817307960073152139829286712 + ], + [ + 6919690594446594824660388293695365136302375315018989420609208580270256193473, + 20152340858374022568747410447956756883573367358669429548835673215400335225747, + 11674218038035204128186342498577020603218612593894174060137243207689886423596, + 16509772385894559653132866836382733797078280267167499296517180188027714772070, + 18387700354891683161007139598833533459740755646851082725263944626765753998128, + 8496770255291121655312305725645117658510206297162207504909146686627154245846, + 3295351580633631545204707236418977224205703294426182202748223245617942752953, + 11318533876720817956139762907441156918032396007060817178042936071438547629319, + 9798692489110933308397734193670030418121117030189402149994776248649813971130, + 7253879530870754821836349446054636336833607432103959373798000294248292966203, + 21808645514001764351825515375665639285586999794992628373968527911077248645185, + 2859748802987042398453158245958377568437550705999358391809538153774651114865, + 4373537692781319117201803895634857716202697229097770645380222493663057011429, + 21723556418902236605356947828522543021755681613733490227407492902239152334538 + ], + [ + 3246325158773052647788911340476235648870186305670446321333841273770228016262, + 21672319732433045166991956534762990382763080837755725716637027617201056105221, + 1097625536358560225015844871776096852794298581720514105921271391131873344240, + 5332470123606111842725808071294486759248878958039162159296029608796276205941, + 4147129560663007063799754603731275683761368247704376766576071776957227165800, + 7032402220603422034618210155106646820962552774510253688945919060625670415308, + 5241022678363371404039324538224070321660623527603509392150146296843642044363, + 5281273397025328620822441453790518717375265972202147684745427714882287252791, + 8109626675067152219533782552336308649905522111608563670382254454484448073029, + 4696661232179595878220108186097206302552300846640109174608389689072223809776, + 13974026846148052806722306435446895222317781098029808723550029071117080027133, + 14451415757463182353662034231421738047130904367363697865823311429455557043904, + 9490728148230530026163178051838213576627639298618884515939110001242292217290, + 227687333556127166330064214209948378192130717415165040791158610964406937909 + ] + ]; + + // Memory data + uint16 constant pVk = 0; + uint16 constant pPairing = 128; + + uint16 constant pLastMem = 896; - /// @dev See {ISemaphoreVerifier-verifyProof}. function verifyProof( - uint256 merkleTreeRoot, - uint256 nullifierHash, - uint256 signal, - uint256 externalNullifier, - uint256[8] calldata proof, - uint256 merkleTreeDepth - ) external view override { - signal = _hash(signal); - externalNullifier = _hash(externalNullifier); - - Proof memory p; - - p.A = Pairing.G1Point(proof[0], proof[1]); - p.B = Pairing.G2Point([proof[2], proof[3]], [proof[4], proof[5]]); - p.C = Pairing.G1Point(proof[6], proof[7]); - - VerificationKey memory vk = _getVerificationKey(merkleTreeDepth - 16); - - Pairing.G1Point memory vk_x = vk.IC[0]; - - vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[1], merkleTreeRoot)); - vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[2], nullifierHash)); - vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[3], signal)); - vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[4], externalNullifier)); - - Pairing.G1Point[] memory p1 = new Pairing.G1Point[](4); - Pairing.G2Point[] memory p2 = new Pairing.G2Point[](4); - - p1[0] = Pairing.negate(p.A); - p2[0] = p.B; - p1[1] = vk.alfa1; - p2[1] = vk.beta2; - p1[2] = vk_x; - p2[2] = vk.gamma2; - p1[3] = p.C; - p2[3] = vk.delta2; - - Pairing.pairingCheck(p1, p2); - } + uint[2] calldata _pA, + uint[2][2] calldata _pB, + uint[2] calldata _pC, + uint[4] calldata _pubSignals, + uint merkleTreeDepth + ) external view returns (bool) { + uint[14] memory _vkPoints = VK_POINTS[merkleTreeDepth - 1]; - /// @dev Creates the verification key for a specific Merkle tree depth. - /// @param vkPointsIndex: Index of the verification key points. - /// @return Verification key. - function _getVerificationKey(uint256 vkPointsIndex) private view returns (VerificationKey memory) { - VerificationKey memory vk; - - vk.alfa1 = Pairing.G1Point( - 20491192805390485299153009773594534940189261866228447918068658471970481763042, - 9383485363053290200918347156157836566562967994039712273449902621266178545958 - ); - - vk.beta2 = Pairing.G2Point( - [ - 4252822878758300859123897981450591353533073413197771768651442665752259397132, - 6375614351688725206403948262868962793625744043794305715222011528459656738731 - ], - [ - 21847035105528745403288232691147584728191162732299865338377159692350059136679, - 10505242626370262277552901082094356697409835680220590971873171140371331206856 - ] - ); - - vk.gamma2 = Pairing.G2Point( - [ - 11559732032986387107991004021392285783925812861821192530917403151452391805634, - 10857046999023057135944570762232829481370756359578518086990519993285655852781 - ], - [ - 4082367875863433681332203403145435568316851327593401208105741076214120093531, - 8495653923123431417604973247489272438418190587263600148770280649306958101930 - ] - ); - - vk.delta2 = Pairing.G2Point(VK_POINTS[vkPointsIndex][0], VK_POINTS[vkPointsIndex][1]); - - vk.IC = new Pairing.G1Point[](5); - - vk.IC[0] = Pairing.G1Point(VK_POINTS[vkPointsIndex][2][0], VK_POINTS[vkPointsIndex][2][1]); - vk.IC[1] = Pairing.G1Point(VK_POINTS[vkPointsIndex][3][0], VK_POINTS[vkPointsIndex][3][1]); - vk.IC[2] = Pairing.G1Point(VK_POINTS[vkPointsIndex][4][0], VK_POINTS[vkPointsIndex][4][1]); - vk.IC[3] = Pairing.G1Point(VK_POINTS[vkPointsIndex][5][0], VK_POINTS[vkPointsIndex][5][1]); - vk.IC[4] = Pairing.G1Point(VK_POINTS[vkPointsIndex][6][0], VK_POINTS[vkPointsIndex][6][1]); - - return vk; - } + assembly { + function checkField(v) { + if iszero(lt(v, q)) { + mstore(0, 0) + return(0, 0x20) + } + } + + // G1 function to multiply a G1 value(x,y) to value in an address + function g1_mulAccC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn, 32), y) + mstore(add(mIn, 64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + + mstore(add(mIn, 64), mload(pR)) + mstore(add(mIn, 96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + } + + function checkPairing(pA, pB, pC, pubSignals, pMem, vkPoints) -> isOk { + let _pPairing := add(pMem, pPairing) + let _pVk := add(pMem, pVk) + + mstore(_pVk, mload(add(vkPoints, 128))) + mstore(add(_pVk, 32), mload(add(vkPoints, 160))) + + // Compute the linear combination vk_x + + g1_mulAccC(_pVk, mload(add(vkPoints, 192)), mload(add(vkPoints, 224)), calldataload(add(pubSignals, 0))) + + g1_mulAccC( + _pVk, + mload(add(vkPoints, 256)), + mload(add(vkPoints, 288)), + calldataload(add(pubSignals, 32)) + ) + + g1_mulAccC( + _pVk, + mload(add(vkPoints, 320)), + mload(add(vkPoints, 352)), + calldataload(add(pubSignals, 64)) + ) + + g1_mulAccC( + _pVk, + mload(add(vkPoints, 384)), + mload(add(vkPoints, 416)), + calldataload(add(pubSignals, 96)) + ) + + // -A + mstore(_pPairing, calldataload(pA)) + mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) + + // B + mstore(add(_pPairing, 64), calldataload(pB)) + mstore(add(_pPairing, 96), calldataload(add(pB, 32))) + mstore(add(_pPairing, 128), calldataload(add(pB, 64))) + mstore(add(_pPairing, 160), calldataload(add(pB, 96))) + + // alpha1 + mstore(add(_pPairing, 192), alphax) + mstore(add(_pPairing, 224), alphay) + + // beta2 + mstore(add(_pPairing, 256), betax1) + mstore(add(_pPairing, 288), betax2) + mstore(add(_pPairing, 320), betay1) + mstore(add(_pPairing, 352), betay2) + + // vk_x + mstore(add(_pPairing, 384), mload(add(pMem, pVk))) + mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) + + // gamma2 + mstore(add(_pPairing, 448), gammax1) + mstore(add(_pPairing, 480), gammax2) + mstore(add(_pPairing, 512), gammay1) + mstore(add(_pPairing, 544), gammay2) + + // C + mstore(add(_pPairing, 576), calldataload(pC)) + mstore(add(_pPairing, 608), calldataload(add(pC, 32))) + + // delta2 + mstore(add(_pPairing, 640), mload(vkPoints)) + mstore(add(_pPairing, 672), mload(add(vkPoints, 32))) + mstore(add(_pPairing, 704), mload(add(vkPoints, 64))) + mstore(add(_pPairing, 736), mload(add(vkPoints, 96))) + + let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) + + isOk := and(success, mload(_pPairing)) + } + + let pMem := mload(0x40) + mstore(0x40, add(pMem, pLastMem)) + + // Validate that all evaluations ∈ F + + checkField(calldataload(add(_pubSignals, 0))) + + checkField(calldataload(add(_pubSignals, 32))) + + checkField(calldataload(add(_pubSignals, 64))) + + checkField(calldataload(add(_pubSignals, 96))) + + checkField(calldataload(add(_pubSignals, 128))) + + // Validate all evaluations + let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem, _vkPoints) - /// @dev Creates a keccak256 hash of a message compatible with the SNARK scalar modulus. - /// @param message: Message to be hashed. - /// @return Message digest. - function _hash(uint256 message) private pure returns (uint256) { - return uint256(keccak256(abi.encodePacked(message))) >> 8; + mstore(0, isValid) + return(0, 0x20) + } } } diff --git a/packages/contracts/contracts/extensions/SemaphoreVoting.sol b/packages/contracts/contracts/extensions/SemaphoreVoting.sol deleted file mode 100644 index 7564fea43..000000000 --- a/packages/contracts/contracts/extensions/SemaphoreVoting.sol +++ /dev/null @@ -1,96 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity 0.8.4; - -import "../interfaces/ISemaphoreVoting.sol"; -import "../interfaces/ISemaphoreVerifier.sol"; -import "../base/SemaphoreGroups.sol"; - -/// @title Semaphore voting contract. -/// @notice It allows users to vote anonymously in a poll. -/// @dev The following code allows you to create polls, add voters and allow them to vote anonymously. -contract SemaphoreVoting is ISemaphoreVoting, SemaphoreGroups { - ISemaphoreVerifier public verifier; - - /// @dev Gets a poll id and returns the poll data. - mapping(uint256 => Poll) internal polls; - - /// @dev Checks if the poll coordinator is the transaction sender. - /// @param pollId: Id of the poll. - modifier onlyCoordinator(uint256 pollId) { - if (polls[pollId].coordinator != _msgSender()) { - revert Semaphore__CallerIsNotThePollCoordinator(); - } - - _; - } - - /// @dev Initializes the Semaphore verifier used to verify the user's ZK proofs. - /// @param _verifier: Semaphore verifier address. - constructor(ISemaphoreVerifier _verifier) { - verifier = _verifier; - } - - /// @dev See {ISemaphoreVoting-createPoll}. - function createPoll(uint256 pollId, address coordinator, uint256 merkleTreeDepth) public override { - if (merkleTreeDepth < 16 || merkleTreeDepth > 32) { - revert Semaphore__MerkleTreeDepthIsNotSupported(); - } - - _createGroup(pollId, merkleTreeDepth); - - polls[pollId].coordinator = coordinator; - - emit PollCreated(pollId, coordinator); - } - - /// @dev See {ISemaphoreVoting-addVoter}. - function addVoter(uint256 pollId, uint256 identityCommitment) public override onlyCoordinator(pollId) { - if (polls[pollId].state != PollState.Created) { - revert Semaphore__PollHasAlreadyBeenStarted(); - } - - _addMember(pollId, identityCommitment); - } - - /// @dev See {ISemaphoreVoting-addVoter}. - function startPoll(uint256 pollId, uint256 encryptionKey) public override onlyCoordinator(pollId) { - if (polls[pollId].state != PollState.Created) { - revert Semaphore__PollHasAlreadyBeenStarted(); - } - - polls[pollId].state = PollState.Ongoing; - - emit PollStarted(pollId, _msgSender(), encryptionKey); - } - - /// @dev See {ISemaphoreVoting-castVote}. - function castVote(uint256 vote, uint256 nullifierHash, uint256 pollId, uint256[8] calldata proof) public override { - if (polls[pollId].state != PollState.Ongoing) { - revert Semaphore__PollIsNotOngoing(); - } - - if (polls[pollId].nullifierHashes[nullifierHash]) { - revert Semaphore__YouAreUsingTheSameNillifierTwice(); - } - - uint256 merkleTreeDepth = getMerkleTreeDepth(pollId); - uint256 merkleTreeRoot = getMerkleTreeRoot(pollId); - - verifier.verifyProof(merkleTreeRoot, nullifierHash, vote, pollId, proof, merkleTreeDepth); - - polls[pollId].nullifierHashes[nullifierHash] = true; - - emit VoteAdded(pollId, vote); - } - - /// @dev See {ISemaphoreVoting-publishDecryptionKey}. - function endPoll(uint256 pollId, uint256 decryptionKey) public override onlyCoordinator(pollId) { - if (polls[pollId].state != PollState.Ongoing) { - revert Semaphore__PollIsNotOngoing(); - } - - polls[pollId].state = PollState.Ended; - - emit PollEnded(pollId, _msgSender(), decryptionKey); - } -} diff --git a/packages/contracts/contracts/extensions/SemaphoreWhistleblowing.sol b/packages/contracts/contracts/extensions/SemaphoreWhistleblowing.sol deleted file mode 100644 index 5a427226e..000000000 --- a/packages/contracts/contracts/extensions/SemaphoreWhistleblowing.sol +++ /dev/null @@ -1,77 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity 0.8.4; - -import "../interfaces/ISemaphoreWhistleblowing.sol"; -import "../interfaces/ISemaphoreVerifier.sol"; -import "../base/SemaphoreGroups.sol"; - -/// @title Semaphore whistleblowing contract. -/// @notice It allows users to leak information anonymously . -/// @dev The following code allows you to create entities for whistleblowers (e.g. non-profit -/// organization, newspaper) and allow them to leak anonymously. -/// Leaks can be IPFS hashes, permanent links or other kinds of references. -contract SemaphoreWhistleblowing is ISemaphoreWhistleblowing, SemaphoreGroups { - ISemaphoreVerifier public verifier; - - /// @dev Gets an entity id and return its editor address. - mapping(uint256 => address) private entities; - - /// @dev Checks if the editor is the transaction sender. - /// @param entityId: Id of the entity. - modifier onlyEditor(uint256 entityId) { - if (entities[entityId] != _msgSender()) { - revert Semaphore__CallerIsNotTheEditor(); - } - - _; - } - - /// @dev Initializes the Semaphore verifier used to verify the user's ZK proofs. - /// @param _verifier: Semaphore verifier address. - constructor(ISemaphoreVerifier _verifier) { - verifier = _verifier; - } - - /// @dev See {ISemaphoreWhistleblowing-createEntity}. - function createEntity(uint256 entityId, address editor, uint256 merkleTreeDepth) public override { - if (merkleTreeDepth < 16 || merkleTreeDepth > 32) { - revert Semaphore__MerkleTreeDepthIsNotSupported(); - } - - _createGroup(entityId, merkleTreeDepth); - - entities[entityId] = editor; - - emit EntityCreated(entityId, editor); - } - - /// @dev See {ISemaphoreWhistleblowing-addWhistleblower}. - function addWhistleblower(uint256 entityId, uint256 identityCommitment) public override onlyEditor(entityId) { - _addMember(entityId, identityCommitment); - } - - /// @dev See {ISemaphoreWhistleblowing-removeWhistleblower}. - function removeWhistleblower( - uint256 entityId, - uint256 identityCommitment, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) public override onlyEditor(entityId) { - _removeMember(entityId, identityCommitment, proofSiblings, proofPathIndices); - } - - /// @dev See {ISemaphoreWhistleblowing-publishLeak}. - function publishLeak( - uint256 leak, - uint256 nullifierHash, - uint256 entityId, - uint256[8] calldata proof - ) public override { - uint256 merkleTreeDepth = getMerkleTreeDepth(entityId); - uint256 merkleTreeRoot = getMerkleTreeRoot(entityId); - - verifier.verifyProof(merkleTreeRoot, nullifierHash, leak, entityId, proof, merkleTreeDepth); - - emit LeakPublished(entityId, leak); - } -} diff --git a/packages/contracts/contracts/interfaces/ISemaphore.sol b/packages/contracts/contracts/interfaces/ISemaphore.sol index 847f203e4..09dd79491 100644 --- a/packages/contracts/contracts/interfaces/ISemaphore.sol +++ b/packages/contracts/contracts/interfaces/ISemaphore.sol @@ -1,29 +1,33 @@ //SPDX-License-Identifier: MIT -pragma solidity 0.8.4; +pragma solidity 0.8.23; /// @title Semaphore contract interface. interface ISemaphore { - error Semaphore__CallerIsNotTheGroupAdmin(); + error Semaphore__GroupHasNoMembers(); error Semaphore__MerkleTreeDepthIsNotSupported(); error Semaphore__MerkleTreeRootIsExpired(); error Semaphore__MerkleTreeRootIsNotPartOfTheGroup(); - error Semaphore__YouAreUsingTheSameNillifierTwice(); + error Semaphore__YouAreUsingTheSameNullifierTwice(); + error Semaphore__InvalidProof(); - /// It defines all the group parameters, in addition to those in the Merkle tree. + /// It defines all the group parameters used by Semaphore.sol. struct Group { - address admin; uint256 merkleTreeDuration; mapping(uint256 => uint256) merkleRootCreationDates; - mapping(uint256 => bool) nullifierHashes; + mapping(uint256 => bool) nullifiers; } - /// @dev Emitted when an admin is assigned to a group. - /// @param groupId: Id of the group. - /// @param oldAdmin: Old admin of the group. - /// @param newAdmin: New admin of the group. - event GroupAdminUpdated(uint256 indexed groupId, address indexed oldAdmin, address indexed newAdmin); + /// It defines all the Semaphore proof parameters used by Semaphore.sol. + struct SemaphoreProof { + uint256 merkleTreeDepth; + uint256 merkleTreeRoot; + uint256 nullifier; + uint256 message; + uint256 scope; + uint256[8] points; + } - /// @dev Emitted when the Merkle tree duration of a group is updated. + /// @dev Event emitted when the Merkle tree duration of a group is updated. /// @param groupId: Id of the group. /// @param oldMerkleTreeDuration: Old Merkle tree duration of the group. /// @param newMerkleTreeDuration: New Merkle tree duration of the group. @@ -33,53 +37,34 @@ interface ISemaphore { uint256 newMerkleTreeDuration ); - /// @dev Emitted when a Semaphore proof is verified. + /// @dev Event emitted when a Semaphore proof is validated. /// @param groupId: Id of the group. + /// @param merkleTreeDepth: Depth of the Merkle tree. /// @param merkleTreeRoot: Root of the Merkle tree. - /// @param nullifierHash: Nullifier hash. - /// @param externalNullifier: External nullifier. - /// @param signal: Semaphore signal. - event ProofVerified( + /// @param nullifier: Nullifier. + /// @param message: Semaphore message. + /// @param scope: Scope. + /// @param points: Zero-knowledge points. + event ProofValidated( uint256 indexed groupId, + uint256 merkleTreeDepth, uint256 indexed merkleTreeRoot, - uint256 nullifierHash, - uint256 indexed externalNullifier, - uint256 signal + uint256 nullifier, + uint256 message, + uint256 indexed scope, + uint256[8] points ); - /// @dev Saves the nullifier hash to avoid double signaling and emits an event - /// if the zero-knowledge proof is valid. - /// @param groupId: Id of the group. - /// @param merkleTreeRoot: Root of the Merkle tree. - /// @param signal: Semaphore signal. - /// @param nullifierHash: Nullifier hash. - /// @param externalNullifier: External nullifier. - /// @param proof: Zero-knowledge proof. - function verifyProof( - uint256 groupId, - uint256 merkleTreeRoot, - uint256 signal, - uint256 nullifierHash, - uint256 externalNullifier, - uint256[8] calldata proof - ) external; - - /// @dev Creates a new group. Only the admin will be able to add or remove members. - /// @param groupId: Id of the group. - /// @param depth: Depth of the tree. - /// @param admin: Admin of the group. - function createGroup(uint256 groupId, uint256 depth, address admin) external; + /// @dev See {SemaphoreGroups-_createGroup}. + function createGroup(uint256 groupId, address admin) external; - /// @dev Creates a new group. Only the admin will be able to add or remove members. + /// @dev It creates a group with a custom Merkle tree duration. /// @param groupId: Id of the group. - /// @param depth: Depth of the tree. - /// @param admin: Admin of the group. - /// @param merkleTreeRootDuration: Time before the validity of a root expires. - function createGroup(uint256 groupId, uint256 depth, address admin, uint256 merkleTreeRootDuration) external; + /// @param admin: Admin of the group. It can be an Ethereum account or a smart contract. + /// @param merkleTreeDuration: Merkle tree duration. + function createGroup(uint256 groupId, address admin, uint256 merkleTreeDuration) external; - /// @dev Updates the group admin. - /// @param groupId: Id of the group. - /// @param newAdmin: New admin of the group. + /// @dev See {SemaphoreGroups-_updateGroupAdmin}. function updateGroupAdmin(uint256 groupId, address newAdmin) external; /// @dev Updates the group Merkle tree duration. @@ -87,41 +72,31 @@ interface ISemaphore { /// @param newMerkleTreeDuration: New Merkle tree duration. function updateGroupMerkleTreeDuration(uint256 groupId, uint256 newMerkleTreeDuration) external; - /// @dev Adds a new member to an existing group. - /// @param groupId: Id of the group. - /// @param identityCommitment: New identity commitment. + /// @dev See {SemaphoreGroups-_addMember}. function addMember(uint256 groupId, uint256 identityCommitment) external; - /// @dev Adds new members to an existing group. - /// @param groupId: Id of the group. - /// @param identityCommitments: New identity commitments. + /// @dev See {SemaphoreGroups-_addMembers}. function addMembers(uint256 groupId, uint256[] calldata identityCommitments) external; - /// @dev Updates an identity commitment of an existing group. A proof of membership is - /// needed to check if the node to be updated is part of the tree. - /// @param groupId: Id of the group. - /// @param identityCommitment: Existing identity commitment to be updated. - /// @param newIdentityCommitment: New identity commitment. - /// @param proofSiblings: Array of the sibling nodes of the proof of membership. - /// @param proofPathIndices: Path of the proof of membership. + /// @dev See {SemaphoreGroups-_updateMember}. function updateMember( uint256 groupId, - uint256 identityCommitment, + uint256 oldIdentityCommitment, uint256 newIdentityCommitment, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices + uint256[] calldata merkleProofSiblings ) external; - /// @dev Removes a member from an existing group. A proof of membership is - /// needed to check if the node to be removed is part of the tree. + /// @dev See {SemaphoreGroups-_removeMember}. + function removeMember(uint256 groupId, uint256 identityCommitment, uint256[] calldata merkleProofSiblings) external; + + /// @dev Saves the nullifier hash to prevent double signaling and emits an event + /// if the zero-knowledge proof is valid. /// @param groupId: Id of the group. - /// @param identityCommitment: Identity commitment to be removed. - /// @param proofSiblings: Array of the sibling nodes of the proof of membership. - /// @param proofPathIndices: Path of the proof of membership. - function removeMember( - uint256 groupId, - uint256 identityCommitment, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) external; + /// @param proof: Semaphore zero-knowledge proof. + function validateProof(uint256 groupId, SemaphoreProof calldata proof) external; + + /// @dev Verifies a zero-knowledge proof by returning true or false. + /// @param groupId: Id of the group. + /// @param proof: Semaphore zero-knowledge proof. + function verifyProof(uint256 groupId, SemaphoreProof calldata proof) external view returns (bool); } diff --git a/packages/contracts/contracts/interfaces/ISemaphoreGroups.sol b/packages/contracts/contracts/interfaces/ISemaphoreGroups.sol index f7bc697e2..357bb3d1b 100644 --- a/packages/contracts/contracts/interfaces/ISemaphoreGroups.sol +++ b/packages/contracts/contracts/interfaces/ISemaphoreGroups.sol @@ -1,25 +1,42 @@ //SPDX-License-Identifier: MIT -pragma solidity 0.8.4; +pragma solidity 0.8.23; /// @title SemaphoreGroups contract interface. interface ISemaphoreGroups { error Semaphore__GroupDoesNotExist(); error Semaphore__GroupAlreadyExists(); + error Semaphore__CallerIsNotTheGroupAdmin(); - /// @dev Emitted when a new group is created. + /// @dev Event emitted when a new group is created. /// @param groupId: Id of the group. - /// @param merkleTreeDepth: Depth of the tree. - /// @param zeroValue: Zero value of the tree. - event GroupCreated(uint256 indexed groupId, uint256 merkleTreeDepth, uint256 zeroValue); + event GroupCreated(uint256 indexed groupId); - /// @dev Emitted when a new identity commitment is added. + /// @dev Event emitted when an admin is assigned to a group. + /// @param groupId: Id of the group. + /// @param oldAdmin: Old admin of the group. + /// @param newAdmin: New admin of the group. + event GroupAdminUpdated(uint256 indexed groupId, address indexed oldAdmin, address indexed newAdmin); + + /// @dev Event emitted when a new identity commitment is added. /// @param groupId: Group id of the group. - /// @param index: Identity commitment index. + /// @param index: Merkle tree leaf index. /// @param identityCommitment: New identity commitment. /// @param merkleTreeRoot: New root hash of the tree. event MemberAdded(uint256 indexed groupId, uint256 index, uint256 identityCommitment, uint256 merkleTreeRoot); - /// @dev Emitted when an identity commitment is updated. + /// @dev Event emitted when many identity commitments are added at the same time. + /// @param groupId: Group id of the group. + /// @param startIndex: Index of the first element of the new identity commitments in the merkle tree. + /// @param identityCommitments: The new identity commitments. + /// @param merkleTreeRoot: New root hash of the tree. + event MembersAdded( + uint256 indexed groupId, + uint256 startIndex, + uint256[] identityCommitments, + uint256 merkleTreeRoot + ); + + /// @dev Event emitted when an identity commitment is updated. /// @param groupId: Group id of the group. /// @param index: Identity commitment index. /// @param identityCommitment: Existing identity commitment to be updated. @@ -33,13 +50,30 @@ interface ISemaphoreGroups { uint256 merkleTreeRoot ); - /// @dev Emitted when a new identity commitment is removed. + /// @dev Event emitted when a new identity commitment is removed. /// @param groupId: Group id of the group. /// @param index: Identity commitment index. /// @param identityCommitment: Existing identity commitment to be removed. /// @param merkleTreeRoot: New root hash of the tree. event MemberRemoved(uint256 indexed groupId, uint256 index, uint256 identityCommitment, uint256 merkleTreeRoot); + /// @dev Returns the address of the group admin. The group admin can be an Ethereum account or a smart contract. + /// @param groupId: Id of the group. + /// @return Address of the group admin. + function getGroupAdmin(uint256 groupId) external view returns (address); + + /// @dev Returns true if a member exists in a group. + /// @param groupId: Id of the group. + /// @param identityCommitment: Identity commitment. + /// @return True if the member exists, false otherwise. + function hasMember(uint256 groupId, uint256 identityCommitment) external view returns (bool); + + /// @dev Returns the index of a member. + /// @param groupId: Id of the group. + /// @param identityCommitment: Identity commitment. + /// @return Index of member. + function indexOf(uint256 groupId, uint256 identityCommitment) external view returns (uint256); + /// @dev Returns the last root hash of a group. /// @param groupId: Id of the group. /// @return Root hash of the group. @@ -53,5 +87,5 @@ interface ISemaphoreGroups { /// @dev Returns the number of tree leaves of a group. /// @param groupId: Id of the group. /// @return Number of tree leaves. - function getNumberOfMerkleTreeLeaves(uint256 groupId) external view returns (uint256); + function getMerkleTreeSize(uint256 groupId) external view returns (uint256); } diff --git a/packages/contracts/contracts/interfaces/ISemaphoreVerifier.sol b/packages/contracts/contracts/interfaces/ISemaphoreVerifier.sol index 110f73cb4..8e54583fa 100644 --- a/packages/contracts/contracts/interfaces/ISemaphoreVerifier.sol +++ b/packages/contracts/contracts/interfaces/ISemaphoreVerifier.sol @@ -1,37 +1,20 @@ //SPDX-License-Identifier: MIT -pragma solidity 0.8.4; - -import "../base/Pairing.sol"; +pragma solidity 0.8.23; /// @title SemaphoreVerifier contract interface. interface ISemaphoreVerifier { - struct VerificationKey { - Pairing.G1Point alfa1; - Pairing.G2Point beta2; - Pairing.G2Point gamma2; - Pairing.G2Point delta2; - Pairing.G1Point[] IC; - } - - struct Proof { - Pairing.G1Point A; - Pairing.G2Point B; - Pairing.G1Point C; - } - - /// @dev Verifies whether a Semaphore proof is valid. - /// @param merkleTreeRoot: Root of the Merkle tree. - /// @param nullifierHash: Nullifier hash. - /// @param signal: Semaphore signal. - /// @param externalNullifier: External nullifier. - /// @param proof: Zero-knowledge proof. - /// @param merkleTreeDepth: Depth of the tree. + /// @dev Returns true if the proof was successfully verified. + /// @param _pA: Point A. + /// @param _pB: Point B. + /// @param _pC: Point C. + /// @param _pubSignals: Public signals. + /// @param merkleTreeDepth: Merkle tree depth. + /// @return True if the proof was successfully verified, false otherwise. function verifyProof( - uint256 merkleTreeRoot, - uint256 nullifierHash, - uint256 signal, - uint256 externalNullifier, - uint256[8] calldata proof, - uint256 merkleTreeDepth - ) external view; + uint[2] calldata _pA, + uint[2][2] calldata _pB, + uint[2] calldata _pC, + uint[4] calldata _pubSignals, + uint merkleTreeDepth + ) external view returns (bool); } diff --git a/packages/contracts/contracts/interfaces/ISemaphoreVoting.sol b/packages/contracts/contracts/interfaces/ISemaphoreVoting.sol deleted file mode 100644 index f2d15d414..000000000 --- a/packages/contracts/contracts/interfaces/ISemaphoreVoting.sol +++ /dev/null @@ -1,78 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity 0.8.4; - -/// @title SemaphoreVoting contract interface. -interface ISemaphoreVoting { - error Semaphore__CallerIsNotThePollCoordinator(); - error Semaphore__MerkleTreeDepthIsNotSupported(); - error Semaphore__PollHasAlreadyBeenStarted(); - error Semaphore__PollIsNotOngoing(); - error Semaphore__YouAreUsingTheSameNillifierTwice(); - - enum PollState { - Created, - Ongoing, - Ended - } - - struct Verifier { - address contractAddress; - uint256 merkleTreeDepth; - } - - struct Poll { - address coordinator; - PollState state; - mapping(uint256 => bool) nullifierHashes; - } - - /// @dev Emitted when a new poll is created. - /// @param pollId: Id of the poll. - /// @param coordinator: Coordinator of the poll. - event PollCreated(uint256 pollId, address indexed coordinator); - - /// @dev Emitted when a poll is started. - /// @param pollId: Id of the poll. - /// @param coordinator: Coordinator of the poll. - /// @param encryptionKey: Key to encrypt the poll votes. - event PollStarted(uint256 pollId, address indexed coordinator, uint256 encryptionKey); - - /// @dev Emitted when a user votes on a poll. - /// @param pollId: Id of the poll. - /// @param vote: User encrypted vote. - event VoteAdded(uint256 indexed pollId, uint256 vote); - - /// @dev Emitted when a poll is ended. - /// @param pollId: Id of the poll. - /// @param coordinator: Coordinator of the poll. - /// @param decryptionKey: Key to decrypt the poll votes. - event PollEnded(uint256 pollId, address indexed coordinator, uint256 decryptionKey); - - /// @dev Creates a poll and the associated Merkle tree/group. - /// @param pollId: Id of the poll. - /// @param coordinator: Coordinator of the poll. - /// @param merkleTreeDepth: Depth of the tree. - function createPoll(uint256 pollId, address coordinator, uint256 merkleTreeDepth) external; - - /// @dev Adds a voter to a poll. - /// @param pollId: Id of the poll. - /// @param identityCommitment: Identity commitment of the group member. - function addVoter(uint256 pollId, uint256 identityCommitment) external; - - /// @dev Starts a pull and publishes the key to encrypt the votes. - /// @param pollId: Id of the poll. - /// @param encryptionKey: Key to encrypt poll votes. - function startPoll(uint256 pollId, uint256 encryptionKey) external; - - /// @dev Casts an anonymous vote in a poll. - /// @param vote: Encrypted vote. - /// @param nullifierHash: Nullifier hash. - /// @param pollId: Id of the poll. - /// @param proof: Private zk-proof parameters. - function castVote(uint256 vote, uint256 nullifierHash, uint256 pollId, uint256[8] calldata proof) external; - - /// @dev Ends a pull and publishes the key to decrypt the votes. - /// @param pollId: Id of the poll. - /// @param decryptionKey: Key to decrypt poll votes. - function endPoll(uint256 pollId, uint256 decryptionKey) external; -} diff --git a/packages/contracts/contracts/interfaces/ISemaphoreWhistleblowing.sol b/packages/contracts/contracts/interfaces/ISemaphoreWhistleblowing.sol deleted file mode 100644 index 8ff7fcf12..000000000 --- a/packages/contracts/contracts/interfaces/ISemaphoreWhistleblowing.sol +++ /dev/null @@ -1,53 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity 0.8.4; - -/// @title SemaphoreWhistleblowing contract interface. -interface ISemaphoreWhistleblowing { - error Semaphore__CallerIsNotTheEditor(); - error Semaphore__MerkleTreeDepthIsNotSupported(); - - struct Verifier { - address contractAddress; - uint256 merkleTreeDepth; - } - - /// @dev Emitted when a new entity is created. - /// @param entityId: Id of the entity. - /// @param editor: Editor of the entity. - event EntityCreated(uint256 entityId, address indexed editor); - - /// @dev Emitted when a whistleblower publish a new leak. - /// @param entityId: Id of the entity. - /// @param leak: News leak. - event LeakPublished(uint256 indexed entityId, uint256 leak); - - /// @dev Creates an entity and the associated Merkle tree/group. - /// @param entityId: Id of the entity. - /// @param editor: Editor of the entity. - /// @param merkleTreeDepth: Depth of the tree. - function createEntity(uint256 entityId, address editor, uint256 merkleTreeDepth) external; - - /// @dev Adds a whistleblower to an entity. - /// @param entityId: Id of the entity. - /// @param identityCommitment: Identity commitment of the group member. - function addWhistleblower(uint256 entityId, uint256 identityCommitment) external; - - /// @dev Removes a whistleblower from an entity. - /// @param entityId: Id of the entity. - /// @param identityCommitment: Identity commitment of the group member. - /// @param proofSiblings: Array of the sibling nodes of the proof of membership. - /// @param proofPathIndices: Path of the proof of membership. - function removeWhistleblower( - uint256 entityId, - uint256 identityCommitment, - uint256[] calldata proofSiblings, - uint8[] calldata proofPathIndices - ) external; - - /// @dev Allows whistleblowers to publish leaks anonymously. - /// @param leak: News leak. - /// @param nullifierHash: Nullifier hash. - /// @param entityId: Id of the entity. - /// @param proof: Private zk-proof parameters. - function publishLeak(uint256 leak, uint256 nullifierHash, uint256 entityId, uint256[8] calldata proof) external; -} diff --git a/packages/contracts/contracts/package.json b/packages/contracts/contracts/package.json index a08ac8a27..933694bcf 100644 --- a/packages/contracts/contracts/package.json +++ b/packages/contracts/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@semaphore-protocol/contracts", - "version": "3.15.2", + "version": "4.0.0-alpha.8", "description": "Semaphore contracts to manage groups and broadcast anonymous signals.", "license": "MIT", "files": [ @@ -30,7 +30,6 @@ "access": "public" }, "dependencies": { - "@openzeppelin/contracts": "4.7.3", - "@zk-kit/incremental-merkle-tree.sol": "1.3.3" + "@zk-kit/imt.sol": "2.0.0-beta.5" } } diff --git a/packages/contracts/deployed-contracts.json b/packages/contracts/deployed-contracts.json new file mode 100644 index 000000000..6aaf0a7db --- /dev/null +++ b/packages/contracts/deployed-contracts.json @@ -0,0 +1,19 @@ +[ + { + "network": "sepolia", + "contracts": [ + { + "name": "SemaphoreVerifier", + "address": "0x1E979ECf8C23d45577904974642592f80C464522" + }, + { + "name": "PoseidonT3", + "address": "0x43AE9518d9FE43898cD705a06C22A73B015bCf12" + }, + { + "name": "Semaphore", + "address": "0x5B8e7cC7bAC61A4b952d472b67056B2f260ba6dc" + } + ] + } +] diff --git a/packages/contracts/deployed-contracts/arbitrum-goerli.json b/packages/contracts/deployed-contracts/arbitrum-goerli.json deleted file mode 100644 index c7d7bb9cd..000000000 --- a/packages/contracts/deployed-contracts/arbitrum-goerli.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "Pairing": "0xEe44c1e83A768E80A3588B409f1A010f9D1dd7e8", - "SemaphoreVerifier": "0xb908Bcb798e5353fB90155C692BddE3b4937217C", - "Poseidon": "0xe136aBACf78E05988154ed85F4Ea911105302595", - "IncrementalBinaryTree": "0x4621EE309EAc747425F0FEd51931dDC241A27F49", - "Semaphore": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131" -} diff --git a/packages/contracts/deployed-contracts/arbitrum.json b/packages/contracts/deployed-contracts/arbitrum.json deleted file mode 100644 index d06f939b1..000000000 --- a/packages/contracts/deployed-contracts/arbitrum.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "Pairing": "0xE3a4C2FE9f025405cA6F60f6E960B4558604A74C", - "SemaphoreVerifier": "0xCAbeED6cB96a287000aBd834b0B79c05e6Ea4d07", - "Poseidon": "0xe0c8d1e53D9Bfc9071F6564755FCFf6cC0dB61d0", - "IncrementalBinaryTree": "0xcDF8efE6334c68aF283C83f2F14648da51fcfFb0", - "Semaphore": "0xc60E0Ee1a2770d5F619858C641f14FC4a6401520" -} diff --git a/packages/contracts/deployed-contracts/goerli.json b/packages/contracts/deployed-contracts/goerli.json deleted file mode 100644 index c7d7bb9cd..000000000 --- a/packages/contracts/deployed-contracts/goerli.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "Pairing": "0xEe44c1e83A768E80A3588B409f1A010f9D1dd7e8", - "SemaphoreVerifier": "0xb908Bcb798e5353fB90155C692BddE3b4937217C", - "Poseidon": "0xe136aBACf78E05988154ed85F4Ea911105302595", - "IncrementalBinaryTree": "0x4621EE309EAc747425F0FEd51931dDC241A27F49", - "Semaphore": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131" -} diff --git a/packages/contracts/deployed-contracts/mumbai.json b/packages/contracts/deployed-contracts/mumbai.json deleted file mode 100644 index c7d7bb9cd..000000000 --- a/packages/contracts/deployed-contracts/mumbai.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "Pairing": "0xEe44c1e83A768E80A3588B409f1A010f9D1dd7e8", - "SemaphoreVerifier": "0xb908Bcb798e5353fB90155C692BddE3b4937217C", - "Poseidon": "0xe136aBACf78E05988154ed85F4Ea911105302595", - "IncrementalBinaryTree": "0x4621EE309EAc747425F0FEd51931dDC241A27F49", - "Semaphore": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131" -} diff --git a/packages/contracts/deployed-contracts/optimism-goerli.json b/packages/contracts/deployed-contracts/optimism-goerli.json deleted file mode 100644 index c7d7bb9cd..000000000 --- a/packages/contracts/deployed-contracts/optimism-goerli.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "Pairing": "0xEe44c1e83A768E80A3588B409f1A010f9D1dd7e8", - "SemaphoreVerifier": "0xb908Bcb798e5353fB90155C692BddE3b4937217C", - "Poseidon": "0xe136aBACf78E05988154ed85F4Ea911105302595", - "IncrementalBinaryTree": "0x4621EE309EAc747425F0FEd51931dDC241A27F49", - "Semaphore": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131" -} diff --git a/packages/contracts/deployed-contracts/sepolia.json b/packages/contracts/deployed-contracts/sepolia.json deleted file mode 100644 index c7d7bb9cd..000000000 --- a/packages/contracts/deployed-contracts/sepolia.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "Pairing": "0xEe44c1e83A768E80A3588B409f1A010f9D1dd7e8", - "SemaphoreVerifier": "0xb908Bcb798e5353fB90155C692BddE3b4937217C", - "Poseidon": "0xe136aBACf78E05988154ed85F4Ea911105302595", - "IncrementalBinaryTree": "0x4621EE309EAc747425F0FEd51931dDC241A27F49", - "Semaphore": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131" -} diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index 319582c03..0500f2401 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -1,6 +1,7 @@ import "@nomicfoundation/hardhat-chai-matchers" -import "@nomiclabs/hardhat-ethers" -import "@nomiclabs/hardhat-etherscan" +import "@nomicfoundation/hardhat-ethers" +import "@nomicfoundation/hardhat-verify" +import "@openzeppelin/hardhat-upgrades" import "@typechain/hardhat" import { config as dotenvConfig } from "dotenv" import "hardhat-gas-reporter" @@ -8,11 +9,8 @@ import { HardhatUserConfig } from "hardhat/config" import { NetworksUserConfig } from "hardhat/types" import { resolve } from "path" import "solidity-coverage" -import { config } from "./package.json" import "./tasks/accounts" -import "./tasks/deploy-semaphore" -import "./tasks/deploy-semaphore-voting" -import "./tasks/deploy-semaphore-whistleblowing" +import "./tasks/deploy" dotenvConfig({ path: resolve(__dirname, "../../.env") }) @@ -25,11 +23,6 @@ function getNetworks(): NetworksUserConfig { const accounts = [`0x${process.env.BACKEND_PRIVATE_KEY}`] return { - goerli: { - url: `https://goerli.infura.io/v3/${infuraApiKey}`, - chainId: 5, - accounts - }, sepolia: { url: `https://sepolia.infura.io/v3/${infuraApiKey}`, chainId: 11155111, @@ -40,14 +33,14 @@ function getNetworks(): NetworksUserConfig { chainId: 80001, accounts }, - "optimism-goerli": { - url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`, - chainId: 420, + "optimism-sepolia": { + url: `https://optimism-sepolia.infura.io/v3/${infuraApiKey}`, + chainId: 11155420, accounts }, - "arbitrum-goerli": { - url: "https://goerli-rollup.arbitrum.io/rpc", - chainId: 421613, + "arbitrum-sepolia": { + url: "https://sepolia-rollup.arbitrum.io/rpc", + chainId: 421614, accounts }, arbitrum: { @@ -59,13 +52,7 @@ function getNetworks(): NetworksUserConfig { } const hardhatConfig: HardhatUserConfig = { - solidity: config.solidity, - paths: { - sources: config.paths.contracts, - tests: config.paths.tests, - cache: config.paths.cache, - artifacts: config.paths.build.contracts - }, + solidity: "0.8.23", networks: { hardhat: { chainId: 1337, @@ -78,12 +65,18 @@ const hardhatConfig: HardhatUserConfig = { enabled: process.env.REPORT_GAS === "true", coinmarketcap: process.env.COINMARKETCAP_API_KEY }, + defender: { + apiKey: process.env.DEFENDER_KEY as string, + apiSecret: process.env.DEFENDER_SECRET as string + }, typechain: { - outDir: config.paths.build.typechain, - target: "ethers-v5" + target: "ethers-v6" }, etherscan: { apiKey: process.env.ETHERSCAN_API_KEY + }, + sourcify: { + enabled: true } } diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 9eeb023cd..5f6bb5d92 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -4,10 +4,9 @@ "scripts": { "start": "hardhat node", "compile": "hardhat compile", - "deploy:semaphore": "hardhat deploy:semaphore", - "deploy:semaphore-voting": "hardhat deploy:semaphore-voting", - "deploy:semaphore-whistleblowing": "hardhat deploy:semaphore-whistleblowing", - "verify:contracts": "hardhat run scripts/verify-contracts.ts", + "deploy": "hardhat deploy", + "verify": "hardhat run scripts/verify-contracts.ts", + "mock": "hardhat run scripts/create-mock-groups.ts", "test": "hardhat test", "test:report-gas": "REPORT_GAS=true hardhat test", "test:coverage": "hardhat coverage", @@ -15,53 +14,31 @@ "lint": "solhint 'contracts/**/*.sol'" }, "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^1.0.5", - "@nomiclabs/hardhat-ethers": "^2.0.6", - "@nomiclabs/hardhat-etherscan": "^3.1.7", - "@semaphore-protocol/group": "workspace:packages/group", - "@semaphore-protocol/identity": "workspace:packages/identity", - "@semaphore-protocol/proof": "workspace:packages/proof", - "@typechain/ethers-v5": "^10.0.0", - "@typechain/hardhat": "^6.0.0", - "@types/chai": "^4.3.0", - "@types/download": "^8.0.1", - "@types/mocha": "^9.1.0", - "@types/node": "^17.0.12", - "@types/rimraf": "^3.0.2", - "chai": "^4.3.5", - "circomlib": "^2.0.2", - "circomlibjs": "^0.1.7", - "download": "^8.0.0", - "ethers": "^5.6.8", - "hardhat": "^2.9.7", + "@nomicfoundation/hardhat-chai-matchers": "^2.0.3", + "@nomicfoundation/hardhat-ethers": "^3.0.0", + "@nomicfoundation/hardhat-network-helpers": "^1.0.0", + "@nomicfoundation/hardhat-toolbox": "^4.0.0", + "@nomicfoundation/hardhat-verify": "^2.0.0", + "@openzeppelin/hardhat-upgrades": "^3.0.4", + "@semaphore-protocol/core": "workspace:packages/core", + "@typechain/ethers-v6": "^0.5.0", + "@typechain/hardhat": "^9.0.0", + "@types/chai": "^4.2.0", + "@types/mocha": "^10.0.6", + "@types/node": "^20.10.7", + "chai": "^4.2.0", + "ethers": "^6.4.0", + "hardhat": "^2.19.4", "hardhat-gas-reporter": "^1.0.8", - "js-logger": "^1.6.1", - "prettier-plugin-solidity": "^1.0.0-beta.19", - "rimraf": "^3.0.2", + "prettier-plugin-solidity": "^1.3.1", "solhint": "^3.3.6", "solhint-plugin-prettier": "^0.0.5", - "solidity-coverage": "^0.7.21", - "ts-node": "^10.4.0", - "typechain": "^8.0.0" - }, - "config": { - "solidity": { - "version": "0.8.4" - }, - "paths": { - "contracts": "./contracts", - "circuit": "./circuit", - "tests": "./test", - "cache": "./cache", - "snarkjs-templates": "./snarkjs-templates", - "build": { - "contracts": "./build/contracts", - "typechain": "./build/typechain" - } - } + "solidity-coverage": "^0.8.0", + "ts-node": "^10.9.2", + "typechain": "^8.3.0", + "typescript": "^5.3.3" }, "dependencies": { - "@openzeppelin/contracts": "4.7.3", - "@zk-kit/incremental-merkle-tree.sol": "1.3.3" + "@zk-kit/imt.sol": "^2.0.0-beta.5" } } diff --git a/packages/contracts/scripts/create-mock-groups.ts b/packages/contracts/scripts/create-mock-groups.ts new file mode 100644 index 000000000..e2ea3fa56 --- /dev/null +++ b/packages/contracts/scripts/create-mock-groups.ts @@ -0,0 +1,64 @@ +import { Group } from "@semaphore-protocol/group" +import { Identity } from "@semaphore-protocol/identity" +import { generateProof } from "@semaphore-protocol/proof" +import { ethers, hardhatArguments } from "hardhat" +import { getDeployedContractAddress } from "./utils" + +async function main() { + if (!hardhatArguments.network) { + throw Error("Please, define a supported network") + } + + const semaphoreAddress = getDeployedContractAddress(hardhatArguments.network, "Semaphore") + + const semaphoreContract = await ethers.getContractAt("Semaphore", semaphoreAddress) + + const [admin] = await ethers.getSigners() + const adminAddress = await admin.getAddress() + + const identity = new Identity(0) + const members = Array.from({ length: 3 }, (_, i) => new Identity(i)).map(({ commitment }) => commitment) + const group = new Group(members) + + const groupId = 42 + + console.info(`Creating group '${groupId}' with ${members.length} members...`) + + // Create a group and add 3 members. + await semaphoreContract["createGroup(uint256,address)"](groupId, adminAddress) + await semaphoreContract.addMembers(groupId, members) + + console.info(`Removing third member from group '${groupId}'...`) + + // Remove the third member. + { + group.removeMember(2) + const { siblings } = group.generateMerkleProof(2) + + await semaphoreContract.removeMember(groupId, members[2], siblings) + } + + console.info(`Updating second member from group '${groupId}'...`) + + // Update the second member. + { + group.updateMember(1, members[2]) + const { siblings } = group.generateMerkleProof(1) + + await semaphoreContract.updateMember(groupId, members[1], members[2], siblings) + } + + console.info(`Validating a proof generated by the first member of group '${groupId}'...`) + + // Validate a proof. + const proof = await generateProof(identity, group, 42, 9, 10) + + await semaphoreContract.validateProof(groupId, proof) +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) diff --git a/packages/contracts/scripts/utils.ts b/packages/contracts/scripts/utils.ts index 9f506a7cd..71d6d1fe2 100644 --- a/packages/contracts/scripts/utils.ts +++ b/packages/contracts/scripts/utils.ts @@ -1,21 +1,62 @@ import { readFileSync, writeFileSync } from "fs" -type DeployedContracts = { - Pairing: string - SemaphoreVerifier: string - Poseidon: string - IncrementalBinaryTree: string - Semaphore: string +export type NetworkDeployedContracts = { + name: "Semaphore" | "SemaphoreVerifier" | "PoseidonT3" + address: string +}[] + +export type DeployedContracts = { + network: string + contracts: NetworkDeployedContracts +}[] + +const supportedNetworks = ["sepolia", "arbitrum", "mumbai", "optimism-sepolia", "arbitrum-sepolia"] + +export function getDeployedContracts(): DeployedContracts { + return JSON.parse(readFileSync(`./deployed-contracts.json`, "utf8")) +} + +export function getDeployedContractsByNetwork(network: string): NetworkDeployedContracts { + const deployedContracts = getDeployedContracts() + const networkDeployedContracts = deployedContracts.find((n) => n.network === network) + + if (!networkDeployedContracts) { + throw Error(`Network '${network}' is not supported`) + } + + return networkDeployedContracts.contracts } -export function getDeployedContracts(network: string | undefined): DeployedContracts | null { - try { - return JSON.parse(readFileSync(`./deployed-contracts/${network}.json`, "utf8")) - } catch (error) { - return null +export function getDeployedContractAddress(network: string, contractName: string): string { + const contracts = getDeployedContractsByNetwork(network) + const semaphoreAddress = contracts.find((contract) => contract.name === contractName) + + if (!semaphoreAddress) { + throw Error(`Contract with name '${contractName}' does not exist`) } + + return semaphoreAddress.address } -export function saveDeployedContracts(network: string | undefined, deployedContracts: DeployedContracts) { - writeFileSync(`./deployed-contracts/${network}.json`, JSON.stringify(deployedContracts, null, 4)) +export function saveDeployedContracts(contracts: NetworkDeployedContracts, network?: string) { + if (network && supportedNetworks.includes(network)) { + const deployedContracts = getDeployedContracts() as DeployedContracts + + for (let i = 0; i < deployedContracts.length; i += 1) { + if (deployedContracts[i].network === network) { + deployedContracts[i].contracts = contracts + + writeFileSync(`./deployed-contracts.json`, JSON.stringify(deployedContracts, null, 4)) + + return + } + } + + deployedContracts.push({ + network, + contracts + }) + + writeFileSync(`./deployed-contracts.json`, JSON.stringify(deployedContracts, null, 4)) + } } diff --git a/packages/contracts/scripts/verify-contracts.ts b/packages/contracts/scripts/verify-contracts.ts index 200cb8bfe..c04016eb8 100644 --- a/packages/contracts/scripts/verify-contracts.ts +++ b/packages/contracts/scripts/verify-contracts.ts @@ -1,5 +1,5 @@ import { hardhatArguments, run } from "hardhat" -import { getDeployedContracts } from "./utils" +import { getDeployedContractAddress } from "./utils" async function verify(address: string, constructorArguments?: any[]): Promise { try { @@ -13,14 +13,17 @@ async function verify(address: string, constructorArguments?: any[]): Promise= BASE_MODULUS || p.Y >= BASE_MODULUS) revert InvalidProof(); - // We know p.Y > 0 and p.Y < BASE_MODULUS. - return G1Point(p.X, BASE_MODULUS - p.Y); - } - - /// @return r the sum of two points of G1 - function addition(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) { - // By EIP-196 all input is validated to be less than the BASE_MODULUS and form points - // on the curve. - uint256[4] memory input; - input[0] = p1.X; - input[1] = p1.Y; - input[2] = p2.X; - input[3] = p2.Y; - bool success; - // solium-disable-next-line security/no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) - } - if (!success) revert InvalidProof(); - } - - /// @return r the product of a point on G1 and a scalar, i.e. - /// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p. - function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) { - // By EIP-196 the values p.X and p.Y are verified to less than the BASE_MODULUS and - // form a valid point on the curve. But the scalar is not verified, so we do that explicitelly. - if (s >= SCALAR_MODULUS) revert InvalidProof(); - uint256[3] memory input; - input[0] = p.X; - input[1] = p.Y; - input[2] = s; - bool success; - // solium-disable-next-line security/no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) - } - if (!success) revert InvalidProof(); - } - - /// Asserts the pairing check - /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 - /// For example pairing([P1(), P1().negate()], [P2(), P2()]) should succeed - function pairingCheck(G1Point[] memory p1, G2Point[] memory p2) internal view { - // By EIP-197 all input is verified to be less than the BASE_MODULUS and form elements in their - // respective groups of the right order. - if (p1.length != p2.length) revert InvalidProof(); - uint256 elements = p1.length; - uint256 inputSize = elements * 6; - uint256[] memory input = new uint256[](inputSize); - for (uint256 i = 0; i < elements; i++) { - input[i * 6 + 0] = p1[i].X; - input[i * 6 + 1] = p1[i].Y; - input[i * 6 + 2] = p2[i].X[0]; - input[i * 6 + 3] = p2[i].X[1]; - input[i * 6 + 4] = p2[i].Y[0]; - input[i * 6 + 5] = p2[i].Y[1]; - } - uint256[1] memory out; - bool success; - // solium-disable-next-line security/no-inline-assembly - assembly { - success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) - } - if (!success || out[0] != 1) revert InvalidProof(); - } -} - -contract Verifier { - using Pairing for *; - - struct VerifyingKey { - Pairing.G1Point alfa1; - Pairing.G2Point beta2; - Pairing.G2Point gamma2; - Pairing.G2Point delta2; - Pairing.G1Point[] IC; - } - - struct Proof { - Pairing.G1Point A; - Pairing.G2Point B; - Pairing.G1Point C; - } - - function verifyingKey() internal pure returns (VerifyingKey memory vk) { - vk.alfa1 = Pairing.G1Point( - <%=vk_alpha_1[0]%>, - <%=vk_alpha_1[1]%> - ); - - vk.beta2 = Pairing.G2Point( - [<%=vk_beta_2[0][1]%>, <%=vk_beta_2[0][0]%>], - [<%=vk_beta_2[1][1]%>, <%=vk_beta_2[1][0]%>] - ); - - vk.gamma2 = Pairing.G2Point( - [<%=vk_gamma_2[0][1]%>, <%=vk_gamma_2[0][0]%>], - [<%=vk_gamma_2[1][1]%>, <%=vk_gamma_2[1][0]%>] - ); - - vk.delta2 = Pairing.G2Point( - [<%=vk_delta_2[0][1]%>, <%=vk_delta_2[0][0]%>], - [<%=vk_delta_2[1][1]%>, <%=vk_delta_2[1][0]%>] - ); - - vk.IC = new Pairing.G1Point[](<%=IC.length%>); - - <% for (let i=0; i - vk.IC[<%=i%>] = Pairing.G1Point( - <%=IC[i][0]%>, - <%=IC[i][1]%> - ); - <% } %> - } - - /// @dev Verifies a Semaphore proof. Reverts with InvalidProof if the proof is invalid. - function verifyProof( - uint[2] memory a, - uint[2][2] memory b, - uint[2] memory c, - uint[<%=IC.length-1%>] memory input - ) public view { - // If the values are not in the correct range, the Pairing contract will revert. - Proof memory proof; - proof.A = Pairing.G1Point(a[0], a[1]); - proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]); - proof.C = Pairing.G1Point(c[0], c[1]); - - VerifyingKey memory vk = verifyingKey(); - - // Compute the linear combination vk_x of inputs times IC - if (input.length + 1 != vk.IC.length) revert Pairing.InvalidProof(); - Pairing.G1Point memory vk_x = vk.IC[0]; - vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[1], input[0])); - vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[2], input[1])); - vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[3], input[2])); - vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[4], input[3])); - - // Check pairing - Pairing.G1Point[] memory p1 = new Pairing.G1Point[](4); - Pairing.G2Point[] memory p2 = new Pairing.G2Point[](4); - p1[0] = Pairing.negate(proof.A); - p2[0] = proof.B; - p1[1] = vk.alfa1; - p2[1] = vk.beta2; - p1[2] = vk_x; - p2[2] = vk.gamma2; - p1[3] = proof.C; - p2[3] = vk.delta2; - Pairing.pairingCheck(p1, p2); - } -} diff --git a/packages/contracts/tasks/accounts.ts b/packages/contracts/tasks/accounts.ts index e00de76b0..6c4ed2803 100644 --- a/packages/contracts/tasks/accounts.ts +++ b/packages/contracts/tasks/accounts.ts @@ -1,10 +1,10 @@ -import { Signer } from "@ethersproject/abstract-signer" +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" import { task, types } from "hardhat/config" task("accounts", "Prints the list of accounts") .addOptionalParam("logs", "Print the logs", true, types.boolean) .setAction(async ({ logs }, { ethers }) => { - const accounts: Signer[] = await ethers.getSigners() + const accounts: HardhatEthersSigner[] = await ethers.getSigners() if (logs) { for (const account of accounts) { diff --git a/packages/contracts/tasks/deploy-semaphore-voting.ts b/packages/contracts/tasks/deploy-semaphore-voting.ts deleted file mode 100644 index 249af9fe3..000000000 --- a/packages/contracts/tasks/deploy-semaphore-voting.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { poseidonContract } from "circomlibjs" -import { task, types } from "hardhat/config" - -task("deploy:semaphore-voting", "Deploy a SemaphoreVoting contract") - .addOptionalParam("logs", "Print the logs", true, types.boolean) - .setAction(async ({ logs }, { ethers }): Promise => { - const PairingFactory = await ethers.getContractFactory("Pairing") - const pairing = await PairingFactory.deploy() - - await pairing.deployed() - - if (logs) { - console.info(`Pairing library has been deployed to: ${pairing.address}`) - } - - const SemaphoreVerifierFactory = await ethers.getContractFactory("SemaphoreVerifier", { - libraries: { - Pairing: pairing.address - } - }) - - const semaphoreVerifier = await SemaphoreVerifierFactory.deploy() - - await semaphoreVerifier.deployed() - - if (logs) { - console.info(`SemaphoreVerifier contract has been deployed to: ${semaphoreVerifier.address}`) - } - - const poseidonABI = poseidonContract.generateABI(2) - const poseidonBytecode = poseidonContract.createCode(2) - - const [signer] = await ethers.getSigners() - - const PoseidonFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer) - const poseidon = await PoseidonFactory.deploy() - - await poseidon.deployed() - - if (logs) { - console.info(`Poseidon library has been deployed to: ${poseidon.address}`) - } - - const IncrementalBinaryTreeFactory = await ethers.getContractFactory("IncrementalBinaryTree", { - libraries: { - PoseidonT3: poseidon.address - } - }) - const incrementalBinaryTree = await IncrementalBinaryTreeFactory.deploy() - - await incrementalBinaryTree.deployed() - - if (logs) { - console.info(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTree.address}`) - } - - const SemaphoreVotingFactory = await ethers.getContractFactory("SemaphoreVoting", { - libraries: { - IncrementalBinaryTree: incrementalBinaryTree.address - } - }) - - const semaphoreVoting = await SemaphoreVotingFactory.deploy(semaphoreVerifier.address) - - await semaphoreVoting.deployed() - - if (logs) { - console.info(`SemaphoreVoting contract has been deployed to: ${semaphoreVoting.address}`) - } - - return { - semaphoreVoting, - pairingAddress: pairing.address, - semaphoreVerifierAddress: semaphoreVerifier.address, - poseidonAddress: poseidon.address, - incrementalBinaryTreeAddress: incrementalBinaryTree.address - } - }) diff --git a/packages/contracts/tasks/deploy-semaphore-whistleblowing.ts b/packages/contracts/tasks/deploy-semaphore-whistleblowing.ts deleted file mode 100644 index f16cc6dc1..000000000 --- a/packages/contracts/tasks/deploy-semaphore-whistleblowing.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { poseidonContract } from "circomlibjs" -import { task, types } from "hardhat/config" - -task("deploy:semaphore-whistleblowing", "Deploy a SemaphoreWhistleblowing contract") - .addOptionalParam("logs", "Print the logs", true, types.boolean) - .setAction(async ({ logs }, { ethers }): Promise => { - const PairingFactory = await ethers.getContractFactory("Pairing") - const pairing = await PairingFactory.deploy() - - await pairing.deployed() - - if (logs) { - console.info(`Pairing library has been deployed to: ${pairing.address}`) - } - - const SemaphoreVerifierFactory = await ethers.getContractFactory("SemaphoreVerifier", { - libraries: { - Pairing: pairing.address - } - }) - - const semaphoreVerifier = await SemaphoreVerifierFactory.deploy() - - await semaphoreVerifier.deployed() - - if (logs) { - console.info(`SemaphoreVerifier contract has been deployed to: ${semaphoreVerifier.address}`) - } - - const poseidonABI = poseidonContract.generateABI(2) - const poseidonBytecode = poseidonContract.createCode(2) - - const [signer] = await ethers.getSigners() - - const PoseidonFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer) - const poseidon = await PoseidonFactory.deploy() - - await poseidon.deployed() - - if (logs) { - console.info(`Poseidon library has been deployed to: ${poseidon.address}`) - } - - const IncrementalBinaryTreeFactory = await ethers.getContractFactory("IncrementalBinaryTree", { - libraries: { - PoseidonT3: poseidon.address - } - }) - const incrementalBinaryTree = await IncrementalBinaryTreeFactory.deploy() - - await incrementalBinaryTree.deployed() - - if (logs) { - console.info(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTree.address}`) - } - - const SemaphoreWhistleblowingFactory = await ethers.getContractFactory("SemaphoreWhistleblowing", { - libraries: { - IncrementalBinaryTree: incrementalBinaryTree.address - } - }) - - const semaphoreWhistleblowing = await SemaphoreWhistleblowingFactory.deploy(semaphoreVerifier.address) - - await semaphoreWhistleblowing.deployed() - - if (logs) { - console.info(`SemaphoreWhistleblowing contract has been deployed to: ${semaphoreWhistleblowing.address}`) - } - - return { - semaphoreWhistleblowing, - pairingAddress: pairing.address, - semaphoreVerifierAddress: semaphoreVerifier.address, - poseidonAddress: poseidon.address, - incrementalBinaryTreeAddress: incrementalBinaryTree.address - } - }) diff --git a/packages/contracts/tasks/deploy-semaphore.ts b/packages/contracts/tasks/deploy-semaphore.ts deleted file mode 100644 index bdcb0ee59..000000000 --- a/packages/contracts/tasks/deploy-semaphore.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { poseidonContract } from "circomlibjs" -import { task, types } from "hardhat/config" -import { saveDeployedContracts } from "../scripts/utils" - -task("deploy:semaphore", "Deploy a Semaphore contract") - .addOptionalParam("pairing", "Pairing library address", undefined, types.string) - .addOptionalParam("semaphoreVerifier", "SemaphoreVerifier contract address", undefined, types.string) - .addOptionalParam("poseidon", "Poseidon library address", undefined, types.string) - .addOptionalParam( - "incrementalBinaryTree", - "IncrementalBinaryTree library address", - undefined, - types.string - ) - .addOptionalParam("logs", "Print the logs", true, types.boolean) - .setAction( - async ( - { - logs, - pairing: pairingAddress, - semaphoreVerifier: semaphoreVerifierAddress, - poseidon: poseidonAddress, - incrementalBinaryTree: incrementalBinaryTreeAddress - }, - { ethers, hardhatArguments } - ): Promise => { - if (!semaphoreVerifierAddress) { - if (!pairingAddress) { - const PairingFactory = await ethers.getContractFactory("Pairing") - const pairing = await PairingFactory.deploy() - - await pairing.deployed() - - if (logs) { - console.info(`Pairing library has been deployed to: ${pairing.address}`) - } - - pairingAddress = pairing.address - } - - const SemaphoreVerifierFactory = await ethers.getContractFactory("SemaphoreVerifier", { - libraries: { - Pairing: pairingAddress - } - }) - - const semaphoreVerifier = await SemaphoreVerifierFactory.deploy() - - await semaphoreVerifier.deployed() - - if (logs) { - console.info(`SemaphoreVerifier contract has been deployed to: ${semaphoreVerifier.address}`) - } - - semaphoreVerifierAddress = semaphoreVerifier.address - } - - if (!incrementalBinaryTreeAddress) { - if (!poseidonAddress) { - const poseidonABI = poseidonContract.generateABI(2) - const poseidonBytecode = poseidonContract.createCode(2) - - const [signer] = await ethers.getSigners() - - const PoseidonFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer) - const poseidon = await PoseidonFactory.deploy() - - await poseidon.deployed() - - if (logs) { - console.info(`Poseidon library has been deployed to: ${poseidon.address}`) - } - - poseidonAddress = poseidon.address - } - - const IncrementalBinaryTreeFactory = await ethers.getContractFactory("IncrementalBinaryTree", { - libraries: { - PoseidonT3: poseidonAddress - } - }) - const incrementalBinaryTree = await IncrementalBinaryTreeFactory.deploy() - - await incrementalBinaryTree.deployed() - - if (logs) { - console.info(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTree.address}`) - } - - incrementalBinaryTreeAddress = incrementalBinaryTree.address - } - - const SemaphoreFactory = await ethers.getContractFactory("Semaphore", { - libraries: { - IncrementalBinaryTree: incrementalBinaryTreeAddress - } - }) - - const semaphore = await SemaphoreFactory.deploy(semaphoreVerifierAddress) - - await semaphore.deployed() - - if (logs) { - console.info(`Semaphore contract has been deployed to: ${semaphore.address}`) - } - - saveDeployedContracts(hardhatArguments.network, { - Pairing: pairingAddress, - SemaphoreVerifier: semaphoreVerifierAddress, - Poseidon: poseidonAddress, - IncrementalBinaryTree: incrementalBinaryTreeAddress, - Semaphore: semaphore.address - }) - - return { - semaphore, - pairingAddress, - semaphoreVerifierAddress, - poseidonAddress, - incrementalBinaryTreeAddress - } - } - ) diff --git a/packages/contracts/tasks/deploy.ts b/packages/contracts/tasks/deploy.ts new file mode 100644 index 000000000..af8224305 --- /dev/null +++ b/packages/contracts/tasks/deploy.ts @@ -0,0 +1,78 @@ +import { task, types } from "hardhat/config" +import { saveDeployedContracts } from "../scripts/utils" +import { deployContract } from "./utils" + +task("deploy", "Deploy a Semaphore contract") + .addOptionalParam("verifier", "Verifier contract address", undefined, types.string) + .addOptionalParam("poseidon", "Poseidon library address", undefined, types.string) + .addOptionalParam("logs", "Print the logs", true, types.boolean) + .setAction( + async ( + { logs, verifier: semaphoreVerifierAddress, poseidon: poseidonT3Address }, + { ethers, hardhatArguments, defender } + ): Promise => { + if (!semaphoreVerifierAddress) { + const VerifierFactory = await ethers.getContractFactory(`SemaphoreVerifier`) + + const verifier = await deployContract(defender, VerifierFactory, hardhatArguments.network) + + semaphoreVerifierAddress = await verifier.getAddress() + + if (logs) { + console.info(`SemaphoreVerifier contract has been deployed to: ${semaphoreVerifierAddress}`) + } + } + + if (!poseidonT3Address) { + const PoseidonT3Factory = await ethers.getContractFactory("PoseidonT3") + + const poseidonT3 = await deployContract(defender, PoseidonT3Factory, hardhatArguments.network) + + poseidonT3Address = await poseidonT3.getAddress() + + if (logs) { + console.info(`PoseidonT3 library has been deployed to: ${poseidonT3Address}`) + } + } + + const SemaphoreFactory = await ethers.getContractFactory("Semaphore", { + libraries: { + PoseidonT3: poseidonT3Address + } + }) + + const semaphore = await deployContract(defender, SemaphoreFactory, hardhatArguments.network, [ + semaphoreVerifierAddress + ]) + + const semaphoreAddress = await semaphore.getAddress() + + if (logs) { + console.info(`Semaphore contract has been deployed to: ${semaphoreAddress}`) + } + + saveDeployedContracts( + [ + { + name: "SemaphoreVerifier", + address: semaphoreVerifierAddress + }, + { + name: "PoseidonT3", + address: poseidonT3Address + }, + { + name: "Semaphore", + address: semaphoreAddress + } + ], + hardhatArguments.network + ) + + return { + semaphore, + verifierAddress: semaphoreVerifierAddress, + poseidonAddress: poseidonT3Address + } + } + ) diff --git a/packages/contracts/tasks/utils.ts b/packages/contracts/tasks/utils.ts new file mode 100644 index 000000000..2c0facb0d --- /dev/null +++ b/packages/contracts/tasks/utils.ts @@ -0,0 +1,21 @@ +import { DefenderHardhatUpgrades } from "@openzeppelin/hardhat-upgrades" +import { ContractFactory } from "ethers" + +export async function deployContract( + defender: DefenderHardhatUpgrades, + contractFactory: ContractFactory, + network?: string, + args: any[] = [] +) { + let contract + + if (network !== undefined && network !== "hardhat" && network !== "localhost") { + contract = await defender.deployContract(contractFactory, args, { salt: process.env.CREATE2_SALT }) + + await contract.waitForDeployment() + } else { + contract = await contractFactory.deploy(...args) + } + + return contract +} diff --git a/packages/contracts/test/Semaphore.ts b/packages/contracts/test/Semaphore.ts index 57daf6035..4ca01e7c2 100644 --- a/packages/contracts/test/Semaphore.ts +++ b/packages/contracts/test/Semaphore.ts @@ -1,84 +1,60 @@ /* eslint-disable @typescript-eslint/no-shadow */ /* eslint-disable jest/valid-expect */ -import { Group } from "@semaphore-protocol/group" -import { Identity } from "@semaphore-protocol/identity" -import { SemaphoreProof, generateProof } from "@semaphore-protocol/proof" +import { Group, Identity, SemaphoreProof, generateProof } from "@semaphore-protocol/core" import { expect } from "chai" -import { constants, Signer } from "ethers" -import { ethers, run } from "hardhat" -import { Pairing, Semaphore } from "../build/typechain" -import { createIdentityCommitments } from "./utils" +import { Signer, ZeroAddress } from "ethers" +import { run } from "hardhat" +// @ts-ignore +import { Semaphore } from "../typechain-types" describe("Semaphore", () => { let semaphoreContract: Semaphore - let pairingContract: Pairing - let signers: Signer[] - let accounts: string[] + let accounts: Signer[] + let accountAddresses: string[] - const treeDepth = Number(process.env.TREE_DEPTH) || 20 - const groupId = 1 - const group = new Group(groupId, treeDepth) - const members = createIdentityCommitments(3) + const merkleTreeDepth = 12 - const wasmFilePath = `../../snark-artifacts/${treeDepth}/semaphore.wasm` - const zkeyFilePath = `../../snark-artifacts/${treeDepth}/semaphore.zkey` + const groupId = 1 + const members = Array.from({ length: 3 }, (_, i) => new Identity(i)).map(({ commitment }) => commitment) before(async () => { - const { semaphore, pairingAddress } = await run("deploy:semaphore", { + const { semaphore } = await run("deploy", { logs: false }) semaphoreContract = semaphore - pairingContract = await ethers.getContractAt("Pairing", pairingAddress) - signers = await run("accounts", { logs: false }) - accounts = await Promise.all(signers.map((signer: Signer) => signer.getAddress())) + accounts = await run("accounts", { logs: false }) + accountAddresses = await Promise.all(accounts.map((signer: Signer) => signer.getAddress())) }) describe("# createGroup", () => { - it("Should not create a group if the tree depth is not supported", async () => { - const transaction = semaphoreContract["createGroup(uint256,uint256,address)"](groupId, 10, accounts[0]) - - await expect(transaction).to.be.revertedWithCustomError( - semaphoreContract, - "Semaphore__MerkleTreeDepthIsNotSupported" - ) - }) - it("Should create a group", async () => { const transaction = semaphoreContract - .connect(signers[1]) - ["createGroup(uint256,uint256,address)"](groupId, treeDepth, accounts[1]) + .connect(accounts[1]) + ["createGroup(uint256,address)"](groupId, accountAddresses[1]) - await expect(transaction) - .to.emit(semaphoreContract, "GroupCreated") - .withArgs(groupId, treeDepth, group.zeroValue) + await expect(transaction).to.emit(semaphoreContract, "GroupCreated").withArgs(groupId) await expect(transaction) .to.emit(semaphoreContract, "GroupAdminUpdated") - .withArgs(groupId, constants.AddressZero, accounts[1]) + .withArgs(groupId, ZeroAddress, accountAddresses[1]) }) it("Should create a group with a custom Merkle tree root expiration", async () => { const groupId = 2 - const group = new Group(2) - const transaction = await semaphoreContract - .connect(signers[1]) - ["createGroup(uint256,uint256,address,uint256)"]( - groupId, - treeDepth, - accounts[0], - 5 // 5 seconds. - ) + const transaction = await semaphoreContract.connect(accounts[1])["createGroup(uint256,address,uint256)"]( + groupId, + accountAddresses[0], + 5 // 5 seconds. + ) await semaphoreContract.addMember(groupId, members[0]) await semaphoreContract.addMember(groupId, members[1]) await semaphoreContract.addMember(groupId, members[2]) - await expect(transaction) - .to.emit(semaphoreContract, "GroupCreated") - .withArgs(groupId, treeDepth, group.zeroValue) + await expect(transaction).to.emit(semaphoreContract, "GroupCreated").withArgs(groupId) await expect(transaction) .to.emit(semaphoreContract, "GroupAdminUpdated") - .withArgs(groupId, constants.AddressZero, accounts[0]) + .withArgs(groupId, ZeroAddress, accountAddresses[0]) }) }) @@ -93,7 +69,7 @@ describe("Semaphore", () => { }) it("Should update the group Merkle tree duration", async () => { - const transaction = semaphoreContract.connect(signers[1]).updateGroupMerkleTreeDuration(groupId, 300) + const transaction = semaphoreContract.connect(accounts[1]).updateGroupMerkleTreeDuration(groupId, 300) await expect(transaction) .to.emit(semaphoreContract, "GroupMerkleTreeDurationUpdated") @@ -103,7 +79,7 @@ describe("Semaphore", () => { describe("# updateGroupAdmin", () => { it("Should not update a group admin if the caller is not the group admin", async () => { - const transaction = semaphoreContract.updateGroupAdmin(groupId, accounts[0]) + const transaction = semaphoreContract.updateGroupAdmin(groupId, accountAddresses[0]) await expect(transaction).to.be.revertedWithCustomError( semaphoreContract, @@ -112,11 +88,11 @@ describe("Semaphore", () => { }) it("Should update the group admin", async () => { - const transaction = semaphoreContract.connect(signers[1]).updateGroupAdmin(groupId, accounts[0]) + const transaction = semaphoreContract.connect(accounts[1]).updateGroupAdmin(groupId, accountAddresses[0]) await expect(transaction) .to.emit(semaphoreContract, "GroupAdminUpdated") - .withArgs(groupId, accounts[1], accounts[0]) + .withArgs(groupId, accountAddresses[1], accountAddresses[0]) }) }) @@ -124,7 +100,7 @@ describe("Semaphore", () => { it("Should not add a member if the caller is not the group admin", async () => { const member = BigInt(2) - const transaction = semaphoreContract.connect(signers[1]).addMember(groupId, member) + const transaction = semaphoreContract.connect(accounts[1]).addMember(groupId, member) await expect(transaction).to.be.revertedWithCustomError( semaphoreContract, @@ -133,7 +109,7 @@ describe("Semaphore", () => { }) it("Should add a new member in an existing group", async () => { - const group = new Group(groupId, treeDepth) + const group = new Group() group.addMember(members[0]) @@ -149,17 +125,17 @@ describe("Semaphore", () => { it("Should add new members to an existing group", async () => { const groupId = 3 const members = [BigInt(1), BigInt(2), BigInt(3)] - const group = new Group(groupId, treeDepth) + const group = new Group() group.addMembers(members) - await semaphoreContract["createGroup(uint256,uint256,address)"](groupId, treeDepth, accounts[0]) + await semaphoreContract["createGroup(uint256,address)"](groupId, accountAddresses[0]) const transaction = semaphoreContract.addMembers(groupId, members) await expect(transaction) - .to.emit(semaphoreContract, "MemberAdded") - .withArgs(groupId, 2, BigInt(3), group.root) + .to.emit(semaphoreContract, "MembersAdded") + .withArgs(groupId, 0, members, group.root) }) }) @@ -167,7 +143,7 @@ describe("Semaphore", () => { it("Should not update a member if the caller is not the group admin", async () => { const member = BigInt(2) - const transaction = semaphoreContract.connect(signers[1]).updateMember(groupId, member, 1, [0, 1], [0, 1]) + const transaction = semaphoreContract.connect(accounts[1]).updateMember(groupId, member, 1, [0, 1]) await expect(transaction).to.be.revertedWithCustomError( semaphoreContract, @@ -178,18 +154,18 @@ describe("Semaphore", () => { it("Should update a member from an existing group", async () => { const groupId = 4 const members = [BigInt(1), BigInt(2), BigInt(3)] - const group = new Group(groupId, treeDepth) + const group = new Group() group.addMembers(members) group.updateMember(0, BigInt(4)) - await semaphoreContract["createGroup(uint256,uint256,address)"](groupId, treeDepth, accounts[0]) + await semaphoreContract["createGroup(uint256,address)"](groupId, accountAddresses[0]) await semaphoreContract.addMembers(groupId, members) - const { siblings, pathIndices, root } = group.generateMerkleProof(0) + const { siblings, root } = group.generateMerkleProof(0) - const transaction = semaphoreContract.updateMember(groupId, BigInt(1), BigInt(4), siblings, pathIndices) + const transaction = semaphoreContract.updateMember(groupId, BigInt(1), BigInt(4), siblings) await expect(transaction) .to.emit(semaphoreContract, "MemberUpdated") @@ -201,7 +177,7 @@ describe("Semaphore", () => { it("Should not remove a member if the caller is not the group admin", async () => { const member = BigInt(2) - const transaction = semaphoreContract.connect(signers[1]).removeMember(groupId, member, [0, 1], [0, 1]) + const transaction = semaphoreContract.connect(accounts[1]).removeMember(groupId, member, [0, 1]) await expect(transaction).to.be.revertedWithCustomError( semaphoreContract, @@ -212,50 +188,64 @@ describe("Semaphore", () => { it("Should remove a member from an existing group", async () => { const groupId = 5 const members = [BigInt(1), BigInt(2), BigInt(3)] - const group = new Group(groupId, treeDepth) + const group = new Group() group.addMembers(members) group.removeMember(2) - await semaphoreContract["createGroup(uint256,uint256,address)"](groupId, treeDepth, accounts[0]) + await semaphoreContract["createGroup(uint256,address)"](groupId, accountAddresses[0]) await semaphoreContract.addMembers(groupId, members) - const { siblings, pathIndices, root } = group.generateMerkleProof(2) + const { siblings, root } = group.generateMerkleProof(2) - const transaction = semaphoreContract.removeMember(groupId, BigInt(3), siblings, pathIndices) + const transaction = semaphoreContract.removeMember(groupId, BigInt(3), siblings) await expect(transaction).to.emit(semaphoreContract, "MemberRemoved").withArgs(groupId, 2, BigInt(3), root) }) }) + describe("# getGroupAdmin", () => { + it("Should return a 0 address if the group does not exist", async () => { + const address = await semaphoreContract.getGroupAdmin(999) + + expect(address).to.equal(ZeroAddress) + }) + + it("Should return the address of the group admin", async () => { + const address = await semaphoreContract.getGroupAdmin(groupId) + + expect(address).to.equal(accountAddresses[0]) + }) + }) + describe("# verifyProof", () => { - const signal = 2 + const groupId = 10 + const message = 2 const identity = new Identity("0") - const group = new Group(groupId, treeDepth) + const group = new Group() group.addMembers(members) - let fullProof: SemaphoreProof + let proof: SemaphoreProof before(async () => { - await semaphoreContract.addMembers(groupId, [members[1], members[2]]) + await semaphoreContract["createGroup(uint256,address)"](groupId, accountAddresses[0]) - fullProof = await generateProof(identity, group, group.root, signal, { - wasmFilePath, - zkeyFilePath - }) + await semaphoreContract.addMembers(groupId, members) + + proof = await generateProof(identity, group, message, group.root as string, merkleTreeDepth) }) it("Should not verify a proof if the group does not exist", async () => { - const transaction = semaphoreContract.verifyProof(10, 1, signal, 0, 0, [0, 0, 0, 0, 0, 0, 0, 0]) + const transaction = semaphoreContract.verifyProof(11, proof) await expect(transaction).to.be.revertedWithCustomError(semaphoreContract, "Semaphore__GroupDoesNotExist") }) it("Should not verify a proof if the Merkle tree root is not part of the group", async () => { - const transaction = semaphoreContract.verifyProof(2, 1, signal, 0, 0, [0, 0, 0, 0, 0, 0, 0, 0]) + const transaction = semaphoreContract.verifyProof(groupId, { ...proof, merkleTreeRoot: 1 }) await expect(transaction).to.be.revertedWithCustomError( semaphoreContract, @@ -263,79 +253,104 @@ describe("Semaphore", () => { ) }) - it("Should throw an exception if the proof is not valid", async () => { - const transaction = semaphoreContract.verifyProof( - groupId, - fullProof.merkleTreeRoot, - fullProof.signal, - fullProof.nullifierHash, - 0, - fullProof.proof - ) + it("Should verify a proof for an onchain group", async () => { + const validProof = await semaphoreContract.verifyProof(groupId, proof) - await expect(transaction).to.be.revertedWithCustomError(pairingContract, "InvalidProof") + expect(validProof).to.equal(true) }) - it("Should verify a proof for an onchain group correctly", async () => { - const transaction = semaphoreContract.verifyProof( - groupId, - fullProof.merkleTreeRoot, - fullProof.signal, - fullProof.nullifierHash, - fullProof.merkleTreeRoot, - fullProof.proof - ) + it("Should not verify a proof if the Merkle tree root is expired", async () => { + const groupId = 2 - await expect(transaction) - .to.emit(semaphoreContract, "ProofVerified") - .withArgs( - groupId, - fullProof.merkleTreeRoot, - fullProof.nullifierHash, - fullProof.merkleTreeRoot, - fullProof.signal - ) - }) + const group = new Group() - it("Should not verify the same proof for an onchain group twice", async () => { - const transaction = semaphoreContract.verifyProof( - groupId, - fullProof.merkleTreeRoot, - fullProof.signal, - fullProof.nullifierHash, - fullProof.merkleTreeRoot, - fullProof.proof - ) + group.addMembers([members[0], members[1]]) + + const proof = await generateProof(identity, group, message, group.root as string, merkleTreeDepth) + + const transaction = semaphoreContract.verifyProof(groupId, proof) await expect(transaction).to.be.revertedWithCustomError( semaphoreContract, - "Semaphore__YouAreUsingTheSameNillifierTwice" + "Semaphore__MerkleTreeRootIsExpired" ) }) + }) - it("Should not verify a proof if the Merkle tree root is expired", async () => { - const groupId = 2 - const group = new Group(groupId, treeDepth) + describe("# validateProof", () => { + const message = 2 + const identity = new Identity("0") + const groupOneMemberId = 6 - group.addMembers([members[0], members[1]]) + const group = new Group() + const groupOneMember = new Group() - const fullProof = await generateProof(identity, group, group.root, signal, { - wasmFilePath, - zkeyFilePath - }) + group.addMembers(members) + groupOneMember.addMember(members[0]) - const transaction = semaphoreContract.verifyProof( - groupId, - fullProof.merkleTreeRoot, - fullProof.signal, - fullProof.nullifierHash, - fullProof.merkleTreeRoot, - fullProof.proof + let proof: SemaphoreProof + let proofOneMember: SemaphoreProof + + before(async () => { + await semaphoreContract["createGroup(uint256,address)"](groupOneMemberId, accountAddresses[0]) + + await semaphoreContract.addMembers(groupId, [members[1], members[2]]) + await semaphoreContract.addMember(groupOneMemberId, members[0]) + + proof = await generateProof(identity, group, message, group.root as string, merkleTreeDepth) + proofOneMember = await generateProof( + identity, + groupOneMember, + message, + groupOneMember.root as string, + merkleTreeDepth ) + }) + + it("Should throw an exception if the proof is not valid", async () => { + const transaction = semaphoreContract.validateProof(groupId, { ...proof, scope: 0 }) + + await expect(transaction).to.be.revertedWithCustomError(semaphoreContract, "Semaphore__InvalidProof") + }) + + it("Should validate a proof for an onchain group with one member correctly", async () => { + const transaction = semaphoreContract.validateProof(groupOneMemberId, proofOneMember) + + await expect(transaction) + .to.emit(semaphoreContract, "ProofValidated") + .withArgs( + groupOneMemberId, + proofOneMember.merkleTreeDepth, + proofOneMember.merkleTreeRoot, + proofOneMember.nullifier, + proofOneMember.message, + proofOneMember.merkleTreeRoot, + proofOneMember.points + ) + }) + + it("Should validate a proof for an onchain group with more than one member correctly", async () => { + const transaction = semaphoreContract.validateProof(groupId, proof) + + await expect(transaction) + .to.emit(semaphoreContract, "ProofValidated") + .withArgs( + groupId, + proof.merkleTreeDepth, + proof.merkleTreeRoot, + proof.nullifier, + proof.message, + proof.merkleTreeRoot, + proof.points + ) + }) + + it("Should not validate the same proof for an onchain group twice", async () => { + const transaction = semaphoreContract.validateProof(groupId, proof) await expect(transaction).to.be.revertedWithCustomError( semaphoreContract, - "Semaphore__MerkleTreeRootIsExpired" + "Semaphore__YouAreUsingTheSameNullifierTwice" ) }) }) diff --git a/packages/contracts/test/SemaphoreVoting.ts b/packages/contracts/test/SemaphoreVoting.ts deleted file mode 100644 index 79423ea92..000000000 --- a/packages/contracts/test/SemaphoreVoting.ts +++ /dev/null @@ -1,224 +0,0 @@ -/* eslint-disable jest/valid-expect */ -import { Group } from "@semaphore-protocol/group" -import { Identity } from "@semaphore-protocol/identity" -import { SemaphoreProof, generateProof } from "@semaphore-protocol/proof" -import { expect } from "chai" -import { Signer } from "ethers" -import { ethers, run } from "hardhat" -import { Pairing, SemaphoreVoting } from "../build/typechain" - -describe("SemaphoreVoting", () => { - let semaphoreVotingContract: SemaphoreVoting - let pairingContract: Pairing - let accounts: Signer[] - let coordinator: string - - const treeDepth = Number(process.env.TREE_DEPTH) || 20 - const pollIds = [1, 2, 3] - const encryptionKey = BigInt(0) - const decryptionKey = BigInt(0) - - const wasmFilePath = `../../snark-artifacts/${treeDepth}/semaphore.wasm` - const zkeyFilePath = `../../snark-artifacts/${treeDepth}/semaphore.zkey` - - before(async () => { - const { semaphoreVoting, pairingAddress } = await run("deploy:semaphore-voting", { - logs: false - }) - - semaphoreVotingContract = semaphoreVoting - pairingContract = await ethers.getContractAt("Pairing", pairingAddress) - - accounts = await ethers.getSigners() - coordinator = await accounts[1].getAddress() - }) - - describe("# createPoll", () => { - it("Should not create a poll with a wrong depth", async () => { - const transaction = semaphoreVotingContract.createPoll(pollIds[0], coordinator, 10) - - await expect(transaction).to.be.revertedWithCustomError( - semaphoreVotingContract, - "Semaphore__MerkleTreeDepthIsNotSupported" - ) - }) - - it("Should create a poll", async () => { - const transaction = semaphoreVotingContract.createPoll(pollIds[0], coordinator, treeDepth) - - await expect(transaction).to.emit(semaphoreVotingContract, "PollCreated").withArgs(pollIds[0], coordinator) - }) - - it("Should not create a poll if it already exists", async () => { - const transaction = semaphoreVotingContract.createPoll(pollIds[0], coordinator, treeDepth) - - await expect(transaction).to.be.revertedWithCustomError( - semaphoreVotingContract, - "Semaphore__GroupAlreadyExists" - ) - }) - }) - - describe("# startPoll", () => { - it("Should not start the poll if the caller is not the coordinator", async () => { - const transaction = semaphoreVotingContract.startPoll(pollIds[0], encryptionKey) - - await expect(transaction).to.be.revertedWithCustomError( - semaphoreVotingContract, - "Semaphore__CallerIsNotThePollCoordinator" - ) - }) - - it("Should start the poll", async () => { - const transaction = semaphoreVotingContract.connect(accounts[1]).startPoll(pollIds[0], encryptionKey) - - await expect(transaction) - .to.emit(semaphoreVotingContract, "PollStarted") - .withArgs(pollIds[0], coordinator, encryptionKey) - }) - - it("Should not start a poll if it has already been started", async () => { - const transaction = semaphoreVotingContract.connect(accounts[1]).startPoll(pollIds[0], encryptionKey) - - await expect(transaction).to.be.revertedWithCustomError( - semaphoreVotingContract, - "Semaphore__PollHasAlreadyBeenStarted" - ) - }) - }) - - describe("# addVoter", () => { - before(async () => { - await semaphoreVotingContract.createPoll(pollIds[1], coordinator, treeDepth) - }) - - it("Should not add a voter if the caller is not the coordinator", async () => { - const { commitment } = new Identity() - - const transaction = semaphoreVotingContract.addVoter(pollIds[0], commitment) - - await expect(transaction).to.be.revertedWithCustomError( - semaphoreVotingContract, - "Semaphore__CallerIsNotThePollCoordinator" - ) - }) - - it("Should not add a voter if the poll has already been started", async () => { - const { commitment } = new Identity() - - const transaction = semaphoreVotingContract.connect(accounts[1]).addVoter(pollIds[0], commitment) - - await expect(transaction).to.be.revertedWithCustomError( - semaphoreVotingContract, - "Semaphore__PollHasAlreadyBeenStarted" - ) - }) - - it("Should add a voter to an existing poll", async () => { - const { commitment } = new Identity("test") - const group = new Group(pollIds[1], treeDepth) - - group.addMember(commitment) - - const transaction = semaphoreVotingContract.connect(accounts[1]).addVoter(pollIds[1], commitment) - - await expect(transaction) - .to.emit(semaphoreVotingContract, "MemberAdded") - .withArgs(pollIds[1], 0, commitment, group.root) - }) - - it("Should return the correct number of poll voters", async () => { - const size = await semaphoreVotingContract.getNumberOfMerkleTreeLeaves(pollIds[1]) - - expect(size).to.be.eq(1) - }) - }) - - describe("# castVote", () => { - const identity = new Identity("test") - const vote = 1 - - const group = new Group(pollIds[1], treeDepth) - - group.addMembers([identity.commitment, BigInt(1)]) - - let fullProof: SemaphoreProof - - before(async () => { - await semaphoreVotingContract.connect(accounts[1]).addVoter(pollIds[1], BigInt(1)) - await semaphoreVotingContract.connect(accounts[1]).startPoll(pollIds[1], encryptionKey) - await semaphoreVotingContract.createPoll(pollIds[2], coordinator, treeDepth) - - fullProof = await generateProof(identity, group, pollIds[1], vote, { - wasmFilePath, - zkeyFilePath - }) - }) - - it("Should not cast a vote if the poll is not ongoing", async () => { - const transaction = semaphoreVotingContract - .connect(accounts[1]) - .castVote(vote, fullProof.nullifierHash, pollIds[2], fullProof.proof) - - await expect(transaction).to.be.revertedWithCustomError( - semaphoreVotingContract, - "Semaphore__PollIsNotOngoing" - ) - }) - - it("Should not cast a vote if the proof is not valid", async () => { - const transaction = semaphoreVotingContract - .connect(accounts[1]) - .castVote(vote, 0, pollIds[1], fullProof.proof) - - await expect(transaction).to.be.revertedWithCustomError(pairingContract, "InvalidProof") - }) - - it("Should cast a vote", async () => { - const transaction = semaphoreVotingContract - .connect(accounts[1]) - .castVote(vote, fullProof.nullifierHash, pollIds[1], fullProof.proof) - - await expect(transaction).to.emit(semaphoreVotingContract, "VoteAdded").withArgs(pollIds[1], vote) - }) - - it("Should not cast a vote twice", async () => { - const transaction = semaphoreVotingContract - .connect(accounts[1]) - .castVote(vote, fullProof.nullifierHash, pollIds[1], fullProof.proof) - - await expect(transaction).to.be.revertedWithCustomError( - semaphoreVotingContract, - "Semaphore__YouAreUsingTheSameNillifierTwice" - ) - }) - }) - - describe("# endPoll", () => { - it("Should not end the poll if the caller is not the coordinator", async () => { - const transaction = semaphoreVotingContract.endPoll(pollIds[1], decryptionKey) - - await expect(transaction).to.be.revertedWithCustomError( - semaphoreVotingContract, - "Semaphore__CallerIsNotThePollCoordinator" - ) - }) - - it("Should end the poll", async () => { - const transaction = semaphoreVotingContract.connect(accounts[1]).endPoll(pollIds[1], encryptionKey) - - await expect(transaction) - .to.emit(semaphoreVotingContract, "PollEnded") - .withArgs(pollIds[1], coordinator, decryptionKey) - }) - - it("Should not end a poll if it has already been ended", async () => { - const transaction = semaphoreVotingContract.connect(accounts[1]).endPoll(pollIds[1], encryptionKey) - - await expect(transaction).to.be.revertedWithCustomError( - semaphoreVotingContract, - "Semaphore__PollIsNotOngoing" - ) - }) - }) -}) diff --git a/packages/contracts/test/SemaphoreWhistleblowing.ts b/packages/contracts/test/SemaphoreWhistleblowing.ts deleted file mode 100644 index adc2b4049..000000000 --- a/packages/contracts/test/SemaphoreWhistleblowing.ts +++ /dev/null @@ -1,179 +0,0 @@ -/* eslint-disable jest/valid-expect */ -import { Group } from "@semaphore-protocol/group" -import { Identity } from "@semaphore-protocol/identity" -import { SemaphoreProof, generateProof } from "@semaphore-protocol/proof" -import { expect } from "chai" -import { Signer, utils } from "ethers" -import { ethers, run } from "hardhat" -import { Pairing, SemaphoreWhistleblowing } from "../build/typechain" - -describe("SemaphoreWhistleblowing", () => { - let semaphoreWhistleblowingContract: SemaphoreWhistleblowing - let pairingContract: Pairing - let accounts: Signer[] - let editor: string - - const treeDepth = Number(process.env.TREE_DEPTH) || 20 - const entityIds = [1, 2] - - const wasmFilePath = `../../snark-artifacts/${treeDepth}/semaphore.wasm` - const zkeyFilePath = `../../snark-artifacts/${treeDepth}/semaphore.zkey` - - before(async () => { - const { semaphoreWhistleblowing, pairingAddress } = await run("deploy:semaphore-whistleblowing", { - logs: false - }) - - semaphoreWhistleblowingContract = semaphoreWhistleblowing - pairingContract = await ethers.getContractAt("Pairing", pairingAddress) - - accounts = await ethers.getSigners() - editor = await accounts[1].getAddress() - }) - - describe("# createEntity", () => { - it("Should not create an entity with a wrong depth", async () => { - const transaction = semaphoreWhistleblowingContract.createEntity(entityIds[0], editor, 10) - - await expect(transaction).to.be.revertedWithCustomError( - semaphoreWhistleblowingContract, - "Semaphore__MerkleTreeDepthIsNotSupported" - ) - }) - - it("Should create an entity", async () => { - const transaction = semaphoreWhistleblowingContract.createEntity(entityIds[0], editor, treeDepth) - - await expect(transaction) - .to.emit(semaphoreWhistleblowingContract, "EntityCreated") - .withArgs(entityIds[0], editor) - }) - - it("Should not create a entity if it already exists", async () => { - const transaction = semaphoreWhistleblowingContract.createEntity(entityIds[0], editor, treeDepth) - - await expect(transaction).to.be.revertedWithCustomError( - semaphoreWhistleblowingContract, - "Semaphore__GroupAlreadyExists" - ) - }) - }) - - describe("# addWhistleblower", () => { - it("Should not add a whistleblower if the caller is not the editor", async () => { - const { commitment } = new Identity() - - const transaction = semaphoreWhistleblowingContract.addWhistleblower(entityIds[0], commitment) - - await expect(transaction).to.be.revertedWithCustomError( - semaphoreWhistleblowingContract, - "Semaphore__CallerIsNotTheEditor" - ) - }) - - it("Should add a whistleblower to an existing entity", async () => { - const { commitment } = new Identity("test") - const group = new Group(entityIds[0], treeDepth) - - group.addMember(commitment) - - const transaction = semaphoreWhistleblowingContract - .connect(accounts[1]) - .addWhistleblower(entityIds[0], commitment) - - await expect(transaction) - .to.emit(semaphoreWhistleblowingContract, "MemberAdded") - .withArgs(entityIds[0], 0, commitment, group.root) - }) - - it("Should return the correct number of whistleblowers of an entity", async () => { - const size = await semaphoreWhistleblowingContract.getNumberOfMerkleTreeLeaves(entityIds[0]) - - expect(size).to.be.eq(1) - }) - }) - - describe("# removeWhistleblower", () => { - it("Should not remove a whistleblower if the caller is not the editor", async () => { - const { commitment } = new Identity() - const group = new Group(entityIds[0], treeDepth) - - group.addMember(commitment) - - const { siblings, pathIndices } = group.generateMerkleProof(0) - - const transaction = semaphoreWhistleblowingContract.removeWhistleblower( - entityIds[0], - commitment, - siblings, - pathIndices - ) - - await expect(transaction).to.be.revertedWithCustomError( - semaphoreWhistleblowingContract, - "Semaphore__CallerIsNotTheEditor" - ) - }) - - it("Should remove a whistleblower from an existing entity", async () => { - const { commitment } = new Identity("test") - const group = new Group(entityIds[0], treeDepth) - - group.addMember(commitment) - - const { siblings, pathIndices } = group.generateMerkleProof(0) - - group.removeMember(0) - - const transaction = semaphoreWhistleblowingContract - .connect(accounts[1]) - .removeWhistleblower(entityIds[0], commitment, siblings, pathIndices) - - await expect(transaction) - .to.emit(semaphoreWhistleblowingContract, "MemberRemoved") - .withArgs(entityIds[0], 0, commitment, group.root) - }) - }) - - describe("# publishLeak", () => { - const identity = new Identity("test") - const leak = utils.formatBytes32String("This is a leak") - - const group = new Group(entityIds[1], treeDepth) - - group.addMembers([identity.commitment, BigInt(1)]) - - let fullProof: SemaphoreProof - - before(async () => { - await semaphoreWhistleblowingContract.createEntity(entityIds[1], editor, treeDepth) - await semaphoreWhistleblowingContract - .connect(accounts[1]) - .addWhistleblower(entityIds[1], identity.commitment) - await semaphoreWhistleblowingContract.connect(accounts[1]).addWhistleblower(entityIds[1], BigInt(1)) - - fullProof = await generateProof(identity, group, entityIds[1], leak, { - wasmFilePath, - zkeyFilePath - }) - }) - - it("Should not publish a leak if the proof is not valid", async () => { - const transaction = semaphoreWhistleblowingContract - .connect(accounts[1]) - .publishLeak(leak, 0, entityIds[1], fullProof.proof) - - await expect(transaction).to.be.revertedWithCustomError(pairingContract, "InvalidProof") - }) - - it("Should publish a leak", async () => { - const transaction = semaphoreWhistleblowingContract - .connect(accounts[1]) - .publishLeak(leak, fullProof.nullifierHash, entityIds[1], fullProof.proof) - - await expect(transaction) - .to.emit(semaphoreWhistleblowingContract, "LeakPublished") - .withArgs(entityIds[1], leak) - }) - }) -}) diff --git a/packages/contracts/test/utils.ts b/packages/contracts/test/utils.ts deleted file mode 100644 index 71175d96b..000000000 --- a/packages/contracts/test/utils.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Identity } from "@semaphore-protocol/identity" - -// eslint-disable-next-line import/prefer-default-export -export function createIdentityCommitments(n: number): bigint[] { - const identityCommitments: bigint[] = [] - - for (let i = 0; i < n; i += 1) { - const { commitment } = new Identity(i.toString()) - - identityCommitments.push(commitment) - } - - return identityCommitments -} diff --git a/packages/contracts/tsconfig.json b/packages/contracts/tsconfig.json index a82266e4a..0a513fff3 100644 --- a/packages/contracts/tsconfig.json +++ b/packages/contracts/tsconfig.json @@ -1,10 +1,13 @@ { - "extends": "../../tsconfig.json", "compilerOptions": { - "target": "ES2018", - "module": "CommonJS", - "outDir": "dist" + "target": "es2020", + "module": "commonjs", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true }, - "include": ["scripts/**/*", "tasks/**/*", "test/**/*", "build/typechain/**/*", "types/**/*"], + "include": ["scripts/**/*", "tasks/**/*", "test/**/*", "typechain-types/**/*"], "files": ["hardhat.config.ts"] } diff --git a/packages/core/LICENSE b/packages/core/LICENSE new file mode 100644 index 000000000..8ef16f7a5 --- /dev/null +++ b/packages/core/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Ethereum Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 000000000..dac3dc1d2 --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,85 @@ +

+

+ Semaphore core +

+

Core library for the essential Semaphore features.

+

+ +

+ + + + + NPM license + + + NPM version + + + Downloads + + + Linter eslint + + + Code style prettier + +

+ + + +| This library is a simple re-export of the Semaphore core libraries: `@semaphore-protocol/identity`, `@semaphore-protocol/group`, `@semaphore-protocol/proof`. So that developers can install a single package to use all the core functionalities of the protocol. | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + +## 🛠 Install + +### npm or yarn + +Install the `@semaphore-protocol/core` package with npm: + +```bash +npm i @semaphore-protocol/core +``` + +or yarn: + +```bash +yarn add @semaphore-protocol/core +``` + +## 📜 Usage + +```typescript +import { Identity, Group, generateProof, verifyProof } from "@semaphore-protocol/core" + +const identity1 = new Identity() +const identity2 = new Identity() +const identity3 = new Identity() + +const group = new Group([identity1.commitment, identity2.commitment, identity3.commitment]) + +const message = "Hello world" +const scope = "Semaphore" + +const proof = await generateProof(identity1, group, message, scope) + +await verifyProof(proof) +``` diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 000000000..50f2f654c --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,49 @@ +{ + "name": "@semaphore-protocol/core", + "version": "4.0.0-alpha.8", + "description": "Core library for the essential Semaphore features.", + "type": "module", + "license": "MIT", + "main": "src/index.js", + "types": "src/types/index.d.ts", + "exports": { + ".": { + "types": "./src/types/index.d.ts", + "require": "./src/index.cjs", + "default": "./src/index.js" + }, + "./identity": { + "types": "./src/identity/types/index.d.ts", + "require": "./src/identity/index.cjs", + "default": "./src/identity/index.js" + }, + "./group": { + "types": "./src/identity/types/index.d.ts", + "require": "./src/group/index.cjs", + "default": "./src/group/index.js" + }, + "./proof": { + "types": "./src/identity/types/index.d.ts", + "require": "./src/proof/index.cjs", + "default": "./src/proof/index.js" + } + }, + "files": [ + "src/", + "LICENSE", + "README.md" + ], + "repository": "https://github.com/semaphore-protocol/semaphore", + "homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/core", + "bugs": { + "url": "https://github.com/semaphore-protocol/semaphore.git/issues" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@semaphore-protocol/group": "4.0.0-alpha.8", + "@semaphore-protocol/identity": "4.0.0-alpha.8", + "@semaphore-protocol/proof": "4.0.0-alpha.8" + } +} diff --git a/packages/core/src/group/index.cjs b/packages/core/src/group/index.cjs new file mode 100644 index 000000000..6f71f4f81 --- /dev/null +++ b/packages/core/src/group/index.cjs @@ -0,0 +1,3 @@ +const identity = require("@semaphore-protocol/group") + +Object.assign(exports, identity) diff --git a/packages/core/src/group/index.js b/packages/core/src/group/index.js new file mode 100644 index 000000000..e4641b3b1 --- /dev/null +++ b/packages/core/src/group/index.js @@ -0,0 +1 @@ +export * from "@semaphore-protocol/group" diff --git a/packages/core/src/group/types/index.d.ts b/packages/core/src/group/types/index.d.ts new file mode 100644 index 000000000..e4641b3b1 --- /dev/null +++ b/packages/core/src/group/types/index.d.ts @@ -0,0 +1 @@ +export * from "@semaphore-protocol/group" diff --git a/packages/core/src/identity/index.cjs b/packages/core/src/identity/index.cjs new file mode 100644 index 000000000..8380d13c4 --- /dev/null +++ b/packages/core/src/identity/index.cjs @@ -0,0 +1,3 @@ +const identity = require("@semaphore-protocol/identity") + +Object.assign(exports, identity) diff --git a/packages/core/src/identity/index.js b/packages/core/src/identity/index.js new file mode 100644 index 000000000..039af707f --- /dev/null +++ b/packages/core/src/identity/index.js @@ -0,0 +1 @@ +export * from "@semaphore-protocol/identity" diff --git a/packages/core/src/identity/types/index.d.ts b/packages/core/src/identity/types/index.d.ts new file mode 100644 index 000000000..039af707f --- /dev/null +++ b/packages/core/src/identity/types/index.d.ts @@ -0,0 +1 @@ +export * from "@semaphore-protocol/identity" diff --git a/packages/core/src/index.cjs b/packages/core/src/index.cjs new file mode 100644 index 000000000..5f460b7bb --- /dev/null +++ b/packages/core/src/index.cjs @@ -0,0 +1,5 @@ +const identity = require("./identity/index.cjs") +const group = require("./group/index.cjs") +const proof = require("./proof/index.cjs") + +Object.assign(exports, identity, group, proof) diff --git a/packages/core/src/index.js b/packages/core/src/index.js new file mode 100644 index 000000000..8e5f6589c --- /dev/null +++ b/packages/core/src/index.js @@ -0,0 +1,4 @@ +/* eslint-disable import/export */ +export * from "./identity/index.js" +export * from "./group/index.js" +export * from "./proof/index.js" diff --git a/packages/core/src/proof/index.cjs b/packages/core/src/proof/index.cjs new file mode 100644 index 000000000..ef9829f88 --- /dev/null +++ b/packages/core/src/proof/index.cjs @@ -0,0 +1,3 @@ +const identity = require("@semaphore-protocol/proof") + +Object.assign(exports, identity) diff --git a/packages/core/src/proof/index.js b/packages/core/src/proof/index.js new file mode 100644 index 000000000..58b6024e6 --- /dev/null +++ b/packages/core/src/proof/index.js @@ -0,0 +1 @@ +export * from "@semaphore-protocol/proof" diff --git a/packages/core/src/proof/types/index.d.ts b/packages/core/src/proof/types/index.d.ts new file mode 100644 index 000000000..58b6024e6 --- /dev/null +++ b/packages/core/src/proof/types/index.d.ts @@ -0,0 +1 @@ +export * from "@semaphore-protocol/proof" diff --git a/packages/core/src/types/index.d.ts b/packages/core/src/types/index.d.ts new file mode 100644 index 000000000..6023d3580 --- /dev/null +++ b/packages/core/src/types/index.d.ts @@ -0,0 +1,4 @@ +/* eslint-disable import/export */ +export * from "../identity/types" +export * from "../group/types" +export * from "../proof/types" diff --git a/packages/data/LICENSE b/packages/data/LICENSE index 4377091ec..8ef16f7a5 100644 --- a/packages/data/LICENSE +++ b/packages/data/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Ethereum Foundation +Copyright (c) 2024 Ethereum Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/data/README.md b/packages/data/README.md index 265636188..3d9c5e7ef 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -10,7 +10,7 @@ - Github license + NPM license NPM version @@ -18,7 +18,7 @@ Downloads - + Documentation typedoc @@ -70,6 +70,8 @@ yarn add @semaphore-protocol/data ## 📜 Usage +For more information on the functions provided by `@semaphore-protocol/data`, please refer to the [TypeDoc documentation](https://js.semaphore.pse.dev/modules/_semaphore_protocol_data). + \# **getSupportedNetworks**(): _string[]_ ```typescript @@ -118,6 +120,18 @@ const group = await semaphoreSubgraph.getGroup("42") const { members, verifiedProofs } = semaphoreSubgraph.getGroup("42", { members: true, verifiedProofs: true }) ``` +\# **getGroupMembers**(groupId: _string_): _Promise\_ + +```typescript +const members = await semaphoreSubgraph.getGroupMembers("42") +``` + +\# **getGroupVerifiedProofs**(groupId: _string_): _Promise\_ + +```typescript +const verifiedProofs = await semaphoreSubgraph.getGroupVerifiedProofs("42") +``` + \# **isGroupMember**(groupId: _string_, member: _string_): _Promise\_ ```typescript @@ -127,7 +141,7 @@ await semaphoreSubgraph.isGroupMember( ) ``` -\# **new Ethers**(networkOrEthereumURL: Network | string = "goerli", options: EthersOptions = {}): _SemaphoreEthers_ +\# **new Ethers**(networkOrEthereumURL: Network | string = "sepolia", options: EthersOptions = {}): _SemaphoreEthers_ ```typescript import { SemaphoreEthers } from "@semaphore-protocol/data" @@ -175,3 +189,12 @@ const members = await semaphoreEthers.getGroupMembers("42") ```typescript const verifiedProofs = await semaphoreEthers.getGroupVerifiedProofs("42") ``` + +\# **isGroupMember**(groupId: _string_, member: _string_): _Promise\_ + +```typescript +await semaphoreEthers.isGroupMember( + "42", + "16948514235341957898454876473214737047419402240398321289450170535251226167324" +) +``` diff --git a/packages/data/package.json b/packages/data/package.json index a44d0c57a..f9eddf9b8 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,15 +1,16 @@ { "name": "@semaphore-protocol/data", - "version": "3.15.2", + "version": "4.0.0-alpha.8", "description": "A library to query Semaphore contracts.", + "type": "module", "license": "MIT", - "main": "dist/index.node.js", + "main": "dist/index.js", + "types": "dist/types/index.d.ts", "exports": { - "import": "./dist/index.mjs", - "require": "./dist/index.node.js", - "types": "./dist/types/index.d.ts" + "types": "./dist/types/index.d.ts", + "require": "./dist/index.cjs", + "default": "./dist/index.js" }, - "types": "./dist/types/index.d.ts", "files": [ "dist/", "src/", @@ -22,7 +23,6 @@ "url": "https://github.com/semaphore-protocol/semaphore.git/issues" }, "scripts": { - "build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript", "build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript", "prepublishOnly": "yarn build" }, @@ -30,13 +30,12 @@ "access": "public" }, "devDependencies": { - "@rollup/plugin-json": "^6.0.0", + "@rollup/plugin-json": "^6.1.0", "rollup-plugin-cleanup": "^3.2.1", - "rollup-plugin-typescript2": "^0.31.2" + "rollup-plugin-typescript2": "^0.36.0" }, "dependencies": { - "@ethersproject/contracts": "^5.7.0", - "@ethersproject/providers": "^5.7.0", - "axios": "^0.27.2" + "axios": "1.6.6", + "ethers": "6.11.0" } } diff --git a/packages/data/rollup.config.ts b/packages/data/rollup.config.ts index f376e72b6..568e76ddc 100644 --- a/packages/data/rollup.config.ts +++ b/packages/data/rollup.config.ts @@ -8,7 +8,7 @@ const banner = `/** * @module ${pkg.name} * @version ${pkg.version} * @file ${pkg.description} - * @copyright Ethereum Foundation 2022 + * @copyright Ethereum Foundation 2024 * @license ${pkg.license} * @see [Github]{@link ${pkg.homepage}} */` @@ -17,9 +17,9 @@ export default { input: "src/index.ts", output: [ { file: pkg.exports.require, format: "cjs", banner, exports: "auto" }, - { file: pkg.exports.import, format: "es", banner } + { file: pkg.exports.default, format: "es", banner } ], - external: Object.keys(pkg.dependencies), + external: [...Object.keys(pkg.dependencies), "ethers/contract", "ethers/constants", "ethers/providers"], plugins: [ json(), typescript({ tsconfig: "./build.tsconfig.json", useTsconfigDeclarationDir: true }), diff --git a/packages/data/src/ethers.ts b/packages/data/src/ethers.ts index 1f0e6c357..e6f790a39 100644 --- a/packages/data/src/ethers.ts +++ b/packages/data/src/ethers.ts @@ -1,4 +1,5 @@ -import { Contract } from "@ethersproject/contracts" +import { ZeroAddress } from "ethers/constants" +import { Contract } from "ethers/contract" import { AlchemyProvider, AnkrProvider, @@ -8,11 +9,11 @@ import { JsonRpcProvider, PocketProvider, Provider -} from "@ethersproject/providers" +} from "ethers/providers" import checkParameter from "./checkParameter" import getEvents from "./getEvents" import SemaphoreABI from "./semaphoreABI.json" -import { EthersOptions, GroupResponse, EthersNetwork } from "./types" +import { EthersNetwork, EthersOptions, GroupResponse } from "./types" export default class SemaphoreEthers { private _network: EthersNetwork | string @@ -46,7 +47,7 @@ export default class SemaphoreEthers { options.address ??= "0xc60E0Ee1a2770d5F619858C641f14FC4a6401520" options.startBlock ??= 77278430 break - case "arbitrum-goerli": + case "arbitrum-sepolia": options.address ??= "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131" options.startBlock ??= 15174410 break @@ -54,15 +55,11 @@ export default class SemaphoreEthers { options.address ??= "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131" options.startBlock ??= 33995010 break - case "goerli": - options.address ??= "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131" - options.startBlock ??= 8777695 - break case "sepolia": - options.address ??= "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131" - options.startBlock ??= 3231111 + options.address ??= "0x5B8e7cC7bAC61A4b952d472b67056B2f260ba6dc" + options.startBlock ??= 5150903 break - case "optimism-goerli": + case "optimism-sepolia": options.address ??= "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131" options.startBlock ??= 7632846 break @@ -78,19 +75,19 @@ export default class SemaphoreEthers { switch (options.provider) { case "infura": - provider = new InfuraProvider(networkOrEthereumURL, options.apiKey) + provider = new InfuraProvider(networkOrEthereumURL, options.projectId, options.projectSecret) break case "alchemy": provider = new AlchemyProvider(networkOrEthereumURL, options.apiKey) break case "cloudflare": - provider = new CloudflareProvider(networkOrEthereumURL, options.apiKey) + provider = new CloudflareProvider(networkOrEthereumURL) break case "etherscan": provider = new EtherscanProvider(networkOrEthereumURL, options.apiKey) break case "pocket": - provider = new PocketProvider(networkOrEthereumURL, options.apiKey) + provider = new PocketProvider(networkOrEthereumURL, options.applicationId, options.applicationSecret) break case "ankr": provider = new AnkrProvider(networkOrEthereumURL, options.apiKey) @@ -150,21 +147,22 @@ export default class SemaphoreEthers { async getGroup(groupId: string): Promise { checkParameter(groupId, "groupId", "string") - const [groupCreatedEvent] = await getEvents(this._contract, "GroupCreated", [groupId], this._options.startBlock) + const groupAdmin = await this._contract.getGroupAdmin(groupId) - if (!groupCreatedEvent) { + if (groupAdmin === ZeroAddress) { throw new Error(`Group '${groupId}' not found`) } const merkleTreeRoot = await this._contract.getMerkleTreeRoot(groupId) - const numberOfLeaves = await this._contract.getNumberOfMerkleTreeLeaves(groupId) + const merkleTreeDepth = await this._contract.getMerkleTreeDepth(groupId) + const merkleTreeSize = await this._contract.getMerkleTreeSize(groupId) const group: GroupResponse = { id: groupId, + admin: groupAdmin, merkleTree: { - depth: groupCreatedEvent.merkleTreeDepth.toString(), - zeroValue: groupCreatedEvent.zeroValue.toString(), - numberOfLeaves: numberOfLeaves.toNumber(), + depth: Number(merkleTreeDepth), + size: Number(merkleTreeSize), root: merkleTreeRoot.toString() } } @@ -172,28 +170,6 @@ export default class SemaphoreEthers { return group } - /** - * Returns a group admin. - * @param groupId Group id. - * @returns Group admin. - */ - async getGroupAdmin(groupId: string): Promise { - checkParameter(groupId, "groupId", "string") - - const groupAdminUpdatedEvents = await getEvents( - this._contract, - "GroupAdminUpdated", - [groupId], - this._options.startBlock - ) - - if (groupAdminUpdatedEvents.length === 0) { - throw new Error(`Group '${groupId}' not found`) - } - - return groupAdminUpdatedEvents[groupAdminUpdatedEvents.length - 1].newAdmin.toString() - } - /** * Returns a list of group members. * @param groupId Group id. @@ -202,13 +178,12 @@ export default class SemaphoreEthers { async getGroupMembers(groupId: string): Promise { checkParameter(groupId, "groupId", "string") - const [groupCreatedEvent] = await getEvents(this._contract, "GroupCreated", [groupId], this._options.startBlock) + const groupAdmin = await this._contract.getGroupAdmin(groupId) - if (!groupCreatedEvent) { + if (groupAdmin === ZeroAddress) { throw new Error(`Group '${groupId}' not found`) } - const zeroValue = groupCreatedEvent.zeroValue.toString() const memberRemovedEvents = await getEvents( this._contract, "MemberRemoved", @@ -221,59 +196,105 @@ export default class SemaphoreEthers { [groupId], this._options.startBlock ) - const groupUpdates = new Map() + const memberUpdatedEventsMap = new Map() - for (const { blockNumber, index, newIdentityCommitment } of memberUpdatedEvents) { - groupUpdates.set(index.toString(), [blockNumber, newIdentityCommitment.toString()]) + for (const [, index, , newIdentityCommitment, , blockNumber] of memberUpdatedEvents) { + memberUpdatedEventsMap.set(index.toString(), [blockNumber, newIdentityCommitment.toString()]) } - for (const { blockNumber, index } of memberRemovedEvents) { - const groupUpdate = groupUpdates.get(index.toString()) + for (const [, index, , , blockNumber] of memberRemovedEvents) { + const groupUpdate = memberUpdatedEventsMap.get(index.toString()) if (!groupUpdate || (groupUpdate && groupUpdate[0] < blockNumber)) { - groupUpdates.set(index.toString(), [blockNumber, zeroValue]) + memberUpdatedEventsMap.set(index.toString(), [blockNumber, "0"]) } } + const membersAddedEvents = await getEvents(this._contract, "MembersAdded", [groupId], this._options.startBlock) + + const membersAddedEventsMap = new Map() + + for (const [, startIndex, identityCommitments] of membersAddedEvents) { + membersAddedEventsMap.set( + startIndex.toString(), + identityCommitments.map((i: any) => i.toString()) + ) + } + const memberAddedEvents = await getEvents(this._contract, "MemberAdded", [groupId], this._options.startBlock) + const members: string[] = [] - for (const { index, identityCommitment } of memberAddedEvents) { - const groupUpdate = groupUpdates.get(index.toString()) - const member = groupUpdate ? groupUpdate[1].toString() : identityCommitment.toString() + const merkleTreeSize = await this._contract.getMerkleTreeSize(groupId) + + let i = 0 - members.push(member) + while (i < Number(merkleTreeSize)) { + const identityCommitments = membersAddedEventsMap.get(i.toString()) + + if (identityCommitments) { + members.push(...identityCommitments) + + i += identityCommitments.length + } else { + members.push(memberAddedEvents[i][2]) + + i += 1 + } + } + + for (let j = 0; j < members.length; j += 1) { + const groupUpdate = memberUpdatedEventsMap.get(j.toString()) + + if (groupUpdate) { + members[j] = groupUpdate[1].toString() + } } return members } /** - * Returns a list of group verified proofs. + * Returns a list of group validated proofs. * @param groupId Group id. - * @returns Group verified proofs. + * @returns Group validated proofs. */ - async getGroupVerifiedProofs(groupId: string): Promise { + async getGroupValidatedProofs(groupId: string): Promise { checkParameter(groupId, "groupId", "string") - const [groupCreatedEvent] = await getEvents(this._contract, "GroupCreated", [groupId], this._options.startBlock) + const groupAdmin = await this._contract.getGroupAdmin(groupId) - if (!groupCreatedEvent) { + if (groupAdmin === ZeroAddress) { throw new Error(`Group '${groupId}' not found`) } - const proofVerifiedEvents = await getEvents( + const proofValidatedEvents = await getEvents( this._contract, - "ProofVerified", + "ProofValidated", [groupId], this._options.startBlock ) - return proofVerifiedEvents.map((event) => ({ - signal: event.signal.toString(), - merkleTreeRoot: event.merkleTreeRoot.toString(), - externalNullifier: event.externalNullifier.toString(), - nullifierHash: event.nullifierHash.toString() + return proofValidatedEvents.map((event) => ({ + merkleTreeDepth: Number(event[1]), + merkleTreeRoot: event[2].toString(), + nullifier: event[3].toString(), + message: event[4].toString(), + scope: event[5].toString(), + points: event[6].map((p: any) => p.toString()) })) } + + /** + * Returns true if a member is part of group, and false otherwise. + * @param groupId Group id + * @param member Group member. + * @returns True if the member is part of the group, false otherwise. + */ + async isGroupMember(groupId: string, member: string): Promise { + checkParameter(groupId, "groupId", "string") + checkParameter(member, "member", "string") + + return this._contract.hasMember(groupId, member) + } } diff --git a/packages/data/src/getEvents.ts b/packages/data/src/getEvents.ts index 9197429ca..94ed86071 100644 --- a/packages/data/src/getEvents.ts +++ b/packages/data/src/getEvents.ts @@ -1,5 +1,5 @@ /* istanbul ignore file */ -import { Contract } from "@ethersproject/contracts" +import { Contract, EventLog } from "ethers/contract" /** * Returns the list of events of a contract with possible filters. @@ -14,9 +14,9 @@ export default async function getEvents( eventName: string, filterArgs: any[] = [], startBlock: number = 0 -): Promise { +): Promise { const filter = contract.filters[eventName](...filterArgs) - const events = await contract.queryFilter(filter, startBlock) + const events = (await contract.queryFilter(filter, startBlock)) as EventLog[] - return events.map(({ args, blockNumber }) => ({ ...args, blockNumber })) + return events.map(({ args, blockNumber }) => [...args, blockNumber]) } diff --git a/packages/data/src/getURL.ts b/packages/data/src/getURL.ts index 193ef6667..3ed218cad 100644 --- a/packages/data/src/getURL.ts +++ b/packages/data/src/getURL.ts @@ -8,12 +8,11 @@ import { SupportedNetwork } from "./types" export default function getURL(supportedNetwork: SupportedNetwork | string): string { switch (supportedNetwork) { case "sepolia": - case "goerli": case "mumbai": - case "optimism-goerli": - case "arbitrum-goerli": + case "optimism-sepolia": + case "arbitrum-sepolia": case "arbitrum": - return `https://api.studio.thegraph.com/query/14377/semaphore-${supportedNetwork}/v3.6.1` + return `https://api.studio.thegraph.com/query/14377/semaphore-${supportedNetwork}/v4.0.0-alpha.4` default: throw new TypeError(`Network '${supportedNetwork}' is not supported`) } diff --git a/packages/data/src/semaphoreABI.json b/packages/data/src/semaphoreABI.json index f41cc0998..191a49ecf 100644 --- a/packages/data/src/semaphoreABI.json +++ b/packages/data/src/semaphoreABI.json @@ -10,6 +10,26 @@ "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [], + "name": "LeafAlreadyExists", + "type": "error" + }, + { + "inputs": [], + "name": "LeafCannotBeZero", + "type": "error" + }, + { + "inputs": [], + "name": "LeafDoesNotExist", + "type": "error" + }, + { + "inputs": [], + "name": "LeafGreaterThanSnarkScalarField", + "type": "error" + }, { "inputs": [], "name": "Semaphore__CallerIsNotTheGroupAdmin", @@ -25,6 +45,16 @@ "name": "Semaphore__GroupDoesNotExist", "type": "error" }, + { + "inputs": [], + "name": "Semaphore__GroupHasNoMembers", + "type": "error" + }, + { + "inputs": [], + "name": "Semaphore__InvalidProof", + "type": "error" + }, { "inputs": [], "name": "Semaphore__MerkleTreeDepthIsNotSupported", @@ -42,7 +72,12 @@ }, { "inputs": [], - "name": "Semaphore__YouAreUsingTheSameNillifierTwice", + "name": "Semaphore__YouAreUsingTheSameNullifierTwice", + "type": "error" + }, + { + "inputs": [], + "name": "WrongSiblingNodes", "type": "error" }, { @@ -78,18 +113,6 @@ "internalType": "uint256", "name": "groupId", "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "merkleTreeDepth", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "zeroValue", - "type": "uint256" } ], "name": "GroupCreated", @@ -229,31 +252,74 @@ "type": "uint256" }, { - "indexed": true, + "indexed": false, + "internalType": "uint256", + "name": "startIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "identityCommitments", + "type": "uint256[]" + }, + { + "indexed": false, "internalType": "uint256", "name": "merkleTreeRoot", "type": "uint256" + } + ], + "name": "MembersAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "merkleTreeDepth", + "type": "uint256" }, { "indexed": true, "internalType": "uint256", - "name": "externalNullifier", + "name": "merkleTreeRoot", "type": "uint256" }, { "indexed": false, "internalType": "uint256", - "name": "nullifierHash", + "name": "nullifier", "type": "uint256" }, { "indexed": false, "internalType": "uint256", - "name": "signal", + "name": "message", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "scope", "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[8]", + "name": "points", + "type": "uint256[8]" } ], - "name": "ProofVerified", + "name": "ProofValidated", "type": "event" }, { @@ -299,11 +365,6 @@ "name": "groupId", "type": "uint256" }, - { - "internalType": "uint256", - "name": "merkleTreeDepth", - "type": "uint256" - }, { "internalType": "address", "name": "admin", @@ -327,11 +388,6 @@ "name": "groupId", "type": "uint256" }, - { - "internalType": "uint256", - "name": "merkleTreeDepth", - "type": "uint256" - }, { "internalType": "address", "name": "admin", @@ -343,6 +399,25 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + } + ], + "name": "getGroupAdmin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -389,7 +464,7 @@ "type": "uint256" } ], - "name": "getNumberOfMerkleTreeLeaves", + "name": "getMerkleTreeSize", "outputs": [ { "internalType": "uint256", @@ -411,13 +486,56 @@ "name": "groups", "outputs": [ { - "internalType": "address", - "name": "admin", - "type": "address" + "internalType": "uint256", + "name": "merkleTreeDuration", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" }, { "internalType": "uint256", - "name": "merkleTreeDuration", + "name": "identityCommitment", + "type": "uint256" + } + ], + "name": "hasMember", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "identityCommitment", + "type": "uint256" + } + ], + "name": "indexOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", "type": "uint256" } ], @@ -438,13 +556,8 @@ }, { "internalType": "uint256[]", - "name": "proofSiblings", + "name": "merkleProofSiblings", "type": "uint256[]" - }, - { - "internalType": "uint8[]", - "name": "proofPathIndices", - "type": "uint8[]" } ], "name": "removeMember", @@ -507,16 +620,61 @@ }, { "internalType": "uint256[]", - "name": "proofSiblings", + "name": "merkleProofSiblings", "type": "uint256[]" + } + ], + "name": "updateMember", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "groupId", + "type": "uint256" }, { - "internalType": "uint8[]", - "name": "proofPathIndices", - "type": "uint8[]" + "components": [ + { + "internalType": "uint256", + "name": "merkleTreeDepth", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "merkleTreeRoot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nullifier", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "message", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "scope", + "type": "uint256" + }, + { + "internalType": "uint256[8]", + "name": "points", + "type": "uint256[8]" + } + ], + "internalType": "struct ISemaphore.SemaphoreProof", + "name": "proof", + "type": "tuple" } ], - "name": "updateMember", + "name": "validateProof", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -542,34 +700,52 @@ "type": "uint256" }, { - "internalType": "uint256", - "name": "merkleTreeRoot", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "signal", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "nullifierHash", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "externalNullifier", - "type": "uint256" - }, - { - "internalType": "uint256[8]", + "components": [ + { + "internalType": "uint256", + "name": "merkleTreeDepth", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "merkleTreeRoot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nullifier", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "message", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "scope", + "type": "uint256" + }, + { + "internalType": "uint256[8]", + "name": "points", + "type": "uint256[8]" + } + ], + "internalType": "struct ISemaphore.SemaphoreProof", "name": "proof", - "type": "uint256[8]" + "type": "tuple" } ], "name": "verifyProof", - "outputs": [], - "stateMutability": "nonpayable", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", "type": "function" } ] diff --git a/packages/data/src/subgraph.ts b/packages/data/src/subgraph.ts index 68c14a405..1480501b1 100644 --- a/packages/data/src/subgraph.ts +++ b/packages/data/src/subgraph.ts @@ -60,10 +60,10 @@ export default class SemaphoreSubgraph { async getGroups(options: GroupOptions = {}): Promise { checkParameter(options, "options", "object") - const { members = false, verifiedProofs = false } = options + const { members = false, validatedProofs = false } = options checkParameter(members, "members", "boolean") - checkParameter(verifiedProofs, "verifiedProofs", "boolean") + checkParameter(validatedProofs, "validatedProofs", "boolean") let filtersQuery = "" @@ -102,8 +102,7 @@ export default class SemaphoreSubgraph { merkleTree { root depth - zeroValue - numberOfLeaves + size } admin ${ @@ -114,12 +113,14 @@ export default class SemaphoreSubgraph { : "" } ${ - verifiedProofs === true - ? `verifiedProofs(orderBy: timestamp) { - signal + validatedProofs === true + ? `validatedProofs(orderBy: timestamp) { + message merkleTreeRoot - externalNullifier - nullifierHash + merkleTreeDepth + scope + nullifier + points timestamp }` : "" @@ -150,10 +151,10 @@ export default class SemaphoreSubgraph { checkParameter(groupId, "groupId", "string") checkParameter(options, "options", "object") - const { members = false, verifiedProofs = false } = options + const { members = false, validatedProofs = false } = options checkParameter(members, "members", "boolean") - checkParameter(verifiedProofs, "verifiedProofs", "boolean") + checkParameter(validatedProofs, "validatedProofs", "boolean") const config: AxiosRequestConfig = { method: "post", @@ -164,8 +165,7 @@ export default class SemaphoreSubgraph { merkleTree { root depth - zeroValue - numberOfLeaves + size } admin ${ @@ -176,12 +176,14 @@ export default class SemaphoreSubgraph { : "" } ${ - verifiedProofs === true - ? `verifiedProofs(orderBy: timestamp) { - signal + validatedProofs === true + ? `validatedProofs(orderBy: timestamp) { + message merkleTreeRoot - externalNullifier - nullifierHash + merkleTreeDepth + scope + nullifier + points timestamp }` : "" @@ -200,6 +202,27 @@ export default class SemaphoreSubgraph { return groups[0] } + /** + * Returns a list of group members. + * @param groupId Group id. + * @returns Group members. + */ + async getGroupMembers(groupId: string): Promise { + const group = await this.getGroup(groupId, { members: true }) // parameters are checked inside getGroup + return group.members! + } + + /** + * Returns a list of validated proofs. + * @param groupId Group id. + * @returns Validated proofs. + */ + async getGroupValidatedProofs(groupId: string): Promise { + const group = await this.getGroup(groupId, { validatedProofs: true }) // parameters are checked inside getGroup + + return group.validatedProofs! + } + /** * Returns true if a member is part of group, and false otherwise. * @param groupId Group id diff --git a/packages/data/src/types/index.ts b/packages/data/src/types/index.ts index 15c07a851..019c252bc 100644 --- a/packages/data/src/types/index.ts +++ b/packages/data/src/types/index.ts @@ -1,27 +1,25 @@ export enum SupportedNetwork { SEPOLIA = "sepolia", - GOERLI = "goerli", MUMBAI = "mumbai", - OPTIMISM_GOERLI = "optimism-goerli", - ARBITRUM_GOERLI = "arbitrum-goerli", + OPTIMISM_SEPOLIA = "optimism-sepolia", + ARBITRUM_SEPOLIA = "arbitrum-sepolia", ARBITRUM = "arbitrum" } export type EthersNetwork = | "homestead" | "matic" - | "goerli" | "arbitrum" | "maticmum" | "mumbai" - | "arbitrum-goerli" + | "arbitrum-sepolia" | "optimism" - | "optimism-goerli" + | "optimism-sepolia" | "sepolia" export type GroupOptions = { members?: boolean - verifiedProofs?: boolean + validatedProofs?: boolean filters?: { admin?: string identityCommitment?: string @@ -36,16 +34,17 @@ export type GroupResponse = { merkleTree: { root: string depth: number - zeroValue: string - numberOfLeaves: number + size: number } admin?: string members?: string[] - verifiedProofs?: { - signal: string + validatedProofs?: { + message: string merkleTreeRoot: string - externalNullifier: string - nullifierHash: string + merkleTreeDepth: number + scope: string + nullifier: string + points: string[] timestamp?: string }[] } @@ -55,4 +54,8 @@ export type EthersOptions = { startBlock?: number provider?: "etherscan" | "infura" | "alchemy" | "cloudflare" | "pocket" | "ankr" apiKey?: string + projectId?: string // Infura + projectSecret?: string // Infura + applicationId?: string // Pocket + applicationSecret?: string // Pocket } diff --git a/packages/data/src/ethers.test.ts b/packages/data/tests/ethers.test.ts similarity index 57% rename from packages/data/src/ethers.test.ts rename to packages/data/tests/ethers.test.ts index 1b40fdacf..d4626c930 100644 --- a/packages/data/src/ethers.test.ts +++ b/packages/data/tests/ethers.test.ts @@ -1,37 +1,40 @@ -import SemaphoreEthers from "./ethers" -import getEvents from "./getEvents" +/* eslint-disable no-sparse-arrays */ +import { ZeroAddress } from "ethers/constants" +import { Contract } from "ethers/contract" +import SemaphoreEthers from "../src/ethers" +import getEvents from "../src/getEvents" -jest.mock("./getEvents", () => ({ +jest.mock("../src/getEvents", () => ({ __esModule: true, default: jest.fn() })) -jest.mock("@ethersproject/contracts", () => ({ +jest.mock("ethers/contract", () => ({ __esModule: true, + ...jest.requireActual("ethers/contract"), Contract: jest.fn( () => ({ getMerkleTreeRoot: () => "222", - getNumberOfMerkleTreeLeaves: () => ({ - toNumber: () => 2 - }) + getMerkleTreeDepth: () => BigInt(3), + getMerkleTreeSize: () => BigInt(8), + getGroupAdmin: () => "0xA9C2B639a28cDa8b59C4377e980F75A93dD8605F" } as any) ) })) const getEventsMocked = getEvents as jest.MockedFunction +const ContractMocked = Contract as jest.MockedClass describe("SemaphoreEthers", () => { - let semaphore: SemaphoreEthers - describe("# SemaphoreEthers", () => { it("Should instantiate a SemaphoreEthers object with different networks", () => { - semaphore = new SemaphoreEthers() + const semaphore = new SemaphoreEthers() const semaphore1 = new SemaphoreEthers("arbitrum") const semaphore2 = new SemaphoreEthers("mumbai") - const semaphore3 = new SemaphoreEthers("optimism-goerli") - const semaphore4 = new SemaphoreEthers("arbitrum-goerli") - const semaphore5 = new SemaphoreEthers("arbitrum-goerli", { + const semaphore3 = new SemaphoreEthers("optimism-sepolia") + const semaphore4 = new SemaphoreEthers("arbitrum-sepolia") + const semaphore5 = new SemaphoreEthers("arbitrum-sepolia", { address: "0x0000000000000000000000000000000000000000", startBlock: 0 }) @@ -44,8 +47,8 @@ describe("SemaphoreEthers", () => { expect(semaphore.contract).toBeInstanceOf(Object) expect(semaphore1.network).toBe("arbitrum") expect(semaphore2.network).toBe("maticmum") - expect(semaphore3.network).toBe("optimism-goerli") - expect(semaphore4.network).toBe("arbitrum-goerli") + expect(semaphore3.network).toBe("optimism-sepolia") + expect(semaphore4.network).toBe("arbitrum-sepolia") expect(semaphore5.options.address).toContain("0x000000") expect(semaphore6.network).toBe("homestead") expect(semaphore6.options.startBlock).toBe(0) @@ -104,7 +107,7 @@ describe("SemaphoreEthers", () => { it("Should throw an error if the provider is not supported", () => { const fun = () => - new SemaphoreEthers("goerli", { + new SemaphoreEthers("sepolia", { provider: "hello" as any }) @@ -114,6 +117,8 @@ describe("SemaphoreEthers", () => { describe("# getGroupIds", () => { it("Should return all the existing groups", async () => { + const semaphore = new SemaphoreEthers() + getEventsMocked.mockReturnValueOnce(Promise.resolve([["32"], ["42"]])) const groupIds = await semaphore.getGroupIds() @@ -124,50 +129,23 @@ describe("SemaphoreEthers", () => { describe("# getGroup", () => { it("Should return a specific group", async () => { - getEventsMocked.mockReturnValueOnce( - Promise.resolve([ - { - merkleTreeDepth: "20", - zeroValue: "111" - } - ]) - ) + const semaphore = new SemaphoreEthers() const group = await semaphore.getGroup("42") - expect(group.merkleTree.depth).toBe("20") + expect(group.merkleTree.depth).toBe(3) expect(group.merkleTree.root).toBe("222") - expect(group.merkleTree.zeroValue).toContain("111") + expect(group.merkleTree.size).toBe(8) }) it("Should throw an error if the group does not exist", async () => { - getEventsMocked.mockReturnValueOnce(Promise.resolve([])) - - const fun = () => semaphore.getGroup("666") - - await expect(fun).rejects.toThrow("Group '666' not found") - }) - }) - - describe("# getGroupAdmin", () => { - it("Should return a group admin", async () => { - getEventsMocked.mockReturnValueOnce( - Promise.resolve([ - { - newAdmin: "0xA9C2B639a28cDa8b59C4377e980F75A93dD8605F" - } - ]) - ) + ContractMocked.mockReturnValueOnce({ + getGroupAdmin: () => ZeroAddress + } as any) - const admin = await semaphore.getGroupAdmin("42") + const semaphore = new SemaphoreEthers() - expect(admin).toBe("0xA9C2B639a28cDa8b59C4377e980F75A93dD8605F") - }) - - it("Should throw an error if the group does not exist", async () => { - getEventsMocked.mockReturnValueOnce(Promise.resolve([])) - - const fun = () => semaphore.getGroupAdmin("666") + const fun = () => semaphore.getGroup("666") await expect(fun).rejects.toThrow("Group '666' not found") }) @@ -175,70 +153,33 @@ describe("SemaphoreEthers", () => { describe("# getGroupMembers", () => { it("Should return a list of group members", async () => { + const semaphore = new SemaphoreEthers() + getEventsMocked.mockReturnValueOnce( Promise.resolve([ - { - merkleTreeDepth: "20", - zeroValue: "0" - } + [, "0", , , 4], + [, "2", , , 5], + [, "5", , , 8] ]) ) getEventsMocked.mockReturnValueOnce( Promise.resolve([ - { - index: "0", - merkleTreeRoot: "223", - blockNumber: 3 - }, - { - index: "2", - merkleTreeRoot: "224", - blockNumber: 4 - } + [, "1", , "113", , 3], + [, "2", , "114", , 3] ]) ) getEventsMocked.mockReturnValueOnce( Promise.resolve([ - { - index: "1", - newIdentityCommitment: "113", - merkleTreeRoot: "225", - blockNumber: 3 - }, - { - index: "2", - newIdentityCommitment: "114", - merkleTreeRoot: "226", - blockNumber: 3 - } + [, "4", ["209", "211"]], + [, "6", ["310", "312"]] ]) ) getEventsMocked.mockReturnValueOnce( Promise.resolve([ - { - index: "0", - identityCommitment: "110", - merkleTreeRoot: "220", - blockNumber: 0 - }, - { - index: "1", - identityCommitment: "111", - merkleTreeRoot: "221", - blockNumber: 1 - }, - { - index: "2", - identityCommitment: "112", - merkleTreeRoot: "222", - blockNumber: 2 - }, - { - index: "3", - identityCommitment: "113", - merkleTreeRoot: "223", - blockNumber: 3 - } + [, , "110"], + [, , "111"], + [, , "112"], + [, , "113"] ]) ) @@ -247,10 +188,17 @@ describe("SemaphoreEthers", () => { expect(members[0]).toBe("0") expect(members[1]).toBe("113") expect(members[2]).toBe("0") + expect(members[4]).toBe("209") + expect(members[5]).toBe("0") + expect(members[7]).toBe("312") }) it("Should throw an error if the group does not exist", async () => { - getEventsMocked.mockReturnValueOnce(Promise.resolve([])) + ContractMocked.mockReturnValueOnce({ + getGroupAdmin: () => ZeroAddress + } as any) + + const semaphore = new SemaphoreEthers() const fun = () => semaphore.getGroupMembers("666") @@ -260,34 +208,35 @@ describe("SemaphoreEthers", () => { describe("# getGroupVerifiedProofs", () => { it("Should return a list of group verified proofs", async () => { + const semaphore = new SemaphoreEthers() + getEventsMocked.mockReturnValueOnce( Promise.resolve([ - { - merkleTreeDepth: "20", - zeroValue: "0" - } - ]) - ) - getEventsMocked.mockReturnValueOnce( - Promise.resolve([ - { - signal: "111", - merkleTreeRoot: "112", - externalNullifier: "113", - nullifierHash: "114" - } + [ + , + "112", + "112", + "114", + "111", + "113", + ["12312", "12312", "12312", "12312", "12312", "12312", "12312", "12312"] + ] ]) ) - const [verifiedProof] = await semaphore.getGroupVerifiedProofs("42") + const [verifiedProof] = await semaphore.getGroupValidatedProofs("42") - expect(verifiedProof.signal).toContain("111") + expect(verifiedProof.message).toContain("111") }) it("Should throw an error if the group does not exist", async () => { - getEventsMocked.mockReturnValueOnce(Promise.resolve([])) + ContractMocked.mockReturnValueOnce({ + getGroupAdmin: () => ZeroAddress + } as any) + + const semaphore = new SemaphoreEthers() - const fun = () => semaphore.getGroupVerifiedProofs("666") + const fun = () => semaphore.getGroupValidatedProofs("666") await expect(fun).rejects.toThrow("Group '666' not found") }) diff --git a/packages/data/src/index.test.ts b/packages/data/tests/index.test.ts similarity index 70% rename from packages/data/src/index.test.ts rename to packages/data/tests/index.test.ts index 8320b0c98..3eb3f609d 100644 --- a/packages/data/src/index.test.ts +++ b/packages/data/tests/index.test.ts @@ -1,11 +1,11 @@ -import getSupportedNetworks from "./getSupportedNetworks" +import getSupportedNetworks from "../src/getSupportedNetworks" describe("Data", () => { describe("# getSupportedNetworks", () => { it("Should return a list of supported networks", () => { const supportedNetworks = getSupportedNetworks() - expect(supportedNetworks).toHaveLength(6) + expect(supportedNetworks).toHaveLength(5) expect(supportedNetworks).toContain("sepolia") }) }) diff --git a/packages/data/src/subgraph.test.ts b/packages/data/tests/subgraph.test.ts similarity index 58% rename from packages/data/src/subgraph.test.ts rename to packages/data/tests/subgraph.test.ts index 4402d3343..98c433f9e 100644 --- a/packages/data/src/subgraph.test.ts +++ b/packages/data/tests/subgraph.test.ts @@ -1,7 +1,7 @@ -import request from "./request" -import SemaphoreSubgraph from "./subgraph" +import request from "../src/request" +import SemaphoreSubgraph from "../src/subgraph" -jest.mock("./request", () => ({ +jest.mock("../src/request", () => ({ __esModule: true, default: jest.fn() })) @@ -72,8 +72,7 @@ describe("SemaphoreSubgraph", () => { id: "1", merkleTree: { depth: 20, - zeroValue: 0, - numberOfLeaves: 2, + size: 2, root: "2" }, admin: "0x7bcd6f009471e9974a77086a69289d16eadba286" @@ -90,8 +89,7 @@ describe("SemaphoreSubgraph", () => { id: "1", merkleTree: { depth: 20, - zeroValue: 0, - numberOfLeaves: 2, + size: 2, root: "2" }, admin: "0x7bcd6f009471e9974a77086a69289d16eadba286" @@ -112,8 +110,7 @@ describe("SemaphoreSubgraph", () => { id: "1", merkleTree: { depth: 20, - zeroValue: 0, - numberOfLeaves: 2, + size: 2, root: "2" }, admin: "0x7bcd6f009471e9974a77086a69289d16eadba286", @@ -125,19 +122,23 @@ describe("SemaphoreSubgraph", () => { identityCommitment: "2" } ], - verifiedProofs: [ + validatedProofs: [ { - signal: "0x3243b", - merkleTree: "1332132", - externalNullifier: "14324", - nullifierHash: "442342", + message: "0x3243b", + merkleTreeRoot: "1332132", + merkleTreeDepth: "32", + scope: "14324", + nullifier: "442342", + points: ["442342"], timestamp: "1657306917" }, { - signal: "0x5233a", - merkleTree: "1332132", - externalNullifier: "14324", - nullifierHash: "442342", + message: "0x5233a", + merkleTreeRoot: "1332132", + merkleTreeDepth: "32", + scope: "14324", + nullifier: "442342", + points: ["442342"], timestamp: "1657306923" } ] @@ -148,7 +149,7 @@ describe("SemaphoreSubgraph", () => { const expectedValue = await semaphore.getGroups({ members: true, - verifiedProofs: true + validatedProofs: true }) expect(expectedValue).toBeDefined() @@ -157,25 +158,28 @@ describe("SemaphoreSubgraph", () => { id: "1", merkleTree: { depth: 20, - zeroValue: 0, - numberOfLeaves: 2, + size: 2, root: "2" }, admin: "0x7bcd6f009471e9974a77086a69289d16eadba286", members: ["1", "2"], - verifiedProofs: [ + validatedProofs: [ { - signal: "0x3243b", - merkleTree: "1332132", - externalNullifier: "14324", - nullifierHash: "442342", + message: "0x3243b", + merkleTreeRoot: "1332132", + merkleTreeDepth: "32", + scope: "14324", + nullifier: "442342", + points: ["442342"], timestamp: "1657306917" }, { - signal: "0x5233a", - merkleTree: "1332132", - externalNullifier: "14324", - nullifierHash: "442342", + message: "0x5233a", + merkleTreeRoot: "1332132", + merkleTreeDepth: "32", + scope: "14324", + nullifier: "442342", + points: ["442342"], timestamp: "1657306923" } ] @@ -190,8 +194,7 @@ describe("SemaphoreSubgraph", () => { id: "1", merkleTree: { depth: 20, - zeroValue: 0, - numberOfLeaves: 2, + size: 2, root: "2" }, admin: "0x7bcd6f009471e9974a77086a69289d16eadba286" @@ -213,8 +216,7 @@ describe("SemaphoreSubgraph", () => { id: "1", merkleTree: { depth: 20, - zeroValue: 0, - numberOfLeaves: 2, + size: 2, root: "2" }, admin: "0x7bcd6f009471e9974a77086a69289d16eadba286" @@ -231,8 +233,7 @@ describe("SemaphoreSubgraph", () => { id: "1", merkleTree: { depth: 20, - zeroValue: 0, - numberOfLeaves: 2, + size: 2, root: "2" }, admin: "0x7bcd6f009471e9974a77086a69289d16eadba286" @@ -248,8 +249,7 @@ describe("SemaphoreSubgraph", () => { id: "1", merkleTree: { depth: 20, - zeroValue: 0, - numberOfLeaves: 2, + size: 2, root: "2" }, admin: "0x7bcd6f009471e9974a77086a69289d16eadba286" @@ -270,8 +270,7 @@ describe("SemaphoreSubgraph", () => { id: "1", merkleTree: { depth: 20, - zeroValue: 0, - numberOfLeaves: 2, + size: 2, root: "2" }, admin: "0x7bcd6f009471e9974a77086a69289d16eadba286", @@ -283,19 +282,23 @@ describe("SemaphoreSubgraph", () => { identityCommitment: "2" } ], - verifiedProofs: [ + validatedProofs: [ { - signal: "0x3243b", - merkleTree: "1332132", - externalNullifier: "14324", - nullifierHash: "442342", + message: "0x3243b", + merkleTreeRoot: "1332132", + merkleTreeDepth: "32", + scope: "14324", + nullifier: "442342", + points: ["442342"], timestamp: "1657306917" }, { - signal: "0x5233a", - merkleTree: "1332132", - externalNullifier: "14324", - nullifierHash: "442342", + message: "0x5233a", + merkleTreeRoot: "1332132", + merkleTreeDepth: "32", + scope: "14324", + nullifier: "442342", + points: ["442342"], timestamp: "1657306923" } ] @@ -306,7 +309,7 @@ describe("SemaphoreSubgraph", () => { const expectedValue = await semaphore.getGroup("1", { members: true, - verifiedProofs: true + validatedProofs: true }) expect(expectedValue).toBeDefined() @@ -314,25 +317,28 @@ describe("SemaphoreSubgraph", () => { id: "1", merkleTree: { depth: 20, - zeroValue: 0, - numberOfLeaves: 2, + size: 2, root: "2" }, admin: "0x7bcd6f009471e9974a77086a69289d16eadba286", members: ["1", "2"], - verifiedProofs: [ + validatedProofs: [ { - signal: "0x3243b", - merkleTree: "1332132", - externalNullifier: "14324", - nullifierHash: "442342", + message: "0x3243b", + merkleTreeRoot: "1332132", + merkleTreeDepth: "32", + scope: "14324", + nullifier: "442342", + points: ["442342"], timestamp: "1657306917" }, { - signal: "0x5233a", - merkleTree: "1332132", - externalNullifier: "14324", - nullifierHash: "442342", + message: "0x5233a", + merkleTreeRoot: "1332132", + merkleTreeDepth: "32", + scope: "14324", + nullifier: "442342", + points: ["442342"], timestamp: "1657306923" } ] @@ -340,6 +346,113 @@ describe("SemaphoreSubgraph", () => { }) }) + describe("# getGroupMembers", () => { + it("Should return a list of group members", async () => { + requestMocked.mockImplementationOnce(() => + Promise.resolve({ + groups: [ + { + id: "1", + merkleTree: { + depth: 20, + size: 2, + root: "2" + }, + admin: "0x7bcd6f009471e9974a77086a69289d16eadba286", + members: [ + { + identityCommitment: "20" + }, + { + identityCommitment: "17" + } + ] + } + ] + }) + ) + + const expectedValue = await semaphore.getGroupMembers("1") + + expect(expectedValue).toBeDefined() + expect(Array.isArray(expectedValue)).toBeTruthy() + expect(expectedValue[0]).toBe("20") + expect(expectedValue[1]).toBe("17") + }) + + it("Should throw an error if the groupId parameter type is wrong", async () => { + const fun = () => semaphore.getGroupMembers(1 as any) + await expect(fun).rejects.toThrow("Parameter 'groupId' is not a string") + }) + }) + + describe("# getGroupValidatedProofs", () => { + it("Should return a list of group validated proofs", async () => { + requestMocked.mockImplementationOnce(() => + Promise.resolve({ + groups: [ + { + id: "1", + merkleTree: { + depth: 20, + size: 2, + root: "2" + }, + admin: "0x7bcd6f009471e9974a77086a69289d16eadba286", + validatedProofs: [ + { + message: "0x3243b", + merkleTreeRoot: "1332132", + merkleTreeDepth: "32", + scope: "14324", + nullifier: "442342", + points: ["442342"], + timestamp: "1657306917" + }, + { + message: "0x5233a", + merkleTreeRoot: "1332132", + merkleTreeDepth: "32", + scope: "14324", + nullifier: "442342", + points: ["442342"], + timestamp: "1657306923" + } + ] + } + ] + }) + ) + const expectedValue = await semaphore.getGroupValidatedProofs("1") + + expect(expectedValue).toBeDefined() + expect(Array.isArray(expectedValue)).toBeTruthy() + expect(expectedValue[0]).toEqual({ + message: "0x3243b", + merkleTreeRoot: "1332132", + merkleTreeDepth: "32", + scope: "14324", + nullifier: "442342", + points: ["442342"], + timestamp: "1657306917" + }) + expect(expectedValue[1]).toEqual({ + message: "0x5233a", + merkleTreeRoot: "1332132", + merkleTreeDepth: "32", + scope: "14324", + nullifier: "442342", + points: ["442342"], + timestamp: "1657306923" + }) + }) + + it("Should throw an error if the groupId parameter type is wrong", async () => { + const fun = () => semaphore.getGroupValidatedProofs(1 as any) + await expect(fun).rejects.toThrow("Parameter 'groupId' is not a string") + }) + }) + describe("# isGroupMember", () => { it("Should throw an error if the member parameter type is wrong", async () => { const fun = () => semaphore.isGroupMember("1", 1 as any) diff --git a/packages/data/src/utils.test.ts b/packages/data/tests/utils.test.ts similarity index 84% rename from packages/data/src/utils.test.ts rename to packages/data/tests/utils.test.ts index 0b89e3859..de1bee180 100644 --- a/packages/data/src/utils.test.ts +++ b/packages/data/tests/utils.test.ts @@ -1,4 +1,4 @@ -import { jsDateToGraphqlDate } from "./utils" +import { jsDateToGraphqlDate } from "../src/utils" describe("Utils", () => { describe("# jsDateToGraphqlDate", () => { diff --git a/packages/data/tsconfig.json b/packages/data/tsconfig.json index dfaf0c1ad..71510a096 100644 --- a/packages/data/tsconfig.json +++ b/packages/data/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "../../tsconfig.json", - "include": ["src", "rollup.config.ts"] + "include": ["src", "tests", "rollup.config.ts"] } diff --git a/packages/group/LICENSE b/packages/group/LICENSE index bdb9b1e08..8ef16f7a5 100644 --- a/packages/group/LICENSE +++ b/packages/group/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Ethereum Foundation +Copyright (c) 2024 Ethereum Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/group/README.md b/packages/group/README.md index 9e3b6373e..0511af259 100644 --- a/packages/group/README.md +++ b/packages/group/README.md @@ -10,7 +10,7 @@ - Github license + NPM license NPM version @@ -18,7 +18,7 @@ Downloads - + Documentation typedoc @@ -49,8 +49,8 @@
-| This library is an abstraction of [`@zk-kit/incremental-merkle-tree`](https://github.com/privacy-scaling-explorations/zk-kit/tree/main/packages/incremental-merkle-tree). The main goal is to make it easier to create offchain groups, which are also used to generate Semaphore proofs. Semaphore groups are actually incremental Merkle trees, and the group members are tree leaves. Since the Merkle tree implementation we are using is a binary tree, the maximum number of members of a group is equal to `2^treeDepth`. | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| This library is an abstraction of the LeanIMT data structure (part of [`@zk-kit/imt`](https://github.com/privacy-scaling-explorations/zk-kit/tree/main/packages/imt)). The main goal is to make it easier to create offchain groups, which are also used to generate Semaphore proofs. Semaphore groups are actually Merkle trees, and the group members are tree leaves. | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ## 🛠 Install @@ -70,54 +70,133 @@ yarn add @semaphore-protocol/group ## 📜 Usage -\# **new Group**(groupId: _Member_, treeDepth = 20): _Group_ +For more information on the functions provided by `@semaphore-protocol/group`, please refer to the [TypeDoc documentation](https://js.semaphore.pse.dev/modules/_semaphore_protocol_group). + +\# **new Group**(members: _BigNumberish[]_ = []): _Group_ ```typescript import { Group } from "@semaphore-protocol/group" import { Identity } from "@semaphore-protocol/identity" -// Group with max 1048576 members (20^²). -const group1 = new Group(1) - -// Group with max 65536 members (16^²). -const group2 = new Group(1, 16) +const group1 = new Group() -// Group with max 16777216 members (24^²). -const group3 = new Group(1, 24) - -// Group with a list of predefined members. const identity1 = new Identity() const identity2 = new Identity() -const identity3 = new Identity() -const group3 = new Group(1, 16, [identity1.commitment, identity2.commitment, identity3.commitment]) +const group2 = new Group([identity1.commitment, identity2.commitment]) ``` -\# **addMember**(identityCommitment: _Member_) +\# **addMember**(member: _BigNumberish_) ```typescript +import { Group } from "@semaphore-protocol/group" import { Identity } from "@semaphore-protocol/identity" -const identity = new Identity() -const commitment = identity.generateCommitment() +const group = new Group() + +const { commitment } = new Identity() group.addMember(commitment) + +// "12989101133047504182892154686643420754368236204022364847543591045056549053997" +console.log(group.members[0]) +``` + +\# **addMembers**(members: _BigNumberish[]_) + +```typescript +import { Group } from "@semaphore-protocol/group" +import { Identity } from "@semaphore-protocol/identity" + +const group = new Group() + +const identity1 = new Identity() +const identity2 = new Identity() + +group.addMembers([identity1.commitment, identity2.commitment]) +``` + +\# **updateMember**(index: _number_, member: _BigNumberish_) + +```typescript +import { Group } from "@semaphore-protocol/group" + +const group = new Group([1, 3]) + +group.updateMember(0, 2) + +console.log(group.members[0]) // "2" ``` \# **removeMember**(index: _number_) ```typescript +import { Group } from "@semaphore-protocol/group" + +const group = new Group([1, 3]) + group.removeMember(0) + +console.log(group.members[0]) // "0" ``` -\# **indexOf**(member: _Member_): _number_ +\# **indexOf**(member: _BigNumberish_): _number_ ```typescript -group.indexOf(commitment) // 0 +import { Group } from "@semaphore-protocol/group" + +const group = new Group([1]) + +const index = group.indexOf(1) + +console.log(index) // 0 ``` \# **generateMerkleProof**(index: _number_): _MerkleProof_ ```typescript +import { Group } from "@semaphore-protocol/group" + +const group = new Group([1, 3]) + const proof = group.generateMerkleProof(0) + +console.log(proof) +/* +{ + index: 0, + leaf: '1', + root: '21106761926285267690763443010820487107972411248208546226053195422384279971821', + siblings: [ '3' ] +} +*/ +``` + +\# **export**(): _string_ + +```typescript +import { Group } from "@semaphore-protocol/group" + +const group = new Group([1, 2, 3]) + +const exportedGroup = group.export() + +console.log(exportedGroup) +/* +[["1","2","3"],["7853200120776062878684798364095072458815029376092732009249414926327459813530","3"],["13816780880028945690020260331303642730075999758909899334839547418969502592169"]] +*/ +``` + +\# **import**(exportedGroup: _string_): _Group_ + +```typescript +import { Group } from "@semaphore-protocol/group" + +const group1 = new Group([1, 2, 3]) + +const exportedGroup = group.export() + +const group2 = Group.import(exportedGroup) + +assert(group1.root === group2.root) ``` diff --git a/packages/group/package.json b/packages/group/package.json index 2511a7ebd..2b60b39dc 100644 --- a/packages/group/package.json +++ b/packages/group/package.json @@ -1,15 +1,16 @@ { "name": "@semaphore-protocol/group", - "version": "3.15.2", + "version": "4.0.0-alpha.8", "description": "A library to create and manage Semaphore groups.", + "type": "module", "license": "MIT", - "main": "dist/index.node.js", + "main": "dist/index.js", + "types": "dist/types/index.d.ts", "exports": { - "import": "./dist/index.mjs", - "require": "./dist/index.node.js", - "types": "./dist/types/index.d.ts" + "types": "./dist/types/index.d.ts", + "require": "./dist/index.cjs", + "default": "./dist/index.js" }, - "types": "dist/types/index.d.ts", "files": [ "dist/", "src/", @@ -22,7 +23,6 @@ "url": "https://github.com/semaphore-protocol/semaphore.git/issues" }, "scripts": { - "build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript", "build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript", "prepublishOnly": "yarn build" }, @@ -30,16 +30,13 @@ "access": "public" }, "devDependencies": { - "@rollup/plugin-commonjs": "^24.0.1", - "@rollup/plugin-node-resolve": "^15.0.1", + "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-node-resolve": "^15.2.3", "poseidon-lite": "^0.2.0", "rollup-plugin-cleanup": "^3.2.1", - "rollup-plugin-typescript2": "^0.31.2" + "rollup-plugin-typescript2": "^0.36.0" }, "dependencies": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@zk-kit/incremental-merkle-tree": "1.1.0" + "@zk-kit/imt": "^2.0.0-beta.1" } } diff --git a/packages/group/rollup.config.ts b/packages/group/rollup.config.ts index 242048859..b11d2bac1 100644 --- a/packages/group/rollup.config.ts +++ b/packages/group/rollup.config.ts @@ -9,7 +9,7 @@ const banner = `/** * @module ${pkg.name} * @version ${pkg.version} * @file ${pkg.description} - * @copyright Ethereum Foundation 2022 + * @copyright Ethereum Foundation 2024 * @license ${pkg.license} * @see [Github]{@link ${pkg.homepage}} */` @@ -18,7 +18,7 @@ export default { input: "src/index.ts", output: [ { file: pkg.exports.require, format: "cjs", banner, exports: "auto" }, - { file: pkg.exports.import, format: "es", banner } + { file: pkg.exports.default, format: "es", banner } ], external: Object.keys(pkg.dependencies), plugins: [ diff --git a/packages/group/src/group.test.ts b/packages/group/src/group.test.ts deleted file mode 100644 index 0910a3ad5..000000000 --- a/packages/group/src/group.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -import Group from "./group" -import hash from "./hash" - -describe("Group", () => { - describe("# Group", () => { - it("Should create a group", () => { - const group = new Group(1) - - expect(group.id).toBe(1) - expect(group.root.toString()).toContain("103543") - expect(group.depth).toBe(20) - expect(group.zeroValue).toBe(hash(1)) - expect(group.members).toHaveLength(0) - }) - - it("Should not create a group with a wrong tree depth", () => { - const fun = () => new Group(1, 33) - - expect(fun).toThrow("The tree depth must be between 16 and 32") - }) - - it("Should create a group with a different tree depth", () => { - const group = new Group(1, 32) - - expect(group.root.toString()).toContain("460373") - expect(group.depth).toBe(32) - expect(group.zeroValue).toBe(hash(1)) - expect(group.members).toHaveLength(0) - }) - - it("Should create a group with a list of members", () => { - const group = new Group(2, 20, [1, 2, 3]) - - const group2 = new Group(2, 20) - - group2.addMember(1) - group2.addMember(2) - group2.addMember(3) - - expect(group.root.toString()).toContain(group2.root.toString()) - expect(group.depth).toBe(20) - expect(group.zeroValue).toBe(hash(2)) - expect(group.members).toHaveLength(3) - }) - }) - - describe("# addMember", () => { - it("Should add a member to a group", () => { - const group = new Group(1) - - group.addMember(BigInt(3)) - - expect(group.members).toHaveLength(1) - }) - }) - - describe("# addMembers", () => { - it("Should add many members to a group", () => { - const group = new Group(1) - - group.addMembers([BigInt(1), BigInt(3)]) - - expect(group.members).toHaveLength(2) - }) - }) - - describe("# indexOf", () => { - it("Should return the index of a member in a group", () => { - const group = new Group(1) - group.addMembers([BigInt(1), BigInt(3)]) - - const index = group.indexOf(BigInt(3)) - - expect(index).toBe(1) - }) - }) - - describe("# updateMember", () => { - it("Should update a member in a group", () => { - const group = new Group(1) - group.addMembers([BigInt(1), BigInt(3)]) - - group.updateMember(0, BigInt(1)) - - expect(group.members).toHaveLength(2) - expect(group.members[0]).toBe(BigInt(1)) - }) - }) - - describe("# removeMember", () => { - it("Should remove a member from a group", () => { - const group = new Group(1) - group.addMembers([BigInt(1), BigInt(3)]) - - group.removeMember(0) - - expect(group.members).toHaveLength(2) - expect(group.members[0]).toBe(group.zeroValue) - }) - }) - - describe("# generateMerkleProof", () => { - it("Should generate a proof of membership", () => { - const group = new Group(1) - group.addMembers([BigInt(1), BigInt(3)]) - - const proof = group.generateMerkleProof(0) - - expect(proof.leaf).toBe(BigInt(1)) - }) - }) -}) diff --git a/packages/group/src/group.ts b/packages/group/src/group.ts index 75534db2b..24c43a88b 100644 --- a/packages/group/src/group.ts +++ b/packages/group/src/group.ts @@ -1,94 +1,86 @@ -import { IncrementalMerkleTree, MerkleProof } from "@zk-kit/incremental-merkle-tree" +import { LeanIMT } from "@zk-kit/imt" import { poseidon2 } from "poseidon-lite/poseidon2" -import hash from "./hash" -import { BigNumberish } from "./types" - +import { BigNumberish, MerkleProof } from "./types" + +/** + * The Semaphore group is a {@link https://zkkit.pse.dev/classes/_zk_kit_imt.LeanIMT.html | LeanIMT} + * (Lean Incremental Merkle Tree), i.e. an optimized version of the incremental binary Merkle tree + * used by Semaphore V3. The new tree does not use zero hashes, and its depth is dynamic. + * The members of a Semaphore group, or the leaves of a tree, are the identity commitments. + * Thanks to the properties of Merkle trees, it can be efficiently demonstrated that a group + * member belongs to the group. + * This class supports operations such as member addition, update, removal and Merkle proof + * generation and verification. Groups can also be exported or imported. + */ export default class Group { - private _id: BigNumberish - - merkleTree: IncrementalMerkleTree + // The {@link https://zkkit.pse.dev/classes/_zk_kit_imt.LeanIMT.html | LeanIMT} instance. + public leanIMT: LeanIMT /** - * Initializes the group with the group id and the tree depth. - * @param id Group identifier. - * @param treeDepth Tree depth. - * @param members List of group members. + * Creates a new instance of the Group. Optionally, a list of identity commitments can + * be passed as a parameter. Adding members in chunks is more efficient than adding + * them one by one with the `addMember` function. + * @param members A list of identity commitments. */ - constructor(id: BigNumberish, treeDepth = 20, members: BigNumberish[] = []) { - if (treeDepth < 16 || treeDepth > 32) { - throw new Error("The tree depth must be between 16 and 32") - } - - this._id = id - this.merkleTree = new IncrementalMerkleTree(poseidon2, treeDepth, hash(id), 2, members.map(BigInt)) - } - - /** - * Returns the id of the group. - * @returns Group id. - */ - get id(): BigNumberish { - return this._id + constructor(members: BigNumberish[] = []) { + this.leanIMT = new LeanIMT((a, b) => poseidon2([a, b]), members.map(BigInt)) } /** * Returns the root hash of the tree. - * @returns Root hash. + * @returns The root hash as a string. */ - get root(): BigNumberish { - return this.merkleTree.root + public get root(): string { + return this.leanIMT.root ? this.leanIMT.root.toString() : "0" } /** * Returns the depth of the tree. - * @returns Tree depth. + * @returns The tree depth as a number. */ - get depth(): number { - return this.merkleTree.depth + public get depth(): number { + return this.leanIMT.depth } /** - * Returns the zero value of the tree. - * @returns Tree zero value. + * Returns the size of the tree (i.e. number of leaves). + * @returns The tree size as a number. */ - get zeroValue(): BigNumberish { - return this.merkleTree.zeroes[0] + public get size(): number { + return this.leanIMT.size } /** * Returns the members (i.e. identity commitments) of the group. - * @returns List of members. + * @returns The list of members of the group. */ - get members(): BigNumberish[] { - return this.merkleTree.leaves + public get members(): string[] { + return this.leanIMT.leaves.map(String) } /** * Returns the index of a member. If the member does not exist it returns -1. - * @param member Group member. - * @returns Index of the member. + * @param member A member of the group. + * @returns The index of the member, or -1 if it does not exist. */ - indexOf(member: BigNumberish): number { - return this.merkleTree.indexOf(member) + public indexOf(member: BigNumberish): number { + return this.leanIMT.indexOf(BigInt(member)) } /** * Adds a new member to the group. - * @param member New member. + * @param member The new member to be added. */ - addMember(member: BigNumberish) { - this.merkleTree.insert(BigInt(member)) + public addMember(member: BigNumberish) { + this.leanIMT.insert(BigInt(member)) } /** * Adds new members to the group. * @param members New members. - * @deprecated Use the new class parameter to add a list of members. */ - addMembers(members: BigNumberish[]) { - for (const member of members) { - this.addMember(member) - } + public addMembers(members: BigNumberish[]) { + this.leanIMT.insertMany(members.map(BigInt)) } /** @@ -96,28 +88,56 @@ export default class Group { * @param index Index of the member to be updated. * @param member New member value. */ - updateMember(index: number, member: BigNumberish) { - this.merkleTree.update(index, member) + public updateMember(index: number, member: BigNumberish) { + this.leanIMT.update(index, BigInt(member)) } /** * Removes a member from the group. - * @param index Index of the member to be removed. + * @param index The index of the member to be removed. + */ + public removeMember(index: number) { + this.leanIMT.update(index, BigInt(0)) + } + + /** + * Creates a proof of membership for a member of the group. + * @param index The index of the member. + * @returns The {@link MerkleProof} object. + */ + public generateMerkleProof(_index: number): MerkleProof { + const { index, leaf, root, siblings } = this.leanIMT.generateProof(_index) + + return { + index, + leaf: leaf.toString(), + root: root.toString(), + siblings: siblings.map(String) + } + } + + /** + * Enables the conversion of the group into a JSON string that + * can be re-used for future imports. This approach is beneficial for + * large groups, as it avoids re-calculating the tree hashes. + * @returns The stringified JSON of the group. */ - removeMember(index: number) { - this.merkleTree.delete(index) + public export(): string { + return this.leanIMT.export() } /** - * Creates a proof of membership. - * @param index Index of the proof's member. - * @returns Proof object. + * Imports an entire group by initializing the tree without calculating + * any hashes. Note that it is crucial to ensure the integrity of the + * exported group. + * @param nodes The stringified JSON of the group. + * @returns The {@link Group} instance. */ - generateMerkleProof(index: number): MerkleProof { - const merkleProof = this.merkleTree.createProof(index) + static import(exportedGroup: string): Group { + const group = new Group() - merkleProof.siblings = merkleProof.siblings.map((s) => s[0]) + group.leanIMT.import(exportedGroup) - return merkleProof + return group } } diff --git a/packages/group/src/hash.ts b/packages/group/src/hash.ts deleted file mode 100644 index c950f7825..000000000 --- a/packages/group/src/hash.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { BigNumber } from "@ethersproject/bignumber" -import { BytesLike, Hexable, zeroPad } from "@ethersproject/bytes" -import { keccak256 } from "@ethersproject/keccak256" - -/** - * Creates a keccak256 hash of a message compatible with the SNARK scalar modulus. - * @param message The message to be hashed. - * @returns The message digest. - */ -export default function hash(message: BytesLike | Hexable | number | bigint): bigint { - message = BigNumber.from(message).toTwos(256).toHexString() - message = zeroPad(message, 32) - - return BigInt(keccak256(message)) >> BigInt(8) -} diff --git a/packages/group/src/types/index.ts b/packages/group/src/types/index.ts index cf1d4d67f..ab547f2ab 100644 --- a/packages/group/src/types/index.ts +++ b/packages/group/src/types/index.ts @@ -1 +1,4 @@ +import { LeanIMTMerkleProof } from "@zk-kit/imt" + export type BigNumberish = string | number | bigint +export type MerkleProof = LeanIMTMerkleProof diff --git a/packages/group/tests/index.test.ts b/packages/group/tests/index.test.ts new file mode 100644 index 000000000..7083d9b54 --- /dev/null +++ b/packages/group/tests/index.test.ts @@ -0,0 +1,122 @@ +import { Group } from "../src" + +describe("Group", () => { + describe("# Group", () => { + it("Should create a group", () => { + const group = new Group() + + expect(group.root).toBe("0") + expect(group.depth).toBe(0) + expect(group.size).toBe(0) + }) + + it("Should create a group with a list of members", () => { + const group = new Group([1, 2, 3]) + + const group2 = new Group() + + group2.addMember(1) + group2.addMember(2) + group2.addMember(3) + + expect(group.root).toContain(group2.root) + expect(group.depth).toBe(2) + expect(group.size).toBe(3) + }) + }) + + describe("# addMember", () => { + it("Should add a member to a group", () => { + const group = new Group() + + group.addMember(3) + + expect(group.size).toBe(1) + }) + }) + + describe("# addMembers", () => { + it("Should add many members to a group", () => { + const group = new Group() + + group.addMembers([1, 3]) + + expect(group.size).toBe(2) + }) + }) + + describe("# indexOf", () => { + it("Should return the index of a member in a group", () => { + const group = new Group() + group.addMembers([1, 3]) + + const index = group.indexOf(3) + + expect(index).toBe(1) + }) + }) + + describe("# updateMember", () => { + it("Should update a member in a group", () => { + const group = new Group() + group.addMembers([1, 3]) + + group.updateMember(0, 1) + + expect(group.size).toBe(2) + expect(group.members[0]).toBe("1") + }) + }) + + describe("# removeMember", () => { + it("Should remove a member from a group", () => { + const group = new Group() + group.addMembers([1, 3]) + + group.removeMember(0) + + expect(group.size).toBe(2) + expect(group.members[0]).toBe("0") + }) + }) + + describe("# generateMerkleProof", () => { + it("Should generate a proof of membership", () => { + const group = new Group() + + group.addMembers([1, 3]) + + const proof = group.generateMerkleProof(0) + + expect(proof.leaf).toBe("1") + }) + }) + + describe("# export", () => { + it("Should export a group", () => { + const group = new Group([1, 2, 3]) + + const exportedGroup = group.export() + + expect(typeof exportedGroup).toBe("string") + expect(JSON.parse(exportedGroup)).toHaveLength(3) + expect(JSON.parse(exportedGroup)[0]).toHaveLength(3) + }) + }) + + describe("# import", () => { + it("Should import a group", () => { + const group1 = new Group([1, 2, 3]) + const exportedGroup = group1.export() + + const group2 = Group.import(exportedGroup) + + group1.addMember(4) + group2.addMember(4) + + expect(group2.depth).toBe(group1.depth) + expect(group2.size).toBe(group1.size) + expect(group2.root).toBe(group1.root) + }) + }) +}) diff --git a/packages/group/tsconfig.json b/packages/group/tsconfig.json index dfaf0c1ad..71510a096 100644 --- a/packages/group/tsconfig.json +++ b/packages/group/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "../../tsconfig.json", - "include": ["src", "rollup.config.ts"] + "include": ["src", "tests", "rollup.config.ts"] } diff --git a/packages/hardhat/README.md b/packages/hardhat/README.md index 55455e743..ed4226068 100644 --- a/packages/hardhat/README.md +++ b/packages/hardhat/README.md @@ -10,7 +10,7 @@ - Github license + NPM license NPM version diff --git a/packages/hardhat/package.json b/packages/hardhat/package.json index cb96fb354..b36f25cc2 100644 --- a/packages/hardhat/package.json +++ b/packages/hardhat/package.json @@ -1,15 +1,16 @@ { "name": "@semaphore-protocol/hardhat", - "version": "3.15.2", + "version": "4.0.0-alpha.8", "description": "A Semaphore Hardhat plugin to deploy verifiers and Semaphore contract.", + "type": "module", "license": "MIT", - "main": "dist/index.node.js", + "main": "dist/index.js", + "types": "dist/types/index.d.ts", "exports": { - "import": "./dist/index.mjs", - "require": "./dist/index.node.js", - "types": "./dist/types/index.d.ts" + "types": "./dist/types/index.d.ts", + "require": "./dist/index.cjs", + "default": "./dist/index.js" }, - "types": "dist/types/index.d.ts", "files": [ "dist/", "src/", @@ -22,7 +23,6 @@ "url": "https://github.com/semaphore-protocol/semaphore.git/issues" }, "scripts": { - "build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript", "build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript", "prepublishOnly": "yarn build" }, @@ -30,18 +30,17 @@ "access": "public" }, "devDependencies": { - "hardhat": "^2.0.0", + "hardhat": "^2.19.4", "rollup-plugin-cleanup": "^3.2.1", - "rollup-plugin-typescript2": "^0.31.2" + "rollup-plugin-typescript2": "^0.36.0" }, "peerDependencies": { - "hardhat": "^2.0.0" + "hardhat": "^2.19.4" }, "dependencies": { - "@nomiclabs/hardhat-ethers": "^2.1.1", - "@semaphore-protocol/contracts": "3.15.2", - "circomlibjs": "^0.1.7", - "ethers": "^5.7.1", + "@nomicfoundation/hardhat-ethers": "^3.0.0", + "@semaphore-protocol/contracts": "4.0.0-alpha.8", + "ethers": "^6.4.0", "hardhat-dependency-compiler": "^1.1.3" } } diff --git a/packages/hardhat/rollup.config.ts b/packages/hardhat/rollup.config.ts index 9ec2ab68c..29ed3760e 100644 --- a/packages/hardhat/rollup.config.ts +++ b/packages/hardhat/rollup.config.ts @@ -7,7 +7,7 @@ const banner = `/** * @module ${pkg.name} * @version ${pkg.version} * @file ${pkg.description} - * @copyright Ethereum Foundation 2022 + * @copyright Ethereum Foundation 2024 * @license ${pkg.license} * @see [Github]{@link ${pkg.homepage}} */` @@ -16,9 +16,9 @@ export default { input: "src/index.ts", output: [ { file: pkg.exports.require, format: "cjs", banner, exports: "auto" }, - { file: pkg.exports.import, format: "es", banner } + { file: pkg.exports.default, format: "es", banner } ], - external: Object.keys(pkg.dependencies), + external: [...Object.keys(pkg.dependencies), "hardhat/config"], plugins: [ typescript({ tsconfig: "./build.tsconfig.json", diff --git a/packages/hardhat/src/index.ts b/packages/hardhat/src/index.ts index ffe0910a2..bfcca5a88 100644 --- a/packages/hardhat/src/index.ts +++ b/packages/hardhat/src/index.ts @@ -2,15 +2,15 @@ import { extendConfig } from "hardhat/config" import { HardhatConfig, HardhatUserConfig } from "hardhat/types" import "hardhat-dependency-compiler" -import "@nomiclabs/hardhat-ethers" +import "@nomicfoundation/hardhat-ethers" import "./tasks/deploy-semaphore" import "./tasks/deploy-semaphore-verifier" extendConfig((config: HardhatConfig, userConfig: Readonly) => { config.dependencyCompiler.paths = [ - "@semaphore-protocol/contracts/base/Pairing.sol", "@semaphore-protocol/contracts/base/SemaphoreVerifier.sol", - "@semaphore-protocol/contracts/Semaphore.sol" + "@semaphore-protocol/contracts/Semaphore.sol", + "poseidon-solidity/PoseidonT3.sol" ] if (userConfig.dependencyCompiler?.paths) { diff --git a/packages/hardhat/src/tasks/deploy-semaphore-verifier.ts b/packages/hardhat/src/tasks/deploy-semaphore-verifier.ts index 0e62e89cd..524d9d999 100644 --- a/packages/hardhat/src/tasks/deploy-semaphore-verifier.ts +++ b/packages/hardhat/src/tasks/deploy-semaphore-verifier.ts @@ -1,38 +1,17 @@ import { task, types } from "hardhat/config" task("deploy:semaphore-verifier", "Deploy a SemaphoreVerifier contract") - .addOptionalParam("pairing", "Pairing library address", undefined, types.string) .addOptionalParam("logs", "Print the logs", true, types.boolean) - .setAction(async ({ logs, pairing: pairingAddress }, { ethers }): Promise => { - if (!pairingAddress) { - const PairingFactory = await ethers.getContractFactory("Pairing") - const pairing = await PairingFactory.deploy() - - await pairing.deployed() - - if (logs) { - console.info(`Pairing library has been deployed to: ${pairing.address}`) - } - - pairingAddress = pairing.address - } - - const SemaphoreVerifierFactory = await ethers.getContractFactory("SemaphoreVerifier", { - libraries: { - Pairing: pairingAddress - } - }) + .setAction(async ({ logs }, { ethers }): Promise => { + const SemaphoreVerifierFactory = await ethers.getContractFactory("SemaphoreVerifier") const semaphoreVerifier = await SemaphoreVerifierFactory.deploy() - await semaphoreVerifier.deployed() - if (logs) { - console.info(`SemaphoreVerifier contract has been deployed to: ${semaphoreVerifier.address}`) + console.info(`SemaphoreVerifier contract has been deployed to: ${await semaphoreVerifier.getAddress()}`) } return { - semaphoreVerifier, - pairingAddress + semaphoreVerifier } }) diff --git a/packages/hardhat/src/tasks/deploy-semaphore.ts b/packages/hardhat/src/tasks/deploy-semaphore.ts index 1646ae7de..8ee8ad3fc 100644 --- a/packages/hardhat/src/tasks/deploy-semaphore.ts +++ b/packages/hardhat/src/tasks/deploy-semaphore.ts @@ -1,114 +1,53 @@ -import { poseidonContract } from "circomlibjs" import { task, types } from "hardhat/config" task("deploy:semaphore", "Deploy a Semaphore contract") - .addOptionalParam("pairing", "Pairing library address", undefined, types.string) .addOptionalParam("semaphoreVerifier", "SemaphoreVerifier contract address", undefined, types.string) .addOptionalParam("poseidon", "Poseidon library address", undefined, types.string) - .addOptionalParam( - "incrementalBinaryTree", - "IncrementalBinaryTree library address", - undefined, - types.string - ) .addOptionalParam("logs", "Print the logs", true, types.boolean) .setAction( async ( - { - logs, - pairing: pairingAddress, - semaphoreVerifier: semaphoreVerifierAddress, - poseidon: poseidonAddress, - incrementalBinaryTree: incrementalBinaryTreeAddress - }, + { logs, semaphoreVerifier: semaphoreVerifierAddress, poseidon: poseidonAddress }, { ethers } ): Promise => { if (!semaphoreVerifierAddress) { - if (!pairingAddress) { - const PairingFactory = await ethers.getContractFactory("Pairing") - const pairing = await PairingFactory.deploy() - - await pairing.deployed() - - if (logs) { - console.info(`Pairing library has been deployed to: ${pairing.address}`) - } - - pairingAddress = pairing.address - } - - const SemaphoreVerifierFactory = await ethers.getContractFactory("SemaphoreVerifier", { - libraries: { - Pairing: pairingAddress - } - }) + const SemaphoreVerifierFactory = await ethers.getContractFactory("SemaphoreVerifier") const semaphoreVerifier = await SemaphoreVerifierFactory.deploy() - await semaphoreVerifier.deployed() + semaphoreVerifierAddress = await semaphoreVerifier.getAddress() if (logs) { - console.info(`SemaphoreVerifier contract has been deployed to: ${semaphoreVerifier.address}`) + console.info(`SemaphoreVerifier contract has been deployed to: ${semaphoreVerifierAddress}`) } - - semaphoreVerifierAddress = semaphoreVerifier.address } - if (!incrementalBinaryTreeAddress) { - if (!poseidonAddress) { - const poseidonABI = poseidonContract.generateABI(2) - const poseidonBytecode = poseidonContract.createCode(2) - - const [signer] = await ethers.getSigners() + if (!poseidonAddress) { + const PoseidonT3Factory = await ethers.getContractFactory("PoseidonT3") + const poseidonT3 = await PoseidonT3Factory.deploy() - const PoseidonFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer) - const poseidon = await PoseidonFactory.deploy() - - await poseidon.deployed() - - if (logs) { - console.info(`Poseidon library has been deployed to: ${poseidon.address}`) - } - - poseidonAddress = poseidon.address - } - - const IncrementalBinaryTreeFactory = await ethers.getContractFactory("IncrementalBinaryTree", { - libraries: { - PoseidonT3: poseidonAddress - } - }) - const incrementalBinaryTree = await IncrementalBinaryTreeFactory.deploy() - - await incrementalBinaryTree.deployed() + poseidonAddress = await poseidonT3.getAddress() if (logs) { - console.info(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTree.address}`) + console.info(`Poseidon library has been deployed to: ${poseidonAddress}`) } - - incrementalBinaryTreeAddress = incrementalBinaryTree.address } const SemaphoreFactory = await ethers.getContractFactory("Semaphore", { libraries: { - IncrementalBinaryTree: incrementalBinaryTreeAddress + PoseidonT3: poseidonAddress } }) const semaphore = await SemaphoreFactory.deploy(semaphoreVerifierAddress) - await semaphore.deployed() - if (logs) { - console.info(`Semaphore contract has been deployed to: ${semaphore.address}`) + console.info(`Semaphore contract has been deployed to: ${await semaphore.getAddress()}`) } return { semaphore, - pairingAddress, semaphoreVerifierAddress, - poseidonAddress, - incrementalBinaryTreeAddress + poseidonAddress } } ) diff --git a/packages/heyauthn/LICENSE b/packages/heyauthn/LICENSE index a778fff82..b74b61fb6 100644 --- a/packages/heyauthn/LICENSE +++ b/packages/heyauthn/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Vivek Bhupatiraju +Copyright (c) 2024 Vivek Bhupatiraju Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/heyauthn/README.md b/packages/heyauthn/README.md index 586039405..d6c79e4d3 100644 --- a/packages/heyauthn/README.md +++ b/packages/heyauthn/README.md @@ -10,7 +10,7 @@ - Github license + NPM license NPM version @@ -18,7 +18,7 @@ Downloads - + Documentation typedoc @@ -70,6 +70,8 @@ yarn add @semaphore-protocol/heyauthn ## 📜 Usage +For more information on the functions provided by `@semaphore-protocol/heyauthn`, please refer to the [TypeDoc documentation](https://js.semaphore.pse.dev/modules/_semaphore_protocol_heyauthn). + ```typescript import { HeyAuthn } from "@semaphore-protocol/heyauthn" @@ -101,16 +103,13 @@ import { Group } from "@semaphore-protocol/group" import { generateProof } from "@semaphore-protocol/proof" import { utils } from "ethers" -const group = new Group("42") +const group = new Group() group.addMembers(memberList) -const signal = utils.formatBytes32String("Hey anon!") +const message = utils.formatBytes32String("Hey anon!") -generateProof(identity, group, group.id, "42", { - zkeyFilePath: "./semaphore.zkey", - wasmFilePath: "./semaphore.wasm" -}) +generateProof(identity, group, message, group.root) ``` ## Authors diff --git a/packages/heyauthn/package.json b/packages/heyauthn/package.json index 286ad8cc0..485dbfc95 100644 --- a/packages/heyauthn/package.json +++ b/packages/heyauthn/package.json @@ -1,15 +1,16 @@ { "name": "@semaphore-protocol/heyauthn", - "version": "3.15.2", + "version": "4.0.0-alpha.8", "description": "A library to allow developers to create and manage Semaphore identities using WebAuthn", + "type": "module", "license": "MIT", - "main": "dist/index.node.js", + "main": "dist/index.js", + "types": "dist/types/index.d.ts", "exports": { - "import": "./dist/index.mjs", - "require": "./dist/index.node.js", - "types": "./dist/types/index.d.ts" + "types": "./dist/types/index.d.ts", + "require": "./dist/index.cjs", + "default": "./dist/index.js" }, - "types": "dist/types/index.d.ts", "files": [ "dist/", "src/", @@ -22,7 +23,6 @@ "url": "https://github.com/semaphore-protocol/semaphore.git/issues" }, "scripts": { - "build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript", "build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript", "prepublishOnly": "yarn build" }, @@ -31,10 +31,10 @@ }, "devDependencies": { "rollup-plugin-cleanup": "^3.2.1", - "rollup-plugin-typescript2": "^0.31.2" + "rollup-plugin-typescript2": "^0.36.0" }, "dependencies": { - "@semaphore-protocol/identity": "3.15.2", + "@semaphore-protocol/identity": "4.0.0-alpha.8", "@simplewebauthn/browser": "7.2.0", "@simplewebauthn/server": "7.2.0" } diff --git a/packages/heyauthn/rollup.config.ts b/packages/heyauthn/rollup.config.ts index 3b0062ca9..5e66f8e12 100644 --- a/packages/heyauthn/rollup.config.ts +++ b/packages/heyauthn/rollup.config.ts @@ -7,7 +7,7 @@ const banner = `/** * @module ${pkg.name} * @version ${pkg.version} * @file ${pkg.description} - * @copyright Vivek Bhupatiraju 2023 + * @copyright Vivek Bhupatiraju 2024 * @license ${pkg.license} * @see [Github]{@link ${pkg.homepage}} */` @@ -16,7 +16,7 @@ export default { input: "src/index.ts", output: [ { file: pkg.exports.require, format: "cjs", banner, exports: "auto" }, - { file: pkg.exports.import, format: "es", banner } + { file: pkg.exports.default, format: "es", banner } ], external: Object.keys(pkg.dependencies), plugins: [ diff --git a/packages/heyauthn/src/heyAuthn.ts b/packages/heyauthn/src/hey-authn.ts similarity index 100% rename from packages/heyauthn/src/heyAuthn.ts rename to packages/heyauthn/src/hey-authn.ts diff --git a/packages/heyauthn/src/index.ts b/packages/heyauthn/src/index.ts index 3d132a9d8..75c197d31 100644 --- a/packages/heyauthn/src/index.ts +++ b/packages/heyauthn/src/index.ts @@ -1,6 +1,6 @@ import { Identity } from "@semaphore-protocol/identity" import { GenerateAuthenticationOptionsOpts, GenerateRegistrationOptionsOpts } from "@simplewebauthn/server" -import HeyAuthn from "./heyAuthn" +import HeyAuthn from "./hey-authn" export { HeyAuthn, diff --git a/packages/heyauthn/src/heyAuthn.test.ts b/packages/heyauthn/tests/index.test.ts similarity index 82% rename from packages/heyauthn/src/heyAuthn.test.ts rename to packages/heyauthn/tests/index.test.ts index 1826213a8..b5ff94deb 100644 --- a/packages/heyauthn/src/heyAuthn.test.ts +++ b/packages/heyauthn/tests/index.test.ts @@ -4,7 +4,7 @@ import { GenerateRegistrationOptionsOpts as RegistrationOptions } from "@simplewebauthn/server" -import HeyAuthn from "./heyAuthn" +import { HeyAuthn } from "../src" jest.mock("@simplewebauthn/browser", () => ({ startRegistration: async () => ({ @@ -52,9 +52,8 @@ describe("HeyAuthn", () => { const { identity } = await HeyAuthn.fromRegister(options) const expectedIdentity = new Identity("my-new-credential") - expect(expectedIdentity.trapdoor).toEqual(identity.trapdoor) - expect(expectedIdentity.nullifier).toEqual(identity.nullifier) - expect(expectedIdentity.commitment).toEqual(identity.commitment) + expect(identity.secretScalar).toEqual(expectedIdentity.secretScalar) + expect(identity.commitment).toEqual(expectedIdentity.commitment) }) }) @@ -67,9 +66,8 @@ describe("HeyAuthn", () => { const { identity } = await HeyAuthn.fromAuthenticate(options) const expectedIdentity = new Identity("my-existing-credential") - expect(expectedIdentity.trapdoor).toEqual(identity.trapdoor) - expect(expectedIdentity.nullifier).toEqual(identity.nullifier) - expect(expectedIdentity.commitment).toEqual(identity.commitment) + expect(identity.secretScalar).toEqual(expectedIdentity.secretScalar) + expect(identity.commitment).toEqual(expectedIdentity.commitment) }) }) }) diff --git a/packages/heyauthn/tsconfig.json b/packages/heyauthn/tsconfig.json index dfaf0c1ad..71510a096 100644 --- a/packages/heyauthn/tsconfig.json +++ b/packages/heyauthn/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "../../tsconfig.json", - "include": ["src", "rollup.config.ts"] + "include": ["src", "tests", "rollup.config.ts"] } diff --git a/packages/identity/LICENSE b/packages/identity/LICENSE index bdb9b1e08..8ef16f7a5 100644 --- a/packages/identity/LICENSE +++ b/packages/identity/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Ethereum Foundation +Copyright (c) 2024 Ethereum Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/identity/README.md b/packages/identity/README.md index d9593655a..c688baa5d 100644 --- a/packages/identity/README.md +++ b/packages/identity/README.md @@ -10,7 +10,7 @@ - Github license + NPM license NPM version @@ -18,7 +18,7 @@ Downloads - + Documentation typedoc @@ -49,8 +49,8 @@
-| This library provides a class that can be used to create identities compatible with the Semaphore [circuits](https://github.com/semaphore-protocol/semaphore/tree/main/circuits). Each identity contains two secret values: _trapdoor_ and _nullifier_, and one public value: _commitment_. The Poseidon hash of the secret values is the identity secret, and its hash is the identity commitment. | -| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| This library provides a class that can be used to create identities compatible with the Semaphore [circuits](https://github.com/semaphore-protocol/semaphore/tree/main/circuits). Each identity contains an [EdDSA](https://www.rfc-editor.org/rfc/rfc8032) private key, its public key, and the identity commitment, which is the [Poseidon](https://eprint.iacr.org/2019/458.pdf) hash of the public key. | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ## 🛠 Install @@ -70,20 +70,40 @@ yarn add @semaphore-protocol/identity ## 📜 Usage -\# **new Identity**(identityOrMessage?: _string_): _Identity_ +For more information on the functions provided by `@semaphore-protocol/identity`, please refer to the [TypeDoc documentation](https://js.semaphore.pse.dev/modules/_semaphore_protocol_identity). + +\# **new Identity**(privateKey?: _BigNumberish_): _Identity_ + +```typescript +import { Identity } from "@semaphore-protocol/identity" + +// The identity will be generated randomly. +const { privateKey, publicKey, commitment } = new Identity() + +// Alternatively, you can pass your private key. +const identity = new Identity("your-private-key") +``` + +\# **identity.signMessage**(message: _BigNumberish_): _Signature\_ ```typescript import { Identity } from "@semaphore-protocol/identity" -// The identity can be generated randomly. -const identity1 = new Identity() +const message = "message" +const identity = new Identity() + +const signature = identity.signMessage(message) +``` + +\# **Identity.verifySignature**(message: _BigNumberish_, signature: _Signature_, publicKey: _Point_): _boolean_ + +```typescript +import { Identity } from "@semaphore-protocol/identity" -// Deterministically from a secret message. -const identity2 = new Identity("secret-message") +const message = "message" +const identity = new Identity() -// Or it can be retrieved from an existing identity. -const identity3 = new Identity(identity1.toString()) +const signature = identity.signMessage("message", signature) -// Trapdoor, nullifier and commitment are the attributes (e.g. JS getters). -const { trapdoor, nullifier, commitment } = identity1 +Identity.verifySignature(message, signature, identity.publicKey) ``` diff --git a/packages/identity/package.json b/packages/identity/package.json index 0726d4f0a..13b7d4c6c 100644 --- a/packages/identity/package.json +++ b/packages/identity/package.json @@ -1,15 +1,22 @@ { "name": "@semaphore-protocol/identity", - "version": "3.15.2", + "version": "4.0.0-alpha.8", "description": "A library to create Semaphore identities.", + "type": "module", "license": "MIT", - "main": "dist/index.node.js", + "main": "dist/index.browser.js", + "types": "dist/types/index.d.ts", "exports": { - "import": "./dist/index.mjs", - "require": "./dist/index.node.js", - "types": "./dist/types/index.d.ts" + ".": { + "types": "./dist/types/index.d.ts", + "node": { + "require": "./dist/index.node.cjs", + "default": "./dist/index.node.js" + }, + "browser": "./dist/index.browser.js", + "default": "./dist/index.browser.js" + } }, - "types": "dist/types/index.d.ts", "files": [ "dist/", "src/", @@ -22,25 +29,23 @@ "url": "https://github.com/semaphore-protocol/semaphore.git/issues" }, "scripts": { - "build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript", - "build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript", + "build": "rimraf dist && yarn build:browser && yarn build:node", + "build:browser": "rollup -c rollup.browser.config.ts --configPlugin typescript", + "build:node": "rollup -c rollup.node.config.ts --configPlugin typescript", "prepublishOnly": "yarn build" }, "publishConfig": { "access": "public" }, "devDependencies": { - "@rollup/plugin-commonjs": "^24.0.1", - "@rollup/plugin-node-resolve": "^15.0.1", - "poseidon-lite": "^0.2.0", + "@rollup/plugin-alias": "^5.1.0", + "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-node-resolve": "^15.2.3", "rollup-plugin-cleanup": "^3.2.1", - "rollup-plugin-typescript2": "^0.31.2" + "rollup-plugin-typescript2": "^0.36.0" }, "dependencies": { - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/random": "^5.5.1", - "@ethersproject/strings": "^5.6.1", - "js-sha512": "^0.8.0" + "@zk-kit/eddsa-poseidon": "0.4.1", + "poseidon-lite": "^0.2.0" } } diff --git a/packages/proof/rollup.config.ts b/packages/identity/rollup.browser.config.ts similarity index 73% rename from packages/proof/rollup.config.ts rename to packages/identity/rollup.browser.config.ts index 254f8cd17..fedbfda8e 100644 --- a/packages/proof/rollup.config.ts +++ b/packages/identity/rollup.browser.config.ts @@ -1,3 +1,4 @@ +import alias from "@rollup/plugin-alias" import commonjs from "@rollup/plugin-commonjs" import json from "@rollup/plugin-json" import { nodeResolve } from "@rollup/plugin-node-resolve" @@ -10,7 +11,7 @@ const banner = `/** * @module ${pkg.name} * @version ${pkg.version} * @file ${pkg.description} - * @copyright Ethereum Foundation 2022 + * @copyright Ethereum Foundation 2024 * @license ${pkg.license} * @see [Github]{@link ${pkg.homepage}} */` @@ -19,27 +20,22 @@ export default { input: "src/index.ts", output: [ { - file: pkg.exports.require, - format: "cjs", - banner, - exports: "auto" - }, - { - file: pkg.exports.import, + file: pkg.exports["."].browser, format: "es", banner } ], - external: Object.keys(pkg.dependencies), + external: pkg.dependencies, plugins: [ + alias({ + entries: [{ find: "./random-number.node", replacement: "./random-number.browser" }] + }), typescript({ tsconfig: "./build.tsconfig.json", useTsconfigDeclarationDir: true }), + commonjs(), nodeResolve(), - commonjs({ - esmExternals: true - }), cleanup({ comments: "jsdoc" }), json() ] diff --git a/packages/identity/rollup.config.ts b/packages/identity/rollup.node.config.ts similarity index 74% rename from packages/identity/rollup.config.ts rename to packages/identity/rollup.node.config.ts index 9358d39af..7c5ab589b 100644 --- a/packages/identity/rollup.config.ts +++ b/packages/identity/rollup.node.config.ts @@ -1,15 +1,15 @@ -import typescript from "rollup-plugin-typescript2" import commonjs from "@rollup/plugin-commonjs" +import { nodeResolve } from "@rollup/plugin-node-resolve" import * as fs from "fs" import cleanup from "rollup-plugin-cleanup" -import { nodeResolve } from "@rollup/plugin-node-resolve" +import typescript from "rollup-plugin-typescript2" const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8")) const banner = `/** * @module ${pkg.name} * @version ${pkg.version} * @file ${pkg.description} - * @copyright Ethereum Foundation 2022 + * @copyright Ethereum Foundation 2024 * @license ${pkg.license} * @see [Github]{@link ${pkg.homepage}} */` @@ -17,10 +17,10 @@ const banner = `/** export default { input: "src/index.ts", output: [ - { file: pkg.exports.require, format: "cjs", banner, exports: "auto" }, - { file: pkg.exports.import, format: "es", banner } + { file: pkg.exports["."].node.require, format: "cjs", banner, exports: "auto" }, + { file: pkg.exports["."].node.default, format: "es", banner } ], - external: Object.keys(pkg.dependencies), + external: [...Object.keys(pkg.dependencies), "node:crypto"], plugins: [ typescript({ tsconfig: "./build.tsconfig.json", diff --git a/packages/identity/src/bytes-to-bigint.ts b/packages/identity/src/bytes-to-bigint.ts new file mode 100644 index 000000000..5f187541a --- /dev/null +++ b/packages/identity/src/bytes-to-bigint.ts @@ -0,0 +1,9 @@ +export function bytesToBigint(bytes: Uint8Array): bigint { + let hex = "0x" + + for (let i = 0; i < bytes.length; i += 1) { + hex += bytes[i].toString(16).padStart(2, "0") + } + + return BigInt(hex) +} diff --git a/packages/identity/src/checkParameter.ts b/packages/identity/src/checkParameter.ts deleted file mode 100644 index c68474f3a..000000000 --- a/packages/identity/src/checkParameter.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default function checkParameter(value: any, name: string, type: string) { - if (typeof value !== type) { - throw new TypeError(`Parameter '${name}' is not a ${type}`) - } -} diff --git a/packages/identity/src/identity.test.ts b/packages/identity/src/identity.test.ts deleted file mode 100644 index 505078b1d..000000000 --- a/packages/identity/src/identity.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { BigNumber } from "@ethersproject/bignumber" -import Identity from "./identity" - -describe("Identity", () => { - describe("# Identity", () => { - it("Should not create a identity if the parameter is not valid", () => { - const fun1 = () => new Identity(13 as any) - const fun2 = () => new Identity(true as any) - const fun3 = () => new Identity((() => true) as any) - - expect(fun1).toThrow("Parameter 'identityOrMessage' is not a string") - expect(fun2).toThrow("Parameter 'identityOrMessage' is not a string") - expect(fun3).toThrow("Parameter 'identityOrMessage' is not a string") - }) - - it("Should create random identities", () => { - const identity1 = new Identity() - const identity2 = new Identity() - - expect(identity1.trapdoor).not.toBe(identity2.getTrapdoor()) - expect(identity1.nullifier).not.toBe(identity2.getNullifier()) - expect(identity1.secret).not.toBe(identity2.getSecret()) - expect(identity1.commitment).not.toBe(identity2.getCommitment()) - }) - - it("Should create deterministic identities from a message", () => { - const identity1 = new Identity("message") - const identity2 = new Identity("message") - - expect(identity1.trapdoor).toBe(identity2.getTrapdoor()) - expect(identity1.nullifier).toBe(identity2.getNullifier()) - }) - - it("Should create deterministic identities from number/boolean messages", () => { - const identity1 = new Identity("true") - const identity2 = new Identity("true") - const identity3 = new Identity("7") - const identity4 = new Identity("7") - - expect(identity1.trapdoor).toBe(identity2.getTrapdoor()) - expect(identity1.nullifier).toBe(identity2.getNullifier()) - expect(identity3.trapdoor).toBe(identity4.getTrapdoor()) - expect(identity3.nullifier).toBe(identity4.getNullifier()) - }) - - it("Should not recreate an existing invalid identity", () => { - const fun = () => new Identity('[true, "01323"]') - - expect(fun).toThrow("invalid BigNumber value") - }) - - it("Should recreate an existing identity", () => { - const identity1 = new Identity("message") - - const identity2 = new Identity(identity1.toString()) - - expect(identity1.trapdoor).toBe(identity2.getTrapdoor()) - expect(identity1.nullifier).toBe(identity2.getNullifier()) - }) - }) - - describe("# getTrapdoor", () => { - it("Should return the identity trapdoor", () => { - const identity = new Identity("message") - - const trapdoor = identity.getTrapdoor() - - expect(trapdoor.toString()).toBe( - "11566083507498623434013707198824105161167204201250008419741119866456392774309" - ) - }) - }) - - describe("# getNullifier", () => { - it("Should return the identity nullifier", () => { - const identity = new Identity("message") - - const nullifier = identity.getNullifier() - - expect(nullifier.toString()).toBe( - "14070056666392584007908120012103355272369511035580155843212703537125048345255" - ) - }) - }) - - describe("# getSecret", () => { - it("Should return an identity secret", () => { - const { secret } = new Identity("message") - - expect(secret.toString()).toBe( - "17452394798940441025978193762953691632066258438336130543532009665042636950194" - ) - }) - }) - - describe("# getCommitment", () => { - it("Should return an identity commitment", () => { - const { commitment } = new Identity("message") - - expect(commitment.toString()).toBe( - "19361462367798001240039467285882167157718016385695743307694056771074972404368" - ) - }) - }) - - describe("# toString", () => { - it("Should return a string", () => { - const identity = new Identity("message") - - const identityString = identity.toString() - - expect(typeof identityString).toBe("string") - }) - - it("Should return a valid identity string", () => { - const identity = new Identity("message") - - const [trapdoor, nullifier] = JSON.parse(identity.toString()) - - expect(BigNumber.from(trapdoor).toBigInt()).toBe(identity.trapdoor) - expect(BigNumber.from(nullifier).toBigInt()).toBe(identity.nullifier) - }) - }) -}) diff --git a/packages/identity/src/identity.ts b/packages/identity/src/identity.ts index 5bd562ebb..986d501c9 100644 --- a/packages/identity/src/identity.ts +++ b/packages/identity/src/identity.ts @@ -1,121 +1,121 @@ -import { BigNumber } from "@ethersproject/bignumber" -import hash from "js-sha512" -import { poseidon1 } from "poseidon-lite/poseidon1" +import { + BigNumberish, + Point, + Signature, + derivePublicKey, + deriveSecretScalar, + signMessage, + verifySignature +} from "@zk-kit/eddsa-poseidon" import { poseidon2 } from "poseidon-lite/poseidon2" -import checkParameter from "./checkParameter" -import { genRandomNumber, isJsonArray } from "./utils" - +import { randomNumber } from "./random-number.node" + +/** + * The Semaphore identity is essentially an {@link https://www.rfc-editor.org/rfc/rfc8032 | EdDSA} + * public/private key pair. The {@link https://github.com/privacy-scaling-explorations/zk-kit/tree/main/packages/eddsa-poseidon | EdDSA implementation} + * in this library uses {@link https://eips.ethereum.org/EIPS/eip-2494 | Baby Jubjub} for public key generation + * and {@link https://www.poseidon-hash.info | Poseidon} for signatures. + * In addition, the commitment, i.e. the hash of the public key, is used to represent + * Semaphore identities in groups, adding an additional layer of privacy and security. + */ export default class Identity { - private _trapdoor: bigint - private _nullifier: bigint - private _secret: bigint - private _commitment: bigint - - /** - * Initializes the class attributes based on the strategy passed as parameter. - * @param identityOrMessage Additional data needed to create identity for given strategy. - */ - constructor(identityOrMessage?: string) { - if (identityOrMessage === undefined) { - this._trapdoor = genRandomNumber() - this._nullifier = genRandomNumber() - this._secret = poseidon2([this._nullifier, this._trapdoor]) - this._commitment = poseidon1([this._secret]) - - return - } - - checkParameter(identityOrMessage, "identityOrMessage", "string") - - if (!isJsonArray(identityOrMessage)) { - const h = hash.sha512(identityOrMessage).padStart(128, "0") - // alt_bn128 is 253.6 bits, so we can safely use 253 bits. - this._trapdoor = BigInt(`0x${h.slice(64)}`) >> BigInt(3) - this._nullifier = BigInt(`0x${h.slice(0, 64)}`) >> BigInt(3) - this._secret = poseidon2([this._nullifier, this._trapdoor]) - this._commitment = poseidon1([this._secret]) - - return - } - - const [trapdoor, nullifier] = JSON.parse(identityOrMessage) - - this._trapdoor = BigNumber.from(trapdoor).toBigInt() - this._nullifier = BigNumber.from(nullifier).toBigInt() - this._secret = poseidon2([this._nullifier, this._trapdoor]) - this._commitment = poseidon1([this._secret]) - } - - /** - * Returns the identity trapdoor. - * @returns The identity trapdoor. - */ - public get trapdoor(): bigint { - return this._trapdoor - } + // The EdDSA private key, passed as a parameter or generated randomly. + private _privateKey: BigNumberish + // The secret scalar derived from the private key. + // It is used in circuits to derive the public key. + private _secretScalar: string + // The EdDSA public key, derived from the private key. + private _publicKey: Point + // The identity commitment used as a public value in Semaphore groups. + private _commitment: string /** - * Returns the identity trapdoor. - * @returns The identity trapdoor. + * Initializes the class attributes based on a given private key. + * If the private key is not passed as a parameter, a random key is generated. + * The constructor calculates the secret scalar and public key from the private key, + * and computes a commitment of the public key using a hash function (Poseidon). + * + * @example + * // Generates an identity. + * const { privateKey, publicKey, commitment } = new Identity("private-key") + * @example + * // Generates an identity with a random private key. + * const { privateKey, publicKey, commitment } = new Identity() + * + * @param privateKey The private key used to derive the public key. */ - public getTrapdoor(): bigint { - return this._trapdoor + constructor(privateKey: BigNumberish = randomNumber().toString()) { + this._privateKey = privateKey + this._secretScalar = deriveSecretScalar(privateKey) + this._publicKey = derivePublicKey(privateKey) + this._commitment = poseidon2(this._publicKey).toString() } /** - * Returns the identity nullifier. - * @returns The identity nullifier. + * Returns the private key. + * @returns The private key as a {@link https://zkkit.pse.dev/types/_zk_kit_utils.BigNumberish.html | BigNumberish}. */ - public get nullifier(): bigint { - return this._nullifier + public get privateKey(): BigNumberish { + return this._privateKey } /** - * Returns the identity nullifier. - * @returns The identity nullifier. + * Returns the secret scalar. + * @returns The secret scalar as a string. */ - public getNullifier(): bigint { - return this._nullifier + public get secretScalar(): string { + return this._secretScalar } /** - * Returns the identity secret. - * @returns The identity secret. + * Returns the public key as a Baby Jubjub {@link https://zkkit.pse.dev/types/_zk_kit_baby_jubjub.Point.html | Point}. + * @returns The public key as a point. */ - public get secret(): bigint { - return this._secret + public get publicKey(): Point { + return this._publicKey } /** - * Returns the identity secret. - * @returns The identity secret. + * Returns the commitment hash of the public key. + * @returns The commitment as a string. */ - public getSecret(): bigint { - return this._secret - } - - /** - * Returns the identity commitment. - * @returns The identity commitment. - */ - public get commitment(): bigint { + public get commitment(): string { return this._commitment } /** - * Returns the identity commitment. - * @returns The identity commitment. + * Generates a signature for a given message using the private key. + * This method demonstrates how to sign a message and could be used + * for authentication or data integrity. + * + * @example + * const identity = new Identity() + * const signature = identity.signMessage("message") + * + * @param message The message to be signed. + * @returns A {@link https://zkkit.pse.dev/types/_zk_kit_eddsa_poseidon.Signature.html | Signature} object containing the signature components. */ - public getCommitment(): bigint { - return this._commitment + public signMessage(message: BigNumberish): Signature { + return signMessage(this.privateKey, message) } /** - * Returns a JSON string with trapdoor and nullifier. It can be used - * to export the identity and reuse it later. - * @returns The string representation of the identity. + * Verifies a signature against a given message and public key. + * This static method allows for the verification of signatures without needing + * an instance of the Identity class. It's useful for cases where you only have + * the public key, the message and a signature, and need to verify if they match. + * + * @example + * const identity = new Identity() + * const signature = identity.signMessage("message") + * Identity.verifySignature("message", signature, identity.publicKey) + * + * @param message The message that was signed. + * @param signature The signature to verify. + * @param publicKey The public key to use for verification. + * @returns A boolean indicating whether the signature is valid. */ - public toString(): string { - return JSON.stringify([`0x${this._trapdoor.toString(16)}`, `0x${this._nullifier.toString(16)}`]) + static verifySignature(message: BigNumberish, signature: Signature, publicKey: Point): boolean { + return verifySignature(message, signature, publicKey) } } diff --git a/packages/identity/src/random-number.browser.ts b/packages/identity/src/random-number.browser.ts new file mode 100644 index 000000000..6ccd20982 --- /dev/null +++ b/packages/identity/src/random-number.browser.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +import { bytesToBigint } from "./bytes-to-bigint" + +export function randomNumber(): bigint { + const bytes = crypto.getRandomValues(new Uint8Array(32)) + + return bytesToBigint(bytes) +} diff --git a/packages/identity/src/random-number.node.ts b/packages/identity/src/random-number.node.ts new file mode 100644 index 000000000..f219b4b11 --- /dev/null +++ b/packages/identity/src/random-number.node.ts @@ -0,0 +1,9 @@ +import { randomBytes } from "node:crypto" + +export function randomNumber(): bigint { + const bytes = randomBytes(32) + + const hex = `0x${bytes.toString("hex")}` + + return BigInt(hex) +} diff --git a/packages/identity/src/utils.ts b/packages/identity/src/utils.ts deleted file mode 100644 index 0351e8eca..000000000 --- a/packages/identity/src/utils.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { BigNumber } from "@ethersproject/bignumber" -import { randomBytes } from "@ethersproject/random" - -/** - * Generates a random big number. - * @param numberOfBytes The number of bytes of the number. - * @returns The generated random number. - */ -export function genRandomNumber(numberOfBytes = 31): bigint { - return BigNumber.from(randomBytes(numberOfBytes)).toBigInt() -} - -/** - * Checks if a string is a JSON. - * @param jsonString The JSON string. - * @returns True or false. - */ -export function isJsonArray(jsonString: string) { - try { - return Array.isArray(JSON.parse(jsonString)) - } catch (error) { - return false - } -} diff --git a/packages/identity/tests/index.test.ts b/packages/identity/tests/index.test.ts new file mode 100644 index 000000000..1ac668e1c --- /dev/null +++ b/packages/identity/tests/index.test.ts @@ -0,0 +1,60 @@ +import { randomBytes } from "crypto" +import { Identity } from "../src" +import { bytesToBigint } from "../src/bytes-to-bigint" + +describe("Identity", () => { + const privateKey = "secret" + + describe("# Identity", () => { + it("Should create a random identity", () => { + const identity = new Identity() + + expect(typeof identity.privateKey).toBe("string") + expect(typeof identity.secretScalar).toBe("string") + expect(identity.publicKey).toHaveLength(2) + expect(typeof identity.commitment).toBe("string") + }) + + it("Should create deterministic identities from a secret (private key)", () => { + const identity = new Identity(privateKey) + + expect(typeof identity.privateKey).toBe("string") + expect(typeof identity.secretScalar).toBe("string") + expect(identity.publicKey).toHaveLength(2) + expect(typeof identity.commitment).toBe("string") + }) + }) + + describe("# signMessage", () => { + it("Should sign a message", () => { + const identity = new Identity(privateKey) + + const signature = identity.signMessage("message") + + expect(signature.R8).toHaveLength(2) + expect(typeof signature.R8[0]).toBe("string") + expect(typeof signature.S).toBe("string") + }) + }) + + describe("# verifySignature", () => { + it("Should verify a signature", () => { + const identity = new Identity(privateKey) + + const signature = identity.signMessage("message") + + expect(Identity.verifySignature("message", signature, identity.publicKey)).toBeTruthy() + }) + }) + + describe("# bytesToBigint", () => { + it("Should convert 32 bytes to bigint", () => { + const bytes = randomBytes(32) + + const integer = bytesToBigint(bytes) + + expect(typeof integer).toBe("bigint") + expect(integer).toBe(BigInt(`0x${bytes.toString("hex")}`)) + }) + }) +}) diff --git a/packages/identity/tsconfig.json b/packages/identity/tsconfig.json index dfaf0c1ad..8c95393fa 100644 --- a/packages/identity/tsconfig.json +++ b/packages/identity/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "../../tsconfig.json", - "include": ["src", "rollup.config.ts"] + "include": ["src", "tests", "rollup.node.config.ts", "rollup.browser.config.ts"] } diff --git a/packages/proof/LICENSE b/packages/proof/LICENSE index bdb9b1e08..8ef16f7a5 100644 --- a/packages/proof/LICENSE +++ b/packages/proof/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Ethereum Foundation +Copyright (c) 2024 Ethereum Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/proof/README.md b/packages/proof/README.md index 691bb41f1..2ca266650 100644 --- a/packages/proof/README.md +++ b/packages/proof/README.md @@ -10,7 +10,7 @@ - Github license + NPM license NPM version @@ -18,7 +18,7 @@ Downloads - + Documentation typedoc @@ -49,8 +49,8 @@
-| This library provides utility functions to generate and verify Semaphore proofs compatible with the Semaphore [circuits](https://github.com/semaphore-protocol/semaphore/tree/main/circuits). Generating valid zero-knowledge proofs requires files that can only be obtained in an attested [trusted-setup ceremony](https://storage.googleapis.com/trustedsetup-a86f4.appspot.com/semaphore/semaphore_top_index.html). For a complete list of ready-to-use files visit [trusted-setup-pse.org](http://www.trusted-setup-pse.org/). | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| This library provides utility functions to generate and verify Semaphore proofs compatible with the Semaphore [circuits](https://github.com/semaphore-protocol/semaphore/tree/main/circuits). Generating valid zero-knowledge proofs requires files that can only be obtained in an attested trusted-setup ceremony. | +| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ## 🛠 Install @@ -70,11 +70,14 @@ yarn add @semaphore-protocol/identity @semaphore-protocol/group @semaphore-proto ## 📜 Usage +For more information on the functions provided by `@semaphore-protocol/proof`, please refer to the [TypeDoc documentation](https://js.semaphore.pse.dev/modules/_semaphore_protocol_proof). + \# **generateProof**( identity: _Identity_, -group: _Group_ | _MerkleProof_, -externalNullifier: _BytesLike | Hexable | number | bigint_, -signal: _BytesLike | Hexable | number | bigint_, +group: _Group_, +message: _BigNumberish_ | _Uint8Array_ | string, +scope: _BigNumberish_ | _Uint8Array_ | string, +merkleTreeDepth: _number_, snarkArtifacts?: _SnarkArtifacts_ ): Promise\<_SemaphoreProof_> @@ -82,43 +85,32 @@ snarkArtifacts?: _SnarkArtifacts_ import { Identity } from "@semaphore-protocol/identity" import { Group } from "@semaphore-protocol/group" import { generateProof } from "@semaphore-protocol/proof" -import { utils } from "ethers" -const identity = new Identity() -const group = new Group() -const externalNullifier = utils.formatBytes32String("Topic") -const signal = utils.formatBytes32String("Hello world") +const identity1 = new Identity() +const identity2 = new Identity() +const identity3 = new Identity() -group.addMembers([...identityCommitments, identity.generateCommitment()]) +const group = new Group([identity1.commitment, identity2.commitment, identity3.commitment]) -const fullProof = await generateProof(identity, group, externalNullifier, signal, { - zkeyFilePath: "./semaphore.zkey", - wasmFilePath: "./semaphore.wasm" -}) +const message = "Hello world" +const scope = "Semaphore" -// You can also use the default zkey/wasm files (it only works from browsers!). -// const fullProof = await generateProof(identity, group, externalNullifier, signal) -``` +const proof1 = await generateProof(identity1, group, message, scope) -\# **verifyProof**(semaphoreProof: _SemaphoreProof_, treeDepth: _number_): Promise\<_boolean_> - -```typescript -import { verifyProof } from "@semaphore-protocol/proof" +// You can also specify the maximum tree depth supported by the proof. +const proof2 = await generateProof(identity2, group, message, scope, 20) -await verifyProof(fullProof, 20) +// You can also specify the default zkey/wasm files. +const proof3 = await generateProof(identity3, group, message, scope, 20, { + wasmFilePath: "./semaphore.wasm", + zkeyFilePath: "./semaphore.zkey" +}) ``` -\# **calculateNullifierHash**( -identityNullifier: _bigint | number | string_, -externalNullifier: \__BytesLike | Hexable | number | bigint_ -): bigint +\# **verifyProof**(semaphoreProof: _SemaphoreProof_): Promise\<_boolean_> ```typescript -import { Identity } from "@semaphore-protocol/identity" -import { calculateNullifierHash } from "@semaphore-protocol/proof" - -const identity = new Identity() -const externalNullifier = utils.formatBytes32String("Topic") +import { verifyProof } from "@semaphore-protocol/proof" -const nullifierHash = calculateNullifierHash(identity.nullifier, externalNullifier) +await verifyProof(proof1) ``` diff --git a/packages/proof/package.json b/packages/proof/package.json index d85743e7d..a77513276 100644 --- a/packages/proof/package.json +++ b/packages/proof/package.json @@ -1,15 +1,22 @@ { "name": "@semaphore-protocol/proof", - "version": "3.15.2", + "version": "4.0.0-alpha.8", "description": "A library to generate and verify Semaphore proofs.", + "type": "module", "license": "MIT", - "main": "dist/index.node.js", + "main": "dist/index.browser.js", + "types": "dist/types/index.d.ts", "exports": { - "import": "./dist/index.node.mjs", - "require": "./dist/index.node.js", - "types": "./dist/types/index.d.ts" + ".": { + "types": "./dist/types/index.d.ts", + "node": { + "require": "./dist/index.node.cjs", + "default": "./dist/index.node.js" + }, + "browser": "./dist/index.browser.js", + "default": "./dist/index.browser.js" + } }, - "types": "dist/types/index.d.ts", "files": [ "dist/", "src/", @@ -22,33 +29,34 @@ "url": "https://github.com/semaphore-protocol/semaphore.git/issues" }, "scripts": { - "build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript", - "build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript", + "build": "rimraf dist && yarn build:browser && yarn build:node", + "build:browser": "rollup -c rollup.browser.config.ts --configPlugin typescript", + "build:node": "rollup -c rollup.node.config.ts --configPlugin typescript", "prepublishOnly": "yarn build" }, "publishConfig": { "access": "public" }, "devDependencies": { - "@rollup/plugin-commonjs": "^24.1.0", - "@rollup/plugin-json": "^5.0.1", - "@rollup/plugin-node-resolve": "^15.0.2", + "@rollup/plugin-alias": "^5.1.0", + "@rollup/plugin-json": "^6.1.0", + "@types/download": "^8.0.5", + "@types/snarkjs": "0.7.8", + "@types/tmp": "^0.2.6", "poseidon-lite": "^0.2.0", "rimraf": "^5.0.5", - "rollup": "^4.0.2", + "rollup": "^4.9.6", "rollup-plugin-cleanup": "^3.2.1", - "rollup-plugin-typescript2": "^0.31.2" + "rollup-plugin-typescript2": "^0.36.0" }, "peerDependencies": { - "@semaphore-protocol/group": "3.15.2", - "@semaphore-protocol/identity": "3.15.2" + "@semaphore-protocol/group": "4.0.0-alpha.8", + "@semaphore-protocol/identity": "4.0.0-alpha.8" }, "dependencies": { - "@ethersproject/bignumber": "^5.5.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/strings": "^5.5.0", - "@zk-kit/groth16": "0.3.0", - "@zk-kit/incremental-merkle-tree": "0.4.3" + "@semaphore-protocol/utils": "4.0.0-alpha.8", + "@zk-kit/utils": "0.2.0", + "ethers": "6.10.0", + "snarkjs": "0.7.3" } } diff --git a/packages/proof/rollup.browser.config.ts b/packages/proof/rollup.browser.config.ts new file mode 100644 index 000000000..60f8ba971 --- /dev/null +++ b/packages/proof/rollup.browser.config.ts @@ -0,0 +1,50 @@ +import alias from "@rollup/plugin-alias" +import json from "@rollup/plugin-json" +import * as fs from "fs" +import cleanup from "rollup-plugin-cleanup" +import typescript from "rollup-plugin-typescript2" + +const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8")) +const banner = `/** + * @module ${pkg.name} + * @version ${pkg.version} + * @file ${pkg.description} + * @copyright Ethereum Foundation 2024 + * @license ${pkg.license} + * @see [Github]{@link ${pkg.homepage}} +*/` + +export default { + input: "src/index.ts", + output: [ + { + file: pkg.exports["."].browser, + format: "es", + banner + } + ], + external: [ + ...Object.keys(pkg.dependencies), + "node:fs", + "node:fs/promises", + "node:os", + "node:path", + "node:stream", + "node:stream/promises", + "ethers/crypto", + "ethers/utils", + "ethers/abi", + "@semaphore-protocol/utils/errors" + ], + plugins: [ + alias({ + entries: [{ find: "./get-snark-artifacts.node", replacement: "./get-snark-artifacts.browser" }] + }), + typescript({ + tsconfig: "./build.tsconfig.json", + useTsconfigDeclarationDir: true + }), + cleanup({ comments: "jsdoc" }), + json() + ] +} diff --git a/packages/proof/rollup.node.config.ts b/packages/proof/rollup.node.config.ts new file mode 100644 index 000000000..fd9c5e3aa --- /dev/null +++ b/packages/proof/rollup.node.config.ts @@ -0,0 +1,52 @@ +import json from "@rollup/plugin-json" +import * as fs from "fs" +import cleanup from "rollup-plugin-cleanup" +import typescript from "rollup-plugin-typescript2" + +const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8")) +const banner = `/** + * @module ${pkg.name} + * @version ${pkg.version} + * @file ${pkg.description} + * @copyright Ethereum Foundation 2024 + * @license ${pkg.license} + * @see [Github]{@link ${pkg.homepage}} +*/` + +export default { + input: "src/index.ts", + output: [ + { + file: pkg.exports["."].node.require, + format: "cjs", + banner, + exports: "auto" + }, + { + file: pkg.exports["."].node.default, + format: "es", + banner + } + ], + external: [ + ...Object.keys(pkg.dependencies), + "node:fs", + "node:fs/promises", + "node:os", + "node:path", + "node:stream", + "node:stream/promises", + "ethers/crypto", + "ethers/utils", + "ethers/abi", + "@semaphore-protocol/utils/errors" + ], + plugins: [ + typescript({ + tsconfig: "./build.tsconfig.json", + useTsconfigDeclarationDir: true + }), + cleanup({ comments: "jsdoc" }), + json() + ] +} diff --git a/packages/proof/src/calculateNullifierHash.ts b/packages/proof/src/calculateNullifierHash.ts deleted file mode 100644 index 8cca53343..000000000 --- a/packages/proof/src/calculateNullifierHash.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { BytesLike, Hexable } from "@ethersproject/bytes" -import { poseidon2 } from "poseidon-lite" -import hash from "./hash" - -/** - * Given the identity nullifier and the external nullifier, it calculates nullifier hash. - * @param identityNullifier The identity nullifier. - * @param externalNullifier The external nullifier. - * @returns The nullifier hash. - */ -export default function calculateNullifierHash( - identityNullifier: number | bigint | string, - externalNullifier: BytesLike | Hexable | number | bigint -): bigint { - return poseidon2([hash(externalNullifier), identityNullifier]) -} diff --git a/packages/proof/src/generate-proof.ts b/packages/proof/src/generate-proof.ts new file mode 100644 index 000000000..2ea55586b --- /dev/null +++ b/packages/proof/src/generate-proof.ts @@ -0,0 +1,122 @@ +import type { Group, MerkleProof } from "@semaphore-protocol/group" +import type { Identity } from "@semaphore-protocol/identity" +import { requireDefined, requireNumber, requireObject, requireTypes } from "@semaphore-protocol/utils/errors" +import { NumericString, groth16 } from "snarkjs" +import { packGroth16Proof } from "@zk-kit/utils" +import getSnarkArtifacts from "./get-snark-artifacts.node" +import hash from "./hash" +import toBigInt from "./to-bigint" +import { BigNumberish, SemaphoreProof, SnarkArtifacts } from "./types" + +/** + * It generates a Semaphore proof, i.e. a zero-knowledge proof that an identity that + * is part of a group has shared an anonymous message. + * The message may be any arbitrary user-defined value (e.g. a vote), or the hash of that value. + * The scope is a value used like a topic on which users can generate a valid proof only once, + * for example the id of an election in which voters can only vote once. + * The hash of the identity's scope and secret scalar is called a nullifier and can be + * used to verify whether that identity has already generated a valid proof in that scope. + * The depth of the tree determines which zero-knowledge artifacts to use to generate the proof. + * If it is not defined, it will be inferred from the group or Merkle proof passed as the second parameter. + * Finally, the artifacts themselves can be passed manually with file paths, + * or they will be automatically fetched. + * @param identity The Semaphore identity. + * @param groupOrMerkleProof The Semaphore group or its Merkle proof. + * @param scope The Semaphore scope. + * @param message The Semaphore message. + * @param merkleTreeDepth The depth of the tree with which the circuit was compiled. + * @param snarkArtifacts The SNARK artifacts. + * @returns The Semaphore proof ready to be verified. + */ +export default async function generateProof( + identity: Identity, + groupOrMerkleProof: Group | MerkleProof, + message: BigNumberish | Uint8Array | string, + scope: BigNumberish | Uint8Array | string, + merkleTreeDepth?: number, + snarkArtifacts?: SnarkArtifacts +): Promise { + requireDefined(identity, "identity") + requireDefined(groupOrMerkleProof, "groupOrMerkleProof") + requireDefined(message, "message") + requireDefined(scope, "scope") + + requireObject(identity, "identity") + requireObject(groupOrMerkleProof, "groupOrMerkleProof") + requireTypes(message, "message", ["string", "bigint", "number", "uint8array"]) + requireTypes(scope, "scope", ["string", "bigint", "number", "uint8array"]) + + if (merkleTreeDepth) { + requireNumber(merkleTreeDepth, "merkleTreeDepth") + } + + if (snarkArtifacts) { + requireObject(snarkArtifacts, "snarkArtifacts") + } + + // Message and scope can be strings, numbers or buffers (i.e. Uint8Array). + // They will be converted to bigints anyway. + message = toBigInt(message) + scope = toBigInt(scope) + + let merkleProof + + // The second parameter can be either a Merkle proof or a group. + // If it is a group the Merkle proof will be calculated here. + if ("siblings" in groupOrMerkleProof) { + merkleProof = groupOrMerkleProof + } else { + const leafIndex = groupOrMerkleProof.indexOf(identity.commitment) + merkleProof = groupOrMerkleProof.generateMerkleProof(leafIndex) + } + + const merkleProofLength = merkleProof.siblings.length + + if (merkleTreeDepth !== undefined) { + if (merkleTreeDepth < 1 || merkleTreeDepth > 12) { + throw new TypeError("The tree depth must be a number between 1 and 12") + } + } else { + merkleTreeDepth = merkleProofLength + } + + // If the Snark artifacts are not defined they will be automatically downloaded. + if (!snarkArtifacts) { + snarkArtifacts = await getSnarkArtifacts(merkleTreeDepth) + } + + // The index must be converted to a list of indices, 1 for each tree level. + // The missing siblings can be set to 0, as they won't be used in the circuit. + const merkleProofIndices = [] + const merkleProofSiblings = merkleProof.siblings + + for (let i = 0; i < merkleTreeDepth; i += 1) { + merkleProofIndices.push((merkleProof.index >> i) & 1) + + if (merkleProofSiblings[i] === undefined) { + merkleProofSiblings[i] = "0" + } + } + + const { proof, publicSignals } = await groth16.fullProve( + { + secret: identity.secretScalar, + merkleProofLength, + merkleProofIndices, + merkleProofSiblings, + scope: hash(scope), + message: hash(message) + }, + snarkArtifacts.wasmFilePath, + snarkArtifacts.zkeyFilePath + ) + + return { + merkleTreeDepth, + merkleTreeRoot: publicSignals[0], + nullifier: publicSignals[1], + message: message.toString() as NumericString, + scope: scope.toString() as NumericString, + points: packGroth16Proof(proof) + } +} diff --git a/packages/proof/src/generateProof.ts b/packages/proof/src/generateProof.ts deleted file mode 100644 index 1407ce1ca..000000000 --- a/packages/proof/src/generateProof.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { BigNumber } from "@ethersproject/bignumber" -import { BytesLike, Hexable } from "@ethersproject/bytes" -import { Group } from "@semaphore-protocol/group" -import type { Identity } from "@semaphore-protocol/identity" -import { prove } from "@zk-kit/groth16" -import { MerkleProof } from "@zk-kit/incremental-merkle-tree" -import type { NumericString } from "snarkjs" -import hash from "./hash" -import packProof from "./packProof" -import { SemaphoreProof, SnarkArtifacts } from "./types" - -/** - * Generates a Semaphore proof. - * @param identity The Semaphore identity. - * @param groupOrMerkleProof The Semaphore group or its Merkle proof. - * @param externalNullifier The external nullifier. - * @param signal The Semaphore signal. - * @param snarkArtifacts The SNARK artifacts. - * @returns The Semaphore proof ready to be verified. - */ -export default async function generateProof( - { trapdoor, nullifier, commitment }: Identity, - groupOrMerkleProof: Group | MerkleProof, - externalNullifier: BytesLike | Hexable | number | bigint, - signal: BytesLike | Hexable | number | bigint, - snarkArtifacts?: SnarkArtifacts -): Promise { - let merkleProof: MerkleProof - - if ("depth" in groupOrMerkleProof) { - const index = groupOrMerkleProof.indexOf(commitment) - - if (index === -1) { - throw new Error("The identity is not part of the group") - } - - merkleProof = groupOrMerkleProof.generateMerkleProof(index) - } else { - merkleProof = groupOrMerkleProof - } - - if (!snarkArtifacts) { - snarkArtifacts = { - wasmFilePath: `https://www.trusted-setup-pse.org/semaphore/${merkleProof.siblings.length}/semaphore.wasm`, - zkeyFilePath: `https://www.trusted-setup-pse.org/semaphore/${merkleProof.siblings.length}/semaphore.zkey` - } - } - - const { proof, publicSignals } = await prove( - { - identityTrapdoor: trapdoor, - identityNullifier: nullifier, - treePathIndices: merkleProof.pathIndices, - treeSiblings: merkleProof.siblings, - externalNullifier: hash(externalNullifier), - signalHash: hash(signal) - }, - snarkArtifacts.wasmFilePath, - snarkArtifacts.zkeyFilePath - ) - - return { - merkleTreeRoot: publicSignals[0], - nullifierHash: publicSignals[1], - signal: BigNumber.from(signal).toString() as NumericString, - externalNullifier: BigNumber.from(externalNullifier).toString() as NumericString, - proof: packProof(proof) - } -} diff --git a/packages/proof/src/get-snark-artifacts.browser.ts b/packages/proof/src/get-snark-artifacts.browser.ts new file mode 100644 index 000000000..b48d796e4 --- /dev/null +++ b/packages/proof/src/get-snark-artifacts.browser.ts @@ -0,0 +1,18 @@ +/* istanbul ignore file */ +import { requireNumber } from "@semaphore-protocol/utils/errors" +import { SnarkArtifacts } from "./types" + +/** + * Returns the zero-knowledge artifact paths. Artifacts exist for each tree + * depth supported by Semaphore, and they were generated in a trusted-setup. + * @param treeDepth The depth of the tree. + * @returns The zero-knowledge artifacts paths. + */ +export default async function getSnarkArtifacts(treeDepth: number): Promise { + requireNumber(treeDepth, "treeDepth") + + return { + wasmFilePath: `https://semaphore.cedoor.dev/artifacts/${treeDepth}/semaphore.wasm`, + zkeyFilePath: `https://semaphore.cedoor.dev/artifacts/${treeDepth}/semaphore.zkey` + } +} diff --git a/packages/proof/src/get-snark-artifacts.node.ts b/packages/proof/src/get-snark-artifacts.node.ts new file mode 100644 index 000000000..f5a4ec75c --- /dev/null +++ b/packages/proof/src/get-snark-artifacts.node.ts @@ -0,0 +1,59 @@ +/* istanbul ignore file */ +import { requireNumber } from "@semaphore-protocol/utils/errors" +import { createWriteStream, existsSync, readdirSync } from "node:fs" +import { mkdir } from "node:fs/promises" +import os from "node:os" +import { dirname } from "node:path" +import { Readable } from "node:stream" +import { finished } from "node:stream/promises" +import { SnarkArtifacts } from "./types" + +/** + * A utility function to download the zero-knowledge artifacts from an external server. + * @param url The URL from which to download the artifacts. + * @param outputPath The path in which to save the artifacts. + */ +async function download(url: string, outputPath: string) { + const response = await fetch(url) + + if (!response.ok) { + throw new Error(`Failed to fetch ${url}: ${response.statusText}`) + } + + // Ensure the directory exists. + const dir = dirname(outputPath) + await mkdir(dir, { recursive: true }) + + const fileStream = createWriteStream(outputPath) + await finished(Readable.fromWeb(response.body as any).pipe(fileStream)) +} + +/** + * Downloads the zero-knowledge artifacts and returns their paths. + * Artifacts exist for each tree depth supported by Semaphore, and + * they were generated in a trusted-setup. + * @param treeDepth The depth of the tree. + * @returns The zero-knowledge artifacts paths. + */ +export default async function getSnarkArtifacts(treeDepth: number): Promise { + requireNumber(treeDepth, "treeDepth") + + const tmpDir = "semaphore-proof" + const tmpPath = `${os.tmpdir()}/${tmpDir}-${treeDepth}` + + if (!existsSync(tmpPath) || readdirSync(tmpPath).length !== 2) { + await download( + `https://semaphore.cedoor.dev/artifacts/${treeDepth}/semaphore.wasm`, + `${tmpPath}/semaphore.wasm` + ) + await download( + `https://semaphore.cedoor.dev/artifacts/${treeDepth}/semaphore.zkey`, + `${tmpPath}/semaphore.zkey` + ) + } + + return { + wasmFilePath: `${tmpPath}/semaphore.wasm`, + zkeyFilePath: `${tmpPath}/semaphore.zkey` + } +} diff --git a/packages/proof/src/hash.ts b/packages/proof/src/hash.ts index 328b3ef52..d50ed404a 100644 --- a/packages/proof/src/hash.ts +++ b/packages/proof/src/hash.ts @@ -1,16 +1,13 @@ -import { BigNumber } from "@ethersproject/bignumber" -import { BytesLike, Hexable, zeroPad } from "@ethersproject/bytes" -import { keccak256 } from "@ethersproject/keccak256" +import { keccak256 } from "ethers/crypto" +import { toBeHex } from "ethers/utils" import { NumericString } from "snarkjs" +import { BigNumberish } from "./types" /** * Creates a keccak256 hash of a message compatible with the SNARK scalar modulus. * @param message The message to be hashed. * @returns The message digest. */ -export default function hash(message: BytesLike | Hexable | number | bigint): NumericString { - message = BigNumber.from(message).toTwos(256).toHexString() - message = zeroPad(message, 32) - - return (BigInt(keccak256(message)) >> BigInt(8)).toString() as NumericString +export default function hash(message: BigNumberish): NumericString { + return (BigInt(keccak256(toBeHex(message, 32))) >> BigInt(8)).toString() } diff --git a/packages/proof/src/index.test.ts b/packages/proof/src/index.test.ts deleted file mode 100644 index 420a45bec..000000000 --- a/packages/proof/src/index.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { formatBytes32String } from "@ethersproject/strings" -import { Group } from "@semaphore-protocol/group" -import { Identity } from "@semaphore-protocol/identity" -import { getCurveFromName } from "ffjavascript" -import calculateNullifierHash from "./calculateNullifierHash" -import generateProof from "./generateProof" -import hash from "./hash" -import packProof from "./packProof" -import { SemaphoreProof } from "./types" -import unpackProof from "./unpackProof" -import verifyProof from "./verifyProof" - -describe("Proof", () => { - const treeDepth = Number(process.env.TREE_DEPTH) || 20 - - const externalNullifier = formatBytes32String("Topic") - const signal = formatBytes32String("Hello world") - - const wasmFilePath = `./snark-artifacts/${treeDepth}/semaphore.wasm` - const zkeyFilePath = `./snark-artifacts/${treeDepth}/semaphore.zkey` - - const identity = new Identity() - - let fullProof: SemaphoreProof - let curve: any - - beforeAll(async () => { - curve = await getCurveFromName("bn128") - }) - - afterAll(async () => { - await curve.terminate() - }) - - describe("# generateProof", () => { - it("Should not generate Semaphore proofs if the identity is not part of the group", async () => { - const group = new Group(treeDepth, 20, [BigInt(1), BigInt(2)]) - - const fun = () => - generateProof(identity, group, externalNullifier, signal, { - wasmFilePath, - zkeyFilePath - }) - - await expect(fun).rejects.toThrow("The identity is not part of the group") - }) - - it("Should not generate a Semaphore proof with default snark artifacts with Node.js", async () => { - const group = new Group(treeDepth, 20, [BigInt(1), BigInt(2), identity.commitment]) - - const fun = () => generateProof(identity, group, externalNullifier, signal) - - await expect(fun).rejects.toThrow("ENOENT: no such file or directory") - }) - - it("Should generate a Semaphore proof passing a group as parameter", async () => { - const group = new Group(treeDepth, 20, [BigInt(1), BigInt(2), identity.commitment]) - - fullProof = await generateProof(identity, group, externalNullifier, signal, { - wasmFilePath, - zkeyFilePath - }) - - expect(typeof fullProof).toBe("object") - expect(fullProof.merkleTreeRoot).toBe(group.root.toString()) - }, 20000) - - it("Should generate a Semaphore proof passing a Merkle proof as parameter", async () => { - const group = new Group(treeDepth, 20, [BigInt(1), BigInt(2), identity.commitment]) - - fullProof = await generateProof(identity, group.generateMerkleProof(2), externalNullifier, signal, { - wasmFilePath, - zkeyFilePath - }) - - expect(typeof fullProof).toBe("object") - expect(fullProof.merkleTreeRoot).toBe(group.root.toString()) - }, 20000) - }) - - describe("# verifyProof", () => { - it("Should not verify a proof if the tree depth is wrong", () => { - const fun = () => verifyProof(fullProof, 3) - - expect(fun).toThrow("The tree depth must be a number between 16 and 32") - }) - - it("Should verify a Semaphore proof", async () => { - const response = await verifyProof(fullProof, treeDepth) - - expect(response).toBe(true) - }) - }) - - describe("# hash", () => { - it("Should hash the signal value correctly", async () => { - const signalHash = hash(signal) - - expect(signalHash).toBe("8665846418922331996225934941481656421248110469944536651334918563951783029") - }) - - it("Should hash the external nullifier value correctly", async () => { - const externalNullifierHash = hash(externalNullifier) - - expect(externalNullifierHash).toBe( - "244178201824278269437519042830883072613014992408751798420801126401127326826" - ) - }) - - it("Should hash a number", async () => { - expect(hash(2)).toBe("113682330006535319932160121224458771213356533826860247409332700812532759386") - }) - - it("Should hash a big number", async () => { - expect(hash(BigInt(2))).toBe("113682330006535319932160121224458771213356533826860247409332700812532759386") - }) - - it("Should hash an hex number", async () => { - expect(hash("0x2")).toBe("113682330006535319932160121224458771213356533826860247409332700812532759386") - }) - - it("Should hash an string number", async () => { - expect(hash("2")).toBe("113682330006535319932160121224458771213356533826860247409332700812532759386") - }) - - it("Should hash an array", async () => { - expect(hash([2])).toBe("113682330006535319932160121224458771213356533826860247409332700812532759386") - }) - }) - - describe("# calculateNullifierHash", () => { - it("Should calculate the nullifier hash correctly", async () => { - const nullifierHash = calculateNullifierHash(identity.nullifier, externalNullifier) - - expect(fullProof.nullifierHash).toBe(nullifierHash.toString()) - }) - }) - - describe("# packProof/unpackProof", () => { - it("Should return a packed proof", async () => { - const originalProof = unpackProof(fullProof.proof) - const proof = packProof(originalProof) - - expect(proof).toStrictEqual(fullProof.proof) - }) - }) -}) diff --git a/packages/proof/src/index.ts b/packages/proof/src/index.ts index 72d4e1389..9c3f9c71f 100644 --- a/packages/proof/src/index.ts +++ b/packages/proof/src/index.ts @@ -1,7 +1,6 @@ -import calculateNullifierHash from "./calculateNullifierHash" -import generateProof from "./generateProof" -import verifyProof from "./verifyProof" +import { packGroth16Proof, unpackGroth16Proof } from "@zk-kit/utils" +import generateProof from "./generate-proof" +import verifyProof from "./verify-proof" -export { MerkleProof } from "@zk-kit/incremental-merkle-tree" export * from "./types" -export { generateProof, verifyProof, calculateNullifierHash } +export { generateProof, packGroth16Proof, unpackGroth16Proof, verifyProof } diff --git a/packages/proof/src/packProof.ts b/packages/proof/src/packProof.ts deleted file mode 100644 index 6677eaeab..000000000 --- a/packages/proof/src/packProof.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Groth16Proof } from "snarkjs" -import { PackedProof } from "./types" - -/** - * Packs a proof into a format compatible with Semaphore. - * @param proof The Groth16 proof generated with SnarkJS. - * @returns The proof compatible with Semaphore. - */ -export default function packProof(proof: Groth16Proof): PackedProof { - return [ - proof.pi_a[0], - proof.pi_a[1], - proof.pi_b[0][1], - proof.pi_b[0][0], - proof.pi_b[1][1], - proof.pi_b[1][0], - proof.pi_c[0], - proof.pi_c[1] - ] -} diff --git a/packages/proof/src/to-bigint.ts b/packages/proof/src/to-bigint.ts new file mode 100644 index 000000000..daa80a529 --- /dev/null +++ b/packages/proof/src/to-bigint.ts @@ -0,0 +1,20 @@ +import { encodeBytes32String } from "ethers/abi" +import { toBigInt as _toBigInt } from "ethers/utils" +import { BigNumberish } from "./types" + +/** + * Converts a bignumberish to a bigint. + * @param value The value to be converted to bigint. + * @return The value converted to bigint. + */ +export default function toBigInt(value: BigNumberish | Uint8Array | string): bigint { + try { + return _toBigInt(value) + } catch (error: any) { + if (typeof value === "string") { + return _toBigInt(encodeBytes32String(value)) + } + + throw TypeError(error.message) + } +} diff --git a/packages/proof/src/types/index.ts b/packages/proof/src/types/index.ts index aa320d9ad..a1becd86c 100644 --- a/packages/proof/src/types/index.ts +++ b/packages/proof/src/types/index.ts @@ -1,19 +1,22 @@ import type { NumericString } from "snarkjs" +export type BigNumberish = string | number | bigint + export type SnarkArtifacts = { wasmFilePath: string zkeyFilePath: string } export type SemaphoreProof = { + merkleTreeDepth: number merkleTreeRoot: NumericString - signal: NumericString - nullifierHash: NumericString - externalNullifier: NumericString - proof: PackedProof + message: NumericString + nullifier: NumericString + scope: NumericString + points: PackedPoints } -export type PackedProof = [ +export type PackedPoints = [ NumericString, NumericString, NumericString, diff --git a/packages/proof/src/unpackProof.ts b/packages/proof/src/unpackProof.ts deleted file mode 100644 index 68ba66599..000000000 --- a/packages/proof/src/unpackProof.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Groth16Proof } from "snarkjs" -import { PackedProof } from "./types" - -/** - * Unpacks a proof into its original form. - * @param proof The proof compatible with Semaphore. - * @returns The proof compatible with SnarkJS. - */ -export default function unpackProof(proof: PackedProof): Groth16Proof { - return { - pi_a: [proof[0], proof[1]], - pi_b: [ - [proof[3], proof[2]], - [proof[5], proof[4]] - ], - pi_c: [proof[6], proof[7]], - protocol: "groth16", - curve: "bn128" - } -} diff --git a/packages/proof/src/verification-keys.json b/packages/proof/src/verification-keys.json new file mode 100644 index 000000000..921bc96a1 --- /dev/null +++ b/packages/proof/src/verification-keys.json @@ -0,0 +1,522 @@ +{ + "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": [ + [ + [ + "16293410697967515504861065986355060225819302510590370360517024529684437085892", + "563562783592406106461234396505774794044312891062077216951605541624542949349" + ], + [ + "10971164449179097832515056670594147898894418081309026322805549037082528587660", + "718666295291146285397081607986058864287612968465033527894198098928522749430" + ], + ["1", "0"] + ], + [ + [ + "8087990171976409378525016197066789089596692733769545253942804888352233244331", + "6925326031648378844726759441690273332175949886518827719224741736678266471462" + ], + [ + "15618724583521291135890928658458637081479213437530496494788106479523123480134", + "10115242383701466910539784663900956956505212347433548599529944880163612117847" + ], + ["1", "0"] + ], + [ + [ + "7699563868952902688273782470023919811229667532027732585467624469008534197561", + "7699900854308622607636895140107457213471038801393300399043779958706267744510" + ], + [ + "15390497513810955087868026868634948499789494261604405008298272598684585331879", + "8605006500231713390564760089884965869867818332514275728271802101761117177706" + ], + ["1", "0"] + ], + [ + [ + "2561989864654045354260435368824127763249975523256535586391379695256700207392", + "9243647856126339588200227750973237380809101852206502177952556679680468734813" + ], + [ + "18264861452415533737686303596832882667852638121244187636443702248458018416929", + "1308059969166897152269078839315249187046718124106168932020710683189419160756" + ], + ["1", "0"] + ], + [ + [ + "21307904757295729894500052152251151076177124351059746872940558537429181679313", + "12554869896570109663558999106053541183136083706372178136870048170147558661370" + ], + [ + "11194665041440536766843756304212299788817698936187605770131686430625103172681", + "13447792161027167738515744919555311619301071111923271126707713963292614473975" + ], + ["1", "0"] + ], + [ + [ + "11706947284665470438085734275890584651957983546291282232304205898476084974953", + "6841243505372324564043525805425666596605335849634400049689614462868641599622" + ], + [ + "2674642993002069213220912300725195098434544703961492590713058622848310061455", + "8134420958061302592465696766566203845441024351721660066575866529624350946525" + ], + ["1", "0"] + ], + [ + [ + "14347453588631116202352669855016330076170847581618919999243717038742915487177", + "16222168105567777760688113388791710300819703656136395550799888633073848798053" + ], + [ + "5558376366205739247464101507558623934779699722049069497418188353860561540238", + "1551977748690015855031859376084014992267998166583511614295554470740621685494" + ], + ["1", "0"] + ], + [ + [ + "13742810756711208866393269789950822956196651488331544225304557369739035583007", + "8039261311473792027408130340265108867783988336829815266149766357424909552805" + ], + [ + "1426283383724925735279168010737048062664480092231550167387716017170422478271", + "5732605965679620193714116075516176231919315490749768120227762344555213912038" + ], + ["1", "0"] + ], + [ + [ + "4869230832458710492613139050501069397194236257402947715710705773326736170245", + "12816421563768297608770270654146762017343826741755988834647936785087288245221" + ], + [ + "11073344652802336528382437239976464979745376329678013840706100925574578891663", + "18301439700961740613282752163964601039402055729651579152511810295028351855917" + ], + ["1", "0"] + ], + [ + [ + "11544253490899949675932185200523769178642714850738952025947643180461452257027", + "10621957863293158040009666146187340406300006617270113507997717497098731431645" + ], + [ + "49583159539136224825737189586941489049516773623689603831917260806184061698", + "19367886286197174023585178612664391272570357530876044259219178006444442279835" + ], + ["1", "0"] + ], + [ + [ + "20152340858374022568747410447956756883573367358669429548835673215400335225747", + "6919690594446594824660388293695365136302375315018989420609208580270256193473" + ], + [ + "16509772385894559653132866836382733797078280267167499296517180188027714772070", + "11674218038035204128186342498577020603218612593894174060137243207689886423596" + ], + ["1", "0"] + ], + [ + [ + "21672319732433045166991956534762990382763080837755725716637027617201056105221", + "3246325158773052647788911340476235648870186305670446321333841273770228016262" + ], + [ + "5332470123606111842725808071294486759248878958039162159296029608796276205941", + "1097625536358560225015844871776096852794298581720514105921271391131873344240" + ], + ["1", "0"] + ] + ], + "vk_alphabeta_12": [ + [ + [ + "2029413683389138792403550203267699914886160938906632433982220835551125967885", + "21072700047562757817161031222997517981543347628379360635925549008442030252106" + ], + [ + "5940354580057074848093997050200682056184807770593307860589430076672439820312", + "12156638873931618554171829126792193045421052652279363021382169897324752428276" + ], + [ + "7898200236362823042373859371574133993780991612861777490112507062703164551277", + "7074218545237549455313236346927434013100842096812539264420499035217050630853" + ] + ], + [ + [ + "7077479683546002997211712695946002074877511277312570035766170199895071832130", + "10093483419865920389913245021038182291233451549023025229112148274109565435465" + ], + [ + "4595479056700221319381530156280926371456704509942304414423590385166031118820", + "19831328484489333784475432780421641293929726139240675179672856274388269393268" + ], + [ + "11934129596455521040620786944827826205713621633706285934057045369193958244500", + "8037395052364110730298837004334506829870972346962140206007064471173334027475" + ] + ] + ], + "IC": [ + [ + [ + "16397970494781113622140409380710514277504568512202481522431528914560031562762", + "15987991045804318696151558137607070651882190737142560406194591976742796145195", + "1" + ], + [ + "19175554796060272870947844726785784415291967441071791144616572094703752022787", + "14708716857471570462880683190724447733812118518043273649838616424043548023127", + "1" + ], + [ + "5109703116431086866657790821388182053265093586387384935734011036204399393630", + "1612476077210311180626421664389270104678910550524363357381847966959374674423", + "1" + ], + [ + "16028984344157742576452936855797086856968817024956118691418341585143424903639", + "6905323418829609143361434077039383502192920586565275784934386988372483556465", + "1" + ], + [ + "5037234187699234788733084505200789125332575456459095878140727750253994537245", + "5152780378461098676484401284161685966625077837157208907964141131514955942959", + "1" + ] + ], + [ + [ + "19573269191775350978579562905764743103417752180580176229566975904395505694207", + "2381522187377974692257359562587606852360557473353020907100590706767276986457", + "1" + ], + [ + "3570865353993345507636525265023758855364727230473021611280650482974485897542", + "3026650225811133060402545160946181194870257210933662802529138266788423022235", + "1" + ], + [ + "10948020004642082805202152014062303498984706484599503419636897666877701726029", + "16425328653566008388486740080892552688012772956429074139489785673820307770702", + "1" + ], + [ + "20638502839000044414864636985783149953889799151008745227055624454042471377832", + "977321369609271151576827768083353705509746900519279122286818202637470656742", + "1" + ], + [ + "18662986393280647182646025762677321210586855801774577114215804961111542726195", + "9182414327249389553719378979750943052880001176853490206379257023646169811116", + "1" + ] + ], + [ + [ + "1172653041469601107707030355715854571110526375194334139771259673595104816259", + "139822645729727942447876327389754883087551266970976361358585992124005959663", + "1" + ], + [ + "807568760006422352594797418374693945705914727214443753238278163089338607471", + "19362805143026565781098954744725161233661862485826387437200176803022106919133", + "1" + ], + [ + "9457043262004277983171082100772969689099515860060732671699192102623198243288", + "19229090298682361289811638032020124933114889462217199961448135904896325661193", + "1" + ], + [ + "18883326163102164343877677909116984239805709949130729675634413304023108763212", + "2795333364660917174391473381893559091159208514832297547909172862364581446206", + "1" + ], + [ + "18012979848063967305324818138773222084850070233834350625943804711450349870116", + "11425098272720529401115253745902976848228234025625773037823969253767737500443", + "1" + ] + ], + [ + [ + "5735580005765404165535708760854311053102182143614810924129912232734875728422", + "12547270286864340931439010083179993915942911007015778927394042746771813715897", + "1" + ], + [ + "17861179987982979247789643870162154315308943006677455616368767345181299723498", + "12520843301982976970226379882714814545061255956743392202788539428471921045815", + "1" + ], + [ + "709419095293140562723620898844593712074413125125086760533949250121053456086", + "20254885558410038279405227446341995510955697359900050994091569463713580982280", + "1" + ], + [ + "8876829807505794597615690202974184696287415186921508358861381955321520402876", + "9692396410060123455009190221005078571838194435772371581970098240972280778466", + "1" + ], + [ + "11541729643712807778212647011796800767186857085220105751644556558522744760878", + "7117533920695574619512162711113223946930183977424569115798612426749467545080", + "1" + ] + ], + [ + [ + "7022760843802757109079393624924627417630030139055381384837279305453559861026", + "14368180091281110030022253036601349874026580767275461373222397498585380849175", + "1" + ], + [ + "20362090815079102760789516334841889611927869342884580401494938701237909313479", + "11683916255585393494577805363227207275174890005674166623110857790951330330248", + "1" + ], + [ + "21680925564107543632769644144771629045807478787600735681275069556336841549441", + "1554686039880869088226596836931999292372497423378849142389700108361111176287", + "1" + ], + [ + "9553151402225656007352139561853210183786116738154807824913092141416366824324", + "21774504332021003611111291017295441063483107704370481606906960187337904118400", + "1" + ], + [ + "3472743162059388238264946309663113383560193397376658519781344886918012396958", + "21850707327192737842691835772722844106916466129654852946865863108319178245521", + "1" + ] + ], + [ + [ + "14007544454490183581525584522132854274412440955138184221862170710084681738226", + "9427297140302346741340407818949601657384670126565058224960470754064287232598", + "1" + ], + [ + "9424279417196769259939653963004219272435595656090601392111859177294419276463", + "5159704841441138198509282351301933053602549958584575945916187562678590534936", + "1" + ], + [ + "5934282708091541939003757621608766449985810436411564642900129758520734711260", + "6572936900512215029112509161886160866328777917297795430264727434969553346474", + "1" + ], + [ + "17332317297867087005634642159909283377570778576256008720318658354552654813365", + "20469716791176463599463762213563135543375669917658488173798369718370224346075", + "1" + ], + [ + "11895216520141526042182770992519307655836464153974170586288146149131884093776", + "2345310584279491449819095573562431881564998414485162753036518957549727207513", + "1" + ] + ], + [ + [ + "15084362489402846752495176069529022447007294106313236924068931072406922960482", + "17446301099629553693162731281967866078816607293341610477850995414321505446665", + "1" + ], + [ + "15416633712259927998995598381829216728913683600537032862643171407716059372732", + "6910124994917236999728700744617337824847623550680442113266553962793201270115", + "1" + ], + [ + "3240338086312503075747637551773566329150860621223080774242357236897458479365", + "9706162939164863315053907226494918827393300584078535638283184502242772542378", + "1" + ], + [ + "3821371825593696440611637099905775310839636477646533943959149234595561019388", + "12220054708690507765048929995921680127285008492320123722886943219569231705313", + "1" + ], + [ + "16484014909498227505527128570845947338361237083477117662394990633734193572570", + "15639273550026538537711009501005182488432976272250651065072907504931158455474", + "1" + ] + ], + [ + [ + "7289543915982634578897897440956973302403422297539950338836810160596047457980", + "15177295033774438197517772642364420015492974487860823338193520175227862903717", + "1" + ], + [ + "5412509155867259433944542755241941469706034032614068890113478300647952353475", + "16755045912589178729548422106539306191579601567866216267685082373250783779841", + "1" + ], + [ + "844425885324371282218176954530597075058496766373014736097970493596574863875", + "12323478340077628423886836431146436403221716179764806398442271274194718652143", + "1" + ], + [ + "19468406721151512215981453048984639951787280312568894384216547824763527840472", + "6512636337641737270183934024333064400224464644359645491020358650071524340464", + "1" + ], + [ + "8198878215192784492258585011520207242810327647198332623948351347768018051267", + "8014960150646978739731831763393714179490854831922196242908407049327430863238", + "1" + ] + ], + [ + [ + "9107882721143487446664847981273782313316003453386555221878701631432129058401", + "9441695541513726822451572070540177043748046411169636568582927338148104680158", + "1" + ], + [ + "8987068930426301924419900826390508311526176863992306858982473479912439758010", + "1725109031239210601724581840155988791039497876559382612372874257775877974323", + "1" + ], + [ + "18516110346729610211387987894593386281257834088357741109524145966500785499183", + "114078214476076549461901405178268623018026929846201249476869510133510589766", + "1" + ], + [ + "15915940069177053049090451855582611051242699463952201407239272373068542013292", + "9373936080407194259749015269175580128112032582312467763484971165406599176646", + "1" + ], + [ + "4922802633190996456945600288106639549790810185209505074744770090776471062338", + "8883001810279320778962047313612748029483858862205749774979015238684570049088", + "1" + ] + ], + [ + [ + "14718035764669896417897962215694711502145567157050183561348015124983050763912", + "16468329864686708461615593125586840110548434159129257976547738477143616225069", + "1" + ], + [ + "19316680422470456517825967239402234468963225932355881906076897312088976727808", + "3793008941035953637072065313624049788550692955904011087840457148221740889857", + "1" + ], + [ + "8994123619474927749524041243795236789619386871069943230512200348341409025802", + "2898455184193535874162795400065700362364113037155462125222703939894764399559", + "1" + ], + [ + "7006987736985681119096469806956612323700302986805047546691419062106874738669", + "6578526693577992862738197741211972861837397562647850241473856881198749597774", + "1" + ], + [ + "18253609658317686864081972526657430282945609846265497350129963610508303097865", + "20602454323920106770928127673050173555696255204782817307960073152139829286712", + "1" + ] + ], + [ + [ + "18387700354891683161007139598833533459740755646851082725263944626765753998128", + "8496770255291121655312305725645117658510206297162207504909146686627154245846", + "1" + ], + [ + "3295351580633631545204707236418977224205703294426182202748223245617942752953", + "11318533876720817956139762907441156918032396007060817178042936071438547629319", + "1" + ], + [ + "9798692489110933308397734193670030418121117030189402149994776248649813971130", + "7253879530870754821836349446054636336833607432103959373798000294248292966203", + "1" + ], + [ + "21808645514001764351825515375665639285586999794992628373968527911077248645185", + "2859748802987042398453158245958377568437550705999358391809538153774651114865", + "1" + ], + [ + "4373537692781319117201803895634857716202697229097770645380222493663057011429", + "21723556418902236605356947828522543021755681613733490227407492902239152334538", + "1" + ] + ], + [ + [ + "4147129560663007063799754603731275683761368247704376766576071776957227165800", + "7032402220603422034618210155106646820962552774510253688945919060625670415308", + "1" + ], + [ + "5241022678363371404039324538224070321660623527603509392150146296843642044363", + "5281273397025328620822441453790518717375265972202147684745427714882287252791", + "1" + ], + [ + "8109626675067152219533782552336308649905522111608563670382254454484448073029", + "4696661232179595878220108186097206302552300846640109174608389689072223809776", + "1" + ], + [ + "13974026846148052806722306435446895222317781098029808723550029071117080027133", + "14451415757463182353662034231421738047130904367363697865823311429455557043904", + "1" + ], + [ + "9490728148230530026163178051838213576627639298618884515939110001242292217290", + "227687333556127166330064214209948378192130717415165040791158610964406937909", + "1" + ] + ] + ] +} diff --git a/packages/proof/src/verificationKeys.json b/packages/proof/src/verificationKeys.json deleted file mode 100644 index 9de3064fe..000000000 --- a/packages/proof/src/verificationKeys.json +++ /dev/null @@ -1,712 +0,0 @@ -{ - "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"] - ], - [ - [ - "13589689305661231568162336263197960570915890299814486885851912452076929115480", - "15629200772768268814959330350023920183087521275477047626405113853190187031523" - ], - [ - "16004221700357242255845535848024178544616388017965468694776181247983831995562", - "11464919285924930973853174493551975632739604254498590354200272115844983493029" - ], - ["1", "0"] - ], - [ - [ - "19717684456458906358368865507225121991585492363133107109865920739019288468011", - "9218320951536642499143228327011901814587826948504871816273184688188019956292" - ], - [ - "18221695645112467945186983098720611586049108689347006136423489099202471884089", - "16717590750910963405756115910371408378114896008824240863060392362901176601412" - ], - ["1", "0"] - ], - [ - [ - "15953239752392927777442331623182226063776310198012173504208557434319753428770", - "3995128789564535587814512245259203300137618476815456454931286633947953135662" - ], - [ - "2523786679709693946058523307330825034772478122295850507521258983130425334580", - "20957319343912866335583737646657534123362052690050674068142580221965936605075" - ], - ["1", "0"] - ], - [ - [ - "1382518990777992893805140303684642328066746531257780279226677247567004248173", - "18976133691706015337908381757202123182841901611067930614519324084182946094218" - ], - [ - "21806956747910197517744499423107239699428979652113081469385876768212706694581", - "6627710380771660558660627878547223719795356903257079198333641681330388499309" - ], - ["1", "0"] - ], - [ - [ - "9032545080831535702239063467087720597970266046938395860207839433937324718536", - "3811592683283527904145155808200366192489850711742363953668998371801696238057" - ], - [ - "12429982191499850873612518410809641163252887523090441166572590809691267943605", - "16308433125974933290258540904373317426123214107276055539769464205982500660715" - ], - ["1", "0"] - ], - [ - [ - "17626503110323089701269363177710295379967225765713250625279671011873619640598", - "9485639152672984144988597737758037391807993615552051606205480347442429414340" - ], - [ - "18953587685067712486092665232725058638563458484886448540567142557894080640927", - "12391874700409435648975069978280047983726144854114915177376036190441913967689" - ], - ["1", "0"] - ], - [ - [ - "11408965575174993375815840422438995549652812400401163392501956884932167624437", - "9830856103389248449121962275587399130605902703453384856543071762984116567573" - ], - [ - "19969543376625663966419118899515353499678204573709836615846115182224340858492", - "11814906841949499037550820576929552248172160643991870665022770052632331265834" - ], - ["1", "0"] - ], - [ - [ - "10090041889587324002759549286390619541526396451963494627957072069124011137562", - "15035335306919942325459417688135340085377315274625768597233474641923619728582" - ], - [ - "10507786999799841055999967456762679569286329319056926475375760604262707147294", - "21342049717074059749518233491526445388158772701642182532370641230478027030319" - ], - ["1", "0"] - ], - [ - [ - "43456740675249348549891878341522275183186932745162972528932808393415299552", - "15718373132479769904443326381037437528372212185108294117696143473979328398658" - ], - [ - "4289247401578837038775845192875793775418122783738936298355403103074020081838", - "11236864934894600819960883124570686936554376109344998527334431594565774237827" - ], - ["1", "0"] - ], - [ - [ - "4023016874169005249382064394379671330447496454371261692205411970999350949293", - "1723458149089715907994189658689343304709709060535625667210252753337752162173" - ], - [ - "17710652158212212080502343565075513548898593397103675832636832371532093744857", - "7651670126664625790835334090273463062538865895183205964669372719235003083565" - ], - ["1", "0"] - ], - [ - [ - "13132169670125192016391258838554965176628317453468870968867717287446623320643", - "745924679191739894055143748466112994378439645681039136007774787076115375124" - ], - [ - "20909608709868730010029182074820840312550443752829480953667886902663547957991", - "2126777833939378028304266129616145667925849332481755567268747182629795296580" - ], - ["1", "0"] - ], - [ - [ - "16835654219229187428071649241190746119082269636345872682107941472241044260584", - "4553625243522856553165922942982108474187282402890756796515747778282922584601" - ], - [ - "873742823867191038535544062852920538566418819521732785500614249239215175476", - "3272293478534046729728233267765357195255129499603632413158978822084188871854" - ], - ["1", "0"] - ], - [ - [ - "7601443214415704135008588588192028557655441716696726549510699770097979655628", - "7252337675475138150830402909353772156046809729627064992143762325769537840623" - ], - [ - "18500126298578278987997086114400065402270866280547473913420536595663876273004", - "436607343827794507835462908831699962173244647704538949914686722631806931932" - ], - ["1", "0"] - ], - [ - [ - "15028154694713144242204861571552635520290993855826554325002991692907421516918", - "10202326166286888893675634318107715186834588694714750762952081034135561546271" - ], - [ - "12766289885372833812620582632847872978085960777075662988932200910695848591357", - "18486039841380105976272577521609866666900576498507352937328726490052296469859" - ], - ["1", "0"] - ], - [ - [ - "13682963731073238132274278610660469286329368216526659590944079211949686450402", - "14930624777162656776068112402283260602512252179767747308433194885322661150422" - ], - [ - "21315724107376627085778492378001676935454590984229146391746301404292016287653", - "18705481657148807016785305378773304476425591636333098330324049960258682574070" - ], - ["1", "0"] - ], - [ - [ - "18994803742708336446369128568423705404354655742604689352630273180469431952708", - "12315240965742683516581565369496371929586281338862761742109651525191835544242" - ], - [ - "12707009780301102830224094192984906206920666691015255692741008594808694787917", - "18019403342409608922812569436317484250134945386869657285229378095251425778096" - ], - ["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" - ] - ], - [ - [ - "17789438292552571310739605737896030466581277887660997531707911256058650850910", - "4112657509505371631825493224748310061184972897405589115208158208294581472016", - "1" - ], - [ - "3322052920119834475842380240689494113984887785733316517680891208549118967155", - "381029395779795399840019487059126246243641886087320875571067736504031557148", - "1" - ], - [ - "8777645223617381095463415690983421308854368583891690388850387317049320450400", - "11923582117369144413749726090967341613266070909169947059497952692052020331958", - "1" - ], - [ - "15493263571528401950994933073246603557158047091963487223668240334879173885581", - "6315532173951617115856055775098532808695228294437279844344466163873167020700", - "1" - ], - [ - "3481637421055377106140197938175958155334313900824697193932986771017625492245", - "20088416136090515091300914661950097694450984520235647990572441134215240947932", - "1" - ] - ], - [ - [ - "4691595252082380256698158158199364410440273386659834000993210659508747323919", - "9205801980459323513061837717352821162780471027241700646145937351740096374660", - "1" - ], - [ - "16150531426263112884093068164597994126623437929929609532055221646496813246000", - "20245743178241899668170758952526381872637304119026868520579207157118516761827", - "1" - ], - [ - "6063536446992770713985314309889717594240410784717230886576072989709763902848", - "18258781411255795973918859665416013869184055573057512603788635470145328981347", - "1" - ], - [ - "10109932964756104512054045207253535333686585863745296080906925765480296575285", - "4174640428253153601540284363759502713687021920150940723252842152556151210349", - "1" - ], - [ - "18049428534741480832385046397049175120355008065781483226058177421025493210952", - "591730261265040164434889324846001338201068482543108348317417391345612814922", - "1" - ] - ], - [ - [ - "9877211178693075145402462781884120278654771727348087433632224794894486095150", - "19972682062587174829535281061580296764150591339640180868104711395548066529340", - "1" - ], - [ - "6324578424031095537345184040149690238371517387586958921377481904541316423724", - "15513931720576048544404512239839508014664224085062729779520992909505663748296", - "1" - ], - [ - "11371337652479737143800707796204655130812036287859296372695832558127430723628", - "11757275188600040111649009832378343123994225623498773406233261322165903848967", - "1" - ], - [ - "13282496583564708104981015168203451877588903263486398132954741568835583461335", - "1746144324840370907926720490289700342734912534857331743685374514401176014195", - "1" - ], - [ - "7993952462467372951144011615584426050192046712674662254138390197508963352374", - "5156942148925224345709309361345680948125600198010285179548841917923439945819", - "1" - ] - ], - [ - [ - "19918517214839406678907482305035208173510172567546071380302965459737278553528", - "7151186077716310064777520690144511885696297127165278362082219441732663131220", - "1" - ], - [ - "690581125971423619528508316402701520070153774868732534279095503611995849608", - "21271996888576045810415843612869789314680408477068973024786458305950370465558", - "1" - ], - [ - "16461282535702132833442937829027913110152135149151199860671943445720775371319", - "2814052162479976678403678512565563275428791320557060777323643795017729081887", - "1" - ], - [ - "4319780315499060392574138782191013129592543766464046592208884866569377437627", - "13920930439395002698339449999482247728129484070642079851312682993555105218086", - "1" - ], - [ - "3554830803181375418665292545416227334138838284686406179598687755626325482686", - "5951609174746846070367113593675211691311013364421437923470787371738135276998", - "1" - ] - ], - [ - [ - "9494885690931955877467315318223108618392113101843890678090902614660136056680", - "11783514256715757384821021009301806722951917744219075907912683963173706887379", - "1" - ], - [ - "7562082660623781416745328104576133910743071878837764423695105915778139873834", - "17954307004260053757579194018551114133664721761483240877658498973152950708099", - "1" - ], - [ - "19338184851116432029108109461622579541195083625346674255186169347975445785058", - "38361206266360048012365562393026952048730052530888439195454086987795985927", - "1" - ], - [ - "21178537742782571863590222710872928190886000600239072595684369348717288330049", - "9786438258541172244884631831247223050494423968411444302812755467521949734320", - "1" - ], - [ - "11330504221972341797183339350494223413034293674225690456356444509688810101433", - "1490009915387901405464437253469086864085891770312035292355706249426866485365", - "1" - ] - ], - [ - [ - "21791720972262589799021600767292883644106575897307484548888696814333235336885", - "11092962469758788187888592619035811117815082357439060720677582048880121542623", - "1" - ], - [ - "9418924955930663972575130074928583215922927562059194231976193350658171304436", - "16113558481826020406162261319744796072664750077095575593106901121115073101408", - "1" - ], - [ - "20054934960262983176880675919444457578562219675808407582143519621873973120773", - "14877415271301547911435683263206245199959943680225555496786470669330176961657", - "1" - ], - [ - "4215199263810110748751715719957184804379752373072771007598572158043965517488", - "5225943468606602818132879686778547605180105897615251160509064537462109826521", - "1" - ], - [ - "6250242626034734280813142093008675407723196706248829741247204621913994561803", - "1472231555266678689888727724824566171966416459791722465278225775922487343641", - "1" - ] - ], - [ - [ - "3047486363455933831148688762823238723024952519326207356549121929667745957778", - "20241836359289449005887237560564358543646542598344362915541027571505243817211", - "1" - ], - [ - "5965631918800530319167124148627450454569264331058008407732200168631989208657", - "20463557477532480934514091877628554948892025887087712764683631108388998871350", - "1" - ], - [ - "16605042322692983282732511249912403956057999815658038166796858627082222971215", - "12219061498275616585164456833410962809536084885494309093787669879221959361956", - "1" - ], - [ - "1548998572074037722622224303222294716243074837074272552644853986075252666508", - "10393312002885367652301897874262367916506364670364584602554176742602334134772", - "1" - ], - [ - "16180907689593358346406392015123900260925622357393826746385511046141256905390", - "12267326749885120640972074479210537480053065569337817484467225562817467244765", - "1" - ] - ], - [ - [ - "19590996174696909242575628014943555633938195923520472786993379268302478708283", - "2673753072556442230312995111304911178679525806396134504594492458566941824354", - "1" - ], - [ - "13411253172375451489380472831999887223592471057462692619008484995624281735092", - "17181767455563581254432161119660408482332423481128600038352147258951772423229", - "1" - ], - [ - "19138864631164378176055647711995352935065134904103255748190268290992108588628", - "14282526277736365863821375748687709839392307698935143595732632710176778519757", - "1" - ], - [ - "20183773658676161990469276414858234178608794783112866811307579993999118293429", - "5223464433544489066271184294750886227362580875255044558831927430970236355539", - "1" - ], - [ - "12333466991139269670298178539679773509487545471126920233507132846828588847444", - "3787586478923104354547687861486563468235879611952775292288436085429794222238", - "1" - ] - ], - [ - [ - "18580370382199518848261939652153768394883698461842792002922164533882262019935", - "20516185953882700254387267244708111605796661864845495645678049276372075842359", - "1" - ], - [ - "20041291712709610738573661974551517833120775539593003477018637287434210072702", - "6326630253906616820412999166182553773360987412889775567442543181359104720511", - "1" - ], - [ - "13268971611130152315428629919012388924225656285593904211561391821918930327614", - "9247437189452353488017802041158840512956111558640958728149597697508914590433", - "1" - ], - [ - "6267384495557139339708615182113725421733376438932580472141549274050146739549", - "1832264154031452148715318442722960696977572389206897240030908464579133134237", - "1" - ], - [ - "16650684165487873559901140599157559153018449083939294496255590830891994564285", - "14140282729498011406186082176268025578697081678243955538935501306868500498994", - "1" - ] - ], - [ - [ - "4247947150009812467217672970806328247513830308400387953244764907353849211641", - "14500381439127180474801393438175928191199696177607750163263715436006533630877", - "1" - ], - [ - "21213779524495874664157797605662894019112036728653622806607467354233012380232", - "1429370857470083395421401524518861545167550347090873730934256398864585069083", - "1" - ], - [ - "12465277751642747637430517396067173985821959773399832969105187923427872239200", - "4377704428607835904642653580543541241155601291484645500691968624389522190030", - "1" - ], - [ - "11283027832501128633761619552392013253304972822086786857121687098087331014745", - "21463394238922953607096052056881931791797740737164052798044623278557203313720", - "1" - ], - [ - "19687293493101130967741578773742597470558958652351513582962108464055656171331", - "4445165696525061401582979300506082669540223774145877762689724631935313716632", - "1" - ] - ], - [ - [ - "3388767735894417381503201756905214431625081913405504580464345986403824999889", - "21014112837214011009096825602791072748195337199912773858499588477762724153070", - "1" - ], - [ - "10521317016331497094903116740581271122844131442882845700567581775404872949272", - "13201921794561774338466680421903602920184688290946713194187958007088351657367", - "1" - ], - [ - "16170260722059932609965743383032703380650557609693540121262881902248073364496", - "6004983491336500911294872035126141746032033211872472427212274143945425740617", - "1" - ], - [ - "10275615677574391293596971122111363003313434841806630200532546038183081960924", - "5955568702561336410725734958627459212680756023420452791680213386065159525989", - "1" - ], - [ - "19059081014385850734732058652137664919364805650872154944590269874395511868415", - "19202365837673729366500417038229950532560250566916189579621883380623278182155", - "1" - ] - ], - [ - [ - "7856986171681248404396064225772749784181602218562773063185003409958949630985", - "11707218736744382138692483591389641607570557654489363179025201039696228471230", - "1" - ], - [ - "2902255937308264958973169948617099471543255757887963647238093192858290079050", - "4092153880227661899721872164083575597602963673456107552146583620177664115673", - "1" - ], - [ - "18380478859138320895837407377103009470968863533040661874531861881638854174636", - "14502773952184441371657781525836310753176308880224816843041318743809785835984", - "1" - ], - [ - "2781117248053224106149213822307598926495461873135153638774638501111353469325", - "3500056595279027698683405880585654897391289317486204483344715855049598477604", - "1" - ], - [ - "8880120765926282932795149634761705738498809569874317407549203808931092257005", - "19080036326648068547894941015038877788526324720587349784852594495705578761000", - "1" - ] - ], - [ - [ - "18427701611614193839908361166447988195308352665132182219164437649866377475111", - "5299493942596042045861137432338955179078182570752746487573709678936617478454", - "1" - ], - [ - "4188155714164125069834512529839479682516489319499446390214266838952761728656", - "2720966082507704094346897998659841489771837229143573083003847010258396944787", - "1" - ], - [ - "13256461570028177373135283778770729308216900804505379897951455548375840027026", - "10722074030307391322177899534114921764931623271723882054692012663305322382747", - "1" - ], - [ - "9824147497244652955949696442395586567974424828238608972020527958186701134273", - "15755269950882650791869946186461432242513999576056199368058858215068920022191", - "1" - ], - [ - "21172488506061181949536573476893375313339715931330476837156243346077173297265", - "13892434487977776248366965108031841947713544939953824768291380177301871559945", - "1" - ] - ], - [ - [ - "1452272927738590248356371174422184656932731110936062990115610832462181634644", - "3608050114233210789542189629343107890943266759827387991788718454179833288695", - "1" - ], - [ - "14798240452388909327945424685903532333765637883272751382037716636327236955001", - "10773894897711848209682368488916121016695006898681985691467605219098835500201", - "1" - ], - [ - "17204267933132009093604099819536245144503489322639121825381131096467570698650", - "7704298975420304156332734115679983371345754866278811368869074990486717531131", - "1" - ], - [ - "8060465662017324080560848316478407038163145149983639907596180500095598669247", - "20475082166427284188002500222093571716651248980245637602667562336751029856573", - "1" - ], - [ - "7457566682692308112726332096733260585025339741083447785327706250123165087868", - "11904519443874922292602150685069370036383697877657723976244907400392778002614", - "1" - ] - ], - [ - [ - "12628427235010608529869146871556870477182704310235373946877240509680742038961", - "15093298104438768585559335868663959710321348106117735180051519837845319121254", - "1" - ], - [ - "6593907467779318957599440584793099005109789224774644007604434924706249001015", - "18549596630007199540674697114946251030815675677713256327810772799104711621483", - "1" - ], - [ - "6271101737045248834759003849256661059806617144229427987717476992610974162336", - "355748132218964841305454070022507122319085542484477110563322753565651576458", - "1" - ], - [ - "2116139772133141967317791473319540620104888687412078412336248003979594158546", - "4004400204967325849492155713520296687406035356901102254880522534085890616486", - "1" - ], - [ - "4206647028595764233995379982714022410660284578620723510907006350595207905228", - "19380634286337609988098517090003334645113675227742745065381519159322795845003", - "1" - ] - ], - [ - [ - "2592407181901686208061988776764501828311271519595797153264758207470081204331", - "11847594161160074962679125411562687287595382335410213641115001866587988494499", - "1" - ], - [ - "3346927026869562921166545684451290646273836362895645367665514203662899621366", - "15758185693543979820528128025093553492246135914029575732836221618882836493143", - "1" - ], - [ - "20528686657810499188368147206002308531447185877994439397529705707372170337045", - "18025396678079701612906003769476076600196287001844168390936182972248852818155", - "1" - ], - [ - "9799815250059685769827017947834627563597884023490186073806184882963949644596", - "4998495094322372762314630336611134866447406022687118703953312157819349892603", - "1" - ], - [ - "16176535527670849161173306151058200762642157343823553073439957507563856439772", - "21877331533292960470552563236986670222564955589137303622102707801351340670855", - "1" - ] - ] - ] -} diff --git a/packages/proof/src/verify-proof.ts b/packages/proof/src/verify-proof.ts new file mode 100644 index 000000000..34b8a5e73 --- /dev/null +++ b/packages/proof/src/verify-proof.ts @@ -0,0 +1,49 @@ +import { + requireArray, + requireDefined, + requireNumber, + requireObject, + requireString +} from "@semaphore-protocol/utils/errors" +import { groth16 } from "snarkjs" +import { unpackGroth16Proof } from "@zk-kit/utils" +import hash from "./hash" +import { SemaphoreProof } from "./types" +import verificationKeys from "./verification-keys.json" + +/** + * Verifies whether a Semaphore proof is valid. Depending on the depth of the tree used to + * generate the proof, a different verification key will be used. + * @param proof The Semaphore proof. + * @returns True if the proof is valid, false otherwise. + */ +export default async function verifyProof(proof: SemaphoreProof): Promise { + requireDefined(proof, "proof") + requireObject(proof, "proof") + + const { merkleTreeDepth, merkleTreeRoot, nullifier, message, scope, points } = proof + + requireNumber(merkleTreeDepth, "proof.merkleTreeDepth") + requireString(merkleTreeRoot, "proof.merkleTreeRoot") + requireString(nullifier, "proof.nullifier") + requireString(message, "proof.message") + requireString(scope, "proof.scope") + requireArray(points, "proof.points") + + // TODO: support all tree depths after trusted-setup. + if (merkleTreeDepth < 1 || merkleTreeDepth > 12) { + throw new TypeError("The tree depth must be a number between 1 and 12") + } + + const verificationKey = { + ...verificationKeys, + vk_delta_2: verificationKeys.vk_delta_2[merkleTreeDepth - 1], + IC: verificationKeys.IC[merkleTreeDepth - 1] + } + + return groth16.verify( + verificationKey, + [merkleTreeRoot, nullifier, hash(message), hash(scope)], + unpackGroth16Proof(points) + ) +} diff --git a/packages/proof/src/verifyProof.ts b/packages/proof/src/verifyProof.ts deleted file mode 100644 index dc4cd3540..000000000 --- a/packages/proof/src/verifyProof.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { verify } from "@zk-kit/groth16" -import hash from "./hash" -import { SemaphoreProof } from "./types" -import unpackProof from "./unpackProof" -import verificationKeys from "./verificationKeys.json" - -/** - * Verifies a Semaphore proof. - * @param fullProof The SnarkJS Semaphore proof. - * @param treeDepth The Merkle tree depth. - * @returns True if the proof is valid, false otherwise. - */ -export default function verifyProof( - { merkleTreeRoot, nullifierHash, externalNullifier, signal, proof }: SemaphoreProof, - treeDepth: number -): Promise { - if (treeDepth < 16 || treeDepth > 32) { - throw new TypeError("The tree depth must be a number between 16 and 32") - } - - const verificationKey = { - ...verificationKeys, - vk_delta_2: verificationKeys.vk_delta_2[treeDepth - 16], - IC: verificationKeys.IC[treeDepth - 16] - } - - return verify(verificationKey, { - publicSignals: [merkleTreeRoot, nullifierHash, hash(signal), hash(externalNullifier)], - proof: unpackProof(proof) - }) -} diff --git a/packages/proof/tests/index.test.ts b/packages/proof/tests/index.test.ts new file mode 100644 index 000000000..3dd767092 --- /dev/null +++ b/packages/proof/tests/index.test.ts @@ -0,0 +1,74 @@ +import { Group } from "@semaphore-protocol/group" +import { Identity } from "@semaphore-protocol/identity" +import { getCurveFromName } from "ffjavascript" +import { SemaphoreProof, generateProof, verifyProof } from "../src" + +describe("Proof", () => { + const treeDepth = 10 + + const message = "Hello world" + const scope = "Scope" + + const identity = new Identity(42) + + let proof: SemaphoreProof + let curve: any + + beforeAll(async () => { + curve = await getCurveFromName("bn128") + }) + + afterAll(async () => { + await curve.terminate() + }) + + describe("# generateProof", () => { + it("Should not generate a Semaphore proof if the tree depth is not supported", async () => { + const group = new Group([BigInt(1), BigInt(2), identity.commitment]) + + const fun = () => generateProof(identity, group, message, scope, 13) + + await expect(fun).rejects.toThrow("tree depth must be") + }) + + it("Should not generate Semaphore proofs if the identity is not part of the group", async () => { + const group = new Group([BigInt(1), BigInt(2)]) + + const fun = () => generateProof(identity, group, message, scope, treeDepth) + + await expect(fun).rejects.toThrow("does not exist") + }) + + it("Should generate a Semaphore proof", async () => { + const group = new Group([BigInt(1), BigInt(2), identity.commitment]) + + proof = await generateProof(identity, group, message, scope, treeDepth) + + expect(typeof proof).toBe("object") + expect(proof.merkleTreeRoot).toBe(group.root) + }, 20000) + + it("Should generate a Semaphore proof passing a Merkle proof instead of a group", async () => { + const group = new Group([BigInt(1), BigInt(2), identity.commitment]) + + proof = await generateProof(identity, group.generateMerkleProof(2), message, scope, treeDepth) + + expect(typeof proof).toBe("object") + expect(proof.merkleTreeRoot).toBe(group.root) + }, 20000) + }) + + describe("# verifyProof", () => { + it("Should not verify a Semaphore proof if the tree depth is not supported", async () => { + const fun = () => verifyProof({ ...proof, merkleTreeDepth: 40 }) + + await expect(fun).rejects.toThrow("tree depth must be") + }) + + it("Should verify a Semaphore proof", async () => { + const response = await verifyProof(proof) + + expect(response).toBe(true) + }) + }) +}) diff --git a/packages/proof/tsconfig.json b/packages/proof/tsconfig.json index dfaf0c1ad..8c95393fa 100644 --- a/packages/proof/tsconfig.json +++ b/packages/proof/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "../../tsconfig.json", - "include": ["src", "rollup.config.ts"] + "include": ["src", "tests", "rollup.node.config.ts", "rollup.browser.config.ts"] } diff --git a/packages/utils/LICENSE b/packages/utils/LICENSE new file mode 100644 index 000000000..8ef16f7a5 --- /dev/null +++ b/packages/utils/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Ethereum Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/utils/README.md b/packages/utils/README.md new file mode 100644 index 000000000..6ec0653c0 --- /dev/null +++ b/packages/utils/README.md @@ -0,0 +1,79 @@ +

+

+ Semaphore utils +

+

A library to provide utility functions to the other Semaphore packages.

+

+ +

+ + + + + NPM license + + + NPM version + + + Downloads + + + Documentation typedoc + + + Linter eslint + + + Code style prettier + +

+ + + +## 🛠 Install + +### npm or yarn + +Install the `@semaphore-protocol/utils` package with npm: + +```bash +npm i @semaphore-protocol/utils +``` + +or yarn: + +```bash +yarn add @semaphore-protocol/utils +``` + +## 📜 Usage + +For more information on the functions and modules provided by `@semaphore-protocol/utils`, please refer to the [TypeDoc documentation](https://js.semaphore.pse.dev/modules/_semaphore_protocol_utils). + +```typescript +// You can import modules from the main bundle. +import { errors, types } from "@semaphore-protocol/utils" + +// Or by using conditional exports. +import { requireNumber } from "@semaphore-protocol/utils/errors" +import { isNumber } from "@semaphore-protocol/utils/types" +``` diff --git a/packages/utils/build.tsconfig.json b/packages/utils/build.tsconfig.json new file mode 100644 index 000000000..2d4a1d6da --- /dev/null +++ b/packages/utils/build.tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "declarationDir": "dist/types" + }, + "include": ["src"] +} diff --git a/packages/utils/package.json b/packages/utils/package.json new file mode 100644 index 000000000..8d1232e40 --- /dev/null +++ b/packages/utils/package.json @@ -0,0 +1,50 @@ +{ + "name": "@semaphore-protocol/utils", + "version": "4.0.0-alpha.8", + "description": "A library to provide utility functions to the other Semaphore packages.", + "type": "module", + "license": "MIT", + "main": "dist/index.js", + "types": "dist/types/index.d.ts", + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "require": "./dist/index.cjs", + "default": "./dist/index.js" + }, + "./errors": { + "types": "./dist/types/errors.d.ts", + "require": "./dist/lib.commonjs/errors.cjs", + "default": "./dist/lib.esm/errors.js" + }, + "./types": { + "types": "./dist/types/types.d.ts", + "require": "./dist/lib.commonjs/types.cjs", + "default": "./dist/lib.esm/types.js" + } + }, + "files": [ + "dist/", + "src/", + "LICENSE", + "README.md" + ], + "repository": "https://github.com/semaphore-protocol/semaphore", + "homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/utils", + "bugs": { + "url": "https://github.com/semaphore-protocol/semaphore.git/issues" + }, + "scripts": { + "build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript", + "prepublishOnly": "yarn build" + }, + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "rimraf": "^5.0.5", + "rollup": "^4.9.6", + "rollup-plugin-cleanup": "^3.2.1", + "rollup-plugin-typescript2": "^0.36.0" + } +} diff --git a/packages/utils/rollup.config.ts b/packages/utils/rollup.config.ts new file mode 100644 index 000000000..959386ff8 --- /dev/null +++ b/packages/utils/rollup.config.ts @@ -0,0 +1,36 @@ +import * as fs from "fs" +import cleanup from "rollup-plugin-cleanup" +import typescript from "rollup-plugin-typescript2" + +const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8")) +const banner = `/** + * @module ${pkg.name} + * @version ${pkg.version} + * @file ${pkg.description} + * @copyright Ethereum Foundation 2024 + * @license ${pkg.license} + * @see [Github]{@link ${pkg.homepage}} +*/` + +export default { + input: "src/index.ts", + output: [ + { file: pkg.exports["."].require, format: "cjs", banner, exports: "auto" }, + { file: pkg.exports["."].default, format: "es", banner }, + { + dir: "./dist/lib.commonjs", + format: "cjs", + banner, + preserveModules: true, + entryFileNames: "[name].cjs" + }, + { dir: "./dist/lib.esm", format: "es", banner, preserveModules: true } + ], + plugins: [ + typescript({ + tsconfig: "./build.tsconfig.json", + useTsconfigDeclarationDir: true + }), + cleanup({ comments: "jsdoc" }) + ] +} diff --git a/packages/utils/src/errors.ts b/packages/utils/src/errors.ts new file mode 100644 index 000000000..230d65c9a --- /dev/null +++ b/packages/utils/src/errors.ts @@ -0,0 +1,132 @@ +/** + * @module Errors + * This module is designed to provide utility functions for validating + * function parameters. It includes functions that throw type errors if + * the parameters do not meet specified criteria, such as being defined, + * a number, a string, a function, or an array. This module helps ensure + * that functions receive the correct types of inputs, enhancing code + * reliability and reducing runtime errors. + */ + +import { + SupportedType, + isArray, + isBigInt, + isDefined, + isFunction, + isNumber, + isObject, + isString, + isSupportedType, + isType, + isUint8Array +} from "./types" + +/** + * It throws a type error if the parameter value has not been defined. + * @param parameterValue The parameter value. + * @param parameterName The parameter name. + */ +export function requireDefined(parameterValue: any, parameterName: string) { + if (!isDefined(parameterValue)) { + throw new TypeError(`Parameter '${parameterName}' is not defined`) + } +} + +/** + * It throws a type error if the parameter value is not a number. + * @param parameterValue The parameter value. + * @param parameterName The parameter name. + */ +export function requireNumber(parameterValue: number, parameterName: string) { + if (!isNumber(parameterValue)) { + throw new TypeError(`Parameter '${parameterName}' is not a number`) + } +} + +/** + * It throws a type error if the parameter value is not a string. + * @param parameterValue The parameter value. + * @param parameterName The parameter name. + */ +export function requireString(parameterValue: string, parameterName: string) { + if (!isString(parameterValue)) { + throw new TypeError(`Parameter '${parameterName}' is not a string`) + } +} + +/** + * It throws a type error if the parameter value is not a function. + * @param parameterValue The parameter value. + * @param parameterName The parameter name. + */ +export function requireFunction(parameterValue: Function, parameterName: string) { + if (!isFunction(parameterValue)) { + throw new TypeError(`Parameter '${parameterName}' is not a function`) + } +} + +/** + * It throws a type error if the parameter value is not an array. + * @param parameterValue The parameter value. + * @param parameterName The parameter name. + */ +export function requireArray(parameterValue: any[], parameterName: string) { + if (!isArray(parameterValue)) { + throw new TypeError(`Parameter '${parameterName}' is not an array`) + } +} + +/** + * It throws a type error if the parameter value is not a uint8array. + * @param parameterValue The parameter value. + * @param parameterName The parameter name. + */ +export function requireUint8Array(parameterValue: Uint8Array, parameterName: string) { + if (!isUint8Array(parameterValue)) { + throw new TypeError(`Parameter '${parameterName}' is not a Uint8Array`) + } +} + +/** + * It throws a type error if the parameter value is not an object. + * @param parameterValue The parameter value. + * @param parameterName The parameter name. + */ +export function requireObject(parameterValue: object, parameterName: string) { + if (!isObject(parameterValue)) { + throw new TypeError(`Parameter '${parameterName}' is not an object`) + } +} + +/** + * It throws a type error if the parameter value is not a bigint. + * @param parameterValue The parameter value. + * @param parameterName The parameter name. + */ +export function requireBigInt(parameterValue: bigint, parameterName: string) { + if (!isBigInt(parameterValue)) { + throw new TypeError(`Parameter '${parameterName}' is not a bigint`) + } +} + +/** + * It throws a type error if the parameter value type is not part of the list of types. + * @param parameterValue The parameter value. + * @param parameterName The parameter name. + */ +export function requireTypes(parameterValue: any, parameterName: string, types: SupportedType[]) { + for (const type of types) { + if (!isSupportedType(type)) { + throw new Error(`Type '${type}' is not supported`) + } + } + + for (const type of types) { + if (isType(parameterValue, type)) { + return + } + } + + throw new TypeError(`Parameter '${parameterName}' is none of the following types: ${types.join(", ")}`) +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts new file mode 100644 index 000000000..a76016484 --- /dev/null +++ b/packages/utils/src/index.ts @@ -0,0 +1,4 @@ +import * as errors from "./errors" +import * as types from "./types" + +export { errors, types } diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts new file mode 100644 index 000000000..99c6b27b6 --- /dev/null +++ b/packages/utils/src/types.ts @@ -0,0 +1,123 @@ +/** + * @module Types + * This module provides utility functions to check data types. + * It defines a set of supported types and includes functions to check if + * a value is defined and if it matches a supported type. These functions + * are useful for type checking and validation in the other libraries, + * enhancing code robustness and reliability. + */ + +// The list of types supported by this utility functions. +const supportedTypes = ["number", "string", "function", "array", "uint8array", "object", "bigint"] as const + +// Type extracted from the list above. +export type SupportedType = (typeof supportedTypes)[number] + +/** + * It returns true if the value is defined, false otherwise. + * @param value The value to be checked. + * @returns True or false. + */ +export function isDefined(value: any): boolean { + return typeof value !== "undefined" +} + +/** + * It returns true if the value is a number, false otherwise. + * @param value The value to be checked. + * @returns True or false. + */ +export function isNumber(value: any): boolean { + return typeof value === "number" +} + +/** + * It returns true if the value is a string, false otherwise. + * @param value The value to be checked. + * @returns True or false. + */ +export function isString(value: any): boolean { + return typeof value === "string" +} + +/** + * It returns true if the value is a function, false otherwise. + * @param value The value to be checked. + * @returns True or false. + */ +export function isFunction(value: any): boolean { + return typeof value === "function" +} + +/** + * It returns true if the value is an array, false otherwise. + * @param value The value to be checked. + * @returns True or false. + */ +export function isArray(value: any): boolean { + return typeof value === "object" && Array.isArray(value) +} + +/** + * It returns true if the value is a uint8array, false otherwise. + * @param value The value to be checked. + * @returns True or false. + */ +export function isUint8Array(value: any): boolean { + return value instanceof Uint8Array +} + +/** + * It returns true if the value is an object, false otherwise. + * @param value The value to be checked. + * @returns True or false. + */ +export function isObject(value: any): boolean { + return typeof value === "object" +} + +/** + * It returns true if the value is a bigint, false otherwise. + * @param value The value to be checked. + * @returns True or false. + */ +export function isBigInt(value: any): boolean { + return typeof value === "bigint" +} + +/** + * It returns true if the value type is the same as the type passed + * as the second parameter, false otherwise. + * @param type The expected type. + * @returns True or false. + */ +export function isType(value: any, type: SupportedType): boolean { + switch (type) { + case "number": + return isNumber(value) + case "string": + return isString(value) + case "function": + return isFunction(value) + case "array": + return isArray(value) + case "uint8array": + return isUint8Array(value) + case "object": + return isObject(value) + case "bigint": + return isBigInt(value) + default: + return false + } +} + +/** + * Return true if the type is being supported by this utility + * functions, false otherwise. + * @param type The type to be checked. + * @returns True or false + */ +export function isSupportedType(type: string): type is SupportedType { + return (supportedTypes as readonly string[]).includes(type) +} diff --git a/packages/utils/tests/index.test.ts b/packages/utils/tests/index.test.ts new file mode 100644 index 000000000..27572011c --- /dev/null +++ b/packages/utils/tests/index.test.ts @@ -0,0 +1,206 @@ +import { errors, types } from "../src" + +describe("Utils", () => { + describe("# types", () => { + it("Should return true if the value is a number", () => { + expect(types.isNumber(1)).toBeTruthy() + }) + + it("Should return false if the value is not a number", () => { + expect(types.isNumber("string")).toBeFalsy() + }) + + it("Should return true if the value is a string", () => { + expect(types.isString("string")).toBeTruthy() + }) + + it("Should return false if the value is not a string", () => { + expect(types.isString(1)).toBeFalsy() + }) + + it("Should return true if the value is a function", () => { + expect(types.isFunction(() => true)).toBeTruthy() + }) + + it("Should return false if the value is not a function", () => { + expect(types.isFunction(1)).toBeFalsy() + }) + + it("Should return true if the value is an array", () => { + expect(types.isArray([])).toBeTruthy() + }) + + it("Should return false if the value is not an array", () => { + expect(types.isArray(1)).toBeFalsy() + }) + + it("Should return true if the value is a uint8array", () => { + expect(types.isUint8Array(new Uint8Array([]))).toBeTruthy() + }) + + it("Should return false if the value is not a uint8array", () => { + expect(types.isUint8Array(1)).toBeFalsy() + }) + + it("Should return true if the value is an object", () => { + expect(types.isObject({})).toBeTruthy() + }) + + it("Should return false if the value is not an object", () => { + expect(types.isObject(1)).toBeFalsy() + }) + + it("Should return true if the value is a bigint", () => { + expect(types.isBigInt(BigInt(1))).toBeTruthy() + }) + + it("Should return false if the value is not a bigint", () => { + expect(types.isBigInt(1)).toBeFalsy() + }) + + it("Should return true if the value type is the one expected", () => { + expect(types.isType(1, "number")).toBeTruthy() + expect(types.isType("string", "string")).toBeTruthy() + expect(types.isType(() => true, "function")).toBeTruthy() + expect(types.isType([], "array")).toBeTruthy() + expect(types.isType(new Uint8Array([]), "uint8array")).toBeTruthy() + expect(types.isType({}, "object")).toBeTruthy() + expect(types.isType(BigInt(1), "bigint")).toBeTruthy() + }) + + it("Should return false if the value type is not the one expected or is not supported", () => { + expect(types.isType("string", "number")).toBeFalsy() + expect(types.isType(1, "string")).toBeFalsy() + expect(types.isType(1, "function")).toBeFalsy() + expect(types.isType(1, "array")).toBeFalsy() + expect(types.isType(1, "uint8array")).toBeFalsy() + expect(types.isType(1, "object")).toBeFalsy() + expect(types.isType(1, "bigint")).toBeFalsy() + expect(types.isType(1, "type" as any)).toBeFalsy() + }) + + it("Should return true if the type is supported", () => { + expect(types.isSupportedType("number")).toBeTruthy() + }) + + it("Should return false if the type is not supported", () => { + expect(types.isSupportedType("type")).toBeFalsy() + }) + }) + + describe("# errors", () => { + it("Should throw an error if the parameter is not defined", () => { + const fun = () => errors.requireDefined(undefined as any, "parameter") + + expect(fun).toThrow("Parameter 'parameter' is not defined") + }) + + it("Should not throw an error if the parameter is defined", () => { + const fun = () => errors.requireDefined(1, "parameter") + + expect(fun).not.toThrow() + }) + + it("Should throw an error if the parameter is not a number", () => { + const fun = () => errors.requireNumber("euo" as any, "parameter") + + expect(fun).toThrow("Parameter 'parameter' is not a number") + }) + + it("Should not throw an error if the parameter is a number", () => { + const fun = () => errors.requireNumber(1, "parameter") + + expect(fun).not.toThrow() + }) + + it("Should throw an error if the parameter is not a string", () => { + const fun = () => errors.requireString(1 as any, "parameter") + + expect(fun).toThrow("Parameter 'parameter' is not a string") + }) + + it("Should not throw an error if the parameter is a string", () => { + const fun = () => errors.requireString("string", "parameter") + + expect(fun).not.toThrow() + }) + + it("Should throw an error if the parameter is not an array", () => { + const fun = () => errors.requireArray(1 as any, "parameter") + + expect(fun).toThrow("Parameter 'parameter' is not an array") + }) + + it("Should not throw an error if the parameter is an array", () => { + const fun = () => errors.requireArray([], "parameter") + + expect(fun).not.toThrow() + }) + + it("Should throw an error if the parameter is not a uint8array", () => { + const fun = () => errors.requireUint8Array([] as any, "parameter") + + expect(fun).toThrow("Parameter 'parameter' is not a Uint8Array") + }) + + it("Should not throw an error if the parameter is a uint8array", () => { + const fun = () => errors.requireUint8Array(new Uint8Array([]), "parameter") + + expect(fun).not.toThrow() + }) + + it("Should throw an error if the parameter is not a function", () => { + const fun = () => errors.requireFunction(1 as any, "parameter") + + expect(fun).toThrow("Parameter 'parameter' is not a function") + }) + + it("Should not throw an error if the parameter is a function", () => { + const fun = () => errors.requireFunction(() => true, "parameter") + + expect(fun).not.toThrow() + }) + + it("Should throw an error if the parameter is not an object", () => { + const fun = () => errors.requireObject(1 as any, "parameter") + + expect(fun).toThrow("Parameter 'parameter' is not an object") + }) + + it("Should not throw an error if the parameter is an object", () => { + const fun = () => errors.requireObject({}, "parameter") + + expect(fun).not.toThrow() + }) + + it("Should throw an error if the parameter is not a bigint", () => { + const fun = () => errors.requireBigInt(1 as any, "parameter") + + expect(fun).toThrow("Parameter 'parameter' is not a bigint") + }) + + it("Should not throw an error if the parameter is a bigint", () => { + const fun = () => errors.requireBigInt(BigInt(1), "parameter") + + expect(fun).not.toThrow() + }) + + it("Should throw an error if the parameter is neither a function nor a number", () => { + const fun = () => errors.requireTypes("string", "parameter", ["function", "number"]) + + expect(fun).toThrow("Parameter 'parameter' is none of the following types: function, number") + }) + + it("Should not throw an error if the parameter is either a string or an array", () => { + const fun = () => errors.requireTypes("string", "parameter", ["string", "array"]) + + expect(fun).not.toThrow() + }) + + it("Should throw an error if the parameter types are not supported", () => { + const fun = () => errors.requireTypes("string", "parameter", ["string", "type" as any]) + + expect(fun).toThrow("Type 'type' is not supported") + }) + }) +}) diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json new file mode 100644 index 000000000..71510a096 --- /dev/null +++ b/packages/utils/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "tests", "rollup.config.ts"] +} diff --git a/packages/utils/typedoc.json b/packages/utils/typedoc.json new file mode 100644 index 000000000..77a471c91 --- /dev/null +++ b/packages/utils/typedoc.json @@ -0,0 +1,3 @@ +{ + "entryPoints": ["src/index.ts"] +} diff --git a/scripts/clean-apps.ts b/scripts/clean-apps.ts new file mode 100644 index 000000000..e4ab667f3 --- /dev/null +++ b/scripts/clean-apps.ts @@ -0,0 +1,30 @@ +import { readdirSync, rmSync } from "fs" + +const folderName = "apps" + +const gitIgnored = [ + "node_modules", + "build", + ".next", + "generated", + "out", + "subgraph.yaml", + "tests/.bin", + ".docusaurus", + ".cache-loader" +] + +async function main() { + const apps = readdirSync(folderName, { withFileTypes: true }) + .filter((file) => file.isDirectory()) + .map((dir) => dir.name) + + apps.map((app) => gitIgnored.map((f) => rmSync(`${folderName}/${app}/${f}`, { recursive: true, force: true }))) +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/clean-cli-templates.ts b/scripts/clean-cli-templates.ts new file mode 100644 index 000000000..ae61c94b5 --- /dev/null +++ b/scripts/clean-cli-templates.ts @@ -0,0 +1,26 @@ +import { rmSync } from "fs" + +const folderName = "packages" + +const gitIgnored = [ + "contracts/build", + "contracts/cache", + "contracts/node_modules", + "web-app/node_modules", + "web-app/.next" +] + +const packages = ["cli-template-monorepo-ethers", "cli-template-monorepo-subgraph"] + +async function main() { + packages.map((pkg) => + gitIgnored.map((f) => rmSync(`${folderName}/${pkg}/apps/${f}`, { recursive: true, force: true })) + ) +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/clean-packages.ts b/scripts/clean-packages.ts new file mode 100644 index 000000000..3aefd8c7a --- /dev/null +++ b/scripts/clean-packages.ts @@ -0,0 +1,23 @@ +import { readdirSync, rmSync } from "fs" + +const folderName = "packages" + +const gitIgnored = ["node_modules", "dist", "build", "ptau", "artifacts", "typechain-types", "cache"] + +async function main() { + const apps = readdirSync(folderName, { withFileTypes: true }) + .filter((file) => file.isDirectory()) + .map((dir) => dir.name) + + apps.map((app) => gitIgnored.map((f) => rmSync(`${folderName}/${app}/${f}`, { recursive: true, force: true }))) + + rmSync(`${folderName}/circuit/main`, { recursive: true, force: true }) + rmSync(`${folderName}/circuit/test`, { recursive: true, force: true }) +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/download-snark-artifacts.ts b/scripts/download-snark-artifacts.ts deleted file mode 100644 index 5e0bee63e..000000000 --- a/scripts/download-snark-artifacts.ts +++ /dev/null @@ -1,30 +0,0 @@ -import dotenv from "dotenv" -import download from "download" - -dotenv.config() - -async function main() { - const snarkArtifactsPath = "./snark-artifacts" - - if (process.env.ALL_SNARK_ARTIFACTS === "true") { - const url = `https://www.trusted-setup-pse.org/semaphore/semaphore.zip` - - await download(url, snarkArtifactsPath, { - extract: true - }) - } else { - const treeDepth = process.env.TREE_DEPTH || 20 - const url = `https://www.trusted-setup-pse.org/semaphore/${treeDepth}` - - await download(`${url}/semaphore.wasm`, `${snarkArtifactsPath}/${treeDepth}`) - await download(`${url}/semaphore.zkey`, `${snarkArtifactsPath}/${treeDepth}`) - await download(`${url}/semaphore.json`, `${snarkArtifactsPath}/${treeDepth}`) - } -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error) - process.exit(1) - }) diff --git a/scripts/remove-template-files.ts b/scripts/remove-template-files.ts deleted file mode 100644 index 8f00c2feb..000000000 --- a/scripts/remove-template-files.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { rmSync } from "fs" - -async function main() { - const templates = ["cli-template-monorepo-ethers", "cli-template-monorepo-subgraph"] - const files: string[] = [ - "contracts/build", - "contracts/cache", - "contracts/node_modules", - "web-app/node_modules", - "web-app/.next", - "web-app/next-env.d.ts" - ] - - templates.map((template) => - files.map((file) => rmSync(`packages/${template}/apps/${file}`, { recursive: true, force: true })) - ) -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error) - process.exit(1) - }) diff --git a/tsconfig.json b/tsconfig.json index e0e4c0aa4..ab2cb8b4d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,8 +3,8 @@ "baseUrl": ".", "strict": true, "target": "ES5", - "module": "ES6", - "moduleResolution": "node", + "module": "ESNext", + "moduleResolution": "Bundler", "esModuleInterop": true, "preserveConstEnums": true, "resolveJsonModule": true, @@ -14,7 +14,7 @@ "declarationDir": "types", "typeRoots": ["node_modules/@types", "types"], "paths": { - "@semaphore-protocol/*": ["packages/*/src"] + "@semaphore-protocol/*": ["./packages/*/src"] } }, "ts-node": { diff --git a/yarn.lock.REMOVED.git-id b/yarn.lock.REMOVED.git-id index dec4649dc..78468ef9b 100644 --- a/yarn.lock.REMOVED.git-id +++ b/yarn.lock.REMOVED.git-id @@ -1 +1 @@ -60c0c1a25ca610528015fc7d8f162989ecc25397 \ No newline at end of file +18f4d257a9272ad02f3dc1ebd8ae8494356ab6f3 \ No newline at end of file