Skip to content

Commit

Permalink
Ethereum Group PCD
Browse files Browse the repository at this point in the history
Onboarding local dev fixes

Fix passport-server:dev

concurrently

concurrently

Ethereum Group PCD

Add test

more test

tests

progress

Ethereum Address Ownership example in add-pcd.tsx

small fix

switch to merkleproof

eth group pcd in add-pcd

smol fix

small rename

Fix ethereum ownership tests and ethereum group ownership tests

remove useNativeBigInt

address PR comments

address pr comments
  • Loading branch information
djma committed Jul 17, 2023
1 parent bf91f9b commit cc70924
Show file tree
Hide file tree
Showing 30 changed files with 1,403 additions and 113 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ Some of these packages are used to share development configuration between the d
- [`@pcd/semaphore-identity-pcd`](packages/semaphore-identity-pcd): a 'self-evident' PCD, representing the public and private components of a Semaphore identity
- [`@pcd/semaphore-signature-pcd`](packages/semaphore-signature-pcd): like `@pcd/semaphore-group-pcd`, but with a more specific purpose of using the semaphore protocol to 'sign' a particular string message on behalf of a particular revealed commitment id.
- [`@pcd/webauthn-pcd`](packages/webauthn-pcd): a pcd that can be used to make claims about [WebAuthn](https://webauthn.guide/) attestations, i.e. signing a particular challenge with a fingerprint, Yubikey, or another [authorization gesture](https://www.w3.org/TR/webauthn-2/#authorization-gesture).
- [`@pcd/ethereum-ownership-pcd`](packages/ethereum-ownership-pcd): a pcd that can be used to claim that a Semaphore identity knows the private key of an ethereum address.
- [`@pcd/ethereum-group-pcd`](packages/ethereum-group-pcd): a pcd that can be used to claim that a Semaphore identity knows the private key of an ethereum address or public key that is part of a merkle group of addresses or public keys, without revealing the specific one.
- ... more to come!

#### shared code packages
Expand Down
1 change: 1 addition & 0 deletions apps/consumer-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"lint": "next lint"
},
"dependencies": {
"@pcd/ethereum-group-pcd": "0.0.1",
"@pcd/ethereum-ownership-pcd": "0.6.0",
"@pcd/passport-interface": "0.6.0",
"@pcd/pcd-types": "0.6.0",
Expand Down
188 changes: 187 additions & 1 deletion apps/consumer-client/pages/examples/add-pcd.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
import {
EthereumGroupPCDPackage,
getRawPubKeyBuffer,
GroupType,
} from "@pcd/ethereum-group-pcd";
import { EthereumOwnershipPCDPackage } from "@pcd/ethereum-ownership-pcd";
import {
constructPassportPcdAddRequestUrl,
constructPassportPcdProveAndAddRequestUrl,
openSignedZuzaluSignInPopup,
usePassportPopupMessages,
} from "@pcd/passport-interface";
import { ArgumentTypeName } from "@pcd/pcd-types";
import { ArgumentTypeName, SerializedPCD } from "@pcd/pcd-types";
import { SemaphoreGroupPCDPackage } from "@pcd/semaphore-group-pcd";
import { SemaphoreIdentityPCDPackage } from "@pcd/semaphore-identity-pcd";
import { SemaphoreSignaturePCDPackage } from "@pcd/semaphore-signature-pcd";
import { WebAuthnPCDPackage } from "@pcd/webauthn-pcd";
import { Poseidon, Tree } from "@personaelabs/spartan-ecdsa";
import { Identity } from "@semaphore-protocol/identity";
import { startRegistration } from "@simplewebauthn/browser";
import {
generateRegistrationOptions,
verifyRegistrationResponse,
} from "@simplewebauthn/server";
import { ethers } from "ethers";
import JSONBig from "json-bigint";
import { useEffect, useState } from "react";
import { HomeLink } from "../../components/Core";
import { ExampleContainer } from "../../components/ExamplePage";
import { ZUPASS_URL, ZUZALU_SEMAPHORE_GROUP_URL } from "../../src/constants";
Expand Down Expand Up @@ -62,11 +74,185 @@ export default function Page() {
<button onClick={addWebAuthnPCD}>
add a new webauthn credential to the passport
</button>
<br />
<br />
<AddEthAddrPCDButton />
<br />
<br />
<AddEthGroupPCDButton />
</ExampleContainer>
</div>
);
}

