diff --git a/apps/extension/src/background/approvals/service.ts b/apps/extension/src/background/approvals/service.ts index 317608ded..64e372f72 100644 --- a/apps/extension/src/background/approvals/service.ts +++ b/apps/extension/src/background/approvals/service.ts @@ -207,7 +207,7 @@ export class ApprovalsService { ): Promise { const pendingTx = await this.txStore.get(msgId); if (!pendingTx) { - throw new Error(ApprovalErrors.PendingTxNotFound(msgId)); + throw new Error(ApprovalErrors.TransactionDataNotFound(msgId)); } const { tx: sdkTx } = this.sdkService.getSdk(); diff --git a/apps/extension/src/background/approvals/types.ts b/apps/extension/src/background/approvals/types.ts index 0bda59b68..fca947bdf 100644 --- a/apps/extension/src/background/approvals/types.ts +++ b/apps/extension/src/background/approvals/types.ts @@ -32,7 +32,6 @@ export const ApprovalErrors = { `Pending signing data not found for ${msgId}!`, PendingSignArbitaryDataNotFound: (msgId: string) => `Pending sign arbitrary data not found for ${msgId}!`, - PendingTxNotFound: (msgId: string) => `Pending tx not found for ${msgId}`, TransactionDataNotFound: (msgId: string) => `Transaction data not found for ${msgId}`, InvalidLedgerSignature: (msgId: string) => diff --git a/apps/namadillo/src/atoms/transfer/services.ts b/apps/namadillo/src/atoms/transfer/services.ts index d89ac35d9..772ba504f 100644 --- a/apps/namadillo/src/atoms/transfer/services.ts +++ b/apps/namadillo/src/atoms/transfer/services.ts @@ -104,14 +104,22 @@ export const createShieldedTransferTx = async ( rpcUrl: string, memo?: string ): Promise | undefined> => { - // For now we only support disposableSigner for non-ledger accounts const { address: signerAddress, publicKey: signerPublicKey } = - account.type === AccountType.Ledger ? account : await getDisposableSigner(); + await getDisposableSigner(); const source = props[0]?.data[0]?.source; const destination = props[0]?.data[0]?.target; const token = props[0]?.data[0]?.token; const amount = props[0]?.data[0]?.amount; + let bparams: BparamsMsgValue[] | undefined; + + if (account.type === AccountType.Ledger) { + const sdk = await getSdkInstance(); + const ledger = await sdk.initLedger(); + bparams = await ledger.getBparams(); + ledger.closeTransport(); + } + return await workerBuildTxPair({ rpcUrl, token, @@ -120,6 +128,7 @@ export const createShieldedTransferTx = async ( const msgValue = new ShieldedTransferMsgValue({ gasSpendingKey: source, data: [{ source, target: destination, token, amount }], + bparams, }); const msg: ShieldedTransfer = { type: "shielded-transfer", @@ -153,6 +162,15 @@ export const createShieldingTransferTx = async ( const token = props[0]?.data[0]?.token; const amount = props[0]?.data[0]?.amount; + let bparams: BparamsMsgValue[] | undefined; + + if (account.type === AccountType.Ledger) { + const sdk = await getSdkInstance(); + const ledger = await sdk.initLedger(); + bparams = await ledger.getBparams(); + ledger.closeTransport(); + } + return await workerBuildTxPair({ rpcUrl, token, @@ -161,6 +179,7 @@ export const createShieldingTransferTx = async ( const msgValue = new ShieldingTransferMsgValue({ target: destination, data: [{ source, token, amount }], + bparams, }); const msg: Shield = { type: "shield", @@ -186,7 +205,6 @@ export const createUnshieldingTransferTx = async ( rpcUrl: string, memo?: string ): Promise | undefined> => { - // For now we only support disposableSigner for non-ledger accounts const { address: signerAddress, publicKey: signerPublicKey } = await getDisposableSigner(); diff --git a/packages/sdk/src/ledger.ts b/packages/sdk/src/ledger.ts index d358a59c4..32d515d31 100644 --- a/packages/sdk/src/ledger.ts +++ b/packages/sdk/src/ledger.ts @@ -170,9 +170,18 @@ export class Ledger { // to ensure that the randomness is not reused await this.namadaApp.cleanRandomnessBuffers(); const results: Bparams[] = []; + let tries = 0; - // TODO: not sure why ledger sometimes returns errors, so we try to get 15 valid responses + // This should not happen usually, but in case some of the responses are not valid, we will retry. + // 15 is a maximum number of spend/output/convert description randomness parameters that can be + // generated on the hardware wallet. This also means that ledger can sign maximum of 15 spend, output + // and convert descriptions in one tx. while (results.length < 15) { + tries++; + if (tries === 20) { + throw new Error("Could not get valid Bparams, too many tries"); + } + const spend_response = await this.namadaApp.getSpendRandomness(); const output_response = await this.namadaApp.getOutputRandomness(); const convert_response = await this.namadaApp.getConvertRandomness(); diff --git a/packages/shared/lib/src/sdk/args.rs b/packages/shared/lib/src/sdk/args.rs index f2cdbbc2b..cb1280ab9 100644 --- a/packages/shared/lib/src/sdk/args.rs +++ b/packages/shared/lib/src/sdk/args.rs @@ -501,6 +501,34 @@ pub fn transparent_transfer_tx_args( Ok(args) } +#[derive(BorshSerialize, BorshDeserialize, Debug)] +#[borsh(crate = "namada_sdk::borsh")] +pub struct BparamsSpendMsg { + rcv: Vec, + alpha: Vec, +} + +#[derive(BorshSerialize, BorshDeserialize, Debug)] +#[borsh(crate = "namada_sdk::borsh")] +pub struct BparamsOutputMsg { + rcv: Vec, + rcm: Vec, +} + +#[derive(BorshSerialize, BorshDeserialize, Debug)] +#[borsh(crate = "namada_sdk::borsh")] +pub struct BparamsConvertMsg { + rcv: Vec, +} + +#[derive(BorshSerialize, BorshDeserialize, Debug)] +#[borsh(crate = "namada_sdk::borsh")] +pub struct BparamsMsg { + spend: BparamsSpendMsg, + output: BparamsOutputMsg, + convert: BparamsConvertMsg, +} + #[derive(BorshSerialize, BorshDeserialize, Debug)] #[borsh(crate = "namada_sdk::borsh")] pub struct ShieldedTransferDataMsg { @@ -515,6 +543,7 @@ pub struct ShieldedTransferDataMsg { pub struct ShieldedTransferMsg { data: Vec, gas_spending_key: Option, + bparams: Option>, } /// Maps serialized tx_msg into TxShieldedTransfer args. @@ -531,11 +560,12 @@ pub struct ShieldedTransferMsg { pub fn shielded_transfer_tx_args( shielded_transfer_msg: &[u8], tx_msg: &[u8], -) -> Result { +) -> Result<(args::TxShieldedTransfer, Option), JsError> { let shielded_transfer_msg = ShieldedTransferMsg::try_from_slice(shielded_transfer_msg)?; let ShieldedTransferMsg { data, gas_spending_key, + bparams: bparams_msg, } = shielded_transfer_msg; let gas_spending_key = gas_spending_key.map(|v| PseudoExtendedKey::decode(v).0); @@ -559,6 +589,7 @@ pub fn shielded_transfer_tx_args( } let tx = tx_msg_into_args(tx_msg)?; + let bparams = bparams_msg_into_bparams(bparams_msg); let args = args::TxShieldedTransfer { data: shielded_transfer_data, @@ -569,7 +600,7 @@ pub fn shielded_transfer_tx_args( gas_spending_key, }; - Ok(args) + Ok((args, bparams)) } #[derive(BorshSerialize, BorshDeserialize, Debug)] @@ -585,6 +616,7 @@ pub struct ShieldingTransferDataMsg { pub struct ShieldingTransferMsg { target: String, data: Vec, + bparams: Option>, } /// Maps serialized tx_msg into TxShieldingTransfer args. @@ -601,9 +633,13 @@ pub struct ShieldingTransferMsg { pub fn shielding_transfer_tx_args( shielding_transfer_msg: &[u8], tx_msg: &[u8], -) -> Result { +) -> Result<(args::TxShieldingTransfer, Option), JsError> { let shielding_transfer_msg = ShieldingTransferMsg::try_from_slice(shielding_transfer_msg)?; - let ShieldingTransferMsg { target, data } = shielding_transfer_msg; + let ShieldingTransferMsg { + target, + data, + bparams: bparams_msg, + } = shielding_transfer_msg; let target = PaymentAddress::from_str(&target)?; let mut shielding_transfer_data: Vec = vec![]; @@ -623,6 +659,7 @@ pub fn shielding_transfer_tx_args( } let tx = tx_msg_into_args(tx_msg)?; + let bparams = bparams_msg_into_bparams(bparams_msg); let args = args::TxShieldingTransfer { data: shielding_transfer_data, @@ -631,7 +668,7 @@ pub fn shielding_transfer_tx_args( tx_code_path: PathBuf::from("tx_transfer.wasm"), }; - Ok(args) + Ok((args, bparams)) } #[derive(BorshSerialize, BorshDeserialize, Debug)] @@ -642,34 +679,6 @@ pub struct UnshieldingTransferDataMsg { amount: String, } -#[derive(BorshSerialize, BorshDeserialize, Debug)] -#[borsh(crate = "namada_sdk::borsh")] -pub struct BparamsSpendMsg { - rcv: Vec, - alpha: Vec, -} - -#[derive(BorshSerialize, BorshDeserialize, Debug)] -#[borsh(crate = "namada_sdk::borsh")] -pub struct BparamsOutputMsg { - rcv: Vec, - rcm: Vec, -} - -#[derive(BorshSerialize, BorshDeserialize, Debug)] -#[borsh(crate = "namada_sdk::borsh")] -pub struct BparamsConvertMsg { - rcv: Vec, -} - -#[derive(BorshSerialize, BorshDeserialize, Debug)] -#[borsh(crate = "namada_sdk::borsh")] -pub struct BparamsMsg { - spend: BparamsSpendMsg, - output: BparamsOutputMsg, - convert: BparamsConvertMsg, -} - #[derive(BorshSerialize, BorshDeserialize, Debug)] #[borsh(crate = "namada_sdk::borsh")] pub struct UnshieldingTransferMsg { @@ -720,45 +729,7 @@ pub fn unshielding_transfer_tx_args( } let tx = tx_msg_into_args(tx_msg)?; - - let bparams = bparams_msg.map(|bparams_msg| { - let mut bparams = StoredBuildParams::default(); - for bpm in bparams_msg { - bparams - .spend_params - .push(sapling::builder::SpendBuildParams { - rcv: masp_primitives::jubjub::Fr::from_bytes( - &bpm.spend.rcv.try_into().unwrap(), - ) - .unwrap(), - alpha: masp_primitives::jubjub::Fr::from_bytes( - &bpm.spend.alpha.try_into().unwrap(), - ) - .unwrap(), - }); - - bparams - .output_params - .push(sapling::builder::OutputBuildParams { - rcv: masp_primitives::jubjub::Fr::from_bytes( - &bpm.output.rcv.try_into().unwrap(), - ) - .unwrap(), - rseed: bpm.output.rcm.try_into().unwrap(), - ..sapling::builder::OutputBuildParams::default() - }); - - bparams - .convert_params - .push(sapling::builder::ConvertBuildParams { - rcv: masp_primitives::jubjub::Fr::from_bytes( - &bpm.convert.rcv.try_into().unwrap(), - ) - .unwrap(), - }); - } - bparams - }); + let bparams = bparams_msg_into_bparams(bparams_msg); let args = args::TxUnshieldingTransfer { data: unshielding_transfer_data, @@ -1030,26 +1001,11 @@ fn tx_msg_into_args(tx_msg: &[u8]) -> Result { pub enum BuildParams { RngBuildParams(RngBuildParams), - // TODO: HD Wallet support - #[allow(dead_code)] StoredBuildParams(StoredBuildParams), } -pub async fn generate_masp_build_params( - // TODO: those will be needed for HD Wallet support - _spend_len: usize, - _convert_len: usize, - _output_len: usize, - args: &args::Tx, -) -> Result { - // Construct the build parameters that parameterized the Transaction - // authorizations - if args.use_device { - // HD Wallet support - Err(error::Error::Other("Device not supported".into())) - } else { - Ok(BuildParams::RngBuildParams(RngBuildParams::new(OsRng))) - } +pub fn generate_rng_build_params() -> BuildParams { + BuildParams::RngBuildParams(RngBuildParams::new(OsRng)) } // Sign the given transaction's MASP component using real signatures @@ -1116,6 +1072,47 @@ where Ok(()) } +fn bparams_msg_into_bparams(bparams_msg: Option>) -> Option { + bparams_msg.map(|bparams_msg| { + let mut bparams = StoredBuildParams::default(); + for bpm in bparams_msg { + bparams + .spend_params + .push(sapling::builder::SpendBuildParams { + rcv: masp_primitives::jubjub::Fr::from_bytes( + &bpm.spend.rcv.try_into().unwrap(), + ) + .unwrap(), + alpha: masp_primitives::jubjub::Fr::from_bytes( + &bpm.spend.alpha.try_into().unwrap(), + ) + .unwrap(), + }); + + bparams + .output_params + .push(sapling::builder::OutputBuildParams { + rcv: masp_primitives::jubjub::Fr::from_bytes( + &bpm.output.rcv.try_into().unwrap(), + ) + .unwrap(), + rseed: bpm.output.rcm.try_into().unwrap(), + ..sapling::builder::OutputBuildParams::default() + }); + + bparams + .convert_params + .push(sapling::builder::ConvertBuildParams { + rcv: masp_primitives::jubjub::Fr::from_bytes( + &bpm.convert.rcv.try_into().unwrap(), + ) + .unwrap(), + }); + } + bparams + }) +} + pub struct MapSaplingSigAuth( pub HashMap::AuthSig>, ); diff --git a/packages/shared/lib/src/sdk/mod.rs b/packages/shared/lib/src/sdk/mod.rs index 0ff5df870..bfa25f9b6 100644 --- a/packages/shared/lib/src/sdk/mod.rs +++ b/packages/shared/lib/src/sdk/mod.rs @@ -13,7 +13,7 @@ use crate::utils::set_panic_hook; #[cfg(feature = "web")] use crate::utils::to_bytes; use crate::utils::to_js_result; -use args::{generate_masp_build_params, masp_sign, BuildParams, MapSaplingSigAuth}; +use args::{generate_rng_build_params, masp_sign, BuildParams, MapSaplingSigAuth}; use gloo_utils::format::JsValueSerdeExt; use js_sys::Uint8Array; use namada_sdk::address::{Address, MASP}; @@ -27,6 +27,7 @@ use namada_sdk::ibc::core::host::types::identifiers::{ChannelId, PortId}; use namada_sdk::io::NamadaIo; use namada_sdk::key::{common, ed25519, SigScheme}; use namada_sdk::masp::ShieldedContext; +use namada_sdk::masp_primitives::transaction::components::sapling::builder::StoredBuildParams; use namada_sdk::masp_primitives::transaction::components::sapling::fees::InputView; use namada_sdk::masp_primitives::zip32::{ExtendedFullViewingKey, ExtendedKey}; use namada_sdk::rpc::{query_epoch, InnerTxResult}; @@ -50,22 +51,6 @@ use std::str::FromStr; use tx::MaspSigningData; use wasm_bindgen::{prelude::wasm_bindgen, JsError, JsValue}; -// Maximum number of spend description randomness parameters that can be -// generated on the hardware wallet. It is hard to compute the exact required -// number because a given MASP source could be distributed amongst several -// notes. -const MAX_HW_SPEND: usize = 15; -// Maximum number of convert description randomness parameters that can be -// generated on the hardware wallet. It is hard to compute the exact required -// number because the number of conversions that are used depends on the -// protocol's current state. -const MAX_HW_CONVERT: usize = 15; -// Maximum number of output description randomness parameters that can be -// generated on the hardware wallet. It is hard to compute the exact required -// number because the number of outputs depends on the number of dummy outputs -// introduced. -const MAX_HW_OUTPUT: usize = 15; - /// Represents the Sdk public API. #[wasm_bindgen] pub struct Sdk { @@ -494,10 +479,14 @@ impl Sdk { shielded_transfer_msg: &[u8], wrapper_tx_msg: &[u8], ) -> Result { - let mut args = args::shielded_transfer_tx_args(shielded_transfer_msg, wrapper_tx_msg)?; - let bparams = - generate_masp_build_params(MAX_HW_SPEND, MAX_HW_CONVERT, MAX_HW_OUTPUT, &args.tx) - .await?; + let (mut args, bparams) = + args::shielded_transfer_tx_args(shielded_transfer_msg, wrapper_tx_msg)?; + + let bparams = if let Some(bparams) = bparams { + BuildParams::StoredBuildParams(bparams) + } else { + generate_rng_build_params() + }; let _ = &self.namada.shielded_mut().await.load().await?; @@ -541,8 +530,7 @@ impl Sdk { let bparams = if let Some(bparams) = bparams { BuildParams::StoredBuildParams(bparams) } else { - generate_masp_build_params(MAX_HW_SPEND, MAX_HW_CONVERT, MAX_HW_OUTPUT, &args.tx) - .await? + generate_rng_build_params() }; let _ = &self.namada.shielded_mut().await.load().await?; @@ -577,10 +565,13 @@ impl Sdk { shielding_transfer_msg: &[u8], wrapper_tx_msg: &[u8], ) -> Result { - let mut args = args::shielding_transfer_tx_args(shielding_transfer_msg, wrapper_tx_msg)?; - let bparams = - generate_masp_build_params(MAX_HW_SPEND, MAX_HW_CONVERT, MAX_HW_OUTPUT, &args.tx) - .await?; + let (mut args, bparams) = + args::shielding_transfer_tx_args(shielding_transfer_msg, wrapper_tx_msg)?; + let bparams = if let Some(bparams) = bparams { + BuildParams::StoredBuildParams(bparams) + } else { + generate_rng_build_params() + }; let _ = &self.namada.shielded_mut().await.load().await?; let (tx, signing_data, _) = match bparams { @@ -601,27 +592,11 @@ impl Sdk { wrapper_tx_msg: &[u8], ) -> Result { let args = args::ibc_transfer_tx_args(ibc_transfer_msg, wrapper_tx_msg)?; - let bparams = - generate_masp_build_params(MAX_HW_SPEND, MAX_HW_CONVERT, MAX_HW_OUTPUT, &args.tx) - .await?; - - let ((tx, signing_data, _), bparams) = match bparams { - BuildParams::RngBuildParams(mut bparams) => { - let tx = build_ibc_transfer(&self.namada, &args, &mut bparams).await?; - let bparams = bparams - .to_stored() - .ok_or_err_msg("Cannot convert bparams to stored")?; - - (tx, bparams) - } - BuildParams::StoredBuildParams(mut bparams) => { - let tx = build_ibc_transfer(&self.namada, &args, &mut bparams).await?; - - (tx, bparams) - } - }; + // TODO: we do not support ibc unshielding yet + let mut bparams = StoredBuildParams::default(); + let (tx, signing_data, _) = build_ibc_transfer(&self.namada, &args, &mut bparams).await?; - // As we can't get ExtendedFullViewingKeys from the tx args we need to get them from the + // As we can't get ExtendedFullViewingKeys from the tx args, we need to get them from the // MASP Builder section of transaction let masp_signing_data = if let Some(shielded_hash) = signing_data.shielded_hash { let masp_builder = tx diff --git a/packages/types/src/tx/schema/transfer.ts b/packages/types/src/tx/schema/transfer.ts index 38d32d43d..dfa4493a0 100644 --- a/packages/types/src/tx/schema/transfer.ts +++ b/packages/types/src/tx/schema/transfer.ts @@ -123,13 +123,24 @@ export class ShieldedTransferMsgValue { @field({ type: option("string") }) gasSpendingKey?: string; - constructor({ data, gasSpendingKey }: ShieldedTransferProps) { + @field({ type: option(vec(BparamsMsgValue)) }) + bparams?: BparamsMsgValue[]; + + constructor({ data, gasSpendingKey, bparams }: ShieldedTransferProps) { Object.assign(this, { data: data.map( (shieldedTransferDataProps) => new ShieldedTransferDataMsgValue(shieldedTransferDataProps) ), gasSpendingKey, + + bparams: bparams?.map((bparam) => { + return new BparamsMsgValue({ + spend: new BparamsSpendMsgValue(bparam.spend), + output: new BparamsOutputMsgValue(bparam.output), + convert: new BparamsConvertMsgValue(bparam.convert), + }); + }), }); } } @@ -159,6 +170,9 @@ export class ShieldingTransferMsgValue { @field({ type: vec(ShieldingTransferDataMsgValue) }) data!: ShieldingTransferDataMsgValue[]; + @field({ type: option(vec(BparamsMsgValue)) }) + bparams?: BparamsMsgValue[]; + constructor({ data, target }: ShieldingTransferProps) { Object.assign(this, { target,