Skip to content

Commit

Permalink
feat: kind of working masp ledger
Browse files Browse the repository at this point in the history
  • Loading branch information
mateuszjasiuk committed Dec 31, 2024
1 parent 753529f commit 0bc79fe
Show file tree
Hide file tree
Showing 20 changed files with 217 additions and 66 deletions.
2 changes: 1 addition & 1 deletion apps/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"@ledgerhq/hw-transport": "^6.31.4",
"@ledgerhq/hw-transport-webhid": "^6.29.4",
"@ledgerhq/hw-transport-webusb": "^6.29.4",
"@zondax/ledger-namada": "^1.0.0",
"@zondax/ledger-namada": "^2.0.0",
"bignumber.js": "^9.1.1",
"buffer": "^6.0.3",
"dompurify": "^3.0.2",
Expand Down
17 changes: 6 additions & 11 deletions apps/extension/src/App/Accounts/ParentAccounts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import {
KeyListItem,
Stack,
} from "@namada/components";
import { AccountType, DerivedAccount } from "@namada/types";
import { DerivedAccount } from "@namada/types";
import { ParentAccountsFooter } from "App/Accounts/ParentAccountsFooter";
import { PageHeader } from "App/Common";
import routes from "App/routes";
import { ParentAccount } from "background/keyring";
import { AccountContext } from "context";
import invariant from "invariant";
import { openSetupTab } from "utils";

