diff --git a/.lintstagedrc.json b/.lintstagedrc.json index 5cda5ff6c..3561b5fe1 100644 --- a/.lintstagedrc.json +++ b/.lintstagedrc.json @@ -1,7 +1,7 @@ { "*.{js,jsx,ts,tsx}": [ "prettier --write", - "eslint --rule 'no-console: [\"error\", { allow: [\"warn\", \"error\"] }]' --fix --max-warnings=0" + "eslint --rule 'no-console: [\"error\", { allow: [\"warn\", \"error\", \"info\"] }]' --fix --max-warnings=0" ], "*.json": ["prettier --write"] } diff --git a/apps/extension/src/Setup/ImportAccount/SeedPhraseImport.tsx b/apps/extension/src/Setup/ImportAccount/SeedPhraseImport.tsx index 35dc2e5a5..10dc03a32 100644 --- a/apps/extension/src/Setup/ImportAccount/SeedPhraseImport.tsx +++ b/apps/extension/src/Setup/ImportAccount/SeedPhraseImport.tsx @@ -76,7 +76,7 @@ export const SeedPhraseImport: React.FC = ({ const isSubmitButtonDisabled = mnemonicType === SecretType.PrivateKey ? privateKey === "" || privateKeyError !== "" - : mnemonics.slice(0, mnemonicType).some((mnemonic) => !mnemonic); + : mnemonics.slice(0, mnemonicType).some((mnemonic) => !mnemonic); const onPaste = useCallback( (index: number, e: React.ClipboardEvent) => { @@ -154,7 +154,7 @@ export const SeedPhraseImport: React.FC = ({ setInvalidWordIndex(invalidWordIndex); typeof invalidWordIndex === "number" ? setMnemonicError(`Word #${invalidWordIndex + 1} is invalid!`) - : setMnemonicError(error); + : setMnemonicError(error); } else { setMnemonicError(error); } diff --git a/apps/extension/src/background/approvals/service.ts b/apps/extension/src/background/approvals/service.ts index 69978e1e7..88964bef9 100644 --- a/apps/extension/src/background/approvals/service.ts +++ b/apps/extension/src/background/approvals/service.ts @@ -62,7 +62,13 @@ export class ApprovalsService { } const msgId = uuid(); - const details = await this.keyRingService.queryAccountDetails(signer); + // If the signer is a disposable signer, get the real address + const realAddress = + (await this.localStorage.getDisposableSigner(signer))?.realAddress || + signer; + + // We use the real address to query the account details + const details = await this.keyRingService.queryAccountDetails(realAddress); if (!details) { throw new Error(ApprovalErrors.AccountNotFound(signer)); } diff --git a/apps/extension/src/background/approvals/types.ts b/apps/extension/src/background/approvals/types.ts index c9e66ec2e..fca947bdf 100644 --- a/apps/extension/src/background/approvals/types.ts +++ b/apps/extension/src/background/approvals/types.ts @@ -16,6 +16,8 @@ export type EncodedSigningData = Pick< "publicKeys" | "threshold" | "feePayer" | "owner" > & { accountPublicKeysMap?: string; + shieldedHash?: string; + masp?: string; }; export type EncodedTxData = Pick & { diff --git a/apps/extension/src/background/index.ts b/apps/extension/src/background/index.ts index 7af712696..07fe4bb0b 100644 --- a/apps/extension/src/background/index.ts +++ b/apps/extension/src/background/index.ts @@ -57,6 +57,8 @@ const init = new Promise(async (resolve) => { const broadcaster = new ExtensionBroadcaster(localStorage, requester); const sdkService = await SdkService.init(); + await localStorage.clearOldDisposableSigners(); + const vaultService = new VaultService( vaultStorage, sessionStore, diff --git a/apps/extension/src/background/keyring/handler.ts b/apps/extension/src/background/keyring/handler.ts index 900cee6c3..0806b3703 100644 --- a/apps/extension/src/background/keyring/handler.ts +++ b/apps/extension/src/background/keyring/handler.ts @@ -1,5 +1,6 @@ import { CheckDurabilityMsg, + GenDisposableSignerMsg, QueryAccountsMsg, QueryDefaultAccountMsg, VerifyArbitraryMsg, @@ -89,6 +90,11 @@ export const getHandler: (service: KeyRingService) => Handler = (service) => { env, msg as QueryAccountDetailsMsg ); + case GenDisposableSignerMsg: + return handleGenDisposableSignerMsg(service)( + env, + msg as GenDisposableSignerMsg + ); default: throw new Error("Unknown msg type"); } @@ -237,3 +243,11 @@ const handleQueryAccountDetails: ( return await service.queryAccountDetails(address); }; }; + +const handleGenDisposableSignerMsg: ( + service: KeyRingService +) => InternalHandler = (service) => { + return async (_, _msg) => { + return await service.genDisposableSigner(); + }; +}; diff --git a/apps/extension/src/background/keyring/init.ts b/apps/extension/src/background/keyring/init.ts index 75c363238..d3bea3a4c 100644 --- a/apps/extension/src/background/keyring/init.ts +++ b/apps/extension/src/background/keyring/init.ts @@ -1,5 +1,6 @@ import { CheckDurabilityMsg, + GenDisposableSignerMsg, QueryAccountsMsg, QueryDefaultAccountMsg, VerifyArbitraryMsg, @@ -40,6 +41,7 @@ export function init(router: Router, service: KeyRingService): void { router.registerMessage(RevealAccountMnemonicMsg); router.registerMessage(RenameAccountMsg); router.registerMessage(VerifyArbitraryMsg); + router.registerMessage(GenDisposableSignerMsg); router.addHandler(ROUTE, getHandler(service)); } diff --git a/apps/extension/src/background/keyring/keyring.ts b/apps/extension/src/background/keyring/keyring.ts index 461d714bd..05d0e62a6 100644 --- a/apps/extension/src/background/keyring/keyring.ts +++ b/apps/extension/src/background/keyring/keyring.ts @@ -4,11 +4,12 @@ import { AccountType, Bip44Path, DerivedAccount, + GenDisposableSignerResponse, Path, SignArbitraryResponse, TxProps, } from "@namada/types"; -import { Result, assertNever, truncateInMiddle } from "@namada/utils"; +import { assertNever, Result, truncateInMiddle } from "@namada/utils"; import { AccountSecret, @@ -24,7 +25,13 @@ import { import { fromHex } from "@cosmjs/encoding"; import { SdkService } from "background/sdk"; import { VaultService } from "background/vault"; -import { KeyStore, KeyStoreType, SensitiveType, VaultStorage } from "storage"; +import { + KeyStore, + KeyStoreType, + LocalStorage, + SensitiveType, + VaultStorage, +} from "storage"; import { generateId, makeStoredPath } from "utils"; // Generated UUID namespace for uuid v5 @@ -33,6 +40,7 @@ const UUID_NAMESPACE = "9bfceade-37fe-11ed-acc0-a3da3461b38c"; export const KEYSTORE_KEY = "key-store"; export const PARENT_ACCOUNT_ID_KEY = "parent-account-id"; export const AUTHKEY_KEY = "auth-key-store"; +export const DISPOSABLE_SIGNER_KEY = "disposable-signer"; export const DEFAULT_BIP44_PATH = { account: 0, change: 0, index: 0 }; type DerivedAccountInfo = { @@ -40,6 +48,7 @@ type DerivedAccountInfo = { id: string; text: string; owner: string; + pseudoExtendedKey?: string; }; /** @@ -52,7 +61,8 @@ export class KeyRing { protected readonly vaultService: VaultService, protected readonly vaultStorage: VaultStorage, protected readonly sdkService: SdkService, - protected readonly utilityStore: KVStore + protected readonly utilityStore: KVStore, + protected readonly localStorage: LocalStorage ) {} public get status(): KeyRingStatus { @@ -293,13 +303,15 @@ export class KeyRing { throw new Error(`Invalid account type! ${parentType}`); } - const { address, viewingKey, spendingKey } = shieldedKeys; + const { address, viewingKey, spendingKey, pseudoExtendedKey } = + shieldedKeys; return { address, id, owner: viewingKey, text: JSON.stringify({ spendingKey }), + pseudoExtendedKey, }; } @@ -353,7 +365,7 @@ export class KeyRing { alias: string, derivedAccountInfo: DerivedAccountInfo ): Promise { - const { address, id, text, owner } = derivedAccountInfo; + const { address, id, text, owner, pseudoExtendedKey } = derivedAccountInfo; const account: AccountStore = { id, address, @@ -362,6 +374,7 @@ export class KeyRing { path, type, owner, + pseudoExtendedKey, }; const sensitive = await this.vaultService.encryptSensitiveData({ text, @@ -526,6 +539,55 @@ export class KeyRing { return accounts.map((account) => account.public as AccountStore); } + private async getSpendingKey(address: string): Promise { + const account = await this.vaultStorage.findOne( + KeyStore, + "address", + address + ); + if (!account) { + throw new Error(`Account for ${address} not found!`); + } + const accountStore = (await this.queryAllAccounts()).find( + (account) => account.address === address + ); + + if (!accountStore) { + throw new Error(`Account for ${address} not found!`); + } + const { path } = accountStore; + + const sensitiveProps = + await this.vaultService.reveal( + account.sensitive + ); + if (!sensitiveProps) { + throw new Error(`Signing key for ${address} not found!`); + } + const { text, passphrase } = sensitiveProps; + + const bip44Path = { + account: path.account, + change: path.change || 0, + index: path.index || 0, + }; + const accountType = accountStore.type; + let shieldedKeys: ShieldedKeys; + const keys = this.sdkService.getSdk().getKeys(); + + if (accountType === AccountType.Mnemonic) { + const mnemonicSdk = this.sdkService.getSdk().getMnemonic(); + const seed = mnemonicSdk.toSeed(text, passphrase); + shieldedKeys = keys.deriveShieldedFromSeed(seed, bip44Path); + } else if (accountType === AccountType.PrivateKey) { + shieldedKeys = keys.deriveShieldedFromPrivateKey(fromHex(text)); + } else { + throw new Error(`Invalid account type! ${accountType}`); + } + + return shieldedKeys.spendingKey; + } + /** * For provided address, return associated private key */ @@ -635,9 +697,24 @@ export class KeyRing { chainId: string ): Promise { await this.vaultService.assertIsUnlocked(); - const key = await this.getSigningKey(signer); + + const disposableKey = await this.localStorage.getDisposableSigner(signer); + + // If disposable key is provided, use it for signing + const key = + disposableKey ? + disposableKey.privateKey + : await this.getSigningKey(signer); + + // If disposable key is provided, use it to map real address to spending key + const spendingKeys = + disposableKey ? + [await this.getSpendingKey(disposableKey.realAddress)] + : []; + const { signing } = this.sdkService.getSdk(); - return await signing.sign(txProps, key, chainId); + + return await signing.sign(txProps, key, spendingKeys, chainId); } async signArbitrary( @@ -666,4 +743,24 @@ export class KeyRing { } return account.public; } + + async genDisposableSigner(): Promise< + GenDisposableSignerResponse | undefined + > { + const sdk = this.sdkService.getSdk(); + const { privateKey, publicKey, address } = sdk.keys.genDisposableKeypair(); + + const defaultAccount = await this.queryDefaultAccount(); + if (!defaultAccount) { + throw new Error("No default account found"); + } + + await this.localStorage.addDisposableSigner( + address, + privateKey, + defaultAccount.address + ); + + return { publicKey, address }; + } } diff --git a/apps/extension/src/background/keyring/service.ts b/apps/extension/src/background/keyring/service.ts index ddc1bf758..7a1a5e0d9 100644 --- a/apps/extension/src/background/keyring/service.ts +++ b/apps/extension/src/background/keyring/service.ts @@ -4,6 +4,7 @@ import { AccountType, Bip44Path, DerivedAccount, + GenDisposableSignerResponse, SignArbitraryResponse, TxProps, } from "@namada/types"; @@ -43,7 +44,8 @@ export class KeyRingService { vaultService, vaultStorage, sdkService, - utilityStore + utilityStore, + localStorage ); } @@ -229,4 +231,10 @@ export class KeyRingService { } return this._keyRing.queryAccountDetails(address); } + + async genDisposableSigner(): Promise< + GenDisposableSignerResponse | undefined + > { + return this._keyRing.genDisposableSigner(); + } } diff --git a/apps/extension/src/background/keyring/types.ts b/apps/extension/src/background/keyring/types.ts index 1dce802ed..a69b3d123 100644 --- a/apps/extension/src/background/keyring/types.ts +++ b/apps/extension/src/background/keyring/types.ts @@ -9,6 +9,7 @@ export interface AccountStore extends StoredRecord { publicKey?: string; path: Path; parentId?: string; + pseudoExtendedKey?: string; type: AccountType; } diff --git a/apps/extension/src/provider/InjectedNamada.ts b/apps/extension/src/provider/InjectedNamada.ts index 01fca91c5..35f18e3ca 100644 --- a/apps/extension/src/provider/InjectedNamada.ts +++ b/apps/extension/src/provider/InjectedNamada.ts @@ -1,5 +1,6 @@ import { Account, + GenDisposableSignerResponse, Namada as INamada, Signer as ISigner, SignArbitraryProps, @@ -11,7 +12,7 @@ import { InjectedProxy } from "./InjectedProxy"; import { Signer } from "./Signer"; export class InjectedNamada implements INamada { - constructor(private readonly _version: string) { } + constructor(private readonly _version: string) {} public async connect(chainId?: string): Promise { return await InjectedProxy.requestMethod("connect", chainId); @@ -69,6 +70,15 @@ export class InjectedNamada implements INamada { ); } + public async genDisposableKeypair(): Promise< + GenDisposableSignerResponse | undefined + > { + return await InjectedProxy.requestMethod< + void, + GenDisposableSignerResponse | undefined + >("genDisposableKeypair"); + } + public getSigner(): ISigner | undefined { return new Signer(this); } diff --git a/apps/extension/src/provider/Namada.ts b/apps/extension/src/provider/Namada.ts index 0ddd29987..d0d135a1c 100644 --- a/apps/extension/src/provider/Namada.ts +++ b/apps/extension/src/provider/Namada.ts @@ -1,5 +1,6 @@ import { Account, + GenDisposableSignerResponse, Namada as INamada, SignArbitraryProps, SignArbitraryResponse, @@ -16,6 +17,7 @@ import { ApproveSignTxMsg, ApproveUpdateDefaultAccountMsg, CheckDurabilityMsg, + GenDisposableSignerMsg, IsConnectionApprovedMsg, QueryAccountsMsg, QueryDefaultAccountMsg, @@ -102,6 +104,15 @@ export class Namada implements INamada { ); } + public async genDisposableKeypair(): Promise< + GenDisposableSignerResponse | undefined + > { + return await this.requester?.sendMessage( + Ports.Background, + new GenDisposableSignerMsg() + ); + } + public async verify(props: VerifyArbitraryProps): Promise { const { publicKey, hash, signature } = props; return await this.requester?.sendMessage( diff --git a/apps/extension/src/provider/Signer.ts b/apps/extension/src/provider/Signer.ts index 95a74f4fb..2db4fcece 100644 --- a/apps/extension/src/provider/Signer.ts +++ b/apps/extension/src/provider/Signer.ts @@ -1,4 +1,5 @@ import { + GenDisposableSignerResponse, Signer as ISigner, Namada, SignArbitraryResponse, @@ -35,4 +36,10 @@ export class Signer implements ISigner { ): Promise { return await this._namada.verify({ publicKey, hash, signature }); } + + public async genDisposableKeypair(): Promise< + GenDisposableSignerResponse | undefined + > { + return await this._namada.genDisposableKeypair(); + } } diff --git a/apps/extension/src/provider/messages.ts b/apps/extension/src/provider/messages.ts index ca70416ee..32e997854 100644 --- a/apps/extension/src/provider/messages.ts +++ b/apps/extension/src/provider/messages.ts @@ -1,4 +1,8 @@ -import { DerivedAccount, SignArbitraryResponse } from "@namada/types"; +import { + DerivedAccount, + GenDisposableSignerResponse, + SignArbitraryResponse, +} from "@namada/types"; import { EncodedTxData } from "background/approvals"; import { Message } from "router"; import { validateProps } from "utils"; @@ -25,9 +29,11 @@ enum MessageType { QueryDefaultAccount = "query-default-account", ApproveUpdateDefaultAccount = "approve-update-default-account", EncodeRevealPublicKey = "encode-reveal-public-key", + SpendingKey = "spending-key", ApproveEthBridgeTransfer = "approve-eth-bridge-transfer", CheckDurability = "check-durability", VerifyArbitrary = "verify-arbitrary", + GenDisposableSigner = "gen-disposable-signer", } export class ApproveSignTxMsg extends Message { @@ -229,6 +235,30 @@ export class ApproveUpdateDefaultAccountMsg extends Message { } } +export class SpendingKeyMsg extends Message< + [Array, Array, Array, Array] | undefined +> { + public static type(): MessageType { + return MessageType.SpendingKey; + } + + constructor(public readonly publicKey: number[]) { + super(); + } + + validate(): void { + validateProps(this, ["publicKey"]); + } + + route(): string { + return Route.KeyRing; + } + + type(): string { + return SpendingKeyMsg.type(); + } +} + export class CheckDurabilityMsg extends Message { public static type(): MessageType { return MessageType.CheckDurability; @@ -271,3 +301,25 @@ export class VerifyArbitraryMsg extends Message { return VerifyArbitraryMsg.type(); } } + +export class GenDisposableSignerMsg extends Message< + GenDisposableSignerResponse | undefined +> { + public static type(): MessageType { + return MessageType.GenDisposableSigner; + } + + constructor() { + super(); + } + + validate(): void {} + + route(): string { + return Route.KeyRing; + } + + type(): string { + return GenDisposableSignerMsg.type(); + } +} diff --git a/apps/extension/src/storage/LocalStorage.ts b/apps/extension/src/storage/LocalStorage.ts index d6e3c56e8..878cbbb2f 100644 --- a/apps/extension/src/storage/LocalStorage.ts +++ b/apps/extension/src/storage/LocalStorage.ts @@ -1,6 +1,8 @@ import { chains } from "@namada/chains"; import { KVStore } from "@namada/storage"; import * as E from "fp-ts/Either"; +import { pipe } from "fp-ts/function"; +import * as O from "fp-ts/Option"; import * as t from "io-ts"; import { ExtStorage } from "./Storage"; @@ -15,26 +17,40 @@ type NamadaExtensionApprovedOriginsType = t.TypeOf< const NamadaExtensionRouterId = t.number; type NamadaExtensionRouterIdType = t.TypeOf; +const DisposableSigners = t.record( + t.string, + t.type({ + privateKey: t.string, + realAddress: t.string, + timestamp: t.number, + }) +); +type DisposableSignersType = t.TypeOf; + type LocalStorageTypes = | ChainIdType | NamadaExtensionApprovedOriginsType - | NamadaExtensionRouterIdType; + | NamadaExtensionRouterIdType + | DisposableSignersType; type LocalStorageSchemas = | typeof ChainId | typeof NamadaExtensionApprovedOrigins - | typeof NamadaExtensionRouterId; + | typeof NamadaExtensionRouterId + | typeof DisposableSigners; export type LocalStorageKeys = | "chainId" | "namadaExtensionApprovedOrigins" | "namadaExtensionRouterId" - | "tabs"; + | "tabs" + | "namadaExtensionDisposableSigners"; const schemasMap = new Map([ [ChainId, "chainId"], [NamadaExtensionApprovedOrigins, "namadaExtensionApprovedOrigins"], [NamadaExtensionRouterId, "namadaExtensionRouterId"], + [DisposableSigners, "namadaExtensionDisposableSigners"], ]); export class LocalStorage extends ExtStorage { @@ -105,6 +121,71 @@ export class LocalStorage extends ExtStorage { await this.setRaw(this.getKey(NamadaExtensionRouterId), id); } + async addDisposableSigner( + address: string, + privateKey: string, + realAddress: string + ): Promise { + const data = (await this.getDisposableSigners()) || {}; + await this.setDisposableSigners({ + ...data, + [address]: { privateKey, realAddress, timestamp: Date.now() }, + }); + } + + async getDisposableSigner( + address: string + ): Promise<{ privateKey: string; realAddress: string } | undefined> { + const data = await this.getDisposableSigners(); + return data?.[address]; + } + + async clearOldDisposableSigners(): Promise { + const data = O.fromNullable(await this.getDisposableSigners()); + const currentTime = Date.now(); + + const newData = pipe( + data, + O.map((data) => { + const newData = Object.entries(data).reduce((acc, [key, value]) => { + // We clear the disposable signers after 2 minutes + if (currentTime - value.timestamp < 120000) { + acc[key] = value; + } + + return acc; + }, {} as DisposableSignersType); + + return newData; + }) + ); + + if (O.isSome(newData)) { + await this.setDisposableSigners(newData.value); + } + } + + private async getDisposableSigners(): Promise< + DisposableSignersType | undefined + > { + const data = await this.getRaw(this.getKey(DisposableSigners)); + + const Schema = t.union([DisposableSigners, t.undefined]); + const decodedData = Schema.decode(data); + + if (E.isLeft(decodedData)) { + throw new Error("Disposable Signers are not valid"); + } + + return decodedData.right; + } + + private async setDisposableSigners( + signers: DisposableSignersType + ): Promise { + await this.setRaw(this.getKey(DisposableSigners), signers); + } + private async setApprovedOrigins( origins: NamadaExtensionApprovedOriginsType ): Promise { diff --git a/apps/extension/src/storage/VaultStorage.ts b/apps/extension/src/storage/VaultStorage.ts index 95c0083ed..5798128a7 100644 --- a/apps/extension/src/storage/VaultStorage.ts +++ b/apps/extension/src/storage/VaultStorage.ts @@ -73,6 +73,7 @@ export const KeyStore = t.exact( t.partial({ publicKey: t.string, parentId: t.string, + pseudoExtendedKey: t.string, }), ]) ); diff --git a/apps/extension/src/utils/index.ts b/apps/extension/src/utils/index.ts index 63637838c..ad9cee20b 100644 --- a/apps/extension/src/utils/index.ts +++ b/apps/extension/src/utils/index.ts @@ -87,6 +87,8 @@ export const toEncodedTx = (txProps: TxProps): EncodedTxData => ({ bytes: toBase64(txProps.bytes), signingData: txProps.signingData.map((sd) => ({ ...sd, + masp: sd.masp ? toBase64(sd.masp) : undefined, + shieldedHash: sd.shieldedHash ? toBase64(sd.shieldedHash) : undefined, accountPublicKeysMap: sd.accountPublicKeysMap ? toBase64(sd.accountPublicKeysMap) : undefined, })), @@ -98,6 +100,8 @@ export const fromEncodedTx = (encodedTxData: EncodedTxData): TxProps => ({ bytes: fromBase64(encodedTxData.bytes), signingData: encodedTxData.signingData.map((sd) => ({ ...sd, + masp: sd.masp ? fromBase64(sd.masp) : undefined, + shieldedHash: sd.shieldedHash ? fromBase64(sd.shieldedHash) : undefined, accountPublicKeysMap: sd.accountPublicKeysMap ? fromBase64(sd.accountPublicKeysMap) : undefined, })), @@ -166,12 +170,14 @@ export const parseTransferType = ( * @returns Account type for public API */ export const toPublicAccount = (derivedAccount: DerivedAccount): Account => { - const { alias, address, type, publicKey, owner } = derivedAccount; + const { alias, address, type, publicKey, owner, pseudoExtendedKey } = + derivedAccount; const isShielded = type === AccountType.ShieldedKeys; const account: Account = { alias, address, type, + pseudoExtendedKey, }; if (isShielded) { account.viewingKey = owner; diff --git a/apps/namadillo/src/App/Masp/MaspUnshield.tsx b/apps/namadillo/src/App/Masp/MaspUnshield.tsx index 68571b900..ad4881731 100644 --- a/apps/namadillo/src/App/Masp/MaspUnshield.tsx +++ b/apps/namadillo/src/App/Masp/MaspUnshield.tsx @@ -56,9 +56,10 @@ export const MaspUnshield: React.FC = () => { const chainId = chainParameters.data?.chainId; - const sourceAddress = defaultAccounts.data?.find( + const sourceAccount = defaultAccounts.data?.find( (account) => account.type === AccountType.ShieldedKeys - )?.address; + ); + const sourceAddress = sourceAccount?.address; const destinationAddress = defaultAccounts.data?.find( (account) => account.type !== AccountType.ShieldedKeys )?.address; @@ -114,6 +115,10 @@ export const MaspUnshield: React.FC = () => { setGeneralErrorMessage(""); setCurrentStep(1); + if (typeof sourceAccount?.pseudoExtendedKey === "undefined") { + throw new Error("Pseudo extended key is not defined"); + } + if (typeof sourceAddress === "undefined") { throw new Error("Source address is not defined"); } @@ -138,7 +143,7 @@ export const MaspUnshield: React.FC = () => { }); const txResponse = await performUnshieldTransfer.mutateAsync({ - sourceAddress, + sourceAddress: sourceAccount.pseudoExtendedKey, destinationAddress, tokenAddress: selectedAsset.originalAddress, amount: displayAmount, @@ -149,7 +154,7 @@ export const MaspUnshield: React.FC = () => { const tx: TransferTransactionData = { type: "ShieldedToTransparent", currentStep: TransferStep.Complete, - sourceAddress, + sourceAddress: sourceAccount.pseudoExtendedKey, destinationAddress, asset: selectedAsset.asset, displayAmount, diff --git a/apps/namadillo/src/App/WorkerTest.tsx b/apps/namadillo/src/App/WorkerTest.tsx index 68dd435f2..146d1e9fe 100644 --- a/apps/namadillo/src/App/WorkerTest.tsx +++ b/apps/namadillo/src/App/WorkerTest.tsx @@ -1,18 +1,27 @@ import * as Comlink from "comlink"; -import { ShieldingTransferMsgValue } from "@namada/types"; -import { defaultAccountAtom } from "atoms/accounts"; +import { + AccountType, + ShieldedTransferMsgValue, + ShieldingTransferMsgValue, + UnshieldingTransferMsgValue, +} from "@namada/types"; +import { + accountsAtom, + defaultAccountAtom, + disposableSignerAtom, +} from "atoms/accounts"; import { chainAtom, nativeTokenAddressAtom } from "atoms/chain"; -import { indexerUrlAtom, rpcUrlAtom } from "atoms/settings"; +import { indexerUrlAtom, maspIndexerUrlAtom, rpcUrlAtom } from "atoms/settings"; import BigNumber from "bignumber.js"; -import { useAtomValue } from "jotai"; +import { useAtom, useAtomValue } from "jotai"; import { signTx } from "lib/query"; -import { Shield } from "workers/ShieldMessages"; +import { Shield, ShieldedTransfer, Unshield } from "workers/MaspTxMessages"; import { + Worker as MaspTxWorkerApi, registerTransferHandlers, - Worker as ShieldWorkerApi, -} from "workers/ShieldWorker"; -import ShieldWorker from "workers/ShieldWorker?worker"; +} from "workers/MaspTxWorker"; +import MaspTxWorker from "workers/MaspTxWorker?worker"; import { Worker as ShieldedSyncWorkerApi } from "workers/ShieldedSyncWorker"; import ShieldedSyncWorker from "workers/ShieldedSyncWorker?worker"; @@ -20,10 +29,15 @@ export function WorkerTest(): JSX.Element { const rpcUrl = useAtomValue(rpcUrlAtom); const { data: account } = useAtomValue(defaultAccountAtom); + const [{ refetch }] = useAtom(disposableSignerAtom); + const { data: accounts } = useAtomValue(accountsAtom); const { data: chain } = useAtomValue(chainAtom); const { data: token } = useAtomValue(nativeTokenAddressAtom); const indexerUrl = useAtomValue(indexerUrlAtom); - + const maspIndexerUrl = useAtomValue(maspIndexerUrlAtom); + const shieldedAccount = accounts?.find( + (a) => a.type === AccountType.ShieldedKeys && a.alias === account?.alias + ); // eslint-disable-next-line @typescript-eslint/no-explicit-any (window as any).shieldedSync = async (vk: string) => { const worker = new ShieldedSyncWorker(); @@ -47,19 +61,17 @@ export function WorkerTest(): JSX.Element { }; // eslint-disable-next-line @typescript-eslint/no-explicit-any - (window as any).buildShieldlingTx = async ( - target: string, - amount: number - ) => { + (window as any).shield = async (target: string, amount: number) => { registerTransferHandlers(); - const worker = new ShieldWorker(); - const shieldWorker = Comlink.wrap(worker); + const worker = new MaspTxWorker(); + const shieldWorker = Comlink.wrap(worker); await shieldWorker.init({ type: "init", payload: { rpcUrl, token: token!, + maspIndexerUrl, }, }); @@ -79,8 +91,8 @@ export function WorkerTest(): JSX.Element { payload: { account: account!, gasConfig: { - gasLimit: BigNumber(250000), - gasPrice: BigNumber(0.000001), + gasLimit: BigNumber(50000), + gasPrice: BigNumber(0), }, shieldingProps: [shieldingMsgValue], indexerUrl, @@ -103,5 +115,134 @@ export function WorkerTest(): JSX.Element { worker.terminate(); }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window as any).unshield = async (target: string, amount: number) => { + registerTransferHandlers(); + const worker = new MaspTxWorker(); + const shieldWorker = Comlink.wrap(worker); + + await shieldWorker.init({ + type: "init", + payload: { + rpcUrl, + token: token!, + maspIndexerUrl, + }, + }); + + const shieldingMsgValue = new UnshieldingTransferMsgValue({ + source: shieldedAccount!.pseudoExtendedKey!, + gasSpendingKey: shieldedAccount!.pseudoExtendedKey!, + data: [ + { + target, + token: token!, + amount: BigNumber(amount), + }, + ], + }); + + const vks = accounts + ?.filter((acc) => acc.type === "shielded-keys") + .map((a) => a.viewingKey!); + + const disposableSigner = (await refetch()).data; + + const msg: Unshield = { + type: "unshield", + payload: { + account: { + ...account!, + publicKey: disposableSigner!.publicKey, + }, + gasConfig: { + gasLimit: BigNumber(100000), + gasPrice: BigNumber(0), + }, + unshieldingProps: [shieldingMsgValue], + chain: chain!, + vks: vks!, + indexerUrl, + }, + }; + + const { payload: encodedTx } = await shieldWorker.unshield(msg); + const signedTxs = await signTx(encodedTx, disposableSigner?.address || ""); + + await shieldWorker.broadcast({ + type: "broadcast", + payload: { + encodedTx, + signedTxs, + }, + }); + + worker.terminate(); + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window as any).shielded = async (target: string, amount: number) => { + registerTransferHandlers(); + const worker = new MaspTxWorker(); + const shieldWorker = Comlink.wrap(worker); + + await shieldWorker.init({ + type: "init", + payload: { + rpcUrl, + token: token!, + maspIndexerUrl, + }, + }); + + const shieldingMsgValue = new ShieldedTransferMsgValue({ + gasSpendingKey: shieldedAccount!.pseudoExtendedKey!, + data: [ + { + source: shieldedAccount!.pseudoExtendedKey!, + target, + token: token!, + amount: BigNumber(amount), + }, + ], + }); + + const vks = accounts + ?.filter((acc) => acc.type === "shielded-keys") + .map((a) => a.viewingKey!); + + const disposableSigner = (await refetch()).data; + + const msg: ShieldedTransfer = { + type: "shielded-transfer", + payload: { + account: { + ...account!, + publicKey: disposableSigner!.publicKey, + }, + gasConfig: { + gasLimit: BigNumber(50000), + gasPrice: BigNumber(0), + }, + shieldingProps: [shieldingMsgValue], + chain: chain!, + vks: vks!, + }, + }; + + const { payload: encodedTx } = await shieldWorker.shieldedTransfer(msg); + const signedTxs = await signTx(encodedTx, disposableSigner?.address || ""); + + await shieldWorker.broadcast({ + type: "broadcast", + payload: { + encodedTx, + signedTxs, + }, + }); + + worker.terminate(); + }; + return
; } diff --git a/apps/namadillo/src/atoms/accounts/atoms.ts b/apps/namadillo/src/atoms/accounts/atoms.ts index ab66a4e05..c4c0af881 100644 --- a/apps/namadillo/src/atoms/accounts/atoms.ts +++ b/apps/namadillo/src/atoms/accounts/atoms.ts @@ -1,4 +1,8 @@ -import { Account, AccountType } from "@namada/types"; +import { + Account, + AccountType, + GenDisposableSignerResponse, +} from "@namada/types"; import { indexerApiAtom } from "atoms/api"; import { nativeTokenAddressAtom } from "atoms/chain"; import { shouldUpdateBalanceAtom } from "atoms/etc"; @@ -95,6 +99,26 @@ export const accountBalanceAtom = atomWithQuery((get) => { }; }); +export const disposableSignerAtom = atomWithQuery( + (get) => { + const isExtensionConnected = get(namadaExtensionConnectedAtom); + return { + // As this generates new keypair each time, we have to refetch it manually + enabled: false, + queryKey: ["disposable-signer", isExtensionConnected], + queryFn: async () => { + const namada = await new NamadaKeychain().get(); + const res = await namada?.genDisposableKeypair(); + if (!res) { + throw new Error("Failed to generate disposable signer"); + } + + return res; + }, + }; + } +); + // TODO combine the `accountBalanceAtom` with the `transparentBalanceAtom` // Then execute only once the `fetchAccountBalance`, deleting the `fetchNamAccountBalance` export const transparentBalanceAtom = atomWithQuery< diff --git a/apps/namadillo/src/atoms/shield/atoms.ts b/apps/namadillo/src/atoms/shield/atoms.ts index 25170cd12..093523f46 100644 --- a/apps/namadillo/src/atoms/shield/atoms.ts +++ b/apps/namadillo/src/atoms/shield/atoms.ts @@ -1,6 +1,7 @@ import { defaultAccountAtom } from "atoms/accounts"; import { chainAtom } from "atoms/chain"; import { indexerUrlAtom, rpcUrlAtom } from "atoms/settings"; +import { NamadaKeychain } from "hooks/useNamadaKeychain"; import { atomWithMutation } from "jotai-tanstack-query"; import { ShieldTransferParams, @@ -32,7 +33,20 @@ export const unshieldTxAtom = atomWithMutation((get) => { return { mutationKey: ["unshield-tx"], mutationFn: async (params: UnshieldTransferParams) => { - return submitUnshieldTx(rpcUrl, account!, chain!, indexerUrl, params); + const namada = await new NamadaKeychain().get(); + const disposableSigner = await namada?.genDisposableKeypair(); + if (!disposableSigner) { + throw new Error("No signer available"); + } + + return submitUnshieldTx( + rpcUrl, + account!, + chain!, + indexerUrl, + params, + disposableSigner + ); }, }; }); diff --git a/apps/namadillo/src/atoms/shield/services.ts b/apps/namadillo/src/atoms/shield/services.ts index 37cc338b7..049cda1fe 100644 --- a/apps/namadillo/src/atoms/shield/services.ts +++ b/apps/namadillo/src/atoms/shield/services.ts @@ -1,5 +1,6 @@ import { Account, + GenDisposableSignerResponse, ShieldingTransferMsgValue, UnshieldingTransferMsgValue, } from "@namada/types"; @@ -7,18 +8,12 @@ import BigNumber from "bignumber.js"; import * as Comlink from "comlink"; import { EncodedTxData, signTx } from "lib/query"; import { Address, ChainSettings, GasConfig } from "types"; -import { Shield } from "workers/ShieldMessages"; +import { Shield, Unshield } from "workers/MaspTxMessages"; import { - registerTransferHandlers as shieldRegisterTransferHandlers, - Worker as ShieldWorkerApi, -} from "workers/ShieldWorker"; -import ShieldWorker from "workers/ShieldWorker?worker"; -import { Unshield } from "workers/UnshieldMessages"; -import { - registerTransferHandlers as unshieldRegisterTransferHandlers, - Worker as UnshieldWorkerApi, -} from "workers/UnshieldWorker"; -import UnshieldWorker from "workers/UnshieldWorker?worker"; + Worker as MaspTxWorkerApi, + registerTransferHandlers as maspTxRegisterTransferHandlers, +} from "workers/MaspTxWorker"; +import MaspTxWorker from "workers/MaspTxWorker?worker"; export type ShieldTransferParams = { sourceAddress: Address; @@ -54,10 +49,13 @@ export const submitShieldTx = async ( gasConfig, } = params; - shieldRegisterTransferHandlers(); - const worker = new ShieldWorker(); - const shieldWorker = Comlink.wrap(worker); - await shieldWorker.init({ type: "init", payload: { rpcUrl, token } }); + maspTxRegisterTransferHandlers(); + const worker = new MaspTxWorker(); + const shieldWorker = Comlink.wrap(worker); + await shieldWorker.init({ + type: "init", + payload: { rpcUrl, token, maspIndexerUrl: "" }, + }); const shieldingMsgValue = new ShieldingTransferMsgValue({ target, @@ -97,7 +95,8 @@ export const submitUnshieldTx = async ( account: Account, chain: ChainSettings, indexerUrl: string, - params: UnshieldTransferParams + params: UnshieldTransferParams, + disposableSigner: GenDisposableSignerResponse ): Promise<{ msg: Unshield; encodedTx: EncodedTxData; @@ -110,30 +109,38 @@ export const submitUnshieldTx = async ( gasConfig, } = params; - unshieldRegisterTransferHandlers(); - const worker = new UnshieldWorker(); - const unshieldWorker = Comlink.wrap(worker); - await unshieldWorker.init({ type: "init", payload: { rpcUrl, token } }); + maspTxRegisterTransferHandlers(); + const worker = new MaspTxWorker(); + const unshieldWorker = Comlink.wrap(worker); + await unshieldWorker.init({ + type: "init", + payload: { rpcUrl, token, maspIndexerUrl: "" }, + }); const unshieldingMsgValue = new UnshieldingTransferMsgValue({ source, + gasSpendingKey: source, data: [{ target, token, amount }], }); const msg: Unshield = { type: "unshield", payload: { - account, + account: { + ...account, + publicKey: disposableSigner.publicKey, + }, gasConfig, unshieldingProps: [unshieldingMsgValue], chain, indexerUrl, + vks: [], }, }; const { payload: encodedTx } = await unshieldWorker.unshield(msg); - const signedTxs = await signTx(encodedTx, source); + const signedTxs = await signTx(encodedTx, disposableSigner.address); await unshieldWorker.broadcast({ type: "broadcast", diff --git a/apps/namadillo/src/types.ts b/apps/namadillo/src/types.ts index 5f522e723..2c50f6a4a 100644 --- a/apps/namadillo/src/types.ts +++ b/apps/namadillo/src/types.ts @@ -21,6 +21,8 @@ type Unique = { uuid: string; }; +export type PublicKey = string; + export type Address = string; export type ChainId = string; diff --git a/apps/namadillo/src/workers/MaspTxMessages.ts b/apps/namadillo/src/workers/MaspTxMessages.ts new file mode 100644 index 000000000..385005af0 --- /dev/null +++ b/apps/namadillo/src/workers/MaspTxMessages.ts @@ -0,0 +1,90 @@ +import BigNumber from "bignumber.js"; + +import { + Account, + ShieldedTransferMsgValue, + ShieldingTransferMsgValue, + TxResponseMsgValue, + UnshieldingTransferMsgValue, +} from "@namada/types"; +import { EncodedTxData } from "lib/query"; +import { ChainSettings } from "types"; +import { WebWorkerMessage } from "./utils"; + +type InitPayload = { + rpcUrl: string; + maspIndexerUrl: string; + token: string; +}; + +export type Init = WebWorkerMessage<"init", InitPayload>; +export type InitDone = WebWorkerMessage<"init-done", null>; + +type ShieldPayload = { + account: Account; + gasConfig: { + gasLimit: BigNumber; + gasPrice: BigNumber; + }; + shieldingProps: ShieldingTransferMsgValue[]; + chain: ChainSettings; + indexerUrl: string; +}; +export type Shield = WebWorkerMessage<"shield", ShieldPayload>; +export type ShieldDone = WebWorkerMessage< + "shield-done", + EncodedTxData +>; + +type UnshieldPayload = { + account: Account; + gasConfig: { + gasLimit: BigNumber; + gasPrice: BigNumber; + }; + unshieldingProps: UnshieldingTransferMsgValue[]; + chain: ChainSettings; + indexerUrl: string; + vks: string[]; +}; +export type Unshield = WebWorkerMessage<"unshield", UnshieldPayload>; +export type UnshieldDone = WebWorkerMessage< + "unshield-done", + EncodedTxData +>; + +type ShieldedTransferPayload = { + account: Account; + gasConfig: { + gasLimit: BigNumber; + gasPrice: BigNumber; + }; + shieldingProps: ShieldedTransferMsgValue[]; + chain: ChainSettings; + vks: string[]; +}; +export type ShieldedTransfer = WebWorkerMessage< + "shielded-transfer", + ShieldedTransferPayload +>; +export type ShieldedTransferDone = WebWorkerMessage< + "shielded-transfer-done", + EncodedTxData +>; + +type BroadcastPayload = { + encodedTx: EncodedTxData; + signedTxs: Uint8Array[]; +}; +export type Broadcast = WebWorkerMessage<"broadcast", BroadcastPayload>; +export type BroadcastDone = WebWorkerMessage< + "broadcast-done", + TxResponseMsgValue[] +>; + +export type ShieldMessageIn = Shield | ShieldedTransfer | Broadcast | Init; +export type ShieldMessageOut = + | ShieldDone + | ShieldedTransferDone + | BroadcastDone + | InitDone; diff --git a/apps/namadillo/src/workers/ShieldWorker.ts b/apps/namadillo/src/workers/MaspTxWorker.ts similarity index 56% rename from apps/namadillo/src/workers/ShieldWorker.ts rename to apps/namadillo/src/workers/MaspTxWorker.ts index ded7e12ee..e2e280d36 100644 --- a/apps/namadillo/src/workers/ShieldWorker.ts +++ b/apps/namadillo/src/workers/MaspTxWorker.ts @@ -1,7 +1,12 @@ import { Configuration, DefaultApi } from "@namada/indexer-client"; import { initMulticore } from "@namada/sdk/inline-init"; import { getSdk, Sdk } from "@namada/sdk/web"; -import { ShieldingTransferMsgValue, TxResponseMsgValue } from "@namada/types"; +import { + ShieldedTransferMsgValue, + ShieldingTransferMsgValue, + TxResponseMsgValue, + UnshieldingTransferMsgValue, +} from "@namada/types"; import * as Comlink from "comlink"; import { buildTx, EncodedTxData } from "lib/query"; import { @@ -11,7 +16,11 @@ import { InitDone, Shield, ShieldDone, -} from "./ShieldMessages"; + ShieldedTransfer, + ShieldedTransferDone, + Unshield, + UnshieldDone, +} from "./MaspTxMessages"; import { registerBNTransferHandler } from "./utils"; export class Worker { @@ -33,6 +42,26 @@ export class Worker { }; } + async unshield(m: Unshield): Promise { + if (!this.sdk) { + throw new Error("SDK is not initialized"); + } + return { + type: "unshield-done", + payload: await unshield(this.sdk, m.payload), + }; + } + + async shieldedTransfer(m: ShieldedTransfer): Promise { + if (!this.sdk) { + throw new Error("SDK is not initialized"); + } + return { + type: "shielded-transfer-done", + payload: await shieldedTransfer(this.sdk, m.payload), + }; + } + async broadcast(m: Broadcast): Promise { if (!this.sdk) { throw new Error("SDK is not initialized"); @@ -70,6 +99,50 @@ async function shield( return encodedTxData; } +async function unshield( + sdk: Sdk, + payload: Unshield["payload"] +): Promise> { + const { account, gasConfig, chain, unshieldingProps, vks } = payload; + + await sdk.rpc.shieldedSync(vks); + await sdk.masp.loadMaspParams(""); + + const encodedTxData = await buildTx( + sdk, + account, + gasConfig, + chain, + unshieldingProps, + sdk.tx.buildUnshieldingTransfer, + true + ); + + return encodedTxData; +} + +async function shieldedTransfer( + sdk: Sdk, + payload: ShieldedTransfer["payload"] +): Promise> { + const { account, gasConfig, chain, shieldingProps, vks } = payload; + + await sdk.rpc.shieldedSync(vks); + await sdk.masp.loadMaspParams(""); + + const encodedTxData = await buildTx( + sdk, + account, + gasConfig, + chain, + shieldingProps, + sdk.tx.buildShieldedTransfer, + true + ); + + return encodedTxData; +} + // TODO: We will probably move this to the separate worker async function broadcast( sdk: Sdk, @@ -95,13 +168,17 @@ function newSdk( cryptoMemory: WebAssembly.Memory, payload: Init["payload"] ): Sdk { - const { rpcUrl, token } = payload; - return getSdk(cryptoMemory, rpcUrl, "", "", token); + const { rpcUrl, token, maspIndexerUrl } = payload; + return getSdk(cryptoMemory, rpcUrl, maspIndexerUrl, "", token); } export const registerTransferHandlers = (): void => { registerBNTransferHandler("shield-done"); registerBNTransferHandler("shield"); + registerBNTransferHandler("shielded-transfer-done"); + registerBNTransferHandler("shielded-transfer"); + registerBNTransferHandler("unshield-done"); + registerBNTransferHandler("unshield"); registerBNTransferHandler("broadcast"); }; diff --git a/apps/namadillo/src/workers/ShieldMessages.ts b/apps/namadillo/src/workers/ShieldMessages.ts deleted file mode 100644 index 9ff562b64..000000000 --- a/apps/namadillo/src/workers/ShieldMessages.ts +++ /dev/null @@ -1,47 +0,0 @@ -import BigNumber from "bignumber.js"; - -import { - Account, - ShieldingTransferMsgValue, - TxResponseMsgValue, -} from "@namada/types"; -import { EncodedTxData } from "lib/query"; -import { ChainSettings } from "types"; -import { WebWorkerMessage } from "./utils"; - -type InitPayload = { - rpcUrl: string; - token: string; -}; - -export type Init = WebWorkerMessage<"init", InitPayload>; -export type InitDone = WebWorkerMessage<"init-done", null>; - -type ShieldPayload = { - account: Account; - gasConfig: { - gasLimit: BigNumber; - gasPrice: BigNumber; - }; - shieldingProps: ShieldingTransferMsgValue[]; - chain: ChainSettings; - indexerUrl: string; -}; -export type Shield = WebWorkerMessage<"shield", ShieldPayload>; -export type ShieldDone = WebWorkerMessage< - "shield-done", - EncodedTxData ->; - -type BroadcastPayload = { - encodedTx: EncodedTxData; - signedTxs: Uint8Array[]; -}; -export type Broadcast = WebWorkerMessage<"broadcast", BroadcastPayload>; -export type BroadcastDone = WebWorkerMessage< - "broadcast-done", - TxResponseMsgValue[] ->; - -export type ShieldMessageIn = Shield | Broadcast | Init; -export type ShieldMessageOut = ShieldDone | BroadcastDone | InitDone; diff --git a/apps/namadillo/src/workers/UnshieldMessages.ts b/apps/namadillo/src/workers/UnshieldMessages.ts deleted file mode 100644 index bf65d08a2..000000000 --- a/apps/namadillo/src/workers/UnshieldMessages.ts +++ /dev/null @@ -1,47 +0,0 @@ -import BigNumber from "bignumber.js"; - -import { - Account, - TxResponseMsgValue, - UnshieldingTransferMsgValue, -} from "@namada/types"; -import { EncodedTxData } from "lib/query"; -import { ChainSettings } from "types"; -import { WebWorkerMessage } from "./utils"; - -type InitPayload = { - rpcUrl: string; - token: string; -}; - -export type Init = WebWorkerMessage<"init", InitPayload>; -export type InitDone = WebWorkerMessage<"init-done", null>; - -type UnshieldPayload = { - account: Account; - gasConfig: { - gasLimit: BigNumber; - gasPrice: BigNumber; - }; - unshieldingProps: UnshieldingTransferMsgValue[]; - chain: ChainSettings; - indexerUrl: string; -}; -export type Unshield = WebWorkerMessage<"unshield", UnshieldPayload>; -export type UnshieldDone = WebWorkerMessage< - "unshield-done", - EncodedTxData ->; - -type BroadcastPayload = { - encodedTx: EncodedTxData; - signedTxs: Uint8Array[]; -}; -export type Broadcast = WebWorkerMessage<"broadcast", BroadcastPayload>; -export type BroadcastDone = WebWorkerMessage< - "broadcast-done", - TxResponseMsgValue[] ->; - -export type UnshieldMessageIn = Unshield | Broadcast | Init; -export type UnshieldMessageOut = UnshieldDone | BroadcastDone | InitDone; diff --git a/apps/namadillo/src/workers/UnshieldWorker.ts b/apps/namadillo/src/workers/UnshieldWorker.ts deleted file mode 100644 index 6c8592a49..000000000 --- a/apps/namadillo/src/workers/UnshieldWorker.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Configuration, DefaultApi } from "@namada/indexer-client"; -import { initMulticore } from "@namada/sdk/inline-init"; -import { getSdk, Sdk } from "@namada/sdk/web"; -import { TxResponseMsgValue, UnshieldingTransferMsgValue } from "@namada/types"; -import * as Comlink from "comlink"; -import { buildTx, EncodedTxData } from "lib/query"; -import { - Broadcast, - BroadcastDone, - Init, - InitDone, - Unshield, - UnshieldDone, -} from "./UnshieldMessages"; -import { registerBNTransferHandler } from "./utils"; - -export class Worker { - private sdk: Sdk | undefined; - - async init(m: Init): Promise { - const { cryptoMemory } = await initMulticore(); - this.sdk = newSdk(cryptoMemory, m.payload); - return { type: "init-done", payload: null }; - } - - async unshield(m: Unshield): Promise { - if (!this.sdk) { - throw new Error("SDK is not initialized"); - } - return { - type: "unshield-done", - payload: await unshield(this.sdk, m.payload), - }; - } - - async broadcast(m: Broadcast): Promise { - if (!this.sdk) { - throw new Error("SDK is not initialized"); - } - - const res = await broadcast(this.sdk, m.payload); - - return { type: "broadcast-done", payload: res }; - } -} - -async function unshield( - sdk: Sdk, - payload: Unshield["payload"] -): Promise> { - const { indexerUrl, account, gasConfig, chain, unshieldingProps } = payload; - - const configuration = new Configuration({ basePath: indexerUrl }); - const api = new DefaultApi(configuration); - const publicKeyRevealed = ( - await api.apiV1RevealedPublicKeyAddressGet(account.address) - ).data.publicKey; - - await sdk.masp.loadMaspParams(""); - const encodedTxData = await buildTx( - sdk, - account, - gasConfig, - chain, - unshieldingProps, - sdk.tx.buildUnshieldingTransfer, - Boolean(publicKeyRevealed) - ); - - return encodedTxData; -} - -// TODO: We will probably move this to the separate worker -async function broadcast( - sdk: Sdk, - payload: Broadcast["payload"] -): Promise { - const { encodedTx, signedTxs } = payload; - - const result: TxResponseMsgValue[] = []; - - for await (const signedTx of signedTxs) { - for await (const _ of encodedTx.txs) { - const response = await sdk.rpc.broadcastTx( - signedTx, - encodedTx.wrapperTxProps - ); - result.push(response); - } - } - return result; -} - -function newSdk( - cryptoMemory: WebAssembly.Memory, - payload: Init["payload"] -): Sdk { - const { rpcUrl, token } = payload; - return getSdk(cryptoMemory, rpcUrl, "", "", token); -} - -export const registerTransferHandlers = (): void => { - registerBNTransferHandler("unshield-done"); - registerBNTransferHandler("unshield"); - registerBNTransferHandler("broadcast"); -}; - -registerTransferHandlers(); -Comlink.expose(new Worker()); diff --git a/packages/crypto/lib/src/crypto/bip32.rs b/packages/crypto/lib/src/crypto/bip32.rs index 30fccaad3..7cd4e5ddd 100644 --- a/packages/crypto/lib/src/crypto/bip32.rs +++ b/packages/crypto/lib/src/crypto/bip32.rs @@ -1,4 +1,5 @@ use crate::crypto::pointer_types::{StringPointer, VecU8Pointer}; +use rand::{rngs::OsRng, RngCore}; use slip10_ed25519; use thiserror::Error; use wasm_bindgen::prelude::*; @@ -80,6 +81,16 @@ impl HDWallet { Ok(private) } + + pub fn disposable_keypair() -> Result { + let path = vec![44, 877, 0, 0, 0]; + let mut key = [0u8; 32]; + OsRng.fill_bytes(&mut key); + + let key = slip10_ed25519::derive_ed25519_private_key(&key, &path); + + Key::new(Vec::from(key)) + } } #[cfg(test)] diff --git a/packages/crypto/lib/src/crypto/mod.rs b/packages/crypto/lib/src/crypto/mod.rs index ab45fe373..229d74e98 100644 --- a/packages/crypto/lib/src/crypto/mod.rs +++ b/packages/crypto/lib/src/crypto/mod.rs @@ -2,7 +2,7 @@ pub mod aes; pub mod argon2; pub mod bip32; pub mod bip39; +pub mod pointer_types; pub mod rng; pub mod salt; pub mod zip32; -pub mod pointer_types; diff --git a/packages/sdk/src/crypto/crypto.ts b/packages/sdk/src/crypto/crypto.ts index f3554e9a4..9b64c9ecc 100644 --- a/packages/sdk/src/crypto/crypto.ts +++ b/packages/sdk/src/crypto/crypto.ts @@ -17,7 +17,7 @@ export class Crypto { /** * @param cryptoMemory - WebAssembly Memory for crypto */ - constructor(protected readonly cryptoMemory: WebAssembly.Memory) {} + constructor(protected readonly cryptoMemory: WebAssembly.Memory) { } /** * Provide object for storing encrypted data diff --git a/packages/sdk/src/keys/keys.ts b/packages/sdk/src/keys/keys.ts index 320d7bc94..5c79af6e9 100644 --- a/packages/sdk/src/keys/keys.ts +++ b/packages/sdk/src/keys/keys.ts @@ -160,9 +160,9 @@ export class Keys { /** * Derive shielded keys and address from private key bytes - * @param seed - Seed - * @param [path] - Zip32 path object - * @param [diversifier] - Diversifier bytes + * @param privateKeyBytes - secret + * @param path - Zip32 path object + * @param diversifier - Diversifier bytes * @returns Shielded keys and address */ deriveShieldedFromPrivateKey( @@ -174,6 +174,13 @@ export class Keys { return this.deriveFromShieldedWallet(shieldedHdWallet, path, diversifier); } + /** + * + * @param shieldedHdWallet - Shielded HD Wallet instance + * @param path - Zip32 path object + * @param diversifier - Diversifier bytes + * @returns Object representing MASP related keys + */ private deriveFromShieldedWallet( shieldedHdWallet: ShieldedHDWallet, path: Zip32Path, @@ -194,6 +201,9 @@ export class Keys { const address = new PaymentAddress(paymentAddress).encode(); const spendingKey = extendedSpendingKey.encode(); const viewingKey = extendedViewingKey.encode(); + const pseudoExtendedKey = extendedSpendingKey + .to_pseudo_extended_key() + .encode(); // Clear wasm resources from memory shieldedHdWallet.free(); @@ -205,6 +215,28 @@ export class Keys { address, spendingKey, viewingKey, + pseudoExtendedKey, + }; + } + + /** + * Generate a disposable transparent keypair + * @returns Keys and address + */ + genDisposableKeypair(): TransparentKeys { + const key = HDWallet.disposable_keypair(); + const privateKeyStringPtr = key.to_hex(); + const privateKey = readStringPointer( + privateKeyStringPtr, + this.cryptoMemory + ); + + key.free(); + privateKeyStringPtr.free(); + + return { + ...this.getAddress(privateKey), + privateKey, }; } } diff --git a/packages/sdk/src/keys/types.ts b/packages/sdk/src/keys/types.ts index d4376190b..de1c8ac2b 100644 --- a/packages/sdk/src/keys/types.ts +++ b/packages/sdk/src/keys/types.ts @@ -20,4 +20,5 @@ export type ShieldedKeys = { address: string; viewingKey: string; spendingKey: string; + pseudoExtendedKey: string; }; diff --git a/packages/sdk/src/ledger.ts b/packages/sdk/src/ledger.ts index 891139ff7..e4ad28ee4 100644 --- a/packages/sdk/src/ledger.ts +++ b/packages/sdk/src/ledger.ts @@ -180,7 +180,7 @@ export class Ledger { nsk: nsk?.toString(), }, }; - } catch (e) { + } catch (_) { throw new Error(`Could not retrieve Viewing Key`); } } diff --git a/packages/sdk/src/rpc/rpc.ts b/packages/sdk/src/rpc/rpc.ts index 0be729db2..bb7690204 100644 --- a/packages/sdk/src/rpc/rpc.ts +++ b/packages/sdk/src/rpc/rpc.ts @@ -36,7 +36,7 @@ export class Rpc { constructor( protected readonly sdk: SdkWasm, protected readonly query: QueryWasm - ) {} + ) { } /** * Query balances from chain @@ -236,9 +236,10 @@ export class Rpc { * Sync the shielded context * @async * @param vks - Array of viewing keys + * @param sks - Array of spending keys * @returns */ - async shieldedSync(vks: string[]): Promise { - await this.query.shielded_sync(vks); + async shieldedSync(vks: string[], sks: string[] = []): Promise { + await this.query.shielded_sync(vks, sks); } } diff --git a/packages/sdk/src/signing.ts b/packages/sdk/src/signing.ts index ecd2a969f..98456cf2b 100644 --- a/packages/sdk/src/signing.ts +++ b/packages/sdk/src/signing.ts @@ -17,19 +17,25 @@ export class Signing { * Sign Namada transaction * @param txProps - TxProps * @param signingKey - private key + * @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, + 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; - return await this.sdk.sign_tx(txBytes, signingKey, chainId); + return await this.sdk.sign_tx(txBytesFinal, signingKey, chainId); } /** diff --git a/packages/shared/lib/.cargo/config.toml b/packages/shared/lib/.cargo/config.toml new file mode 100644 index 000000000..f4e8c002f --- /dev/null +++ b/packages/shared/lib/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/packages/shared/lib/Cargo.lock b/packages/shared/lib/Cargo.lock index c4f60e291..982cce9be 100644 --- a/packages/shared/lib/Cargo.lock +++ b/packages/shared/lib/Cargo.lock @@ -80,9 +80,9 @@ checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" [[package]] name = "ark-bls12-381" @@ -2772,7 +2772,7 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "masp_note_encryption" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=12ed8b060b295c06502a2ff8468e4a941cb7cca4#12ed8b060b295c06502a2ff8468e4a941cb7cca4" +source = "git+https://github.com/anoma/masp?rev=0d0da3507a6f9ad135f00fd8201dc54c2f1d9efe#0d0da3507a6f9ad135f00fd8201dc54c2f1d9efe" dependencies = [ "borsh", "chacha20", @@ -2785,7 +2785,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=12ed8b060b295c06502a2ff8468e4a941cb7cca4#12ed8b060b295c06502a2ff8468e4a941cb7cca4" +source = "git+https://github.com/anoma/masp?rev=0d0da3507a6f9ad135f00fd8201dc54c2f1d9efe#0d0da3507a6f9ad135f00fd8201dc54c2f1d9efe" dependencies = [ "aes", "bip0039", @@ -2816,7 +2816,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=12ed8b060b295c06502a2ff8468e4a941cb7cca4#12ed8b060b295c06502a2ff8468e4a941cb7cca4" +source = "git+https://github.com/anoma/masp?rev=0d0da3507a6f9ad135f00fd8201dc54c2f1d9efe#0d0da3507a6f9ad135f00fd8201dc54c2f1d9efe" dependencies = [ "bellman", "blake2b_simd", @@ -2886,86 +2886,29 @@ checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" [[package]] name = "namada_account" version = "0.46.0" -source = "git+https://github.com/anoma/namada?tag=v0.46.0#cc531ec513b8ade85b221dd0a6c11818927fd247" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "borsh", - "namada_core 0.46.0", - "namada_macros 0.46.0", - "namada_storage 0.46.0", - "serde", -] - -[[package]] -name = "namada_account" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" -dependencies = [ - "borsh", - "namada_core 0.46.1", - "namada_macros 0.46.1", - "namada_storage 0.46.1", + "namada_core", + "namada_macros", + "namada_storage", "serde", ] [[package]] name = "namada_controller" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" -dependencies = [ - "namada_core 0.46.1", - "smooth-operator", - "thiserror", -] - -[[package]] -name = "namada_core" version = "0.46.0" -source = "git+https://github.com/anoma/namada?tag=v0.46.0#cc531ec513b8ade85b221dd0a6c11818927fd247" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ - "bech32 0.8.1", - "borsh", - "borsh-ext", - "chrono", - "data-encoding", - "ed25519-consensus", - "ethabi", - "ethbridge-structs", - "eyre", - "ibc", - "ics23", - "impl-num-traits", - "index-set", - "indexmap 2.2.4", - "k256", - "masp_primitives", - "namada_macros 0.46.0", - "num-integer", - "num-rational", - "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", - "num256", - "num_enum", - "primitive-types", - "prost-types 0.13.2", - "rayon", - "ripemd", - "serde", - "serde_json", - "sha2 0.9.9", + "namada_core", "smooth-operator", - "sparse-merkle-tree", - "tendermint 0.38.1", - "tendermint-proto 0.38.1", "thiserror", - "tiny-keccak", - "tracing", - "uint", - "zeroize", ] [[package]] name = "namada_core" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "bech32 0.8.1", "borsh", @@ -2984,7 +2927,7 @@ dependencies = [ "k256", "lazy_static", "masp_primitives", - "namada_macros 0.46.1", + "namada_macros", "num-integer", "num-rational", "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3014,24 +2957,24 @@ dependencies = [ [[package]] name = "namada_ethereum_bridge" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "borsh", "ethers", "eyre", "itertools 0.12.1", "konst", - "namada_core 0.46.1", - "namada_events 0.46.1", - "namada_macros 0.46.1", + "namada_core", + "namada_events", + "namada_macros", "namada_parameters", "namada_proof_of_stake", "namada_state", - "namada_storage 0.46.1", + "namada_storage", "namada_systems", "namada_trans_token", - "namada_tx 0.46.1", + "namada_tx", "namada_vote_ext", "namada_vp_env", "serde", @@ -3043,25 +2986,11 @@ dependencies = [ [[package]] name = "namada_events" version = "0.46.0" -source = "git+https://github.com/anoma/namada?tag=v0.46.0#cc531ec513b8ade85b221dd0a6c11818927fd247" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "borsh", - "namada_core 0.46.0", - "namada_macros 0.46.0", - "serde", - "serde_json", - "thiserror", - "tracing", -] - -[[package]] -name = "namada_events" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" -dependencies = [ - "borsh", - "namada_core 0.46.1", - "namada_macros 0.46.1", + "namada_core", + "namada_macros", "serde", "serde_json", "thiserror", @@ -3071,44 +3000,31 @@ dependencies = [ [[package]] name = "namada_gas" version = "0.46.0" -source = "git+https://github.com/anoma/namada?tag=v0.46.0#cc531ec513b8ade85b221dd0a6c11818927fd247" -dependencies = [ - "borsh", - "namada_core 0.46.0", - "namada_events 0.46.0", - "namada_macros 0.46.0", - "serde", - "thiserror", -] - -[[package]] -name = "namada_gas" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "borsh", - "namada_core 0.46.1", - "namada_events 0.46.1", - "namada_macros 0.46.1", + "namada_core", + "namada_events", + "namada_macros", "serde", "thiserror", ] [[package]] name = "namada_governance" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "borsh", "itertools 0.12.1", "konst", - "namada_account 0.46.1", - "namada_core 0.46.1", - "namada_events 0.46.1", - "namada_macros 0.46.1", + "namada_account", + "namada_core", + "namada_events", + "namada_macros", "namada_state", "namada_systems", - "namada_tx 0.46.1", + "namada_tx", "namada_vp_env", "serde", "serde_json", @@ -3119,8 +3035,8 @@ dependencies = [ [[package]] name = "namada_ibc" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "borsh", "data-encoding", @@ -3129,13 +3045,13 @@ dependencies = [ "ics23", "konst", "masp_primitives", - "namada_core 0.46.1", - "namada_events 0.46.1", - "namada_gas 0.46.1", - "namada_macros 0.46.1", + "namada_core", + "namada_events", + "namada_gas", + "namada_macros", "namada_state", "namada_systems", - "namada_tx 0.46.1", + "namada_tx", "namada_vp", "primitive-types", "prost 0.13.2", @@ -3149,12 +3065,12 @@ dependencies = [ [[package]] name = "namada_io" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "async-trait", "kdam", - "namada_core 0.46.1", + "namada_core", "tendermint-rpc", "thiserror", "tokio", @@ -3163,19 +3079,7 @@ dependencies = [ [[package]] name = "namada_macros" version = "0.46.0" -source = "git+https://github.com/anoma/namada?tag=v0.46.0#cc531ec513b8ade85b221dd0a6c11818927fd247" -dependencies = [ - "data-encoding", - "proc-macro2", - "quote", - "sha2 0.9.9", - "syn 1.0.109", -] - -[[package]] -name = "namada_macros" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "data-encoding", "proc-macro2", @@ -3187,28 +3091,13 @@ dependencies = [ [[package]] name = "namada_merkle_tree" version = "0.46.0" -source = "git+https://github.com/anoma/namada?tag=v0.46.0#cc531ec513b8ade85b221dd0a6c11818927fd247" -dependencies = [ - "borsh", - "eyre", - "ics23", - "namada_core 0.46.0", - "namada_macros 0.46.0", - "prost 0.13.2", - "sparse-merkle-tree", - "thiserror", -] - -[[package]] -name = "namada_merkle_tree" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "borsh", "eyre", "ics23", - "namada_core 0.46.1", - "namada_macros 0.46.1", + "namada_core", + "namada_macros", "prost 0.13.2", "sparse-merkle-tree", "thiserror", @@ -3216,14 +3105,14 @@ dependencies = [ [[package]] name = "namada_parameters" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ - "namada_core 0.46.1", - "namada_macros 0.46.1", + "namada_core", + "namada_macros", "namada_state", "namada_systems", - "namada_tx 0.46.1", + "namada_tx", "namada_vp_env", "smooth-operator", "thiserror", @@ -3231,20 +3120,20 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "borsh", "itertools 0.12.1", "konst", - "namada_account 0.46.1", + "namada_account", "namada_controller", - "namada_core 0.46.1", - "namada_events 0.46.1", - "namada_macros 0.46.1", + "namada_core", + "namada_events", + "namada_macros", "namada_state", "namada_systems", - "namada_tx 0.46.1", + "namada_tx", "namada_vp_env", "once_cell", "serde", @@ -3256,23 +3145,15 @@ dependencies = [ [[package]] name = "namada_replay_protection" version = "0.46.0" -source = "git+https://github.com/anoma/namada?tag=v0.46.0#cc531ec513b8ade85b221dd0a6c11818927fd247" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ - "namada_core 0.46.0", -] - -[[package]] -name = "namada_replay_protection" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" -dependencies = [ - "namada_core 0.46.1", + "namada_core", ] [[package]] name = "namada_sdk" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "async-trait", "bimap", @@ -3292,21 +3173,21 @@ dependencies = [ "lazy_static", "masp_primitives", "masp_proofs", - "namada_account 0.46.1", - "namada_core 0.46.1", + "namada_account", + "namada_core", "namada_ethereum_bridge", - "namada_events 0.46.1", - "namada_gas 0.46.1", + "namada_events", + "namada_gas", "namada_governance", "namada_ibc", "namada_io", - "namada_macros 0.46.1", + "namada_macros", "namada_parameters", "namada_proof_of_stake", "namada_state", - "namada_storage 0.46.1", + "namada_storage", "namada_token", - "namada_tx 0.46.1", + "namada_tx", "namada_vm", "namada_vote_ext", "namada_vp", @@ -3340,8 +3221,8 @@ dependencies = [ [[package]] name = "namada_shielded_token" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "async-trait", "borsh", @@ -3352,16 +3233,16 @@ dependencies = [ "lazy_static", "masp_primitives", "masp_proofs", - "namada_account 0.46.1", + "namada_account", "namada_controller", - "namada_core 0.46.1", - "namada_events 0.46.1", - "namada_gas 0.46.1", + "namada_core", + "namada_events", + "namada_gas", "namada_io", - "namada_macros 0.46.1", + "namada_macros", "namada_state", "namada_systems", - "namada_tx 0.46.1", + "namada_tx", "namada_vp_env", "namada_wallet", "rand 0.8.5", @@ -3381,21 +3262,21 @@ dependencies = [ [[package]] name = "namada_state" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "borsh", "clru", "itertools 0.12.1", - "namada_core 0.46.1", - "namada_events 0.46.1", - "namada_gas 0.46.1", - "namada_macros 0.46.1", - "namada_merkle_tree 0.46.1", - "namada_replay_protection 0.46.1", - "namada_storage 0.46.1", + "namada_core", + "namada_events", + "namada_gas", + "namada_macros", + "namada_merkle_tree", + "namada_replay_protection", + "namada_storage", "namada_systems", - "namada_tx 0.46.1", + "namada_tx", "patricia_tree", "smooth-operator", "thiserror", @@ -3405,34 +3286,15 @@ dependencies = [ [[package]] name = "namada_storage" version = "0.46.0" -source = "git+https://github.com/anoma/namada?tag=v0.46.0#cc531ec513b8ade85b221dd0a6c11818927fd247" -dependencies = [ - "borsh", - "itertools 0.12.1", - "namada_core 0.46.0", - "namada_gas 0.46.0", - "namada_macros 0.46.0", - "namada_merkle_tree 0.46.0", - "namada_replay_protection 0.46.0", - "regex", - "serde", - "smooth-operator", - "thiserror", - "tracing", -] - -[[package]] -name = "namada_storage" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "borsh", "itertools 0.12.1", - "namada_core 0.46.1", - "namada_gas 0.46.1", - "namada_macros 0.46.1", - "namada_merkle_tree 0.46.1", - "namada_replay_protection 0.46.1", + "namada_core", + "namada_gas", + "namada_macros", + "namada_merkle_tree", + "namada_replay_protection", "regex", "serde", "smooth-operator", @@ -3442,43 +3304,43 @@ dependencies = [ [[package]] name = "namada_systems" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ - "namada_core 0.46.1", - "namada_events 0.46.1", - "namada_storage 0.46.1", + "namada_core", + "namada_events", + "namada_storage", ] [[package]] name = "namada_token" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "borsh", - "namada_core 0.46.1", - "namada_events 0.46.1", - "namada_macros 0.46.1", + "namada_core", + "namada_events", + "namada_macros", "namada_shielded_token", - "namada_storage 0.46.1", + "namada_storage", "namada_systems", "namada_trans_token", - "namada_tx 0.46.1", + "namada_tx", "namada_tx_env", "serde", ] [[package]] name = "namada_trans_token" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "konst", - "namada_core 0.46.1", - "namada_events 0.46.1", + "namada_core", + "namada_events", "namada_state", "namada_systems", - "namada_tx 0.46.1", + "namada_tx", "namada_tx_env", "namada_vp_env", "thiserror", @@ -3488,7 +3350,7 @@ dependencies = [ [[package]] name = "namada_tx" version = "0.46.0" -source = "git+https://github.com/anoma/namada?tag=v0.46.0#cc531ec513b8ade85b221dd0a6c11818927fd247" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "ark-bls12-381", "bitflags 2.5.0", @@ -3497,40 +3359,11 @@ dependencies = [ "either", "konst", "masp_primitives", - "namada_account 0.46.0", - "namada_core 0.46.0", - "namada_events 0.46.0", - "namada_gas 0.46.0", - "namada_macros 0.46.0", - "num-derive 0.4.2", - "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", - "prost 0.13.2", - "prost-types 0.13.2", - "rand_core 0.6.4", - "serde", - "serde_json", - "sha2 0.9.9", - "thiserror", - "tonic-build", -] - -[[package]] -name = "namada_tx" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" -dependencies = [ - "ark-bls12-381", - "bitflags 2.5.0", - "borsh", - "data-encoding", - "either", - "konst", - "masp_primitives", - "namada_account 0.46.1", - "namada_core 0.46.1", - "namada_events 0.46.1", - "namada_gas 0.46.1", - "namada_macros 0.46.1", + "namada_account", + "namada_core", + "namada_events", + "namada_gas", + "namada_macros", "num-derive 0.4.2", "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "prost 0.13.2", @@ -3545,29 +3378,29 @@ dependencies = [ [[package]] name = "namada_tx_env" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ - "namada_core 0.46.1", - "namada_events 0.46.1", - "namada_storage 0.46.1", + "namada_core", + "namada_events", + "namada_storage", ] [[package]] name = "namada_vm" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "borsh", "clru", - "namada_account 0.46.1", - "namada_core 0.46.1", - "namada_events 0.46.1", - "namada_gas 0.46.1", + "namada_account", + "namada_core", + "namada_events", + "namada_gas", "namada_parameters", "namada_state", "namada_token", - "namada_tx 0.46.1", + "namada_tx", "namada_vp", "smooth-operator", "thiserror", @@ -3577,26 +3410,26 @@ dependencies = [ [[package]] name = "namada_vote_ext" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "borsh", - "namada_core 0.46.1", - "namada_macros 0.46.1", - "namada_tx 0.46.1", + "namada_core", + "namada_macros", + "namada_tx", "serde", ] [[package]] name = "namada_vp" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ - "namada_core 0.46.1", - "namada_events 0.46.1", - "namada_gas 0.46.1", + "namada_core", + "namada_events", + "namada_gas", "namada_state", - "namada_tx 0.46.1", + "namada_tx", "namada_vp_env", "smooth-operator", "thiserror", @@ -3605,23 +3438,23 @@ dependencies = [ [[package]] name = "namada_vp_env" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "derivative", "masp_primitives", - "namada_core 0.46.1", - "namada_events 0.46.1", - "namada_gas 0.46.1", - "namada_storage 0.46.1", - "namada_tx 0.46.1", + "namada_core", + "namada_events", + "namada_gas", + "namada_storage", + "namada_tx", "smooth-operator", ] [[package]] name = "namada_wallet" -version = "0.46.1" -source = "git+https://github.com/anoma/namada?tag=v0.46.1#b24938efd948cb43fc996a729138cb099abcadc0" +version = "0.46.0" +source = "git+https://github.com/anoma/namada?rev=49a4a5d3260423df19ead14df82d18a51fa9b157#49a4a5d3260423df19ead14df82d18a51fa9b157" dependencies = [ "bimap", "borsh", @@ -3630,9 +3463,9 @@ dependencies = [ "derivation-path", "itertools 0.12.1", "masp_primitives", - "namada_core 0.46.1", + "namada_core", "namada_ibc", - "namada_macros 0.46.1", + "namada_macros", "orion", "rand 0.8.5", "rand_core 0.6.4", @@ -4360,7 +4193,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.65", @@ -5097,7 +4930,7 @@ dependencies = [ "hex", "js-sys", "namada_sdk", - "namada_tx 0.46.0", + "namada_tx", "rand 0.8.5", "rayon", "reqwest", diff --git a/packages/shared/lib/Cargo.toml b/packages/shared/lib/Cargo.toml index 288baee76..664a33b36 100644 --- a/packages/shared/lib/Cargo.toml +++ b/packages/shared/lib/Cargo.toml @@ -18,7 +18,7 @@ nodejs = [] web = [] [build-dependencies] -namada_tx = { git = "https://github.com/anoma/namada", tag = "v0.46.0" } +namada_tx = { git = "https://github.com/anoma/namada", rev = "49a4a5d3260423df19ead14df82d18a51fa9b157" } [dependencies] async-trait = {version = "0.1.51"} @@ -27,7 +27,7 @@ chrono = "0.4.22" getrandom = { version = "0.2.7", features = ["js"] } gloo-utils = { version = "0.1.5", features = ["serde"] } js-sys = "0.3.60" -namada_sdk = { git = "https://github.com/anoma/namada", tag="v0.46.1", default-features = false } +namada_sdk = { git = "https://github.com/anoma/namada", rev="49a4a5d3260423df19ead14df82d18a51fa9b157", default-features = false } rand = "0.8.5" rayon = { version = "1.5.3", optional = true } rexie = "0.5" diff --git a/packages/shared/lib/src/query.rs b/packages/shared/lib/src/query.rs index b2ba74195..e815c2a9f 100644 --- a/packages/shared/lib/src/query.rs +++ b/packages/shared/lib/src/query.rs @@ -24,9 +24,9 @@ use namada_sdk::parameters::storage; use namada_sdk::proof_of_stake::Epoch; use namada_sdk::queries::RPC; use namada_sdk::rpc::{ - self, get_public_key_at, get_token_balance, get_total_staked_tokens, - is_steward, query_epoch, query_masp_epoch, query_native_token, query_proposal_by_id, - query_proposal_votes, query_storage_value, + self, get_public_key_at, get_token_balance, get_total_staked_tokens, is_steward, query_epoch, + query_masp_epoch, query_native_token, query_proposal_by_id, query_proposal_votes, + query_storage_value, }; use namada_sdk::state::BlockHeight; use namada_sdk::state::Key; @@ -37,6 +37,7 @@ use namada_sdk::tx::{ }; use namada_sdk::uint::I256; use namada_sdk::wallet::DatedKeypair; +use namada_sdk::ExtendedSpendingKey; use namada_sdk::ExtendedViewingKey; use std::collections::BTreeMap; use std::str::FromStr; @@ -93,8 +94,6 @@ pub struct Query { masp_client: MaspClient, } -const MAX_CONCURRENT_FETCHES: usize = 10; - #[wasm_bindgen] impl Query { #[wasm_bindgen(constructor)] @@ -111,7 +110,8 @@ impl Query { } else { MaspClient::Ledger(LedgerMaspClient::new( client.clone(), - MAX_CONCURRENT_FETCHES, + // Using one does not break the progress indicators + 1, Duration::from_millis(5), )) }; @@ -325,8 +325,12 @@ impl Query { Ok(result) } - pub async fn shielded_sync(&self, owners: Box<[JsValue]>) -> Result<(), JsError> { - let owners: Vec = owners + pub async fn shielded_sync( + &self, + vks: Box<[JsValue]>, + sks: Box<[JsValue]>, + ) -> Result<(), JsError> { + let vks: Vec = vks .iter() .filter_map(|owner| owner.as_string()) .map(|o| { @@ -336,7 +340,13 @@ impl Query { }) .collect(); - let dated_keypairs = owners + let sks = sks + .iter() + .filter_map(|owner| owner.as_string()) + .map(|sk| ExtendedSpendingKey::from_str(&sk).unwrap()) + .collect::>(); + + let dated_keypairs = vks .into_iter() .map(|vk| DatedKeypair { key: vk, @@ -344,14 +354,22 @@ impl Query { }) .collect::>(); + let dated_sks = sks + .into_iter() + .map(|sk| DatedKeypair { + key: sk, + birthday: BlockHeight::from(0), + }) + .collect::>(); + match &self.masp_client { MaspClient::Indexer(client) => { web_sys::console::log_1(&"Syncing using IndexerMaspClient".into()); - self.sync(client.clone(), dated_keypairs).await? + self.sync(client.clone(), dated_keypairs, dated_sks).await? } MaspClient::Ledger(client) => { web_sys::console::log_1(&"Syncing using LedgerMaspClient".into()); - self.sync(client.clone(), dated_keypairs).await? + self.sync(client.clone(), dated_keypairs, dated_sks).await? } }; @@ -362,6 +380,7 @@ impl Query { &self, client: C, dated_keypairs: Vec>, + dated_sks: Vec>, ) -> Result<(), JsError> where C: NamadaMaspClient + Send + Sync + Unpin + 'static, @@ -393,7 +412,13 @@ impl Query { let mut shielded_context: ShieldedContext = ShieldedContext::default(); shielded_context - .sync(env, config, None, &[], dated_keypairs.as_slice()) + .sync( + env, + config, + None, + dated_sks.as_slice(), + dated_keypairs.as_slice(), + ) .await .map_err(|e| JsError::new(&format!("{:?}", e)))?; @@ -463,10 +488,7 @@ impl Query { let mut mapped_result: Vec<(Address, String)> = vec![]; for (token, amount) in result { - mapped_result.push(( - token.clone(), - amount.to_string() - )) + mapped_result.push((token.clone(), amount.to_string())) } to_js_result(mapped_result) @@ -571,6 +593,7 @@ impl Query { Ok(Uint8Array::from(writer.as_slice())) } + #[allow(clippy::type_complexity)] pub async fn query_proposal_votes( &self, proposal_id: u64, @@ -594,6 +617,7 @@ impl Query { (address.clone(), String::from(vote), voting_power) })); + // TODO: refactor this to fix type_complexity clippy warning let delegator_votes: Vec<(Address, String, Vec<(Address, token::Amount)>)> = Vec::from_iter(votes.delegators_vote.iter().map(|(address, vote)| { let vote = match vote { diff --git a/packages/shared/lib/src/rpc_client.rs b/packages/shared/lib/src/rpc_client.rs index 5c311a8e2..4327224e3 100644 --- a/packages/shared/lib/src/rpc_client.rs +++ b/packages/shared/lib/src/rpc_client.rs @@ -114,9 +114,8 @@ impl Client for HttpClient { ) .await?; let response = response.clone(); - let code = Code::from(response.code); - match code { + match response.code { Code::Ok => Ok(EncodedResponseQuery { data: response.value, info: response.info, diff --git a/packages/shared/lib/src/sdk/args.rs b/packages/shared/lib/src/sdk/args.rs index 96a6becd2..d3cb120f3 100644 --- a/packages/shared/lib/src/sdk/args.rs +++ b/packages/shared/lib/src/sdk/args.rs @@ -1,10 +1,23 @@ +use std::ops::Deref; use std::{path::PathBuf, str::FromStr}; use namada_sdk::borsh::{BorshDeserialize, BorshSerialize}; +use namada_sdk::collections::HashMap; use namada_sdk::ibc::core::host::types::identifiers::{ChannelId, PortId}; use namada_sdk::ibc::IbcShieldingData; -use namada_sdk::tendermint_rpc; +use namada_sdk::masp::partial_deauthorize; +use namada_sdk::masp_primitives::sapling::redjubjub::PrivateKey; +use namada_sdk::masp_primitives::sapling::spend_sig; +use namada_sdk::masp_primitives::transaction::components::sapling; +use namada_sdk::masp_primitives::transaction::components::sapling::builder::{ + BuildParams as BuildParamsTrait, RngBuildParams, StoredBuildParams, +}; +use namada_sdk::masp_primitives::transaction::sighash::{signature_hash, SignableInput}; +use namada_sdk::masp_primitives::transaction::txid::TxIdDigester; +use namada_sdk::masp_primitives::zip32; +use namada_sdk::signing::SigningTxData; use namada_sdk::tx::data::GasLimit; +use namada_sdk::tx::{Section, Tx}; use namada_sdk::{ address::Address, args::{self, InputAmount, TxExpiration}, @@ -14,9 +27,13 @@ use namada_sdk::{ token::{Amount, DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES}, TransferSource, }; +use namada_sdk::{error, masp_primitives, tendermint_rpc}; use namada_sdk::{ExtendedSpendingKey, PaymentAddress}; +use rand::rngs::OsRng; use wasm_bindgen::JsError; +use crate::types::masp::PseudoExtendedKey; + #[derive(BorshSerialize, BorshDeserialize, Debug)] #[borsh(crate = "namada_sdk::borsh")] pub struct RevealPkMsg { @@ -521,10 +538,12 @@ pub fn shielded_transfer_tx_args( gas_spending_key, } = shielded_transfer_msg; + let gas_spending_key = gas_spending_key.map(|v| PseudoExtendedKey::decode(v).0); + let mut shielded_transfer_data: Vec = vec![]; for shielded_transfer in data { - let source = ExtendedSpendingKey::from_str(&shielded_transfer.source)?; + let source = PseudoExtendedKey::decode(shielded_transfer.source).0; let target = PaymentAddress::from_str(&shielded_transfer.target)?; let token = Address::from_str(&shielded_transfer.token)?; let denom_amount = @@ -540,15 +559,12 @@ pub fn shielded_transfer_tx_args( } let tx = tx_msg_into_args(tx_msg)?; - let gas_spending_key = gas_spending_key - .map(|v| ExtendedSpendingKey::from_str(&v)) - .transpose()?; let args = args::TxShieldedTransfer { data: shielded_transfer_data, tx, tx_code_path: PathBuf::from("tx_transfer.wasm"), - // TODO: false for now + // false, we do this manually disposable_signing_key: false, gas_spending_key, }; @@ -656,8 +672,8 @@ pub fn unshielding_transfer_tx_args( data, gas_spending_key, } = unshielding_transfer_msg; - let source = ExtendedSpendingKey::from_str(&source)?; - + let source = PseudoExtendedKey::decode(source).0; + let gas_spending_key = gas_spending_key.map(|v| PseudoExtendedKey::decode(v).0); let mut unshielding_transfer_data: Vec = vec![]; for unshielding_transfer in data { @@ -674,9 +690,6 @@ pub fn unshielding_transfer_tx_args( }); } - let gas_spending_key = gas_spending_key - .map(|v| ExtendedSpendingKey::from_str(&v)) - .transpose()?; let tx = tx_msg_into_args(tx_msg)?; let args = args::TxUnshieldingTransfer { @@ -684,7 +697,7 @@ pub fn unshielding_transfer_tx_args( source, tx, gas_spending_key, - // TODO: false for now + // false, we do this manually disposable_signing_key: false, tx_code_path: PathBuf::from("tx_transfer.wasm"), }; @@ -893,7 +906,7 @@ fn tx_msg_into_args(tx_msg: &[u8]) -> Result { let token = Address::from_str(&token)?; let fee_amount = DenominatedAmount::from_str(&fee_amount) - .expect(format!("Fee amount has to be valid. Received {}", fee_amount).as_str()); + .unwrap_or_else(|_| panic!("Fee amount has to be valid. Received {}", fee_amount)); let fee_input_amount = InputAmount::Unvalidated(fee_amount); let public_key = match public_key { @@ -933,7 +946,7 @@ fn tx_msg_into_args(tx_msg: &[u8]) -> Result { wrapper_fee_payer: None, output_folder: None, expiration: TxExpiration::Default, - chain_id: Some(ChainId(String::from(chain_id))), + chain_id: Some(ChainId(chain_id)), signatures: vec![], wrapper_signature: None, signing_keys, @@ -946,3 +959,115 @@ fn tx_msg_into_args(tx_msg: &[u8]) -> Result { Ok(args) } + +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))) + } +} + +// Sign the given transaction's MASP component using real signatures +pub async fn masp_sign( + tx: &mut Tx, + signing_data: &SigningTxData, + mut bparams: T, + xsk: ExtendedSpendingKey, +) -> Result<(), error::Error> +where + T: BuildParamsTrait, +{ + // Get the MASP section that is the target of our signing + if let Some(shielded_hash) = signing_data.shielded_hash { + let mut masp_tx = tx + .get_masp_section(&shielded_hash) + .expect("Expected to find the indicated MASP Transaction") + .clone(); + let masp_builder = tx + .get_masp_builder(&shielded_hash) + .expect("Expected to find the indicated MASP Builder"); + + // Reverse the spend metadata to enable looking up construction + // material + 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; + }; + } + + let tx_data = masp_tx.deref(); + + let unauth_tx_data = partial_deauthorize(tx_data).unwrap(); + + let txid_parts = unauth_tx_data.digest(TxIdDigester); + let sighash = signature_hash(&unauth_tx_data, &SignableInput::Shielded, &txid_parts); + + let mut authorizations = HashMap::new(); + for (tx_pos, _) in descriptor_map.iter().enumerate() { + let pk = PrivateKey(zip32::ExtendedSpendingKey::from(xsk).expsk.ask); + let mut rng = OsRng; + + let sig = spend_sig(pk, bparams.spend_alpha(tx_pos), sighash.as_ref(), &mut rng); + + authorizations.insert(tx_pos, sig); + } + + masp_tx = (*masp_tx) + .clone() + .map_authorization::( + (), + MapSaplingSigAuth(authorizations), + ) + .freeze() + .unwrap(); + + tx.remove_masp_section(&shielded_hash); + tx.add_section(Section::MaspTx(masp_tx)); + } + Ok(()) +} + +struct MapSaplingSigAuth(HashMap::AuthSig>); + +impl sapling::MapAuth for MapSaplingSigAuth { + fn map_proof( + &self, + p: ::Proof, + _pos: usize, + ) -> ::Proof { + p + } + + fn map_auth_sig( + &self, + s: ::AuthSig, + pos: usize, + ) -> ::AuthSig { + self.0.get(&pos).cloned().unwrap_or(s) + } + + fn map_authorization(&self, a: sapling::Authorized) -> sapling::Authorized { + a + } +} diff --git a/packages/shared/lib/src/sdk/masp/masp_web.rs b/packages/shared/lib/src/sdk/masp/masp_web.rs index 3fb80bc34..1c9a06089 100644 --- a/packages/shared/lib/src/sdk/masp/masp_web.rs +++ b/packages/shared/lib/src/sdk/masp/masp_web.rs @@ -13,7 +13,7 @@ const DB_PREFIX: &str = "namada_sdk::MASP"; const SHIELDED_CONTEXT_TABLE: &str = "ShieldedContext"; const SHIELDED_CONTEXT_KEY_CONFIRMED: &str = "shielded-context-confirmed"; const SHIELDED_CONTEXT_KEY_SPECULATIVE: &str = "shielded-context-speculative"; -const SHIELDED_CONTEXT_KEY_TEMP: &str = "shielded-context-speculative"; +const SHIELDED_CONTEXT_KEY_TEMP: &str = "shielded-context-temp"; #[derive(Default, Debug, BorshSerialize, BorshDeserialize, Clone)] #[borsh(crate = "namada_sdk::borsh")] diff --git a/packages/shared/lib/src/sdk/masp/sync.rs b/packages/shared/lib/src/sdk/masp/sync.rs index 6994d6c01..42ca4bc9f 100644 --- a/packages/shared/lib/src/sdk/masp/sync.rs +++ b/packages/shared/lib/src/sdk/masp/sync.rs @@ -55,6 +55,12 @@ mod spawner { pub struct TaskEnvWeb {} +impl Default for TaskEnvWeb { + fn default() -> Self { + Self::new() + } +} + impl TaskEnvWeb { pub fn new() -> Self { Self {} diff --git a/packages/shared/lib/src/sdk/mod.rs b/packages/shared/lib/src/sdk/mod.rs index 0f683b358..69493b1ed 100644 --- a/packages/shared/lib/src/sdk/mod.rs +++ b/packages/shared/lib/src/sdk/mod.rs @@ -13,6 +13,7 @@ use crate::utils::set_panic_hook; #[cfg(feature = "web")] use crate::utils::to_bytes; use crate::utils::to_js_result; +use args::{generate_masp_build_params, masp_sign, BuildParams}; use gloo_utils::format::JsValueSerdeExt; use namada_sdk::address::{Address, MASP}; use namada_sdk::args::{GenIbcShieldingTransfer, InputAmount, Query, TxExpiration}; @@ -24,11 +25,14 @@ use namada_sdk::ibc::core::host::types::identifiers::{ChannelId, PortId}; use namada_sdk::io::NamadaIo; use namada_sdk::key::{common, ed25519, SigScheme}; use namada_sdk::masp::ShieldedContext; +use namada_sdk::masp_primitives::transaction::components::sapling::fees::InputView; +use namada_sdk::masp_primitives::zip32::{ExtendedFullViewingKey, ExtendedKey}; use namada_sdk::rpc::{query_epoch, InnerTxResult}; use namada_sdk::signing::SigningTxData; 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::{ build_batch, build_bond, build_claim_rewards, build_ibc_transfer, build_redelegation, build_reveal_pk, build_shielded_transfer, build_shielding_transfer, build_transparent_transfer, @@ -38,9 +42,27 @@ use namada_sdk::tx::{ }; use namada_sdk::wallet::{Store, Wallet}; use namada_sdk::{Namada, NamadaImpl, PaymentAddress, TransferTarget}; +use std::collections::BTreeMap; 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 { @@ -157,6 +179,51 @@ impl Sdk { Ok(()) } + pub async fn sign_masp(&self, xsks: Box<[String]>, tx: Vec) -> Result { + let tx: tx::Tx = borsh::from_slice(&tx)?; + let mut namada_tx: Tx = borsh::from_slice(&tx.tx_bytes())?; + + // Use keys_map to easily map xfvk to xsk + let mut keys_map = BTreeMap::new(); + for xsk_s in xsks.iter() { + let xsk = namada_sdk::ExtendedSpendingKey::from_str(xsk_s)?; + let xvk = xsk.to_viewing_key(); + let xfvk = ExtendedFullViewingKey::from(xvk); + + keys_map.insert(xfvk, xsk); + } + + for signing_data in tx.signing_data() { + if let Some(masp_signing_data) = signing_data.masp() { + let masp_signing_data = + borsh::from_slice::(&masp_signing_data).ok(); + + if let Some(masp_signing_data) = masp_signing_data { + let signing_tx_data = signing_data.to_signing_tx_data()?; + let bparams = masp_signing_data.bparams(); + + for xfvk in masp_signing_data.xfvks() { + let xsk = keys_map.get(&xfvk).ok_or_err_msg("Can't map xfvk to xsk")?; + masp_sign(&mut namada_tx, &signing_tx_data, bparams.clone(), *xsk).await?; + } + } + } + } + + let signing_data = tx + .signing_tx_data()? + .iter() + .cloned() + .map(|std| (std, None)) + .collect::)>>(); + + // 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)?; + + to_js_result(borsh::to_vec(&tx)?) + } + pub async fn sign_tx( &self, tx: Vec, @@ -274,21 +341,47 @@ impl Sdk { let args = first_tx.args(); let mut txs: Vec<(Tx, SigningTxData)> = vec![]; + let mut masp_map: BTreeMap = BTreeMap::new(); // Iterate through provided tx::Tx and deserialize bytes to Namada Tx for built_tx in built_txs.into_iter() { let tx_bytes = built_tx.tx_bytes(); - let signing_tx_data = built_tx.signing_tx_data()?; - let tx: Tx = Tx::try_from_slice(&tx_bytes)?; - let first_signing_data = signing_tx_data + let signing_data = built_tx.signing_data(); + let first_signing_data = signing_data .first() .expect("At least one signing data should be present on a Tx"); - txs.push((tx, first_signing_data.to_owned())); + let signing_tx_data = first_signing_data.to_signing_tx_data()?; + + if let Some(sh) = signing_tx_data.shielded_hash { + let masp_signing_data = first_signing_data.masp(); + + // We do not need to insert masp_signing_data when we shield + if let Some(masp_signing_data) = masp_signing_data { + let masp_signing_data = ::try_from_slice(&masp_signing_data)?; + masp_map.insert(sh, masp_signing_data); + } + } + + let tx: Tx = Tx::try_from_slice(&tx_bytes)?; + + txs.push((tx, signing_tx_data.to_owned())); } let (tx, signing_data) = build_batch(txs.clone())?; + let signing_data = signing_data + .iter() + .cloned() + .map(|sd| { + if let Some(sh) = sd.shielded_hash { + (sd, masp_map.get(&sh).cloned()) + } else { + (sd, None) + } + }) + .collect::>(); + to_js_result(borsh::to_vec(&tx::Tx::new( tx, &borsh::to_vec(&args)?, @@ -335,7 +428,7 @@ impl Sdk { ) -> Result { let mut args = args::transparent_transfer_tx_args(transfer_msg, wrapper_tx_msg)?; let (tx, signing_data) = build_transparent_transfer(&self.namada, &mut args).await?; - self.serialize_tx_result(tx, wrapper_tx_msg, signing_data) + self.serialize_tx_result(tx, wrapper_tx_msg, signing_data, None) } pub async fn build_shielded_transfer( @@ -344,8 +437,39 @@ impl Sdk { wrapper_tx_msg: &[u8], ) -> Result { let mut args = args::shielded_transfer_tx_args(shielded_transfer_msg, wrapper_tx_msg)?; - let (tx, signing_data) = build_shielded_transfer(&self.namada, &mut args).await?; - self.serialize_tx_result(tx, wrapper_tx_msg, signing_data) + let bparams = + generate_masp_build_params(MAX_HW_SPEND, MAX_HW_CONVERT, MAX_HW_OUTPUT, &args.tx) + .await?; + + let _ = &self.namada.shielded_mut().await.load().await?; + + let xfvks = args + .data + .iter() + .map(|data| data.source.to_viewing_key()) + .collect::>(); + + let ((tx, signing_data), masp_signing_data) = match bparams { + BuildParams::RngBuildParams(mut bparams) => { + let tx = build_shielded_transfer(&self.namada, &mut args, &mut bparams).await?; + let masp_signing_data = MaspSigningData::new( + bparams + .to_stored() + .ok_or_err_msg("Cannot convert bparams to stored")?, + xfvks, + ); + + (tx, masp_signing_data) + } + BuildParams::StoredBuildParams(mut bparams) => { + let tx = build_shielded_transfer(&self.namada, &mut args, &mut bparams).await?; + let masp_signing_data = MaspSigningData::new(bparams, xfvks); + + (tx, masp_signing_data) + } + }; + + self.serialize_tx_result(tx, wrapper_tx_msg, signing_data, Some(masp_signing_data)) } pub async fn build_unshielding_transfer( @@ -355,8 +479,35 @@ impl Sdk { ) -> Result { let mut args = args::unshielding_transfer_tx_args(unshielding_transfer_msg, wrapper_tx_msg)?; - let (tx, signing_data) = build_unshielding_transfer(&self.namada, &mut args).await?; - self.serialize_tx_result(tx, wrapper_tx_msg, signing_data) + let bparams = + generate_masp_build_params(MAX_HW_SPEND, MAX_HW_CONVERT, MAX_HW_OUTPUT, &args.tx) + .await?; + + let _ = &self.namada.shielded_mut().await.load().await?; + + let xfvks = vec![args.source.to_viewing_key()]; + + let ((tx, signing_data), masp_signing_data) = match bparams { + BuildParams::RngBuildParams(mut bparams) => { + let tx = build_unshielding_transfer(&self.namada, &mut args, &mut bparams).await?; + let masp_signing_data = MaspSigningData::new( + bparams + .to_stored() + .ok_or_err_msg("Cannot convert bparams to stored")?, + xfvks, + ); + + (tx, masp_signing_data) + } + BuildParams::StoredBuildParams(mut bparams) => { + let tx = build_unshielding_transfer(&self.namada, &mut args, &mut bparams).await?; + let masp_signing_data = MaspSigningData::new(bparams, xfvks); + + (tx, masp_signing_data) + } + }; + + self.serialize_tx_result(tx, wrapper_tx_msg, signing_data, Some(masp_signing_data)) } pub async fn build_shielding_transfer( @@ -365,9 +516,20 @@ impl Sdk { wrapper_tx_msg: &[u8], ) -> Result { let mut args = args::shielding_transfer_tx_args(shielding_transfer_msg, wrapper_tx_msg)?; - let (tx, signing_data, _masp_epoch) = - build_shielding_transfer(&self.namada, &mut args).await?; - self.serialize_tx_result(tx, wrapper_tx_msg, signing_data) + let bparams = + generate_masp_build_params(MAX_HW_SPEND, MAX_HW_CONVERT, MAX_HW_OUTPUT, &args.tx) + .await?; + + let (tx, signing_data, _) = match bparams { + BuildParams::RngBuildParams(mut bparams) => { + build_shielding_transfer(&self.namada, &mut args, &mut bparams).await? + } + BuildParams::StoredBuildParams(mut bparams) => { + build_shielding_transfer(&self.namada, &mut args, &mut bparams).await? + } + }; + + self.serialize_tx_result(tx, wrapper_tx_msg, signing_data, None) } pub async fn build_ibc_transfer( @@ -376,8 +538,48 @@ impl Sdk { wrapper_tx_msg: &[u8], ) -> Result { let args = args::ibc_transfer_tx_args(ibc_transfer_msg, wrapper_tx_msg)?; - let (tx, signing_data, _) = build_ibc_transfer(&self.namada, &args).await?; - self.serialize_tx_result(tx, wrapper_tx_msg, signing_data) + 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) + } + }; + + // 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 + .get_masp_builder(&shielded_hash) + .ok_or_err_msg("Expected to find the indicated MASP Builder")?; + let xfvks = masp_builder + .builder + .sapling_inputs() + .iter() + .map(|input| input.key()) + .cloned() + .collect::>(); + + let masp_signing_data = MaspSigningData::new(bparams, xfvks); + + Some(masp_signing_data) + } else { + None + }; + + self.serialize_tx_result(tx, wrapper_tx_msg, signing_data, masp_signing_data) } pub async fn build_eth_bridge_transfer( @@ -387,7 +589,7 @@ impl Sdk { ) -> Result { let args = args::eth_bridge_transfer_tx_args(eth_bridge_transfer_msg, wrapper_tx_msg)?; let (tx, signing_data) = build_bridge_pool_tx(&self.namada, args.clone()).await?; - self.serialize_tx_result(tx, wrapper_tx_msg, signing_data) + self.serialize_tx_result(tx, wrapper_tx_msg, signing_data, None) } pub async fn build_vote_proposal( @@ -400,7 +602,7 @@ impl Sdk { let (tx, signing_data) = build_vote_proposal(&self.namada, &args, epoch) .await .map_err(JsError::from)?; - self.serialize_tx_result(tx, wrapper_tx_msg, signing_data) + self.serialize_tx_result(tx, wrapper_tx_msg, signing_data, None) } pub async fn build_claim_rewards( @@ -412,7 +614,7 @@ impl Sdk { let (tx, signing_data) = build_claim_rewards(&self.namada, &args) .await .map_err(JsError::from)?; - self.serialize_tx_result(tx, wrapper_tx_msg, signing_data) + self.serialize_tx_result(tx, wrapper_tx_msg, signing_data, None) } pub async fn build_bond( @@ -422,7 +624,7 @@ impl Sdk { ) -> Result { let args = args::bond_tx_args(bond_msg, wrapper_tx_msg)?; let (tx, signing_data) = build_bond(&self.namada, &args).await?; - self.serialize_tx_result(tx, wrapper_tx_msg, signing_data) + self.serialize_tx_result(tx, wrapper_tx_msg, signing_data, None) } pub async fn build_unbond( @@ -432,7 +634,7 @@ impl Sdk { ) -> Result { let args = args::unbond_tx_args(unbond_msg, wrapper_tx_msg)?; let (tx, signing_data, _) = build_unbond(&self.namada, &args).await?; - self.serialize_tx_result(tx, wrapper_tx_msg, signing_data) + self.serialize_tx_result(tx, wrapper_tx_msg, signing_data, None) } pub async fn build_withdraw( @@ -442,7 +644,7 @@ impl Sdk { ) -> Result { let args = args::withdraw_tx_args(withdraw_msg, wrapper_tx_msg)?; let (tx, signing_data) = build_withdraw(&self.namada, &args).await?; - self.serialize_tx_result(tx, wrapper_tx_msg, signing_data) + self.serialize_tx_result(tx, wrapper_tx_msg, signing_data, None) } pub async fn build_redelegate( @@ -452,14 +654,14 @@ impl Sdk { ) -> Result { let args = args::redelegate_tx_args(redelegate_msg, wrapper_tx_msg)?; let (tx, signing_data) = build_redelegation(&self.namada, &args).await?; - self.serialize_tx_result(tx, wrapper_tx_msg, signing_data) + self.serialize_tx_result(tx, wrapper_tx_msg, signing_data, None) } pub async fn build_reveal_pk(&self, wrapper_tx_msg: &[u8]) -> Result { let args = args::tx_args_from_slice(wrapper_tx_msg)?; let public_key = args.signing_keys[0].clone(); let (tx, signing_data) = build_reveal_pk(&self.namada, &args.clone(), &public_key).await?; - self.serialize_tx_result(tx, wrapper_tx_msg, signing_data) + self.serialize_tx_result(tx, wrapper_tx_msg, signing_data, None) } // Sign arbitrary data with the provided signing key @@ -531,8 +733,10 @@ impl Sdk { tx: Tx, wrapper_tx_msg: &[u8], signing_data: SigningTxData, + masp_signing_data: Option, ) -> Result { - let tx = tx::Tx::new(tx, wrapper_tx_msg, vec![signing_data])?; + let tx = tx::Tx::new(tx, wrapper_tx_msg, vec![(signing_data, masp_signing_data)])?; + to_js_result(borsh::to_vec(&tx)?) } } diff --git a/packages/shared/lib/src/sdk/signature.rs b/packages/shared/lib/src/sdk/signature.rs index 4479510c4..498de142b 100644 --- a/packages/shared/lib/src/sdk/signature.rs +++ b/packages/shared/lib/src/sdk/signature.rs @@ -20,7 +20,7 @@ pub struct SignatureMsg { /// from Tx /// /// # Arguments -//// +/// /// * `pubkey` - Public key bytes /// * `sec_indices` - Indices indicating hash location /// * `signature` - Signature bytes @@ -44,5 +44,5 @@ pub fn construct_signature_section( signatures, }; - Ok(Section::Authorization(compressed_signature.expand(&tx))) + Ok(Section::Authorization(compressed_signature.expand(tx))) } diff --git a/packages/shared/lib/src/sdk/tx.rs b/packages/shared/lib/src/sdk/tx.rs index dac53d3e0..4bd5ecf67 100644 --- a/packages/shared/lib/src/sdk/tx.rs +++ b/packages/shared/lib/src/sdk/tx.rs @@ -3,6 +3,8 @@ use std::str::FromStr; use gloo_utils::format::JsValueSerdeExt; use namada_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; +use namada_sdk::masp_primitives::transaction::components::sapling::builder::StoredBuildParams; +use namada_sdk::masp_primitives::zip32::ExtendedFullViewingKey; use namada_sdk::signing::SigningTxData; use namada_sdk::tx::data::compute_inner_tx_hash; use namada_sdk::tx::either::Either; @@ -43,15 +45,17 @@ pub struct SigningData { threshold: u8, account_public_keys_map: Option>, fee_payer: String, + shielded_hash: Option>, + masp: Option>, } impl SigningData { // Create serializable struct from Namada type - pub fn from_signing_tx_data(signing_tx_data: SigningTxData) -> Result { - let owner: Option = match signing_tx_data.owner { - Some(addr) => Some(addr.to_string()), - None => None, - }; + pub fn from_signing_tx_data( + signing_tx_data: SigningTxData, + masp_signing_data: Option, + ) -> Result { + let owner: Option = signing_tx_data.owner.map(|addr| addr.to_string()); let public_keys = signing_tx_data .public_keys .into_iter() @@ -65,6 +69,14 @@ impl SigningData { let fee_payer = signing_tx_data.fee_payer.to_string(); let threshold = signing_tx_data.threshold; + let shielded_hash = match signing_tx_data.shielded_hash { + Some(v) => Some(borsh::to_vec(&v)?), + None => None, + }; + let masp_signing_data = match masp_signing_data { + Some(v) => Some(borsh::to_vec(&v)?), + None => None, + }; Ok(SigningData { owner, @@ -72,13 +84,15 @@ impl SigningData { threshold, account_public_keys_map, fee_payer, + shielded_hash, + masp: masp_signing_data, }) } // Create Namada type from this struct pub fn to_signing_tx_data(&self) -> Result { let owner: Option
= match &self.owner { - Some(addr) => Some(Address::from_str(&addr)?), + Some(addr) => Some(Address::from_str(addr)?), None => None, }; @@ -91,7 +105,11 @@ impl SigningData { let fee_payer = PublicKey::from_str(&self.fee_payer)?; let threshold = self.threshold; let account_public_keys_map = match &self.account_public_keys_map { - Some(pk_map) => Some(borsh::from_slice(&pk_map)?), + Some(pk_map) => Some(borsh::from_slice(pk_map)?), + None => None, + }; + let shielded_hash = match &self.shielded_hash { + Some(v) => Some(borsh::from_slice(v)?), None => None, }; @@ -101,8 +119,34 @@ impl SigningData { fee_payer, threshold, account_public_keys_map, + shielded_hash, }) } + + pub fn masp(&self) -> Option> { + self.masp.clone() + } +} + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)] +#[borsh(crate = "namada_sdk::borsh")] +pub struct MaspSigningData { + pub bparams: StoredBuildParams, + pub xfvks: Vec, +} + +impl MaspSigningData { + pub fn new(bparams: StoredBuildParams, xfvks: Vec) -> MaspSigningData { + MaspSigningData { bparams, xfvks } + } + + pub fn xfvks(&self) -> Vec { + self.xfvks.clone() + } + + pub fn bparams(&self) -> StoredBuildParams { + self.bparams.clone() + } } /// Serializable Tx for exported build functions @@ -112,19 +156,19 @@ pub struct Tx { args: WrapperTxMsg, hash: String, bytes: Vec, - signing_data: Vec, + pub signing_data: Vec, } impl Tx { pub fn new( tx: tx::Tx, args: &[u8], - signing_tx_data: Vec, + signing_tx_data: Vec<(SigningTxData, Option)>, ) -> Result { - let args: WrapperTxMsg = borsh::from_slice(&args)?; + let args: WrapperTxMsg = borsh::from_slice(args)?; let mut signing_data: Vec = vec![]; - for sd in signing_tx_data.into_iter() { - let sd = SigningData::from_signing_tx_data(sd)?; + for (sd, msd) in signing_tx_data.into_iter() { + let sd = SigningData::from_signing_tx_data(sd, msd)?; signing_data.push(sd); } let hash = tx.wrapper_hash(); @@ -151,6 +195,10 @@ impl Tx { Ok(signing_tx_data) } + pub fn signing_data(&self) -> Vec { + self.signing_data.clone() + } + pub fn args(&self) -> WrapperTxMsg { self.args.clone() } @@ -165,7 +213,7 @@ pub fn get_inner_tx_hashes(tx_bytes: &[u8]) -> Result, JsError> { let mut inner_tx_hashes: Vec = vec![]; for cmt in cmts { - let inner_tx_hash = compute_inner_tx_hash(hash.as_ref(), Either::Right(&cmt)); + let inner_tx_hash = compute_inner_tx_hash(hash.as_ref(), Either::Right(cmt)); inner_tx_hashes.push(inner_tx_hash.to_string()); } @@ -189,8 +237,8 @@ pub fn wasm_hash_to_tx_type(wasm_hash: &str, wasm_hashes: &Vec) -> Opt if wh.hash() == wasm_hash { let tx_type = type_map.get(&wh.path()); - if tx_type.is_some() { - return Some(*tx_type.unwrap()); + if let Some(tx_type) = tx_type { + return Some(*tx_type); } } } @@ -227,7 +275,7 @@ impl TxDetails { let tx: tx::Tx = borsh::from_slice(&tx_bytes)?; let chain_id = tx.header().chain_id.to_string(); - let tx_details = match tx.header().tx_type { + match tx.header().tx_type { tx::data::TxType::Wrapper(wrapper) => { let fee_amount = wrapper.fee.amount_per_gas_unit.to_string(); let gas_limit = Uint::from(wrapper.gas_limit).to_string(); @@ -240,7 +288,7 @@ impl TxDetails { for cmt in tx.commitments() { let memo = tx - .memo(&cmt) + .memo(cmt) .map(|memo_bytes| String::from_utf8_lossy(&memo_bytes).to_string()); let hash = cmt.get_hash().to_string(); @@ -258,7 +306,7 @@ impl TxDetails { if tx_type.is_some() { let tx_type = tx_type.unwrap(); - let tx_data = tx.data(&cmt).unwrap_or_default(); + let tx_data = tx.data(cmt).unwrap_or_default(); let tx_kind = transaction::TransactionKind::from(tx_type, &tx_data); let data = tx_kind.to_bytes()?; @@ -279,9 +327,7 @@ impl TxDetails { }) } _ => Err(JsError::new("Invalid transaction type!")), - }; - - Ok(tx_details?) + } } } diff --git a/packages/shared/lib/src/sdk/wallet/mod.rs b/packages/shared/lib/src/sdk/wallet/mod.rs index 6184ba6a2..4346c991e 100644 --- a/packages/shared/lib/src/sdk/wallet/mod.rs +++ b/packages/shared/lib/src/sdk/wallet/mod.rs @@ -48,7 +48,7 @@ pub fn add_viewing_key(wallet: &mut Wallet, xvk: String, alias: if wallet .store_mut() - .insert_viewing_key::(alias.clone(), xvk, None, true) + .insert_viewing_key::(alias.clone(), xvk, None, None, true) .is_none() { panic!("Action cancelled, no changes persisted."); diff --git a/packages/shared/lib/src/types/masp.rs b/packages/shared/lib/src/types/masp.rs index 115a295c3..9d279d801 100644 --- a/packages/shared/lib/src/types/masp.rs +++ b/packages/shared/lib/src/types/masp.rs @@ -1,7 +1,10 @@ //! PaymentAddress - Provide wasm_bindgen bindings for shielded addresses //! See @namada/crypto for zip32 HD wallet functionality. -use namada_sdk::borsh::BorshDeserialize; +use js_sys::Uint8Array; +use namada_sdk::borsh::{self, BorshDeserialize}; +use namada_sdk::masp_primitives::zip32::ExtendedKey; use namada_sdk::masp_primitives::{sapling, zip32}; +use namada_sdk::masp_proofs::jubjub; use namada_sdk::{ ExtendedSpendingKey as NamadaExtendedSpendingKey, ExtendedViewingKey as NamadaExtendedViewingKey, PaymentAddress as NamadaPaymentAddress, @@ -41,6 +44,45 @@ impl ExtendedViewingKey { } } +#[wasm_bindgen] +pub struct ProofGenerationKey(pub(crate) sapling::ProofGenerationKey); + +#[wasm_bindgen] +impl ProofGenerationKey { + pub fn encode(&self) -> String { + hex::encode( + borsh::to_vec(&self.0).expect("Serializing ProofGenerationKey should not fail!"), + ) + } + pub fn decode(encoded: String) -> ProofGenerationKey { + let decoded = hex::decode(encoded).expect("Decoding ProofGenerationKey should not fail!"); + + ProofGenerationKey( + sapling::ProofGenerationKey::try_from_slice(decoded.as_slice()) + .expect("Deserializing ProofGenerationKey should not fail!"), + ) + } +} + +/// Wrap ExtendedSpendingKey +#[wasm_bindgen] +pub struct PseudoExtendedKey(pub(crate) zip32::PseudoExtendedKey); + +#[wasm_bindgen] +impl PseudoExtendedKey { + pub fn encode(&self) -> String { + hex::encode(borsh::to_vec(&self.0).expect("Serializing PseudoExtendedKey should not fail!")) + } + pub fn decode(encoded: String) -> PseudoExtendedKey { + let decoded = hex::decode(encoded).expect("Decoding PsuedoExtendedKey should not fail!"); + + PseudoExtendedKey( + zip32::PseudoExtendedKey::try_from_slice(decoded.as_slice()) + .expect("Deserializing ProofGenerationKey should not fail!"), + ) + } +} + /// Wrap ExtendedSpendingKey #[wasm_bindgen] pub struct ExtendedSpendingKey(pub(crate) NamadaExtendedSpendingKey); @@ -50,15 +92,35 @@ pub struct ExtendedSpendingKey(pub(crate) NamadaExtendedSpendingKey); impl ExtendedSpendingKey { /// Instantiate ExtendedSpendingKey from serialized vector #[wasm_bindgen(constructor)] - pub fn new(key: &[u8]) -> Result { - let xsk: zip32::ExtendedSpendingKey = BorshDeserialize::try_from_slice(key) - .map_err(|err| format!("{}: {:?}", MaspError::BorshDeserialize, err))?; + pub fn new(key: Uint8Array) -> Result { + let xsk: zip32::ExtendedSpendingKey = + BorshDeserialize::try_from_slice(key.to_vec().as_slice()) + .map_err(|err| format!("{}: {:?}", MaspError::BorshDeserialize, err))?; let xsk = NamadaExtendedSpendingKey::from(xsk); Ok(ExtendedSpendingKey(xsk)) } + pub fn to_proof_generation_key(&self) -> ProofGenerationKey { + let xsk = zip32::ExtendedSpendingKey::from(self.0); + let pgk = xsk + .to_proof_generation_key() + .expect("Converting to proof generation key should not fail!"); + + ProofGenerationKey(pgk) + } + + pub fn to_pseudo_extended_key(&self) -> PseudoExtendedKey { + let xsk = zip32::ExtendedSpendingKey::from(self.0); + let mut pxk = zip32::PseudoExtendedKey::from(xsk); + pxk.augment_spend_authorizing_key_unchecked(sapling::redjubjub::PrivateKey( + jubjub::Fr::default(), + )); + + PseudoExtendedKey(pxk) + } + /// Return ExtendedSpendingKey as Bech32-encoded String pub fn encode(&self) -> String { self.0.to_string() @@ -112,7 +174,7 @@ mod tests { 112, 25, 36, 51, 226, 228, 45, 242, 201, 220, 212, 220, 58, 92, 127, 47, 214, 59, 174, 182, 74, 90, 65, 229, 187, 76, 65, 246, 34, 237, 107, 208, 178, 243, ]; - let xsk = ExtendedSpendingKey::new(encoded_xsk) + let xsk = ExtendedSpendingKey::new(encoded_xsk.into()) .expect("Instantiating ExtendedSpendingKey struct should not fail!"); let key = xsk.encode(); diff --git a/packages/types/src/account.ts b/packages/types/src/account.ts index 475a53163..205f960d7 100644 --- a/packages/types/src/account.ts +++ b/packages/types/src/account.ts @@ -37,11 +37,12 @@ export type DerivedAccount = { parentId?: string; path: Path; type: AccountType; + pseudoExtendedKey?: string; }; export type Account = Pick< DerivedAccount, - "address" | "alias" | "type" | "publicKey" + "address" | "alias" | "type" | "publicKey" | "owner" | "pseudoExtendedKey" > & { viewingKey?: string; }; diff --git a/packages/types/src/namada.ts b/packages/types/src/namada.ts index 7c2ac2458..1818245b7 100644 --- a/packages/types/src/namada.ts +++ b/packages/types/src/namada.ts @@ -1,5 +1,9 @@ import { Account } from "./account"; -import { SignArbitraryResponse, Signer } from "./signer"; +import { + GenDisposableSignerResponse, + SignArbitraryResponse, + Signer, +} from "./signer"; import { TxProps } from "./tx"; export type SignArbitraryProps = { @@ -36,6 +40,7 @@ export interface Namada { props: SignArbitraryProps ): Promise; verify(props: VerifyArbitraryProps): Promise; + genDisposableKeypair(): Promise; version: () => string; } diff --git a/packages/types/src/signer.ts b/packages/types/src/signer.ts index 19fa063fa..e71abe144 100644 --- a/packages/types/src/signer.ts +++ b/packages/types/src/signer.ts @@ -5,6 +5,11 @@ export type SignArbitraryResponse = { signature: string; }; +export type GenDisposableSignerResponse = { + publicKey: string; + address: string; +}; + export interface Signer { sign: ( tx: TxProps | TxProps[], @@ -16,4 +21,5 @@ export interface Signer { data: string ) => Promise; verify: (publicKey: string, hash: string, signature: string) => Promise; + genDisposableKeypair: () => Promise; } diff --git a/packages/types/src/tx/schema/transfer.ts b/packages/types/src/tx/schema/transfer.ts index 3eaa7cca9..cc756a313 100644 --- a/packages/types/src/tx/schema/transfer.ts +++ b/packages/types/src/tx/schema/transfer.ts @@ -72,7 +72,7 @@ export class ShieldedTransferMsgValue { @field({ type: vec(ShieldedTransferDataMsgValue) }) data!: ShieldedTransferDataMsgValue[]; - @field({ type: option(vec("string")) }) + @field({ type: option("string") }) gasSpendingKey?: string; constructor({ data, gasSpendingKey }: ShieldedTransferProps) { @@ -147,8 +147,8 @@ export class UnshieldingTransferMsgValue { @field({ type: vec(UnshieldingTransferDataMsgValue) }) data!: UnshieldingTransferDataMsgValue[]; - @field({ type: option(vec("string")) }) - gasSpendingKey?: string[]; + @field({ type: option("string") }) + gasSpendingKey?: string; constructor({ source, data, gasSpendingKey }: UnshieldingTransferProps) { Object.assign(this, { diff --git a/packages/types/src/tx/schema/tx.ts b/packages/types/src/tx/schema/tx.ts index 5b282a63b..a2430e234 100644 --- a/packages/types/src/tx/schema/tx.ts +++ b/packages/types/src/tx/schema/tx.ts @@ -23,6 +23,12 @@ export class SigningDataMsgValue { @field({ type: "string" }) feePayer!: string; + @field({ type: option(vec("u8")) }) + shieldedHash?: Uint8Array; + + @field({ type: option(vec("u8")) }) + masp?: Uint8Array; + constructor(data: SigningDataProps) { Object.assign(this, data); }