Skip to content
This repository has been archived by the owner on Aug 16, 2024. It is now read-only.

Commit

Permalink
Add verifiable presentation method (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
orhoj authored Mar 13, 2024
2 parents 7822ab2 + 4fc9c95 commit a29bac4
Show file tree
Hide file tree
Showing 22 changed files with 397 additions and 7 deletions.
4 changes: 4 additions & 0 deletions packages/react-components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- Dependency on `@concordium/wallet-connectors` bumped to v0.5.0+.

## [0.4.0] - 2023-11-13

### Changed
Expand Down
2 changes: 1 addition & 1 deletion packages/react-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"build": "tsc"
},
"dependencies": {
"@concordium/wallet-connectors": "^0.4.0"
"@concordium/wallet-connectors": "^0.5.0"
},
"devDependencies": {
"@tsconfig/recommended": "^1.0.1",
Expand Down
4 changes: 4 additions & 0 deletions packages/wallet-connectors/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Added a new method `requestVerifiablePresentation` to the `WalletConnect` interface for requesting verifiable presentations from a wallet.

## [0.4.0] - 2023-11-13

### Changed
Expand Down
2 changes: 1 addition & 1 deletion packages/wallet-connectors/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@concordium/wallet-connectors",
"version": "0.4.0",
"version": "0.5.0",
"description": "Utility interface for dApps to interact with wallets without depending on the underlying protocol and implementations for Concordium Browser Wallet and Wallet Connect v2.",
"author": "Concordium Software",
"license": "Apache-2.0",
Expand Down
14 changes: 13 additions & 1 deletion packages/wallet-connectors/src/BrowserWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import {
WalletApi,
detectConcordiumProvider,
} from '@concordium/browser-wallet-api-helpers';
import { AccountTransactionSignature, AccountTransactionType } from '@concordium/web-sdk';
import {
AccountTransactionSignature,
AccountTransactionType,
CredentialStatements,
VerifiablePresentation,
} from '@concordium/web-sdk';
import {
SignableMessage,
TypedSmartContractParameters,
Expand Down Expand Up @@ -173,4 +178,11 @@ export class BrowserWalletConnector implements WalletConnector, WalletConnection
throw new UnreachableCaseError('message', msg);
}
}

