Skip to content

Commit 200bd4b

Browse files
authored
feat: Keychain/Namadillo: Payment Address gen (#1905)
1 parent e4d0818 commit 200bd4b

File tree

22 files changed

+697
-36
lines changed

22 files changed

+697
-36
lines changed

apps/extension/src/App/Accounts/ViewAccount.tsx

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,27 @@ import { makeBip44Path, makeSaplingPath } from "@namada/sdk/web";
44
import { AccountType, DerivedAccount } from "@namada/types";
55
import { PageHeader } from "App/Common";
66
import routes from "App/routes";
7+
import { GenPaymentAddressMsg } from "background/keyring";
78
import { AccountContext } from "context";
9+
import { useRequester } from "hooks/useRequester";
810
import { useContext, useEffect, useState } from "react";
911
import { useNavigate, useParams } from "react-router-dom";
12+
import { Ports } from "router";
1013
import { isCustomPath } from "utils";
1114

1215
type ViewAccountUrlParams = {
1316
accountId: string;
1417
};
1518

1619
export const ViewAccount = (): JSX.Element => {
20+
const requester = useRequester();
1721
const { accountId = "" } = useParams<ViewAccountUrlParams>();
1822
const { accounts: accountStore } = useContext(AccountContext);
1923
const [parentAccount, setParentAccount] = useState<DerivedAccount>();
2024
const [transparentAddress, setTransparentAddress] = useState("");
2125
const [transparentPath, setTransparentPath] = useState<string>();
22-
const [shieldedAddress, setShieldedAddress] = useState("");
2326
const [shieldedPath, setShieldedPath] = useState<string>();
27+
const [shieldedAccount, setShieldedAccount] = useState<DerivedAccount>();
2428
const [viewingKey, setViewingKey] = useState("");
2529
const navigate = useNavigate();
2630

@@ -40,13 +44,26 @@ export const ViewAccount = (): JSX.Element => {
4044
);
4145
};
4246

47+
const handleIncrementPaymentAddress = async (): Promise<void> => {
48+
if (shieldedAccount) {
49+
const { id } = shieldedAccount;
50+
const updatedAccount = await requester.sendMessage(
51+
Ports.Background,
52+
new GenPaymentAddressMsg(id)
53+
);
54+
if (updatedAccount) {
55+
setShieldedAccount(updatedAccount);
56+
}
57+
}
58+
};
59+
4360
useEffect(() => {
4461
const parentAccount = searchParentAccount(accountId);
4562
if (parentAccount) {
4663
setParentAccount(parentAccount);
4764

4865
if (parentAccount.type === AccountType.ShieldedKeys) {
49-
setShieldedAddress(parentAccount.address);
66+
setShieldedAccount(parentAccount);
5067
setShieldedPath(
5168
isCustomPath(parentAccount.path) ?
5269
makeSaplingPath(
@@ -79,7 +96,7 @@ export const ViewAccount = (): JSX.Element => {
7996

8097
const shieldedAccount = searchShieldedKey(accountId);
8198
if (shieldedAccount) {
82-
setShieldedAddress(shieldedAccount.address);
99+
setShieldedAccount(shieldedAccount);
83100
setShieldedPath(
84101
isCustomPath(shieldedAccount.path) ?
85102
makeSaplingPath(
@@ -109,12 +126,19 @@ export const ViewAccount = (): JSX.Element => {
109126
publicKeyAddress={parentAccount.publicKey ?? ""}
110127
transparentAccountAddress={transparentAddress}
111128
transparentAccountPath={transparentPath}
112-
shieldedAccountAddress={shieldedAddress}
129+
shieldedAccountAddress={shieldedAccount?.address}
113130
shieldedAccountPath={shieldedPath}
114131
trimCharacters={21}
115132
/>
116133
{viewingKey && (
117134
<>
135+
<ActionButton
136+
outlineColor="yellow"
137+
size="sm"
138+
onClick={handleIncrementPaymentAddress}
139+
>
140+
Generate Payment Address
141+
</ActionButton>
118142
{parentAccount.type !== AccountType.Ledger && (
119143
<ActionButton
120144
outlineColor="yellow"

apps/extension/src/Setup/Ledger/LedgerConnect.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export const LedgerConnect: React.FC<Props> = ({
4949
let encodedExtendedViewingKey: string | undefined;
5050
let encodedPaymentAddress: string | undefined;
5151
let encodedPseudoExtendedKey: string | undefined;
52+
let diversifierIndex: number | undefined;
5253

5354
try {
5455
const {
@@ -84,9 +85,8 @@ export const LedgerConnect: React.FC<Props> = ({
8485

8586
const extendedViewingKey = new ExtendedViewingKey(xfvk);
8687
encodedExtendedViewingKey = extendedViewingKey.encode();
87-
encodedPaymentAddress = extendedViewingKey
88-
.default_payment_address()
89-
.encode();
88+
[diversifierIndex, encodedPaymentAddress] =
89+
extendedViewingKey.default_payment_address();
9090

9191
const proofGenerationKey = ProofGenerationKey.from_bytes(ak, nsk);
9292
const pseudoExtendedKey = PseudoExtendedKey.from(
@@ -105,6 +105,7 @@ export const LedgerConnect: React.FC<Props> = ({
105105
extendedViewingKey: encodedExtendedViewingKey,
106106
paymentAddress: encodedPaymentAddress,
107107
pseudoExtendedKey: encodedPseudoExtendedKey,
108+
diversifierIndex,
108109
},
109110
});
110111
} catch (e) {

apps/extension/src/Setup/Ledger/LedgerImport.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type LedgerImportLocationState = {
1313
extendedViewingKey: string;
1414
pseudoExtendedKey: string;
1515
paymentAddress: string;
16+
diversifierIndex: number;
1617
};
1718

1819
type LedgerProps = {
@@ -66,6 +67,7 @@ export const LedgerImport = ({
6667
extendedViewingKey,
6768
paymentAddress,
6869
pseudoExtendedKey,
70+
diversifierIndex,
6971
} = locationState;
7072
const account = await accountManager.saveLedgerAccount({
7173
alias,
@@ -76,6 +78,7 @@ export const LedgerImport = ({
7678
paymentAddress,
7779
extendedViewingKey,
7880
pseudoExtendedKey,
81+
diversifierIndex,
7982
});
8083

8184
navigate(routes.ledgerComplete(), {

apps/extension/src/Setup/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ export type LedgerAccountDetails = {
2424
extendedViewingKey: string;
2525
pseudoExtendedKey: string;
2626
paymentAddress: string;
27+
diversifierIndex: number;
2728
};

apps/extension/src/background/keyring/handler.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
DeleteAccountMsg,
1212
DeriveShieldedAccountMsg,
1313
GenerateMnemonicMsg,
14+
GenPaymentAddressMsg,
1415
GetActiveAccountMsg,
1516
QueryAccountDetailsMsg,
1617
QueryParentAccountsMsg,
@@ -104,7 +105,11 @@ export const getHandler: (service: KeyRingService) => Handler = (service) => {
104105
env,
105106
msg as RevealSpendingKeyMsg
106107
);
107-
108+
case GenPaymentAddressMsg:
109+
return handleGenPaymentAddressMsg(service)(
110+
env,
111+
msg as GenPaymentAddressMsg
112+
);
108113
default:
109114
throw new Error("Unknown msg type");
110115
}
@@ -124,6 +129,7 @@ const handleAddLedgerAccountMsg: (
124129
extendedViewingKey,
125130
pseudoExtendedKey,
126131
paymentAddress,
132+
diversifierIndex,
127133
} = msg;
128134
return await service.saveLedger(
129135
alias,
@@ -133,7 +139,8 @@ const handleAddLedgerAccountMsg: (
133139
zip32Path,
134140
extendedViewingKey,
135141
pseudoExtendedKey,
136-
paymentAddress
142+
paymentAddress,
143+
diversifierIndex
137144
);
138145
};
139146
};
@@ -293,3 +300,11 @@ const handleRevealSpendingKeyMsg: (
293300
return await service.revealSpendingKey(msg.accountId);
294301
};
295302
};
303+
304+
const handleGenPaymentAddressMsg: (
305+
service: KeyRingService
306+
) => InternalHandler<GenPaymentAddressMsg> = (service) => {
307+
return async (_, { accountId }) => {
308+
return await service.genPaymentAddress(accountId);
309+
};
310+
};

apps/extension/src/background/keyring/init.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
DeleteAccountMsg,
1414
DeriveShieldedAccountMsg,
1515
GenerateMnemonicMsg,
16+
GenPaymentAddressMsg,
1617
GetActiveAccountMsg,
1718
QueryAccountDetailsMsg,
1819
QueryParentAccountsMsg,
@@ -44,6 +45,7 @@ export function init(router: Router, service: KeyRingService): void {
4445
router.registerMessage(RenameAccountMsg);
4546
router.registerMessage(VerifyArbitraryMsg);
4647
router.registerMessage(GenDisposableSignerMsg);
48+
router.registerMessage(GenPaymentAddressMsg);
4749

4850
router.addHandler(ROUTE, getHandler(service));
4951
}

apps/extension/src/background/keyring/keyring.ts

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type DerivedAccountInfo = {
5757
text: string;
5858
owner: string;
5959
pseudoExtendedKey?: string;
60+
diversifierIndex?: number;
6061
};
6162

6263
/**
@@ -110,7 +111,8 @@ export class KeyRing {
110111
zip32Path?: Zip32Path,
111112
pseudoExtendedKey?: string,
112113
extendedViewingKey?: string,
113-
paymentAddress?: string
114+
paymentAddress?: string,
115+
diversifierIndex?: number
114116
): Promise<AccountStore | false> {
115117
const id = generateId(UUID_NAMESPACE, alias, address);
116118
const accountStore: AccountStore = {
@@ -152,6 +154,7 @@ export class KeyRing {
152154
parentId: id,
153155
type: AccountType.ShieldedKeys,
154156
source: "imported",
157+
diversifierIndex,
155158
timestamp: 0,
156159
};
157160

@@ -296,6 +299,7 @@ export class KeyRing {
296299
alias,
297300
address: shieldedKeys.address,
298301
owner: shieldedKeys.viewingKey,
302+
diversifierIndex: shieldedKeys.diversifierIndex,
299303
path,
300304
pseudoExtendedKey: shieldedKeys.pseudoExtendedKey,
301305
type: accountType,
@@ -432,6 +436,10 @@ export class KeyRing {
432436
pseudoExtendedKey,
433437
source,
434438
timestamp,
439+
diversifierIndex:
440+
type === AccountType.ShieldedKeys ?
441+
derivedAccountInfo.diversifierIndex
442+
: undefined,
435443
};
436444
const sensitive = await this.vaultService.encryptSensitiveData({
437445
text,
@@ -482,15 +490,21 @@ export class KeyRing {
482490
throw new Error(`Invalid account type! ${parentType}`);
483491
}
484492

485-
const { address, viewingKey, spendingKey, pseudoExtendedKey } =
486-
shieldedKeys;
493+
const {
494+
address,
495+
diversifierIndex,
496+
viewingKey,
497+
spendingKey,
498+
pseudoExtendedKey,
499+
} = shieldedKeys;
487500

488501
const info = {
489502
address,
490503
id,
491504
owner: viewingKey,
492505
text: JSON.stringify({ spendingKey }),
493506
pseudoExtendedKey,
507+
diversifierIndex,
494508
};
495509

496510
// Check whether keys already exist for this account
@@ -516,15 +530,18 @@ export class KeyRing {
516530

517531
public async queryAllAccounts(): Promise<DerivedAccount[]> {
518532
const accounts = await this.vaultStorage.findAll(KeyStore);
519-
return accounts.map((entry) => entry.public as AccountStore);
533+
return accounts.map((entry) => entry.public);
520534
}
521535

522536
/**
523537
* Query single account by ID
524538
*/
525539
public async queryAccountById(accountId: string): Promise<DerivedAccount> {
526-
return (await this.vaultStorage.findOneOrFail(KeyStore, "id", accountId))
527-
.public;
540+
const account = (
541+
await this.vaultStorage.findOneOrFail(KeyStore, "id", accountId)
542+
).public;
543+
544+
return account;
528545
}
529546

530547
/**
@@ -852,4 +869,60 @@ export class KeyRing {
852869

853870
return { publicKey, address };
854871
}
872+
873+
// Query and validate that account is a shielded account
874+
async queryShieldedAccountById(accountId: string): Promise<DerivedAccount> {
875+
const account = await this.queryAccountById(accountId);
876+
if (!account) {
877+
throw new Error(`Account with ID ${accountId} not found!`);
878+
}
879+
880+
if (account.type !== AccountType.ShieldedKeys) {
881+
throw new Error(
882+
`Account with ID ${accountId} is not a shielded account!`
883+
);
884+
}
885+
886+
if (!account.owner) {
887+
throw new Error(
888+
`Account with ID ${accountId} does not have a viewing key!`
889+
);
890+
}
891+
892+
return account;
893+
}
894+
895+
async genPaymentAddress(
896+
accountId: string
897+
): Promise<DerivedAccount | undefined> {
898+
try {
899+
const account = await this.queryShieldedAccountById(accountId);
900+
let currentIndex = account.diversifierIndex;
901+
const { keys } = this.sdkService.getSdk();
902+
903+
if (!currentIndex) {
904+
// Pre-existing accounts may not have a diversifier index, therefore,
905+
// we query the default, and increment the first valid diversifier index
906+
const genPaymentAddress = keys.genPaymentAddress(account.owner!);
907+
currentIndex = genPaymentAddress.diversifierIndex;
908+
}
909+
910+
const { address, diversifierIndex } = keys.genPaymentAddress(
911+
account.owner!,
912+
currentIndex + 1
913+
);
914+
915+
await this.vaultStorage.update(KeyStore, "id", accountId, {
916+
address,
917+
diversifierIndex,
918+
});
919+
920+
return {
921+
...account,
922+
address,
923+
};
924+
} catch (e) {
925+
throw new Error(`${e}`);
926+
}
927+
}
855928
}

0 commit comments

Comments
 (0)