function AddEthAddrPCDButton() {
const [pcdStr] = usePassportPopupMessages();
const [isActive, setIsActive] = useState(false);

useEffect(() => {
if (!pcdStr) return;
if (!isActive) return;

const parsed = JSON.parse(pcdStr) as SerializedPCD;

const ethereum = (window as any).ethereum;
const provider = new ethers.providers.Web3Provider(ethereum);
if (!ethereum) {
alert("Please install MetaMask to use this dApp!");
}

(async function () {
await ethereum.request({ method: "eth_requestAccounts" });
const pcd = await SemaphoreSignaturePCDPackage.deserialize(parsed.pcd);
const signature = await provider
.getSigner()
.signMessage(pcd.claim.identityCommitment);

const popupUrl = window.location.origin + "/popup";

const proofUrl = constructPassportPcdProveAndAddRequestUrl<
typeof EthereumOwnershipPCDPackage
>(ZUPASS_URL, popupUrl, EthereumOwnershipPCDPackage.name, {
identity: {
argumentType: ArgumentTypeName.PCD,
pcdType: SemaphoreIdentityPCDPackage.name,
value: undefined,
userProvided: true,
description:
"The Semaphore Identity which you are proving owns the given Ethereum address.",
},
ethereumAddress: {
argumentType: ArgumentTypeName.String,
value: await provider.getSigner().getAddress(),
},
ethereumSignatureOfCommitment: {
argumentType: ArgumentTypeName.String,
value: signature,
},
});

sendPassportRequest(proofUrl);
})();

setIsActive(false);
}, [pcdStr, isActive]);

return (
<button
onClick={() => {
setIsActive(true);
zupassSignIn("eth-pcd");
}}
>
add a new Ethereum address to the passport
</button>
);
}

async function zupassSignIn(originalSiteName: string) {
openSignedZuzaluSignInPopup(
ZUPASS_URL,
window.location.origin + "/popup",
originalSiteName
);
}

function AddEthGroupPCDButton() {
const [pcdStr] = usePassportPopupMessages();
const [isActive, setIsActive] = useState(false);

useEffect(() => {
if (!pcdStr) return;
if (!isActive) return;

const parsed = JSON.parse(pcdStr) as SerializedPCD;

const ethereum = (window as any).ethereum;
const provider = new ethers.providers.Web3Provider(ethereum);
if (!ethereum) {
alert("Please install MetaMask to use this dApp!");
}

(async function () {
await ethereum.request({ method: "eth_requestAccounts" });
const pcd = await SemaphoreSignaturePCDPackage.deserialize(parsed.pcd);

const msgHash = Buffer.from(
ethers.utils.hashMessage(pcd.claim.identityCommitment).slice(2),
"hex"
);
const signatureOfIdentityCommitment = await provider
.getSigner()
.signMessage(pcd.claim.identityCommitment);

const poseidon = new Poseidon();
await poseidon.initWasm();
const treeDepth = 20; // Provided circuits have tree depth = 20
const pubKeyTree = new Tree(treeDepth, poseidon);

// Add some public keys to the tree
for (const member of [
"0x04b4d5188949bf70c4db5e965a9ea67b80407e8ee7fa3a260ccf86e9c0395fe82cba155fdff55829b3c862322aba402d00b563861b603879ee8ae211c34257d4ad",
"0x042d21e6aa2021a991a82d08591fa0528d0bebe4ac9a34d851a74507327d930dec217380bd602fe48a143bb21106ab274d6a51aff396f0e4f7e1e3a8a673d46d83",
]) {
pubKeyTree.insert(poseidon.hashPubKey(getRawPubKeyBuffer(member)));
}
// Add the prover's public key to the tree
const proverPubkeyBuffer: Buffer = getRawPubKeyBuffer(
ethers.utils.recoverPublicKey(msgHash, signatureOfIdentityCommitment)
);
pubKeyTree.insert(poseidon.hashPubKey(proverPubkeyBuffer));
const pubKeyIndex = pubKeyTree.indexOf(
poseidon.hashPubKey(proverPubkeyBuffer)
); // == 2 in this test

// Prove membership of the prover's public key in the tree
const merkleProof = pubKeyTree.createProof(pubKeyIndex);

const popupUrl = window.location.origin + "/popup";
const proofUrl = constructPassportPcdProveAndAddRequestUrl<
typeof EthereumGroupPCDPackage
>(ZUPASS_URL, popupUrl, EthereumGroupPCDPackage.name, {
identity: {
argumentType: ArgumentTypeName.PCD,
pcdType: SemaphoreIdentityPCDPackage.name,
value: undefined,
userProvided: true,
description:
"The Semaphore Identity which you are signing the message.",
},
groupType: {
argumentType: ArgumentTypeName.String,
value: GroupType.PUBLICKEY,
},
signatureOfIdentityCommitment: {
argumentType: ArgumentTypeName.String,
value: signatureOfIdentityCommitment,
},
merkleProof: {
argumentType: ArgumentTypeName.String,
value: JSONBig({ useNativeBigInt: true }).stringify(merkleProof),
},
});

sendPassportRequest(proofUrl);
})();

setIsActive(false);
}, [pcdStr, isActive]);

return (
<button
onClick={() => {
setIsActive(true);
zupassSignIn("eth-group-pcd");
}}
>
add a new Ethereum Group Membership to the passport
</button>
);
}

