diff --git a/apps/extension/src/Approvals/ConfirmSignLedgerTx.tsx b/apps/extension/src/Approvals/ConfirmSignLedgerTx.tsx index ff7152d309..6f6a8e113b 100644 --- a/apps/extension/src/Approvals/ConfirmSignLedgerTx.tsx +++ b/apps/extension/src/Approvals/ConfirmSignLedgerTx.tsx @@ -2,21 +2,29 @@ import clsx from "clsx"; import { ReactNode, useCallback, useEffect, useState } from "react"; import { ActionButton, Stack } from "@namada/components"; -import { Ledger, makeBip44Path } from "@namada/sdk/web"; +import { + Ledger, + makeBip44Path, + makeSaplingPath, + TxType, +} from "@namada/sdk/web"; import { LedgerError, ResponseSign } from "@zondax/ledger-namada"; -import { fromBase64 } from "@cosmjs/encoding"; +import { fromBase64, toBase64 } from "@cosmjs/encoding"; import { chains } from "@namada/chains"; +import { TransferProps } from "@namada/types"; import { PageHeader } from "App/Common"; import { ApprovalDetails, Status } from "Approvals/Approvals"; import { QueryPendingTxBytesMsg, + ReplaceMaspSignaturesMsg, SubmitApprovedSignLedgerTxMsg, + SubmitApprovedSignTxMsg, } from "background/approvals"; import { QueryAccountDetailsMsg } from "background/keyring"; import { useRequester } from "hooks/useRequester"; import { Ports } from "router"; -import { closeCurrentTab } from "utils"; +import { closeCurrentTab, parseTransferType } from "utils"; import { ApproveIcon } from "./ApproveIcon"; import { LedgerIcon } from "./LedgerIcon"; import { StatusBox } from "./StatusBox"; @@ -64,7 +72,7 @@ export const ConfirmSignLedgerTx: React.FC = ({ details }) => { useState(); const [isLedgerConnected, setIsLedgerConnected] = useState(false); const [ledger, setLedger] = useState(); - const { msgId, signer } = details; + const { msgId, signer, txDetails } = details; useEffect(() => { if (status === Status.Completed) { @@ -72,6 +80,32 @@ export const ConfirmSignLedgerTx: React.FC = ({ details }) => { } }, [status]); + const signMaspTx = async ( + ledger: Ledger, + bytes: Uint8Array, + path: string + ): Promise<{ sbar: Uint8Array; rbar: Uint8Array }> => { + const signMaspSpendsResponse = await ledger.namadaApp.signMaspSpends( + path, + Buffer.from(bytes) + ); + + if (signMaspSpendsResponse.returnCode !== LedgerError.NoErrors) { + throw new Error( + `Signing masp spends error encountered: ${signMaspSpendsResponse.errorMessage}` + ); + } + + const spendSignatureResponse = await ledger.namadaApp.getSpendSignature(); + if (spendSignatureResponse.returnCode !== LedgerError.NoErrors) { + throw new Error( + `Getting spends signature error encountered: ${signMaspSpendsResponse.errorMessage}` + ); + } + + return spendSignatureResponse; + }; + const signLedgerTx = async ( ledger: Ledger, bytes: Uint8Array, @@ -90,6 +124,37 @@ export const ConfirmSignLedgerTx: React.FC = ({ details }) => { return signature; }; + const handleMaspSignTx = useCallback( + async ( + ledger: Ledger, + tx: string, + zip32Path: string, + signatures: string[] + ) => { + const { sbar, rbar } = await signMaspTx( + ledger, + fromBase64(tx), + zip32Path + ); + const signature = toBase64(new Uint8Array([...rbar, ...sbar])); + signatures.push(signature); + }, + [] + ); + + const handleSignTx = useCallback( + async ( + ledger: Ledger, + tx: string, + bip44Path: string, + signatures: ResponseSign[] + ) => { + const signature = await signLedgerTx(ledger, fromBase64(tx), bip44Path); + signatures.push(signature); + }, + [] + ); + const handleApproveLedgerSignTx = useCallback( async (e: React.FormEvent): Promise => { e.preventDefault(); @@ -122,6 +187,8 @@ export const ConfirmSignLedgerTx: React.FC = ({ details }) => { setStepTwoDescription("Preparing transaction..."); try { + // TODO: we have to check if the signer is disposable or not + const accountDetails = await requester.sendMessage( Ports.Background, new QueryAccountDetailsMsg(signer) @@ -134,7 +201,7 @@ export const ConfirmSignLedgerTx: React.FC = ({ details }) => { change: accountDetails.path.change || 0, index: accountDetails.path.index || 0, }; - const bip44Path = makeBip44Path(chains.namada.bip44.coinType, path); + const pendingTxs = await requester.sendMessage( Ports.Background, new QueryPendingTxBytesMsg(msgId) @@ -146,8 +213,6 @@ export const ConfirmSignLedgerTx: React.FC = ({ details }) => { ); } - const signatures: ResponseSign[] = []; - let txIndex = 0; const txCount = pendingTxs.length; const stepTwoText = "Approve on your device"; @@ -156,6 +221,24 @@ export const ConfirmSignLedgerTx: React.FC = ({ details }) => { setStepTwoDescription(

{stepTwoText}

); } + // Those collections are being mutated in the loop + const signatures: ResponseSign[] = []; + const maspSignatures: string[] = []; + + const transferTypes = txDetails.flatMap((details) => + details.commitments + .filter((cmt) => cmt.txType === TxType.Transfer) + .map( + (cmt) => + parseTransferType(cmt as TransferProps, details.wrapperFeePayer) + .type + ) + ); + // For now we work under the assumption that we can't batch transfers from masp with other tx types + const fromMasp = + transferTypes.includes("Shielded") || + transferTypes.includes("Unshielding"); + for await (const tx of pendingTxs) { if (txCount > 1) { setStepTwoDescription( @@ -166,20 +249,39 @@ export const ConfirmSignLedgerTx: React.FC = ({ details }) => {

); } - const signature = await signLedgerTx( - ledger, - fromBase64(tx), - bip44Path - ); - signatures.push(signature); + + if (fromMasp) { + const zip32Path = makeSaplingPath(chains.namada.bip44.coinType, { + account: path.account, + }); + // Adds new signature to the collection + await handleMaspSignTx(ledger, tx, zip32Path, maspSignatures); + } else { + const bip44Path = makeBip44Path(chains.namada.bip44.coinType, path); + // Adds new signature to the collection + await handleSignTx(ledger, tx, bip44Path, signatures); + } + txIndex++; } setStepTwoDescription(

Submitting...

); - await requester.sendMessage( - Ports.Background, - new SubmitApprovedSignLedgerTxMsg(msgId, signatures) - ); + + if (fromMasp) { + await requester.sendMessage( + Ports.Background, + new ReplaceMaspSignaturesMsg(msgId, maspSignatures) + ); + await requester.sendMessage( + Ports.Background, + new SubmitApprovedSignTxMsg(msgId, signer) + ); + } else { + await requester.sendMessage( + Ports.Background, + new SubmitApprovedSignLedgerTxMsg(msgId, signatures) + ); + } setStatus(Status.Completed); } catch (e) { diff --git a/apps/extension/src/Approvals/ConfirmSignTx.tsx b/apps/extension/src/Approvals/ConfirmSignTx.tsx index d62b406406..3be85db9af 100644 --- a/apps/extension/src/Approvals/ConfirmSignTx.tsx +++ b/apps/extension/src/Approvals/ConfirmSignTx.tsx @@ -4,7 +4,7 @@ import { useNavigate } from "react-router-dom"; import { ActionButton, Input, Stack } from "@namada/components"; import { PageHeader } from "App/Common"; import { ApprovalDetails, Status } from "Approvals/Approvals"; -import { SubmitApprovedSignTxMsg } from "background/approvals"; +import { SignMaspMsg, SubmitApprovedSignTxMsg } from "background/approvals"; import { UnlockVaultMsg } from "background/vault"; import { useRequester } from "hooks/useRequester"; import { Ports } from "router"; @@ -41,6 +41,13 @@ export const ConfirmSignTx: React.FC = ({ details }) => { throw new Error("Invalid password!"); } + // TODO: ideally we should only calling this for Unshielding and Shielded Transfers, + // it should not break anything it's just unnecessary computation + await requester.sendMessage( + Ports.Background, + new SignMaspMsg(msgId, signer) + ); + await requester.sendMessage( Ports.Background, new SubmitApprovedSignTxMsg(msgId, signer) diff --git a/apps/extension/src/background/approvals/handler.ts b/apps/extension/src/background/approvals/handler.ts index 0f9c3665f9..b03e2c8dff 100644 --- a/apps/extension/src/background/approvals/handler.ts +++ b/apps/extension/src/background/approvals/handler.ts @@ -16,7 +16,9 @@ import { QueryTxDetailsMsg, RejectSignArbitraryMsg, RejectSignTxMsg, + ReplaceMaspSignaturesMsg, RevokeConnectionMsg, + SignMaspMsg, SubmitApprovedSignArbitraryMsg, SubmitApprovedSignLedgerTxMsg, SubmitApprovedSignTxMsg, @@ -113,6 +115,13 @@ export const getHandler: (service: ApprovalsService) => Handler = (service) => { env, msg as SubmitApprovedSignLedgerTxMsg ); + case ReplaceMaspSignaturesMsg: + return handleReplaceMaspSignaturesMsg(service)( + env, + msg as ReplaceMaspSignaturesMsg + ); + case SignMaspMsg: + return handleSignMaspMsg(service)(env, msg as SignMaspMsg); default: throw new Error("Unknown msg type"); @@ -279,6 +288,22 @@ const handleSubmitApprovedSignLedgerTxMsg: ( }; }; +const handleReplaceMaspSignaturesMsg: ( + service: ApprovalsService +) => InternalHandler = (service) => { + return async (_, { msgId, signatures }) => { + return await service.replaceMaspSignatures(msgId, signatures); + }; +}; + +const handleSignMaspMsg: ( + service: ApprovalsService +) => InternalHandler = (service) => { + return async (_, { msgId, signer }) => { + return await service.signMasp(msgId, signer); + }; +}; + const handleCheckIsApprovedSite: ( service: ApprovalsService ) => InternalHandler = (service) => { diff --git a/apps/extension/src/background/approvals/init.ts b/apps/extension/src/background/approvals/init.ts index 37e95732a1..e7d534eaa3 100644 --- a/apps/extension/src/background/approvals/init.ts +++ b/apps/extension/src/background/approvals/init.ts @@ -16,7 +16,9 @@ import { QueryTxDetailsMsg, RejectSignArbitraryMsg, RejectSignTxMsg, + ReplaceMaspSignaturesMsg, RevokeConnectionMsg, + SignMaspMsg, SubmitApprovedSignArbitraryMsg, SubmitApprovedSignLedgerTxMsg, SubmitApprovedSignTxMsg, @@ -36,6 +38,7 @@ export function init(router: Router, service: ApprovalsService): void { router.registerMessage(SubmitApprovedSignTxMsg); router.registerMessage(SubmitApprovedSignArbitraryMsg); router.registerMessage(SubmitApprovedSignLedgerTxMsg); + router.registerMessage(ReplaceMaspSignaturesMsg); router.registerMessage(IsConnectionApprovedMsg); router.registerMessage(ApproveConnectInterfaceMsg); router.registerMessage(ConnectInterfaceResponseMsg); @@ -47,6 +50,7 @@ export function init(router: Router, service: ApprovalsService): void { router.registerMessage(QueryTxDetailsMsg); router.registerMessage(QuerySignArbitraryDataMsg); router.registerMessage(QueryPendingTxBytesMsg); + router.registerMessage(SignMaspMsg); router.addHandler(ROUTE, getHandler(service)); } diff --git a/apps/extension/src/background/approvals/messages.ts b/apps/extension/src/background/approvals/messages.ts index 400ecf2991..397c5978dc 100644 --- a/apps/extension/src/background/approvals/messages.ts +++ b/apps/extension/src/background/approvals/messages.ts @@ -10,6 +10,7 @@ export enum MessageType { SubmitApprovedSignTx = "submit-approved-sign-tx", SubmitApprovedSignArbitrary = "submit-approved-sign-arbitrary", SubmitApprovedSignLedgerTx = "submit-approved-sign-ledger-tx", + ReplaceMaspSignatures = "replace-masp-signatures", RejectSignArbitrary = "reject-sign-arbitrary", ConnectInterfaceResponse = "connect-interface-response", DisconnectInterfaceResponse = "disconnect-interface-response", @@ -19,6 +20,7 @@ export enum MessageType { QuerySignArbitraryData = "query-sign-arbitrary-data", QueryPendingTxBytes = "query-pending-tx-bytes", CheckIsApprovedSite = "check-is-approved-site", + SignMaspMsg = "sign-masp", } export class SubmitApprovedSignTxMsg extends Message { @@ -46,6 +48,31 @@ export class SubmitApprovedSignTxMsg extends Message { } } +export class SignMaspMsg extends Message { + public static type(): MessageType { + return MessageType.SignMaspMsg; + } + + constructor( + public readonly msgId: string, + public readonly signer: string + ) { + super(); + } + + validate(): void { + validateProps(this, ["msgId", "signer"]); + } + + route(): string { + return ROUTE; + } + + type(): string { + return SignMaspMsg.type(); + } +} + export class SubmitApprovedSignLedgerTxMsg extends Message { public static type(): MessageType { return MessageType.SubmitApprovedSignLedgerTx; @@ -71,6 +98,32 @@ export class SubmitApprovedSignLedgerTxMsg extends Message { } } +export class ReplaceMaspSignaturesMsg extends Message { + public static type(): MessageType { + return MessageType.ReplaceMaspSignatures; + } + + constructor( + public readonly msgId: string, + // base64 encoded + public readonly signatures: string[] + ) { + super(); + } + + validate(): void { + validateProps(this, ["msgId", "signatures"]); + } + + route(): string { + return ROUTE; + } + + type(): string { + return ReplaceMaspSignaturesMsg.type(); + } +} + export class RejectSignTxMsg extends Message { public static type(): MessageType { return MessageType.RejectSignTx; diff --git a/apps/extension/src/background/approvals/service.ts b/apps/extension/src/background/approvals/service.ts index 88964bef99..64e372f728 100644 --- a/apps/extension/src/background/approvals/service.ts +++ b/apps/extension/src/background/approvals/service.ts @@ -1,9 +1,15 @@ -import { toBase64 } from "@cosmjs/encoding"; +import { fromBase64, toBase64 } from "@cosmjs/encoding"; import { v4 as uuid } from "uuid"; import browser, { Windows } from "webextension-polyfill"; import { KVStore } from "@namada/storage"; -import { SignArbitraryResponse, TxDetails } from "@namada/types"; +import { + Message, + SignArbitraryResponse, + SigningDataMsgValue, + TxDetails, + TxProps, +} from "@namada/types"; import { paramsToUrl } from "@namada/utils"; import { ResponseSign } from "@zondax/ledger-namada"; @@ -147,8 +153,8 @@ export class ApprovalsService { const { tx } = this.sdkService.getSdk(); try { - const signedTxs = pendingTx.txs.map(({ bytes }, i) => { - return tx.appendSignature(bytes, responseSign[i]); + const signedTxs = pendingTx.txs.map((pendingTx, i) => { + return tx.appendSignature(pendingTx.bytes, responseSign[i]); }); resolvers.resolve(signedTxs); } catch (e) { @@ -158,6 +164,71 @@ export class ApprovalsService { await this.clearPendingSignature(msgId); } + /** + * Modifies pending transaction data by appending real MASP signatures + * + * @async + * @param {string} msgId - message ID + * @param {string} signer - signer + * @throws {Error} - if pending transaction data is not found + * @returns void + */ + async signMasp(msgId: string, signer: string): Promise { + const pendingTx = await this.txStore.get(msgId); + + if (!pendingTx) { + throw new Error(ApprovalErrors.PendingSigningDataNotFound(msgId)); + } + + const txs: TxProps[] = []; + for await (const tx of pendingTx.txs) { + const bytes = await this.keyRingService.signMasp(tx, signer); + txs.push({ + ...tx, + bytes, + }); + } + + await this.txStore.set(msgId, { ...pendingTx, txs }); + } + + /** + * Modifies pending transaction data by replacing MASP signatures + * + * @async + * @param {string} msgId - message ID + * @param {string[]} signatures - MASP signatures + * @throws {Error} - if pending transaction data is not found + * @returns void + */ + async replaceMaspSignatures( + msgId: string, + signatures: string[] + ): Promise { + const pendingTx = await this.txStore.get(msgId); + if (!pendingTx) { + throw new Error(ApprovalErrors.TransactionDataNotFound(msgId)); + } + + const { tx: sdkTx } = this.sdkService.getSdk(); + + const txsWithSignatures = signatures.map((signature, i) => { + const tx = pendingTx.txs[i]; + const signingData = tx.signingData.map((signingData) => + new Message().encode(new SigningDataMsgValue(signingData)) + ); + const txBytes = sdkTx.appendMaspSignature( + tx.bytes, + signingData, + fromBase64(signature) + ); + + return { ...tx, bytes: txBytes }; + }); + + await this.txStore.set(msgId, { ...pendingTx, txs: txsWithSignatures }); + } + async submitSignArbitrary( popupTabId: number, msgId: string, diff --git a/apps/extension/src/background/keyring/keyring.ts b/apps/extension/src/background/keyring/keyring.ts index ef73b1178a..2ca1136a6a 100644 --- a/apps/extension/src/background/keyring/keyring.ts +++ b/apps/extension/src/background/keyring/keyring.ts @@ -739,7 +739,6 @@ export class KeyRing { chainId: string ): Promise { await this.vaultService.assertIsUnlocked(); - const disposableKey = await this.localStorage.getDisposableSigner(signer); // If disposable key is provided, use it for signing @@ -748,15 +747,23 @@ export class KeyRing { disposableKey.privateKey : await this.getSigningKey(signer); + const { signing } = this.sdkService.getSdk(); + + return await signing.sign(txProps, key, chainId); + } + + async signMasp(txProps: TxProps, signer: string): Promise { + await this.vaultService.assertIsUnlocked(); + + const disposableKey = await this.localStorage.getDisposableSigner(signer); + const realAddress = disposableKey?.realAddress || signer; + // If disposable key is provided, use it to map real address to spending key - const spendingKeys = - disposableKey ? - [await this.getSpendingKey(disposableKey.realAddress)] - : []; + const xsks = [await this.getSpendingKey(realAddress)]; const { signing } = this.sdkService.getSdk(); - return await signing.sign(txProps, key, spendingKeys, chainId); + return await signing.signMasp(txProps, xsks); } async signArbitrary( @@ -775,10 +782,13 @@ export class KeyRing { async queryAccountDetails( address: string ): Promise { + const disposableKey = await this.localStorage.getDisposableSigner(address); + const account = await this.vaultStorage.findOneOrFail( KeyStore, "address", - address + // if we use disposable key, we want to get the real address + disposableKey?.realAddress || address ); if (!account) { return; diff --git a/apps/extension/src/background/keyring/service.ts b/apps/extension/src/background/keyring/service.ts index 7c806f6f03..cd4409f41c 100644 --- a/apps/extension/src/background/keyring/service.ts +++ b/apps/extension/src/background/keyring/service.ts @@ -216,6 +216,10 @@ export class KeyRingService { return await this._keyRing.sign(txProps, signer, chainId); } + async signMasp(txProps: TxProps, signer: string): Promise { + return await this._keyRing.signMasp(txProps, signer); + } + async signArbitrary( signer: string, data: string diff --git a/apps/extension/src/utils/index.ts b/apps/extension/src/utils/index.ts index 12a5d5f208..fadb9da122 100644 --- a/apps/extension/src/utils/index.ts +++ b/apps/extension/src/utils/index.ts @@ -145,7 +145,7 @@ export const isShieldedPool = (address: string): boolean => { */ export const parseTransferType = ( tx: TransferProps, - wrapperFeePayer?: string + wrapperFeePayer: string ): { source: string; target: string; type: TransferType } => { const { sources, targets } = tx; const source = sources[0].owner; diff --git a/apps/extension/webpack.config.js b/apps/extension/webpack.config.js index 6411a9b8cf..60db123fc2 100644 --- a/apps/extension/webpack.config.js +++ b/apps/extension/webpack.config.js @@ -91,7 +91,7 @@ const plugins = [ MANIFEST_PATH, ...(NODE_ENV === "development" && TARGET === "firefox" ? [MANIFEST_V2_DEV_ONLY_PATH] - : []), + : []), ], output: { fileName: "./manifest.json", @@ -140,7 +140,7 @@ module.exports = { devtool: NODE_ENV === "development" && TARGET === "firefox" ? "eval-source-map" - : false, + : false, entry: { content: "./src/content", background: "./src/background", @@ -224,7 +224,7 @@ module.exports = { hints: "warning", maxAssetSize: 200000, maxEntrypointSize: 400000, - assetFilter: function (assetFilename) { + assetFilter: function(assetFilename) { assetFilename.endsWith(".wasm"); }, }, diff --git a/apps/namadillo/public/config.toml b/apps/namadillo/public/config.toml index 23dfc293be..29d5b71031 100644 --- a/apps/namadillo/public/config.toml +++ b/apps/namadillo/public/config.toml @@ -3,4 +3,3 @@ #rpc_url = "" #masp_indexer_url = "" #localnet_enabled = false - diff --git a/apps/namadillo/src/atoms/transfer/services.ts b/apps/namadillo/src/atoms/transfer/services.ts index 9a11a6fd3c..b30a8b2fd5 100644 --- a/apps/namadillo/src/atoms/transfer/services.ts +++ b/apps/namadillo/src/atoms/transfer/services.ts @@ -1,5 +1,7 @@ import { Account, + AccountType, + BparamsMsgValue, GenDisposableSignerResponse, ShieldedTransferMsgValue, ShieldedTransferProps, @@ -108,27 +110,38 @@ export const createShieldedTransferTx = async ( rpcUrl: string, memo?: string ): Promise | undefined> => { - const disposableSigner = await getDisposableSigner(); + const { address: signerAddress, publicKey: signerPublicKey } = + 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, - signerAddress: disposableSigner.address, + signerAddress, buildTxFn: async (workerLink) => { const msgValue = new ShieldedTransferMsgValue({ gasSpendingKey: source, data: [{ source, target: destination, token, amount }], + bparams, }); const msg: ShieldedTransfer = { type: "shielded-transfer", payload: { account: { ...account, - publicKey: disposableSigner.publicKey, + publicKey: signerPublicKey, }, gasConfig, props: [msgValue], @@ -154,6 +167,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, @@ -164,6 +186,7 @@ export const createShieldingTransferTx = async ( const msgValue = new ShieldingTransferMsgValue({ target: destination, data: [{ source, token, amount }], + bparams, }); const msg: Shield = { type: "shield", @@ -190,28 +213,40 @@ export const createUnshieldingTransferTx = async ( rpcUrl: string, memo?: string ): Promise | undefined> => { - const disposableSigner = await getDisposableSigner(); + const { address: signerAddress, publicKey: signerPublicKey } = + await getDisposableSigner(); + const source = props[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, - signerAddress: disposableSigner.address, + signerAddress, buildTxFn: async (workerLink) => { const msgValue = new UnshieldingTransferMsgValue({ source, gasSpendingKey: source, data: [{ target: destination, token, amount }], + bparams, }); const msg: Unshield = { type: "unshield", payload: { account: { ...account, - publicKey: disposableSigner.publicKey, + publicKey: signerPublicKey, }, gasConfig, props: [msgValue], diff --git a/packages/sdk/src/ledger.ts b/packages/sdk/src/ledger.ts index cf3c9b484e..804b3652dd 100644 --- a/packages/sdk/src/ledger.ts +++ b/packages/sdk/src/ledger.ts @@ -32,6 +32,20 @@ export type LedgerStatus = { const LEDGER_MIN_VERSION_ZIP32 = "2.0.0"; +export type Bparams = { + spend: { + rcv: Uint8Array; + alpha: Uint8Array; + }; + output: { + rcv: Uint8Array; + rcm: Uint8Array; + }; + convert: { + rcv: Uint8Array; + }; +}; + /** * Initialize USB transport * @async @@ -58,7 +72,7 @@ export class Ledger { /** * @param namadaApp - Inititalized NamadaApp class from Zondax package */ - private constructor(public readonly namadaApp: NamadaApp) {} + private constructor(public readonly namadaApp: NamadaApp) { } /** * Initialize and return Ledger class instance with initialized Transport @@ -139,6 +153,57 @@ export class Ledger { } } + /** + * Get Bparams for masp transactions + * @async + * @returns bparams + */ + public async getBparams(): Promise { + // We need to clean the randomness buffers before getting randomness + // to ensure that the randomness is not reused + await this.namadaApp.cleanRandomnessBuffers(); + const results: Bparams[] = []; + let tries = 0; + + // 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(); + if ( + spend_response.returnCode !== LedgerError.NoErrors || + output_response.returnCode !== LedgerError.NoErrors || + convert_response.returnCode !== LedgerError.NoErrors + ) { + continue; + } + + results.push({ + spend: { + rcv: spend_response.rcv, + alpha: spend_response.alpha, + }, + output: { + rcv: output_response.rcv, + rcm: output_response.rcm, + }, + convert: { + rcv: convert_response.rcv, + }, + }); + } + + return results; + } + /** * Prompt user to get viewing key associated with optional path, otherwise, use default path. * Throw exception if app is not initialized, zip32 is not supported, or key is not returned. @@ -277,7 +342,7 @@ export class Ledger { } = await this.status(); throw new Error( `This method requires Zip32 and is unsupported in ${appVersion}! ` + - `Please update to at least ${LEDGER_MIN_VERSION_ZIP32}!` + `Please update to at least ${LEDGER_MIN_VERSION_ZIP32}!` ); } } diff --git a/packages/sdk/src/signing.ts b/packages/sdk/src/signing.ts index ca9560bf66..0ede34e434 100644 --- a/packages/sdk/src/signing.ts +++ b/packages/sdk/src/signing.ts @@ -11,30 +11,23 @@ export class Signing { * Signing constructor * @param sdk - Instance of Sdk struct from wasm lib */ - constructor(protected readonly sdk: SdkWasm) {} + constructor(protected readonly sdk: SdkWasm) { } /** * Sign Namada transaction * @param txProps - TxProps * @param signingKey - private key(s) - * @param xsks - spending keys * @param [chainId] - optional chain ID, will enforce validation if present * @returns signed tx bytes - Promise resolving to Uint8Array */ async sign( txProps: TxProps, signingKey: string | string[], - xsks?: string[], chainId?: string ): Promise { const txMsgValue = new TxMsgValue(txProps); const msg = new Message(); const txBytes = msg.encode(txMsgValue); - const txBytesFinal = - xsks && xsks.length > 0 ? - await this.sdk.sign_masp(xsks, txBytes) - : txBytes; - let signingKeys: string[] = []; if (signingKey instanceof Array) { @@ -43,7 +36,21 @@ export class Signing { signingKeys.push(signingKey); } - return await this.sdk.sign_tx(txBytesFinal, signingKeys, chainId); + return await this.sdk.sign_tx(txBytes, signingKeys, chainId); + } + + /** + * Sign masp spends + * @param txProps - TxProps + * @param xsks - spending keys + * @returns tx with masp spends signed - Promise resolving to Uint8Array + */ + async signMasp(txProps: TxProps, xsks: string[]): Promise { + const txMsgValue = new TxMsgValue(txProps); + const msg = new Message(); + const txBytes = msg.encode(txMsgValue); + + return await this.sdk.sign_masp(xsks, txBytes); } /** diff --git a/packages/sdk/src/tx/tx.ts b/packages/sdk/src/tx/tx.ts index fedaf3289c..67fdb6ed28 100644 --- a/packages/sdk/src/tx/tx.ts +++ b/packages/sdk/src/tx/tx.ts @@ -366,6 +366,21 @@ export class Tx { return deserialize(Buffer.from(batch), TxMsgValue); } + /** + * Append signature for transactions signed by Ledger Hardware Wallet + * @param txBytes - bytes of the transaction + * @param signingData - signing data + * @param signature - masp signature + * @returns transaction bytes with signature appended + */ + appendMaspSignature( + txBytes: Uint8Array, + signingData: Uint8Array[], + signature: Uint8Array + ): Uint8Array { + return this.sdk.sign_masp_ledger(txBytes, signingData, signature); + } + /** * Append signature for transactions signed by Ledger Hardware Wallet * @param txBytes - Serialized transaction diff --git a/packages/shared/lib/src/sdk/args.rs b/packages/shared/lib/src/sdk/args.rs index 15bc829bad..e4750d5577 100644 --- a/packages/shared/lib/src/sdk/args.rs +++ b/packages/shared/lib/src/sdk/args.rs @@ -516,6 +516,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 { @@ -530,6 +558,7 @@ pub struct ShieldedTransferDataMsg { pub struct ShieldedTransferMsg { data: Vec, gas_spending_key: Option, + bparams: Option>, } /// Maps serialized tx_msg into TxShieldedTransfer args. @@ -546,11 +575,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); @@ -574,6 +604,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, @@ -584,7 +615,7 @@ pub fn shielded_transfer_tx_args( gas_spending_key, }; - Ok(args) + Ok((args, bparams)) } #[derive(BorshSerialize, BorshDeserialize, Debug)] @@ -600,6 +631,7 @@ pub struct ShieldingTransferDataMsg { pub struct ShieldingTransferMsg { target: String, data: Vec, + bparams: Option>, } /// Maps serialized tx_msg into TxShieldingTransfer args. @@ -616,9 +648,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![]; @@ -638,6 +674,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, @@ -646,7 +683,7 @@ pub fn shielding_transfer_tx_args( tx_code_path: PathBuf::from("tx_transfer.wasm"), }; - Ok(args) + Ok((args, bparams)) } #[derive(BorshSerialize, BorshDeserialize, Debug)] @@ -663,8 +700,8 @@ pub struct UnshieldingTransferMsg { source: String, data: Vec, gas_spending_key: Option, + bparams: Option>, } - /// Maps serialized tx_msg into TxUnshieldingTransfer args. /// /// # Arguments @@ -679,13 +716,14 @@ pub struct UnshieldingTransferMsg { pub fn unshielding_transfer_tx_args( unshielding_transfer_msg: &[u8], tx_msg: &[u8], -) -> Result { +) -> Result<(args::TxUnshieldingTransfer, Option), JsError> { let unshielding_transfer_msg = UnshieldingTransferMsg::try_from_slice(unshielding_transfer_msg)?; let UnshieldingTransferMsg { source, data, gas_spending_key, + bparams: bparams_msg, } = unshielding_transfer_msg; let source = PseudoExtendedKey::decode(source).0; let gas_spending_key = gas_spending_key.map(|v| PseudoExtendedKey::decode(v).0); @@ -706,6 +744,7 @@ pub fn unshielding_transfer_tx_args( } let tx = tx_msg_into_args(tx_msg)?; + let bparams = bparams_msg_into_bparams(bparams_msg); let args = args::TxUnshieldingTransfer { data: unshielding_transfer_data, @@ -717,7 +756,7 @@ pub fn unshielding_transfer_tx_args( tx_code_path: PathBuf::from("tx_transfer.wasm"), }; - Ok(args) + Ok((args, bparams)) } #[derive(BorshSerialize, BorshDeserialize, Debug)] @@ -989,26 +1028,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 @@ -1075,7 +1099,50 @@ where Ok(()) } -struct MapSaplingSigAuth(HashMap::AuthSig>); +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>, +); impl sapling::MapAuth for MapSaplingSigAuth { fn map_proof( diff --git a/packages/shared/lib/src/sdk/mod.rs b/packages/shared/lib/src/sdk/mod.rs index 995161c959..14d227f39a 100644 --- a/packages/shared/lib/src/sdk/mod.rs +++ b/packages/shared/lib/src/sdk/mod.rs @@ -13,11 +13,13 @@ 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}; +use args::{generate_rng_build_params, masp_sign, BuildParams, MapSaplingSigAuth}; use gloo_utils::format::JsValueSerdeExt; +use js_sys::Uint8Array; use namada_sdk::address::{Address, ImplicitAddress, MASP}; use namada_sdk::args::{GenIbcShieldingTransfer, InputAmount, Query, TxExpiration}; use namada_sdk::borsh::{self, BorshDeserialize}; +use namada_sdk::collections::HashMap; use namada_sdk::eth_bridge::bridge_pool::build_bridge_pool_tx; use namada_sdk::hash::Hash; use namada_sdk::ibc::convert_masp_tx_to_ibc_memo; @@ -25,6 +27,7 @@ use namada_sdk::ibc::core::host::types::identifiers::{ChannelId, PortId}; use namada_sdk::io::NamadaIo; use namada_sdk::key::{common, ed25519, RefTo, 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}; @@ -34,6 +37,7 @@ use namada_sdk::tendermint_rpc::Url; use namada_sdk::token::DenominatedAmount; use namada_sdk::token::{MaspTxId, OptionExt}; use namada_sdk::tx::data::TxType; +use namada_sdk::tx::Section; use namada_sdk::tx::{ build_batch, build_bond, build_claim_rewards, build_ibc_transfer, build_redelegation, build_reveal_pk, build_shielded_transfer, build_shielding_transfer, build_transparent_transfer, @@ -48,22 +52,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 { @@ -147,12 +135,12 @@ impl Sdk { pub async fn load_masp_params( &self, context_dir: JsValue, - chain_id: &str, + chain_id: String, ) -> Result<(), JsValue> { let context_dir = context_dir.as_string().unwrap(); let mut shielded = self.namada.shielded_mut().await; - *shielded = ShieldedContext::new(masp::JSShieldedUtils::new(&context_dir, chain_id).await); + *shielded = ShieldedContext::new(masp::JSShieldedUtils::new(&context_dir, &chain_id).await); Ok(()) } @@ -227,18 +215,57 @@ impl Sdk { } } - let signing_data = tx - .signing_tx_data()? - .iter() - .cloned() - .map(|std| (std, None)) - .collect::)>>(); + to_js_result(borsh::to_vec(&namada_tx)?) + } - // Recreate the tx with the new signatures, we can pass None for masp_signing_data as it - // was already used - let tx = tx::Tx::new(namada_tx, &borsh::to_vec(&tx.args())?, signing_data)?; + // TODO: this should be unified with sign_masp somehow + pub fn sign_masp_ledger( + &self, + tx: Vec, + signing_data: Box<[Uint8Array]>, + signature: Vec, + ) -> Result { + let mut namada_tx: Tx = borsh::from_slice(&tx)?; + let signing_data = signing_data + .iter() + .map(|sd| { + borsh::from_slice(&sd.to_vec()).expect("Expected to deserialize signing data") + }) + .collect::>(); + + for signing_data in signing_data { + let signing_tx_data = signing_data.to_signing_tx_data()?; + if let Some(shielded_hash) = signing_tx_data.shielded_hash { + let mut masp_tx = namada_tx + .get_masp_section(&shielded_hash) + .expect("Expected to find the indicated MASP Transaction") + .clone(); + + let mut authorizations = HashMap::new(); + + let signature = + namada_sdk::masp_primitives::sapling::redjubjub::Signature::try_from_slice( + &signature.to_vec(), + )?; + // TODO: this works only if we assume that we do one + // shielded transfer in the transaction + authorizations.insert(0_usize, signature); + + masp_tx = (*masp_tx) + .clone() + .map_authorization::( + (), + MapSaplingSigAuth(authorizations), + ) + .freeze() + .unwrap(); + + namada_tx.remove_masp_section(&shielded_hash); + namada_tx.add_section(Section::MaspTx(masp_tx)); + } + } - to_js_result(borsh::to_vec(&tx)?) + to_js_result(borsh::to_vec(&namada_tx)?) } pub async fn sign_tx( @@ -467,10 +494,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?; @@ -508,11 +539,14 @@ impl Sdk { unshielding_transfer_msg: &[u8], wrapper_tx_msg: &[u8], ) -> Result { - let mut args = + let (mut args, bparams) = args::unshielding_transfer_tx_args(unshielding_transfer_msg, wrapper_tx_msg)?; - let bparams = - generate_masp_build_params(MAX_HW_SPEND, MAX_HW_CONVERT, MAX_HW_OUTPUT, &args.tx) - .await?; + + let bparams = if let Some(bparams) = bparams { + BuildParams::StoredBuildParams(bparams) + } else { + generate_rng_build_params() + }; let _ = &self.namada.shielded_mut().await.load().await?; @@ -546,10 +580,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 { @@ -570,27 +607,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/messages/index.ts b/packages/types/src/tx/messages/index.ts index c98cf101fa..7f3c0a160e 100644 --- a/packages/types/src/tx/messages/index.ts +++ b/packages/types/src/tx/messages/index.ts @@ -10,6 +10,7 @@ export class Message implements IMessage { try { return serialize(value); } catch (e) { + console.log("error", e); throw new Error(`Unable to serialize message: ${e}`); } } diff --git a/packages/types/src/tx/schema/transfer.ts b/packages/types/src/tx/schema/transfer.ts index 8622cce513..e03aa0aaa4 100644 --- a/packages/types/src/tx/schema/transfer.ts +++ b/packages/types/src/tx/schema/transfer.ts @@ -47,6 +47,54 @@ export class TransparentTransferMsgValue { } } +export class BparamsSpendMsgValue { + @field({ type: vec("u8") }) + rcv!: Uint8Array; + + @field({ type: vec("u8") }) + alpha!: Uint8Array; + + constructor(data: BparamsSpendMsgValue) { + Object.assign(this, data); + } +} + +export class BparamsOutputMsgValue { + @field({ type: vec("u8") }) + rcv!: Uint8Array; + + @field({ type: vec("u8") }) + rcm!: Uint8Array; + + constructor(data: BparamsOutputMsgValue) { + Object.assign(this, data); + } +} + +export class BparamsConvertMsgValue { + @field({ type: vec("u8") }) + rcv!: Uint8Array; + + constructor(data: BparamsConvertMsgValue) { + Object.assign(this, data); + } +} + +export class BparamsMsgValue { + @field({ type: BparamsSpendMsgValue }) + spend!: BparamsSpendMsgValue; + + @field({ type: BparamsOutputMsgValue }) + output!: BparamsOutputMsgValue; + + @field({ type: BparamsConvertMsgValue }) + convert!: BparamsConvertMsgValue; + + constructor(data: BparamsMsgValue) { + Object.assign(this, data); + } +} + /** * Shielded Transfer schemas */ @@ -75,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), + }); + }), }); } } @@ -111,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, @@ -150,7 +212,15 @@ export class UnshieldingTransferMsgValue { @field({ type: option("string") }) gasSpendingKey?: string; - constructor({ source, data, gasSpendingKey }: UnshieldingTransferProps) { + @field({ type: option(vec(BparamsMsgValue)) }) + bparams?: BparamsMsgValue[]; + + constructor({ + source, + data, + gasSpendingKey, + bparams, + }: UnshieldingTransferProps) { Object.assign(this, { source, data: data.map( @@ -158,6 +228,13 @@ export class UnshieldingTransferMsgValue { new UnshieldingTransferDataMsgValue(unshieldingTransferDataProps) ), gasSpendingKey, + bparams: bparams?.map((bparam) => { + return new BparamsMsgValue({ + spend: new BparamsSpendMsgValue(bparam.spend), + output: new BparamsOutputMsgValue(bparam.output), + convert: new BparamsConvertMsgValue(bparam.convert), + }); + }), }); } } diff --git a/yarn.lock b/yarn.lock index 4f12c55994..3a7c1d82a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3209,7 +3209,7 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/devices@npm:8.4.4, @ledgerhq/devices@npm:^8.4.4": +"@ledgerhq/devices@npm:^8.4.4": version: 8.4.4 resolution: "@ledgerhq/devices@npm:8.4.4" dependencies: @@ -3228,18 +3228,6 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/hw-transport-webhid@npm:^6.29.4": - version: 6.30.0 - resolution: "@ledgerhq/hw-transport-webhid@npm:6.30.0" - dependencies: - "@ledgerhq/devices": "npm:8.4.4" - "@ledgerhq/errors": "npm:^6.19.1" - "@ledgerhq/hw-transport": "npm:^6.31.4" - "@ledgerhq/logs": "npm:^6.12.0" - checksum: 10c0/1cb6ddb50127d6cb73d80259e10da687a2b7aa87ebbac8cc3e770ac5b95a3ef0001bdaf77109da0eb62509cb8668a9642858b59cb0ff355c1adb0fe2114c532c - languageName: node - linkType: hard - "@ledgerhq/hw-transport-webusb@npm:^6.29.4": version: 6.29.4 resolution: "@ledgerhq/hw-transport-webusb@npm:6.29.4" @@ -3430,7 +3418,6 @@ __metadata: "@cosmjs/encoding": "npm:^0.29.0" "@dao-xyz/borsh": "npm:^5.1.5" "@ledgerhq/hw-transport": "npm:^6.31.4" - "@ledgerhq/hw-transport-webhid": "npm:^6.29.4" "@ledgerhq/hw-transport-webusb": "npm:^6.29.4" "@svgr/webpack": "npm:^6.3.1" "@types/chrome": "npm:^0.0.237" @@ -3443,7 +3430,7 @@ __metadata: "@types/w3c-web-usb": "npm:^1.0.10" "@types/webextension-polyfill": "npm:^0.10.6" "@types/zxcvbn": "npm:^4.4.1" - "@zondax/ledger-namada": "npm:^1.0.0" + "@zondax/ledger-namada": "npm:^2.0.0" bignumber.js: "npm:^9.1.1" buffer: "npm:^6.0.3" copy-webpack-plugin: "npm:^11.0.0" @@ -3707,11 +3694,10 @@ __metadata: "@cosmjs/encoding": "npm:^0.29.0" "@dao-xyz/borsh": "npm:^5.1.5" "@ledgerhq/hw-transport": "npm:^6.31.4" - "@ledgerhq/hw-transport-webhid": "npm:^6.29.4" "@ledgerhq/hw-transport-webusb": "npm:^6.29.4" "@types/jest": "npm:^29.5.12" "@types/node": "npm:^20.11.4" - "@zondax/ledger-namada": "npm:^1.0.0" + "@zondax/ledger-namada": "npm:^2.0.0" babel-jest: "npm:^29.0.3" bignumber.js: "npm:^9.1.1" buffer: "npm:^6.0.3" @@ -3727,6 +3713,7 @@ __metadata: jest-mock-server: "npm:^0.1.0" jsdoc-babel: "npm:^0.5.0" rimraf: "npm:^5.0.5" + semver: "npm:^7.6.3" slip44: "npm:^3.0.18" ts-jest: "npm:^29.2.5" ts-node: "npm:^10.9.1" @@ -6072,12 +6059,12 @@ __metadata: languageName: node linkType: hard -"@zondax/ledger-namada@npm:^1.0.0": - version: 1.0.0 - resolution: "@zondax/ledger-namada@npm:1.0.0" +"@zondax/ledger-namada@npm:^2.0.0": + version: 2.0.0 + resolution: "@zondax/ledger-namada@npm:2.0.0" dependencies: "@ledgerhq/hw-transport": "npm:^6.30.6" - checksum: 10c0/f7490964ccd41f9a63f2bc8d89ea9f01e7d6a58be796db0b454a3f1455dcc8dc8dd578a8642aa0f0799c93f838e8e3afd69f59f7e9947816b9471338c2b9dd63 + checksum: 10c0/1fa2a9a537bc42df01444332529a606ed77f608a2cc1dbb029915ed854ff447976930a4338c2d68d50d98869828cd76b1a4f4b5c2c989fd84af7b66d55dc51fc languageName: node linkType: hard