/**
Expand All @@ -26,20 +27,14 @@ export const ParentAccounts = (): JSX.Element => {
accounts: allAccounts,
changeActiveAccountId,
} = useContext(AccountContext);

// We check which accounts need to be re-imported
const accounts = allAccounts
.filter(
(account) => account.parentId || account.type === AccountType.Ledger
)
.filter((account) => account.parentId)
.map((account) => {
const outdated =
account.type !== AccountType.Ledger &&
typeof account.pseudoExtendedKey === "undefined";
const outdated = typeof account.pseudoExtendedKey === "undefined";

// The only account without a parent is the ledger account
const parent =
parentAccounts.find((pa) => pa.id === account.parentId) || account;
const parent = parentAccounts.find((pa) => pa.id === account.parentId);
invariant(parent, `Parent account not found for account ${account.id}`);

return { ...parent, outdated };
});
Expand Down
24 changes: 24 additions & 0 deletions apps/extension/src/Setup/Ledger/LedgerConnect.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { chains } from "@namada/chains";
import { ActionButton, Alert, Image, Stack } from "@namada/components";
import { Ledger as LedgerApp, makeBip44Path } from "@namada/sdk/web";
import sdkInit from "@namada/sdk/web-init";
import {
ExtendedViewingKey,
ProofGenerationKey,
PseudoExtendedKey,
} from "@namada/shared";
import { Bip44Path } from "@namada/types";
import { LedgerError } from "@zondax/ledger-namada";
import { LedgerStep } from "Setup/Common";
Expand Down Expand Up @@ -33,14 +39,32 @@ export const LedgerConnect: React.FC<Props> = ({ path, setPath }) => {
}

setIsLedgerConnecting(true);

await sdkInit();
const { xfvk, pgk } = await ledger.getShieldedKeys();
const { address, publicKey } = await ledger.showAddressAndPublicKey(
makeBip44Path(chains.namada.bip44.coinType, path)
);
const extendedViewingKey = new ExtendedViewingKey(xfvk);
const encodedExtendedViewingKey = extendedViewingKey.encode();
const encodedPaymentAddress = extendedViewingKey
.default_payment_address()
.encode();
const proofGenerationKey = ProofGenerationKey.from_bytes(pgk.ak, pgk.nsk);
const pseudoExtendedKey = PseudoExtendedKey.from(
extendedViewingKey,
proofGenerationKey
);
const encodedPseudoExtendedKey = pseudoExtendedKey.encode();

setIsLedgerConnecting(false);
navigate(routes.ledgerImport(), {
state: {
address,
publicKey,
extendedViewingKey: encodedExtendedViewingKey,
pseudoExtendedKey: encodedPseudoExtendedKey,
paymentAddress: encodedPaymentAddress,
},
});
} catch (e) {
Expand Down
14 changes: 13 additions & 1 deletion apps/extension/src/Setup/Ledger/LedgerImport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { useLocation, useNavigate } from "react-router-dom";
type LedgerImportLocationState = {
address: string;
publicKey: string;
extendedViewingKey: string;
pseudoExtendedKey: string;
paymentAddress: string;
};

type LedgerProps = {
Expand Down Expand Up @@ -55,12 +58,21 @@ export const LedgerImport = ({
await accountManager.savePassword(password);
}

const { address, publicKey } = locationState;
const {
address,
publicKey,
extendedViewingKey,
pseudoExtendedKey,
paymentAddress,
} = locationState;
const account = await accountManager.saveLedgerAccount({
alias,
address,
publicKey,
path,
extendedViewingKey,
pseudoExtendedKey,
paymentAddress,
});

navigate(routes.ledgerComplete(), {
Expand Down
4 changes: 2 additions & 2 deletions apps/extension/src/Setup/Setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ export const Setup: React.FC = () => {

const prettyAccountSecret =
accountSecret.t === "Mnemonic" ? "mnemonic"
: accountSecret.t === "PrivateKey" ? "private key"
: assertNever(accountSecret);
: accountSecret.t === "PrivateKey" ? "private key"
: assertNever(accountSecret);
setCompletionStatusInfo(`Encrypting and storing ${prettyAccountSecret}.`);

// Create parent account
Expand Down
22 changes: 19 additions & 3 deletions apps/extension/src/Setup/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { DeriveAccountDetails, LedgerAccountDetails } from "./types";

// Wrap account management calls with extension requester instance
export class AccountManager {
constructor(private readonly requester: ExtensionRequester) {}
constructor(private readonly requester: ExtensionRequester) { }

/**
* Set password for the extension
Expand Down Expand Up @@ -88,10 +88,26 @@ export class AccountManager {
async saveLedgerAccount(
details: LedgerAccountDetails
): Promise<AccountStore> {
const { alias, address, publicKey, path } = details;
const {
alias,
address,
publicKey,
path,
extendedViewingKey,
pseudoExtendedKey,
paymentAddress,
} = details;
return (await this.requester.sendMessage(
Ports.Background,
new AddLedgerAccountMsg(alias, address, publicKey, path)
new AddLedgerAccountMsg(
alias,
address,
publicKey,
path,
extendedViewingKey,
pseudoExtendedKey,
paymentAddress
)
)) as AccountStore;
}
}
3 changes: 3 additions & 0 deletions apps/extension/src/Setup/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ export type LedgerAccountDetails = {
path: Bip44Path;
address: string;
publicKey: string;
extendedViewingKey: string;
pseudoExtendedKey: string;
paymentAddress: string;
};
2 changes: 2 additions & 0 deletions apps/extension/src/background/approvals/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ export class ApprovalsService {
(await this.localStorage.getDisposableSigner(signer))?.realAddress ||
signer;

console.log("realAddress", realAddress);
// We use the real address to query the account details
const details = await this.keyRingService.queryAccountDetails(realAddress);
console.log("details", details);
if (!details) {
throw new Error(ApprovalErrors.AccountNotFound(signer));
}
Expand Down
22 changes: 19 additions & 3 deletions apps/extension/src/background/keyring/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,24 @@ const handleAddLedgerAccountMsg: (
service: KeyRingService
) => InternalHandler<AddLedgerAccountMsg> = (service) => {
return async (_, msg) => {
const { alias, address, publicKey, bip44Path } = msg;
return await service.saveLedger(alias, address, publicKey, bip44Path);
const {
alias,
address,
publicKey,
bip44Path,
extendedViewingKey,
pseudoExtendedKey,
paymentAddress,
} = msg;
return await service.saveLedger(
alias,
address,
publicKey,
bip44Path,
extendedViewingKey,
pseudoExtendedKey,
paymentAddress
);
};
};

Expand Down Expand Up @@ -179,7 +195,7 @@ const handleQueryAccountsMsg: (
const output =
query && query.accountId ?
await service.queryAccountsByParentId(query.accountId)
: await service.queryAccounts();
: await service.queryAccounts();

return output;
};
Expand Down
39 changes: 34 additions & 5 deletions apps/extension/src/background/keyring/keyring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class KeyRing {
protected readonly sdkService: SdkService,
protected readonly utilityStore: KVStore<UtilityStore>,
protected readonly localStorage: LocalStorage
) {}
) { }

public get status(): KeyRingStatus {
return this._status;
Expand Down Expand Up @@ -101,7 +101,10 @@ export class KeyRing {
alias: string,
address: string,
publicKey: string,
bip44Path: Bip44Path
bip44Path: Bip44Path,
pseudoExtendedKey: string,
extendedViewingKey: string,
paymentAddress: string
): Promise<AccountStore | false> {
const id = generateId(UUID_NAMESPACE, alias, address);
const accountStore: AccountStore = {
Expand All @@ -125,6 +128,30 @@ export class KeyRing {
sensitive,
});

const shieldedId = generateId(UUID_NAMESPACE, alias, address);
const shieldedAccountStore: AccountStore = {
id: shieldedId,
alias,
address: paymentAddress,
publicKey,
owner: extendedViewingKey,
path: bip44Path,
pseudoExtendedKey,
parentId: id,
type: AccountType.ShieldedKeys,
source: "imported",
timestamp: 0,
};

const shieldedSensitive = await this.vaultService.encryptSensitiveData({
text: "",
passphrase: "",
});
await this.vaultStorage.add(KeyStore, {
public: shieldedAccountStore,
sensitive: shieldedSensitive,
});

await this.setActiveAccount(id, AccountType.Ledger);
return accountStore;
}
Expand Down Expand Up @@ -436,7 +463,7 @@ export class KeyRing {
const deriveFn = (
type === AccountType.PrivateKey ?
this.deriveTransparentAccount
: this.deriveShieldedAccount).bind(this);
: this.deriveShieldedAccount).bind(this);

const { secret } = await this.getParentSecret(parentId);
const info = deriveFn(secret, derivationPath, parentAccount);
Expand Down Expand Up @@ -719,13 +746,13 @@ export class KeyRing {
const key =
disposableKey ?
disposableKey.privateKey
: await this.getSigningKey(signer);
: 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();

Expand All @@ -748,6 +775,7 @@ export class KeyRing {
async queryAccountDetails(
address: string
): Promise<DerivedAccount | undefined> {
console.log("queryAccountDetails address", address);
const account = await this.vaultStorage.findOneOrFail(
KeyStore,
"address",
Expand All @@ -769,6 +797,7 @@ export class KeyRing {
if (!defaultAccount) {
throw new Error("No default account found");
}
console.log("defaultAccount", defaultAccount);

await this.localStorage.addDisposableSigner(
address,
Expand Down
16 changes: 15 additions & 1 deletion apps/extension/src/background/keyring/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,9 @@ export class AddLedgerAccountMsg extends Message<AccountStore | false> {
public readonly address: string,
public readonly publicKey: string,
public readonly bip44Path: Bip44Path,
public readonly parentId?: string
public readonly extendedViewingKey: string,
public readonly pseudoExtendedKey: string,
public readonly paymentAddress: string
) {
super();
}
Expand All @@ -207,6 +209,18 @@ export class AddLedgerAccountMsg extends Message<AccountStore | false> {
if (!this.bip44Path) {
throw new Error("BIP44 Path was not provided!");
}

if (!this.pseudoExtendedKey) {
throw new Error("Pseudo extended key was not provided!");
}

if (!this.extendedViewingKey) {
throw new Error("Extended viewing key was not provided!");
}

if (!this.paymentAddress) {
throw new Error("Payment address was not provided!");
}
}

route(): string {
Expand Down
11 changes: 9 additions & 2 deletions apps/extension/src/background/keyring/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ export class KeyRingService {
alias: string,
address: string,
publicKey: string,
bip44Path: Bip44Path
bip44Path: Bip44Path,
extendedViewingKey: string,
pseudoExtendedKey: string,
paymentAddress: string
): Promise<AccountStore | false> {
const account = await this._keyRing.queryAccountByAddress(address);
if (account) {
Expand All @@ -94,7 +97,10 @@ export class KeyRingService {
alias,
address,
publicKey,
bip44Path
bip44Path,
pseudoExtendedKey,
extendedViewingKey,
paymentAddress
);

await this.broadcaster.updateAccounts();
Expand Down Expand Up @@ -230,6 +236,7 @@ export class KeyRingService {
async queryAccountDetails(
address: string
): Promise<DerivedAccount | undefined> {
console.log("queryAccountDetails");
if (await this.vaultService.isLocked()) {
throw new Error(ApprovalErrors.KeychainLocked());
}
Expand Down
2 changes: 1 addition & 1 deletion apps/namadillo/public/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
#indexer_url = ""
#rpc_url = ""
#masp_indexer_url = ""
#localnet_enabled = false
localnet_enabled = true
Loading

0 comments on commit 0bc79fe

Please sign in to comment.