Skip to content

Commit

Permalink
Add ZK proof generation
Browse files Browse the repository at this point in the history
  • Loading branch information
DOBEN committed Sep 3, 2024
1 parent 8d95a23 commit 42dfd70
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 24 deletions.
6 changes: 4 additions & 2 deletions compliant-reward-distribution/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@concordium/react-components": "^0.4.0",
"@concordium/wallet-connectors": "^0.4.0",
"@concordium/web-sdk": "^7.3.2",
"@types/sha256": "^0.2.2",
"bootstrap": "^5.3.3",
"json-bigint": "^1.0.0",
"moment": "^2.30.1",
Expand All @@ -27,17 +28,18 @@
"react-select": "^5.8.0",
"react-switch": "^7.0.0",
"rollup-plugin-node-polyfills": "^0.2.1",
"sha256": "^0.2.0",
"vite-plugin-node-polyfills": "^0.21.0"
},
"devDependencies": {
"@concordium/ccd-js-gen": "^1.2.0",
"@swc-jotai/react-refresh": "^0.1.0",
"@types/json-bigint": "^1.0.4",
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.20.0",
"@types/json-bigint": "^1.0.4",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react-swc": "^3.6.0",
"dotenv": "^16.3.1",
"eslint": "^8.45.0",
Expand Down
5 changes: 4 additions & 1 deletion compliant-reward-distribution/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import { ConnectWallet } from './components/ConnectWallet';
import { ZkProofSubmission } from './components/ZkProofSubmission';
import { version } from '../package.json';
import './styles.scss';
import { TESTNET, useGrpcClient } from '@concordium/react-components';

export const App = () => {
const [provider, setProvider] = useState<WalletProvider>();
const [account, setAccount] = useState<string>();

const grpcClient = useGrpcClient(TESTNET);

useEffect(() => {
if (provider !== undefined) {
return () => {
Expand Down Expand Up @@ -69,7 +72,7 @@ export const App = () => {
/>
<Route
path="/zkProofSubmission"
element={<ZkProofSubmission accountAddress={account} provider={provider} />}
element={<ZkProofSubmission accountAddress={account} provider={provider} grpcClient={grpcClient} />}
/>
{/* <Route
path="/tweetSubmission"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,91 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { Alert, Button, Form } from 'react-bootstrap';
import { useForm, useWatch } from 'react-hook-form';
import { useForm } from 'react-hook-form';

import { WalletProvider } from '../../wallet-connection';
import { ConcordiumGRPCClient, CredentialStatement } from '@concordium/web-sdk';
import { getStatement, submitZkProof } from '../utils';
import { CONTEXT_STRING } from '../constants';
import sha256 from 'sha256';

interface Props {
accountAddress: string | undefined;
provider: WalletProvider | undefined;
grpcClient: ConcordiumGRPCClient | undefined;
}

export function ZkProofSubmission(props: Props) {
const { provider } = props;
const { provider, grpcClient } = props;

interface FormType {
zkStatement: JSON | undefined;
}
const { control, handleSubmit } = useForm<FormType>({ mode: 'all' });
const [error, setError] = useState<string | undefined>(undefined);
const [zkStatement, setZkStatement] = useState<CredentialStatement | undefined>(undefined);

const [zkStatement] = useWatch({
control: control,
name: ['zkStatement'],
});
useEffect(() => {
const fetchStatement = async () => {
const statement: CredentialStatement = await getStatement();
setZkStatement(statement);
};

const [error, setError] = useState<string | undefined>(undefined);
fetchStatement();
}, []);

interface FormType { }
const { handleSubmit } = useForm<FormType>({ mode: 'all' });

async function onSubmit() {
setError(undefined);

if (grpcClient === undefined) {
setError(`'grpcClient' is undefined`);
throw Error(`'grpcClient' is undefined`);
}

const bestBlockHeight = (await grpcClient.client.getConsensusInfo(''))?.response.bestBlockHeight;

if (bestBlockHeight === undefined) {
setError(`Couldn't get 'bestBlockHeight' from chain`);
throw Error(`Couldn't get 'bestBlockHeight' from chain`);
}

const recentBlockHeight = bestBlockHeight.value - 10n;

const recentBlockHash = (
await grpcClient.client.getBlocksAtHeight({
// TODO: Type in web-sdk needs to be fixed to do this ergonomically.
blocksAtHeight: {
oneofKind: 'absolute',
absolute: {
height: { value: recentBlockHeight },
},
},
})
)?.response.blocks[0].value;

if (recentBlockHash === undefined) {
setError(`'recentBlockHash' is undefined`);
throw Error(`'recentBlockHash' is undefined`);
}

const hashDigest = [recentBlockHash, CONTEXT_STRING];
const challenge = sha256(hashDigest.flatMap((item) => Array.from(item)));

if (zkStatement === undefined) {
setError(`'statement' input field is undefined`);
throw Error(`'statement' input field is undefined`);
setError(`'zkStatement' is undefined`);
throw Error(`'zkStatement' is undefined`);
}

if (provider === undefined) {
setError(`'provider' is undefined`);
throw Error(`'provider' is undefined`);
}

const presentation = await provider.requestVerifiablePresentation(challenge, [zkStatement]);

try {
await submitZkProof(presentation, recentBlockHeight);
} catch (error) {
setError((error as Error).message);
}
}

return (
Expand All @@ -44,12 +94,6 @@ export function ZkProofSubmission(props: Props) {
<h2 className="centered"> Submit ZK Proof</h2>
<br />
<Form onSubmit={handleSubmit(onSubmit)}>
{/* <Form.Group className="col mb-3">
<Form.Label>ZK statement</Form.Label>
<Form.Control {...register('zkStatement', { required: true })} placeholder="Enter metadata URL" />
{formState.errors.zkStatement && <Alert variant="info"> ZK statement is required </Alert>}
<Form.Text />
</Form.Group> */}
<Button variant="secondary" type="submit">
Submit
</Button>
Expand Down
10 changes: 10 additions & 0 deletions compliant-reward-distribution/frontend/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ export const CONTRACT_ADDRESS = ContractAddress.fromSerializable(CONFIG.contract
export const NETWORK = CONFIG.network === 'mainnet' ? MAINNET : TESTNET;

export const CCD_SCAN_URL = NETWORK === MAINNET ? 'https://ccdscan.io' : 'https://testnet.ccdscan.io';
export const BACKEDN_BASE_URL = 'http://localhost:8080/';

// The string "CONCORDIUM_COMPLIANT_REWARD_DISTRIBUTION_DAPP" in bytes is used
// as context for signing messages and generating ZK proofs. The same account
// can be used in different Concordium services without the risk of re-playing
// signatures/zk-proofs across the different services due to this context string.
export const CONTEXT_STRING = new Uint8Array([
67, 79, 78, 67, 79, 82, 68, 73, 85, 77, 95, 67, 79, 77, 80, 76, 73, 65, 78, 84, 95, 82, 69, 87, 65, 82, 68, 95, 68,
73, 83, 84, 82, 73, 66, 85, 84, 73, 79, 78, 95, 68, 65, 80, 80,
]);

// Before submitting a transaction we simulate/dry-run the transaction to get an
// estimate of the energy needed for executing the transaction. In addition, we
Expand Down
54 changes: 53 additions & 1 deletion compliant-reward-distribution/frontend/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,56 @@
import { AccountAddress } from '@concordium/web-sdk';
import { AccountAddress, AtomicStatementV2, CredentialStatement, VerifiablePresentation } from '@concordium/web-sdk';
import { BACKEDN_BASE_URL } from './constants';

/**
* Fetch the statement to prove from the backend
*/
export async function getStatement(): Promise<CredentialStatement> {
const response = await fetch(`${BACKEDN_BASE_URL}api/getZKProofStatements`, { method: 'get' });

if (!response.ok) {
const error = (await response.json()) as Error;
throw new Error(`Unable to get the ZK statement from the backend: ${JSON.stringify(error)}`);
}

const body = (await response.json()).data as AtomicStatementV2[];

if (body) {
const credentialStatement: CredentialStatement = {
idQualifier: {
type: 'cred',
// We allow all identity providers on mainnet and on testnet.
// This list is longer than necessary to include all current/future
// identity providers on mainnet and testnet.
// This list should be updated to only include the identity providers that you trust.
issuers: [0, 1, 2, 3, 4, 5, 6, 7],
},
statement: body,
};

return credentialStatement;
} else {
throw new Error(`Unable to get the ZK statement from the backend`);
}
}

/**
* Submit ZK proof to the backend
*/
export async function submitZkProof(presentation: VerifiablePresentation, recentBlockHeight: bigint) {
const response = await fetch(`${BACKEDN_BASE_URL}api/postZKProof`, {
method: 'POST',
headers: new Headers({ 'content-type': 'application/json' }),
body: JSON.stringify({
blockHeight: Number(recentBlockHeight),
presentation: presentation,
}),
});

if (!response.ok) {
const error = (await response.json()) as Error;
throw new Error(`Unable to submit ZK proof to the backend: ${JSON.stringify(error)}`);
}
}

/**
* This function validates if a string represents a valid accountAddress in base58 encoding.
Expand Down
37 changes: 37 additions & 0 deletions compliant-reward-distribution/frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,13 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==

"@types/node@*":
version "22.5.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.2.tgz#e42344429702e69e28c839a7e16a8262a8086793"
integrity sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==
dependencies:
undici-types "~6.19.2"

"@types/node@>=13.7.0":
version "20.12.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.3.tgz#d6658c2c7776c1cad93534bb45428195ed840c65"
Expand Down Expand Up @@ -1098,6 +1105,13 @@
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e"
integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==

"@types/sha256@^0.2.2":
version "0.2.2"
resolved "https://registry.yarnpkg.com/@types/sha256/-/sha256-0.2.2.tgz#633bf20405e90cab0c4df1054b657f206ef15bf5"
integrity sha512-uKMaDzyzfcDYGEwTgLh+hmgDMxXWyIVodY8T+qt7A+NYvikW0lmGLMGbQ7BipCB8dzXHa55C9g+Ii/3Lgt1KmA==
dependencies:
"@types/node" "*"

"@types/warning@^3.0.0":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.3.tgz#d1884c8cc4a426d1ac117ca2611bf333834c6798"
Expand Down Expand Up @@ -2028,11 +2042,21 @@ constants-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
integrity sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==

convert-hex@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/convert-hex/-/convert-hex-0.1.0.tgz#08c04568922c27776b8a2e81a95d393362ea0b65"
integrity sha512-w20BOb1PiR/sEJdS6wNrUjF5CSfscZFUp7R9NSlXH8h2wynzXVEPFPJECAnkNylZ+cvf3p7TyRUHggDmrwXT9A==

convert-source-map@^1.5.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==

convert-string@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/convert-string/-/convert-string-0.1.0.tgz#79ce41a9bb0d03bcf72cdc6a8f3c56fbbc64410a"
integrity sha512-1KX9ESmtl8xpT2LN2tFnKSbV4NiarbVi8DVb39ZriijvtTklyrT+4dT1wsGMHKD3CJUjXgvJzstm9qL9ICojGA==

cookie-es@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/cookie-es/-/cookie-es-1.1.0.tgz#68f8d9f48aeb5a534f3896f80e792760d3d20def"
Expand Down Expand Up @@ -4648,6 +4672,14 @@ sha.js@^2.4.0, sha.js@^2.4.8:
inherits "^2.0.1"
safe-buffer "^5.0.1"

sha256@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/sha256/-/sha256-0.2.0.tgz#73a0b418daab7035bff86e8491e363412fc2ab05"
integrity sha512-kTWMJUaez5iiT9CcMv8jSq6kMhw3ST0uRdcIWl3D77s6AsLXNXRp3heeqqfu5+Dyfu4hwpQnMzhqHh8iNQxw0w==
dependencies:
convert-hex "~0.1.0"
convert-string "~0.1.0"

shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
Expand Down Expand Up @@ -5075,6 +5107,11 @@ undici-types@~5.26.4:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==

undici-types@~6.19.2:
version "6.19.8"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==

unenv@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/unenv/-/unenv-1.9.0.tgz#469502ae85be1bd3a6aa60f810972b1a904ca312"
Expand Down

0 comments on commit 42dfd70

Please sign in to comment.