diff --git a/compliant-reward-distribution/frontend/package.json b/compliant-reward-distribution/frontend/package.json index 83719471..29bc6fe9 100644 --- a/compliant-reward-distribution/frontend/package.json +++ b/compliant-reward-distribution/frontend/package.json @@ -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", diff --git a/compliant-reward-distribution/frontend/src/App.tsx b/compliant-reward-distribution/frontend/src/App.tsx index e95c6166..078c1a11 100644 --- a/compliant-reward-distribution/frontend/src/App.tsx +++ b/compliant-reward-distribution/frontend/src/App.tsx @@ -78,7 +78,10 @@ export const App = () => { element={} /> - } /> + } + /> } /> diff --git a/compliant-reward-distribution/frontend/src/components/Admin.tsx b/compliant-reward-distribution/frontend/src/components/Admin.tsx index 915d1676..75649f4a 100644 --- a/compliant-reward-distribution/frontend/src/components/Admin.tsx +++ b/compliant-reward-distribution/frontend/src/components/Admin.tsx @@ -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 (
- - - + + +
); } diff --git a/compliant-reward-distribution/frontend/src/components/AdminGetAccountData.tsx b/compliant-reward-distribution/frontend/src/components/AdminGetAccountData.tsx index f5488f6d..23f0d80e 100644 --- a/compliant-reward-distribution/frontend/src/components/AdminGetAccountData.tsx +++ b/compliant-reward-distribution/frontend/src/components/AdminGetAccountData.tsx @@ -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; @@ -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); diff --git a/compliant-reward-distribution/frontend/src/components/AdminGetPendingApprovals.tsx b/compliant-reward-distribution/frontend/src/components/AdminGetPendingApprovals.tsx index d7a8489c..9400c7b1 100644 --- a/compliant-reward-distribution/frontend/src/components/AdminGetPendingApprovals.tsx +++ b/compliant-reward-distribution/frontend/src/components/AdminGetPendingApprovals.tsx @@ -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' }); @@ -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); diff --git a/compliant-reward-distribution/frontend/src/components/AdminSetClaimed.tsx b/compliant-reward-distribution/frontend/src/components/AdminSetClaimed.tsx index aced57a0..1604b04c 100644 --- a/compliant-reward-distribution/frontend/src/components/AdminSetClaimed.tsx +++ b/compliant-reward-distribution/frontend/src/components/AdminSetClaimed.tsx @@ -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; @@ -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); } diff --git a/compliant-reward-distribution/frontend/src/components/TweetSubmission.tsx b/compliant-reward-distribution/frontend/src/components/TweetSubmission.tsx index 061d8d36..b7875f51 100644 --- a/compliant-reward-distribution/frontend/src/components/TweetSubmission.tsx +++ b/compliant-reward-distribution/frontend/src/components/TweetSubmission.tsx @@ -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 { @@ -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) { diff --git a/compliant-reward-distribution/frontend/src/components/ZkProofSubmission.tsx b/compliant-reward-distribution/frontend/src/components/ZkProofSubmission.tsx index 11cdf94d..9b69d4c6 100644 --- a/compliant-reward-distribution/frontend/src/components/ZkProofSubmission.tsx +++ b/compliant-reward-distribution/frontend/src/components/ZkProofSubmission.tsx @@ -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; @@ -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]); diff --git a/compliant-reward-distribution/frontend/src/constants.ts b/compliant-reward-distribution/frontend/src/constants.ts index 447f9de7..d1910421 100644 --- a/compliant-reward-distribution/frontend/src/constants.ts +++ b/compliant-reward-distribution/frontend/src/constants.ts @@ -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 @@ -50,6 +47,3 @@ export const walletConnectOpts: SignClientTypes.Options = { icons: ['https://walletconnect.com/walletconnect-logo.png'], }, }; - -export const SERIALIZATION_HELPER_SCHEMA_PERMIT_MESSAGE = - 'FAAFAAAAEAAAAGNvbnRyYWN0X2FkZHJlc3MMBQAAAG5vbmNlBQkAAAB0aW1lc3RhbXANCwAAAGVudHJ5X3BvaW50FgEHAAAAcGF5bG9hZBABAg=='; diff --git a/compliant-reward-distribution/frontend/src/utils.ts b/compliant-reward-distribution/frontend/src/utils.ts index 95c540f8..0d14331d 100644 --- a/compliant-reward-distribution/frontend/src/utils.ts +++ b/compliant-reward-distribution/frontend/src/utils.ts @@ -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; @@ -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 { + 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 */ diff --git a/compliant-reward-distribution/frontend/wallet-connection.tsx b/compliant-reward-distribution/frontend/wallet-connection.tsx index c737191c..b0fa3e6a 100644 --- a/compliant-reward-distribution/frontend/wallet-connection.tsx +++ b/compliant-reward-distribution/frontend/wallet-connection.tsx @@ -3,6 +3,8 @@ import { AccountTransactionSignature, CredentialStatements, HexString, + serializeTypeValue, + toBuffer, VerifiablePresentation, } from '@concordium/web-sdk'; import { SessionTypes } from '@walletconnect/types'; @@ -10,8 +12,15 @@ 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; @@ -23,7 +32,12 @@ export abstract class WalletProvider extends EventEmitter { disconnect?(): Promise; - abstract signMessage(accountAddress: string, message: string): Promise; + abstract signMessage( + accountAddress: string, + message: string | string[] | object, + recentBlockHash: Uint8Array, + schema: string, + ): Promise; /** * @param account string when account is changed, undefined when disconnected @@ -66,8 +80,25 @@ export class BrowserWalletProvider extends WalletProvider { return browserWalletInstance; } - async signMessage(accountAddress: string, message: string): Promise { - return this.provider.signMessage(accountAddress, message); + async signMessage( + accountAddress: string, + message: string | string[] | object, + recentBlockHash: Uint8Array, + schema: string, + ): Promise { + 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 { @@ -155,9 +186,16 @@ export class WalletConnectProvider extends WalletProvider { } } - async signMessage(accountAddress: string, message: string): Promise { + async signMessage( + accountAddress: string, + message: string | string[] | object, + recentBlockHash: Uint8Array, + schema: string, + ): Promise { console.log(accountAddress); console.log(message); + console.log(recentBlockHash); + console.log(schema); throw new Error('Not yet implemented'); } diff --git a/compliant-reward-distribution/indexer-and-server/Cargo.toml b/compliant-reward-distribution/indexer-and-server/Cargo.toml index fc7332b7..3f651a81 100644 --- a/compliant-reward-distribution/indexer-and-server/Cargo.toml +++ b/compliant-reward-distribution/indexer-and-server/Cargo.toml @@ -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"] } diff --git a/compliant-reward-distribution/indexer-and-server/src/bin/server.rs b/compliant-reward-distribution/indexer-and-server/src/bin/server.rs index 4f6ee95b..537ab1fd 100644 --- a/compliant-reward-distribution/indexer-and-server/src/bin/server.rs +++ b/compliant-reward-distribution/indexer-and-server/src/bin/server.rs @@ -7,6 +7,7 @@ use axum::{ }; use chrono::Utc; use clap::Parser; +use concordium_rust_sdk::smart_contracts::common::to_bytes; use concordium_rust_sdk::{ id::{ constants::ArCurve, @@ -23,17 +24,17 @@ use concordium_rust_sdk::{ }; use indexer::{ constants::{ - CONTEXT_STRING, CURRENT_TWEET_VERIFICATION_VERSION, CURRENT_ZK_PROOF_VERIFICATION_VERSION, - MAX_REQUEST_LIMIT, SIGNATURE_AND_PROOF_EXPIRY_DURATION_BLOCKS, TESTNET_GENESIS_BLOCK_HASH, - ZK_STATEMENTS, + CONTEXT_STRING, CONTEXT_STRING_2, CURRENT_TWEET_VERIFICATION_VERSION, + CURRENT_ZK_PROOF_VERIFICATION_VERSION, MAX_REQUEST_LIMIT, + SIGNATURE_AND_PROOF_EXPIRY_DURATION_BLOCKS, TESTNET_GENESIS_BLOCK_HASH, ZK_STATEMENTS, }, db::{AccountData, Database, StoredAccountData}, error::ServerError, types::{ CanClaimParam, CanClaimReturn, ClaimExpiryDurationDays, GetAccountDataParam, - GetPendingApprovalsParam, HasSigningData, Health, PostTweetParam, PostZKProofParam, - SetClaimedParam, SigningData, UserData, VecAccountDataReturn, ZKProofExtractedData, - ZKProofStatementsReturn, + GetPendingApprovalsParam, HasSigningData, Health, MessageSigned, PostTweetParam, + PostZKProofParam, SetClaimedParam, SigningData, UserData, VecAccountDataReturn, + ZKProofExtractedData, ZKProofStatementsReturn, }, }; use sha2::Digest; @@ -440,25 +441,17 @@ where .map_err(ServerError::QueryError)? .block_hash; - // Calculate the `message_hash` that was signed. - // The `message_hash` consists of the`block_hash` (this + // Calculate the `message` that was signed. + // The `message` consists of the`block_hash` (this // ensures that the signature is generated on the spot and the signature // expires after SIGNATURE_AND_PROOF_EXPIRY_DURATION_BLOCKS), a context // string (this ensures that an account can be re-used for signing in different // Concordium services), and the message. - let message_bytes = bincode::serialize(message)?; - let message_hash = sha2::Sha256::digest( - [ - &block_hash as &[u8], - &CONTEXT_STRING, - // Remove the lenght prefix (first 8 bytes). - &message_bytes[8..], - ] - .concat(), - ); - - // The Concordium wallets convert the message string into ASCII characters before singing. - let ascii_characters: Vec = hex::encode(message_hash).as_bytes().to_vec(); + let message_signed_in_wallet = MessageSigned { + block_hash: hex::encode(block_hash), + context_string: CONTEXT_STRING_2.to_string(), + message, + }; // The message signed in the Concordium wallet is prepended with the // `account` address (signer) and 8 zero bytes. Accounts in the Concordium @@ -469,8 +462,14 @@ where // does not accidentally sign a transaction. The account nonce is of type // u64 (8 bytes). // Add the prepend to the message and calculate the final message hash. - let final_message_hash = - sha2::Sha256::digest([&signer.as_ref() as &[u8], &[0u8; 8], &ascii_characters].concat()); + let final_message_hash = sha2::Sha256::digest( + [ + &signer.as_ref() as &[u8], + &[0u8; 8], + to_bytes(&message_signed_in_wallet).as_slice(), + ] + .concat(), + ); // Get the public key of the signer. diff --git a/compliant-reward-distribution/indexer-and-server/src/constants.rs b/compliant-reward-distribution/indexer-and-server/src/constants.rs index e28f46ca..f4e2c5aa 100644 --- a/compliant-reward-distribution/indexer-and-server/src/constants.rs +++ b/compliant-reward-distribution/indexer-and-server/src/constants.rs @@ -16,6 +16,7 @@ pub const CONTEXT_STRING: [u8; 45] = [ 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, ]; +pub const CONTEXT_STRING_2: &str = "CONCORDIUM_COMPLIANT_REWARD_DISTRIBUTION_DAPP"; /// The number of blocks after that a generated signature or ZK proof is /// considered expired. diff --git a/compliant-reward-distribution/indexer-and-server/src/types.rs b/compliant-reward-distribution/indexer-and-server/src/types.rs index 07dd2f2c..9d81bf00 100644 --- a/compliant-reward-distribution/indexer-and-server/src/types.rs +++ b/compliant-reward-distribution/indexer-and-server/src/types.rs @@ -3,6 +3,7 @@ use crate::{ DatabasePool, }; use chrono::Days; +use concordium_rust_sdk::smart_contracts::common as concordium_std; use concordium_rust_sdk::{ common::types::Signature, id::{ @@ -10,6 +11,7 @@ use concordium_rust_sdk::{ id_proof_types::Statement, types::{AccountAddress, GlobalContext}, }, + smart_contracts::common::Serial, types::AbsoluteBlockHeight, v2::Client, web3id::{did::Network, Presentation, Web3IdAttribute}, @@ -44,7 +46,7 @@ pub struct Server { /// additional data to be part of the message signed. #[derive(serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] -pub struct SigningData { +pub struct SigningData { /// Signer account. pub signer: AccountAddress, /// Message signed. @@ -60,7 +62,7 @@ pub struct SigningData { /// Trait definition of `HasSigningData`. This trait is implemented for all /// input parameter structs used by endpoints that require a signature check. pub trait HasSigningData { - type Message; + type Message: Serial; fn signing_data(&self) -> &SigningData; } @@ -88,7 +90,7 @@ pub struct ZKProofExtractedData { /// Message struct for the `postTweet` endpoint. #[repr(transparent)] -#[derive(serde::Deserialize, serde::Serialize)] +#[derive(serde::Deserialize, serde::Serialize, Serial)] #[serde(rename_all = "camelCase")] pub struct TweetMessage { pub tweet: String, @@ -113,7 +115,7 @@ pub struct PostTweetParam { /// Message struct for the `setClaimed` endpoint. #[repr(transparent)] -#[derive(serde::Deserialize, serde::Serialize)] +#[derive(serde::Deserialize, serde::Serialize, Serial)] #[serde(rename_all = "camelCase")] pub struct SetClaimedMessage { /// Vector of accounts that should be marked as `claimed` in the database. @@ -171,7 +173,7 @@ impl HasSigningData for GetAccountDataParam { /// Message struct for the `getAccountData` endpoint. #[repr(transparent)] -#[derive(serde::Deserialize, serde::Serialize)] +#[derive(serde::Deserialize, serde::Serialize, Serial)] #[serde(rename_all = "camelCase")] pub struct GetAccountDataMessage { /// Account address for which the data should be retrieved. @@ -194,7 +196,7 @@ pub struct AccountDataReturn { } /// Message struct for the `getPendingApprovals` endpoint. -#[derive(serde::Deserialize, serde::Serialize)] +#[derive(serde::Deserialize, serde::Serialize, Serial)] #[serde(rename_all = "camelCase")] pub struct GetPendingApprovalsMessage { /// Limit used in the query to the database. @@ -272,3 +274,13 @@ impl std::fmt::Display for ClaimExpiryDurationDays { write!(f, "{:?}", self.0) } } + +/// Final message signed in the wallet. +/// It contains in addition the `context_string` (so that signatures cannot be replayed +/// across Concordium services) and `block_hash` (so that signatures expire). +#[derive(Debug, Serial)] +pub struct MessageSigned { + pub context_string: String, + pub message: T, + pub block_hash: String, +}