From d02e493edfe6ea946c6dbc37c15008af1ef1a3c5 Mon Sep 17 00:00:00 2001 From: orhoj Date: Wed, 13 Dec 2023 10:23:43 +0100 Subject: [PATCH 01/44] Replace key deriving functions with concordium-base impl --- deps/concordium-base | 2 +- packages/rust-bindings/Cargo.lock | 23 ++- packages/rust-bindings/Cargo.toml | 3 + .../rust-bindings/packages/wallet/Cargo.toml | 1 + .../packages/wallet/src/aux_functions.rs | 143 +----------------- .../packages/wallet/src/external_functions.rs | 1 + 6 files changed, 26 insertions(+), 147 deletions(-) diff --git a/deps/concordium-base b/deps/concordium-base index 97a0efec0..63ae07498 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit 97a0efec01661e6c1efaf5ea9b464d0d830fddf4 +Subproject commit 63ae07498313819f1b7f38f1d5508d4b1a5d4aea diff --git a/packages/rust-bindings/Cargo.lock b/packages/rust-bindings/Cargo.lock index abcb9c1d5..cda274721 100644 --- a/packages/rust-bindings/Cargo.lock +++ b/packages/rust-bindings/Cargo.lock @@ -234,7 +234,7 @@ dependencies = [ [[package]] name = "concordium-contracts-common" -version = "8.0.0" +version = "8.1.1" dependencies = [ "base64", "bs58", @@ -254,7 +254,7 @@ dependencies = [ [[package]] name = "concordium-contracts-common-derive" -version = "4.0.0" +version = "4.0.1" dependencies = [ "proc-macro2", "quote", @@ -263,7 +263,7 @@ dependencies = [ [[package]] name = "concordium_base" -version = "3.0.1" +version = "3.2.0" dependencies = [ "anyhow", "bs58", @@ -348,6 +348,7 @@ dependencies = [ "serde_json", "serde_with", "thiserror", + "wallet_library", "wasm-bindgen", ] @@ -790,7 +791,7 @@ dependencies = [ [[package]] name = "key_derivation" -version = "1.3.0" +version = "2.0.1" dependencies = [ "concordium_base", "ed25519-dalek", @@ -800,6 +801,7 @@ dependencies = [ "pbkdf2", "serde", "sha2 0.10.7", + "thiserror", ] [[package]] @@ -1536,6 +1538,19 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wallet_library" +version = "0.1.0" +dependencies = [ + "anyhow", + "concordium_base", + "hex", + "key_derivation", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" diff --git a/packages/rust-bindings/Cargo.toml b/packages/rust-bindings/Cargo.toml index 4fa3c8774..f01dff352 100644 --- a/packages/rust-bindings/Cargo.toml +++ b/packages/rust-bindings/Cargo.toml @@ -28,3 +28,6 @@ path = "../../deps/concordium-base/rust-src/ed25519_hd_key_derivation" [workspace.dependencies.key_derivation] path = "../../deps/concordium-base/rust-src/key_derivation" + +[workspace.dependencies.wallet_library] +path = "../../deps/concordium-base/rust-src/wallet_library" diff --git a/packages/rust-bindings/packages/wallet/Cargo.toml b/packages/rust-bindings/packages/wallet/Cargo.toml index 763e885f6..4008f249a 100644 --- a/packages/rust-bindings/packages/wallet/Cargo.toml +++ b/packages/rust-bindings/packages/wallet/Cargo.toml @@ -19,6 +19,7 @@ serde-wasm-bindgen.workspace = true concordium_base.workspace = true ed25519_hd_key_derivation.workspace = true key_derivation.workspace = true +wallet_library.workspace = true concordium_rust_bindings_common.workspace = true ed25519-dalek = { version = "=1.0" } chrono = "0.4.24" diff --git a/packages/rust-bindings/packages/wallet/src/aux_functions.rs b/packages/rust-bindings/packages/wallet/src/aux_functions.rs index af61026d4..f84a42f74 100644 --- a/packages/rust-bindings/packages/wallet/src/aux_functions.rs +++ b/packages/rust-bindings/packages/wallet/src/aux_functions.rs @@ -18,7 +18,7 @@ use concordium_base::{ dodis_yampolskiy_prf as prf, id_proof_types::{Proof, ProofVersion, Statement, StatementWithContext}, pedersen_commitment::{ - CommitmentKey as PedersenKey, Randomness as PedersenRandomness, Value as PedersenValue, + Randomness as PedersenRandomness, Value as PedersenValue, Value, }, secret_sharing::Threshold, @@ -143,147 +143,6 @@ fn get_wallet(seed_as_hex: HexString, raw_net: &str) -> anyhow::Result Result { - let wallet = get_wallet(seed_as_hex, raw_net)?; - let key = wallet.get_account_signing_key( - identity_provider_index, - identity_index, - credential_counter, - )?; - Ok(hex::encode(key.as_bytes())) -} - -pub fn get_account_public_key_aux( - seed_as_hex: HexString, - raw_net: &str, - identity_provider_index: u32, - identity_index: u32, - credential_counter: u32, -) -> Result { - let wallet = get_wallet(seed_as_hex, raw_net)?; - let key = wallet.get_account_public_key( - identity_provider_index, - identity_index, - credential_counter, - )?; - Ok(hex::encode(key.as_bytes())) -} - -pub fn get_credential_id_aux( - seed_as_hex: HexString, - raw_net: &str, - identity_provider_index: u32, - identity_index: u32, - credential_counter: u8, - raw_on_chain_commitment_key: &str, -) -> Result { - let wallet = get_wallet(seed_as_hex, raw_net)?; - let prf_key = wallet.get_prf_key(identity_provider_index, identity_index)?; - - let cred_id_exponent = prf_key.prf_exponent(credential_counter)?; - let on_chain_commitment_key: PedersenKey = - base16_decode_string(raw_on_chain_commitment_key)?; - let cred_id = on_chain_commitment_key - .hide( - &Value::::new(cred_id_exponent), - &PedersenRandomness::zero(), - ) - .0; - Ok(base16_encode_string(&cred_id)) -} - -pub fn get_prf_key_aux( - seed_as_hex: HexString, - raw_net: &str, - identity_provider_index: u32, - identity_index: u32, -) -> Result { - let wallet = get_wallet(seed_as_hex, raw_net)?; - let key = wallet.get_prf_key(identity_provider_index, identity_index)?; - Ok(hex::encode(to_bytes(&key))) -} - -pub fn get_id_cred_sec_aux( - seed_as_hex: HexString, - raw_net: &str, - identity_provider_index: u32, - identity_index: u32, -) -> Result { - let wallet = get_wallet(seed_as_hex, raw_net)?; - let key = wallet.get_id_cred_sec(identity_provider_index, identity_index)?; - Ok(hex::encode(to_bytes(&key))) -} - -pub fn get_signature_blinding_randomness_aux( - seed_as_hex: HexString, - raw_net: &str, - identity_provider_index: u32, - identity_index: u32, -) -> Result { - let wallet = get_wallet(seed_as_hex, raw_net)?; - let key = wallet.get_blinding_randomness(identity_provider_index, identity_index)?; - Ok(hex::encode(to_bytes(&key))) -} - -pub fn get_attribute_commitment_randomness_aux( - seed_as_hex: HexString, - raw_net: &str, - identity_provider_index: u32, - identity_index: u32, - credential_counter: u32, - attribute: u8, -) -> Result { - let wallet = get_wallet(seed_as_hex, raw_net)?; - let key = wallet.get_attribute_commitment_randomness( - identity_provider_index, - identity_index, - credential_counter, - AttributeTag(attribute), - )?; - Ok(hex::encode(to_bytes(&key))) -} - -pub fn get_verifiable_credential_signing_key_aux( - seed_as_hex: HexString, - raw_net: &str, - issuer_index: u64, - issuer_subindex: u64, - verifiable_credential_index: u32, -) -> Result { - let issuer: ContractAddress = ContractAddress::new(issuer_index, issuer_subindex); - let wallet = get_wallet(seed_as_hex, raw_net)?; - let key = wallet.get_verifiable_credential_signing_key(issuer, verifiable_credential_index)?; - Ok(hex::encode(key.as_bytes())) -} - -pub fn get_verifiable_credential_public_key_aux( - seed_as_hex: HexString, - raw_net: &str, - issuer_index: u64, - issuer_subindex: u64, - verifiable_credential_index: u32, -) -> Result { - let issuer: ContractAddress = ContractAddress::new(issuer_index, issuer_subindex); - let wallet = get_wallet(seed_as_hex, raw_net)?; - let key = wallet.get_verifiable_credential_public_key(issuer, verifiable_credential_index)?; - Ok(hex::encode(key.as_bytes())) -} - -pub fn get_verifiable_credential_backup_encryption_key_aux( - seed_as_hex: HexString, - raw_net: &str, -) -> Result { - let wallet = get_wallet(seed_as_hex, raw_net)?; - let key = wallet.get_verifiable_credential_backup_encryption_key()?; - Ok(hex::encode(key.as_bytes())) -} - pub fn create_id_request_v1_aux(input: IdRequestInput) -> Result { let seed_decoded = hex::decode(&input.seed)?; let seed: [u8; 64] = match seed_decoded.try_into() { diff --git a/packages/rust-bindings/packages/wallet/src/external_functions.rs b/packages/rust-bindings/packages/wallet/src/external_functions.rs index 16ed41c36..76aced14f 100644 --- a/packages/rust-bindings/packages/wallet/src/external_functions.rs +++ b/packages/rust-bindings/packages/wallet/src/external_functions.rs @@ -3,6 +3,7 @@ use concordium_rust_bindings_common::{ helpers::{to_js_error, JsResult}, types::{Base58String, HexString, JsonString}, }; +use wallet_library::wallet::{get_account_signing_key_aux, get_account_public_key_aux, get_credential_id_aux, get_prf_key_aux, get_id_cred_sec_aux, get_signature_blinding_randomness_aux, get_attribute_commitment_randomness_aux, get_verifiable_credential_signing_key_aux, get_verifiable_credential_public_key_aux, get_verifiable_credential_backup_encryption_key_aux}; use wasm_bindgen::prelude::*; #[wasm_bindgen(js_name = generateUnsignedCredential)] From c06487127f82a476fae2a87b57fe51692d7eeff4 Mon Sep 17 00:00:00 2001 From: orhoj Date: Wed, 13 Dec 2023 11:53:33 +0100 Subject: [PATCH 02/44] Replace identity request functions concordium-base impl --- .../packages/wallet/src/aux_functions.rs | 118 +----------------- .../packages/wallet/src/external_functions.rs | 4 +- packages/sdk/src/wasm/identity.ts | 20 ++- 3 files changed, 22 insertions(+), 120 deletions(-) diff --git a/packages/rust-bindings/packages/wallet/src/aux_functions.rs b/packages/rust-bindings/packages/wallet/src/aux_functions.rs index f84a42f74..10c1f2a05 100644 --- a/packages/rust-bindings/packages/wallet/src/aux_functions.rs +++ b/packages/rust-bindings/packages/wallet/src/aux_functions.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, bail, ensure, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use concordium_base::{ base::{BakerAggregationSignKey, BakerElectionSignKey, BakerKeyPairs, BakerSignatureSignKey}, cis4_types::IssuerKey, @@ -10,9 +10,7 @@ use concordium_base::{ curve_arithmetic::Pairing, id::{ account_holder::{ - create_credential, create_unsigned_credential, generate_id_recovery_request, - generate_pio_v1, - }, + create_credential, create_unsigned_credential}, constants, constants::{ArCurve, AttributeKind}, dodis_yampolskiy_prf as prf, @@ -21,7 +19,6 @@ use concordium_base::{ Randomness as PedersenRandomness, Value as PedersenValue, Value, }, - secret_sharing::Threshold, types::*, }, ps_sig::SigRetrievalRandomness, @@ -36,7 +33,7 @@ use either::Either::Left; use key_derivation::{ConcordiumHdWallet, CredentialContext, Net}; use rand::thread_rng; use serde::{Deserialize as SerdeDeserialize, Serialize as SerdeSerialize}; -use serde_json::{from_str, from_value, to_string, Value as SerdeValue}; +use serde_json::{from_str, from_value, Value as SerdeValue}; use std::{collections::BTreeMap, convert::TryInto}; use thiserror::Error; @@ -111,18 +108,6 @@ pub struct CredId { pub cred_id: constants::ArCurve, } -#[derive(SerdeSerialize, SerdeDeserialize)] -#[serde(rename_all = "camelCase")] -pub struct IdRequestInput { - ip_info: IpInfo, - global_context: GlobalContext, - ars_infos: BTreeMap>, - seed: String, - net: String, - identity_index: u32, - ar_threshold: u8, -} - fn get_net(net: &str) -> Result { Ok(match net { "Mainnet" => Net::Mainnet, @@ -131,103 +116,6 @@ fn get_net(net: &str) -> Result { }) } -fn get_wallet(seed_as_hex: HexString, raw_net: &str) -> anyhow::Result { - let seed_decoded = hex::decode(&seed_as_hex)?; - let seed: [u8; 64] = match seed_decoded.try_into() { - Ok(s) => s, - Err(_) => bail!("The provided seed {} was not 64 bytes", seed_as_hex), - }; - - let net = get_net(raw_net)?; - let wallet = ConcordiumHdWallet { seed, net }; - Ok(wallet) -} - -pub fn create_id_request_v1_aux(input: IdRequestInput) -> Result { - let seed_decoded = hex::decode(&input.seed)?; - let seed: [u8; 64] = match seed_decoded.try_into() { - Ok(s) => s, - Err(_) => bail!("The provided seed {} was not 64 bytes", input.seed), - }; - let identity_provider_index = input.ip_info.ip_identity.0; - - let net = get_net(&input.net)?; - let wallet = ConcordiumHdWallet { seed, net }; - - let prf_key: prf::SecretKey = - wallet.get_prf_key(identity_provider_index, input.identity_index)?; - - let id_cred_sec: PedersenValue = - PedersenValue::new(wallet.get_id_cred_sec(identity_provider_index, input.identity_index)?); - let id_cred: IdCredentials = IdCredentials { id_cred_sec }; - - let sig_retrievel_randomness: concordium_base::id::ps_sig::SigRetrievalRandomness< - constants::IpPairing, - > = wallet.get_blinding_randomness(identity_provider_index, input.identity_index)?; - - let num_of_ars = input.ars_infos.len(); - - ensure!(input.ar_threshold > 0, "arThreshold must be at least 1."); - ensure!( - num_of_ars >= usize::from(input.ar_threshold), - "Number of anonymity revokers in arsInfos should be at least arThreshold." - ); - - let threshold = Threshold(input.ar_threshold); - - let chi = CredentialHolderInfo:: { id_cred }; - - let aci = AccCredentialInfo { - cred_holder_info: chi, - prf_key, - }; - - let context = IpContext::new(&input.ip_info, &input.ars_infos, &input.global_context); - - let id_use_data = IdObjectUseData { - aci, - randomness: sig_retrievel_randomness, - }; - let (pio, _) = { - match generate_pio_v1(&context, threshold, &id_use_data) { - Some(x) => x, - None => bail!("Generating the pre-identity object failed."), - } - }; - - let response = json!({ "idObjectRequest": Versioned::new(VERSION_0, pio) }); - - Ok(to_string(&response)?) -} - -#[derive(SerdeSerialize, SerdeDeserialize)] -#[serde(rename_all = "camelCase")] -pub struct IdRecoveryRequestInput { - ip_info: IpInfo, - global_context: GlobalContext, - seed_as_hex: HexString, - net: String, - identity_index: u32, - timestamp: u64, -} - -pub fn create_identity_recovery_request_aux(input: IdRecoveryRequestInput) -> Result { - let identity_provider_index = input.ip_info.ip_identity.0; - let wallet = get_wallet(input.seed_as_hex, &input.net)?; - let id_cred_sec = wallet.get_id_cred_sec(identity_provider_index, input.identity_index)?; - let request = generate_id_recovery_request( - &input.ip_info, - &input.global_context, - &PedersenValue::new(id_cred_sec), - input.timestamp, - ); - - let response = json!({ - "idRecoveryRequest": Versioned::new(VERSION_0, request), - }); - Ok(to_string(&response)?) -} - #[derive(SerdeSerialize, SerdeDeserialize)] #[serde(rename_all = "camelCase")] pub struct CredentialInput { diff --git a/packages/rust-bindings/packages/wallet/src/external_functions.rs b/packages/rust-bindings/packages/wallet/src/external_functions.rs index 76aced14f..f21da099f 100644 --- a/packages/rust-bindings/packages/wallet/src/external_functions.rs +++ b/packages/rust-bindings/packages/wallet/src/external_functions.rs @@ -3,7 +3,7 @@ use concordium_rust_bindings_common::{ helpers::{to_js_error, JsResult}, types::{Base58String, HexString, JsonString}, }; -use wallet_library::wallet::{get_account_signing_key_aux, get_account_public_key_aux, get_credential_id_aux, get_prf_key_aux, get_id_cred_sec_aux, get_signature_blinding_randomness_aux, get_attribute_commitment_randomness_aux, get_verifiable_credential_signing_key_aux, get_verifiable_credential_public_key_aux, get_verifiable_credential_backup_encryption_key_aux}; +use wallet_library::{wallet::{get_account_signing_key_aux, get_account_public_key_aux, get_credential_id_aux, get_prf_key_aux, get_id_cred_sec_aux, get_signature_blinding_randomness_aux, get_attribute_commitment_randomness_aux, get_verifiable_credential_signing_key_aux, get_verifiable_credential_public_key_aux, get_verifiable_credential_backup_encryption_key_aux}, identity::{create_id_request_v1_aux, create_identity_recovery_request_with_seed_aux}}; use wasm_bindgen::prelude::*; #[wasm_bindgen(js_name = generateUnsignedCredential)] @@ -48,7 +48,7 @@ pub fn create_id_request_v1_ext(input: JsonString) -> JsResult { #[wasm_bindgen(js_name = createIdentityRecoveryRequest)] pub fn create_identity_recovery_request_ext(input: JsonString) -> JsResult { - create_identity_recovery_request_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) + create_identity_recovery_request_with_seed_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) } #[wasm_bindgen(js_name = createCredentialV1)] diff --git a/packages/sdk/src/wasm/identity.ts b/packages/sdk/src/wasm/identity.ts index bf8ea4fca..5df71a2fc 100644 --- a/packages/sdk/src/wasm/identity.ts +++ b/packages/sdk/src/wasm/identity.ts @@ -10,14 +10,22 @@ import type { } from '../types.js'; import type { IdProofInput, IdProofOutput } from '../id/index.js'; -export type IdentityRequestInput = { +interface IdentityRequestInputCommon { ipInfo: IpInfo; globalContext: CryptographicParameters; arsInfos: Record; - seed: string; net: Network; identityIndex: number; arThreshold: number; +} + +export type IdentityRequestInput = IdentityRequestInputCommon & { + seed: string; +}; + +type IdentityRequestInputInternal = { + common: IdentityRequestInputCommon; + seed: string; }; /** @@ -26,7 +34,13 @@ export type IdentityRequestInput = { export function createIdentityRequest( input: IdentityRequestInput ): Versioned { - const rawRequest = wasm.createIdRequestV1(JSON.stringify(input)); + const { seed, ...common } = input; + const internalInput: IdentityRequestInputInternal = { + common, + seed, + }; + + const rawRequest = wasm.createIdRequestV1(JSON.stringify(internalInput)); try { return JSON.parse(rawRequest).idObjectRequest; } catch (e) { From cb95485f683602a84e42ce6b12c57229a51687f8 Mon Sep 17 00:00:00 2001 From: orhoj Date: Wed, 13 Dec 2023 14:50:57 +0100 Subject: [PATCH 03/44] Replace create unsigned credential with concordium-base impl --- .../packages/wallet/src/aux_functions.rs | 42 ------------------- .../packages/wallet/src/external_functions.rs | 2 +- .../wasm/credentialDeploymentTransactions.ts | 14 ++++++- 3 files changed, 13 insertions(+), 45 deletions(-) diff --git a/packages/rust-bindings/packages/wallet/src/aux_functions.rs b/packages/rust-bindings/packages/wallet/src/aux_functions.rs index 10c1f2a05..6fc7f5852 100644 --- a/packages/rust-bindings/packages/wallet/src/aux_functions.rs +++ b/packages/rust-bindings/packages/wallet/src/aux_functions.rs @@ -525,48 +525,6 @@ impl HasAttributeRandomness for AttributeRandomness { } } -pub fn create_unsigned_credential_v1_aux(input: UnsignedCredentialInput) -> Result { - let chi = CredentialHolderInfo:: { - id_cred: IdCredentials { - id_cred_sec: input.id_cred_sec, - }, - }; - let aci = AccCredentialInfo { - cred_holder_info: chi, - prf_key: input.prf_key, - }; - - let blinding_randomness: Value = concordium_base::common::from_bytes( - &mut hex::decode(&input.sig_retrievel_randomness)?.as_slice(), - )?; - let id_use_data = IdObjectUseData { - aci, - randomness: - concordium_base::id::ps_sig::SigRetrievalRandomness::::new( - *blinding_randomness, - ), - }; - - let context = IpContext::new(&input.ip_info, &input.ars_infos, &input.global_context); - - let policy = build_policy(&input.id_object.alist, input.revealed_attributes)?; - - let (cdi, rand) = create_unsigned_credential( - context, - &input.id_object, - &id_use_data, - input.cred_number, - policy, - input.credential_public_keys, - None, - &AttributeRandomness(input.attribute_randomness), - )?; - - let response = json!({"unsignedCdi": cdi, "randomness": rand}); - - Ok(response.to_string()) -} - #[derive(SerdeSerialize)] #[serde(rename_all = "camelCase")] pub struct BakerKeys { diff --git a/packages/rust-bindings/packages/wallet/src/external_functions.rs b/packages/rust-bindings/packages/wallet/src/external_functions.rs index f21da099f..8d01d4439 100644 --- a/packages/rust-bindings/packages/wallet/src/external_functions.rs +++ b/packages/rust-bindings/packages/wallet/src/external_functions.rs @@ -3,7 +3,7 @@ use concordium_rust_bindings_common::{ helpers::{to_js_error, JsResult}, types::{Base58String, HexString, JsonString}, }; -use wallet_library::{wallet::{get_account_signing_key_aux, get_account_public_key_aux, get_credential_id_aux, get_prf_key_aux, get_id_cred_sec_aux, get_signature_blinding_randomness_aux, get_attribute_commitment_randomness_aux, get_verifiable_credential_signing_key_aux, get_verifiable_credential_public_key_aux, get_verifiable_credential_backup_encryption_key_aux}, identity::{create_id_request_v1_aux, create_identity_recovery_request_with_seed_aux}}; +use wallet_library::{wallet::{get_account_signing_key_aux, get_account_public_key_aux, get_credential_id_aux, get_prf_key_aux, get_id_cred_sec_aux, get_signature_blinding_randomness_aux, get_attribute_commitment_randomness_aux, get_verifiable_credential_signing_key_aux, get_verifiable_credential_public_key_aux, get_verifiable_credential_backup_encryption_key_aux}, identity::{create_id_request_v1_aux, create_identity_recovery_request_with_seed_aux}, credential::create_unsigned_credential_v1_aux}; use wasm_bindgen::prelude::*; #[wasm_bindgen(js_name = generateUnsignedCredential)] diff --git a/packages/sdk/src/wasm/credentialDeploymentTransactions.ts b/packages/sdk/src/wasm/credentialDeploymentTransactions.ts index 28cdfda74..615b9d7af 100644 --- a/packages/sdk/src/wasm/credentialDeploymentTransactions.ts +++ b/packages/sdk/src/wasm/credentialDeploymentTransactions.ts @@ -91,7 +91,7 @@ function createUnsignedCredentialInfo( /** * Create a credential deployment transaction, which is the transaction used * when deploying a new account. - * @deprecated This function doesn't use allow supplying the randomness. {@link createCredentialTransactionV1 } or { @link createCredentialTransactionV1NoSeed } should be used instead. + * @deprecated This function doesn't use allow supplying the randomness. {@link createCredentialTransaction} or {@link createCredentialTransactionNoSeed} should be used instead. * @param identity the identity to create a credential for * @param cryptographicParameters the global cryptographic parameters from the chain * @param threshold the signature threshold for the credential, has to be less than number of public keys @@ -286,7 +286,17 @@ export function createCredentialTransactionNoSeed( input: CredentialInputNoSeed, expiry: TransactionExpiry.Type ): CredentialDeploymentTransaction { - const rawRequest = wasm.createUnsignedCredentialV1(JSON.stringify(input)); + const { prfKey, idCredSec, sigRetrievelRandomness, ...common } = input; + const internalInput = { + common, + prfKey, + idCredSec, + blindingRandomness: sigRetrievelRandomness, + }; + + const rawRequest = wasm.createUnsignedCredentialV1( + JSON.stringify(internalInput) + ); let info: UnsignedCdiWithRandomness; try { info = JSON.parse(rawRequest); From f5880ec2effbdbc3b192d13dc1f803b85725d2b1 Mon Sep 17 00:00:00 2001 From: orhoj Date: Thu, 14 Dec 2023 15:25:30 +0100 Subject: [PATCH 04/44] Adjust types to changes in wallet_library --- deps/concordium-base | 2 +- packages/rust-bindings/Cargo.lock | 2 +- .../packages/wallet/src/aux_functions.rs | 8 ++--- .../packages/wallet/src/external_functions.rs | 22 +++++++++--- packages/sdk/src/wasm/identity.ts | 36 +++++++++++++++---- 5 files changed, 51 insertions(+), 19 deletions(-) diff --git a/deps/concordium-base b/deps/concordium-base index 63ae07498..9205a032d 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit 63ae07498313819f1b7f38f1d5508d4b1a5d4aea +Subproject commit 9205a032de93e711c8345f82ad4afdb0553b7e81 diff --git a/packages/rust-bindings/Cargo.lock b/packages/rust-bindings/Cargo.lock index cda274721..93b49ae03 100644 --- a/packages/rust-bindings/Cargo.lock +++ b/packages/rust-bindings/Cargo.lock @@ -791,7 +791,7 @@ dependencies = [ [[package]] name = "key_derivation" -version = "2.0.1" +version = "2.1.0" dependencies = [ "concordium_base", "ed25519-dalek", diff --git a/packages/rust-bindings/packages/wallet/src/aux_functions.rs b/packages/rust-bindings/packages/wallet/src/aux_functions.rs index 6fc7f5852..25bd2dcb3 100644 --- a/packages/rust-bindings/packages/wallet/src/aux_functions.rs +++ b/packages/rust-bindings/packages/wallet/src/aux_functions.rs @@ -9,16 +9,12 @@ use concordium_base::{ contracts_common::ContractAddress, curve_arithmetic::Pairing, id::{ - account_holder::{ - create_credential, create_unsigned_credential}, + account_holder::{create_credential, create_unsigned_credential}, constants, constants::{ArCurve, AttributeKind}, dodis_yampolskiy_prf as prf, id_proof_types::{Proof, ProofVersion, Statement, StatementWithContext}, - pedersen_commitment::{ - Randomness as PedersenRandomness, Value as PedersenValue, - Value, - }, + pedersen_commitment::{Randomness as PedersenRandomness, Value as PedersenValue, Value}, types::*, }, ps_sig::SigRetrievalRandomness, diff --git a/packages/rust-bindings/packages/wallet/src/external_functions.rs b/packages/rust-bindings/packages/wallet/src/external_functions.rs index 8d01d4439..e27c71653 100644 --- a/packages/rust-bindings/packages/wallet/src/external_functions.rs +++ b/packages/rust-bindings/packages/wallet/src/external_functions.rs @@ -3,7 +3,19 @@ use concordium_rust_bindings_common::{ helpers::{to_js_error, JsResult}, types::{Base58String, HexString, JsonString}, }; -use wallet_library::{wallet::{get_account_signing_key_aux, get_account_public_key_aux, get_credential_id_aux, get_prf_key_aux, get_id_cred_sec_aux, get_signature_blinding_randomness_aux, get_attribute_commitment_randomness_aux, get_verifiable_credential_signing_key_aux, get_verifiable_credential_public_key_aux, get_verifiable_credential_backup_encryption_key_aux}, identity::{create_id_request_v1_aux, create_identity_recovery_request_with_seed_aux}, credential::create_unsigned_credential_v1_aux}; +use wallet_library::{ + credential::create_unsigned_credential_with_keys_v1_aux, + identity::{ + create_id_request_with_seed_v1_aux, create_identity_recovery_request_with_seed_aux, + }, + wallet::{ + get_account_public_key_aux, get_account_signing_key_aux, + get_attribute_commitment_randomness_aux, get_credential_id_aux, get_id_cred_sec_aux, + get_prf_key_aux, get_signature_blinding_randomness_aux, + get_verifiable_credential_backup_encryption_key_aux, + get_verifiable_credential_public_key_aux, get_verifiable_credential_signing_key_aux, + }, +}; use wasm_bindgen::prelude::*; #[wasm_bindgen(js_name = generateUnsignedCredential)] @@ -43,12 +55,13 @@ pub fn get_credential_deployment_info_ext(signatures: &JsValue, unsigned_info: & #[wasm_bindgen(js_name = createIdRequestV1)] pub fn create_id_request_v1_ext(input: JsonString) -> JsResult { - create_id_request_v1_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) + create_id_request_with_seed_v1_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) } #[wasm_bindgen(js_name = createIdentityRecoveryRequest)] pub fn create_identity_recovery_request_ext(input: JsonString) -> JsResult { - create_identity_recovery_request_with_seed_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) + create_identity_recovery_request_with_seed_aux(serde_json::from_str(&input).unwrap()) + .map_err(to_js_error) } #[wasm_bindgen(js_name = createCredentialV1)] @@ -59,7 +72,8 @@ pub fn create_credential_v1_ext(raw_input: JsonString) -> JsResult { #[wasm_bindgen(js_name = createUnsignedCredentialV1)] pub fn create_unsigned_credential_v1_ext(input: JsonString) -> JsResult { - create_unsigned_credential_v1_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) + create_unsigned_credential_with_keys_v1_aux(serde_json::from_str(&input).unwrap()) + .map_err(to_js_error) } #[wasm_bindgen(js_name = createIdProof)] diff --git a/packages/sdk/src/wasm/identity.ts b/packages/sdk/src/wasm/identity.ts index 5df71a2fc..cbf1217b0 100644 --- a/packages/sdk/src/wasm/identity.ts +++ b/packages/sdk/src/wasm/identity.ts @@ -15,17 +15,18 @@ interface IdentityRequestInputCommon { globalContext: CryptographicParameters; arsInfos: Record; net: Network; - identityIndex: number; arThreshold: number; } export type IdentityRequestInput = IdentityRequestInputCommon & { seed: string; + identityIndex: number; }; type IdentityRequestInputInternal = { common: IdentityRequestInputCommon; - seed: string; + seedAsHex: string; + identityIndex: number; }; /** @@ -34,10 +35,11 @@ type IdentityRequestInputInternal = { export function createIdentityRequest( input: IdentityRequestInput ): Versioned { - const { seed, ...common } = input; + const { seed, identityIndex, ...common } = input; const internalInput: IdentityRequestInputInternal = { common, - seed, + seedAsHex: seed, + identityIndex, }; const rawRequest = wasm.createIdRequestV1(JSON.stringify(internalInput)); @@ -48,13 +50,24 @@ export function createIdentityRequest( } } -export type IdentityRecoveryRequestInput = { +type IdentityRecoveryRequestInputCommon = { ipInfo: IpInfo; globalContext: CryptographicParameters; + timestamp: number; +}; + +export type IdentityRecoveryRequestInput = + IdentityRecoveryRequestInputCommon & { + seedAsHex: string; + net: Network; + identityIndex: number; + }; + +type IdentityRecoveryRequestInputInternal = { + common: IdentityRecoveryRequestInputCommon; seedAsHex: string; net: Network; identityIndex: number; - timestamp: number; }; /** @@ -63,8 +76,17 @@ export type IdentityRecoveryRequestInput = { export function createIdentityRecoveryRequest( input: IdentityRecoveryRequestInput ): Versioned { + const { seedAsHex, net, identityIndex, ...common } = input; + + const internalInput: IdentityRecoveryRequestInputInternal = { + common, + identityIndex, + net, + seedAsHex, + }; + const rawRequest = wasm.createIdentityRecoveryRequest( - JSON.stringify(input) + JSON.stringify(internalInput) ); try { return JSON.parse(rawRequest).idRecoveryRequest; From fb8f559c40bad87eebec05634f3c302588234c40 Mon Sep 17 00:00:00 2001 From: orhoj Date: Thu, 14 Dec 2023 16:14:12 +0100 Subject: [PATCH 05/44] Add functions for identity requests from keys --- .../packages/wallet/src/external_functions.rs | 13 +- packages/sdk/src/wasm/identity.ts | 82 ++++++++++- packages/sdk/test/ci/id.test.ts | 131 ++++++++++++++---- 3 files changed, 193 insertions(+), 33 deletions(-) diff --git a/packages/rust-bindings/packages/wallet/src/external_functions.rs b/packages/rust-bindings/packages/wallet/src/external_functions.rs index e27c71653..51c1a7c7f 100644 --- a/packages/rust-bindings/packages/wallet/src/external_functions.rs +++ b/packages/rust-bindings/packages/wallet/src/external_functions.rs @@ -6,7 +6,8 @@ use concordium_rust_bindings_common::{ use wallet_library::{ credential::create_unsigned_credential_with_keys_v1_aux, identity::{ - create_id_request_with_seed_v1_aux, create_identity_recovery_request_with_seed_aux, + create_id_request_with_keys_v1_aux, create_id_request_with_seed_v1_aux, + create_identity_recovery_request_aux, create_identity_recovery_request_with_seed_aux, }, wallet::{ get_account_public_key_aux, get_account_signing_key_aux, @@ -58,12 +59,22 @@ pub fn create_id_request_v1_ext(input: JsonString) -> JsResult { create_id_request_with_seed_v1_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) } +#[wasm_bindgen(js_name = createIdRequestWithKeysV1)] +pub fn create_id_request_with_keys_v1_ext(input: JsonString) -> JsResult { + create_id_request_with_keys_v1_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) +} + #[wasm_bindgen(js_name = createIdentityRecoveryRequest)] pub fn create_identity_recovery_request_ext(input: JsonString) -> JsResult { create_identity_recovery_request_with_seed_aux(serde_json::from_str(&input).unwrap()) .map_err(to_js_error) } +#[wasm_bindgen(js_name = createIdentityRecoveryRequestWithKeys)] +pub fn create_identity_recovery_request_with_keys_ext(input: JsonString) -> JsResult { + create_identity_recovery_request_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) +} + #[wasm_bindgen(js_name = createCredentialV1)] pub fn create_credential_v1_ext(raw_input: JsonString) -> JsResult { let input = serde_json::from_str(&raw_input)?; diff --git a/packages/sdk/src/wasm/identity.ts b/packages/sdk/src/wasm/identity.ts index cbf1217b0..5793d0d4d 100644 --- a/packages/sdk/src/wasm/identity.ts +++ b/packages/sdk/src/wasm/identity.ts @@ -14,6 +14,7 @@ interface IdentityRequestInputCommon { ipInfo: IpInfo; globalContext: CryptographicParameters; arsInfos: Record; + // TODO Net should be moved out of common when fixed in concordium-base/wallet_library. net: Network; arThreshold: number; } @@ -29,8 +30,49 @@ type IdentityRequestInputInternal = { identityIndex: number; }; +export type IdentityRequestWithKeysInput = IdentityRequestInputCommon & { + prfKey: string; + idCredSec: string; + blindingRandomness: string; +}; + +type IdentityRequestWithKeysInputInternal = { + common: IdentityRequestInputCommon; + prfKey: string; + idCredSec: string; + blindingRandomness: string; +}; + /** - * Creates a V1 identityRequest. + * Creates a V1 identity request by providing the secret keys directly. + * This allows for the generation of the keys separately from creating + * the request. + */ +export function createIdentityRequestWithKeys( + input: IdentityRequestWithKeysInput +): Versioned { + const { prfKey, idCredSec, blindingRandomness, ...common } = input; + const internalInput: IdentityRequestWithKeysInputInternal = { + common, + prfKey, + idCredSec, + blindingRandomness, + }; + + const rawRequest = wasm.createIdRequestWithKeysV1( + JSON.stringify(internalInput) + ); + try { + return JSON.parse(rawRequest).idObjectRequest; + } catch (e) { + throw new Error(rawRequest); + } +} + +/** + * Creates a V1 identity request from a seed. This will derive the corresponding + * keys based on the provided identity index, identity provider index and seed. + * The identity provider index is extracted from the provided IpInfo. */ export function createIdentityRequest( input: IdentityRequestInput @@ -70,8 +112,20 @@ type IdentityRecoveryRequestInputInternal = { identityIndex: number; }; +export type IdentityRecoveryRequestWithKeysInput = + IdentityRecoveryRequestInputCommon & { + idCredSec: string; + }; + +type IdentityRecoveryRequestWithKeysInputInternal = { + common: IdentityRecoveryRequestInputCommon; + idCredSec: string; +}; + /** - * Creates a identity Recovery Request. + * Creates an identity recovery request from a seed. This will derive the + * corresponding keys based on the provided identity index, identity provider index + * and seed. The identity provider index is extracted from the provided IpInfo. */ export function createIdentityRecoveryRequest( input: IdentityRecoveryRequestInput @@ -95,6 +149,30 @@ export function createIdentityRecoveryRequest( } } +/** + * Creates an identity recovery request from a seed. This will derive the + * corresponding keys based on the provided identity index, identity provider index + * and seed. The identity provider index is extracted from the provided IpInfo. + */ +export function createIdentityRecoveryRequestWithKeys( + input: IdentityRecoveryRequestWithKeysInput +): Versioned { + const { idCredSec, ...common } = input; + const internalInput: IdentityRecoveryRequestWithKeysInputInternal = { + common, + idCredSec, + }; + + const rawRequest = wasm.createIdentityRecoveryRequestWithKeys( + JSON.stringify(internalInput) + ); + try { + return JSON.parse(rawRequest).idRecoveryRequest; + } catch (e) { + throw new Error(rawRequest); + } +} + /** * Given a statement about an identity and the inputs necessary to prove the statement, produces a proof that the associated identity fulfills the statement. */ diff --git a/packages/sdk/test/ci/id.test.ts b/packages/sdk/test/ci/id.test.ts index ded8db24a..b75605d0c 100644 --- a/packages/sdk/test/ci/id.test.ts +++ b/packages/sdk/test/ci/id.test.ts @@ -1,35 +1,48 @@ +import { + ArInfo, + ConcordiumHdWallet, + CryptographicParameters, + IdObjectRequestV1, + IdRecoveryRequest, + IpInfo, +} from '../../src/index.ts'; import { createIdentityRequest, IdentityRequestInput, IdentityRecoveryRequestInput, createIdentityRecoveryRequest, + IdentityRequestWithKeysInput, + createIdentityRequestWithKeys, + IdentityRecoveryRequestWithKeysInput, + createIdentityRecoveryRequestWithKeys, } from '../../src/wasm/identity.js'; import fs from 'fs'; -test('idrequest', () => { - const ipInfo = JSON.parse( +const seed = + 'efa5e27326f8fa0902e647b52449bf335b7b605adc387015ec903f41d95080eb71361cbc7fb78721dcd4f3926a337340aa1406df83332c44c1cdcfe100603860'; + +function getTestData() { + const ipInfo: IpInfo = JSON.parse( fs.readFileSync('./test/ci/resources/ip_info.json').toString() ).value; - const globalContext = JSON.parse( + const globalContext: CryptographicParameters = JSON.parse( fs.readFileSync('./test/ci/resources/global.json').toString() ).value; - const arsInfos = JSON.parse( + const arsInfos: Record = JSON.parse( fs.readFileSync('./test/ci/resources/ars_infos.json').toString() ).value; - const seed = - 'efa5e27326f8fa0902e647b52449bf335b7b605adc387015ec903f41d95080eb71361cbc7fb78721dcd4f3926a337340aa1406df83332c44c1cdcfe100603860'; - const arThreshold = 2; - const input: IdentityRequestInput = { + return { ipInfo, globalContext, arsInfos, - seed, - net: 'Testnet', - identityIndex: 0, - arThreshold, }; - const output = createIdentityRequest(input).value; +} + +function assertIdentityRequestResult( + output: IdObjectRequestV1, + arThreshold: number +) { expect(typeof output.proofsOfKnowledge).toBe('string'); expect(typeof output.prfKeySharingCoeffCommitments[0]).toEqual('string'); expect(typeof output.idCredSecCommitment).toEqual('string'); @@ -45,33 +58,91 @@ test('idrequest', () => { expect(typeof output.ipArData[2].proofComEncEq).toEqual('string'); expect(typeof output.ipArData[3].encPrfKeyShare).toEqual('string'); expect(typeof output.ipArData[3].proofComEncEq).toEqual('string'); +} + +function assertIdentityRecoveryRequestResult( + output: IdRecoveryRequest, + timestamp: number +) { + expect(output.idCredPub).toEqual( + 'b23e360b21cb8baad1fb1f9a593d1115fc678cb9b7c1a5b5631f82e088092d79d34b6a6c8520c06c41002a666adf792f' + ); + expect(typeof output.proof).toBe('string'); + expect(output.timestamp).toBe(timestamp); +} + +test('Create identity request using individual keys', () => { + const { ipInfo, globalContext, arsInfos } = getTestData(); + + const wallet = ConcordiumHdWallet.fromHex(seed, 'Testnet'); + const idCredSec = wallet.getIdCredSec(0, 0).toString('hex'); + const prfKey = wallet.getPrfKey(0, 0).toString('hex'); + const blindingRandomness = wallet + .getSignatureBlindingRandomness(0, 0) + .toString('hex'); + const arThreshold = 2; + const input: IdentityRequestWithKeysInput = { + arsInfos, + globalContext, + ipInfo, + arThreshold, + net: 'Testnet', + idCredSec, + prfKey, + blindingRandomness, + }; + + const output = createIdentityRequestWithKeys(input).value; + + assertIdentityRequestResult(output, arThreshold); }); -test('Create id recovery request', () => { - const ipInfo = JSON.parse( - fs.readFileSync('./test/ci/resources/ip_info.json').toString() - ).value; - const globalContext = JSON.parse( - fs.readFileSync('./test/ci/resources/global.json').toString() - ).value; - const seedAsHex = - 'efa5e27326f8fa0902e647b52449bf335b7b605adc387015ec903f41d95080eb71361cbc7fb78721dcd4f3926a337340aa1406df83332c44c1cdcfe100603860'; +test('Create identity request using seed', () => { + const { ipInfo, globalContext, arsInfos } = getTestData(); + const arThreshold = 2; + const input: IdentityRequestInput = { + ipInfo, + globalContext, + arsInfos, + seed, + net: 'Testnet', + identityIndex: 0, + arThreshold, + }; - const timestamp = 1660550412; + const output = createIdentityRequest(input).value; + assertIdentityRequestResult(output, arThreshold); +}); + +test('Create identity recovery request using seed', () => { + const { ipInfo, globalContext } = getTestData(); + const timestamp = 1660550412; const input: IdentityRecoveryRequestInput = { ipInfo, globalContext, - seedAsHex, + seedAsHex: seed, net: 'Testnet', identityIndex: 0, timestamp, }; - const output = createIdentityRecoveryRequest(input); + const output = createIdentityRecoveryRequest(input).value; - expect(output.value.idCredPub).toEqual( - 'b23e360b21cb8baad1fb1f9a593d1115fc678cb9b7c1a5b5631f82e088092d79d34b6a6c8520c06c41002a666adf792f' - ); - expect(typeof output.value.proof).toBe('string'); - expect(output.value.timestamp).toBe(timestamp); + assertIdentityRecoveryRequestResult(output, timestamp); +}); + +test('Create identity recovery request using individual keys', () => { + const { ipInfo, globalContext } = getTestData(); + const timestamp = 1660550412; + const wallet = ConcordiumHdWallet.fromHex(seed, 'Testnet'); + const idCredSec = wallet.getIdCredSec(0, 0).toString('hex'); + const input: IdentityRecoveryRequestWithKeysInput = { + ipInfo, + globalContext, + timestamp, + idCredSec, + }; + const output = createIdentityRecoveryRequestWithKeys(input).value; + + assertIdentityRecoveryRequestResult(output, timestamp); }); From ebddd885d82c01725af87c53b9c274da3f8dd7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20=C3=98rh=C3=B8j?= Date: Fri, 15 Dec 2023 10:55:04 +0100 Subject: [PATCH 06/44] Update concordium-base version --- deps/concordium-base | 2 +- .../packages/wallet/src/external_functions.rs | 4 ++-- packages/sdk/src/wasm/identity.ts | 7 ++++--- packages/sdk/test/ci/id.test.ts | 1 - 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/deps/concordium-base b/deps/concordium-base index 9205a032d..f17a83a0a 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit 9205a032de93e711c8345f82ad4afdb0553b7e81 +Subproject commit f17a83a0a4cc51ec221995ff80fd2b8d3442b7db diff --git a/packages/rust-bindings/packages/wallet/src/external_functions.rs b/packages/rust-bindings/packages/wallet/src/external_functions.rs index 51c1a7c7f..4d3843ee2 100644 --- a/packages/rust-bindings/packages/wallet/src/external_functions.rs +++ b/packages/rust-bindings/packages/wallet/src/external_functions.rs @@ -7,7 +7,7 @@ use wallet_library::{ credential::create_unsigned_credential_with_keys_v1_aux, identity::{ create_id_request_with_keys_v1_aux, create_id_request_with_seed_v1_aux, - create_identity_recovery_request_aux, create_identity_recovery_request_with_seed_aux, + create_identity_recovery_request_with_keys_aux, create_identity_recovery_request_with_seed_aux, }, wallet::{ get_account_public_key_aux, get_account_signing_key_aux, @@ -72,7 +72,7 @@ pub fn create_identity_recovery_request_ext(input: JsonString) -> JsResult { #[wasm_bindgen(js_name = createIdentityRecoveryRequestWithKeys)] pub fn create_identity_recovery_request_with_keys_ext(input: JsonString) -> JsResult { - create_identity_recovery_request_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) + create_identity_recovery_request_with_keys_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) } #[wasm_bindgen(js_name = createCredentialV1)] diff --git a/packages/sdk/src/wasm/identity.ts b/packages/sdk/src/wasm/identity.ts index 5793d0d4d..87f0a3dc5 100644 --- a/packages/sdk/src/wasm/identity.ts +++ b/packages/sdk/src/wasm/identity.ts @@ -14,18 +14,18 @@ interface IdentityRequestInputCommon { ipInfo: IpInfo; globalContext: CryptographicParameters; arsInfos: Record; - // TODO Net should be moved out of common when fixed in concordium-base/wallet_library. - net: Network; arThreshold: number; } export type IdentityRequestInput = IdentityRequestInputCommon & { seed: string; + net: Network; identityIndex: number; }; type IdentityRequestInputInternal = { common: IdentityRequestInputCommon; + net: Network; seedAsHex: string; identityIndex: number; }; @@ -77,9 +77,10 @@ export function createIdentityRequestWithKeys( export function createIdentityRequest( input: IdentityRequestInput ): Versioned { - const { seed, identityIndex, ...common } = input; + const { seed, identityIndex, net, ...common } = input; const internalInput: IdentityRequestInputInternal = { common, + net, seedAsHex: seed, identityIndex, }; diff --git a/packages/sdk/test/ci/id.test.ts b/packages/sdk/test/ci/id.test.ts index b75605d0c..d307725c7 100644 --- a/packages/sdk/test/ci/id.test.ts +++ b/packages/sdk/test/ci/id.test.ts @@ -86,7 +86,6 @@ test('Create identity request using individual keys', () => { globalContext, ipInfo, arThreshold, - net: 'Testnet', idCredSec, prfKey, blindingRandomness, From 49a1b094bfb8a758cbfb47621d0dced267d61b67 Mon Sep 17 00:00:00 2001 From: orhoj Date: Mon, 18 Dec 2023 10:57:33 +0100 Subject: [PATCH 07/44] Create identity request without providing seed --- examples/wallet/src/CreateIdentity.tsx | 30 ++++++++++++++++++++------ examples/wallet/src/identity-worker.ts | 8 +++---- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/examples/wallet/src/CreateIdentity.tsx b/examples/wallet/src/CreateIdentity.tsx index 0d35db7ec..4511daa0f 100644 --- a/examples/wallet/src/CreateIdentity.tsx +++ b/examples/wallet/src/CreateIdentity.tsx @@ -1,9 +1,10 @@ import React, { useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { + ConcordiumHdWallet, CryptographicParameters, IdObjectRequestV1, - IdentityRequestInput, + IdentityRequestWithKeysInput, Versioned, } from '@concordium/web-sdk'; import { IdentityProviderWithMetadata } from './types'; @@ -14,8 +15,6 @@ import { getRedirectUri, sendIdentityRequest, } from './util'; -import { mnemonicToSeedSync } from '@scure/bip39'; -import { Buffer } from 'buffer/'; import { identityIndex, network, @@ -87,16 +86,33 @@ export function CreateIdentity() { worker.removeEventListener('message', listener); }); - const identityRequestInput: IdentityRequestInput = { - net: network, - seed: Buffer.from(mnemonicToSeedSync(seedPhrase)).toString('hex'), - identityIndex, + // Derive the required secret key material. + const wallet = ConcordiumHdWallet.fromSeedPhrase(seedPhrase, network); + const identityProviderIndex = + selectedIdentityProvider.ipInfo.ipIdentity; + const idCredSec = wallet + .getIdCredSec(identityProviderIndex, identityIndex) + .toString('hex'); + const prfKey = wallet + .getPrfKey(identityProviderIndex, identityIndex) + .toString('hex'); + const blindingRandomness = wallet + .getSignatureBlindingRandomness( + identityProviderIndex, + identityIndex + ) + .toString('hex'); + + const identityRequestInput: IdentityRequestWithKeysInput = { arsInfos: selectedIdentityProvider.arsInfos, arThreshold: determineAnonymityRevokerThreshold( Object.keys(selectedIdentityProvider.arsInfos).length ), ipInfo: selectedIdentityProvider.ipInfo, globalContext: cryptographicParameters, + idCredSec, + prfKey, + blindingRandomness, }; worker.postMessage(identityRequestInput); } diff --git a/examples/wallet/src/identity-worker.ts b/examples/wallet/src/identity-worker.ts index 75a6239ca..d730b0197 100644 --- a/examples/wallet/src/identity-worker.ts +++ b/examples/wallet/src/identity-worker.ts @@ -1,9 +1,9 @@ import { - IdentityRequestInput, - createIdentityRequest, + IdentityRequestWithKeysInput, + createIdentityRequestWithKeys, } from '@concordium/web-sdk'; -self.onmessage = (e: MessageEvent) => { - const identityRequest = createIdentityRequest(e.data); +self.onmessage = (e: MessageEvent) => { + const identityRequest = createIdentityRequestWithKeys(e.data); self.postMessage(identityRequest); }; From 2e55fd8fd1a59bbbb51b8d993a4f9eb40e17c551 Mon Sep 17 00:00:00 2001 From: orhoj Date: Mon, 18 Dec 2023 11:36:06 +0100 Subject: [PATCH 08/44] Create credential without providing seed --- examples/wallet/src/CreateAccount.tsx | 33 +++++++++++---- examples/wallet/src/account-worker.ts | 4 +- examples/wallet/src/types.ts | 4 +- examples/wallet/src/util.ts | 60 +++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 13 deletions(-) diff --git a/examples/wallet/src/CreateAccount.tsx b/examples/wallet/src/CreateAccount.tsx index 9adc73c3d..13c4ff74d 100644 --- a/examples/wallet/src/CreateAccount.tsx +++ b/examples/wallet/src/CreateAccount.tsx @@ -1,7 +1,5 @@ import React, { useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { mnemonicToSeedSync } from '@scure/bip39'; -import { Buffer } from 'buffer/'; import { AccountWorkerInput } from './types'; import { credNumber, @@ -16,10 +14,11 @@ import { getCryptographicParameters, getIdentityProviders, getDefaultTransactionExpiry, + createCredentialDeploymentKeysAndRandomness, } from './util'; import { CredentialDeploymentTransaction, - CredentialInput, + CredentialInputNoSeed, IdentityObjectV1, getAccountAddress, signCredentialTransaction, @@ -79,19 +78,35 @@ export function CreateAccount({ identity }: { identity: IdentityObjectV1 }) { }); const global = await getCryptographicParameters(); - const credentialInput: CredentialInput = { - net: network, + + const { + idCredSec, + prfKey, + attributeRandomness, + blindingRandomness, + credentialPublicKeys, + } = createCredentialDeploymentKeysAndRandomness( + seedPhrase, + network, + selectedIdentityProvider.ipInfo.ipIdentity, + identityIndex, + credNumber + ); + + const credentialInput: CredentialInputNoSeed = { revealedAttributes: [], - seedAsHex: Buffer.from(mnemonicToSeedSync(seedPhrase)).toString( - 'hex' - ), idObject: identity, - identityIndex, globalContext: global, credNumber, ipInfo: selectedIdentityProvider.ipInfo, arsInfos: selectedIdentityProvider.arsInfos, + attributeRandomness, + credentialPublicKeys, + idCredSec, + prfKey, + sigRetrievelRandomness: blindingRandomness, }; + const expiry = getDefaultTransactionExpiry(); const workerInput: AccountWorkerInput = { credentialInput, diff --git a/examples/wallet/src/account-worker.ts b/examples/wallet/src/account-worker.ts index 1c045e2ec..e53695dcb 100644 --- a/examples/wallet/src/account-worker.ts +++ b/examples/wallet/src/account-worker.ts @@ -1,8 +1,8 @@ -import { createCredentialTransaction } from '@concordium/web-sdk'; +import { createCredentialTransactionNoSeed } from '@concordium/web-sdk'; import { AccountWorkerInput } from './types'; self.onmessage = (e: MessageEvent) => { - const credentialTransaction = createCredentialTransaction( + const credentialTransaction = createCredentialTransactionNoSeed( e.data.credentialInput, e.data.expiry ); diff --git a/examples/wallet/src/types.ts b/examples/wallet/src/types.ts index bc24fb831..aa6d35caf 100644 --- a/examples/wallet/src/types.ts +++ b/examples/wallet/src/types.ts @@ -1,5 +1,5 @@ import { - CredentialInput, + CredentialInputNoSeed, IdentityObjectV1, IdentityProvider, TransactionExpiry, @@ -18,7 +18,7 @@ export type IdentityProviderWithMetadata = IdentityProvider & { }; export interface AccountWorkerInput { - credentialInput: CredentialInput; + credentialInput: CredentialInputNoSeed; expiry: TransactionExpiry.Type; } diff --git a/examples/wallet/src/util.ts b/examples/wallet/src/util.ts index 32d9eb939..a7ea7935c 100644 --- a/examples/wallet/src/util.ts +++ b/examples/wallet/src/util.ts @@ -4,12 +4,14 @@ import { AccountTransaction, AccountTransactionHeader, AccountTransactionType, + AttributesKeys, CcdAmount, ConcordiumGRPCWebClient, ConcordiumHdWallet, CredentialDeploymentTransaction, IdObjectRequestV1, IdentityObjectV1, + Network, SimpleTransferPayload, TransactionExpiry, Versioned, @@ -30,6 +32,7 @@ import { nodePort, walletProxyBaseUrl, } from './constants'; +import { filterRecord, mapRecord } from '../../../packages/sdk/lib/esm/util'; // Redirect URI used in the identity creation protocol. // This determines where the identity provider will redirect the @@ -170,6 +173,63 @@ export async function fetchIdentity( }); } +/** + * Derive the required secret key material, the randomness and public key for a credential deployment transaction + * from a seed phrase. + */ +export function createCredentialDeploymentKeysAndRandomness( + seedPhrase: string, + net: Network, + identityProviderIndex: number, + identityIndex: number, + credNumber: number +) { + const wallet = ConcordiumHdWallet.fromSeedPhrase(seedPhrase, net); + const publicKey = wallet + .getAccountPublicKey(identityProviderIndex, identityIndex, credNumber) + .toString('hex'); + + const verifyKey = { + schemeId: 'Ed25519', + verifyKey: publicKey, + }; + const credentialPublicKeys = { + keys: { 0: verifyKey }, + threshold: 1, + }; + + const prfKey = wallet + .getPrfKey(identityProviderIndex, identityIndex) + .toString('hex'); + const idCredSec = wallet + .getIdCredSec(identityProviderIndex, identityIndex) + .toString('hex'); + const blindingRandomness = wallet + .getSignatureBlindingRandomness(identityProviderIndex, identityIndex) + .toString('hex'); + + const attributeRandomness = mapRecord( + filterRecord(AttributesKeys, (k) => isNaN(Number(k))), + (x) => + wallet + .getAttributeCommitmentRandomness( + identityProviderIndex, + identityIndex, + credNumber, + x + ) + .toString('hex') + ); + + return { + prfKey, + idCredSec, + blindingRandomness, + attributeRandomness, + credentialPublicKeys, + }; +} + /** * Serializes a credential deployment transaction and sends it to a node. * @param credentialDeployment the credential deployment to send From 71d7260bc751845019916cb1569c1986cd9d7d4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20=C3=98rh=C3=B8j?= Date: Tue, 19 Dec 2023 13:14:07 +0100 Subject: [PATCH 09/44] Use wallet_library for creating credential from seed --- deps/concordium-base | 2 +- .../packages/wallet/src/external_functions.rs | 8 +- .../wasm/credentialDeploymentTransactions.ts | 73 +++++-------------- 3 files changed, 25 insertions(+), 58 deletions(-) diff --git a/deps/concordium-base b/deps/concordium-base index f17a83a0a..55abaf239 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit f17a83a0a4cc51ec221995ff80fd2b8d3442b7db +Subproject commit 55abaf2391ffc0e78a5fbb03fc8093de3ea207ba diff --git a/packages/rust-bindings/packages/wallet/src/external_functions.rs b/packages/rust-bindings/packages/wallet/src/external_functions.rs index 4d3843ee2..178e2e724 100644 --- a/packages/rust-bindings/packages/wallet/src/external_functions.rs +++ b/packages/rust-bindings/packages/wallet/src/external_functions.rs @@ -4,7 +4,7 @@ use concordium_rust_bindings_common::{ types::{Base58String, HexString, JsonString}, }; use wallet_library::{ - credential::create_unsigned_credential_with_keys_v1_aux, + credential::{create_unsigned_credential_with_keys_v1_aux, create_unsigned_credential_with_seed_v1_aux}, identity::{ create_id_request_with_keys_v1_aux, create_id_request_with_seed_v1_aux, create_identity_recovery_request_with_keys_aux, create_identity_recovery_request_with_seed_aux, @@ -87,6 +87,12 @@ pub fn create_unsigned_credential_v1_ext(input: JsonString) -> JsResult { .map_err(to_js_error) } +#[wasm_bindgen(js_name = createUnsignedCredentialWithSeedV1)] +pub fn create_unsigned_credential_with_seed_v1_ext(input: JsonString) -> JsResult { + create_unsigned_credential_with_seed_v1_aux(serde_json::from_str(&input).unwrap()) + .map_err(to_js_error) +} + #[wasm_bindgen(js_name = createIdProof)] pub fn create_id_proof_ext(raw_input: JsonString) -> JsResult { let input = serde_json::from_str(&raw_input)?; diff --git a/packages/sdk/src/wasm/credentialDeploymentTransactions.ts b/packages/sdk/src/wasm/credentialDeploymentTransactions.ts index 615b9d7af..b065117cb 100644 --- a/packages/sdk/src/wasm/credentialDeploymentTransactions.ts +++ b/packages/sdk/src/wasm/credentialDeploymentTransactions.ts @@ -24,8 +24,6 @@ import { import * as TransactionExpiry from '../types/TransactionExpiry.js'; import * as AccountAddress from '../types/AccountAddress.js'; import { sha256 } from '../hash.js'; -import { ConcordiumHdWallet } from './HdWallet.js'; -import { filterRecord, mapRecord } from '../util.js'; import { getCredentialDeploymentSignDigest } from '../serialization.js'; /** @@ -219,64 +217,27 @@ export function createCredentialTransaction( input: CredentialInput, expiry: TransactionExpiry.Type ): CredentialDeploymentTransaction { - const wallet = ConcordiumHdWallet.fromHex(input.seedAsHex, input.net); - const publicKey = wallet - .getAccountPublicKey( - input.ipInfo.ipIdentity, - input.identityIndex, - input.credNumber - ) - .toString('hex'); - - const verifyKey = { - schemeId: 'Ed25519', - verifyKey: publicKey, - }; - const credentialPublicKeys = { - keys: { 0: verifyKey }, - threshold: 1, + const { seedAsHex, net, identityIndex, ...common } = input; + const internalInput = { + common, + seedAsHex, + net, + identityIndex, }; - const prfKey = wallet - .getPrfKey(input.ipInfo.ipIdentity, input.identityIndex) - .toString('hex'); - const idCredSec = wallet - .getIdCredSec(input.ipInfo.ipIdentity, input.identityIndex) - .toString('hex'); - const randomness = wallet - .getSignatureBlindingRandomness( - input.ipInfo.ipIdentity, - input.identityIndex - ) - .toString('hex'); - - const attributeRandomness = mapRecord( - filterRecord(AttributesKeys, (k) => isNaN(Number(k))), - (x) => - wallet - .getAttributeCommitmentRandomness( - input.ipInfo.ipIdentity, - input.identityIndex, - input.credNumber, - x - ) - .toString('hex') + const rawRequest = wasm.createUnsignedCredentialWithSeedV1( + JSON.stringify(internalInput) ); - - const noSeedInput: CredentialInputNoSeed = { - ipInfo: input.ipInfo, - globalContext: input.globalContext, - arsInfos: input.arsInfos, - idObject: input.idObject, - idCredSec, - prfKey, - sigRetrievelRandomness: randomness, - credentialPublicKeys, - attributeRandomness, - revealedAttributes: input.revealedAttributes, - credNumber: input.credNumber, + let info: UnsignedCdiWithRandomness; + try { + info = JSON.parse(rawRequest); + } catch (e) { + throw new Error(rawRequest); + } + return { + expiry, + ...info, }; - return createCredentialTransactionNoSeed(noSeedInput, expiry); } /** From 3ea35783490113efcfadd71367d626ef7f32b48d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20=C3=98rh=C3=B8j?= Date: Tue, 19 Dec 2023 13:33:33 +0100 Subject: [PATCH 10/44] Update CHANGELOG and versions --- packages/rust-bindings/CHANGELOG.md | 13 +++++++++++++ packages/rust-bindings/package.json | 2 +- packages/sdk/CHANGELOG.md | 4 +++- packages/sdk/package.json | 4 ++-- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/rust-bindings/CHANGELOG.md b/packages/rust-bindings/CHANGELOG.md index 95a1a29c9..145c2686b 100644 --- a/packages/rust-bindings/CHANGELOG.md +++ b/packages/rust-bindings/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 2.1.0 + +### Added + +- WASM entrypoint (`createIdRequestWithKeysV1`) for creating an identity request by supplying the secret key material instead of the seed. +- WASM entrypoint Method (`createIdentityRecoveryRequestWithKeys`) for creating an identity recovery request by supplying the secret key material instead of the seed. +- WASM entrypoint (`createUnsignedCredentialWithSeedV1`) for creating an unsigned credential by supplying a seed instead of the secret key material. + +### Changed + +- Identity requests, identity recovery requests and unsigned credentials are now created using the `wallet_library` from `concordium-base` instead of the local implementation. +- Deriving wallet keys is now done by utilizing functionality from `wallet_library` in `concordium-base` instead of the local implementation. + ## 2.0.1 ### Added diff --git a/packages/rust-bindings/package.json b/packages/rust-bindings/package.json index af3b7098f..90998b5dc 100644 --- a/packages/rust-bindings/package.json +++ b/packages/rust-bindings/package.json @@ -1,6 +1,6 @@ { "name": "@concordium/rust-bindings", - "version": "2.0.1", + "version": "2.1.0", "license": "Apache-2.0", "engines": { "node": ">=16" diff --git a/packages/sdk/CHANGELOG.md b/packages/sdk/CHANGELOG.md index 61b16af81..abe159de7 100644 --- a/packages/sdk/CHANGELOG.md +++ b/packages/sdk/CHANGELOG.md @@ -1,11 +1,13 @@ # Changelog -## Unreleased changes +## 7.2.0 ### Added - `ContractAddress.toString` function that converts the address to a string in the `` format. +- Method (`createIdRequestWithKeysV1`) for creating an identity request by supplying the secret key material instead of the seed. +- Method (`createIdentityRecoveryRequestWithKeys`) for creating an identity recovery request by supplying the secret key material instead of the seed. ### Fixed diff --git a/packages/sdk/package.json b/packages/sdk/package.json index d258c876b..26048313a 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@concordium/web-sdk", - "version": "7.1.0", + "version": "7.2.0", "license": "Apache-2.0", "engines": { "node": ">=16" @@ -91,7 +91,7 @@ "clean": "rimraf -- lib src/grpc-api" }, "dependencies": { - "@concordium/rust-bindings": "2.0.1", + "@concordium/rust-bindings": "2.1.0", "@grpc/grpc-js": "^1.9.4", "@noble/ed25519": "^2.0.0", "@noble/hashes": "^1.3.2", From 6b1e4c0ae897390f0fa0cdfaf164251d677808f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20=C3=98rh=C3=B8j?= Date: Tue, 19 Dec 2023 14:53:09 +0100 Subject: [PATCH 11/44] Update naming and bump rust-bindings a major --- packages/rust-bindings/CHANGELOG.md | 16 ++++++++++++---- packages/rust-bindings/package.json | 2 +- .../packages/wallet/src/external_functions.rs | 4 ++-- packages/sdk/package.json | 2 +- .../src/wasm/credentialDeploymentTransactions.ts | 4 ++-- yarn.lock | 4 ++-- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/packages/rust-bindings/CHANGELOG.md b/packages/rust-bindings/CHANGELOG.md index 145c2686b..980ea08ef 100644 --- a/packages/rust-bindings/CHANGELOG.md +++ b/packages/rust-bindings/CHANGELOG.md @@ -1,12 +1,20 @@ # Changelog -## 2.1.0 +## 3.0.0 + +## Breaking changes + +- The function `create_id_request_v1_ext` exposed with the WASM entrypoint `createIdRequestV1` now expects a new JSON type. +- The function `create_identity_recovery_request_ext` exposed with the WASM entrypoint `createIdentityRecoveryRequest` now expects a new JSON type. +- The function `create_unsigned_credential_v1_ext` previously exposed with the WASM entrypoint `createUnsignedCredentialV1` now expects a new JSON type and is instead exposed with the WASM entrypoint `createUnsignedCredentialWithKeysV1`. +- The WASM entrypoint `createUnsignedCredentialV1` is now instead used to create an unsigned credential from a provided seed to align it with the naming convention used for identity requests and identity recovery requests. ### Added -- WASM entrypoint (`createIdRequestWithKeysV1`) for creating an identity request by supplying the secret key material instead of the seed. -- WASM entrypoint Method (`createIdentityRecoveryRequestWithKeys`) for creating an identity recovery request by supplying the secret key material instead of the seed. -- WASM entrypoint (`createUnsignedCredentialWithSeedV1`) for creating an unsigned credential by supplying a seed instead of the secret key material. +- WASM entrypoint `createIdRequestWithKeysV1` for creating an identity request by supplying the secret key material instead of the seed. +- WASM entrypoint Method `createIdentityRecoveryRequestWithKeys` for creating an identity recovery request by supplying the secret key material instead of the seed. +- WASM entrypoint `createUnsignedCredentialV1` for creating an unsigned credential by supplying a seed instead of the secret key material. + - Note that this re-uses the WASM entrypoint previously used when supplying the secret key material directly. ### Changed diff --git a/packages/rust-bindings/package.json b/packages/rust-bindings/package.json index 90998b5dc..a4d6234fe 100644 --- a/packages/rust-bindings/package.json +++ b/packages/rust-bindings/package.json @@ -1,6 +1,6 @@ { "name": "@concordium/rust-bindings", - "version": "2.1.0", + "version": "3.0.0", "license": "Apache-2.0", "engines": { "node": ">=16" diff --git a/packages/rust-bindings/packages/wallet/src/external_functions.rs b/packages/rust-bindings/packages/wallet/src/external_functions.rs index 178e2e724..fd3bf8e13 100644 --- a/packages/rust-bindings/packages/wallet/src/external_functions.rs +++ b/packages/rust-bindings/packages/wallet/src/external_functions.rs @@ -81,13 +81,13 @@ pub fn create_credential_v1_ext(raw_input: JsonString) -> JsResult { create_credential_v1_aux(input).map_err(to_js_error) } -#[wasm_bindgen(js_name = createUnsignedCredentialV1)] +#[wasm_bindgen(js_name = createUnsignedCredentialWithKeysV1)] pub fn create_unsigned_credential_v1_ext(input: JsonString) -> JsResult { create_unsigned_credential_with_keys_v1_aux(serde_json::from_str(&input).unwrap()) .map_err(to_js_error) } -#[wasm_bindgen(js_name = createUnsignedCredentialWithSeedV1)] +#[wasm_bindgen(js_name = createUnsignedCredentialV1)] pub fn create_unsigned_credential_with_seed_v1_ext(input: JsonString) -> JsResult { create_unsigned_credential_with_seed_v1_aux(serde_json::from_str(&input).unwrap()) .map_err(to_js_error) diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 26048313a..55bc20f9d 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -91,7 +91,7 @@ "clean": "rimraf -- lib src/grpc-api" }, "dependencies": { - "@concordium/rust-bindings": "2.1.0", + "@concordium/rust-bindings": "3.0.0", "@grpc/grpc-js": "^1.9.4", "@noble/ed25519": "^2.0.0", "@noble/hashes": "^1.3.2", diff --git a/packages/sdk/src/wasm/credentialDeploymentTransactions.ts b/packages/sdk/src/wasm/credentialDeploymentTransactions.ts index b065117cb..08cb10675 100644 --- a/packages/sdk/src/wasm/credentialDeploymentTransactions.ts +++ b/packages/sdk/src/wasm/credentialDeploymentTransactions.ts @@ -225,7 +225,7 @@ export function createCredentialTransaction( identityIndex, }; - const rawRequest = wasm.createUnsignedCredentialWithSeedV1( + const rawRequest = wasm.createUnsignedCredentialV1( JSON.stringify(internalInput) ); let info: UnsignedCdiWithRandomness; @@ -255,7 +255,7 @@ export function createCredentialTransactionNoSeed( blindingRandomness: sigRetrievelRandomness, }; - const rawRequest = wasm.createUnsignedCredentialV1( + const rawRequest = wasm.createUnsignedCredentialWithKeysV1( JSON.stringify(internalInput) ); let info: UnsignedCdiWithRandomness; diff --git a/yarn.lock b/yarn.lock index e5c816c6e..715504799 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2196,7 +2196,7 @@ __metadata: languageName: unknown linkType: soft -"@concordium/rust-bindings@2.0.1, @concordium/rust-bindings@workspace:packages/rust-bindings": +"@concordium/rust-bindings@3.0.0, @concordium/rust-bindings@workspace:packages/rust-bindings": version: 0.0.0-use.local resolution: "@concordium/rust-bindings@workspace:packages/rust-bindings" dependencies: @@ -2219,7 +2219,7 @@ __metadata: version: 0.0.0-use.local resolution: "@concordium/web-sdk@workspace:packages/sdk" dependencies: - "@concordium/rust-bindings": 2.0.1 + "@concordium/rust-bindings": 3.0.0 "@grpc/grpc-js": ^1.9.4 "@noble/ed25519": ^2.0.0 "@noble/hashes": ^1.3.2 From 333ccb7596005a3cd221f2934814861836f5e6be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20=C3=98rh=C3=B8j?= Date: Tue, 19 Dec 2023 15:19:54 +0100 Subject: [PATCH 12/44] Run rust fmt --- .../packages/wallet/src/external_functions.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/rust-bindings/packages/wallet/src/external_functions.rs b/packages/rust-bindings/packages/wallet/src/external_functions.rs index fd3bf8e13..c6f50df8d 100644 --- a/packages/rust-bindings/packages/wallet/src/external_functions.rs +++ b/packages/rust-bindings/packages/wallet/src/external_functions.rs @@ -4,10 +4,13 @@ use concordium_rust_bindings_common::{ types::{Base58String, HexString, JsonString}, }; use wallet_library::{ - credential::{create_unsigned_credential_with_keys_v1_aux, create_unsigned_credential_with_seed_v1_aux}, + credential::{ + create_unsigned_credential_with_keys_v1_aux, create_unsigned_credential_with_seed_v1_aux, + }, identity::{ create_id_request_with_keys_v1_aux, create_id_request_with_seed_v1_aux, - create_identity_recovery_request_with_keys_aux, create_identity_recovery_request_with_seed_aux, + create_identity_recovery_request_with_keys_aux, + create_identity_recovery_request_with_seed_aux, }, wallet::{ get_account_public_key_aux, get_account_signing_key_aux, @@ -72,7 +75,8 @@ pub fn create_identity_recovery_request_ext(input: JsonString) -> JsResult { #[wasm_bindgen(js_name = createIdentityRecoveryRequestWithKeys)] pub fn create_identity_recovery_request_with_keys_ext(input: JsonString) -> JsResult { - create_identity_recovery_request_with_keys_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) + create_identity_recovery_request_with_keys_aux(serde_json::from_str(&input).unwrap()) + .map_err(to_js_error) } #[wasm_bindgen(js_name = createCredentialV1)] From c4c6bbe28f2a9e8d9ae2827fb4bf927010d58d2e Mon Sep 17 00:00:00 2001 From: orhoj Date: Wed, 20 Dec 2023 11:53:33 +0100 Subject: [PATCH 13/44] Bump base to correct commit --- deps/concordium-base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/concordium-base b/deps/concordium-base index 55abaf239..36944d023 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit 55abaf2391ffc0e78a5fbb03fc8093de3ea207ba +Subproject commit 36944d0237ebd988e3f70762ba707d6d7cb56acd From c7a37fcec4dc6e0cc880a48993aec58e12d02f1e Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Mon, 8 Jan 2024 13:44:33 +0100 Subject: [PATCH 14/44] Add output-type flag to cli of ccd-js-gen tool --- packages/ccd-js-gen/package.json | 2 +- packages/ccd-js-gen/src/cli.ts | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/ccd-js-gen/package.json b/packages/ccd-js-gen/package.json index e7a401bae..f869388e7 100644 --- a/packages/ccd-js-gen/package.json +++ b/packages/ccd-js-gen/package.json @@ -12,7 +12,7 @@ "build-dev": "yarn build", "clean": "rm -r lib", "lint": "eslint src/** bin/** --cache --ext .ts,.js --max-warnings 0", - "lint-fix": "yarn --silent lint --fix; exit 0", + "lint-fix": "yarn lint --fix; exit 0", "test": "jest", "test-ci": "yarn test" }, diff --git a/packages/ccd-js-gen/src/cli.ts b/packages/ccd-js-gen/src/cli.ts index a86a82a98..f316e2ac6 100644 --- a/packages/ccd-js-gen/src/cli.ts +++ b/packages/ccd-js-gen/src/cli.ts @@ -11,6 +11,8 @@ type Options = { module: string; /** The output directory for the generated code */ outDir: string; + /** The output file types for the generated code */ + outputType: lib.OutputOptions; }; // Main function, which is called in the executable script in `bin`. @@ -28,12 +30,33 @@ export async function main(): Promise { '-o, --out-dir ', 'The output directory for the generated code' ) + .option( + '-t, --output-type [TypeScript|JavaScript|TypedJavaScript|Everything]', + 'The output file types for the generated code.', + (value: string) => { + switch (value) { + case 'TypeScript': + case 'JavaScript': + case 'TypedJavaScript': + case 'Everything': + return value as lib.OutputOptions; + default: + // Exit in case `value` is not a valid OutputOptions. + console.error( + `Invalid '--output-type' flag: ${value}. Use 'TypeScript', 'JavaScript', 'TypedJavaScript', or 'Everything'.` + ); + process.exit(1); + } + }, + 'Everything' + ) .parse(process.argv); const options = program.opts(); console.log('Generating smart contract clients...'); const startTime = Date.now(); await lib.generateContractClientsFromFile(options.module, options.outDir, { + output: options.outputType, onProgress(update) { if (update.type === 'Progress') { console.log( From 915935ca4846c5156d86c1f388026f7cda0701c2 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Mon, 8 Jan 2024 14:16:38 +0100 Subject: [PATCH 15/44] Update change.log --- packages/ccd-js-gen/CHANGELOG.md | 1 + packages/ccd-js-gen/src/cli.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/ccd-js-gen/CHANGELOG.md b/packages/ccd-js-gen/CHANGELOG.md index c46a49215..33024debf 100644 --- a/packages/ccd-js-gen/CHANGELOG.md +++ b/packages/ccd-js-gen/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Add output-type flag to cli. - Fix the executable version of the package on Windows. ## 1.0.1 diff --git a/packages/ccd-js-gen/src/cli.ts b/packages/ccd-js-gen/src/cli.ts index f316e2ac6..ad76cef7b 100644 --- a/packages/ccd-js-gen/src/cli.ts +++ b/packages/ccd-js-gen/src/cli.ts @@ -31,7 +31,7 @@ export async function main(): Promise { 'The output directory for the generated code' ) .option( - '-t, --output-type [TypeScript|JavaScript|TypedJavaScript|Everything]', + '-t, --output-type ', 'The output file types for the generated code.', (value: string) => { switch (value) { @@ -39,7 +39,7 @@ export async function main(): Promise { case 'JavaScript': case 'TypedJavaScript': case 'Everything': - return value as lib.OutputOptions; + return value; default: // Exit in case `value` is not a valid OutputOptions. console.error( From e5bb33c8fd13a7af5b957a350f0818bdbfd14858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Holm=20Gj=C3=B8rup?= Date: Tue, 9 Jan 2024 09:08:47 +0100 Subject: [PATCH 16/44] ccd-js-gen: Flag for generating @ts-nocheck --- packages/ccd-js-gen/CHANGELOG.md | 3 ++- packages/ccd-js-gen/src/cli.ts | 8 ++++++++ packages/ccd-js-gen/src/lib.ts | 17 +++++++++++++---- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/ccd-js-gen/CHANGELOG.md b/packages/ccd-js-gen/CHANGELOG.md index 33024debf..5aedcb930 100644 --- a/packages/ccd-js-gen/CHANGELOG.md +++ b/packages/ccd-js-gen/CHANGELOG.md @@ -2,7 +2,8 @@ ## Unreleased -- Add output-type flag to cli. +- Add optional flag `--ts-nocheck`, enabling it will add `// @ts-nocheck` in each generated file. +- Add `--output-type ` flag to CLI allowing to specify whether to generate TypeScript or JavaScript directly. - Fix the executable version of the package on Windows. ## 1.0.1 diff --git a/packages/ccd-js-gen/src/cli.ts b/packages/ccd-js-gen/src/cli.ts index ad76cef7b..21f593378 100644 --- a/packages/ccd-js-gen/src/cli.ts +++ b/packages/ccd-js-gen/src/cli.ts @@ -13,6 +13,8 @@ type Options = { outDir: string; /** The output file types for the generated code */ outputType: lib.OutputOptions; + /** Generate `// @ts-nocheck` annotations in each file. Disabled by default. */ + tsNocheck: boolean; }; // Main function, which is called in the executable script in `bin`. @@ -50,6 +52,11 @@ export async function main(): Promise { }, 'Everything' ) + .option( + '--ts-nocheck', + 'Generate `@ts-nocheck` annotations at the top of each typescript file.', + false + ) .parse(process.argv); const options = program.opts(); console.log('Generating smart contract clients...'); @@ -57,6 +64,7 @@ export async function main(): Promise { const startTime = Date.now(); await lib.generateContractClientsFromFile(options.module, options.outDir, { output: options.outputType, + tsNocheck: options.tsNocheck, onProgress(update) { if (update.type === 'Progress') { console.log( diff --git a/packages/ccd-js-gen/src/lib.ts b/packages/ccd-js-gen/src/lib.ts index 1798c9a83..244ea67f7 100644 --- a/packages/ccd-js-gen/src/lib.ts +++ b/packages/ccd-js-gen/src/lib.ts @@ -21,6 +21,8 @@ export type OutputOptions = export type GenerateContractClientsOptions = { /** Options for the output. */ output?: OutputOptions; + /** Generate `// @ts-nocheck` annotations in each file. Disabled by default. */ + tsNocheck?: boolean; /** Callback for getting notified on progress. */ onProgress?: NotifyProgress; }; @@ -97,6 +99,7 @@ export async function generateContractClients( outName, outDirPath, moduleSource, + options.tsNocheck ?? false, options.onProgress ); @@ -118,6 +121,7 @@ export async function generateContractClients( * @param {string} outModuleName The name for outputting the module client file. * @param {string} outDirPath The directory to use for outputting files. * @param {SDK.VersionedModuleSource} moduleSource The source of the smart contract module. + * @param {boolean} tsNocheck When `true` generate `// @ts-nocheck` annotations in each file. * @param {NotifyProgress} [notifyProgress] Callback to report progress. */ async function generateCode( @@ -125,6 +129,7 @@ async function generateCode( outModuleName: string, outDirPath: string, moduleSource: SDK.VersionedModuleSource, + tsNocheck: boolean, notifyProgress?: NotifyProgress ) { const [moduleInterface, moduleRef, rawModuleSchema] = await Promise.all([ @@ -150,9 +155,13 @@ async function generateCode( name: outModuleName, ext: '.ts', }); - const moduleSourceFile = project.createSourceFile(outputFilePath, '', { - overwrite: true, - }); + const moduleSourceFile = project.createSourceFile( + outputFilePath, + tsNocheck ? '// @ts-nocheck' : '', + { + overwrite: true, + } + ); const moduleClientId = 'moduleClient'; const moduleClientType = `${toPascalCase(outModuleName)}Module`; const internalModuleClientId = 'internalModuleClient'; @@ -188,7 +197,7 @@ async function generateCode( }); const contractSourceFile = project.createSourceFile( contractOutputFilePath, - '', + tsNocheck ? '// @ts-nocheck' : '', { overwrite: true, } From 0066f04092fc1caec826faf67049ab8adc3b6d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Holm=20Gj=C3=B8rup?= Date: Tue, 9 Jan 2024 10:30:03 +0100 Subject: [PATCH 17/44] ccd-js-gen: Move @ts-nocheck to the top of the file --- packages/ccd-js-gen/src/lib.ts | 44 ++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/packages/ccd-js-gen/src/lib.ts b/packages/ccd-js-gen/src/lib.ts index 244ea67f7..0541a479f 100644 --- a/packages/ccd-js-gen/src/lib.ts +++ b/packages/ccd-js-gen/src/lib.ts @@ -155,13 +155,9 @@ async function generateCode( name: outModuleName, ext: '.ts', }); - const moduleSourceFile = project.createSourceFile( - outputFilePath, - tsNocheck ? '// @ts-nocheck' : '', - { - overwrite: true, - } - ); + const moduleSourceFile = project.createSourceFile(outputFilePath, '', { + overwrite: true, + }); const moduleClientId = 'moduleClient'; const moduleClientType = `${toPascalCase(outModuleName)}Module`; const internalModuleClientId = 'internalModuleClient'; @@ -171,7 +167,8 @@ async function generateCode( moduleRef, moduleClientId, moduleClientType, - internalModuleClientId + internalModuleClientId, + tsNocheck ); for (const contract of moduleInterface.values()) { @@ -197,7 +194,7 @@ async function generateCode( }); const contractSourceFile = project.createSourceFile( contractOutputFilePath, - tsNocheck ? '// @ts-nocheck' : '', + '', { overwrite: true, } @@ -214,6 +211,7 @@ async function generateCode( contractClientId, contractClientType, moduleRef, + tsNocheck, contractSchema ); @@ -254,14 +252,19 @@ function generateModuleBaseCode( moduleRef: SDK.ModuleReference.Type, moduleClientId: string, moduleClientType: string, - internalModuleClientId: string + internalModuleClientId: string, + tsNocheck: boolean ) { const moduleRefId = 'moduleReference'; - moduleSourceFile.addImportDeclaration({ - namespaceImport: 'SDK', - moduleSpecifier: '@concordium/web-sdk', - }); + moduleSourceFile.addStatements([ + tsNocheck ? '// @ts-nocheck' : '', + { + kind: tsm.StructureKind.ImportDeclaration, + namespaceImport: 'SDK', + moduleSpecifier: '@concordium/web-sdk', + }, + ]); moduleSourceFile.addVariableStatement({ isExported: true, @@ -574,6 +577,7 @@ function generateContractBaseCode( contractClientId: string, contractClientType: string, moduleRef: SDK.ModuleReference.Type, + tsNocheck: boolean, contractSchema?: SDK.SchemaContractV3 ) { const moduleRefId = 'moduleReference'; @@ -583,10 +587,14 @@ function generateContractBaseCode( const contractAddressId = 'contractAddress'; const blockHashId = 'blockHash'; - contractSourceFile.addImportDeclaration({ - namespaceImport: 'SDK', - moduleSpecifier: '@concordium/web-sdk', - }); + contractSourceFile.addStatements([ + tsNocheck ? '// @ts-nocheck' : '', + { + kind: tsm.StructureKind.ImportDeclaration, + namespaceImport: 'SDK', + moduleSpecifier: '@concordium/web-sdk', + }, + ]); contractSourceFile.addVariableStatement({ docs: [ From 9137decff27952acf7dacad6aff841fa54cd7340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Holm=20Gj=C3=B8rup?= Date: Tue, 9 Jan 2024 10:34:37 +0100 Subject: [PATCH 18/44] Address review comments --- packages/ccd-js-gen/src/cli.ts | 4 ++-- packages/ccd-js-gen/src/lib.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ccd-js-gen/src/cli.ts b/packages/ccd-js-gen/src/cli.ts index 21f593378..19f821a74 100644 --- a/packages/ccd-js-gen/src/cli.ts +++ b/packages/ccd-js-gen/src/cli.ts @@ -13,7 +13,7 @@ type Options = { outDir: string; /** The output file types for the generated code */ outputType: lib.OutputOptions; - /** Generate `// @ts-nocheck` annotations in each file. Disabled by default. */ + /** Generate `// @ts-nocheck` annotations in each file. */ tsNocheck: boolean; }; @@ -53,7 +53,7 @@ export async function main(): Promise { 'Everything' ) .option( - '--ts-nocheck', + '-n, --ts-nocheck', 'Generate `@ts-nocheck` annotations at the top of each typescript file.', false ) diff --git a/packages/ccd-js-gen/src/lib.ts b/packages/ccd-js-gen/src/lib.ts index 0541a479f..e9bbcd734 100644 --- a/packages/ccd-js-gen/src/lib.ts +++ b/packages/ccd-js-gen/src/lib.ts @@ -21,7 +21,7 @@ export type OutputOptions = export type GenerateContractClientsOptions = { /** Options for the output. */ output?: OutputOptions; - /** Generate `// @ts-nocheck` annotations in each file. Disabled by default. */ + /** Generate `// @ts-nocheck` annotations in each file. Defaults to `false`. */ tsNocheck?: boolean; /** Callback for getting notified on progress. */ onProgress?: NotifyProgress; From 79f82ec0800a192c5fee41b644af0ea1645fe88a Mon Sep 17 00:00:00 2001 From: orhoj Date: Tue, 9 Jan 2024 14:41:39 +0100 Subject: [PATCH 19/44] Refactor so that using seed is on web-sdk side --- deps/concordium-base | 2 +- packages/rust-bindings/CHANGELOG.md | 12 +- .../packages/wallet/src/external_functions.rs | 20 +-- packages/sdk/src/wasm/identity.ts | 115 ++++++------------ 4 files changed, 47 insertions(+), 102 deletions(-) diff --git a/deps/concordium-base b/deps/concordium-base index 36944d023..c7fc3c032 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit 36944d0237ebd988e3f70762ba707d6d7cb56acd +Subproject commit c7fc3c032d9fc2eb1e8ab2385935d2884ac182cb diff --git a/packages/rust-bindings/CHANGELOG.md b/packages/rust-bindings/CHANGELOG.md index 980ea08ef..84bf20d0c 100644 --- a/packages/rust-bindings/CHANGELOG.md +++ b/packages/rust-bindings/CHANGELOG.md @@ -2,19 +2,11 @@ ## 3.0.0 -## Breaking changes +### Breaking changes - The function `create_id_request_v1_ext` exposed with the WASM entrypoint `createIdRequestV1` now expects a new JSON type. - The function `create_identity_recovery_request_ext` exposed with the WASM entrypoint `createIdentityRecoveryRequest` now expects a new JSON type. -- The function `create_unsigned_credential_v1_ext` previously exposed with the WASM entrypoint `createUnsignedCredentialV1` now expects a new JSON type and is instead exposed with the WASM entrypoint `createUnsignedCredentialWithKeysV1`. -- The WASM entrypoint `createUnsignedCredentialV1` is now instead used to create an unsigned credential from a provided seed to align it with the naming convention used for identity requests and identity recovery requests. - -### Added - -- WASM entrypoint `createIdRequestWithKeysV1` for creating an identity request by supplying the secret key material instead of the seed. -- WASM entrypoint Method `createIdentityRecoveryRequestWithKeys` for creating an identity recovery request by supplying the secret key material instead of the seed. -- WASM entrypoint `createUnsignedCredentialV1` for creating an unsigned credential by supplying a seed instead of the secret key material. - - Note that this re-uses the WASM entrypoint previously used when supplying the secret key material directly. +- The function `create_unsigned_credential_v1_ext` exposed with the WASM entrypoint `createUnsignedCredentialV1` now expects a new JSON type. ### Changed diff --git a/packages/rust-bindings/packages/wallet/src/external_functions.rs b/packages/rust-bindings/packages/wallet/src/external_functions.rs index c6f50df8d..b305186a2 100644 --- a/packages/rust-bindings/packages/wallet/src/external_functions.rs +++ b/packages/rust-bindings/packages/wallet/src/external_functions.rs @@ -8,9 +8,8 @@ use wallet_library::{ create_unsigned_credential_with_keys_v1_aux, create_unsigned_credential_with_seed_v1_aux, }, identity::{ - create_id_request_with_keys_v1_aux, create_id_request_with_seed_v1_aux, - create_identity_recovery_request_with_keys_aux, - create_identity_recovery_request_with_seed_aux, + create_id_request_v1_aux, + create_identity_recovery_request_aux, }, wallet::{ get_account_public_key_aux, get_account_signing_key_aux, @@ -59,23 +58,12 @@ pub fn get_credential_deployment_info_ext(signatures: &JsValue, unsigned_info: & #[wasm_bindgen(js_name = createIdRequestV1)] pub fn create_id_request_v1_ext(input: JsonString) -> JsResult { - create_id_request_with_seed_v1_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) -} - -#[wasm_bindgen(js_name = createIdRequestWithKeysV1)] -pub fn create_id_request_with_keys_v1_ext(input: JsonString) -> JsResult { - create_id_request_with_keys_v1_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) + create_id_request_v1_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) } #[wasm_bindgen(js_name = createIdentityRecoveryRequest)] pub fn create_identity_recovery_request_ext(input: JsonString) -> JsResult { - create_identity_recovery_request_with_seed_aux(serde_json::from_str(&input).unwrap()) - .map_err(to_js_error) -} - -#[wasm_bindgen(js_name = createIdentityRecoveryRequestWithKeys)] -pub fn create_identity_recovery_request_with_keys_ext(input: JsonString) -> JsResult { - create_identity_recovery_request_with_keys_aux(serde_json::from_str(&input).unwrap()) + create_identity_recovery_request_aux(serde_json::from_str(&input).unwrap()) .map_err(to_js_error) } diff --git a/packages/sdk/src/wasm/identity.ts b/packages/sdk/src/wasm/identity.ts index 87f0a3dc5..943045e86 100644 --- a/packages/sdk/src/wasm/identity.ts +++ b/packages/sdk/src/wasm/identity.ts @@ -9,6 +9,7 @@ import type { Versioned, } from '../types.js'; import type { IdProofInput, IdProofOutput } from '../id/index.js'; +import { ConcordiumHdWallet } from './HdWallet.ts'; interface IdentityRequestInputCommon { ipInfo: IpInfo; @@ -23,26 +24,12 @@ export type IdentityRequestInput = IdentityRequestInputCommon & { identityIndex: number; }; -type IdentityRequestInputInternal = { - common: IdentityRequestInputCommon; - net: Network; - seedAsHex: string; - identityIndex: number; -}; - export type IdentityRequestWithKeysInput = IdentityRequestInputCommon & { prfKey: string; idCredSec: string; blindingRandomness: string; }; -type IdentityRequestWithKeysInputInternal = { - common: IdentityRequestInputCommon; - prfKey: string; - idCredSec: string; - blindingRandomness: string; -}; - /** * Creates a V1 identity request by providing the secret keys directly. * This allows for the generation of the keys separately from creating @@ -51,17 +38,7 @@ type IdentityRequestWithKeysInputInternal = { export function createIdentityRequestWithKeys( input: IdentityRequestWithKeysInput ): Versioned { - const { prfKey, idCredSec, blindingRandomness, ...common } = input; - const internalInput: IdentityRequestWithKeysInputInternal = { - common, - prfKey, - idCredSec, - blindingRandomness, - }; - - const rawRequest = wasm.createIdRequestWithKeysV1( - JSON.stringify(internalInput) - ); + const rawRequest = wasm.createIdRequestV1(JSON.stringify(input)); try { return JSON.parse(rawRequest).idObjectRequest; } catch (e) { @@ -77,20 +54,30 @@ export function createIdentityRequestWithKeys( export function createIdentityRequest( input: IdentityRequestInput ): Versioned { - const { seed, identityIndex, net, ...common } = input; - const internalInput: IdentityRequestInputInternal = { - common, - net, - seedAsHex: seed, - identityIndex, + const wallet = ConcordiumHdWallet.fromHex(input.seed, input.net); + const identityProviderIndex = input.ipInfo.ipIdentity; + const identityIndex = input.identityIndex; + const idCredSec = wallet + .getIdCredSec(identityProviderIndex, identityIndex) + .toString('hex'); + const prfKey = wallet + .getPrfKey(identityProviderIndex, identityIndex) + .toString('hex'); + const blindingRandomness = wallet + .getSignatureBlindingRandomness(identityProviderIndex, identityIndex) + .toString('hex'); + + const inputWithKeys: IdentityRequestWithKeysInput = { + arsInfos: input.arsInfos, + arThreshold: input.arThreshold, + globalContext: input.globalContext, + ipInfo: input.ipInfo, + idCredSec, + prfKey, + blindingRandomness, }; - const rawRequest = wasm.createIdRequestV1(JSON.stringify(internalInput)); - try { - return JSON.parse(rawRequest).idObjectRequest; - } catch (e) { - throw new Error(rawRequest); - } + return createIdentityRequestWithKeys(inputWithKeys); } type IdentityRecoveryRequestInputCommon = { @@ -106,23 +93,11 @@ export type IdentityRecoveryRequestInput = identityIndex: number; }; -type IdentityRecoveryRequestInputInternal = { - common: IdentityRecoveryRequestInputCommon; - seedAsHex: string; - net: Network; - identityIndex: number; -}; - export type IdentityRecoveryRequestWithKeysInput = IdentityRecoveryRequestInputCommon & { idCredSec: string; }; -type IdentityRecoveryRequestWithKeysInputInternal = { - common: IdentityRecoveryRequestInputCommon; - idCredSec: string; -}; - /** * Creates an identity recovery request from a seed. This will derive the * corresponding keys based on the provided identity index, identity provider index @@ -131,41 +106,31 @@ type IdentityRecoveryRequestWithKeysInputInternal = { export function createIdentityRecoveryRequest( input: IdentityRecoveryRequestInput ): Versioned { - const { seedAsHex, net, identityIndex, ...common } = input; - - const internalInput: IdentityRecoveryRequestInputInternal = { - common, - identityIndex, - net, - seedAsHex, + const wallet = ConcordiumHdWallet.fromHex(input.seedAsHex, input.net); + const idCredSec = wallet + .getIdCredSec(input.ipInfo.ipIdentity, input.identityIndex) + .toString('hex'); + + const inputWithKeys: IdentityRecoveryRequestWithKeysInput = { + globalContext: input.globalContext, + ipInfo: input.ipInfo, + timestamp: input.timestamp, + idCredSec, }; - const rawRequest = wasm.createIdentityRecoveryRequest( - JSON.stringify(internalInput) - ); - try { - return JSON.parse(rawRequest).idRecoveryRequest; - } catch (e) { - throw new Error(rawRequest); - } + return createIdentityRecoveryRequestWithKeys(inputWithKeys); } /** - * Creates an identity recovery request from a seed. This will derive the - * corresponding keys based on the provided identity index, identity provider index - * and seed. The identity provider index is extracted from the provided IpInfo. + * Creates an indentity recovery request by providing the secret key directly. + * This allows for the generation of the keys separately from creating + * the request. */ export function createIdentityRecoveryRequestWithKeys( input: IdentityRecoveryRequestWithKeysInput ): Versioned { - const { idCredSec, ...common } = input; - const internalInput: IdentityRecoveryRequestWithKeysInputInternal = { - common, - idCredSec, - }; - - const rawRequest = wasm.createIdentityRecoveryRequestWithKeys( - JSON.stringify(internalInput) + const rawRequest = wasm.createIdentityRecoveryRequest( + JSON.stringify(input) ); try { return JSON.parse(rawRequest).idRecoveryRequest; From 5e3360d37b7c5e03818555807210313481709762 Mon Sep 17 00:00:00 2001 From: orhoj Date: Tue, 9 Jan 2024 15:14:31 +0100 Subject: [PATCH 20/44] Refactor so that using seed is on web-sdk side for credentials --- deps/concordium-base | 2 +- packages/rust-bindings/CHANGELOG.md | 1 - .../packages/wallet/src/external_functions.rs | 14 +-- .../wasm/credentialDeploymentTransactions.ts | 86 +++++++++++++------ packages/sdk/src/wasm/identity.ts | 2 +- 5 files changed, 63 insertions(+), 42 deletions(-) diff --git a/deps/concordium-base b/deps/concordium-base index c7fc3c032..6cce6bd27 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit c7fc3c032d9fc2eb1e8ab2385935d2884ac182cb +Subproject commit 6cce6bd2727511550e21d28d3ffc562f6c6fdd7d diff --git a/packages/rust-bindings/CHANGELOG.md b/packages/rust-bindings/CHANGELOG.md index 84bf20d0c..290bdccb3 100644 --- a/packages/rust-bindings/CHANGELOG.md +++ b/packages/rust-bindings/CHANGELOG.md @@ -6,7 +6,6 @@ - The function `create_id_request_v1_ext` exposed with the WASM entrypoint `createIdRequestV1` now expects a new JSON type. - The function `create_identity_recovery_request_ext` exposed with the WASM entrypoint `createIdentityRecoveryRequest` now expects a new JSON type. -- The function `create_unsigned_credential_v1_ext` exposed with the WASM entrypoint `createUnsignedCredentialV1` now expects a new JSON type. ### Changed diff --git a/packages/rust-bindings/packages/wallet/src/external_functions.rs b/packages/rust-bindings/packages/wallet/src/external_functions.rs index b305186a2..49bc01cff 100644 --- a/packages/rust-bindings/packages/wallet/src/external_functions.rs +++ b/packages/rust-bindings/packages/wallet/src/external_functions.rs @@ -4,9 +4,7 @@ use concordium_rust_bindings_common::{ types::{Base58String, HexString, JsonString}, }; use wallet_library::{ - credential::{ - create_unsigned_credential_with_keys_v1_aux, create_unsigned_credential_with_seed_v1_aux, - }, + credential::create_unsigned_credential_v1_aux, identity::{ create_id_request_v1_aux, create_identity_recovery_request_aux, @@ -73,15 +71,9 @@ pub fn create_credential_v1_ext(raw_input: JsonString) -> JsResult { create_credential_v1_aux(input).map_err(to_js_error) } -#[wasm_bindgen(js_name = createUnsignedCredentialWithKeysV1)] -pub fn create_unsigned_credential_v1_ext(input: JsonString) -> JsResult { - create_unsigned_credential_with_keys_v1_aux(serde_json::from_str(&input).unwrap()) - .map_err(to_js_error) -} - #[wasm_bindgen(js_name = createUnsignedCredentialV1)] -pub fn create_unsigned_credential_with_seed_v1_ext(input: JsonString) -> JsResult { - create_unsigned_credential_with_seed_v1_aux(serde_json::from_str(&input).unwrap()) +pub fn create_unsigned_credential_v1_ext(input: JsonString) -> JsResult { + create_unsigned_credential_v1_aux(serde_json::from_str(&input).unwrap()) .map_err(to_js_error) } diff --git a/packages/sdk/src/wasm/credentialDeploymentTransactions.ts b/packages/sdk/src/wasm/credentialDeploymentTransactions.ts index 08cb10675..b0a41193a 100644 --- a/packages/sdk/src/wasm/credentialDeploymentTransactions.ts +++ b/packages/sdk/src/wasm/credentialDeploymentTransactions.ts @@ -25,6 +25,8 @@ import * as TransactionExpiry from '../types/TransactionExpiry.js'; import * as AccountAddress from '../types/AccountAddress.js'; import { sha256 } from '../hash.js'; import { getCredentialDeploymentSignDigest } from '../serialization.js'; +import { ConcordiumHdWallet } from './HdWallet.js'; +import { filterRecord, mapRecord } from '../util.js'; /** * Generates the unsigned credential information that has to be signed when @@ -217,27 +219,65 @@ export function createCredentialTransaction( input: CredentialInput, expiry: TransactionExpiry.Type ): CredentialDeploymentTransaction { - const { seedAsHex, net, identityIndex, ...common } = input; - const internalInput = { - common, - seedAsHex, - net, - identityIndex, + const wallet = ConcordiumHdWallet.fromHex(input.seedAsHex, input.net); + const publicKey = wallet + .getAccountPublicKey( + input.ipInfo.ipIdentity, + input.identityIndex, + input.credNumber + ) + .toString('hex'); + + const verifyKey = { + schemeId: 'Ed25519', + verifyKey: publicKey, + }; + const credentialPublicKeys = { + keys: { 0: verifyKey }, + threshold: 1, }; - const rawRequest = wasm.createUnsignedCredentialV1( - JSON.stringify(internalInput) + const prfKey = wallet + .getPrfKey(input.ipInfo.ipIdentity, input.identityIndex) + .toString('hex'); + const idCredSec = wallet + .getIdCredSec(input.ipInfo.ipIdentity, input.identityIndex) + .toString('hex'); + const randomness = wallet + .getSignatureBlindingRandomness( + input.ipInfo.ipIdentity, + input.identityIndex + ) + .toString('hex'); + + const attributeRandomness = mapRecord( + filterRecord(AttributesKeys, (k) => isNaN(Number(k))), + (x) => + wallet + .getAttributeCommitmentRandomness( + input.ipInfo.ipIdentity, + input.identityIndex, + input.credNumber, + x + ) + .toString('hex') ); - let info: UnsignedCdiWithRandomness; - try { - info = JSON.parse(rawRequest); - } catch (e) { - throw new Error(rawRequest); - } - return { - expiry, - ...info, + + const noSeedInput: CredentialInputNoSeed = { + ipInfo: input.ipInfo, + globalContext: input.globalContext, + arsInfos: input.arsInfos, + idObject: input.idObject, + idCredSec, + prfKey, + sigRetrievelRandomness: randomness, + credentialPublicKeys, + attributeRandomness, + revealedAttributes: input.revealedAttributes, + credNumber: input.credNumber, }; + + return createCredentialTransactionNoSeed(noSeedInput, expiry); } /** @@ -247,17 +287,7 @@ export function createCredentialTransactionNoSeed( input: CredentialInputNoSeed, expiry: TransactionExpiry.Type ): CredentialDeploymentTransaction { - const { prfKey, idCredSec, sigRetrievelRandomness, ...common } = input; - const internalInput = { - common, - prfKey, - idCredSec, - blindingRandomness: sigRetrievelRandomness, - }; - - const rawRequest = wasm.createUnsignedCredentialWithKeysV1( - JSON.stringify(internalInput) - ); + const rawRequest = wasm.createUnsignedCredentialV1(JSON.stringify(input)); let info: UnsignedCdiWithRandomness; try { info = JSON.parse(rawRequest); diff --git a/packages/sdk/src/wasm/identity.ts b/packages/sdk/src/wasm/identity.ts index 943045e86..d8236bf41 100644 --- a/packages/sdk/src/wasm/identity.ts +++ b/packages/sdk/src/wasm/identity.ts @@ -9,7 +9,7 @@ import type { Versioned, } from '../types.js'; import type { IdProofInput, IdProofOutput } from '../id/index.js'; -import { ConcordiumHdWallet } from './HdWallet.ts'; +import { ConcordiumHdWallet } from './HdWallet.js'; interface IdentityRequestInputCommon { ipInfo: IpInfo; From a02db9f85bd49be5f9f3240ac1e7f897c53ad699 Mon Sep 17 00:00:00 2001 From: orhoj Date: Tue, 9 Jan 2024 15:31:46 +0100 Subject: [PATCH 21/44] Fix JSON mismatch for credential --- .../sdk/src/wasm/credentialDeploymentTransactions.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/sdk/src/wasm/credentialDeploymentTransactions.ts b/packages/sdk/src/wasm/credentialDeploymentTransactions.ts index b0a41193a..de62ac43f 100644 --- a/packages/sdk/src/wasm/credentialDeploymentTransactions.ts +++ b/packages/sdk/src/wasm/credentialDeploymentTransactions.ts @@ -287,7 +287,14 @@ export function createCredentialTransactionNoSeed( input: CredentialInputNoSeed, expiry: TransactionExpiry.Type ): CredentialDeploymentTransaction { - const rawRequest = wasm.createUnsignedCredentialV1(JSON.stringify(input)); + const { sigRetrievelRandomness, ...other } = input; + const internalInput = { + ...other, + blindingRandomness: input.sigRetrievelRandomness, + }; + const rawRequest = wasm.createUnsignedCredentialV1( + JSON.stringify(internalInput) + ); let info: UnsignedCdiWithRandomness; try { info = JSON.parse(rawRequest); From 31c948ad13a4c4f052f8ee5e6ead11281d28ac08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Holm=20Gj=C3=B8rup?= Date: Wed, 10 Jan 2024 10:12:04 +0100 Subject: [PATCH 22/44] ccd-js-gen: Generate functions for generating web wallet parameters --- packages/ccd-js-gen/CHANGELOG.md | 4 + packages/ccd-js-gen/README.md | 70 +++++++ packages/ccd-js-gen/src/lib.ts | 334 ++++++++++++++++++++++--------- 3 files changed, 315 insertions(+), 93 deletions(-) diff --git a/packages/ccd-js-gen/CHANGELOG.md b/packages/ccd-js-gen/CHANGELOG.md index 5aedcb930..78ea36039 100644 --- a/packages/ccd-js-gen/CHANGELOG.md +++ b/packages/ccd-js-gen/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +- Generate `createParameterWebWallet` and `createParameterWebWallet` functions for constructing the smart contract parameters in the format used by the Concordium Web-Wallet. +- Minor breaking change: `createParameter` function for contract entries and `createParameter` function in module are no longer generated when parameter schema type is `Unit`. + This function is mainly used internally, but were still exposed. +- Fix bug for generating JSON for schema type `ULeb128` and `ILeb128`. - Add optional flag `--ts-nocheck`, enabling it will add `// @ts-nocheck` in each generated file. - Add `--output-type ` flag to CLI allowing to specify whether to generate TypeScript or JavaScript directly. - Fix the executable version of the package on Windows. diff --git a/packages/ccd-js-gen/README.md b/packages/ccd-js-gen/README.md index 27021edba..804c0d6c5 100644 --- a/packages/ccd-js-gen/README.md +++ b/packages/ccd-js-gen/README.md @@ -69,6 +69,7 @@ const transactionHash = await MyContract.sendTransfer(contractClient, { - [function `getModuleSource`](#function-getmodulesource) - [type `Parameter`](#type-contractnameparameter) - [function `instantiate`](#function-instantiatecontractname) + - [function `createParameterWebWallet`](#function-createcontractnameparameterwebwallet) - [Generated contract client](#generated-contract-client) - [The contract client type](#the-contract-client-type) - [function `create`](#function-create-1) @@ -83,6 +84,7 @@ const transactionHash = await MyContract.sendTransfer(contractClient, { - [function `parseReturnValue`](#function-parsereturnvalueentrypointname) - [type `ErrorMessage`](#type-errormessageentrypointname) - [function `parseErrorMessage`](#function-parseerrormessageentrypointname) + - [function `createParameterWebWallet`](#function-createentrypointnameparameterwebwallet) ## Install the package @@ -314,6 +316,40 @@ const parameter = ...; const transactionHash = await MyModule.instantiateMyContract(myModule, transactionMeta, parameter, signer); ``` +#### function `createParameterWebWallet` + +For each smart contract in module a function for _constructing_ the WebWallet formattet parameter for initializing a new instance is produced. This is to be used with the [`@concordium/wallet-connector`](https://www.npmjs.com/package/@concordium/wallet-connectors) package. +These are named `createParameterWebWallet` where `` is the smart contract name in Pascal case. + +An example for a smart contract module containing a smart contract named `my-contract`, +the function becomes `createMyContractParameterWebWallet`. + +_This is only generated when the schema contains contract initialization parameter type._ + +**Parameter** + +The function parameter is: + +- `parameter` Parameter to provide the smart contract module for the instantiation. + +**Returns**: Parameter for initializing a contract instance in the format used by [`@concordium/wallet-connector`](https://www.npmjs.com/package/@concordium/wallet-connectors). + +```typescript +// Wallet connection from `@concordium/wallet-connector`. +const webWalletConnection: WalletConnection = ...; +// Parameter to pass the smart contract init-function. The structure depends on the contract. +const parameter: MyModule.MyContractParameter = ...; +// Convert the parameter into the format expected by the wallet. +const walletParameter = MyModule.createMyContractParameterWebWallet(parameter); +// Use wallet connection to sign and send the transaction. +const sender = ...; +const transactionHash = await webWalletConnection.signAndSendTransaction( + sender, + AccountTransactionType.InitContract, + walletParameter +); +``` + ### Generated contract client For each of the smart contracts in the module a file is produced named after the smart contract. @@ -559,3 +595,37 @@ const invokeResult = await MyContract.dryRunLaunchRocket(myContract, invoker, pa // Parse the error message const message: MyContract.ErrorMessageLaunchRocket | undefined = MyContract.parseErrorMessageLaunchRocket(invokeResult); ``` + +#### function `createParameterWebWallet` + +For each entrypoint of the smart contract a function for _constructing_ the WebWallet formattet parameter is produced. This is to be used with the [`@concordium/wallet-connector`](https://www.npmjs.com/package/@concordium/wallet-connectors) package. +These are named `createParameterWebWallet` where `` is the smart contract name in Pascal case. + +_This is only generated when the schema contains contract entrypoint parameter type._ + +An example for a smart contract with an entrypoint named `launch-rocket`, the function +becomes `createLaunchRocketParameterWebWallet`. + +**Parameter** + +The function parameter is: + +- `parameter` Parameter to provide to the smart contract entrypoint. + +**Returns**: Parameter for updating a contract instance in the format used by [`@concordium/wallet-connector`](https://www.npmjs.com/package/@concordium/wallet-connectors). + +```typescript +// Wallet connection from `@concordium/wallet-connector`. +const webWalletConnection: WalletConnection = ...; +// Parameter to pass the smart contract init-function. The structure depends on the contract. +const parameter: MyContract.LaunchRocketParameter = ...; +// Convert the parameter into the format expected by the wallet. +const walletParameter = MyContract.createLaunchRocketParameterWebWallet(parameter); +// Use wallet connection to sign and send the transaction. +const sender = ...; +const transactionHash = await webWalletConnection.signAndSendTransaction( + sender, + AccountTransactionType.Update, + walletParameter +); +``` diff --git a/packages/ccd-js-gen/src/lib.ts b/packages/ccd-js-gen/src/lib.ts index e9bbcd734..c580a709b 100644 --- a/packages/ccd-js-gen/src/lib.ts +++ b/packages/ccd-js-gen/src/lib.ts @@ -458,33 +458,86 @@ function generactionModuleContractCode( const transactionMetadataId = 'transactionMetadata'; const parameterId = 'parameter'; const signerId = 'signer'; - - const initParameter = createParameterCode( - parameterId, - contractSchema?.init?.parameter - ); - const initParameterTypeId = `${toPascalCase(contractName)}Parameter`; - + const base64InitParameterSchemaTypeId = `base64${toPascalCase( + contractName + )}ParameterSchema`; + const initParameterJsonTypeId = `${toPascalCase( + contractName + )}ParameterSchemaJson`; const createInitParameterFnId = `create${toPascalCase( contractName )}Parameter`; + const createInitParameterJsonFnId = `create${toPascalCase( + contractName + )}ParameterSchemaJson`; + const createInitParameterFnWebWalletId = `create${toPascalCase( + contractName + )}ParameterWebWallet`; + + const initParameterSchemaType = contractSchema?.init?.parameter; - if (initParameter !== undefined) { + if (initParameterSchemaType !== undefined) { + moduleSourceFile.addVariableStatement({ + docs: [ + `Base64 encoding of the parameter schema type used when instantiating a new '${contractName}' smart contract instance.`, + ], + declarationKind: tsm.VariableDeclarationKind.Const, + declarations: [ + { + name: base64InitParameterSchemaTypeId, + initializer: `'${Buffer.from( + SDK.serializeSchemaType(initParameterSchemaType) + ).toString('base64')}'`, + }, + ], + }); + + const typeAndMapper = schemaAsNativeType(initParameterSchemaType); moduleSourceFile.addTypeAlias({ docs: [ - `Parameter type transaction for instantiating a new '${contractName}' smart contract instance`, + `Parameter JSON type needed by the schema when instantiating a new '${contractName}' smart contract instance.`, ], - isExported: true, - name: initParameterTypeId, - type: initParameter.type, + name: initParameterJsonTypeId, + type: typeAndMapper.jsonType, }); - moduleSourceFile - .addFunction({ + if (initParameterSchemaType.type !== 'Unit') { + moduleSourceFile.addTypeAlias({ + docs: [ + `Parameter type transaction for instantiating a new '${contractName}' smart contract instance.`, + ], + isExported: true, + name: initParameterTypeId, + type: typeAndMapper.nativeType, + }); + + const mappedParameter = typeAndMapper.nativeToJson(parameterId); + moduleSourceFile.addFunction({ + docs: [ + [ + `Construct schema JSON representation used in transactions for instantiating a new '${contractName}' smart contract instance.`, + `@param {${initParameterTypeId}} ${parameterId} The structured parameter to construct from.`, + `@returns {${initParameterJsonTypeId}} The smart contract parameter JSON.`, + ].join('\n'), + ], + name: createInitParameterJsonFnId, + parameters: [ + { + type: initParameterTypeId, + name: parameterId, + }, + ], + returnType: initParameterJsonTypeId, + statements: [ + ...mappedParameter.code, + `return ${mappedParameter.id};`, + ], + }); + moduleSourceFile.addFunction({ docs: [ [ - `Construct Parameter type transaction for instantiating a new '${contractName}' smart contract instance.`, + `Construct Parameter type used in transactions for instantiating a new '${contractName}' smart contract instance.`, `@param {${initParameterTypeId}} ${parameterId} The structured parameter to construct from.`, '@returns {SDK.Parameter.Type} The smart contract parameter.', ].join('\n'), @@ -498,12 +551,51 @@ function generactionModuleContractCode( }, ], returnType: 'SDK.Parameter.Type', - }) - .setBodyText( - [...initParameter.code, `return ${initParameter.id}`].join('\n') - ); + statements: [ + `return SDK.Parameter.fromBase64SchemaType(${base64InitParameterSchemaTypeId}, ${createInitParameterJsonFnId}(${parameterId}));`, + ], + }); + + moduleSourceFile.addFunction({ + docs: [ + [ + `Construct WebWallet parameter type used in transactions for instantiating a new '${contractName}' smart contract instance.`, + `@param {${initParameterTypeId}} ${parameterId} The structured parameter to construct from.`, + '@returns The smart contract parameter support by the WebWallet.', + ].join('\n'), + ], + isExported: true, + name: createInitParameterFnWebWalletId, + parameters: [ + { + type: initParameterTypeId, + name: parameterId, + }, + ], + statements: [ + 'return {', + ` parameters: ${createInitParameterJsonFnId}(${parameterId}),`, + ' schema: {', + " type: 'TypeSchema' as const,", + ` value: ${base64InitParameterSchemaTypeId}`, + ' },', + '}', + ], + }); + } + } else { + moduleSourceFile.addTypeAlias({ + docs: [ + `Parameter type transaction for instantiating a new '${contractName}' smart contract instance.`, + ], + isExported: true, + name: initParameterTypeId, + type: 'SDK.Parameter.Type', + }); } + const initTakesNoParameter = initParameterSchemaType?.type === 'Unit'; + moduleSourceFile .addFunction({ docs: [ @@ -511,7 +603,7 @@ function generactionModuleContractCode( `Send transaction for instantiating a new '${contractName}' smart contract instance.`, `@param {${moduleClientType}} ${moduleClientId} - The client of the on-chain smart contract module with referecence '${moduleRef.moduleRef}'.`, `@param {SDK.ContractTransactionMetadata} ${transactionMetadataId} - Metadata related to constructing a transaction for a smart contract module.`, - ...(initParameter === undefined + ...(initTakesNoParameter ? [] : [ `@param {${initParameterTypeId}} ${parameterId} - Parameter to provide as part of the transaction for the instantiation of a new smart contract contract.`, @@ -532,7 +624,7 @@ function generactionModuleContractCode( name: transactionMetadataId, type: 'SDK.ContractTransactionMetadata', }, - ...(initParameter === undefined + ...(initTakesNoParameter ? [] : [ { @@ -553,7 +645,9 @@ function generactionModuleContractCode( ` ${moduleClientId}.${internalModuleClientId},`, ` SDK.ContractName.fromStringUnchecked('${contractName}'),`, ` ${transactionMetadataId},`, - ...(initParameter === undefined + ...(initParameterSchemaType === undefined + ? [` ${parameterId},`] + : initParameterSchemaType.type === 'Unit' ? [] : [` ${createInitParameterFnId}(${parameterId}),`]), ` ${signerId}`, @@ -854,33 +948,86 @@ function generateContractEntrypointCode( const signerId = 'signer'; const genericContractId = 'genericContract'; const blockHashId = 'blockHash'; - - const receiveParameter = createParameterCode( - parameterId, - entrypointSchema?.parameter - ); - const receiveParameterTypeId = `${toPascalCase(entrypointName)}Parameter`; - + const base64ReceiveParameterSchemaTypeId = `base64${toPascalCase( + entrypointName + )}ParameterSchema`; + const receiveParameterJsonTypeId = `${toPascalCase( + entrypointName + )}ParameterSchemaJson`; const createReceiveParameterFnId = `create${toPascalCase( entrypointName )}Parameter`; + const createReceiveParameterJsonFnId = `create${toPascalCase( + entrypointName + )}ParameterSchemaJson`; + const createReceiveParameterFnWebWalletId = `create${toPascalCase( + entrypointName + )}ParameterWebWallet`; + + const receiveParameterSchemaType = entrypointSchema?.parameter; + + if (receiveParameterSchemaType !== undefined) { + contractSourceFile.addVariableStatement({ + docs: [ + `Base64 encoding of the parameter schema type for update transactions to '${entrypointName}' entrypoint of the '${contractName}' contract.`, + ], + declarationKind: tsm.VariableDeclarationKind.Const, + declarations: [ + { + name: base64ReceiveParameterSchemaTypeId, + initializer: `'${Buffer.from( + SDK.serializeSchemaType(receiveParameterSchemaType) + ).toString('base64')}'`, + }, + ], + }); - if (receiveParameter !== undefined) { + const typeAndMapper = schemaAsNativeType(receiveParameterSchemaType); contractSourceFile.addTypeAlias({ docs: [ - `Parameter type for update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, + `Parameter JSON type needed by the schema for update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, ], - isExported: true, - name: receiveParameterTypeId, - type: receiveParameter.type, + name: receiveParameterJsonTypeId, + type: typeAndMapper.jsonType, }); + if (receiveParameterSchemaType.type !== 'Unit') { + contractSourceFile.addTypeAlias({ + docs: [ + `Parameter type for update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, + ], + isExported: true, + name: receiveParameterTypeId, + type: typeAndMapper.nativeType, + }); - contractSourceFile - .addFunction({ + const mappedParameter = typeAndMapper.nativeToJson(parameterId); + + contractSourceFile.addFunction({ docs: [ [ - `Construct Parameter for update transactions for '${entrypointName}' entrypoint of the '${contractName}' contract.`, + `Construct schema JSON representation used in update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, + `@param {${receiveParameterTypeId}} ${parameterId} The structured parameter to construct from.`, + `@returns {${receiveParameterJsonTypeId}} The smart contract parameter JSON.`, + ].join('\n'), + ], + name: createReceiveParameterJsonFnId, + parameters: [ + { + type: receiveParameterTypeId, + name: parameterId, + }, + ], + returnType: receiveParameterJsonTypeId, + statements: [ + ...mappedParameter.code, + `return ${mappedParameter.id};`, + ], + }); + contractSourceFile.addFunction({ + docs: [ + [ + `Construct Parameter type used in update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, `@param {${receiveParameterTypeId}} ${parameterId} The structured parameter to construct from.`, '@returns {SDK.Parameter.Type} The smart contract parameter.', ].join('\n'), @@ -894,15 +1041,51 @@ function generateContractEntrypointCode( }, ], returnType: 'SDK.Parameter.Type', - }) - .setBodyText( - [ - ...receiveParameter.code, - `return ${receiveParameter.id};`, - ].join('\n') - ); + statements: [ + `return SDK.Parameter.fromBase64SchemaType(${base64ReceiveParameterSchemaTypeId}, ${createReceiveParameterJsonFnId}(${parameterId}));`, + ], + }); + + contractSourceFile.addFunction({ + docs: [ + [ + `Construct WebWallet parameter type used in update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, + `@param {${receiveParameterTypeId}} ${parameterId} The structured parameter to construct from.`, + '@returns The smart contract parameter support by the WebWallet.', + ].join('\n'), + ], + isExported: true, + name: createReceiveParameterFnWebWalletId, + parameters: [ + { + type: receiveParameterTypeId, + name: parameterId, + }, + ], + statements: [ + 'return {', + ` parameters: ${createReceiveParameterJsonFnId}(${parameterId}),`, + ' schema: {', + " type: 'TypeSchema' as const,", + ` value: ${base64ReceiveParameterSchemaTypeId}`, + ' },', + '}', + ], + }); + } + } else { + contractSourceFile.addTypeAlias({ + docs: [ + `Parameter type used in update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, + ], + isExported: true, + name: receiveParameterTypeId, + type: 'SDK.Parameter.Type', + }); } + const receiveTakesNoParameter = receiveParameterSchemaType?.type === 'Unit'; + contractSourceFile .addFunction({ docs: [ @@ -910,7 +1093,7 @@ function generateContractEntrypointCode( `Send an update-contract transaction to the '${entrypointName}' entrypoint of the '${contractName}' contract.`, `@param {${contractClientType}} ${contractClientId} The client for a '${contractName}' smart contract instance on chain.`, `@param {SDK.ContractTransactionMetadata} ${transactionMetadataId} - Metadata related to constructing a transaction for a smart contract.`, - ...(receiveParameter === undefined + ...(receiveTakesNoParameter ? [] : [ `@param {${receiveParameterTypeId}} ${parameterId} - Parameter to provide the smart contract entrypoint as part of the transaction.`, @@ -931,7 +1114,7 @@ function generateContractEntrypointCode( name: transactionMetadataId, type: 'SDK.ContractTransactionMetadata', }, - ...(receiveParameter === undefined + ...(receiveTakesNoParameter ? [] : [ { @@ -952,7 +1135,9 @@ function generateContractEntrypointCode( ` SDK.EntrypointName.fromStringUnchecked('${entrypointName}'),`, ' SDK.Parameter.toBuffer,', ` ${transactionMetadataId},`, - ...(receiveParameter === undefined + ...(receiveParameterSchemaType === undefined + ? [` ${parameterId},`] + : receiveParameterSchemaType.type === 'Unit' ? [] : [` ${createReceiveParameterFnId}(${parameterId}),`]), ` ${signerId}`, @@ -967,7 +1152,7 @@ function generateContractEntrypointCode( `Dry-run an update-contract transaction to the '${entrypointName}' entrypoint of the '${contractName}' contract.`, `@param {${contractClientType}} ${contractClientId} The client for a '${contractName}' smart contract instance on chain.`, `@param {SDK.ContractAddress.Type | SDK.AccountAddress.Type} ${invokeMetadataId} - The address of the account or contract which is invoking this transaction.`, - ...(receiveParameter === undefined + ...(receiveTakesNoParameter ? [] : [ `@param {${receiveParameterTypeId}} ${parameterId} - Parameter to provide the smart contract entrypoint as part of the transaction.`, @@ -984,7 +1169,7 @@ function generateContractEntrypointCode( name: contractClientId, type: contractClientType, }, - ...(receiveParameter === undefined + ...(receiveTakesNoParameter ? [] : [ { @@ -1011,7 +1196,9 @@ function generateContractEntrypointCode( ` SDK.EntrypointName.fromStringUnchecked('${entrypointName}'),`, ` ${invokeMetadataId},`, ' SDK.Parameter.toBuffer,', - ...(receiveParameter === undefined + ...(receiveParameterSchemaType === undefined + ? [` ${parameterId},`] + : receiveParameterSchemaType.type === 'Unit' ? [] : [` ${createReceiveParameterFnId}(${parameterId}),`]), ` ${blockHashId}`, @@ -1733,11 +1920,11 @@ function schemaAsNativeType(schemaType: SDK.SchemaType): SchemaNativeType { }; case 'ULeb128': case 'ILeb128': + const resultId = idGenerator('leb'); return { nativeType: 'number | bigint', - jsonType: 'bigint', + jsonType: 'string', nativeToJson(id) { - const resultId = idGenerator('number'); return { code: [`const ${resultId} = BigInt(${id}).toString();`], id: resultId, @@ -1745,8 +1932,8 @@ function schemaAsNativeType(schemaType: SDK.SchemaType): SchemaNativeType { }, jsonToNative(id) { return { - code: [], - id: `BigInt(${id})`, + code: [`const ${resultId} = BigInt(${id});`], + id: resultId, }; }, }; @@ -1936,45 +2123,6 @@ type TypeConversionCode = { id: string; }; -/** - * Generate tokens for creating the parameter from input. - * @param {string} parameterId Identifier of the input. - * @param {SDK.SchemaType} [schemaType] The schema type to use for the parameter. - * @returns Undefined if no parameter is expected. - */ -function createParameterCode( - parameterId: string, - schemaType?: SDK.SchemaType -): TypeConversionCode | undefined { - // No schema type is present so fallback to plain parameter. - if (schemaType === undefined) { - return { - type: 'SDK.Parameter.Type', - code: [], - id: parameterId, - }; - } - - if (schemaType.type === 'Unit') { - // No parameter is needed according to the schema. - return undefined; - } - const typeAndMapper = schemaAsNativeType(schemaType); - const buffer = SDK.serializeSchemaType(schemaType); - const base64Schema = Buffer.from(buffer).toString('base64'); - - const mappedParameter = typeAndMapper.nativeToJson(parameterId); - const resultId = 'out'; - return { - type: typeAndMapper.nativeType, - code: [ - ...mappedParameter.code, - `const ${resultId} = SDK.Parameter.fromBase64SchemaType('${base64Schema}', ${mappedParameter.id});`, - ], - id: resultId, - }; -} - /** * Generate tokens for parsing a contract event. * @param {string} eventId Identifier of the event to parse. From 796707af8422cac15729231e19ee5d81caed77a2 Mon Sep 17 00:00:00 2001 From: orhoj Date: Wed, 10 Jan 2024 14:44:04 +0100 Subject: [PATCH 23/44] Update base dependency --- deps/concordium-base | 2 +- .../rust-bindings/packages/wallet/src/external_functions.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deps/concordium-base b/deps/concordium-base index 6cce6bd27..3fd977651 160000 --- a/deps/concordium-base +++ b/deps/concordium-base @@ -1 +1 @@ -Subproject commit 6cce6bd2727511550e21d28d3ffc562f6c6fdd7d +Subproject commit 3fd977651b6b80997b44e9e788776f2915b01f25 diff --git a/packages/rust-bindings/packages/wallet/src/external_functions.rs b/packages/rust-bindings/packages/wallet/src/external_functions.rs index 49bc01cff..dfe145eb8 100644 --- a/packages/rust-bindings/packages/wallet/src/external_functions.rs +++ b/packages/rust-bindings/packages/wallet/src/external_functions.rs @@ -6,7 +6,7 @@ use concordium_rust_bindings_common::{ use wallet_library::{ credential::create_unsigned_credential_v1_aux, identity::{ - create_id_request_v1_aux, + create_identity_object_request_v1_aux, create_identity_recovery_request_aux, }, wallet::{ @@ -56,7 +56,7 @@ pub fn get_credential_deployment_info_ext(signatures: &JsValue, unsigned_info: & #[wasm_bindgen(js_name = createIdRequestV1)] pub fn create_id_request_v1_ext(input: JsonString) -> JsResult { - create_id_request_v1_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) + create_identity_object_request_v1_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) } #[wasm_bindgen(js_name = createIdentityRecoveryRequest)] From 9161b1fdbbf79d6c387f09a4e85b17634e8ea20d Mon Sep 17 00:00:00 2001 From: orhoj Date: Wed, 10 Jan 2024 14:58:46 +0100 Subject: [PATCH 24/44] Add comments to exports --- packages/sdk/src/wasm/identity.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/sdk/src/wasm/identity.ts b/packages/sdk/src/wasm/identity.ts index d8236bf41..9ff5188e6 100644 --- a/packages/sdk/src/wasm/identity.ts +++ b/packages/sdk/src/wasm/identity.ts @@ -18,12 +18,20 @@ interface IdentityRequestInputCommon { arThreshold: number; } +/** + * The input parameter for creating an identity object request where + * the secret keys are derived from the provided seed. + */ export type IdentityRequestInput = IdentityRequestInputCommon & { seed: string; net: Network; identityIndex: number; }; +/** + * The input parameter for creating an identity object request where + * the secret keys and randomness are provided directly. + */ export type IdentityRequestWithKeysInput = IdentityRequestInputCommon & { prfKey: string; idCredSec: string; @@ -86,6 +94,10 @@ type IdentityRecoveryRequestInputCommon = { timestamp: number; }; +/** + * The input parameter for creating an identity recovery request where + * the secret keys are derived from the provided seed. + */ export type IdentityRecoveryRequestInput = IdentityRecoveryRequestInputCommon & { seedAsHex: string; @@ -93,6 +105,10 @@ export type IdentityRecoveryRequestInput = identityIndex: number; }; +/** + * The input parameter for creating an identity recovery request where + * the secret keys and randomness are provided directly. + */ export type IdentityRecoveryRequestWithKeysInput = IdentityRecoveryRequestInputCommon & { idCredSec: string; From 1a23da6ce4cf9889e2c42261f1e46284bbd2ca1d Mon Sep 17 00:00:00 2001 From: orhoj Date: Wed, 10 Jan 2024 15:00:19 +0100 Subject: [PATCH 25/44] Format rust --- .../packages/wallet/src/external_functions.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/rust-bindings/packages/wallet/src/external_functions.rs b/packages/rust-bindings/packages/wallet/src/external_functions.rs index dfe145eb8..59c5399fa 100644 --- a/packages/rust-bindings/packages/wallet/src/external_functions.rs +++ b/packages/rust-bindings/packages/wallet/src/external_functions.rs @@ -5,10 +5,7 @@ use concordium_rust_bindings_common::{ }; use wallet_library::{ credential::create_unsigned_credential_v1_aux, - identity::{ - create_identity_object_request_v1_aux, - create_identity_recovery_request_aux, - }, + identity::{create_identity_object_request_v1_aux, create_identity_recovery_request_aux}, wallet::{ get_account_public_key_aux, get_account_signing_key_aux, get_attribute_commitment_randomness_aux, get_credential_id_aux, get_id_cred_sec_aux, @@ -56,13 +53,13 @@ pub fn get_credential_deployment_info_ext(signatures: &JsValue, unsigned_info: & #[wasm_bindgen(js_name = createIdRequestV1)] pub fn create_id_request_v1_ext(input: JsonString) -> JsResult { - create_identity_object_request_v1_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) + create_identity_object_request_v1_aux(serde_json::from_str(&input).unwrap()) + .map_err(to_js_error) } #[wasm_bindgen(js_name = createIdentityRecoveryRequest)] pub fn create_identity_recovery_request_ext(input: JsonString) -> JsResult { - create_identity_recovery_request_aux(serde_json::from_str(&input).unwrap()) - .map_err(to_js_error) + create_identity_recovery_request_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) } #[wasm_bindgen(js_name = createCredentialV1)] @@ -73,8 +70,7 @@ pub fn create_credential_v1_ext(raw_input: JsonString) -> JsResult { #[wasm_bindgen(js_name = createUnsignedCredentialV1)] pub fn create_unsigned_credential_v1_ext(input: JsonString) -> JsResult { - create_unsigned_credential_v1_aux(serde_json::from_str(&input).unwrap()) - .map_err(to_js_error) + create_unsigned_credential_v1_aux(serde_json::from_str(&input).unwrap()).map_err(to_js_error) } #[wasm_bindgen(js_name = createIdProof)] From d8f3fc80535134901f8c6c48760c9b9c3b757d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Holm=20Gj=C3=B8rup?= Date: Wed, 10 Jan 2024 16:54:51 +0100 Subject: [PATCH 26/44] Convert schema to buffer for webwallet --- packages/ccd-js-gen/src/lib.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ccd-js-gen/src/lib.ts b/packages/ccd-js-gen/src/lib.ts index c580a709b..26022782d 100644 --- a/packages/ccd-js-gen/src/lib.ts +++ b/packages/ccd-js-gen/src/lib.ts @@ -577,7 +577,7 @@ function generactionModuleContractCode( ` parameters: ${createInitParameterJsonFnId}(${parameterId}),`, ' schema: {', " type: 'TypeSchema' as const,", - ` value: ${base64InitParameterSchemaTypeId}`, + ` value: SDK.toBuffer(${base64InitParameterSchemaTypeId}, 'base64')`, ' },', '}', ], @@ -1067,7 +1067,7 @@ function generateContractEntrypointCode( ` parameters: ${createReceiveParameterJsonFnId}(${parameterId}),`, ' schema: {', " type: 'TypeSchema' as const,", - ` value: ${base64ReceiveParameterSchemaTypeId}`, + ` value: SDK.toBuffer(${base64ReceiveParameterSchemaTypeId}, 'base64')`, ' },', '}', ], From a42b3eec9206960870dc4aae5bd29c5c9013fecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Holm=20Gj=C3=B8rup?= Date: Thu, 11 Jan 2024 15:41:09 +0100 Subject: [PATCH 27/44] Address review comments --- packages/ccd-js-gen/CHANGELOG.md | 3 +-- packages/ccd-js-gen/README.md | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/ccd-js-gen/CHANGELOG.md b/packages/ccd-js-gen/CHANGELOG.md index 78ea36039..df655600e 100644 --- a/packages/ccd-js-gen/CHANGELOG.md +++ b/packages/ccd-js-gen/CHANGELOG.md @@ -3,8 +3,7 @@ ## Unreleased - Generate `createParameterWebWallet` and `createParameterWebWallet` functions for constructing the smart contract parameters in the format used by the Concordium Web-Wallet. -- Minor breaking change: `createParameter` function for contract entries and `createParameter` function in module are no longer generated when parameter schema type is `Unit`. - This function is mainly used internally, but were still exposed. +- Minor breaking change: `createParameter` function for contract entries and `createParameter` function in module are no longer generated when parameter schema type is `Unit`. These functions are mainly used internally, but are still exposed. - Fix bug for generating JSON for schema type `ULeb128` and `ILeb128`. - Add optional flag `--ts-nocheck`, enabling it will add `// @ts-nocheck` in each generated file. - Add `--output-type ` flag to CLI allowing to specify whether to generate TypeScript or JavaScript directly. diff --git a/packages/ccd-js-gen/README.md b/packages/ccd-js-gen/README.md index 804c0d6c5..66620cf6e 100644 --- a/packages/ccd-js-gen/README.md +++ b/packages/ccd-js-gen/README.md @@ -599,7 +599,7 @@ const message: MyContract.ErrorMessageLaunchRocket | undefined = MyContract.pars #### function `createParameterWebWallet` For each entrypoint of the smart contract a function for _constructing_ the WebWallet formattet parameter is produced. This is to be used with the [`@concordium/wallet-connector`](https://www.npmjs.com/package/@concordium/wallet-connectors) package. -These are named `createParameterWebWallet` where `` is the smart contract name in Pascal case. +These are named `createParameterWebWallet` where `` is the entrypoint name in Pascal case. _This is only generated when the schema contains contract entrypoint parameter type._ From 60abf842531aad0430cb2ee0069fc13120e0c697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Bruus=20Zeppelin?= Date: Mon, 15 Jan 2024 15:00:45 +0100 Subject: [PATCH 28/44] Remove console log from function (#327) --- packages/sdk/src/signHelpers.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/sdk/src/signHelpers.ts b/packages/sdk/src/signHelpers.ts index e92755202..a5b3474fb 100644 --- a/packages/sdk/src/signHelpers.ts +++ b/packages/sdk/src/signHelpers.ts @@ -54,7 +54,6 @@ export interface WalletExportFormat { */ export function parseWallet(walletString: JsonString): WalletExportFormat { const wallet = JSON.parse(walletString); - console.log(typeof wallet.type); if (typeof wallet.type !== 'string') { throw Error( 'Expected field "type" to be of type "string" but was of type "' + From 2d71fa29c04cc81d1dde9700096770da63abd4b5 Mon Sep 17 00:00:00 2001 From: Hjort Date: Fri, 12 Jan 2024 15:47:35 +0100 Subject: [PATCH 29/44] Recover and save identity in wallet example --- examples/wallet/src/ConfirmIdentity.tsx | 58 +++++++++ examples/wallet/src/CreateIdentity.tsx | 2 + examples/wallet/src/Identity.tsx | 36 +++--- examples/wallet/src/Index.tsx | 10 ++ examples/wallet/src/RecoverIdentity.tsx | 153 ++++++++++++++++++++++++ examples/wallet/src/constants.ts | 1 + examples/wallet/src/recovery-worker.ts | 9 ++ examples/wallet/src/util.ts | 24 +++- 8 files changed, 271 insertions(+), 22 deletions(-) create mode 100644 examples/wallet/src/ConfirmIdentity.tsx create mode 100644 examples/wallet/src/RecoverIdentity.tsx create mode 100644 examples/wallet/src/recovery-worker.ts diff --git a/examples/wallet/src/ConfirmIdentity.tsx b/examples/wallet/src/ConfirmIdentity.tsx new file mode 100644 index 000000000..3a54af660 --- /dev/null +++ b/examples/wallet/src/ConfirmIdentity.tsx @@ -0,0 +1,58 @@ +import { AttributeList, IdentityObjectV1 } from '@concordium/web-sdk'; +import React, { useEffect, useMemo, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { extractIdentityObjectUrl, fetchIdentity } from './util'; +import { CreateAccount } from './CreateAccount'; +import { identityObjectKey } from './constants'; + +function DisplayIdentity({ attributes }: { attributes: AttributeList }) { + return ( +
    +
  • + Name: {attributes.chosenAttributes.firstName}{' '} + {attributes.chosenAttributes.lastName} +
  • +
  • Nationality: {attributes.chosenAttributes.nationality}
  • +
+ ); +} + +export function ConfirmIdentity() { + const location = useLocation(); + const [error, setError] = useState(); + const identity = useMemo( + () => { + const raw = localStorage.getItem(identityObjectKey) + if (raw) { + return JSON.parse(raw) + } + }, + [] + ); + const navigate = useNavigate(); + + useEffect(() => { + const identityObjectUrl = extractIdentityObjectUrl(location.hash); + fetchIdentity(identityObjectUrl) + .then((raw) => { + localStorage.setItem(identityObjectKey, JSON.stringify(raw)) + navigate("/identity") + }) + .catch(setError); + }, [location.hash]); + + if (error) { + return ( +
+

Identity creation failed

+
{error}
+
+ ); + } + + return ( + <> +

Identity is not ready yet

+ + ); +} diff --git a/examples/wallet/src/CreateIdentity.tsx b/examples/wallet/src/CreateIdentity.tsx index 4511daa0f..277d9da72 100644 --- a/examples/wallet/src/CreateIdentity.tsx +++ b/examples/wallet/src/CreateIdentity.tsx @@ -119,6 +119,7 @@ export function CreateIdentity() { return (
+

Identity Issuance

+ + {createButtonDisabled && ( +
Generating identity recovery request. This can take a while.
+ )} +
+ ); +} diff --git a/examples/wallet/src/constants.ts b/examples/wallet/src/constants.ts index 61fad6d1d..bf7d16f1f 100644 --- a/examples/wallet/src/constants.ts +++ b/examples/wallet/src/constants.ts @@ -5,6 +5,7 @@ export const network: Network = 'Testnet'; // Local storage keys export const seedPhraseKey = 'seed-phrase'; export const selectedIdentityProviderKey = 'selected-identity-provider'; +export const identityObjectKey = 'identity-object'; // The indices of the identity and the credential deployed on the account. // These are static in this example as we only create a single identity and diff --git a/examples/wallet/src/recovery-worker.ts b/examples/wallet/src/recovery-worker.ts new file mode 100644 index 000000000..51b5e4aae --- /dev/null +++ b/examples/wallet/src/recovery-worker.ts @@ -0,0 +1,9 @@ +import { + createIdentityRecoveryRequest, + IdentityRecoveryRequestInput +} from '@concordium/web-sdk'; + +self.onmessage = (e: MessageEvent) => { + const recoveryRequest = createIdentityRecoveryRequest(e.data); + self.postMessage(recoveryRequest); +}; diff --git a/examples/wallet/src/util.ts b/examples/wallet/src/util.ts index a7ea7935c..10c5aa8b0 100644 --- a/examples/wallet/src/util.ts +++ b/examples/wallet/src/util.ts @@ -18,6 +18,7 @@ import { buildBasicAccountSigner, serializeCredentialDeploymentPayload, signTransaction, + IdRecoveryRequest, } from '@concordium/web-sdk'; import { IdentityProviderWithMetadata, @@ -40,7 +41,7 @@ import { filterRecord, mapRecord } from '../../../packages/sdk/lib/esm/util'; // We dynamically resolve this as the hosted server can run at different // locations. export function getRedirectUri() { - return window.location.origin + '/identity'; + return window.location.origin + '/confirm-identity'; } /** @@ -295,6 +296,27 @@ export async function sendIdentityRequest( } } +/** + * Sends an identity recovery object request, which is the start of the identity recovery flow, to the + * provided URL. + * @param recoveryRequest the identity recovery request to send + * @param baseUrl the identity recovery start URL + * @returns the URL that the identity provider redirects to. This URL should point to to versioned identity object. + */ +export async function sendIdentityRecoveryRequest( + recoveryRequest: IdRecoveryRequest, + baseUrl: string +) { + const searchParams = new URLSearchParams({ state: JSON.stringify({idRecoveryRequest: recoveryRequest})}); + const url = `${baseUrl}?${searchParams.toString()}`; + const response = await fetch(url); + + if (response.ok) { + return response.url; + } else { + throw new Error((await response.json()).message); + } +} /** * Gets information about an account from the node. The method will continue to poll for some time * as the account might not be in a block when this is first called. From f5b062d2ffd9e9aca339fb694dd33f403b314623 Mon Sep 17 00:00:00 2001 From: Hjort Date: Tue, 16 Jan 2024 11:58:48 +0100 Subject: [PATCH 30/44] Fix lint --- examples/wallet/src/ConfirmIdentity.tsx | 37 ++++-------------- examples/wallet/src/CreateIdentity.tsx | 2 +- examples/wallet/src/Identity.tsx | 26 +++++-------- examples/wallet/src/Index.tsx | 4 +- examples/wallet/src/RecoverIdentity.tsx | 51 ++++++++++++++----------- examples/wallet/src/recovery-worker.ts | 2 +- examples/wallet/src/util.ts | 4 +- 7 files changed, 52 insertions(+), 74 deletions(-) diff --git a/examples/wallet/src/ConfirmIdentity.tsx b/examples/wallet/src/ConfirmIdentity.tsx index 3a54af660..66e3ffb61 100644 --- a/examples/wallet/src/ConfirmIdentity.tsx +++ b/examples/wallet/src/ConfirmIdentity.tsx @@ -1,44 +1,21 @@ -import { AttributeList, IdentityObjectV1 } from '@concordium/web-sdk'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { extractIdentityObjectUrl, fetchIdentity } from './util'; -import { CreateAccount } from './CreateAccount'; import { identityObjectKey } from './constants'; -function DisplayIdentity({ attributes }: { attributes: AttributeList }) { - return ( -
    -
  • - Name: {attributes.chosenAttributes.firstName}{' '} - {attributes.chosenAttributes.lastName} -
  • -
  • Nationality: {attributes.chosenAttributes.nationality}
  • -
- ); -} - export function ConfirmIdentity() { const location = useLocation(); const [error, setError] = useState(); - const identity = useMemo( - () => { - const raw = localStorage.getItem(identityObjectKey) - if (raw) { - return JSON.parse(raw) - } - }, - [] - ); const navigate = useNavigate(); - + useEffect(() => { const identityObjectUrl = extractIdentityObjectUrl(location.hash); fetchIdentity(identityObjectUrl) - .then((raw) => { - localStorage.setItem(identityObjectKey, JSON.stringify(raw)) - navigate("/identity") - }) - .catch(setError); + .then((raw) => { + localStorage.setItem(identityObjectKey, JSON.stringify(raw)); + navigate('/identity'); + }) + .catch(setError); }, [location.hash]); if (error) { diff --git a/examples/wallet/src/CreateIdentity.tsx b/examples/wallet/src/CreateIdentity.tsx index 277d9da72..78c59e094 100644 --- a/examples/wallet/src/CreateIdentity.tsx +++ b/examples/wallet/src/CreateIdentity.tsx @@ -144,7 +144,7 @@ export function CreateIdentity() { > Create identity - + {createButtonDisabled && (
Generating identity request. This can take a while.
)} diff --git a/examples/wallet/src/Identity.tsx b/examples/wallet/src/Identity.tsx index 411da6962..77cf0b313 100644 --- a/examples/wallet/src/Identity.tsx +++ b/examples/wallet/src/Identity.tsx @@ -1,7 +1,5 @@ import { AttributeList, IdentityObjectV1 } from '@concordium/web-sdk'; -import React, { useEffect, useMemo, useState } from 'react'; -import { useLocation } from 'react-router-dom'; -import { extractIdentityObjectUrl, fetchIdentity } from './util'; +import React, { useMemo, useState } from 'react'; import { CreateAccount } from './CreateAccount'; import { identityObjectKey } from './constants'; @@ -18,24 +16,20 @@ function DisplayIdentity({ attributes }: { attributes: AttributeList }) { } export function Identity() { - const location = useLocation(); const [missingIdentity, setMissingIdentity] = useState(false); - const identity = useMemo( - () => { - const raw = localStorage.getItem(identityObjectKey) - if (raw) { - return JSON.parse(raw) - } else { - setMissingIdentity(true); - } - }, - [] - ); + const identity = useMemo(() => { + const raw = localStorage.getItem(identityObjectKey); + if (raw) { + return JSON.parse(raw); + } else { + setMissingIdentity(true); + } + }, []); return ( <>

Your Concordium identity

- {missingIdentity && (

Missing identity in storage

)} + {missingIdentity &&

Missing identity in storage

} {identity && ( <> diff --git a/examples/wallet/src/Index.tsx b/examples/wallet/src/Index.tsx index 6e4103a0a..7e5e49754 100644 --- a/examples/wallet/src/Index.tsx +++ b/examples/wallet/src/Index.tsx @@ -26,7 +26,7 @@ const router = createBrowserRouter([ }, { path: '/confirm-identity', - element: + element: , }, { path: '/identity', @@ -34,7 +34,7 @@ const router = createBrowserRouter([ }, { path: '/recover', - element: + element: , }, { path: '/account/:accountAddress', diff --git a/examples/wallet/src/RecoverIdentity.tsx b/examples/wallet/src/RecoverIdentity.tsx index 32e43eeba..059a83804 100644 --- a/examples/wallet/src/RecoverIdentity.tsx +++ b/examples/wallet/src/RecoverIdentity.tsx @@ -2,8 +2,6 @@ import React, { useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { CryptographicParameters, - IdObjectRequestV1, - Versioned, IdentityRecoveryRequestInput, IdRecoveryRequest, } from '@concordium/web-sdk'; @@ -11,9 +9,7 @@ import { IdentityProviderWithMetadata } from './types'; import { getCryptographicParameters, getIdentityProviders, - getRedirectUri, sendIdentityRecoveryRequest, - sendIdentityRequest, } from './util'; import { mnemonicToSeedSync } from '@scure/bip39'; import { Buffer } from 'buffer/'; @@ -42,7 +38,7 @@ export function RecoverIdentity() { cryptographicParameters !== undefined && selectedIdentityProvider !== undefined; const seedPhrase = useMemo(() => localStorage.getItem(seedPhraseKey), []); - const [recoveryFailed, setRecoveryFailed] = useState(); + const [recoveryFailed, setRecoveryFailed] = useState(); useEffect(() => { getIdentityProviders().then((idps) => { @@ -80,26 +76,31 @@ export function RecoverIdentity() { ) => { try { const url = await sendIdentityRecoveryRequest( - e.data, - selectedIdentityProvider.metadata.recoveryStart - ); - worker.removeEventListener('message', listener); - const response = await fetch(url); + e.data, + selectedIdentityProvider.metadata.recoveryStart + ); + worker.removeEventListener('message', listener); + const response = await fetch(url); - if (response.ok) { - const identity = await response.json() - localStorage.setItem(identityObjectKey, JSON.stringify(identity.value)); - } + if (response.ok) { + const identity = await response.json(); + localStorage.setItem( + identityObjectKey, + JSON.stringify(identity.value) + ); + } - navigate('/identity'); + navigate('/identity'); } catch (e) { - setRecoveryFailed((e as any).message); + setRecoveryFailed((e as Error).message); } }); const identityRequestInput: IdentityRecoveryRequestInput = { net: network, - seedAsHex: Buffer.from(mnemonicToSeedSync(seedPhrase)).toString('hex'), + seedAsHex: Buffer.from(mnemonicToSeedSync(seedPhrase)).toString( + 'hex' + ), identityIndex: identityIndex, ipInfo: selectedIdentityProvider.ipInfo, globalContext: cryptographicParameters, @@ -110,11 +111,13 @@ export function RecoverIdentity() { if (recoveryFailed) { return ( -
-

Recovery failed

-

Error: {recoveryFailed}

- -
+
+

Recovery failed

+

Error: {recoveryFailed}

+ +
); } @@ -146,7 +149,9 @@ export function RecoverIdentity() { Recover identity {createButtonDisabled && ( -
Generating identity recovery request. This can take a while.
+
+ Generating identity recovery request. This can take a while. +
)} ); diff --git a/examples/wallet/src/recovery-worker.ts b/examples/wallet/src/recovery-worker.ts index 51b5e4aae..06b45c0a5 100644 --- a/examples/wallet/src/recovery-worker.ts +++ b/examples/wallet/src/recovery-worker.ts @@ -1,6 +1,6 @@ import { createIdentityRecoveryRequest, - IdentityRecoveryRequestInput + IdentityRecoveryRequestInput, } from '@concordium/web-sdk'; self.onmessage = (e: MessageEvent) => { diff --git a/examples/wallet/src/util.ts b/examples/wallet/src/util.ts index 10c5aa8b0..72c203bb0 100644 --- a/examples/wallet/src/util.ts +++ b/examples/wallet/src/util.ts @@ -307,7 +307,9 @@ export async function sendIdentityRecoveryRequest( recoveryRequest: IdRecoveryRequest, baseUrl: string ) { - const searchParams = new URLSearchParams({ state: JSON.stringify({idRecoveryRequest: recoveryRequest})}); + const searchParams = new URLSearchParams({ + state: JSON.stringify({ idRecoveryRequest: recoveryRequest }), + }); const url = `${baseUrl}?${searchParams.toString()}`; const response = await fetch(url); From de345de180231c6dc0f01596143f1aa7e3a87bbf Mon Sep 17 00:00:00 2001 From: Hjort Date: Tue, 16 Jan 2024 17:02:50 +0100 Subject: [PATCH 31/44] Address feedback + create page for choosing create or recover --- examples/wallet/src/ConfirmIdentity.tsx | 2 +- examples/wallet/src/CreateIdentity.tsx | 1 - examples/wallet/src/Identity.tsx | 6 +++--- examples/wallet/src/Index.tsx | 5 +++++ examples/wallet/src/NewIdentity.tsx | 17 +++++++++++++++++ examples/wallet/src/Setup.tsx | 2 +- 6 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 examples/wallet/src/NewIdentity.tsx diff --git a/examples/wallet/src/ConfirmIdentity.tsx b/examples/wallet/src/ConfirmIdentity.tsx index 66e3ffb61..3179539e0 100644 --- a/examples/wallet/src/ConfirmIdentity.tsx +++ b/examples/wallet/src/ConfirmIdentity.tsx @@ -29,7 +29,7 @@ export function ConfirmIdentity() { return ( <> -

Identity is not ready yet

+

Identity is not ready yet. Please wait.

); } diff --git a/examples/wallet/src/CreateIdentity.tsx b/examples/wallet/src/CreateIdentity.tsx index 78c59e094..dbaf0cc5d 100644 --- a/examples/wallet/src/CreateIdentity.tsx +++ b/examples/wallet/src/CreateIdentity.tsx @@ -144,7 +144,6 @@ export function CreateIdentity() { > Create identity - {createButtonDisabled && (
Generating identity request. This can take a while.
)} diff --git a/examples/wallet/src/Identity.tsx b/examples/wallet/src/Identity.tsx index 77cf0b313..ab5ff6af6 100644 --- a/examples/wallet/src/Identity.tsx +++ b/examples/wallet/src/Identity.tsx @@ -18,9 +18,9 @@ function DisplayIdentity({ attributes }: { attributes: AttributeList }) { export function Identity() { const [missingIdentity, setMissingIdentity] = useState(false); const identity = useMemo(() => { - const raw = localStorage.getItem(identityObjectKey); - if (raw) { - return JSON.parse(raw); + const identityObjectJson = localStorage.getItem(identityObjectKey); + if (identityObjectJson) { + return JSON.parse(identityObjectJson); } else { setMissingIdentity(true); } diff --git a/examples/wallet/src/Index.tsx b/examples/wallet/src/Index.tsx index 7e5e49754..b86db4c36 100644 --- a/examples/wallet/src/Index.tsx +++ b/examples/wallet/src/Index.tsx @@ -8,6 +8,7 @@ import { CreateIdentity } from './CreateIdentity'; import { SetupSeedPhrase } from './Setup'; import { RecoverIdentity } from './RecoverIdentity'; import { ConfirmIdentity } from './ConfirmIdentity'; +import { NewIdentity } from './NewIdentity'; const container = document.getElementById('root'); @@ -20,6 +21,10 @@ const router = createBrowserRouter([ path: '/', element: , }, + { + path: '/new', + element: , + }, { path: '/create', element: , diff --git a/examples/wallet/src/NewIdentity.tsx b/examples/wallet/src/NewIdentity.tsx new file mode 100644 index 000000000..6fe9133e2 --- /dev/null +++ b/examples/wallet/src/NewIdentity.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; + +export function NewIdentity() { + const navigate = useNavigate(); + return ( +
+

Your Concordium Identity

+ + +
+ ); +} diff --git a/examples/wallet/src/Setup.tsx b/examples/wallet/src/Setup.tsx index ef9b36216..3b1c3933d 100644 --- a/examples/wallet/src/Setup.tsx +++ b/examples/wallet/src/Setup.tsx @@ -21,7 +21,7 @@ export function SetupSeedPhrase() { try { localStorage.setItem(seedPhraseKey, seedPhraseWords); - navigate('/create'); + navigate('/new'); } catch { alert('An invalid seed phrase was provided'); return; From 9b6252038e93c6f735516a676c5e8c719f4787b8 Mon Sep 17 00:00:00 2001 From: Hjort Date: Tue, 16 Jan 2024 17:05:16 +0100 Subject: [PATCH 32/44] Fix naming in RecoverIdentity --- examples/wallet/src/RecoverIdentity.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/wallet/src/RecoverIdentity.tsx b/examples/wallet/src/RecoverIdentity.tsx index 059a83804..3312c281a 100644 --- a/examples/wallet/src/RecoverIdentity.tsx +++ b/examples/wallet/src/RecoverIdentity.tsx @@ -59,7 +59,7 @@ export function RecoverIdentity() { return null; } - async function createIdentity() { + async function recoverIdentity() { if (!dataLoaded || !seedPhrase) { return; } @@ -144,7 +144,7 @@ export function RecoverIdentity() { From adad80ea583d4eea0d0c47f823bf2ce53b5f8fdf Mon Sep 17 00:00:00 2001 From: Hjort Date: Mon, 22 Jan 2024 10:42:01 +0100 Subject: [PATCH 33/44] Example wallet, if account exists during recovery go to account page --- examples/wallet/src/RecoverIdentity.tsx | 15 +++++++++++++++ examples/wallet/src/util.ts | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/examples/wallet/src/RecoverIdentity.tsx b/examples/wallet/src/RecoverIdentity.tsx index 3312c281a..37bbd33e8 100644 --- a/examples/wallet/src/RecoverIdentity.tsx +++ b/examples/wallet/src/RecoverIdentity.tsx @@ -1,12 +1,17 @@ import React, { useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { + ConcordiumHdWallet, + CredentialRegistrationId, CryptographicParameters, + getAccountAddress, IdentityRecoveryRequestInput, IdRecoveryRequest, } from '@concordium/web-sdk'; import { IdentityProviderWithMetadata } from './types'; import { + client, + getCredentialId, getCryptographicParameters, getIdentityProviders, sendIdentityRecoveryRequest, @@ -88,6 +93,16 @@ export function RecoverIdentity() { identityObjectKey, JSON.stringify(identity.value) ); + + // Check if the account exists, in which case we go directly to the account page. + const credId = getCredentialId(seedPhrase, selectedIdentityProvider.ipInfo.ipIdentity,cryptographicParameters) + try { + const accountInfo = await client.getAccountInfo(CredentialRegistrationId.fromHexString(credId)); + navigate(`/account/${accountInfo.accountAddress.address}`); + return; + } catch { + // We assume that the account does not exist, so we continue to the identity page + } } navigate('/identity'); diff --git a/examples/wallet/src/util.ts b/examples/wallet/src/util.ts index 72c203bb0..ec30c12d0 100644 --- a/examples/wallet/src/util.ts +++ b/examples/wallet/src/util.ts @@ -19,6 +19,7 @@ import { serializeCredentialDeploymentPayload, signTransaction, IdRecoveryRequest, + CryptographicParameters, } from '@concordium/web-sdk'; import { IdentityProviderWithMetadata, @@ -89,6 +90,25 @@ export function getAccountSigningKey( .toString('hex'); } +/** + * Derives a credential (registration) id. + * + * For this example we only work with a single identity and a single account, therefore + * those indices are hardcoded to 0. In a production wallet any number of identities and + * accounts could be created. + */ +export function getCredentialId(seedPhrase: string, identityProviderIdentity: number, global: CryptographicParameters) { + const wallet = ConcordiumHdWallet.fromSeedPhrase(seedPhrase, network); + return wallet + .getCredentialId( + identityProviderIdentity, + identityIndex, + credNumber, + global + ) + .toString('hex'); +} + /** * Utility function for extracting the URL where the identity object can be fetched * when ready. From f75d872315395a03b615a39fbc67c906d6b04203 Mon Sep 17 00:00:00 2001 From: Hjort Date: Mon, 22 Jan 2024 11:24:14 +0100 Subject: [PATCH 34/44] Fix lint --- examples/wallet/src/RecoverIdentity.tsx | 16 +++++++++++----- examples/wallet/src/util.ts | 6 +++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/examples/wallet/src/RecoverIdentity.tsx b/examples/wallet/src/RecoverIdentity.tsx index 37bbd33e8..27a67d7c6 100644 --- a/examples/wallet/src/RecoverIdentity.tsx +++ b/examples/wallet/src/RecoverIdentity.tsx @@ -1,10 +1,8 @@ import React, { useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { - ConcordiumHdWallet, CredentialRegistrationId, CryptographicParameters, - getAccountAddress, IdentityRecoveryRequestInput, IdRecoveryRequest, } from '@concordium/web-sdk'; @@ -95,10 +93,18 @@ export function RecoverIdentity() { ); // Check if the account exists, in which case we go directly to the account page. - const credId = getCredentialId(seedPhrase, selectedIdentityProvider.ipInfo.ipIdentity,cryptographicParameters) + const credId = getCredentialId( + seedPhrase, + selectedIdentityProvider.ipInfo.ipIdentity, + cryptographicParameters + ); try { - const accountInfo = await client.getAccountInfo(CredentialRegistrationId.fromHexString(credId)); - navigate(`/account/${accountInfo.accountAddress.address}`); + const accountInfo = await client.getAccountInfo( + CredentialRegistrationId.fromHexString(credId) + ); + navigate( + `/account/${accountInfo.accountAddress.address}` + ); return; } catch { // We assume that the account does not exist, so we continue to the identity page diff --git a/examples/wallet/src/util.ts b/examples/wallet/src/util.ts index ec30c12d0..4d6b969e1 100644 --- a/examples/wallet/src/util.ts +++ b/examples/wallet/src/util.ts @@ -97,7 +97,11 @@ export function getAccountSigningKey( * those indices are hardcoded to 0. In a production wallet any number of identities and * accounts could be created. */ -export function getCredentialId(seedPhrase: string, identityProviderIdentity: number, global: CryptographicParameters) { +export function getCredentialId( + seedPhrase: string, + identityProviderIdentity: number, + global: CryptographicParameters +) { const wallet = ConcordiumHdWallet.fromSeedPhrase(seedPhrase, network); return wallet .getCredentialId( From 54572bda3a4b0c698d3d76cba812bb34f16b8f79 Mon Sep 17 00:00:00 2001 From: Hjort Date: Mon, 22 Jan 2024 11:34:58 +0100 Subject: [PATCH 35/44] Create recovery request with keys instead of seedPhrase --- examples/wallet/src/RecoverIdentity.tsx | 20 ++++++++++++-------- examples/wallet/src/recovery-worker.ts | 8 ++++---- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/examples/wallet/src/RecoverIdentity.tsx b/examples/wallet/src/RecoverIdentity.tsx index 27a67d7c6..c4252586a 100644 --- a/examples/wallet/src/RecoverIdentity.tsx +++ b/examples/wallet/src/RecoverIdentity.tsx @@ -1,9 +1,11 @@ import React, { useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { + ConcordiumHdWallet, CredentialRegistrationId, CryptographicParameters, IdentityRecoveryRequestInput, + IdentityRecoveryRequestWithKeysInput, IdRecoveryRequest, } from '@concordium/web-sdk'; import { IdentityProviderWithMetadata } from './types'; @@ -69,9 +71,11 @@ export function RecoverIdentity() { setCreateButtonDisabled(true); + const ipIdentity = selectedIdentityProvider.ipInfo.ipIdentity; + localStorage.setItem( selectedIdentityProviderKey, - selectedIdentityProvider.ipInfo.ipIdentity.toString() + ipIdentity.toString() ); const listener = (worker.onmessage = async ( @@ -95,7 +99,7 @@ export function RecoverIdentity() { // Check if the account exists, in which case we go directly to the account page. const credId = getCredentialId( seedPhrase, - selectedIdentityProvider.ipInfo.ipIdentity, + ipIdentity, cryptographicParameters ); try { @@ -117,12 +121,12 @@ export function RecoverIdentity() { } }); - const identityRequestInput: IdentityRecoveryRequestInput = { - net: network, - seedAsHex: Buffer.from(mnemonicToSeedSync(seedPhrase)).toString( - 'hex' - ), - identityIndex: identityIndex, + const wallet = ConcordiumHdWallet.fromSeedPhrase(seedPhrase, network); + const idCredSec = wallet + .getIdCredSec(ipIdentity, identityIndex) + .toString('hex'); + const identityRequestInput: IdentityRecoveryRequestWithKeysInput = { + idCredSec, ipInfo: selectedIdentityProvider.ipInfo, globalContext: cryptographicParameters, timestamp: Math.floor(Date.now() / 1000), diff --git a/examples/wallet/src/recovery-worker.ts b/examples/wallet/src/recovery-worker.ts index 06b45c0a5..3d501c3f5 100644 --- a/examples/wallet/src/recovery-worker.ts +++ b/examples/wallet/src/recovery-worker.ts @@ -1,9 +1,9 @@ import { - createIdentityRecoveryRequest, - IdentityRecoveryRequestInput, + createIdentityRecoveryRequestWithKeys, + IdentityRecoveryRequestWithKeysInput, } from '@concordium/web-sdk'; -self.onmessage = (e: MessageEvent) => { - const recoveryRequest = createIdentityRecoveryRequest(e.data); +self.onmessage = (e: MessageEvent) => { + const recoveryRequest = createIdentityRecoveryRequestWithKeys(e.data); self.postMessage(recoveryRequest); }; From 98c64d7fef93f4cac9a5f99bf270ede3262bd716 Mon Sep 17 00:00:00 2001 From: Hjort Date: Mon, 22 Jan 2024 11:36:09 +0100 Subject: [PATCH 36/44] Fix lint --- examples/wallet/src/RecoverIdentity.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/wallet/src/RecoverIdentity.tsx b/examples/wallet/src/RecoverIdentity.tsx index c4252586a..5dd874af3 100644 --- a/examples/wallet/src/RecoverIdentity.tsx +++ b/examples/wallet/src/RecoverIdentity.tsx @@ -4,7 +4,6 @@ import { ConcordiumHdWallet, CredentialRegistrationId, CryptographicParameters, - IdentityRecoveryRequestInput, IdentityRecoveryRequestWithKeysInput, IdRecoveryRequest, } from '@concordium/web-sdk'; @@ -16,8 +15,6 @@ import { getIdentityProviders, sendIdentityRecoveryRequest, } from './util'; -import { mnemonicToSeedSync } from '@scure/bip39'; -import { Buffer } from 'buffer/'; import { identityIndex, identityObjectKey, From 50672412f458530e2c39ab217ab8151cd4a8fe34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Holm=20Gj=C3=B8rup?= Date: Mon, 22 Jan 2024 16:28:03 +0100 Subject: [PATCH 37/44] Add web-wallet functions to ccd-js-gen unit tests --- packages/ccd-js-gen/test/generate-wCCD.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ccd-js-gen/test/generate-wCCD.test.ts b/packages/ccd-js-gen/test/generate-wCCD.test.ts index 6c8f27baa..24b099c54 100644 --- a/packages/ccd-js-gen/test/generate-wCCD.test.ts +++ b/packages/ccd-js-gen/test/generate-wCCD.test.ts @@ -91,8 +91,10 @@ describe('Generated contract client file', () => { const module = require(`${outContractFile}.js`); expect(module.parseEvent).toBeDefined(); expect(module.createUnwrapParameter).toBeDefined(); + expect(module.createUnwrapParameterWebWallet).toBeDefined(); expect(module.parseErrorMessageUnwrap).toBeDefined(); expect(module.createTransferParameter).toBeDefined(); + expect(module.createTransferParameterWebWallet).toBeDefined(); expect(module.parseErrorMessageTransfer).toBeDefined(); }); }); From 00397489a5fb9e9dfd1b7ff56852060e47045682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Holm=20Gj=C3=B8rup?= Date: Tue, 9 Jan 2024 13:39:28 +0100 Subject: [PATCH 38/44] Improve performance and CLI progress reporting --- packages/ccd-js-gen/CHANGELOG.md | 1 + packages/ccd-js-gen/src/cli.ts | 5 +- packages/ccd-js-gen/src/lib.ts | 1256 ++++++++++++++++-------------- 3 files changed, 664 insertions(+), 598 deletions(-) diff --git a/packages/ccd-js-gen/CHANGELOG.md b/packages/ccd-js-gen/CHANGELOG.md index df655600e..e8742936d 100644 --- a/packages/ccd-js-gen/CHANGELOG.md +++ b/packages/ccd-js-gen/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Improve information when reporting progress during code-generation. - Generate `createParameterWebWallet` and `createParameterWebWallet` functions for constructing the smart contract parameters in the format used by the Concordium Web-Wallet. - Minor breaking change: `createParameter` function for contract entries and `createParameter` function in module are no longer generated when parameter schema type is `Unit`. These functions are mainly used internally, but are still exposed. - Fix bug for generating JSON for schema type `ULeb128` and `ILeb128`. diff --git a/packages/ccd-js-gen/src/cli.ts b/packages/ccd-js-gen/src/cli.ts index 19f821a74..c84c8654b 100644 --- a/packages/ccd-js-gen/src/cli.ts +++ b/packages/ccd-js-gen/src/cli.ts @@ -68,7 +68,10 @@ export async function main(): Promise { onProgress(update) { if (update.type === 'Progress') { console.log( - `[${update.doneItems}/${update.totalItems}] ${update.spentTime}ms` + `[${update.doneItems}/${update.totalItems}] ${update.spentTime}ms `.padEnd( + 15, + ' ' + ) + update.description ); } }, diff --git a/packages/ccd-js-gen/src/lib.ts b/packages/ccd-js-gen/src/lib.ts index 26022782d..250262090 100644 --- a/packages/ccd-js-gen/src/lib.ts +++ b/packages/ccd-js-gen/src/lib.ts @@ -41,6 +41,8 @@ export type Progress = { doneItems: number; /** Number of milliseconds spent on the previous item. */ spentTime: number; + /** Description of the item being completed. */ + description: string; }; /** @@ -85,12 +87,27 @@ export async function generateContractClients( outDirPath: string, options: GenerateContractClientsOptions = {} ): Promise { + const notifier = new Notifier(options.onProgress); const outputOption = options.output ?? 'Everything'; + const outputTypeScript = + outputOption === 'Everything' || outputOption === 'TypeScript'; + const outputJavaScript = + outputOption === 'Everything' || + outputOption === 'JavaScript' || + outputOption === 'TypedJavaScript'; + const outputDeclarations = + outputOption === 'Everything' || outputOption === 'TypedJavaScript'; + + if (outputTypeScript) { + notifier.todo(1); + } + if (outputJavaScript) { + notifier.todo(1); + } const compilerOptions: tsm.CompilerOptions = { outDir: outDirPath, - declaration: - outputOption === 'Everything' || outputOption === 'TypedJavaScript', + declaration: outputDeclarations, }; const project = new tsm.Project({ compilerOptions }); @@ -100,18 +117,52 @@ export async function generateContractClients( outDirPath, moduleSource, options.tsNocheck ?? false, - options.onProgress + notifier ); - if (outputOption === 'Everything' || outputOption === 'TypeScript') { + if (outputTypeScript) { await project.save(); + notifier.done('Saving generated TypeScript files.'); } - if ( - outputOption === 'Everything' || - outputOption === 'JavaScript' || - outputOption === 'TypedJavaScript' - ) { + if (outputJavaScript) { await project.emit(); + notifier.done('Emitting JavaScript files.'); + } +} + +/** Wrapper around the NotifyProgress callback for tracking and reporting progress state. */ +class Notifier { + constructor(private notifyProgress?: NotifyProgress) {} + /** Total number of items to do. */ + private totalItems = 0; + /** Total number of items done so far. */ + private doneItems = 0; + /** Timestamp for last reporting or creation, + * this is used to measure the time spent between reporting items done. */ + private startTime = Date.now(); + /** + * Increment the total number of items to do. + * @param {number} todoItems The amount to increment it with. + */ + todo(todoItems: number) { + this.totalItems += todoItems; + } + /** + * Mark an item as done, note the time and report progress. + * This restarts the timer for the next task. + * @param {string} description A description of the item which is done. + */ + done(description: string) { + const now = Date.now(); + this.doneItems += 1; + this.notifyProgress?.({ + type: 'Progress', + totalItems: this.totalItems, + doneItems: this.doneItems, + spentTime: now - this.startTime, + description, + }); + this.startTime = now; } } @@ -122,7 +173,7 @@ export async function generateContractClients( * @param {string} outDirPath The directory to use for outputting files. * @param {SDK.VersionedModuleSource} moduleSource The source of the smart contract module. * @param {boolean} tsNocheck When `true` generate `// @ts-nocheck` annotations in each file. - * @param {NotifyProgress} [notifyProgress] Callback to report progress. + * @param {Notifier} notifier Callback to report progress. */ async function generateCode( project: tsm.Project, @@ -130,7 +181,7 @@ async function generateCode( outDirPath: string, moduleSource: SDK.VersionedModuleSource, tsNocheck: boolean, - notifyProgress?: NotifyProgress + notifier: Notifier ) { const [moduleInterface, moduleRef, rawModuleSchema] = await Promise.all([ SDK.parseModuleInterface(moduleSource), @@ -138,12 +189,14 @@ async function generateCode( SDK.getEmbeddedModuleSchema(moduleSource), ]); - let totalItems = 0; + notifier.todo(2); // Parsing and adding base statements to module. for (const contracts of moduleInterface.values()) { - totalItems += contracts.entrypointNames.size; + // Add initialize function to module and base statements to contract source files. + notifier.todo(2); + // Adding entrypoint functions to contract. + notifier.todo(contracts.entrypointNames.size); } - let doneItems = 0; - notifyProgress?.({ type: 'Progress', totalItems, doneItems, spentTime: 0 }); + notifier.done('Parse smart contract module.'); const moduleSchema = rawModuleSchema === null @@ -162,27 +215,33 @@ async function generateCode( const moduleClientType = `${toPascalCase(outModuleName)}Module`; const internalModuleClientId = 'internalModuleClient'; - generateModuleBaseCode( - moduleSourceFile, - moduleRef, - moduleClientId, - moduleClientType, - internalModuleClientId, - tsNocheck + moduleSourceFile.addStatements( + generateModuleBaseStatements( + moduleRef, + moduleClientId, + moduleClientType, + internalModuleClientId, + tsNocheck + ) ); + notifier.done('Generate base statements in module.'); for (const contract of moduleInterface.values()) { const contractSchema: SDK.SchemaContractV3 | undefined = moduleSchema?.module.contracts.get(contract.contractName); - generactionModuleContractCode( - moduleSourceFile, - contract.contractName, - moduleClientId, - moduleClientType, - internalModuleClientId, - moduleRef, - contractSchema + moduleSourceFile.addStatements( + generateModuleContractStatements( + contract.contractName, + moduleClientId, + moduleClientType, + internalModuleClientId, + moduleRef, + contractSchema + ) + ); + notifier.done( + `Generate initialize statements for '${contract.contractName}' in module.` ); const contractOutputFilePath = path.format({ @@ -205,68 +264,68 @@ async function generateCode( )}Contract`; const contractClientId = 'contractClient'; - generateContractBaseCode( - contractSourceFile, - contract.contractName, - contractClientId, - contractClientType, - moduleRef, - tsNocheck, - contractSchema + contractSourceFile.addStatements( + generateContractBaseStatements( + contract.contractName, + contractClientId, + contractClientType, + moduleRef, + tsNocheck, + contractSchema + ) + ); + notifier.done( + `Generate base statements for '${contract.contractName}'.` ); for (const entrypointName of contract.entrypointNames) { - const startTime = Date.now(); const entrypointSchema = contractSchema?.receive.get(entrypointName); - generateContractEntrypointCode( - contractSourceFile, - contract.contractName, - contractClientId, - contractClientType, - entrypointName, - entrypointSchema + contractSourceFile.addStatements( + generateContractEntrypointStatements( + contract.contractName, + contractClientId, + contractClientType, + entrypointName, + entrypointSchema + ) + ); + notifier.done( + `Generate statements for '${contract.contractName}.${entrypointName}'.` ); - const spentTime = Date.now() - startTime; - doneItems++; - notifyProgress?.({ - type: 'Progress', - totalItems, - doneItems, - spentTime, - }); } } } /** * Generate code for a smart contract module client. - * @param moduleSourceFile The sourcefile of the module. * @param moduleRef The module reference. * @param moduleClientId The identifier to use for the module client. * @param moduleClientType The identifier to use for the type of the module client. + * @param tsNocheck When `true` generate `// @ts-nocheck` annotations in each file. * @param internalModuleClientId The identifier to use for referencing the internal module client. */ -function generateModuleBaseCode( - moduleSourceFile: tsm.SourceFile, +function generateModuleBaseStatements( moduleRef: SDK.ModuleReference.Type, moduleClientId: string, moduleClientType: string, internalModuleClientId: string, tsNocheck: boolean -) { +): Array { + const statements: Array = []; const moduleRefId = 'moduleReference'; + if (tsNocheck) { + statements.push('// @ts-nocheck'); + } - moduleSourceFile.addStatements([ - tsNocheck ? '// @ts-nocheck' : '', - { - kind: tsm.StructureKind.ImportDeclaration, - namespaceImport: 'SDK', - moduleSpecifier: '@concordium/web-sdk', - }, - ]); + statements.push({ + kind: tsm.StructureKind.ImportDeclaration, + namespaceImport: 'SDK', + moduleSpecifier: '@concordium/web-sdk', + }); - moduleSourceFile.addVariableStatement({ + statements.push({ + kind: tsm.StructureKind.VariableStatement, isExported: true, declarationKind: tsm.VariableDeclarationKind.Const, docs: [ @@ -281,7 +340,8 @@ function generateModuleBaseCode( ], }); - const moduleClassDecl = moduleSourceFile.addClass({ + statements.push({ + kind: tsm.StructureKind.Class, docs: [ `Client for an on-chain smart contract module with module reference '${moduleRef.moduleRef}', can be used for instantiating new smart contract instances.`, ], @@ -303,25 +363,24 @@ function generateModuleBaseCode( type: 'SDK.ModuleClient.Type', }, ], + ctors: [ + { + docs: [ + 'Constructor is only ment to be used internally in this module. Use functions such as `create` or `createUnchecked` for construction.', + ], + parameters: [ + { + name: internalModuleClientId, + type: 'SDK.ModuleClient.Type', + }, + ], + statements: `this.${internalModuleClientId} = ${internalModuleClientId};`, + }, + ], }); - moduleClassDecl - .addConstructor({ - docs: [ - 'Constructor is only ment to be used internally in this module. Use functions such as `create` or `createUnchecked` for construction.', - ], - parameters: [ - { - name: internalModuleClientId, - type: 'SDK.ModuleClient.Type', - }, - ], - }) - .setBodyText( - `this.${internalModuleClientId} = ${internalModuleClientId};` - ); - - moduleSourceFile.addTypeAlias({ + statements.push({ + kind: tsm.StructureKind.TypeAlias, docs: [ `Client for an on-chain smart contract module with module reference '${moduleRef.moduleRef}', can be used for instantiating new smart contract instances.`, ], @@ -331,130 +390,123 @@ function generateModuleBaseCode( }); const grpcClientId = 'grpcClient'; - moduleSourceFile - .addFunction({ - docs: [ - [ - `Construct a ${moduleClientType} client for interacting with a smart contract module on chain.`, - 'This function ensures the smart contract module is deployed on chain.', - `@param {SDK.ConcordiumGRPCClient} ${grpcClientId} - The concordium node client to use.`, - '@throws If failing to communicate with the concordium node or if the module reference is not present on chain.', - `@returns {${moduleClientType}} A module client ensured to be deployed on chain.`, - ].join('\n'), - ], - isExported: true, - isAsync: true, - name: 'create', - parameters: [ - { - name: grpcClientId, - type: 'SDK.ConcordiumGRPCClient', - }, - ], - returnType: `Promise<${moduleClientType}>`, - }) - .setBodyText( + statements.push({ + kind: tsm.StructureKind.Function, + docs: [ [ - `const moduleClient = await SDK.ModuleClient.create(${grpcClientId}, ${moduleRefId});`, - `return new ${moduleClientType}(moduleClient);`, - ].join('\n') - ); - moduleSourceFile - .addFunction({ - docs: [ - [ - `Construct a ${moduleClientType} client for interacting with a smart contract module on chain.`, - 'It is up to the caller to ensure the module is deployed on chain.', - `@param {SDK.ConcordiumGRPCClient} ${grpcClientId} - The concordium node client to use.`, - `@returns {${moduleClientType}}`, - ].join('\n'), - ], - isExported: true, - name: 'createUnchecked', - parameters: [ - { - name: grpcClientId, - type: 'SDK.ConcordiumGRPCClient', - }, - ], - returnType: `${moduleClientType}`, - }) - .setBodyText( + `Construct a ${moduleClientType} client for interacting with a smart contract module on chain.`, + 'This function ensures the smart contract module is deployed on chain.', + `@param {SDK.ConcordiumGRPCClient} ${grpcClientId} - The concordium node client to use.`, + '@throws If failing to communicate with the concordium node or if the module reference is not present on chain.', + `@returns {${moduleClientType}} A module client ensured to be deployed on chain.`, + ].join('\n'), + ], + isExported: true, + isAsync: true, + name: 'create', + parameters: [ + { + name: grpcClientId, + type: 'SDK.ConcordiumGRPCClient', + }, + ], + returnType: `Promise<${moduleClientType}>`, + statements: [ + `const moduleClient = await SDK.ModuleClient.create(${grpcClientId}, ${moduleRefId});`, + `return new ${moduleClientType}(moduleClient);`, + ], + }); + statements.push({ + kind: tsm.StructureKind.Function, + docs: [ [ - `const moduleClient = SDK.ModuleClient.createUnchecked(${grpcClientId}, ${moduleRefId});`, - `return new ${moduleClientType}(moduleClient);`, - ].join('\n') - ); + `Construct a ${moduleClientType} client for interacting with a smart contract module on chain.`, + 'It is up to the caller to ensure the module is deployed on chain.', + `@param {SDK.ConcordiumGRPCClient} ${grpcClientId} - The concordium node client to use.`, + `@returns {${moduleClientType}}`, + ].join('\n'), + ], + isExported: true, + name: 'createUnchecked', + parameters: [ + { + name: grpcClientId, + type: 'SDK.ConcordiumGRPCClient', + }, + ], + returnType: `${moduleClientType}`, + statements: [ + `const moduleClient = SDK.ModuleClient.createUnchecked(${grpcClientId}, ${moduleRefId});`, + `return new ${moduleClientType}(moduleClient);`, + ], + }); - moduleSourceFile - .addFunction({ - docs: [ - [ - `Construct a ${moduleClientType} client for interacting with a smart contract module on chain.`, - 'This function ensures the smart contract module is deployed on chain.', - `@param {${moduleClientType}} ${moduleClientId} - The client of the on-chain smart contract module with referecence '${moduleRef.moduleRef}'.`, - '@throws If failing to communicate with the concordium node or if the module reference is not present on chain.', - `@returns {${moduleClientType}} A module client ensured to be deployed on chain.`, - ].join('\n'), - ], - isExported: true, - name: 'checkOnChain', - parameters: [ - { - name: moduleClientId, - type: moduleClientType, - }, - ], - returnType: 'Promise', - }) - .setBodyText( - `return SDK.ModuleClient.checkOnChain(${moduleClientId}.${internalModuleClientId});` - ); + statements.push({ + kind: tsm.StructureKind.Function, + docs: [ + [ + `Construct a ${moduleClientType} client for interacting with a smart contract module on chain.`, + 'This function ensures the smart contract module is deployed on chain.', + `@param {${moduleClientType}} ${moduleClientId} - The client of the on-chain smart contract module with referecence '${moduleRef.moduleRef}'.`, + '@throws If failing to communicate with the concordium node or if the module reference is not present on chain.', + `@returns {${moduleClientType}} A module client ensured to be deployed on chain.`, + ].join('\n'), + ], + isExported: true, + name: 'checkOnChain', + parameters: [ + { + name: moduleClientId, + type: moduleClientType, + }, + ], + returnType: 'Promise', + statements: `return SDK.ModuleClient.checkOnChain(${moduleClientId}.${internalModuleClientId});`, + }); - moduleSourceFile - .addFunction({ - docs: [ - [ - 'Get the module source of the deployed smart contract module.', - `@param {${moduleClientType}} ${moduleClientId} - The client of the on-chain smart contract module with referecence '${moduleRef.moduleRef}'.`, - '@throws {SDK.RpcError} If failing to communicate with the concordium node or module not found.', - '@returns {SDK.VersionedModuleSource} Module source of the deployed smart contract module.', - ].join('\n'), - ], - isExported: true, - name: 'getModuleSource', - parameters: [ - { - name: moduleClientId, - type: moduleClientType, - }, - ], - returnType: 'Promise', - }) - .setBodyText( - `return SDK.ModuleClient.getModuleSource(${moduleClientId}.${internalModuleClientId});` - ); + statements.push({ + kind: tsm.StructureKind.Function, + docs: [ + [ + 'Get the module source of the deployed smart contract module.', + `@param {${moduleClientType}} ${moduleClientId} - The client of the on-chain smart contract module with referecence '${moduleRef.moduleRef}'.`, + '@throws {SDK.RpcError} If failing to communicate with the concordium node or module not found.', + '@returns {SDK.VersionedModuleSource} Module source of the deployed smart contract module.', + ].join('\n'), + ], + isExported: true, + name: 'getModuleSource', + parameters: [ + { + name: moduleClientId, + type: moduleClientType, + }, + ], + returnType: 'Promise', + statements: `return SDK.ModuleClient.getModuleSource(${moduleClientId}.${internalModuleClientId});`, + }); + return statements; } /** * Generate code in the module client specific to each contract in the module. - * @param {tsm.SourceFile} moduleSourceFile The sourcefile of the module client. * @param {string} contractName The name of the contract. * @param {string} moduleClientId The identifier for the module client. * @param {string} moduleClientType The identifier for the type of the module client. * @param {string} internalModuleClientId The identifier for the internal module client. * @param {SDK.ModuleReference.Type} moduleRef The reference of the module. * @param {SDK.SchemaContractV3} [contractSchema] The contract schema. + * @returns {tsm.StatementStructures[]} List of statements to be included in the module source file. */ -function generactionModuleContractCode( - moduleSourceFile: tsm.SourceFile, +function generateModuleContractStatements( contractName: string, moduleClientId: string, moduleClientType: string, internalModuleClientId: string, moduleRef: SDK.ModuleReference.Type, contractSchema?: SDK.SchemaContractV3 -) { +): tsm.StatementStructures[] { + const statements: tsm.StatementStructures[] = []; const transactionMetadataId = 'transactionMetadata'; const parameterId = 'parameter'; const signerId = 'signer'; @@ -478,7 +530,8 @@ function generactionModuleContractCode( const initParameterSchemaType = contractSchema?.init?.parameter; if (initParameterSchemaType !== undefined) { - moduleSourceFile.addVariableStatement({ + statements.push({ + kind: tsm.StructureKind.VariableStatement, docs: [ `Base64 encoding of the parameter schema type used when instantiating a new '${contractName}' smart contract instance.`, ], @@ -492,9 +545,9 @@ function generactionModuleContractCode( }, ], }); - const typeAndMapper = schemaAsNativeType(initParameterSchemaType); - moduleSourceFile.addTypeAlias({ + statements.push({ + kind: tsm.StructureKind.TypeAlias, docs: [ `Parameter JSON type needed by the schema when instantiating a new '${contractName}' smart contract instance.`, ], @@ -503,7 +556,8 @@ function generactionModuleContractCode( }); if (initParameterSchemaType.type !== 'Unit') { - moduleSourceFile.addTypeAlias({ + statements.push({ + kind: tsm.StructureKind.TypeAlias, docs: [ `Parameter type transaction for instantiating a new '${contractName}' smart contract instance.`, ], @@ -513,7 +567,8 @@ function generactionModuleContractCode( }); const mappedParameter = typeAndMapper.nativeToJson(parameterId); - moduleSourceFile.addFunction({ + statements.push({ + kind: tsm.StructureKind.Function, docs: [ [ `Construct schema JSON representation used in transactions for instantiating a new '${contractName}' smart contract instance.`, @@ -534,7 +589,8 @@ function generactionModuleContractCode( `return ${mappedParameter.id};`, ], }); - moduleSourceFile.addFunction({ + statements.push({ + kind: tsm.StructureKind.Function, docs: [ [ `Construct Parameter type used in transactions for instantiating a new '${contractName}' smart contract instance.`, @@ -556,7 +612,8 @@ function generactionModuleContractCode( ], }); - moduleSourceFile.addFunction({ + statements.push({ + kind: tsm.StructureKind.Function, docs: [ [ `Construct WebWallet parameter type used in transactions for instantiating a new '${contractName}' smart contract instance.`, @@ -584,7 +641,8 @@ function generactionModuleContractCode( }); } } else { - moduleSourceFile.addTypeAlias({ + statements.push({ + kind: tsm.StructureKind.TypeAlias, docs: [ `Parameter type transaction for instantiating a new '${contractName}' smart contract instance.`, ], @@ -596,84 +654,85 @@ function generactionModuleContractCode( const initTakesNoParameter = initParameterSchemaType?.type === 'Unit'; - moduleSourceFile - .addFunction({ - docs: [ - [ - `Send transaction for instantiating a new '${contractName}' smart contract instance.`, - `@param {${moduleClientType}} ${moduleClientId} - The client of the on-chain smart contract module with referecence '${moduleRef.moduleRef}'.`, - `@param {SDK.ContractTransactionMetadata} ${transactionMetadataId} - Metadata related to constructing a transaction for a smart contract module.`, - ...(initTakesNoParameter - ? [] - : [ - `@param {${initParameterTypeId}} ${parameterId} - Parameter to provide as part of the transaction for the instantiation of a new smart contract contract.`, - ]), - `@param {SDK.AccountSigner} ${signerId} - The signer of the update contract transaction.`, - '@throws If failing to communicate with the concordium node.', - '@returns {SDK.TransactionHash.Type}', - ].join('\n'), - ], - isExported: true, - name: `instantiate${toPascalCase(contractName)}`, - parameters: [ - { - name: moduleClientId, - type: moduleClientType, - }, - { - name: transactionMetadataId, - type: 'SDK.ContractTransactionMetadata', - }, + statements.push({ + kind: tsm.StructureKind.Function, + docs: [ + [ + `Send transaction for instantiating a new '${contractName}' smart contract instance.`, + `@param {${moduleClientType}} ${moduleClientId} - The client of the on-chain smart contract module with referecence '${moduleRef.moduleRef}'.`, + `@param {SDK.ContractTransactionMetadata} ${transactionMetadataId} - Metadata related to constructing a transaction for a smart contract module.`, ...(initTakesNoParameter ? [] : [ - { - name: parameterId, - type: initParameterTypeId, - }, + `@param {${initParameterTypeId}} ${parameterId} - Parameter to provide as part of the transaction for the instantiation of a new smart contract contract.`, ]), - { - name: signerId, - type: 'SDK.AccountSigner', - }, - ], - returnType: 'Promise', - }) - .setBodyText( - [ - 'return SDK.ModuleClient.createAndSendInitTransaction(', - ` ${moduleClientId}.${internalModuleClientId},`, - ` SDK.ContractName.fromStringUnchecked('${contractName}'),`, - ` ${transactionMetadataId},`, - ...(initParameterSchemaType === undefined - ? [` ${parameterId},`] - : initParameterSchemaType.type === 'Unit' - ? [] - : [` ${createInitParameterFnId}(${parameterId}),`]), - ` ${signerId}`, - ');', - ].join('\n') - ); + `@param {SDK.AccountSigner} ${signerId} - The signer of the update contract transaction.`, + '@throws If failing to communicate with the concordium node.', + '@returns {SDK.TransactionHash.Type}', + ].join('\n'), + ], + isExported: true, + name: `instantiate${toPascalCase(contractName)}`, + parameters: [ + { + name: moduleClientId, + type: moduleClientType, + }, + { + name: transactionMetadataId, + type: 'SDK.ContractTransactionMetadata', + }, + ...(initTakesNoParameter + ? [] + : [ + { + name: parameterId, + type: initParameterTypeId, + }, + ]), + { + name: signerId, + type: 'SDK.AccountSigner', + }, + ], + returnType: 'Promise', + statements: [ + 'return SDK.ModuleClient.createAndSendInitTransaction(', + ` ${moduleClientId}.${internalModuleClientId},`, + ` SDK.ContractName.fromStringUnchecked('${contractName}'),`, + ` ${transactionMetadataId},`, + ...(initParameterSchemaType === undefined + ? [` ${parameterId},`] + : initParameterSchemaType.type === 'Unit' + ? [] + : [` ${createInitParameterFnId}(${parameterId}),`]), + ` ${signerId}`, + ');', + ], + }); + + return statements; } /** * Generate code for a smart contract instance client. - * @param {tsm.SourceFile} contractSourceFile The sourcefile of the contract client. * @param {string} contractName The name of the smart contract. * @param {string} contractClientId The identifier to use for the contract client. * @param {string} contractClientType The identifier to use for the type of the contract client. * @param {SDK.ModuleReference.Type} moduleRef The module reference. + * @param {boolean} tsNocheck When `true` generate `// @ts-nocheck` annotations in each file. * @param {SDK.SchemaContractV3} [contractSchema] The contract schema to use in the client. + * @returns {Array} A list of statements to be included in the contract source file. */ -function generateContractBaseCode( - contractSourceFile: tsm.SourceFile, +function generateContractBaseStatements( contractName: string, contractClientId: string, contractClientType: string, moduleRef: SDK.ModuleReference.Type, tsNocheck: boolean, contractSchema?: SDK.SchemaContractV3 -) { +): Array { + const statements: Array = []; const moduleRefId = 'moduleReference'; const grpcClientId = 'grpcClient'; const contractNameId = 'contractName'; @@ -681,16 +740,17 @@ function generateContractBaseCode( const contractAddressId = 'contractAddress'; const blockHashId = 'blockHash'; - contractSourceFile.addStatements([ - tsNocheck ? '// @ts-nocheck' : '', - { - kind: tsm.StructureKind.ImportDeclaration, - namespaceImport: 'SDK', - moduleSpecifier: '@concordium/web-sdk', - }, - ]); + if (tsNocheck) { + statements.push('// @ts-nocheck'); + } + statements.push({ + kind: tsm.StructureKind.ImportDeclaration, + namespaceImport: 'SDK', + moduleSpecifier: '@concordium/web-sdk', + }); - contractSourceFile.addVariableStatement({ + statements.push({ + kind: tsm.StructureKind.VariableStatement, docs: [ 'The reference of the smart contract module supported by the provided client.', ], @@ -705,7 +765,8 @@ function generateContractBaseCode( ], }); - contractSourceFile.addVariableStatement({ + statements.push({ + kind: tsm.StructureKind.VariableStatement, docs: ['Name of the smart contract supported by this client.'], isExported: true, declarationKind: tsm.VariableDeclarationKind.Const, @@ -718,7 +779,8 @@ function generateContractBaseCode( ], }); - const contractClassDecl = contractSourceFile.addClass({ + statements.push({ + kind: tsm.StructureKind.Class, docs: ['Smart contract client for a contract instance on chain.'], name: contractClientType, properties: [ @@ -753,195 +815,193 @@ function generateContractBaseCode( type: 'SDK.Contract', }, ], + ctors: [ + { + kind: tsm.StructureKind.Constructor, + parameters: [ + { name: grpcClientId, type: 'SDK.ConcordiumGRPCClient' }, + { + name: contractAddressId, + type: 'SDK.ContractAddress.Type', + }, + { name: genericContractId, type: 'SDK.Contract' }, + ], + statements: [ + grpcClientId, + contractAddressId, + genericContractId, + ].map((name) => `this.${name} = ${name};`), + }, + ], }); - contractClassDecl - .addConstructor({ - parameters: [ - { name: grpcClientId, type: 'SDK.ConcordiumGRPCClient' }, - { - name: contractAddressId, - type: 'SDK.ContractAddress.Type', - }, - { name: genericContractId, type: 'SDK.Contract' }, - ], - }) - .setBodyText( - [grpcClientId, contractAddressId, genericContractId] - .map((name) => `this.${name} = ${name};`) - .join('\n') - ); - - contractSourceFile.addTypeAlias({ + statements.push({ + kind: tsm.StructureKind.TypeAlias, docs: ['Smart contract client for a contract instance on chain.'], name: 'Type', isExported: true, type: contractClientType, }); - contractSourceFile - .addFunction({ - docs: [ - [ - `Construct an instance of \`${contractClientType}\` for interacting with a '${contractName}' contract on chain.`, - 'Checking the information instance on chain.', - `@param {SDK.ConcordiumGRPCClient} ${grpcClientId} - The client used for contract invocations and updates.`, - `@param {SDK.ContractAddress.Type} ${contractAddressId} - Address of the contract instance.`, - `@param {SDK.BlockHash.Type} [${blockHashId}] - Hash of the block to check the information at. When not provided the last finalized block is used.`, - '@throws If failing to communicate with the concordium node or if any of the checks fails.', - `@returns {${contractClientType}}`, - ].join('\n'), - ], - isExported: true, - isAsync: true, - name: 'create', - parameters: [ - { - name: grpcClientId, - type: 'SDK.ConcordiumGRPCClient', - }, - { - name: contractAddressId, - type: 'SDK.ContractAddress.Type', - }, - { - name: blockHashId, - hasQuestionToken: true, - type: 'SDK.BlockHash.Type', - }, - ], - returnType: `Promise<${contractClientType}>`, - }) - .setBodyText( + statements.push({ + kind: tsm.StructureKind.Function, + docs: [ [ - `const ${genericContractId} = new SDK.Contract(${grpcClientId}, ${contractAddressId}, ${contractNameId});`, - `await ${genericContractId}.checkOnChain({ moduleReference: ${moduleRefId}, blockHash: ${blockHashId} });`, - `return new ${contractClientType}(`, - ` ${grpcClientId},`, - ` ${contractAddressId},`, - ` ${genericContractId}`, - ');', - ].join('\n') - ); + `Construct an instance of \`${contractClientType}\` for interacting with a '${contractName}' contract on chain.`, + 'Checking the information instance on chain.', + `@param {SDK.ConcordiumGRPCClient} ${grpcClientId} - The client used for contract invocations and updates.`, + `@param {SDK.ContractAddress.Type} ${contractAddressId} - Address of the contract instance.`, + `@param {SDK.BlockHash.Type} [${blockHashId}] - Hash of the block to check the information at. When not provided the last finalized block is used.`, + '@throws If failing to communicate with the concordium node or if any of the checks fails.', + `@returns {${contractClientType}}`, + ].join('\n'), + ], + isExported: true, + isAsync: true, + name: 'create', + parameters: [ + { + name: grpcClientId, + type: 'SDK.ConcordiumGRPCClient', + }, + { + name: contractAddressId, + type: 'SDK.ContractAddress.Type', + }, + { + name: blockHashId, + hasQuestionToken: true, + type: 'SDK.BlockHash.Type', + }, + ], + returnType: `Promise<${contractClientType}>`, + statements: [ + `const ${genericContractId} = new SDK.Contract(${grpcClientId}, ${contractAddressId}, ${contractNameId});`, + `await ${genericContractId}.checkOnChain({ moduleReference: ${moduleRefId}, blockHash: ${blockHashId} });`, + `return new ${contractClientType}(`, + ` ${grpcClientId},`, + ` ${contractAddressId},`, + ` ${genericContractId}`, + ');', + ], + }); - contractSourceFile - .addFunction({ - docs: [ - [ - `Construct the \`${contractClientType}\` for interacting with a '${contractName}' contract on chain.`, - 'Without checking the instance information on chain.', - `@param {SDK.ConcordiumGRPCClient} ${grpcClientId} - The client used for contract invocations and updates.`, - `@param {SDK.ContractAddress.Type} ${contractAddressId} - Address of the contract instance.`, - `@returns {${contractClientType}}`, - ].join('\n'), - ], - isExported: true, - name: 'createUnchecked', - parameters: [ - { - name: grpcClientId, - type: 'SDK.ConcordiumGRPCClient', - }, - { - name: contractAddressId, - type: 'SDK.ContractAddress.Type', - }, - ], - returnType: contractClientType, - }) - .setBodyText( + statements.push({ + kind: tsm.StructureKind.Function, + docs: [ [ - `const ${genericContractId} = new SDK.Contract(${grpcClientId}, ${contractAddressId}, ${contractNameId});`, - `return new ${contractClientType}(`, - ` ${grpcClientId},`, - ` ${contractAddressId},`, - ` ${genericContractId},`, - ');', - ].join('\n') - ); + `Construct the \`${contractClientType}\` for interacting with a '${contractName}' contract on chain.`, + 'Without checking the instance information on chain.', + `@param {SDK.ConcordiumGRPCClient} ${grpcClientId} - The client used for contract invocations and updates.`, + `@param {SDK.ContractAddress.Type} ${contractAddressId} - Address of the contract instance.`, + `@returns {${contractClientType}}`, + ].join('\n'), + ], + isExported: true, + name: 'createUnchecked', + parameters: [ + { + name: grpcClientId, + type: 'SDK.ConcordiumGRPCClient', + }, + { + name: contractAddressId, + type: 'SDK.ContractAddress.Type', + }, + ], + returnType: contractClientType, + statements: [ + `const ${genericContractId} = new SDK.Contract(${grpcClientId}, ${contractAddressId}, ${contractNameId});`, + `return new ${contractClientType}(`, + ` ${grpcClientId},`, + ` ${contractAddressId},`, + ` ${genericContractId},`, + ');', + ], + }); - contractSourceFile - .addFunction({ - docs: [ - [ - 'Check if the smart contract instance exists on the blockchain and whether it uses a matching contract name and module reference.', - `@param {${contractClientType}} ${contractClientId} The client for a '${contractName}' smart contract instance on chain.`, - `@param {SDK.BlockHash.Type} [${blockHashId}] A optional block hash to use for checking information on chain, if not provided the last finalized will be used.`, - '@throws {SDK.RpcError} If failing to communicate with the concordium node or if any of the checks fails.', - ].join('\n'), - ], - isExported: true, - name: 'checkOnChain', - parameters: [ - { - name: contractClientId, - type: contractClientType, - }, - { - name: blockHashId, - hasQuestionToken: true, - type: 'SDK.BlockHash.Type', - }, - ], - returnType: 'Promise', - }) - .setBodyText( - `return ${contractClientId}.${genericContractId}.checkOnChain({moduleReference: ${moduleRefId}, blockHash: ${blockHashId} });` - ); + statements.push({ + kind: tsm.StructureKind.Function, + docs: [ + [ + 'Check if the smart contract instance exists on the blockchain and whether it uses a matching contract name and module reference.', + `@param {${contractClientType}} ${contractClientId} The client for a '${contractName}' smart contract instance on chain.`, + `@param {SDK.BlockHash.Type} [${blockHashId}] A optional block hash to use for checking information on chain, if not provided the last finalized will be used.`, + '@throws {SDK.RpcError} If failing to communicate with the concordium node or if any of the checks fails.', + ].join('\n'), + ], + isExported: true, + name: 'checkOnChain', + parameters: [ + { + name: contractClientId, + type: contractClientType, + }, + { + name: blockHashId, + hasQuestionToken: true, + type: 'SDK.BlockHash.Type', + }, + ], + returnType: 'Promise', + statements: `return ${contractClientId}.${genericContractId}.checkOnChain({moduleReference: ${moduleRefId}, blockHash: ${blockHashId} });`, + }); const eventParameterId = 'event'; const eventParameterTypeId = 'Event'; const eventParser = parseEventCode(eventParameterId, contractSchema?.event); if (eventParser !== undefined) { - contractSourceFile.addTypeAlias({ + statements.push({ + kind: tsm.StructureKind.TypeAlias, docs: [`Contract event type for the '${contractName}' contract.`], isExported: true, name: eventParameterTypeId, type: eventParser.type, }); - contractSourceFile - .addFunction({ - docs: [ - [ - `Parse the contract events logged by the '${contractName}' contract.`, - `@param {SDK.ContractEvent.Type} ${eventParameterId} The unparsed contract event.`, - `@returns {${eventParameterTypeId}} The structured contract event.`, - ].join('\n'), - ], - isExported: true, - name: 'parseEvent', - parameters: [ - { - name: eventParameterId, - type: 'SDK.ContractEvent.Type', - }, - ], - returnType: eventParameterTypeId, - }) - .setBodyText( - [...eventParser.code, `return ${eventParser.id};`].join('\n') - ); + statements.push({ + kind: tsm.StructureKind.Function, + docs: [ + [ + `Parse the contract events logged by the '${contractName}' contract.`, + `@param {SDK.ContractEvent.Type} ${eventParameterId} The unparsed contract event.`, + `@returns {${eventParameterTypeId}} The structured contract event.`, + ].join('\n'), + ], + isExported: true, + name: 'parseEvent', + parameters: [ + { + name: eventParameterId, + type: 'SDK.ContractEvent.Type', + }, + ], + returnType: eventParameterTypeId, + statements: [...eventParser.code, `return ${eventParser.id};`].join( + '\n' + ), + }); } + return statements; } /** - * Generate contract client code for each entrypoint. - * @param {tsm.SourceFile} contractSourceFile The sourcefile of the contract. + * Generate contract client statements for an entrypoint. * @param {string} contractName The name of the contract. * @param {string} contractClientId The identifier to use for the contract client. * @param {string} contractClientType The identifier to use for the type of the contract client. * @param {string} entrypointName The name of the entrypoint. * @param {SDK.SchemaFunctionV2} [entrypointSchema] The schema to use for the entrypoint. + * @return {tsm.StatementStructures[]} List of statements related to an entrypoint. */ -function generateContractEntrypointCode( - contractSourceFile: tsm.SourceFile, +function generateContractEntrypointStatements( contractName: string, contractClientId: string, contractClientType: string, entrypointName: string, entrypointSchema?: SDK.SchemaFunctionV2 -) { +): tsm.StatementStructures[] { + const statements: tsm.StatementStructures[] = []; const invokeMetadataId = 'invokeMetadata'; const parameterId = 'parameter'; const transactionMetadataId = 'transactionMetadata'; @@ -968,7 +1028,8 @@ function generateContractEntrypointCode( const receiveParameterSchemaType = entrypointSchema?.parameter; if (receiveParameterSchemaType !== undefined) { - contractSourceFile.addVariableStatement({ + statements.push({ + kind: tsm.StructureKind.VariableStatement, docs: [ `Base64 encoding of the parameter schema type for update transactions to '${entrypointName}' entrypoint of the '${contractName}' contract.`, ], @@ -984,7 +1045,8 @@ function generateContractEntrypointCode( }); const typeAndMapper = schemaAsNativeType(receiveParameterSchemaType); - contractSourceFile.addTypeAlias({ + statements.push({ + kind: tsm.StructureKind.TypeAlias, docs: [ `Parameter JSON type needed by the schema for update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, ], @@ -992,7 +1054,8 @@ function generateContractEntrypointCode( type: typeAndMapper.jsonType, }); if (receiveParameterSchemaType.type !== 'Unit') { - contractSourceFile.addTypeAlias({ + statements.push({ + kind: tsm.StructureKind.TypeAlias, docs: [ `Parameter type for update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, ], @@ -1003,7 +1066,8 @@ function generateContractEntrypointCode( const mappedParameter = typeAndMapper.nativeToJson(parameterId); - contractSourceFile.addFunction({ + statements.push({ + kind: tsm.StructureKind.Function, docs: [ [ `Construct schema JSON representation used in update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, @@ -1024,7 +1088,8 @@ function generateContractEntrypointCode( `return ${mappedParameter.id};`, ], }); - contractSourceFile.addFunction({ + statements.push({ + kind: tsm.StructureKind.Function, docs: [ [ `Construct Parameter type used in update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, @@ -1046,7 +1111,8 @@ function generateContractEntrypointCode( ], }); - contractSourceFile.addFunction({ + statements.push({ + kind: tsm.StructureKind.Function, docs: [ [ `Construct WebWallet parameter type used in update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, @@ -1074,7 +1140,8 @@ function generateContractEntrypointCode( }); } } else { - contractSourceFile.addTypeAlias({ + statements.push({ + kind: tsm.StructureKind.TypeAlias, docs: [ `Parameter type used in update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, ], @@ -1086,125 +1153,121 @@ function generateContractEntrypointCode( const receiveTakesNoParameter = receiveParameterSchemaType?.type === 'Unit'; - contractSourceFile - .addFunction({ - docs: [ - [ - `Send an update-contract transaction to the '${entrypointName}' entrypoint of the '${contractName}' contract.`, - `@param {${contractClientType}} ${contractClientId} The client for a '${contractName}' smart contract instance on chain.`, - `@param {SDK.ContractTransactionMetadata} ${transactionMetadataId} - Metadata related to constructing a transaction for a smart contract.`, - ...(receiveTakesNoParameter - ? [] - : [ - `@param {${receiveParameterTypeId}} ${parameterId} - Parameter to provide the smart contract entrypoint as part of the transaction.`, - ]), - `@param {SDK.AccountSigner} ${signerId} - The signer of the update contract transaction.`, - '@throws If the entrypoint is not successfully invoked.', - '@returns {SDK.TransactionHash.Type} Hash of the transaction.', - ].join('\n'), - ], - isExported: true, - name: `send${toPascalCase(entrypointName)}`, - parameters: [ - { - name: contractClientId, - type: contractClientType, - }, - { - name: transactionMetadataId, - type: 'SDK.ContractTransactionMetadata', - }, + statements.push({ + kind: tsm.StructureKind.Function, + docs: [ + [ + `Send an update-contract transaction to the '${entrypointName}' entrypoint of the '${contractName}' contract.`, + `@param {${contractClientType}} ${contractClientId} The client for a '${contractName}' smart contract instance on chain.`, + `@param {SDK.ContractTransactionMetadata} ${transactionMetadataId} - Metadata related to constructing a transaction for a smart contract.`, ...(receiveTakesNoParameter ? [] : [ - { - name: parameterId, - type: receiveParameterTypeId, - }, + `@param {${receiveParameterTypeId}} ${parameterId} - Parameter to provide the smart contract entrypoint as part of the transaction.`, ]), - { - name: signerId, - type: 'SDK.AccountSigner', - }, - ], - returnType: 'Promise', - }) - .setBodyText( - [ - `return ${contractClientId}.${genericContractId}.createAndSendUpdateTransaction(`, - ` SDK.EntrypointName.fromStringUnchecked('${entrypointName}'),`, - ' SDK.Parameter.toBuffer,', - ` ${transactionMetadataId},`, - ...(receiveParameterSchemaType === undefined - ? [` ${parameterId},`] - : receiveParameterSchemaType.type === 'Unit' - ? [] - : [` ${createReceiveParameterFnId}(${parameterId}),`]), - ` ${signerId}`, - ');', - ].join('\n') - ); + `@param {SDK.AccountSigner} ${signerId} - The signer of the update contract transaction.`, + '@throws If the entrypoint is not successfully invoked.', + '@returns {SDK.TransactionHash.Type} Hash of the transaction.', + ].join('\n'), + ], + isExported: true, + name: `send${toPascalCase(entrypointName)}`, + parameters: [ + { + name: contractClientId, + type: contractClientType, + }, + { + name: transactionMetadataId, + type: 'SDK.ContractTransactionMetadata', + }, + ...(receiveTakesNoParameter + ? [] + : [ + { + name: parameterId, + type: receiveParameterTypeId, + }, + ]), + { + name: signerId, + type: 'SDK.AccountSigner', + }, + ], + returnType: 'Promise', + statements: [ + `return ${contractClientId}.${genericContractId}.createAndSendUpdateTransaction(`, + ` SDK.EntrypointName.fromStringUnchecked('${entrypointName}'),`, + ' SDK.Parameter.toBuffer,', + ` ${transactionMetadataId},`, + ...(receiveParameterSchemaType === undefined + ? [` ${parameterId},`] + : receiveParameterSchemaType.type === 'Unit' + ? [] + : [` ${createReceiveParameterFnId}(${parameterId}),`]), + ` ${signerId}`, + ');', + ], + }); - contractSourceFile - .addFunction({ - docs: [ - [ - `Dry-run an update-contract transaction to the '${entrypointName}' entrypoint of the '${contractName}' contract.`, - `@param {${contractClientType}} ${contractClientId} The client for a '${contractName}' smart contract instance on chain.`, - `@param {SDK.ContractAddress.Type | SDK.AccountAddress.Type} ${invokeMetadataId} - The address of the account or contract which is invoking this transaction.`, - ...(receiveTakesNoParameter - ? [] - : [ - `@param {${receiveParameterTypeId}} ${parameterId} - Parameter to provide the smart contract entrypoint as part of the transaction.`, - ]), - `@param {SDK.BlockHash.Type} [${blockHashId}] - Optional block hash allowing for dry-running the transaction at the end of a specific block.`, - '@throws {SDK.RpcError} If failing to communicate with the concordium node or if any of the checks fails.', - '@returns {SDK.InvokeContractResult} The result of invoking the smart contract instance.', - ].join('\n'), - ], - isExported: true, - name: `dryRun${toPascalCase(entrypointName)}`, - parameters: [ - { - name: contractClientId, - type: contractClientType, - }, + statements.push({ + kind: tsm.StructureKind.Function, + docs: [ + [ + `Dry-run an update-contract transaction to the '${entrypointName}' entrypoint of the '${contractName}' contract.`, + `@param {${contractClientType}} ${contractClientId} The client for a '${contractName}' smart contract instance on chain.`, + `@param {SDK.ContractAddress.Type | SDK.AccountAddress.Type} ${invokeMetadataId} - The address of the account or contract which is invoking this transaction.`, ...(receiveTakesNoParameter ? [] : [ - { - name: parameterId, - type: receiveParameterTypeId, - }, + `@param {${receiveParameterTypeId}} ${parameterId} - Parameter to provide the smart contract entrypoint as part of the transaction.`, ]), - { - name: invokeMetadataId, - type: 'SDK.ContractInvokeMetadata', - initializer: '{}', - }, - { - name: blockHashId, - hasQuestionToken: true, - type: 'SDK.BlockHash.Type', - }, - ], - returnType: 'Promise', - }) - .setBodyText( - [ - `return ${contractClientId}.${genericContractId}.dryRun.invokeMethod(`, - ` SDK.EntrypointName.fromStringUnchecked('${entrypointName}'),`, - ` ${invokeMetadataId},`, - ' SDK.Parameter.toBuffer,', - ...(receiveParameterSchemaType === undefined - ? [` ${parameterId},`] - : receiveParameterSchemaType.type === 'Unit' - ? [] - : [` ${createReceiveParameterFnId}(${parameterId}),`]), - ` ${blockHashId}`, - ');', - ].join('\n') - ); + `@param {SDK.BlockHash.Type} [${blockHashId}] - Optional block hash allowing for dry-running the transaction at the end of a specific block.`, + '@throws {SDK.RpcError} If failing to communicate with the concordium node or if any of the checks fails.', + '@returns {SDK.InvokeContractResult} The result of invoking the smart contract instance.', + ].join('\n'), + ], + isExported: true, + name: `dryRun${toPascalCase(entrypointName)}`, + parameters: [ + { + name: contractClientId, + type: contractClientType, + }, + ...(receiveTakesNoParameter + ? [] + : [ + { + name: parameterId, + type: receiveParameterTypeId, + }, + ]), + { + name: invokeMetadataId, + type: 'SDK.ContractInvokeMetadata', + initializer: '{}', + }, + { + name: blockHashId, + hasQuestionToken: true, + type: 'SDK.BlockHash.Type', + }, + ], + returnType: 'Promise', + statements: [ + `return ${contractClientId}.${genericContractId}.dryRun.invokeMethod(`, + ` SDK.EntrypointName.fromStringUnchecked('${entrypointName}'),`, + ` ${invokeMetadataId},`, + ' SDK.Parameter.toBuffer,', + ...(receiveParameterSchemaType === undefined + ? [` ${parameterId},`] + : receiveParameterSchemaType.type === 'Unit' + ? [] + : [` ${createReceiveParameterFnId}(${parameterId}),`]), + ` ${blockHashId}`, + ');', + ], + }); const invokeResultId = 'invokeResult'; const returnValueTokens = parseReturnValueCode( @@ -1214,7 +1277,8 @@ function generateContractEntrypointCode( if (returnValueTokens !== undefined) { const returnValueTypeId = `ReturnValue${toPascalCase(entrypointName)}`; - contractSourceFile.addTypeAlias({ + statements.push({ + kind: tsm.StructureKind.TypeAlias, docs: [ `Return value for dry-running update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, ], @@ -1223,38 +1287,36 @@ function generateContractEntrypointCode( type: returnValueTokens.type, }); - contractSourceFile - .addFunction({ - docs: [ - [ - `Get and parse the return value from dry-running update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, - 'Returns undefined if the result is not successful.', - '@param {SDK.InvokeContractResult} invokeResult The result from dry-running the transaction.', - `@returns {${returnValueTypeId} | undefined} The structured return value or undefined if result was not a success.`, - ].join('\n'), - ], - isExported: true, - name: `parseReturnValue${toPascalCase(entrypointName)}`, - parameters: [ - { - name: invokeResultId, - type: 'SDK.InvokeContractResult', - }, - ], - returnType: `${returnValueTypeId} | undefined`, - }) - .setBodyText( + statements.push({ + kind: tsm.StructureKind.Function, + docs: [ [ - `if (${invokeResultId}.tag !== 'success') {`, - ' return undefined;', - '}', - `if (${invokeResultId}.returnValue === undefined) {`, - " throw new Error('Unexpected missing \\'returnValue\\' in result of invocation. Client expected a V1 smart contract.');", - '}', - ...returnValueTokens.code, - `return ${returnValueTokens.id};`, - ].join('\n') - ); + `Get and parse the return value from dry-running update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, + 'Returns undefined if the result is not successful.', + '@param {SDK.InvokeContractResult} invokeResult The result from dry-running the transaction.', + `@returns {${returnValueTypeId} | undefined} The structured return value or undefined if result was not a success.`, + ].join('\n'), + ], + isExported: true, + name: `parseReturnValue${toPascalCase(entrypointName)}`, + parameters: [ + { + name: invokeResultId, + type: 'SDK.InvokeContractResult', + }, + ], + returnType: `${returnValueTypeId} | undefined`, + statements: [ + `if (${invokeResultId}.tag !== 'success') {`, + ' return undefined;', + '}', + `if (${invokeResultId}.returnValue === undefined) {`, + " throw new Error('Unexpected missing \\'returnValue\\' in result of invocation. Client expected a V1 smart contract.');", + '}', + ...returnValueTokens.code, + `return ${returnValueTokens.id};`, + ], + }); } const errorMessageTokens = parseReturnValueCode( @@ -1266,7 +1328,8 @@ function generateContractEntrypointCode( entrypointName )}`; - contractSourceFile.addTypeAlias({ + statements.push({ + kind: tsm.StructureKind.TypeAlias, docs: [ `Error message for dry-running update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, ], @@ -1275,39 +1338,38 @@ function generateContractEntrypointCode( type: errorMessageTokens.type, }); - contractSourceFile - .addFunction({ - docs: [ - [ - `Get and parse the error message from dry-running update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, - 'Returns undefined if the result is not a failure.', - '@param {SDK.InvokeContractResult} invokeResult The result from dry-running the transaction.', - `@returns {${errorMessageTypeId} | undefined} The structured error message or undefined if result was not a failure or failed for other reason than contract rejectedReceive.`, - ].join('\n'), - ], - isExported: true, - name: `parseErrorMessage${toPascalCase(entrypointName)}`, - parameters: [ - { - name: invokeResultId, - type: 'SDK.InvokeContractResult', - }, - ], - returnType: `${errorMessageTypeId} | undefined`, - }) - .setBodyText( + statements.push({ + kind: tsm.StructureKind.Function, + docs: [ [ - `if (${invokeResultId}.tag !== 'failure' || ${invokeResultId}.reason.tag !== 'RejectedReceive') {`, - ' return undefined;', - '}', - `if (${invokeResultId}.returnValue === undefined) {`, - " throw new Error('Unexpected missing \\'returnValue\\' in result of invocation. Client expected a V1 smart contract.');", - '}', - ...errorMessageTokens.code, - `return ${errorMessageTokens.id}`, - ].join('\n') - ); + `Get and parse the error message from dry-running update transaction for '${entrypointName}' entrypoint of the '${contractName}' contract.`, + 'Returns undefined if the result is not a failure.', + '@param {SDK.InvokeContractResult} invokeResult The result from dry-running the transaction.', + `@returns {${errorMessageTypeId} | undefined} The structured error message or undefined if result was not a failure or failed for other reason than contract rejectedReceive.`, + ].join('\n'), + ], + isExported: true, + name: `parseErrorMessage${toPascalCase(entrypointName)}`, + parameters: [ + { + name: invokeResultId, + type: 'SDK.InvokeContractResult', + }, + ], + returnType: `${errorMessageTypeId} | undefined`, + statements: [ + `if (${invokeResultId}.tag !== 'failure' || ${invokeResultId}.reason.tag !== 'RejectedReceive') {`, + ' return undefined;', + '}', + `if (${invokeResultId}.returnValue === undefined) {`, + " throw new Error('Unexpected missing \\'returnValue\\' in result of invocation. Client expected a V1 smart contract.');", + '}', + ...errorMessageTokens.code, + `return ${errorMessageTokens.id}`, + ], + }); } + return statements; } /** Make the first character in a string uppercase */ From ac4ea0fd1c6dc08cd338d2f197fa741ee5c0c9cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Holm=20Gj=C3=B8rup?= Date: Thu, 11 Jan 2024 15:49:54 +0100 Subject: [PATCH 39/44] Apply suggestions from code review Co-authored-by: Doris Benda --- packages/ccd-js-gen/src/lib.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ccd-js-gen/src/lib.ts b/packages/ccd-js-gen/src/lib.ts index 250262090..546f32a29 100644 --- a/packages/ccd-js-gen/src/lib.ts +++ b/packages/ccd-js-gen/src/lib.ts @@ -302,8 +302,8 @@ async function generateCode( * @param moduleRef The module reference. * @param moduleClientId The identifier to use for the module client. * @param moduleClientType The identifier to use for the type of the module client. - * @param tsNocheck When `true` generate `// @ts-nocheck` annotations in each file. * @param internalModuleClientId The identifier to use for referencing the internal module client. + * @param tsNocheck When `true` generate `// @ts-nocheck` annotations in each file. */ function generateModuleBaseStatements( moduleRef: SDK.ModuleReference.Type, @@ -366,7 +366,7 @@ function generateModuleBaseStatements( ctors: [ { docs: [ - 'Constructor is only ment to be used internally in this module. Use functions such as `create` or `createUnchecked` for construction.', + 'Constructor is only meant to be used internally in this module. Use functions such as `create` or `createUnchecked` for construction.', ], parameters: [ { From 4e9b5d0b0aaae98f8e34c38e836f1d0dc977b12e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Holm=20Gj=C3=B8rup?= Date: Thu, 11 Jan 2024 16:09:13 +0100 Subject: [PATCH 40/44] ccd-js-gen: Sanitize module type names, add node engine and improve readme --- packages/ccd-js-gen/CHANGELOG.md | 1 + packages/ccd-js-gen/README.md | 36 +++++++++++++++++++++++++++++++ packages/ccd-js-gen/package.json | 3 +++ packages/ccd-js-gen/src/lib.ts | 20 ++++++++++++++++- packages/ccd-js-gen/tsconfig.json | 2 +- 5 files changed, 60 insertions(+), 2 deletions(-) diff --git a/packages/ccd-js-gen/CHANGELOG.md b/packages/ccd-js-gen/CHANGELOG.md index e8742936d..89d937bc1 100644 --- a/packages/ccd-js-gen/CHANGELOG.md +++ b/packages/ccd-js-gen/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Fix issue related to inferring module type names from the input file name, by removing characters, which are not valid in type names. - Improve information when reporting progress during code-generation. - Generate `createParameterWebWallet` and `createParameterWebWallet` functions for constructing the smart contract parameters in the format used by the Concordium Web-Wallet. - Minor breaking change: `createParameter` function for contract entries and `createParameter` function in module are no longer generated when parameter schema type is `Unit`. These functions are mainly used internally, but are still exposed. diff --git a/packages/ccd-js-gen/README.md b/packages/ccd-js-gen/README.md index 66620cf6e..1343e3431 100644 --- a/packages/ccd-js-gen/README.md +++ b/packages/ccd-js-gen/README.md @@ -85,6 +85,9 @@ const transactionHash = await MyContract.sendTransfer(contractClient, { - [type `ErrorMessage`](#type-errormessageentrypointname) - [function `parseErrorMessage`](#function-parseerrormessageentrypointname) - [function `createParameterWebWallet`](#function-createentrypointnameparameterwebwallet) +- [Development](#development) + - [Setup](#setup) + - [Development workflow](#development-workflow) ## Install the package @@ -629,3 +632,36 @@ const transactionHash = await webWalletConnection.signAndSendTransaction( walletParameter ); ``` + +## Development + +This section describes how to setup and start developing this package. + +### Setup +To be able to develop `ccd-js-gen` make sure to have: + +- [NodeJs](https://nodejs.org/en) (see `package.json` for which versions are supported). +- [Yarn](https://yarnpkg.com/) +- [Rust and cargo](https://rustup.rs/) + +After cloning this repository makes sure to do the following: + +- Initialize git submodules recursively, can be done using `git submodules update --init --recursive`. +- Install dependencies by running `yarn install` in the root of this repo. +- Build everything using `yarn build` in the root of this repo. + +### Development workflow + +After doing changes to the source code of `ccd-js-gen` run `yarn build` from the `packages/ccd-js-gen` directory. + +To run CLI locally use: + +``` +./bin/ccd-js-gen.js --module "" --out "./lib/generated" +``` + +To run tests locally use: + +``` +yarn test +``` \ No newline at end of file diff --git a/packages/ccd-js-gen/package.json b/packages/ccd-js-gen/package.json index f869388e7..a9bb50ecf 100644 --- a/packages/ccd-js-gen/package.json +++ b/packages/ccd-js-gen/package.json @@ -37,6 +37,9 @@ "url": "https://concordium.com" }, "license": "Apache-2.0", + "engines": { + "node": ">=16.15.0 <21" + }, "peerDependencies": { "@concordium/web-sdk": "7.x" }, diff --git a/packages/ccd-js-gen/src/lib.ts b/packages/ccd-js-gen/src/lib.ts index 546f32a29..56350ab09 100644 --- a/packages/ccd-js-gen/src/lib.ts +++ b/packages/ccd-js-gen/src/lib.ts @@ -212,7 +212,8 @@ async function generateCode( overwrite: true, }); const moduleClientId = 'moduleClient'; - const moduleClientType = `${toPascalCase(outModuleName)}Module`; + const sanitizedOutModuleName = sanitizeIdentifier(outModuleName); + const moduleClientType = `${toPascalCase(sanitizedOutModuleName)}Module`; const internalModuleClientId = 'internalModuleClient'; moduleSourceFile.addStatements( @@ -2290,3 +2291,20 @@ function defineProp(propId: string, valueId: string): string { * when accessing props or defining fields in an object. */ const identifierRegex = /^[$A-Z_][0-9A-Z_$]*$/i; + +/** + * Regular expression matching any character which cannot be used as an identifier. + */ +const notIdentifierSymbolRegex = /[^0-9A-Z_$]/gi; + +/** + * Remove any characters from a string which cannot be used as an identifier. + * @param {string} input Input to sanitize. + * @returns {string} Input string without any characters which are invalid for identifiers. + * + * @example + * sanitizeIdentifier("some/weird variable.name") // "someweirdvariablename" + */ +function sanitizeIdentifier(input: string): string { + return input.replaceAll(notIdentifierSymbolRegex, ''); +} diff --git a/packages/ccd-js-gen/tsconfig.json b/packages/ccd-js-gen/tsconfig.json index 4f493d24a..fd5220160 100644 --- a/packages/ccd-js-gen/tsconfig.json +++ b/packages/ccd-js-gen/tsconfig.json @@ -5,7 +5,7 @@ "module": "NodeNext", "moduleResolution": "NodeNext", "outDir": "./lib", - "lib": ["ES2020", "dom"], // "dom" is only added to get the typings for the global variable `WebAssembly`. + "lib": ["ES2021", "dom"], // "dom" is only added to get the typings for the global variable `WebAssembly`. "resolveJsonModule": true } } From d5b29ce8dd7a27c06bebeac982da1ddfcfb8cf81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Holm=20Gj=C3=B8rup?= Date: Mon, 22 Jan 2024 17:01:04 +0100 Subject: [PATCH 41/44] Avoid import assertions in ccd-js-gen CLI, providing support for more node versions --- packages/ccd-js-gen/package.json | 3 --- packages/ccd-js-gen/src/cli.ts | 14 +++++++++++++- packages/ccd-js-gen/tsconfig.build.json | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/ccd-js-gen/package.json b/packages/ccd-js-gen/package.json index a9bb50ecf..f869388e7 100644 --- a/packages/ccd-js-gen/package.json +++ b/packages/ccd-js-gen/package.json @@ -37,9 +37,6 @@ "url": "https://concordium.com" }, "license": "Apache-2.0", - "engines": { - "node": ">=16.15.0 <21" - }, "peerDependencies": { "@concordium/web-sdk": "7.x" }, diff --git a/packages/ccd-js-gen/src/cli.ts b/packages/ccd-js-gen/src/cli.ts index c84c8654b..4281d4716 100644 --- a/packages/ccd-js-gen/src/cli.ts +++ b/packages/ccd-js-gen/src/cli.ts @@ -1,8 +1,10 @@ /* This file contains code for building the command line inferface to the ccd-js-gen library. */ +import * as fs from 'node:fs/promises'; +import * as path from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; import { Command } from 'commander'; -import packageJson from '../package.json' assert { type: 'json' }; import * as lib from './lib.js'; /** Type representing the CLI options/arguments and needs to match the options set with commander.js */ @@ -19,6 +21,16 @@ type Options = { // Main function, which is called in the executable script in `bin`. export async function main(): Promise { + // Read package.json for version and description: + const scriptPath = fileURLToPath(import.meta.url); + const binFolder = path.parse(scriptPath).dir; + const cliPath = pathToFileURL( + path.resolve(binFolder, '..', '..', 'package.json') + ); + const packageJson: { version: string; description: string } = await fs + .readFile(cliPath, 'utf-8') + .then(JSON.parse); + const program = new Command(); program .name('ccd-js-gen') diff --git a/packages/ccd-js-gen/tsconfig.build.json b/packages/ccd-js-gen/tsconfig.build.json index edc2723b8..65b66b79a 100644 --- a/packages/ccd-js-gen/tsconfig.build.json +++ b/packages/ccd-js-gen/tsconfig.build.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", - "include": ["src/**/*", "package.json"] + "include": ["src/**/*", "package.json"] // package.json is used by the CLI, therefore it is included here. } From 12a1c30f9db40ca6056e657e678f45ec7f41f33a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Holm=20Gj=C3=B8rup?= Date: Tue, 23 Jan 2024 09:03:23 +0100 Subject: [PATCH 42/44] Update packages/ccd-js-gen/README.md Co-authored-by: Doris Benda --- packages/ccd-js-gen/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ccd-js-gen/README.md b/packages/ccd-js-gen/README.md index 1343e3431..f2113f6fd 100644 --- a/packages/ccd-js-gen/README.md +++ b/packages/ccd-js-gen/README.md @@ -657,7 +657,7 @@ After doing changes to the source code of `ccd-js-gen` run `yarn build` from the To run CLI locally use: ``` -./bin/ccd-js-gen.js --module "" --out "./lib/generated" +./bin/ccd-js-gen.js --module "" --out-dir "./lib/generated" ``` To run tests locally use: From 39db3685bdd3566d7ffac416e4b870cc8754ba11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Holm=20Gj=C3=B8rup?= Date: Tue, 23 Jan 2024 09:20:26 +0100 Subject: [PATCH 43/44] Fix markdown lint errors --- packages/ccd-js-gen/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/ccd-js-gen/README.md b/packages/ccd-js-gen/README.md index f2113f6fd..9613e12a0 100644 --- a/packages/ccd-js-gen/README.md +++ b/packages/ccd-js-gen/README.md @@ -638,6 +638,7 @@ const transactionHash = await webWalletConnection.signAndSendTransaction( This section describes how to setup and start developing this package. ### Setup + To be able to develop `ccd-js-gen` make sure to have: - [NodeJs](https://nodejs.org/en) (see `package.json` for which versions are supported). @@ -656,12 +657,12 @@ After doing changes to the source code of `ccd-js-gen` run `yarn build` from the To run CLI locally use: -``` +```bash ./bin/ccd-js-gen.js --module "" --out-dir "./lib/generated" ``` To run tests locally use: -``` +```bash yarn test -``` \ No newline at end of file +``` From 0907caf4688e6350840db7c311b604aac8522a2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Holm=20Gj=C3=B8rup?= Date: Tue, 23 Jan 2024 09:17:07 +0100 Subject: [PATCH 44/44] Prepare release of ccd-js-gen 1.2.0 --- packages/ccd-js-gen/CHANGELOG.md | 2 ++ packages/ccd-js-gen/package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ccd-js-gen/CHANGELOG.md b/packages/ccd-js-gen/CHANGELOG.md index 89d937bc1..51c5c9841 100644 --- a/packages/ccd-js-gen/CHANGELOG.md +++ b/packages/ccd-js-gen/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.2.0 + - Fix issue related to inferring module type names from the input file name, by removing characters, which are not valid in type names. - Improve information when reporting progress during code-generation. - Generate `createParameterWebWallet` and `createParameterWebWallet` functions for constructing the smart contract parameters in the format used by the Concordium Web-Wallet. diff --git a/packages/ccd-js-gen/package.json b/packages/ccd-js-gen/package.json index f869388e7..98657a9ce 100644 --- a/packages/ccd-js-gen/package.json +++ b/packages/ccd-js-gen/package.json @@ -1,6 +1,6 @@ { "name": "@concordium/ccd-js-gen", - "version": "1.0.1", + "version": "1.2.0", "description": "Generate JS clients for the Concordium Blockchain", "type": "module", "bin": "bin/ccd-js-gen.js",