Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP - Extension: feat/746 - Ledger HW Shielded Keys #1262

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 28 additions & 10 deletions apps/extension/src/Setup/Ledger/LedgerConnect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ type Props = {
setPath: (path: Bip44Path) => void;
};

enum LedgerReviewPrompt {
AddressAndPublicKey = "address and public key",
ViewingKey = "viewing key",
ProofGenerationKeys = "proof generation keys",
}

export const LedgerConnect: React.FC<Props> = ({ path, setPath }) => {
const navigate = useNavigate();
const [error, setError] = useState<string>();
const [isLedgerConnecting, setIsLedgerConnecting] = useState(false);
const [reviewPrompt, setReviewPrompt] = useState<LedgerReviewPrompt>();
const [ledger, setLedger] = useState<LedgerApp>();

const queryLedger = async (ledger: LedgerApp): Promise<void> => {
Expand All @@ -32,19 +38,31 @@ export const LedgerConnect: React.FC<Props> = ({ path, setPath }) => {
throw new Error(errorMessage);
}

setIsLedgerConnecting(true);
const { address, publicKey } = await ledger.showAddressAndPublicKey(
makeBip44Path(chains.namada.bip44.coinType, path)
);
setIsLedgerConnecting(false);
const bip44Path = makeBip44Path(chains.namada.bip44.coinType, path);

setReviewPrompt(LedgerReviewPrompt.AddressAndPublicKey);
const { address, publicKey } =
await ledger.showAddressAndPublicKey(bip44Path);

setReviewPrompt(LedgerReviewPrompt.ViewingKey);
const viewingKey = await ledger.getViewingKey(bip44Path, true);
console.log({ viewingKey });

setReviewPrompt(LedgerReviewPrompt.ProofGenerationKeys);
const proofGenerationKeys =
await ledger.getProofGenerationKeys(bip44Path);

console.log({ proofGenerationKeys });
setReviewPrompt(undefined);

navigate(routes.ledgerImport(), {
state: {
address,
publicKey,
},
});
} catch (e) {
setIsLedgerConnecting(false);
setReviewPrompt(undefined);
handleError(e);
} finally {
await ledger.closeTransport();
Expand Down Expand Up @@ -94,8 +112,8 @@ export const LedgerConnect: React.FC<Props> = ({ path, setPath }) => {
</Alert>
)}

{isLedgerConnecting && (
<Alert type="warning">Review on your Ledger</Alert>
{reviewPrompt && (
<Alert type="warning">Review {reviewPrompt} on your Ledger</Alert>
)}

<LedgerStep
Expand All @@ -116,7 +134,7 @@ export const LedgerConnect: React.FC<Props> = ({ path, setPath }) => {
active={!!ledger}
complete={false}
onClick={() => connectNamadaApp()}
buttonDisabled={!ledger || isLedgerConnecting}
buttonDisabled={!ledger || Boolean(reviewPrompt)}
image={
<Image styleOverrides={{ width: "100%" }} imageName="LogoMinimal" />
}
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export {
} from "./ledger";
export type {
LedgerAddressAndPublicKey,
LedgerShieldedKeys,
LedgerStatus,
LedgerViewingKey,
} from "./ledger";

// Export types
Expand Down
65 changes: 40 additions & 25 deletions packages/sdk/src/ledger.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { toHex } from "@cosmjs/encoding";
import Transport from "@ledgerhq/hw-transport";
import TransportHID from "@ledgerhq/hw-transport-webhid";
import TransportUSB from "@ledgerhq/hw-transport-webusb";
Expand All @@ -17,16 +18,15 @@ import { makeBip44Path } from "./utils";
const { coinType } = chains.namada.bip44;

export type LedgerAddressAndPublicKey = { address: string; publicKey: string };
export type LedgerShieldedKeys = {
viewingKey: {
viewKey?: string;
ivk?: string;
ovk?: string;
};
proofGenerationKey: {
ak?: string;
nsk?: string;
};
// TODO: This should be xfvk, awaiting an updated version!
export type LedgerViewingKey = {
viewKey?: string;
ivk?: string;
ovk?: string;
};
export type LedgerProofGenerationKey = {
ak?: string;
nsk?: string;
};

export type LedgerStatus = {
Expand Down Expand Up @@ -147,41 +147,56 @@ export class Ledger {
}

/**
* Prompt user to get viewing and proof gen key associated with optional path, otherwise, use default path.
* Prompt user to get viewing key associated with optional path, otherwise, use default path.
* Throw exception if app is not initialized.
* @async
* @param [path] Bip44 path for deriving key
* @param [promptUser] boolean to determine whether to display on Ledger device and require approval
* @returns ShieldedKeys
* @returns LedgerViewingKey
*/
public async getShieldedKeys(
public async getViewingKey(
path: string = DEFAULT_LEDGER_BIP44_PATH,
promptUser = true
): Promise<LedgerShieldedKeys> {
): Promise<LedgerViewingKey> {
try {
const { viewKey, ivk, ovk }: ResponseViewKey =
await this.namadaApp.retrieveKeys(path, NamadaKeys.ViewKey, promptUser);

return {
viewKey: viewKey ? toHex(new Uint8Array(viewKey)) : undefined,
ivk: ivk ? toHex(new Uint8Array(ivk)) : undefined,
ovk: ovk ? toHex(new Uint8Array(ovk)) : undefined,
};
} catch (e) {
throw new Error(`Could not retrieve Viewing Key: ${e}`);
}
}

/**
* Prompt user to get proof generation keys associated with optional path, otherwise, use default path.
* Throw exception if app is not initialized.
* @async
* @param [path] Bip44 path for deriving key
* @returns LedgerProofGenerationKey
*/
public async getProofGenerationKeys(
path: string = DEFAULT_LEDGER_BIP44_PATH
): Promise<LedgerProofGenerationKey> {
try {
const { ak, nsk }: ResponseProofGenKey =
await this.namadaApp.retrieveKeys(
path,
NamadaKeys.ProofGenerationKey,
promptUser
// NOTE: Setting this to false will result in undefined values
true
);

return {
viewingKey: {
viewKey: viewKey?.toString(),
ivk: ivk?.toString(),
ovk: ovk?.toString(),
},
proofGenerationKey: {
ak: ak?.toString(),
nsk: nsk?.toString(),
},
ak: ak ? toHex(new Uint8Array(ak)) : undefined,
nsk: nsk ? toHex(new Uint8Array(nsk)) : undefined,
};
} catch (e) {
throw new Error(`Could not retrieve Viewing Key`);
throw new Error(`Could not retrive Proof Generation Keys: ${e}`);
}
}

Expand Down
17 changes: 10 additions & 7 deletions packages/shared/lib/src/types/masp.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
//! PaymentAddress - Provide wasm_bindgen bindings for shielded addresses
//! See @namada/crypto for zip32 HD wallet functionality.
use namada_sdk::borsh::BorshDeserialize;
use namada_sdk::{ExtendedViewingKey as NamadaExtendedViewingKey, ExtendedSpendingKey as NamadaExtendedSpendingKey, PaymentAddress as NamadaPaymentAddress};
use namada_sdk::masp_primitives::{sapling, zip32};
use namada_sdk::{
ExtendedSpendingKey as NamadaExtendedSpendingKey,
ExtendedViewingKey as NamadaExtendedViewingKey, PaymentAddress as NamadaPaymentAddress,
};
use thiserror::Error;
use wasm_bindgen::prelude::*;

Expand All @@ -23,13 +26,13 @@ pub struct ExtendedViewingKey(pub(crate) NamadaExtendedViewingKey);
impl ExtendedViewingKey {
/// Instantiate ExtendedViewingKey from serialized vector
#[wasm_bindgen(constructor)]
pub fn new(key: &[u8]) -> Result<ExtendedViewingKey, String> {
let xfvk: zip32::ExtendedFullViewingKey = BorshDeserialize::try_from_slice(key)
pub fn new(xfvk_bytes: &[u8]) -> Result<ExtendedViewingKey, String> {
let xfvk: zip32::ExtendedFullViewingKey = BorshDeserialize::try_from_slice(xfvk_bytes)
.map_err(|err| format!("{}: {:?}", MaspError::BorshDeserialize, err))?;

let vk = NamadaExtendedViewingKey::from(xfvk);
let xvk = NamadaExtendedViewingKey::from(xfvk);

Ok(ExtendedViewingKey(vk))
Ok(ExtendedViewingKey(xvk))
}

/// Return ExtendedViewingKey as Bech32-encoded String
Expand All @@ -47,8 +50,8 @@ pub struct ExtendedSpendingKey(pub(crate) NamadaExtendedSpendingKey);
impl ExtendedSpendingKey {
/// Instantiate ExtendedSpendingKey from serialized vector
#[wasm_bindgen(constructor)]
pub fn new(key: &[u8]) -> Result<ExtendedSpendingKey, String> {
let xsk: zip32::ExtendedSpendingKey = BorshDeserialize::try_from_slice(key)
pub fn new(xsk_bytes: &[u8]) -> Result<ExtendedSpendingKey, String> {
let xsk: zip32::ExtendedSpendingKey = BorshDeserialize::try_from_slice(xsk_bytes)
.map_err(|err| format!("{}: {:?}", MaspError::BorshDeserialize, err))?;

let xsk = NamadaExtendedSpendingKey::from(xsk);
Expand Down
Loading