Skip to content

Commit

Permalink
Revise signature verification
Browse files Browse the repository at this point in the history
  • Loading branch information
DOBEN committed Sep 9, 2024
1 parent cd8f302 commit c9df25b
Show file tree
Hide file tree
Showing 15 changed files with 151 additions and 101 deletions.
1 change: 1 addition & 0 deletions compliant-reward-distribution/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@concordium/web-sdk": "^7.3.2",
"@types/sha256": "^0.2.2",
"bootstrap": "^5.3.3",
"buffer": "^6.0.3",
"json-bigint": "^1.0.0",
"moment": "^2.30.1",
"react": "^18.2.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 @@ -78,7 +78,10 @@ export const App = () => {
element={<TweetSubmission signer={account} provider={provider} grpcClient={grpcClient} />}
/>

<Route path="/Admin" element={<Admin accountAddress={account} grpcClient={grpcClient} />} />
<Route
path="/Admin"
element={<Admin provider={provider} accountAddress={account} grpcClient={grpcClient} />}
/>
<Route path="/" element={<div></div>} />
</Routes>
</Router>
Expand Down
10 changes: 6 additions & 4 deletions compliant-reward-distribution/frontend/src/components/Admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@ import { ConcordiumGRPCClient } from '@concordium/web-sdk';
import { AdminGetAccountData } from './AdminGetAccountData';
import { AdminGetPendingApprovals } from './AdminGetPendingApprovals';
import { AdminSetClaimed } from './AdminSetClaimed';
import { WalletProvider } from '../../wallet-connection';

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

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

