Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add webauthn-pcd package #134

Merged
merged 15 commits into from
May 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,27 @@

## What is the Zuzalu Passport?

*This README is generally aimed at developers interested in building on top of the Zuzalu Passport. If you are a resident looking for instructions on how to set up your Passport, visit [this link](https://docs.google.com/document/d/1pADZ8ixBkKkJcw3NA1dNMhrB6dwWGfFx6H92IV6dKuU/edit?usp=sharing).*
_This README is generally aimed at developers interested in building on top of the Zuzalu Passport. If you are a resident looking for instructions on how to set up your Passport, visit [this link](https://docs.google.com/document/d/1pADZ8ixBkKkJcw3NA1dNMhrB6dwWGfFx6H92IV6dKuU/edit?usp=sharing)._

The [Zuzalu Passport](https://zupass.org) allows Zuzalu residents to store personal data relating to Zuzalu identity, reputation, activity, and more, and to share any part of this data with any (physical and digital) Zuzalu service or app of their choosing. Zuzalu services may include anything built by Zuzalu administrators or other residents: physical authentication procedures, the Zuzalu website, Zuzalu message boards, Zuzalu governance infrastructure, a resident *Mafia* game, a community matchmaking service, a community newsletter, and more.
The [Zuzalu Passport](https://zupass.org) allows Zuzalu residents to store personal data relating to Zuzalu identity, reputation, activity, and more, and to share any part of this data with any (physical and digital) Zuzalu service or app of their choosing. Zuzalu services may include anything built by Zuzalu administrators or other residents: physical authentication procedures, the Zuzalu website, Zuzalu message boards, Zuzalu governance infrastructure, a resident _Mafia_ game, a community matchmaking service, a community newsletter, and more.

**Every active Zuzalu resident and visitor has created a Zuzalu Passport, making it a simple but powerful "base" for anyone to build and experiment on top of.** The goal of the Zuzalu Passport is to help the Zuzalu community collectively build out community infrastructure over the course of the next two months, by providing a solid foundation and by onboarding people onto new tools enabled by zero-knowledge.

Under the hood, the Zuzalu Passport stores cryptographically-manipulable data such as keypairs, credentials, attestations, and more, and uses a very lightweight standard for arbitrary zero-knowledge proofs to pass along this data to applications. ZKPs enable three critical features in the Passport system:

- **Any Zuzalu resident can build applications that use the Passport system**, without needing permission from Zuzalu organizers or Passport maintainers. There are no API keys, centralized servers, proprietary standards, special tokens, or gated endpoints* that you need approval for in order to build an experimental community governance project that anyone with a Zuzalu Passport can participate in. With the Passport architecture, application developers can simply give or request data from users directly.
- **Any Zuzalu resident can build applications that use the Passport system**, without needing permission from Zuzalu organizers or Passport maintainers. There are no API keys, centralized servers, proprietary standards, special tokens, or gated endpoints\* that you need approval for in order to build an experimental community governance project that anyone with a Zuzalu Passport can participate in. With the Passport architecture, application developers can simply give or request data from users directly.
- **Zuzalu applications are interoperable by default, and can understand and "talk to" one another without the need for special permissioning.** For example, one resident could build a message board where posters can accumulate "Zuzalu karma" for posting high-quality content, another resident can build a "private POAPs" service allowing Zuzalu event attendees to prove participation in community events, and a third resident could build a private polling app where users with either more "Zuzalu karma" OR provably high community event participation can cast votes carrying more voting weight--without the three application builders having to coordinate at all!
- **Users store and control their own data.** Zuzalu user data is by default only accessible by the user on their own devices--not by the Zuzalu organizers, the Passport maintainers, or the developers of any Zuzalu applications. Additionally, thanks to ZKPs, users have total control over who they share this data with, and how.

As mentioned above, we hope for the Zuzalu Passport to enable more permissionless experimentation in community and governance infrastructure at Zuzalu. You can find a list of starter ideas for things to build on the Zuzalu Passport [here](https://0xparc.notion.site/2023-03-28-Zuzalu-Passport-RFP-31f9fa45d3ba40289edcf45559536bbb). We'll also be running workshops and a hackathon track throughout ZK Week, for Zuzalu residents and visitors who are interested in hacking on top of the Passport!

**Currently, we run a server that serves a Merkle Tree of participant public keys and some metadata for convenience, though this could easily be migrated on-chain.*
\*_Currently, we run a server that serves a Merkle Tree of participant public keys and some metadata for convenience, though this could easily be migrated on-chain._

## For Developers: Understanding the Zuzalu Passport Model

### Zuzalu Passport Cards

The Zuzalu Passport holds a collection of *cards*. UX-wise, the Zuzalu Passport interface is similar in concept to the [Apple Wallet](https://help.apple.com/assets/63BCA8F46048E91596771871/63BCA8F56048E9159677187F/en_US/36d4991d06798f24f230e7282a911222.png).
The Zuzalu Passport holds a collection of _cards_. UX-wise, the Zuzalu Passport interface is similar in concept to the [Apple Wallet](https://help.apple.com/assets/63BCA8F46048E91596771871/63BCA8F56048E9159677187F/en_US/36d4991d06798f24f230e7282a911222.png).

Initially, the only card in the Zuzalu Passport wallet is a [Semaphore keypair](https://semaphore.appliedzkp.org/) that acts as your primary identifier as a resident or visitor. This is a special card: it displays a QR code which you can use to prove that you are indeed a Zuzalu resident.

Expand All @@ -45,7 +45,7 @@ Any third-party service--for example, a Zuzalu voting app--can request a card, m

As a developer, if you are interested in working with the Zuzalu Passport and with Zuzalu Passport Cards, you'll need to understand the "PCD" abstraction.

"PCD" is short for "Proof-Carrying Data"*. In this repository, we use this term to refer to any claim about the world that is attached to a proof of its own correctness, without the need for external context to verify--such as a user card, or a response to a third-party request for information about user cards.
"PCD" is short for "Proof-Carrying Data"\*. In this repository, we use this term to refer to any claim about the world that is attached to a proof of its own correctness, without the need for external context to verify--such as a user card, or a response to a third-party request for information about user cards.

All PCDs consist of a "claim", which is the human-interpretable statement that the PCD is making (i.e. "I am a Zuzalu resident"); and a "proof" attached to the "claim," which is a cryptographic or mathematical proof of the claim. All PCDs within this SDK also expose a `prove` and `verify` function, which allow you to instantiate them, and verify that they are indeed correct.

Expand All @@ -61,7 +61,7 @@ Many PCDs are [zkSNARKs](https://learn.0xparc.org/materials/circom/prereq-materi

This is a PCD because anyone can verify that what it claims is true by running the RSA signature verification locally.

**Our "PCD" abstraction is partially inspired by a spiritually similar academic cryptography concept of [the same name](https://dspace.mit.edu/handle/1721.1/61151). Note that the academic term has a slightly different technical definition.*
\*_Our "PCD" abstraction is partially inspired by a spiritually similar academic cryptography concept of [the same name](https://dspace.mit.edu/handle/1721.1/61151). Note that the academic term has a slightly different technical definition._

### What is the PCD SDK?

Expand Down Expand Up @@ -123,6 +123,7 @@ Some of these packages are used to share development configuration between the d
- [`@pcd/semaphore-group-pcd`](packages/semaphore-group-pcd): a pcd which wraps the [Semaphore](https://semaphore.appliedzkp.org/docs/introduction) protocol, which allows PCD-consuming applications to consume and generate Semaphore proofs.
- [`@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).
- ... more to come!

#### shared code packages
Expand Down
3 changes: 3 additions & 0 deletions apps/consumer-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@
"@pcd/ethereum-ownership-pcd": "0.5.3",
"@pcd/passport-interface": "0.5.3",
"@pcd/pcd-types": "0.5.3",
"@pcd/webauthn-pcd": "0.5.3",
"@pcd/semaphore-group-pcd": "0.5.3",
"@pcd/semaphore-identity-pcd": "0.5.3",
"@semaphore-protocol/group": "^3.10.0",
"@semaphore-protocol/identity": "^3.10.0",
"@simplewebauthn/browser": "^7.2.0",
"@simplewebauthn/server": "^7.2.0",
"next": "^13.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
68 changes: 68 additions & 0 deletions apps/consumer-client/pages/examples/add-pcd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import { ArgumentTypeName } 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 { Identity } from "@semaphore-protocol/identity";
import { startRegistration } from "@simplewebauthn/browser";
import {
generateRegistrationOptions,
verifyRegistrationResponse,
} from "@simplewebauthn/server";
import { HomeLink } from "../../components/Core";
import { ExampleContainer } from "../../components/ExamplePage";
import { PASSPORT_URL, SEMAPHORE_GROUP_URL } from "../../src/constants";
Expand Down Expand Up @@ -51,6 +57,11 @@ export default function Page() {
<button onClick={addIdentityPCD}>
add a new semaphore identity to the passport
</button>
<br />
<br />
<button onClick={addWebAuthnPCD}>
add a new webauthn credential to the passport
</button>
</ExampleContainer>
</div>
);
Expand Down Expand Up @@ -149,3 +160,60 @@ async function addIdentityPCD() {

sendPassportRequest(url);
}

async function addWebAuthnPCD() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verbosely commenting this function and each step may be nice

// Register a new WebAuthn credential for testing.
const generatedRegistrationOptions = await generateRegistrationOptions({
rpName: "consumer-client",
rpID: window.location.hostname,
userID: "user-id",
userName: "user",
attestationType: "direct",
challenge: "challenge",
supportedAlgorithmIDs: [-7],
});
const startRegistrationResponse = await startRegistration(
generatedRegistrationOptions
);
const verificationResponse = await verifyRegistrationResponse({
response: startRegistrationResponse,
expectedOrigin: window.location.origin,
expectedChallenge: generatedRegistrationOptions.challenge,
supportedAlgorithmIDs: [-7], // support ES256 signing algorithm
});

if (!verificationResponse.registrationInfo) {
throw new Error("Registration failed the return correct response.");
}

// Get relevant credential arguments from registration response.
const { credentialID, credentialPublicKey, counter } =
verificationResponse.registrationInfo;

// Create new WebAuthn PCD. This process initiates the WebAuth
// authentication ceremony, prompting a authorization gesture like
// a fingerprint or Face ID scan, depending on the device.
const newCredential = await WebAuthnPCDPackage.prove({
rpID: window.location.hostname,
authenticator: {
credentialID,
credentialPublicKey,
counter,
},
challenge: "1", // arbitrary challenge to be signed
origin: window.location.origin,
});

const serializedNewCredential = await WebAuthnPCDPackage.serialize(
newCredential
);

// Add new WebAuthn PCD to Passport.
const url = constructPassportPcdAddRequestUrl(
PASSPORT_URL,
window.location.origin + "/popup",
serializedNewCredential
);

sendPassportRequest(url);
}
1 change: 1 addition & 0 deletions apps/passport-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@pcd/semaphore-group-pcd": "0.5.3",
"@pcd/semaphore-identity-pcd": "0.5.3",
"@pcd/halo-nonce-pcd": "0.1.1",
"@pcd/webauthn-pcd": "0.5.3",
"@rollbar/react": "^0.11.1",
"@semaphore-protocol/group": "^3.10.0",
"@semaphore-protocol/identity": "^3.10.0",
Expand Down
2 changes: 2 additions & 0 deletions apps/passport-client/src/pcdPackages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { RLNPCDPackage } from "@pcd/rln-pcd";
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 { JubJubSignaturePCDPackage } from "jubjub-signature-pcd";
import { config } from "./config";

Expand Down Expand Up @@ -49,6 +50,7 @@ async function loadPackages(): Promise<PCDPackage[]> {
EthereumOwnershipPCDPackage,
JubJubSignaturePCDPackage,
RLNPCDPackage,
WebAuthnPCDPackage,
HaLoNoncePCDPackage,
];
}
5 changes: 4 additions & 1 deletion apps/passport-client/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
"downlevelIteration": true,
"jsx": "react-jsx",
"lib": ["ES2015", "DOM"],
"esModuleInterop": true
"esModuleInterop": true,
// To allow for mocha and jest to work together:
// https://stackoverflow.com/a/65568463
"skipLibCheck": true
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
Expand Down
1 change: 1 addition & 0 deletions packages/passport-interface/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@pcd/semaphore-group-pcd": "0.5.3",
"@pcd/semaphore-identity-pcd": "0.5.3",
"@pcd/semaphore-signature-pcd": "0.5.3",
"@pcd/webauthn-pcd": "0.5.3",
"@semaphore-protocol/group": "^3.10.0",
"@semaphore-protocol/identity": "^3.10.0",
"@semaphore-protocol/proof": "^3.10.0",
Expand Down
4 changes: 4 additions & 0 deletions packages/webauthn-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,
};
3 changes: 3 additions & 0 deletions packages/webauthn-pcd/.mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"loader": "ts-node/esm"
}
1 change: 1 addition & 0 deletions packages/webauthn-pcd/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @pcd/webauthn-pcd
11 changes: 11 additions & 0 deletions packages/webauthn-pcd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# `@pcd/webauthn-pcd`

A wrapper around WebAuthn authentication verification as specified by the [W3C protocol](https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion). WebAuthn enables authentication via a keypair rather than a password, including Face ID, Yubico devices, and many other devices. More options can be configured, such as allowed origin, a unique client ID, and a challenge to be signed.

In contrast to purely software-based PCDs, the WebAuthn PCD allows for actions in the physical world to form the basis of a proof. The specific _authorization gesture_ used for registration and authentication can be associated with a hardware device and includes actions like facial recognition, PINs, and fingerprints. With a TPM or secure enclave, the authenticator can have certain security guarantees, such as the private key not being knowable even by the owner of the device.

Some example use cases:

- Proof that I own a particular Yubikey and therefore am a authorized member of an organization.
- Proof that I own an Apple device that has a particular [Passkey](https://developer.apple.com/passkeys/), and that I've used Face ID or Touch ID to authenticate.
- Proof that a human has in some way interacted with a hardware device (through fingerprint, facial scan, or other [test of user presence](https://www.w3.org/TR/webauthn-2/#test-of-user-presence)), and therefore not an script or automated spammer.
1 change: 1 addition & 0 deletions packages/webauthn-pcd/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./src/WebAuthnPCD";
5 changes: 5 additions & 0 deletions packages/webauthn-pcd/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
};
34 changes: 34 additions & 0 deletions packages/webauthn-pcd/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@pcd/webauthn-pcd",
"version": "0.5.3",
"license": "GPL-3.0-or-later",
"main": "./index.ts",
"types": "./index.ts",
"scripts": {
"lint": "eslint \"**/*.ts{,x}\"",
"test": "jest",
"build": "tsc --noEmit"
},
"dependencies": {
"@pcd/pcd-types": "0.5.3",
"@simplewebauthn/browser": "^7.2.0",
"@simplewebauthn/server": "^7.2.0",
"@simplewebauthn/typescript-types": "^7.0.0",
"@types/expect": "^24.3.0",
"@types/jest": "^29.5.0",
"@types/json-bigint": "^1.0.1",
"json-bigint": "^1.0.0",
"typescript": "^4.5.2",
"uuid": "^9.0.0"
},
"devDependencies": {
"@pcd/eslint-config-custom": "*",
"@pcd/tsconfig": "*",
"eslint": "^7.32.0",
"jest": "^29.5.0",
"ts-jest": "^29.1.0"
},
"publishConfig": {
"access": "public"
}
}
45 changes: 45 additions & 0 deletions packages/webauthn-pcd/src/CardBody.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { FieldLabel, Separator, Spacer, TextContainer } from "@pcd/passport-ui";
import React from "react";
import styled from "styled-components";
import { WebAuthnPCD } from "./WebAuthnPCD";

export function WebAuthnCardBody({ pcd }: { pcd: WebAuthnPCD }) {
return (
<Container>
<p>
This PCD represents a signature proof in the context of a WebAuthn
credential. In other words, this is a ZK proof that a particular
credential keypair signed a particular challenge.
</p>
Comment on lines +9 to +13
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯


<Separator />

<FieldLabel>Credential Public Key</FieldLabel>
<TextContainer>
{pcd.claim.credentialDetails.credentialPublicKey}
</TextContainer>
<Spacer h={8} />

<FieldLabel>Credential ID</FieldLabel>
<TextContainer>{pcd.claim.credentialDetails.credentialID}</TextContainer>
<Spacer h={8} />

<FieldLabel>Challenge</FieldLabel>
<TextContainer>{pcd.claim.challenge}</TextContainer>
<Spacer h={8} />

<FieldLabel>Origin</FieldLabel>
<TextContainer>{pcd.claim.origin}</TextContainer>
<Spacer h={8} />

<FieldLabel>Relying Party ID</FieldLabel>
<TextContainer>{pcd.claim.rpID}</TextContainer>
</Container>
);
}

const Container = styled.div`
padding: 16px;
overflow: hidden;
width: 100%;
`;
Loading