Skip to content

Commit

Permalink
feat: unshield + shielded transfers + disposable gas payer (#1191)
Browse files Browse the repository at this point in the history
* feat: masp building compiles

* feat: unshield nam

* feat: shielded transfers - dirty

* feat: unshielding with masp sign

* chore: fix clippy warnings

* chore: cleanups

* feat: shield

* chore: cleanup

* chore: cleanup

* fix: rebase

* feat: disposable gas payer

* feat: pass masp indexer

* feat: persist disposable signer in a local storage and clear after
interval

* feat: build

* chore: comment out localnet_enabled flag

* chore: some after merge cleanups

* fix: unshielding and shielded transfers with imported private key

* feat: wire unshielding into UI

* chore: cleanup WorkerTest component

* chore: use revision instead of branch name

* fix: cr comments

* feat: make xsks optional
  • Loading branch information
mateuszjasiuk authored Dec 10, 2024
1 parent ff6e03e commit e8f0b39
Show file tree
Hide file tree
Showing 54 changed files with 1,521 additions and 694 deletions.
2 changes: 1 addition & 1 deletion .lintstagedrc.json
Original file line number Diff line number Diff line change
@@ -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"]
}
4 changes: 2 additions & 2 deletions apps/extension/src/Setup/ImportAccount/SeedPhraseImport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const SeedPhraseImport: React.FC<Props> = ({
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<HTMLInputElement>) => {
Expand Down Expand Up @@ -154,7 +154,7 @@ export const SeedPhraseImport: React.FC<Props> = ({
setInvalidWordIndex(invalidWordIndex);
typeof invalidWordIndex === "number" ?
setMnemonicError(`Word #${invalidWordIndex + 1} is invalid!`)
: setMnemonicError(error);
: setMnemonicError(error);
} else {
setMnemonicError(error);
}
Expand Down
8 changes: 7 additions & 1 deletion apps/extension/src/background/approvals/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
2 changes: 2 additions & 0 deletions apps/extension/src/background/approvals/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export type EncodedSigningData = Pick<
"publicKeys" | "threshold" | "feePayer" | "owner"
> & {
accountPublicKeysMap?: string;
shieldedHash?: string;
masp?: string;
};

export type EncodedTxData = Pick<TxProps, "args" | "hash"> & {
Expand Down
2 changes: 2 additions & 0 deletions apps/extension/src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ const init = new Promise<void>(async (resolve) => {
const broadcaster = new ExtensionBroadcaster(localStorage, requester);
const sdkService = await SdkService.init();

await localStorage.clearOldDisposableSigners();

const vaultService = new VaultService(
vaultStorage,
sessionStore,
Expand Down
14 changes: 14 additions & 0 deletions apps/extension/src/background/keyring/handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
CheckDurabilityMsg,
GenDisposableSignerMsg,
QueryAccountsMsg,
QueryDefaultAccountMsg,
VerifyArbitraryMsg,
Expand Down Expand Up @@ -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");
}
Expand Down Expand Up @@ -237,3 +243,11 @@ const handleQueryAccountDetails: (
return await service.queryAccountDetails(address);
};
};

const handleGenDisposableSignerMsg: (
service: KeyRingService
) => InternalHandler<GenDisposableSignerMsg> = (service) => {
return async (_, _msg) => {
return await service.genDisposableSigner();
};
};
2 changes: 2 additions & 0 deletions apps/extension/src/background/keyring/init.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
CheckDurabilityMsg,
GenDisposableSignerMsg,
QueryAccountsMsg,
QueryDefaultAccountMsg,
VerifyArbitraryMsg,
Expand Down Expand Up @@ -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));
}
111 changes: 104 additions & 7 deletions apps/extension/src/background/keyring/keyring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -33,13 +40,15 @@ 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 = {
address: string;
id: string;
text: string;
owner: string;
pseudoExtendedKey?: string;
};

/**
Expand All @@ -52,7 +61,8 @@ export class KeyRing {
protected readonly vaultService: VaultService,
protected readonly vaultStorage: VaultStorage,
protected readonly sdkService: SdkService,
protected readonly utilityStore: KVStore<UtilityStore>
protected readonly utilityStore: KVStore<UtilityStore>,
protected readonly localStorage: LocalStorage
) {}

public get status(): KeyRingStatus {
Expand Down Expand Up @@ -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,
};
}

Expand Down Expand Up @@ -353,7 +365,7 @@ export class KeyRing {
alias: string,
derivedAccountInfo: DerivedAccountInfo
): Promise<DerivedAccount> {
const { address, id, text, owner } = derivedAccountInfo;
const { address, id, text, owner, pseudoExtendedKey } = derivedAccountInfo;
const account: AccountStore = {
id,
address,
Expand All @@ -362,6 +374,7 @@ export class KeyRing {
path,
type,
owner,
pseudoExtendedKey,
};
const sensitive = await this.vaultService.encryptSensitiveData({
text,
Expand Down Expand Up @@ -526,6 +539,55 @@ export class KeyRing {
return accounts.map((account) => account.public as AccountStore);
}

private async getSpendingKey(address: string): Promise<string> {
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<SensitiveAccountStoreData>(
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
*/
Expand Down Expand Up @@ -635,9 +697,24 @@ export class KeyRing {
chainId: string
): Promise<Uint8Array> {
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(
Expand Down Expand Up @@ -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 };
}
}
10 changes: 9 additions & 1 deletion apps/extension/src/background/keyring/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
AccountType,
Bip44Path,
DerivedAccount,
GenDisposableSignerResponse,
SignArbitraryResponse,
TxProps,
} from "@namada/types";
Expand Down Expand Up @@ -43,7 +44,8 @@ export class KeyRingService {
vaultService,
vaultStorage,
sdkService,
utilityStore
utilityStore,
localStorage
);
}

Expand Down Expand Up @@ -229,4 +231,10 @@ export class KeyRingService {
}
return this._keyRing.queryAccountDetails(address);
}

async genDisposableSigner(): Promise<
GenDisposableSignerResponse | undefined
> {
return this._keyRing.genDisposableSigner();
}
}
1 change: 1 addition & 0 deletions apps/extension/src/background/keyring/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface AccountStore extends StoredRecord {
publicKey?: string;
path: Path;
parentId?: string;
pseudoExtendedKey?: string;
type: AccountType;
}

Expand Down
12 changes: 11 additions & 1 deletion apps/extension/src/provider/InjectedNamada.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Account,
GenDisposableSignerResponse,
Namada as INamada,
Signer as ISigner,
SignArbitraryProps,
Expand All @@ -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<void> {
return await InjectedProxy.requestMethod<string, void>("connect", chainId);
Expand Down Expand Up @@ -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);
}
Expand Down
Loading

1 comment on commit e8f0b39

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.