async function addGroupMembershipProofPCD() {
const url = constructPassportPcdProveAndAddRequestUrl<
typeof SemaphoreGroupPCDPackage
Expand Down
22 changes: 22 additions & 0 deletions apps/passport-client/src/pcdPackages.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { EthereumGroupPCDPackage } from "@pcd/ethereum-group-pcd";
import { EthereumOwnershipPCDPackage } from "@pcd/ethereum-ownership-pcd";
import { HaLoNoncePCDPackage } from "@pcd/halo-nonce-pcd";
import { PCDPackage } from "@pcd/pcd-types";
Expand Down Expand Up @@ -41,6 +42,26 @@ async function loadPackages(): Promise<PCDPackage[]> {
zkeyFilePath: "/semaphore-artifacts/16.zkey",
});

await EthereumGroupPCDPackage.init({
wasmFilePath: "/semaphore-artifacts/16.wasm",
zkeyFilePath: "/semaphore-artifacts/16.zkey",

// TODO: update these to point to pcd pass' static server
addrMembershipConfig: {
circuit:
"https://storage.googleapis.com/personae-proving-keys/membership/addr_membership.circuit",
witnessGenWasm:
"https://storage.googleapis.com/personae-proving-keys/membership/addr_membership.wasm",
},

pubkeyMembershipConfig: {
circuit:
"https://storage.googleapis.com/personae-proving-keys/membership/pubkey_membership.circuit",
witnessGenWasm:
"https://storage.googleapis.com/personae-proving-keys/membership/pubkey_membership.wasm",
},
});

await RLNPCDPackage.init({
wasmFilePath: SERVER_STATIC_URL + "rln-artifacts/16.wasm",
zkeyFilePath: SERVER_STATIC_URL + "rln-artifacts/16.zkey",
Expand All @@ -55,6 +76,7 @@ async function loadPackages(): Promise<PCDPackage[]> {
SemaphoreIdentityPCDPackage,
SemaphoreSignaturePCDPackage,
EthereumOwnershipPCDPackage,
EthereumGroupPCDPackage,
JubJubSignaturePCDPackage,
RLNPCDPackage,
WebAuthnPCDPackage,
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
4 changes: 4 additions & 0 deletions packages/ethereum-group-pcd/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
extends: ["@pcd/eslint-config-custom"],
root: true,
};
5 changes: 5 additions & 0 deletions packages/ethereum-group-pcd/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.js
*.d.ts
*.ts.map
!.eslintrc.js
dist
7 changes: 7 additions & 0 deletions packages/ethereum-group-pcd/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# @pcd/ethereum-group-pcd

## 0.0.1

### Patch Changes

- Hello world
17 changes: 17 additions & 0 deletions packages/ethereum-group-pcd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# `@pcd/ethereum-group-pcd`

PCD which proves that a particular Semaphore Identity owns a particular ethereum address that is part of an address merkle set or public key merkle set, without revealing the ethereum address.

It uses an optimized group ecdsa zk [circuit](https://github.com/personaelabs/spartan-ecdsa) written by Personae Labs.

By pre-computing a set of Ethereum addresses that satisfy a particular property, one can prove that property about themselves without revealing their exact address. For example, proving that one:

- owns a NounDao (punk, or etc) NFT at a particular block
- made a transaction before 2020
- made at least 100 transactions
- owns at least 10 eth
- used tornado cash when it was cool
- has been validated by proof-of-humanity
- participated in a conference that issued a POAP

If you always use one address, proving multiple properties about yourself will narrow the anonymity set. However, if you use multiple addresses, you can own multiple PCDs without narrowing the anonymity set.
Loading

0 comments on commit cc70924

Please sign in to comment.