From 260add836b2cacaa67ee2b69d74690cceab847d0 Mon Sep 17 00:00:00 2001 From: Mateusz Jasiuk Date: Fri, 3 Jan 2025 15:10:04 +0100 Subject: [PATCH] feat: support ledger masp tx wip --- .../src/Approvals/ConfirmSignLedgerTx.tsx | 24 +- .../extension/src/Approvals/ConfirmSignTx.tsx | 1 + .../src/background/approvals/handler.ts | 12 +- .../src/background/approvals/messages.ts | 9 +- .../src/background/approvals/service.ts | 43 +++- .../src/background/keyring/handler.ts | 1 + apps/extension/webpack.config.js | 6 +- apps/namadillo/src/App/WorkerTest.tsx | 2 + apps/namadillo/src/atoms/shield/services.ts | 237 ++++++++++++++++++ apps/namadillo/src/lib/query.ts | 11 +- apps/namadillo/src/workers/MaspTxWorker.ts | 1 + packages/sdk/src/ledger.ts | 53 ++++ packages/sdk/src/signing.ts | 4 +- packages/sdk/src/tx/tx.ts | 17 +- packages/shared/lib/src/sdk/args.rs | 78 +++++- packages/shared/lib/src/sdk/mod.rs | 102 +++++++- packages/types/src/tx/messages/index.ts | 1 + packages/types/src/tx/schema/transfer.ts | 65 ++++- 18 files changed, 632 insertions(+), 35 deletions(-) create mode 100644 apps/namadillo/src/atoms/shield/services.ts diff --git a/apps/extension/src/Approvals/ConfirmSignLedgerTx.tsx b/apps/extension/src/Approvals/ConfirmSignLedgerTx.tsx index ff7152d309..96cd2be4d2 100644 --- a/apps/extension/src/Approvals/ConfirmSignLedgerTx.tsx +++ b/apps/extension/src/Approvals/ConfirmSignLedgerTx.tsx @@ -72,6 +72,20 @@ export const ConfirmSignLedgerTx: React.FC = ({ details }) => { } }, [status]); + const signMaspTx = async ( + ledger: Ledger, + bytes: Uint8Array, + _path: string + ): Promise<{ sbar: Uint8Array; rbar: Uint8Array }> => { + const _response = await ledger.namadaApp.signMaspSpends( + // TODO: + "m/32'/877'/0'", + Buffer.from(bytes) + ); + // TODO + return await ledger.namadaApp.getSpendSignature(); + }; + const signLedgerTx = async ( ledger: Ledger, bytes: Uint8Array, @@ -122,6 +136,7 @@ export const ConfirmSignLedgerTx: React.FC = ({ details }) => { setStepTwoDescription("Preparing transaction..."); try { + console.log("Querying account details for", signer); const accountDetails = await requester.sendMessage( Ports.Background, new QueryAccountDetailsMsg(signer) @@ -147,6 +162,7 @@ export const ConfirmSignLedgerTx: React.FC = ({ details }) => { } const signatures: ResponseSign[] = []; + const maspSignatures: number[][] = []; let txIndex = 0; const txCount = pendingTxs.length; @@ -156,7 +172,7 @@ export const ConfirmSignLedgerTx: React.FC = ({ details }) => { setStepTwoDescription(

{stepTwoText}

); } - for await (const tx of pendingTxs) { + for await (const { bytes: tx } of pendingTxs) { if (txCount > 1) { setStepTwoDescription(

@@ -166,6 +182,10 @@ export const ConfirmSignLedgerTx: React.FC = ({ details }) => {

); } + const asd = await signMaspTx(ledger, fromBase64(tx), bip44Path); + const maspSignature = [...asd.rbar, ...asd.sbar]; + maspSignatures.push(maspSignature); + const signature = await signLedgerTx( ledger, fromBase64(tx), @@ -178,7 +198,7 @@ export const ConfirmSignLedgerTx: React.FC = ({ details }) => { setStepTwoDescription(

Submitting...

); await requester.sendMessage( Ports.Background, - new SubmitApprovedSignLedgerTxMsg(msgId, signatures) + new SubmitApprovedSignLedgerTxMsg(msgId, signatures, maspSignatures) ); setStatus(Status.Completed); diff --git a/apps/extension/src/Approvals/ConfirmSignTx.tsx b/apps/extension/src/Approvals/ConfirmSignTx.tsx index d62b406406..ce762b734e 100644 --- a/apps/extension/src/Approvals/ConfirmSignTx.tsx +++ b/apps/extension/src/Approvals/ConfirmSignTx.tsx @@ -40,6 +40,7 @@ export const ConfirmSignTx: React.FC = ({ details }) => { if (!isAuthenticated) { throw new Error("Invalid password!"); } + console.log("signer", signer); await requester.sendMessage( Ports.Background, diff --git a/apps/extension/src/background/approvals/handler.ts b/apps/extension/src/background/approvals/handler.ts index 0f9c3665f9..65f880333e 100644 --- a/apps/extension/src/background/approvals/handler.ts +++ b/apps/extension/src/background/approvals/handler.ts @@ -274,8 +274,16 @@ const handleQuerySignArbitraryData: ( const handleSubmitApprovedSignLedgerTxMsg: ( service: ApprovalsService ) => InternalHandler = (service) => { - return async ({ senderTabId: popupTabId }, { msgId, responseSign }) => { - return await service.submitSignLedgerTx(popupTabId, msgId, responseSign); + return async ( + { senderTabId: popupTabId }, + { msgId, responseSign, maspSignatures } + ) => { + return await service.submitSignLedgerTx( + popupTabId, + msgId, + responseSign, + maspSignatures + ); }; }; diff --git a/apps/extension/src/background/approvals/messages.ts b/apps/extension/src/background/approvals/messages.ts index 400ecf2991..80dbe70f69 100644 --- a/apps/extension/src/background/approvals/messages.ts +++ b/apps/extension/src/background/approvals/messages.ts @@ -53,13 +53,14 @@ export class SubmitApprovedSignLedgerTxMsg extends Message { constructor( public readonly msgId: string, - public readonly responseSign: ResponseSign[] + public readonly responseSign: ResponseSign[], + public readonly maspSignatures: number[][] ) { super(); } validate(): void { - validateProps(this, ["msgId", "responseSign"]); + validateProps(this, ["msgId", "responseSign", "maspSignatures"]); } route(): string { @@ -258,7 +259,9 @@ export class QueryTxDetailsMsg extends Message { } } -export class QueryPendingTxBytesMsg extends Message { +export class QueryPendingTxBytesMsg extends Message< + { bytes: string; signingData: string[] }[] | undefined +> { public static type(): MessageType { return MessageType.QueryPendingTxBytes; } diff --git a/apps/extension/src/background/approvals/service.ts b/apps/extension/src/background/approvals/service.ts index 88964bef99..c24efb074b 100644 --- a/apps/extension/src/background/approvals/service.ts +++ b/apps/extension/src/background/approvals/service.ts @@ -3,7 +3,12 @@ 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, +} from "@namada/types"; import { paramsToUrl } from "@namada/utils"; import { ResponseSign } from "@zondax/ledger-namada"; @@ -131,7 +136,8 @@ export class ApprovalsService { async submitSignLedgerTx( popupTabId: number, msgId: string, - responseSign: ResponseSign[] + responseSign: ResponseSign[], + maspSignatures: number[][] ): Promise { const pendingTx = await this.txStore.get(msgId); const resolvers = this.getResolver(popupTabId); @@ -146,9 +152,19 @@ export class ApprovalsService { const { tx } = this.sdkService.getSdk(); + console.log("maspSignatures", maspSignatures); try { - const signedTxs = pendingTx.txs.map(({ bytes }, i) => { - return tx.appendSignature(bytes, responseSign[i]); + const signedTxs = pendingTx.txs.map(({ bytes, signingData }, i) => { + const sd = signingData.map((sd) => + new Message().encode(new SigningDataMsgValue(sd)) + ); + const wwww = new Uint8Array(maspSignatures[i]); + + console.log("sd", sd, maspSignatures[i], i); + const asd = tx.appendMaspSignature(bytes, sd, wwww); + console.log("asd", asd); + // return tx.appendSignature(asd, responseSign[i]); + return asd; }); resolvers.resolve(signedTxs); } catch (e) { @@ -333,6 +349,7 @@ export class ApprovalsService { } async approveUpdateDefaultAccount(address: string): Promise { + console.log("approveUpdateDefaultAccount"); const account = await this.keyRingService.queryAccountDetails(address); return this.launchApprovalPopup(TopLevelRoute.ApproveUpdateDefaultAccount, { @@ -368,7 +385,10 @@ export class ApprovalsService { ); } - async queryPendingTxBytes(msgId: string): Promise { + // TODO: all of this is not needed most likelyk + async queryPendingTxBytes( + msgId: string + ): Promise<{ bytes: string; signingData: string[] }[] | undefined> { const pendingTx = await this.txStore.get(msgId); if (!pendingTx) { @@ -376,7 +396,18 @@ export class ApprovalsService { } if (pendingTx.txs) { - return pendingTx.txs.map(({ bytes }) => toBase64(bytes)); + // TODO: + return pendingTx.txs.map(({ bytes, signingData }) => { + console.log("signingData223455", signingData); + const www = { + bytes: toBase64(bytes), + signingData: signingData.map((sd) => + toBase64(new Message().encode(new SigningDataMsgValue(sd))) + ), + }; + console.log("www", www.signingData); + return www; + }); } } diff --git a/apps/extension/src/background/keyring/handler.ts b/apps/extension/src/background/keyring/handler.ts index b4088cc0f6..f8b40fe20b 100644 --- a/apps/extension/src/background/keyring/handler.ts +++ b/apps/extension/src/background/keyring/handler.ts @@ -262,6 +262,7 @@ const handleQueryAccountDetails: ( service: KeyRingService ) => InternalHandler = (service) => { return async (_, { address }) => { + console.log("queryAccountDetails 2222"); return await service.queryAccountDetails(address); }; }; diff --git a/apps/extension/webpack.config.js b/apps/extension/webpack.config.js index d7f982e734..8ee4590e37 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/src/App/WorkerTest.tsx b/apps/namadillo/src/App/WorkerTest.tsx index 4371b90efe..7e02db2eec 100644 --- a/apps/namadillo/src/App/WorkerTest.tsx +++ b/apps/namadillo/src/App/WorkerTest.tsx @@ -164,7 +164,9 @@ export function WorkerTest(): JSX.Element { }; const { payload: encodedTx } = await shieldWorker.unshield(msg); + console.log("Encoded tx", encodedTx); const signedTxs = await signTx(encodedTx, disposableSigner?.address || ""); + console.log("Signed txs", signedTxs); await shieldWorker.broadcast({ type: "broadcast", diff --git a/apps/namadillo/src/atoms/shield/services.ts b/apps/namadillo/src/atoms/shield/services.ts new file mode 100644 index 0000000000..1dc31fbfec --- /dev/null +++ b/apps/namadillo/src/atoms/shield/services.ts @@ -0,0 +1,237 @@ +import { + Account, + GenDisposableSignerResponse, + ShieldedTransferMsgValue, + ShieldingTransferMsgValue, + UnshieldingTransferMsgValue, +} from "@namada/types"; +import BigNumber from "bignumber.js"; +import * as Comlink from "comlink"; +import { EncodedTxData, signTx } from "lib/query"; +import { Address, ChainSettings, GasConfig } from "types"; +import { getSdkInstance } from "utils/sdk"; +import { Shield, ShieldedTransfer, Unshield } from "workers/MaspTxMessages"; +import { + Worker as MaspTxWorkerApi, + registerTransferHandlers as maspTxRegisterTransferHandlers, +} from "workers/MaspTxWorker"; +import MaspTxWorker from "workers/MaspTxWorker?worker"; + +export type ShieldTransferParams = { + sourceAddress: Address; + destinationAddress: Address; + tokenAddress: Address; + amount: BigNumber; + gasConfig: GasConfig; +}; + +export type UnshieldTransferParams = { + sourceAddress: Address; + destinationAddress: Address; + tokenAddress: Address; + amount: BigNumber; + gasConfig: GasConfig; +}; + +export type ShieldedTransferParams = { + sourceAddress: Address; + destinationAddress: Address; + tokenAddress: Address; + amount: BigNumber; + gasConfig: GasConfig; +}; + +export const submitShieldTx = async ( + rpcUrl: string, + account: Account, + chain: ChainSettings, + indexerUrl: string, + params: ShieldTransferParams +): Promise<{ + msg: Shield; + encodedTxData: EncodedTxData; +}> => { + const { + sourceAddress: source, + destinationAddress: target, + tokenAddress: token, + amount, + gasConfig, + } = params; + + maspTxRegisterTransferHandlers(); + const worker = new MaspTxWorker(); + const shieldWorker = Comlink.wrap(worker); + await shieldWorker.init({ + type: "init", + payload: { rpcUrl, token, maspIndexerUrl: "" }, + }); + + const shieldingMsgValue = new ShieldingTransferMsgValue({ + target, + data: [{ source, token, amount }], + }); + + const msg: Shield = { + type: "shield", + payload: { + account, + gasConfig, + shieldingProps: [shieldingMsgValue], + chain, + indexerUrl, + }, + }; + + const { payload: encodedTx } = await shieldWorker.shield(msg); + + const signedTxs = await signTx(encodedTx, source); + + await shieldWorker.broadcast({ + type: "broadcast", + payload: { + encodedTx, + signedTxs, + }, + }); + + worker.terminate(); + + return { msg, encodedTxData: encodedTx }; +}; + +export const submitUnshieldTx = async ( + rpcUrl: string, + account: Account, + chain: ChainSettings, + params: UnshieldTransferParams, + disposableSigner: GenDisposableSignerResponse +): Promise<{ + msg: Unshield; + encodedTxData: EncodedTxData; +}> => { + const { + sourceAddress: source, + destinationAddress: target, + tokenAddress: token, + amount, + gasConfig, + } = params; + + const sdk = await getSdkInstance(); + const ledger = await sdk.initLedger(); + + const bparams = await ledger.getBparams(); + ledger.closeTransport(); + console.log("bparams closed", bparams); + + maspTxRegisterTransferHandlers(); + const worker = new MaspTxWorker(); + const unshieldWorker = Comlink.wrap(worker); + await unshieldWorker.init({ + type: "init", + payload: { rpcUrl, token, maspIndexerUrl: "" }, + }); + + const unshieldingMsgValue = new UnshieldingTransferMsgValue({ + source, + data: [{ target, token, amount }], + gasSpendingKey: source, + bparams, + }); + + const msg: Unshield = { + type: "unshield", + payload: { + account: { + ...account, + // TODO: + // publicKey: disposableSigner.publicKey, + }, + gasConfig, + unshieldingProps: [unshieldingMsgValue], + chain, + }, + }; + + const { payload: encodedTxData } = await unshieldWorker.unshield(msg); + + const signedTxs = await signTx( + encodedTxData, + // disposableSigner.address + account.address + ); + + await unshieldWorker.broadcast({ + type: "broadcast", + payload: { + encodedTx: encodedTxData, + signedTxs, + }, + }); + + worker.terminate(); + + return { msg, encodedTxData }; +}; + +export const submitShieldedTx = async ( + rpcUrl: string, + account: Account, + chain: ChainSettings, + params: ShieldedTransferParams, + disposableSigner: GenDisposableSignerResponse +): Promise<{ + msg: ShieldedTransfer; + encodedTxData: EncodedTxData; +}> => { + const { + sourceAddress: source, + destinationAddress: target, + tokenAddress: token, + amount, + gasConfig, + } = params; + + maspTxRegisterTransferHandlers(); + const worker = new MaspTxWorker(); + const workerApi = Comlink.wrap(worker); + await workerApi.init({ + type: "init", + payload: { rpcUrl, token, maspIndexerUrl: "" }, + }); + + const shieldedTransferMsgValue = new ShieldedTransferMsgValue({ + gasSpendingKey: source, + data: [{ source, target, token, amount }], + }); + + const msg: ShieldedTransfer = { + type: "shielded-transfer", + payload: { + account: { + ...account, + publicKey: disposableSigner.publicKey, + }, + gasConfig, + props: [shieldedTransferMsgValue], + chain, + }, + }; + + const { payload: encodedTxData } = await workerApi.shieldedTransfer(msg); + + const signedTxs = await signTx(encodedTxData, disposableSigner.address); + + await workerApi.broadcast({ + type: "broadcast", + payload: { + encodedTx: encodedTxData, + signedTxs, + }, + }); + + worker.terminate(); + + return { msg, encodedTxData }; +}; diff --git a/apps/namadillo/src/lib/query.ts b/apps/namadillo/src/lib/query.ts index c4bb824263..8e258b1282 100644 --- a/apps/namadillo/src/lib/query.ts +++ b/apps/namadillo/src/lib/query.ts @@ -28,9 +28,9 @@ export type TransactionPair = { export type EncodedTxData = { type: string; txs: TxProps[] & - { - innerTxHashes: string[]; - }[]; + { + innerTxHashes: string[]; + }[]; wrapperTxProps: WrapperTxProps; meta?: { props: T[]; @@ -67,7 +67,7 @@ const getTxProps = ( feeAmount: gasConfig.asset ? toDisplayAmount(gasConfig.asset, gasConfig.gasPrice) - : gasConfig.gasPrice, + : gasConfig.gasPrice, gasLimit: gasConfig.gasLimit, chainId: chain.chainId, publicKey: account.publicKey!, @@ -83,7 +83,7 @@ export const isPublicKeyRevealed = async ( try { publicKey = (await api.apiV1RevealedPublicKeyAddressGet(address)).data ?.publicKey; - } catch {} + } catch { } return Boolean(publicKey); }; @@ -166,6 +166,7 @@ export const signTx = async ( owner, checksums ); + console.log("Signed txs", signedTxBytes); if (!signedTxBytes) { throw new Error("Signing batch Tx failed"); diff --git a/apps/namadillo/src/workers/MaspTxWorker.ts b/apps/namadillo/src/workers/MaspTxWorker.ts index 7a35608826..644da1a446 100644 --- a/apps/namadillo/src/workers/MaspTxWorker.ts +++ b/apps/namadillo/src/workers/MaspTxWorker.ts @@ -115,6 +115,7 @@ async function unshield( await sdk.masp.loadMaspParams("", chain.chainId); + console.log("Unshielding props", unshieldingProps); const encodedTxData = await buildTx( sdk, account, diff --git a/packages/sdk/src/ledger.ts b/packages/sdk/src/ledger.ts index 2bfcb9047d..a38f6759d8 100644 --- a/packages/sdk/src/ledger.ts +++ b/packages/sdk/src/ledger.ts @@ -31,6 +31,20 @@ export type LedgerStatus = { info: ResponseAppInfo; }; +export type Bparams = { + spend: { + rcv: Uint8Array; + alpha: Uint8Array; + }; + output: { + rcv: Uint8Array; + rcm: Uint8Array; + }; + convert: { + rcv: Uint8Array; + }; +}; + /** * Initialize USB transport * @async @@ -146,6 +160,45 @@ export class Ledger { } } + /** + * Get Bparams for masp transactions + * @async + * @returns bparams + */ + public async getBparams(): Promise { + const results: Bparams[] = []; + + // TODO: not sure why ledger sometimes returns errors, so we try to get 15 valid responses + while (results.length < 15) { + 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 and proof gen key associated with optional path, otherwise, use default path. * Throw exception if app is not initialized. diff --git a/packages/sdk/src/signing.ts b/packages/sdk/src/signing.ts index 98456cf2b0..d05733d70d 100644 --- a/packages/sdk/src/signing.ts +++ b/packages/sdk/src/signing.ts @@ -11,7 +11,7 @@ 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 @@ -33,7 +33,7 @@ export class Signing { const txBytesFinal = xsks && xsks.length > 0 ? await this.sdk.sign_masp(xsks, txBytes) - : txBytes; + : txBytes; return await this.sdk.sign_tx(txBytesFinal, signingKey, chainId); } diff --git a/packages/sdk/src/tx/tx.ts b/packages/sdk/src/tx/tx.ts index 3984a393fc..ad291d63aa 100644 --- a/packages/sdk/src/tx/tx.ts +++ b/packages/sdk/src/tx/tx.ts @@ -53,7 +53,7 @@ export class Tx { /** * @param sdk - Instance of Sdk struct from wasm lib */ - constructor(protected readonly sdk: SdkWasm) {} + constructor(protected readonly sdk: SdkWasm) { } /** * Build Transparent Transfer Tx @@ -366,6 +366,21 @@ export class Tx { return deserialize(Buffer.from(batch), TxMsgValue); } + /** + * TODO + * @param txBytes - [TODO:description] + * @param signingData - [TODO:description] + * @param signature - [TODO:description] + * @returns [TODO:description] + */ + 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 d3cb120f39..f2cdbbc2b4 100644 --- a/packages/shared/lib/src/sdk/args.rs +++ b/packages/shared/lib/src/sdk/args.rs @@ -642,14 +642,42 @@ 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 { source: String, data: Vec, gas_spending_key: Option, + bparams: Option>, } - /// Maps serialized tx_msg into TxUnshieldingTransfer args. /// /// # Arguments @@ -664,13 +692,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); @@ -692,6 +721,45 @@ 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 args = args::TxUnshieldingTransfer { data: unshielding_transfer_data, source, @@ -702,7 +770,7 @@ pub fn unshielding_transfer_tx_args( tx_code_path: PathBuf::from("tx_transfer.wasm"), }; - Ok(args) + Ok((args, bparams)) } #[derive(BorshSerialize, BorshDeserialize, Debug)] @@ -1048,7 +1116,9 @@ where Ok(()) } -struct MapSaplingSigAuth(HashMap::AuthSig>); +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 c38cbd6380..117c54cc06 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_masp_build_params, masp_sign, BuildParams, MapSaplingSigAuth}; use gloo_utils::format::JsValueSerdeExt; +use js_sys::Uint8Array; use namada_sdk::address::{Address, 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; @@ -33,6 +35,7 @@ use namada_sdk::string_encoding::Format; use namada_sdk::tendermint_rpc::Url; use namada_sdk::token::DenominatedAmount; use namada_sdk::token::{MaspTxId, OptionExt}; +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, @@ -146,12 +149,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(()) } @@ -240,6 +243,79 @@ impl Sdk { to_js_result(borsh::to_vec(&tx)?) } + pub fn sign_masp_ledger( + &self, + tx: Vec, + signing_data: Box<[Uint8Array]>, + signature: Vec, + ) -> Result { + web_sys::console::log_1(&"sign_masp_ledger".into()); + let mut namada_tx: Tx = borsh::from_slice(&tx)?; + web_sys::console::log_1(&format!("namada_tx: {:?}", namada_tx).into()); + let signing_data = signing_data + .iter() + .map(|sd| { + borsh::from_slice(&sd.to_vec()).expect("Expected to deserialize signing data") + }) + .collect::>(); + + web_sys::console::log_1(&format!("signing_data: {:?}", signing_data).into()); + // let signing_data = >::try_from_slice(&signing_data)?; + + for signing_data in signing_data { + web_sys::console::log_1(&format!("signing_data222: {:?}", signing_data).into()); + 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 masp_builder = namada_tx + .get_masp_builder(&shielded_hash) + .expect("Expected to find the indicated MASP Builder"); + + let sapling_inputs = masp_builder.builder.sapling_inputs(); + // let mut descriptor_map = vec![0; sapling_inputs.len()]; + // for i in 0.. { + // if let Some(pos) = masp_builder.metadata.spend_index(i) { + // descriptor_map[pos] = i; + // } else { + // break; + // }; + // } + + web_sys::console::log_1( + &format!("sapling_inputs len: {:?}", sapling_inputs.len()).into(), + ); + let mut authorizations = HashMap::new(); + + web_sys::console::log_1(&format!("signature: {:?}", signature).into()); + let signature = + namada_sdk::masp_primitives::sapling::redjubjub::Signature::try_from_slice( + &signature.to_vec(), + )?; + authorizations.insert(0_usize, signature); + + web_sys::console::log_1(&format!("authorizations: {:?}", authorizations).into()); + 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)); + } + } + + web_sys::console::log_1(&format!("namada_tx: {:?}", namada_tx).into()); + to_js_result(borsh::to_vec(&namada_tx)?) + } + pub async fn sign_tx( &self, tx: Vec, @@ -493,11 +569,15 @@ 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 = + + 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?; + .await? + }; let _ = &self.namada.shielded_mut().await.load().await?; @@ -523,6 +603,16 @@ impl Sdk { } }; + if let Some(shielded_hash) = signing_data.shielded_hash { + web_sys::console::log_1( + &format!( + "tx: {:?}", + borsh::to_vec(tx.get_masp_section(&shielded_hash).unwrap()) + ) + .into(), + ); + } + self.serialize_tx_result(tx, wrapper_tx_msg, signing_data, Some(masp_signing_data)) } 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 cc756a313b..a56f667a39 100644 --- a/packages/types/src/tx/schema/transfer.ts +++ b/packages/types/src/tx/schema/transfer.ts @@ -140,6 +140,54 @@ export class UnshieldingTransferDataMsgValue { } } +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); + } +} + export class UnshieldingTransferMsgValue { @field({ type: "string" }) source!: string; @@ -150,7 +198,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 +214,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), + }); + }), }); } }