async requestVerifiablePresentation(
challenge: string,
credentialStatements: CredentialStatements
): Promise<VerifiablePresentation> {
return this.client.requestVerifiablePresentation(challenge, credentialStatements);
}
}
18 changes: 17 additions & 1 deletion packages/wallet-connectors/src/WalletConnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import {
AccountTransactionType,
BigintFormatType,
ContractName,
CredentialStatements,
EntrypointName,
InitContractPayload,
Parameter,
UpdateContractPayload,
VerifiablePresentation,
getTransactionKindString,
jsonUnwrapStringify,
serializeInitContractParameters,
Expand Down Expand Up @@ -41,7 +43,7 @@ async function connect(client: ISignClient, chainId: string, cancel: () => void)
const { uri, approval } = await client.connect({
requiredNamespaces: {
ccd: {
methods: ['sign_and_send_transaction', 'sign_message'],
methods: ['sign_and_send_transaction', 'sign_message', 'request_verifiable_presentation'],
chains: [chainId],
events: ['chain_changed', 'accounts_changed'],
},
Expand Down Expand Up @@ -296,6 +298,20 @@ export class WalletConnectConnection implements WalletConnection {
}
}

async requestVerifiablePresentation(
challenge: string,
credentialStatements: CredentialStatements
): Promise<VerifiablePresentation> {
return this.connector.client.request<VerifiablePresentation>({
topic: this.session.topic,
request: {
method: 'request_verifiable_presentation',
params: { challenge, credentialStatements },
},
chainId: this.chainId,
});
}

async disconnect() {
await this.connector.client.disconnect({
topic: this.session.topic,
Expand Down
23 changes: 22 additions & 1 deletion packages/wallet-connectors/src/WalletConnection.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { Buffer } from 'buffer/';
import { SendTransactionPayload, SmartContractParameters } from '@concordium/browser-wallet-api-helpers';
import { AccountTransactionSignature, AccountTransactionType, SchemaVersion, toBuffer } from '@concordium/web-sdk';
import {
AccountTransactionSignature,
AccountTransactionType,
CredentialStatements,
SchemaVersion,
VerifiablePresentation,
toBuffer,
} from '@concordium/web-sdk';
import { GrpcWebOptions } from '@protobuf-ts/grpcweb-transport';

export type ModuleSchema = {
Expand Down Expand Up @@ -179,6 +186,20 @@ export interface WalletConnection {
*/
signMessage(accountAddress: string, msg: SignableMessage): Promise<AccountTransactionSignature>;

/**
* Request the wallet to provide a verifiable presentation for the provided challenge and statements.
*
* The returned promise resolves to the verifiable presentation once the wallet approves the request. If
* this doesn't happen, the promise rejects with an explanatory error message.
* @param challenge a challenge that is used to avoid accepting proofs created for other contexts.
* @param credentialStatements the statements to provide a verifiable presentation for
* @return A promise for the verifiable presentation for the statements.
*/
requestVerifiablePresentation(
challenge: string,
credentialStatements: CredentialStatements
): Promise<VerifiablePresentation>;

/**
* Close the connection and clean up relevant resources.
* There's no guarantee that the wallet will consider the connection closed
Expand Down
23 changes: 23 additions & 0 deletions samples/proofs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
1 change: 1 addition & 0 deletions samples/proofs/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/
45 changes: 45 additions & 0 deletions samples/proofs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "concordium-dapp-request-verifiable-presentation",
"version": "0.1.0",
"private": true,
"dependencies": {
"@concordium/react-components": "workspace:^",
"@concordium/web-sdk": "^7.1.0",
"@types/node": "^16.18.16",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"bootstrap": "^5.2.3",
"react": "^18.2.0",
"react-bootstrap": "^2.7.2",
"react-dom": "^18.2.0",
"react-scripts": "^5.0.1",
"typescript": "^5.2.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"prettier": "2.8.1"
}
}
Binary file added samples/proofs/public/favicon.ico
Binary file not shown.
15 changes: 15 additions & 0 deletions samples/proofs/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Identity proofs | Sample Concordium dApp</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
Binary file added samples/proofs/public/logo192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/proofs/public/logo512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions samples/proofs/public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"short_name": "Sample dApp",
"name": "Sample Concordium dApp",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
3 changes: 3 additions & 0 deletions samples/proofs/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
116 changes: 116 additions & 0 deletions samples/proofs/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import React, { useCallback, useState } from 'react';
import { Alert, Button, Card, Col, Container, Form, Row, Spinner } from 'react-bootstrap';
import {
TESTNET,
WalletConnectionProps,
WithWalletConnector,
useConnect,
useConnection,
} from '@concordium/react-components';
import {
AttributeKeyString,
MIN_DATE,
VerifiablePresentation,
Web3StatementBuilder,
getPastDate,
} from '@concordium/web-sdk';
import { WalletConnectorButton } from './WalletConnectorButton';
import { BROWSER_WALLET, WALLET_CONNECT } from './config';

export default function App() {
return (
<Container>
<h1>Identity proofs</h1>
<WithWalletConnector network={TESTNET}>{(props) => <Main {...props} />}</WithWalletConnector>
</Container>
);
}

function Main(props: WalletConnectionProps) {
const { activeConnectorType, activeConnector, activeConnectorError, connectedAccounts, genesisHashes } = props;
const { connection, setConnection, account } = useConnection(connectedAccounts, genesisHashes);
const { connect, isConnecting, connectError } = useConnect(activeConnector, setConnection);

const [verifiablePresentation, setVerifiablePresentation] = useState<VerifiablePresentation>();
const [error, setError] = useState('');
const [isWaiting, setIsWaiting] = useState(false);

const handleSubmit = useCallback(() => {
setVerifiablePresentation(undefined);
setError('');
setIsWaiting(true);

const statementBuilder = new Web3StatementBuilder().addForIdentityCredentials([0, 1, 2, 3, 4, 5], (b) =>
b.addRange(AttributeKeyString.dob, MIN_DATE, getPastDate(18, 1))
);
const statement = statementBuilder.getStatements();
// In a production scenario the challenge should not be hardcoded, in order to avoid accepting proofs created for other contexts.
const challenge = 'beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef';

connection
?.requestVerifiablePresentation(challenge, statement)
.then((res) => setVerifiablePresentation(res))
.catch((e: Error) => setError(`Failed to get verifiable presentation: ${e.message}`))
.finally(() => setIsWaiting(false));
}, [connection]);

return (
<>
<Row className="mt-3 mb-3">
<Col>
<WalletConnectorButton
connectorType={BROWSER_WALLET}
connectorName="Browser Wallet"
connection={connection}
{...props}
/>
</Col>
<Col>
<WalletConnectorButton
connectorType={WALLET_CONNECT}
connectorName="WalletConnect"
connection={connection}
{...props}
/>
</Col>
</Row>
<Row className="mt-3 mb-3">
<Col>
{activeConnectorError && <Alert variant="danger">Connector error: {activeConnectorError}.</Alert>}
{!activeConnectorError && activeConnectorType && !activeConnector && <Spinner />}
{connectError && <Alert variant="danger">Connection error: {connectError}.</Alert>}
{activeConnector && !account && (
<Button type="button" onClick={connect} disabled={isConnecting}>
{isConnecting && 'Connecting...'}
{!isConnecting && activeConnectorType === BROWSER_WALLET && 'Connect Browser Wallet'}
{!isConnecting && activeConnectorType === WALLET_CONNECT && 'Connect Mobile Wallet'}
</Button>
)}
</Col>
</Row>
{account && (
<Row className="mt-3 mb-3">
<Col>
Connected to account <code>{account}</code>.
</Col>
</Row>
)}
<Form.Group as={Row} className="mb-3">
<Col sm={3}>
<Button variant="primary" onClick={handleSubmit} disabled={!connection || isWaiting}>
{isWaiting ? 'Waiting for proof...' : 'Request proof'}
</Button>
</Col>
</Form.Group>
{error && <Alert variant="danger">{error}</Alert>}
{verifiablePresentation && (
<Card>
<Card.Header>Verifiable presentation</Card.Header>
<Card.Body>
<Card.Text>{JSON.stringify(verifiablePresentation)}</Card.Text>
</Card.Body>
</Card>
)}
</>
);
}
Loading

0 comments on commit a29bac4

Please sign in to comment.