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