return (
<div className="centered">
<AdminGetPendingApprovals signer={accountAddress} grpcClient={grpcClient} />
<AdminGetAccountData signer={accountAddress} grpcClient={grpcClient} />
<AdminSetClaimed signer={accountAddress} grpcClient={grpcClient} />
<AdminGetPendingApprovals provider={provider} signer={accountAddress} grpcClient={grpcClient} />
<AdminGetAccountData provider={provider} signer={accountAddress} grpcClient={grpcClient} />
<AdminSetClaimed provider={provider} signer={accountAddress} grpcClient={grpcClient} />
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ import { useState } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { Alert, Button, Form } from 'react-bootstrap';

import { getAccountData, getARecentBlockHash, validateAccountAddress } from '../utils';
import { getAccountData, getARecentBlockHash, requestSignature, validateAccountAddress } from '../utils';
import { ConcordiumGRPCClient } from '@concordium/web-sdk';
import JSONbig from 'json-bigint';
import { WalletProvider } from '../../wallet-connection';

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

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

interface FormType {
address: string;
Expand All @@ -37,15 +39,10 @@ export function AdminGetAccountData(props: Props) {
}

const [recentBlockHash, recentBlockHeight] = await getARecentBlockHash(grpcClient);
console.log(recentBlockHash);
// TODO: add signature generation
const schema = 'FAADAAAADgAAAGNvbnRleHRfc3RyaW5nFgIHAAAAbWVzc2FnZQsKAAAAYmxvY2tfaGFzaBYC';
const signature = await requestSignature(recentBlockHash, schema, address, signer, provider);

const data = await getAccountData(
signer,
address,
'c4bb83e7d7a9e6fe7a1b5f527174f7e368db9385b25fce0f4e4b7190781e57b5de6ad65a7481f1038f859d4c4ba8c07ed649b84f9e3c17e2bbdb87cf527cd602',
recentBlockHeight,
);
const data = await getAccountData(signer, address, signature, recentBlockHeight);
setAccountData(JSONbig.stringify(data));
} catch (error) {
setError((error as Error).message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ import { useForm } from 'react-hook-form';
import { Alert, Button, Form } from 'react-bootstrap';

import { ConcordiumGRPCClient } from '@concordium/web-sdk';
import { getARecentBlockHash, getPendingApprovals } from '../utils';
import { getARecentBlockHash, getPendingApprovals, requestSignature } from '../utils';
import JSONbig from 'json-bigint';
import { WalletProvider } from '../../wallet-connection';

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

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

const { handleSubmit } = useForm<[]>({ mode: 'all' });

Expand All @@ -29,18 +31,14 @@ export function AdminGetPendingApprovals(props: Props) {
}

const [recentBlockHash, recentBlockHeight] = await getARecentBlockHash(grpcClient);
console.log(recentBlockHash);
const limit = 5;
const offset = 0;
// TODO: add signature generation

const data = await getPendingApprovals(
signer,
'c4bb83e7d7a9e6fe7a1b5f527174f7e368db9385b25fce0f4e4b7190781e57b5de6ad65a7481f1038f859d4c4ba8c07ed649b84f9e3c17e2bbdb87cf527cd602',
recentBlockHeight,
limit,
offset,
);
const schema =
'FAADAAAADgAAAGNvbnRleHRfc3RyaW5nFgIHAAAAbWVzc2FnZRQAAgAAAAUAAABsaW1pdAQGAAAAb2Zmc2V0BAoAAABibG9ja19oYXNoFgI';
const signature = await requestSignature(recentBlockHash, schema, { limit, offset }, signer, provider);

const data = await getPendingApprovals(signer, signature, recentBlockHeight, limit, offset);
setPendingApprovals(JSONbig.stringify(data));
} catch (error) {
setError((error as Error).message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ import { useState } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { Alert, Button, Form } from 'react-bootstrap';

import { getARecentBlockHash, setClaimed, validateAccountAddress } from '../utils';
import { getARecentBlockHash, requestSignature, setClaimed, validateAccountAddress } from '../utils';
import { ConcordiumGRPCClient } from '@concordium/web-sdk';
import { WalletProvider } from '../../wallet-connection';

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

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

interface FormType {
address: string;
Expand All @@ -34,15 +36,11 @@ export function AdminSetClaimed(props: Props) {
}

const [recentBlockHash, recentBlockHeight] = await getARecentBlockHash(grpcClient);
console.log(recentBlockHash);
// TODO: add signature generation

await setClaimed(
signer,
'c4bb83e7d7a9e6fe7a1b5f527174f7e368db9385b25fce0f4e4b7190781e57b5de6ad65a7481f1038f859d4c4ba8c07ed649b84f9e3c17e2bbdb87cf527cd602',
recentBlockHeight,
address,
);
const schema = 'FAADAAAADgAAAGNvbnRleHRfc3RyaW5nFgIHAAAAbWVzc2FnZRACCwoAAABibG9ja19oYXNoFgI';
const signature = await requestSignature(recentBlockHash, schema, [address], signer, provider);

await setClaimed(signer, signature, recentBlockHeight, address);
} catch (error) {
setError((error as Error).message);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ import { useState } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { Alert, Button, Form } from 'react-bootstrap';

import sha256 from 'sha256';

import { getARecentBlockHash, submitTweet } from '../utils';
import { getARecentBlockHash, requestSignature, submitTweet } from '../utils';
import { ConcordiumGRPCClient } from '@concordium/web-sdk';
import { CONTEXT_STRING } from '../constants';
import { WalletProvider } from '../../wallet-connection';

interface Props {
Expand Down Expand Up @@ -40,24 +37,10 @@ export function TweetSubmission(props: Props) {
throw Error(`'signer' is undefined. Connect your wallet.`);
}

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

const [recentBlockHash, recentBlockHeight] = await getARecentBlockHash(grpcClient);
const schema = 'FAADAAAADgAAAGNvbnRleHRfc3RyaW5nFgIHAAAAbWVzc2FnZRYCCgAAAGJsb2NrX2hhc2gWAg';

const encoder = new TextEncoder();
const tweetBytes = encoder.encode(tweet);

const digest = [recentBlockHash, CONTEXT_STRING, tweetBytes].flatMap((item) => Array.from(item));

const messageHash = sha256(digest);

const signatures = (await provider.signMessage(signer, messageHash))
if (Object.keys(signatures).length !== 1 || Object.keys(signatures[0]).length !== 1) {
throw Error(`Dapp only supports single singer accounts`);
}
const signature = signatures[0][0];
const signature = await requestSignature(recentBlockHash, schema, tweet, signer, provider);

await submitTweet(signer, signature, recentBlockHeight, tweet);
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ConcordiumGRPCClient, CredentialStatement } from '@concordium/web-sdk';
import { getARecentBlockHash, getStatement, submitZkProof } from '../utils';
import { CONTEXT_STRING } from '../constants';
import sha256 from 'sha256';
import { Buffer } from 'buffer';

interface Props {
accountAddress: string | undefined;
Expand Down Expand Up @@ -41,12 +42,12 @@ export function ZkProofSubmission(props: Props) {
}

if (!provider) {
throw Error(`'provider' is undefined`);
throw Error(`'provider' is undefined. Connect your wallet.`);
}

const [recentBlockHash, recentBlockHeight] = await getARecentBlockHash(grpcClient);

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

const presentation = await provider.requestVerifiablePresentation(challenge, [zkStatement]);
Expand Down
10 changes: 2 additions & 8 deletions compliant-reward-distribution/frontend/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,11 @@ 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
// The string "CONCORDIUM_COMPLIANT_REWARD_DISTRIBUTION_DAPP" 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,
]);
export const CONTEXT_STRING = 'CONCORDIUM_COMPLIANT_REWARD_DISTRIBUTION_DAPP';

// 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 All @@ -50,6 +47,3 @@ export const walletConnectOpts: SignClientTypes.Options = {
icons: ['https://walletconnect.com/walletconnect-logo.png'],
},
};

export const SERIALIZATION_HELPER_SCHEMA_PERMIT_MESSAGE =
'FAAFAAAAEAAAAGNvbnRyYWN0X2FkZHJlc3MMBQAAAG5vbmNlBQkAAAB0aW1lc3RhbXANCwAAAGVudHJ5X3BvaW50FgEHAAAAcGF5bG9hZBABAg==';
23 changes: 23 additions & 0 deletions compliant-reward-distribution/frontend/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
} from '@concordium/web-sdk';
import { BACKEDN_BASE_URL } from './constants';

import { WalletProvider } from '../wallet-connection';

interface AccountData {
// The account address that was indexed.
accountAddress: AccountAddress.Type;
Expand Down Expand Up @@ -207,6 +209,27 @@ export async function getAccountData(
return body;
}

/**
* Request signature from wallet.
*/
export async function requestSignature(
recentBlockHash: Uint8Array,
schema: string,
message: string | string[] | object,
signer: string,
provider: WalletProvider | undefined,
): Promise<string> {
if (!provider) {
throw Error(`'provider' is undefined. Connect your wallet.`);
}

const signatures = await provider.signMessage(signer, message, recentBlockHash, schema);
if (Object.keys(signatures).length !== 1 || Object.keys(signatures[0]).length !== 1) {
throw Error(`Dapp only supports single singer accounts`);
}
return signatures[0][0];
}

/**
* Fetch the statement to prove from the backend
*/
Expand Down
48 changes: 43 additions & 5 deletions compliant-reward-distribution/frontend/wallet-connection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,24 @@ import {
AccountTransactionSignature,
CredentialStatements,
HexString,
serializeTypeValue,
toBuffer,
VerifiablePresentation,
} from '@concordium/web-sdk';
import { SessionTypes } from '@walletconnect/types';
import SignClient from '@walletconnect/sign-client';
import QRCodeModal from '@walletconnect/qrcode-modal';
import EventEmitter from 'events';
import JSONBigInt from 'json-bigint';
import { Buffer } from 'buffer';

import { CHAIN_ID, ID_METHOD, WALLET_CONNECT_SESSION_NAMESPACE, walletConnectOpts } from './src/constants';
import {
CHAIN_ID,
CONTEXT_STRING,
ID_METHOD,
WALLET_CONNECT_SESSION_NAMESPACE,
walletConnectOpts,
} from './src/constants';

export abstract class WalletProvider extends EventEmitter {
abstract connect(): Promise<string[] | undefined>;
Expand All @@ -23,7 +32,12 @@ export abstract class WalletProvider extends EventEmitter {

disconnect?(): Promise<void>;

abstract signMessage(accountAddress: string, message: string): Promise<AccountTransactionSignature>;
abstract signMessage(
accountAddress: string,
message: string | string[] | object,
recentBlockHash: Uint8Array,
schema: string,
): Promise<AccountTransactionSignature>;

/**
* @param account string when account is changed, undefined when disconnected
Expand Down Expand Up @@ -66,8 +80,25 @@ export class BrowserWalletProvider extends WalletProvider {
return browserWalletInstance;
}

async signMessage(accountAddress: string, message: string): Promise<AccountTransactionSignature> {
return this.provider.signMessage(accountAddress, message);
async signMessage(
accountAddress: string,
message: string | string[] | object,
recentBlockHash: Uint8Array,
schema: string,
): Promise<AccountTransactionSignature> {
const payload = Buffer.from(
serializeTypeValue(
{ block_hash: Buffer.from(recentBlockHash).toString('hex'), context_string: CONTEXT_STRING, message },
toBuffer(schema, 'base64'),
).buffer,
).toString('hex');

const messageToSign = {
data: payload,
schema,
};

return this.provider.signMessage(accountAddress, messageToSign);
}

async connect(): Promise<string[] | undefined> {
Expand Down Expand Up @@ -155,9 +186,16 @@ export class WalletConnectProvider extends WalletProvider {
}
}

async signMessage(accountAddress: string, message: string): Promise<AccountTransactionSignature> {
async signMessage(
accountAddress: string,
message: string | string[] | object,
recentBlockHash: Uint8Array,
schema: string,
): Promise<AccountTransactionSignature> {
console.log(accountAddress);
console.log(message);
console.log(recentBlockHash);
console.log(schema);
throw new Error('Not yet implemented');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ tower-http = { version = "0.4", features = [
"limit",
"fs",
"compression-br",
"compression-zstd",
"compression-zstd"
] }
http = "0.2"
tonic = { version = "0.10", features = ["tls-roots", "tls"] }
Expand Down
Loading

0 comments on commit c9df25b

Please sign in to comment.