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] Keystone USB integration #119

Draft
wants to merge 43 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
e512988
add keystone usb button
ww3512687 Nov 26, 2024
baf6cc6
add some log
ww3512687 Nov 28, 2024
370240c
add the option to select device type
ww3512687 Nov 28, 2024
4bdb49e
update
ww3512687 Dec 10, 2024
5fa47fd
feat: add Keystone wallet connector
Charon-Fan Dec 26, 2024
3794ab2
feat: enhance Keystone connection flow with device selection and stat…
Charon-Fan Dec 26, 2024
e263ed4
feat: open up avalanche methods CP-9587 (#100)
gergelylovas Nov 26, 2024
ecfa2a2
feat: add response check for Paraswap API (#99)
gergelylovas Nov 26, 2024
b9aca43
feat: CP-9474 import private key activate account, design fixes (#89)
vvava Dec 2, 2024
0d9bc43
docs: Documentation Typos and Content Improvements (#102)
cypherpepe Dec 4, 2024
18d0b71
feat: migrate to Unified Bridge, introduce the ICTT bridge (#82)
meeh0w Dec 4, 2024
3175d9f
fix: max button using rounded up value (#103)
meeh0w Dec 4, 2024
0bd15de
feat: CP-9472 add duplicated account dialog (#90)
vvava Dec 5, 2024
a7eb8ac
feaT: CP-9487 CP-9473 ledger flow (#93)
vvava Dec 5, 2024
9eafdf8
feat: new account switcher (#94)
meeh0w Dec 6, 2024
99b8301
fix: provide default value to convert (#104)
meeh0w Dec 6, 2024
073c43a
fix: only show P-Chain Details link for active wallet (#105)
meeh0w Dec 9, 2024
ece94e3
fix: contact dropdown size (#107)
meeh0w Dec 9, 2024
94e8895
feat: bring back address copy button (#110)
meeh0w Dec 13, 2024
102a7fc
chore: eslint version update and minor refactor (#106)
csabbee Dec 16, 2024
1a07de0
chore: add data-testids to account mgmt UI (#113)
ryanewood Dec 19, 2024
7ed361e
fix: swap issues (#114)
meeh0w Dec 19, 2024
14b3ff1
feat: (dApp Handler) CP-9471 expose delete accounts endpoint (#115)
frichards Dec 20, 2024
9c43ecc
fix: prompting invalid ledger app (#116)
meeh0w Dec 20, 2024
a018664
add keystone usb button
ww3512687 Nov 26, 2024
ae6fb73
add some log
ww3512687 Nov 28, 2024
3b48cd4
update
ww3512687 Dec 10, 2024
5587358
feat: add Keystone wallet connector
Charon-Fan Dec 26, 2024
064f536
chore: update dependencies in yarn.lock
Charon-Fan Dec 26, 2024
b91579c
chore: remove unnecessary console logs and update navigation path for…
Charon-Fan Dec 26, 2024
1068d0e
feat: integrate KeystoneUsbContextProvider into KeystoneContextProvider
Charon-Fan Dec 26, 2024
3251261
fix: update Keystone USB route and event name for consistency
Charon-Fan Dec 26, 2024
edb8dcf
refactor: simplify ExistingWalletOptions component by removing redund…
Charon-Fan Dec 26, 2024
47b20d9
refactor: remove KeystoneTrouble route and clean up KeystoneConnector…
Charon-Fan Dec 26, 2024
bb0c8da
feat: add support for Keystone3Pro wallet type and update related com…
Charon-Fan Jan 8, 2025
a467593
Merge branch 'ava-labs:main' into feat/keystone-bk
Charon-Fan Jan 8, 2025
d4d4ec5
feat: add Keystone3 support with troubleshooting and approval overlays
Charon-Fan Jan 8, 2025
587db5d
feat: add support for KEYSTONE_USB feature and refactor related methods
Charon-Fan Jan 8, 2025
dfd2aaa
feat: add USB signing support for Bitcoin Keystone wallet and refacto…
Charon-Fan Jan 8, 2025
b47c555
fix: add missing newline at end of file in popup.tsx
Charon-Fan Jan 8, 2025
bd333d4
fix: update @keystonehq/hw-transport-webusb version to ^0.5.1 in pack…
Charon-Fan Jan 9, 2025
aaf789f
refactor: simplify getAddressPublicKeyFromXPub calls by using account…
Charon-Fan Jan 9, 2025
b23e4af
Merge remote-tracking branch 'origin/main' into feat/keystone-bk
Charon-Fan Feb 5, 2025
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
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@
"@avalabs/core-wallets-sdk": "3.1.0-alpha.32",
"@avalabs/evm-module": "1.2.0",
"@avalabs/glacier-sdk": "3.1.0-alpha.32",
"@avalabs/hw-app-avalanche": "0.14.1",
"@avalabs/hvm-module": "1.2.0",
"@avalabs/hw-app-avalanche": "0.14.1",
"@avalabs/types": "3.1.0-alpha.32",
"@avalabs/vm-module-types": "1.2.0",
"@blockaid/client": "0.10.0",
Expand All @@ -50,8 +50,12 @@
"@ethereumjs/common": "2.6.5",
"@ethereumjs/tx": "3.4.0",
"@hpke/core": "1.2.5",
"@keystonehq/alias-sampling": "^0.1.2",
"@keystonehq/animated-qr": "0.5.2",
"@keystonehq/bc-ur-registry-eth": "0.15.2",
"@keystonehq/hw-app-avalanche": "^0.0.2",
"@keystonehq/hw-app-eth": "^0.4.4",
"@keystonehq/hw-transport-webusb": "^0.5.1",
"@keystonehq/ur-decoder": "0.9.2",
"@ledgerhq/hw-app-btc": "10.2.4",
"@ledgerhq/hw-app-eth": "6.36.1",
Expand Down
2 changes: 2 additions & 0 deletions src/background/connections/extensionConnection/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ export enum ExtensionRequest {
LEDGER_VERSION_WARNING_CLOSED = 'ledger_version_warning_closed',
LEDGER_MIGRATE_MISSING_PUBKEYS = 'ledger_migrate_missing_pubkeys',

KEYSTONE_INIT_TRANSPORT = 'keystone_init_transport',
KEYSTONE_CLOSE_TRANSPORT = 'keystone_close_transport',
KEYSTONE_SUBMIT_SIGNATURE = 'keystone_submit_signature',

NAVIGATION_HISTORY_GET = 'navigation_history_get',
Expand Down
3 changes: 3 additions & 0 deletions src/background/services/featureFlags/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export enum FeatureGates {
BUY_MOONPAY = 'buy-feature-moonpay',
BUY_COINBASE = 'buy-feature-coinbase',
KEYSTONE = 'keystone',
KEYSTONE_USB = 'keystone-usb',
NFT_MARKETPLACE = 'nft-marketplace',
BOTTOM_NAVIGATION = 'bottom-navigation',
DEFI = 'defi-feature',
Expand Down Expand Up @@ -65,6 +66,7 @@ export const DISABLED_FLAG_VALUES: FeatureFlags = {
[FeatureGates.BUY_MOONPAY]: false,
[FeatureGates.BUY_COINBASE]: false,
[FeatureGates.KEYSTONE]: false,
[FeatureGates.KEYSTONE_USB]: false,
[FeatureGates.NFT_MARKETPLACE]: false,
[FeatureGates.BOTTOM_NAVIGATION]: false,
[FeatureGates.DEFI]: false,
Expand Down Expand Up @@ -116,6 +118,7 @@ export const DEFAULT_FLAGS: FeatureFlags = {
[FeatureGates.BUY_MOONPAY]: true,
[FeatureGates.BUY_COINBASE]: true,
[FeatureGates.KEYSTONE]: true,
[FeatureGates.KEYSTONE_USB]: true,
[FeatureGates.NFT_MARKETPLACE]: true,
[FeatureGates.BOTTOM_NAVIGATION]: true,
[FeatureGates.DEFI]: true,
Expand Down
25 changes: 22 additions & 3 deletions src/background/services/keystone/BitcoinKeystoneWallet.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { BitcoinProviderAbstract, createPsbt } from '@avalabs/core-wallets-sdk';
import { CryptoPSBT } from '@keystonehq/bc-ur-registry-eth';
import KeystoneUSBEthSDK from '@keystonehq/hw-app-eth';
import { Psbt, Transaction } from 'bitcoinjs-lib';
import { createKeystoneTransport } from '@keystonehq/hw-transport-webusb';
import { UREncoder } from '@ngraveio/bc-ur';

import { parseResponoseUR } from './KeystoneWallet';
import { BtcTransactionRequest } from '../wallet/models';
import { KeystoneTransport } from './models';

Expand All @@ -13,6 +17,7 @@ export class BitcoinKeystoneWallet {
private keystoneTransport: KeystoneTransport,
private provider: BitcoinProviderAbstract,
private tabId?: number,
private viaUSB?: boolean,
) {}

async signTx(
Expand All @@ -34,12 +39,26 @@ export class BitcoinKeystoneWallet {
});

const cryptoPSBT = new CryptoPSBT(psbt.toBuffer());
const { type, cbor } = cryptoPSBT.toUR();
const ur = cryptoPSBT.toUR();

if (this.viaUSB) {
const app = new KeystoneUSBEthSDK(
(await createKeystoneTransport()) as any,
);
const encodedUR = new UREncoder(ur!, Infinity).nextPart().toUpperCase();
const signedCborBuffer = parseResponoseUR(
(await app.signTransactionFromUr(encodedUR)).payload,
).cbor;

const signedTx = CryptoPSBT.fromCBOR(signedCborBuffer).getPSBT();

return Psbt.fromBuffer(signedTx).extractTransaction();
}

const signedCborBuffer = await this.keystoneTransport.requestSignature(
{
type,
cbor: cbor.toString('hex'),
type: ur.type,
cbor: ur.cbor.toString('hex'),
},
this.tabId,
);
Expand Down
83 changes: 80 additions & 3 deletions src/background/services/keystone/KeystoneWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,66 @@ import {
} from '@keystonehq/bc-ur-registry-eth';
import { TransactionRequest, hexlify } from 'ethers';
import { BufferLike, rlp } from 'ethereumjs-util';

import { Avalanche } from '@avalabs/core-wallets-sdk';
import KeystoneUSBAvalancheSDK from '@keystonehq/hw-app-avalanche';
import KeystoneUSBEthSDK from '@keystonehq/hw-app-eth';
import { createKeystoneTransport } from '@keystonehq/hw-transport-webusb';
import { UREncoder, URDecoder, UR } from '@ngraveio/bc-ur';
import { makeBNLike } from '@src/utils/makeBNLike';

import { CBOR, KeystoneTransport } from './models';
import { convertTxData } from './utils';

export const parseResponoseUR = (urPlayload: string): UR => {
const decoder = new URDecoder();
decoder.receivePart(urPlayload);
if (!decoder.isComplete()) {
throw new Error('UR incomplete');
}
const resultUR = decoder.resultUR();
return resultUR;
};

export class KeystoneWallet {
constructor(
private fingerprint: string,
private activeAccountIndex: number,
private keystoneTransport: KeystoneTransport,
private chainId?: number,
private tabId?: number,
private xpub?: string,
private xpubXP?: string,
) {}

async signTransaction(txRequest: TransactionRequest): Promise<string> {
const isKeystone3 = !!this.xpubXP;
if (isKeystone3) {
const app = new KeystoneUSBEthSDK(
(await createKeystoneTransport()) as any,
);
const ur = await this.buildSignatureUR(
txRequest,
this.fingerprint,
this.activeAccountIndex,
);
const encodedUR = new UREncoder(ur!, Infinity).nextPart().toUpperCase();
const payload = (await app.signTransactionFromUr(encodedUR)).payload;
const signature = ETHSignature.fromCBOR(
parseResponoseUR(payload).cbor,
).getSignature();

const r = hexlify(new Uint8Array(signature.slice(0, 32)));
const s = hexlify(new Uint8Array(signature.slice(32, 64)));
const v = new BN(signature.slice(64)).toNumber();

const signedTx = await this.getTxFromTransactionRequest(txRequest, {
r,
s,
v,
});

return '0x' + signedTx.serialize().toString('hex');
}
const cborBuffer = await this.keystoneTransport.requestSignature(
await this.buildSignatureRequest(
txRequest,
Expand All @@ -51,11 +95,11 @@ export class KeystoneWallet {
return '0x' + signedTx.serialize().toString('hex');
}

private async buildSignatureRequest(
private async buildSignatureUR(
txRequest: TransactionRequest,
fingerprint: string,
activeAccountIndex: number,
): Promise<CBOR> {
): Promise<UR> {
const chainId = txRequest.chainId ?? this.chainId;
const isLegacyTx = typeof txRequest.gasPrice !== 'undefined';

Expand Down Expand Up @@ -85,6 +129,19 @@ export class KeystoneWallet {

const ur = ethSignRequest.toUR();

return ur;
}

private async buildSignatureRequest(
txRequest: TransactionRequest,
fingerprint: string,
activeAccountIndex: number,
): Promise<CBOR> {
const ur = await this.buildSignatureUR(
txRequest,
fingerprint,
activeAccountIndex,
);
return {
cbor: ur.cbor.toString('hex'),
type: ur.type,
Expand Down Expand Up @@ -148,4 +205,24 @@ export class KeystoneWallet {
type: type || undefined,
};
}

/**
* Avalanche P/X chain transaction signing
*/
public async signTx(
txRequest: Avalanche.SignTxRequest,
): Promise<Avalanche.SignTxRequest['tx']> {
const tx = txRequest.tx;
const isEvmChain = tx.getVM() === 'EVM';
const app = new KeystoneUSBAvalancheSDK(await createKeystoneTransport());
const sig = await app.signTx(
tx as any,
this.fingerprint,
isEvmChain ? this.xpub! : this.xpubXP!,
this.activeAccountIndex,
);

tx.addSignature(Buffer.from(sig, 'hex'));
return tx;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ describe('src/background/services/onboarding/handlers/keystoneOnboardingHandler.
const request = getRequest([
{
xpub: 'xpub',
xpubXP: '',
password: 'password',
analyticsConsent: false,
masterFingerprint: 'masterFingerprint',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type HandlerType = ExtensionRequestHandler<
{
masterFingerprint: string;
xpub: string;
xpubXP: string;
password: string;
analyticsConsent: boolean;
walletName?: string;
Expand All @@ -44,8 +45,14 @@ export class KeystoneOnboardingHandler implements HandlerType {
) {}

handle: HandlerType['handle'] = async ({ request }) => {
const { masterFingerprint, xpub, password, analyticsConsent, walletName } =
(request.params ?? [])[0] ?? {};
const {
masterFingerprint,
xpub,
xpubXP,
password,
analyticsConsent,
walletName,
} = (request.params ?? [])[0] ?? {};

await startOnboarding({
settingsService: this.settingsService,
Expand All @@ -56,8 +63,9 @@ export class KeystoneOnboardingHandler implements HandlerType {
});

const walletId = await this.walletService.init({
secretType: SecretType.Keystone,
secretType: xpubXP ? SecretType.Keystone3Pro : SecretType.Keystone,
xpub,
xpubXP,
masterFingerprint,
derivationPath: DerivationPath.BIP44,
name: walletName,
Expand Down
3 changes: 3 additions & 0 deletions src/background/services/onboarding/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export enum OnboardingPhase {
LEDGER_TROUBLE = 'ledger_trouble',
ANALYTICS_CONSENT = 'analytics_consent',
KEYSTONE = 'keystone',
KEYSTONE_USB = 'keystoneUsb',
KEYSTONE_TUTORIAL = 'keystone_tutorial',
SEEDLESS_GOOGLE = 'seedless_google',
SEEDLESS_APPLE = 'seedless_apple',
Expand All @@ -25,6 +26,7 @@ export enum OnboardingURLs {
CREATE_WALLET = '/onboarding/create-wallet',
SEED_PHRASE = '/onboarding/seed-phrase',
KEYSTONE = '/onboarding/keystone',
KEYSTONE_USB = '/onboarding/Keystone-usb',
LEDGER = '/onboarding/ledger',
CREATE_PASSWORD = '/onboarding/create-password',
ANALYTICS_CONSENT = '/onboarding/analytics-consent',
Expand All @@ -39,6 +41,7 @@ export const ONBOARDING_EVENT_NAMES = {
[OnboardingPhase.IMPORT_WALLET]: 'OnboardingImportMnemonicSelected',
[OnboardingPhase.LEDGER]: 'OnboardingImportLedgerSelected',
[OnboardingPhase.KEYSTONE]: 'OnboardingKeystoneSelected',
[OnboardingPhase.KEYSTONE_USB]: 'OnboardingKeystoneUSBSelected',
[OnboardingPhase.SEEDLESS_GOOGLE]: 'OnboardingSeedlessGoogleSelected',
[OnboardingPhase.SEEDLESS_APPLE]: 'OnboardingSeedlessAppleSelected',
};
Expand Down
4 changes: 3 additions & 1 deletion src/background/services/secrets/SecretsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export class SecretsService implements OnUnlock {
[SecretType.Ledger]: 'Ledger',
[SecretType.LedgerLive]: 'Ledger Live',
[SecretType.Keystone]: 'Keystone',
[SecretType.Keystone3Pro]: 'Keystone3 Pro',
[SecretType.Seedless]: 'Seedless',
};
const storedSecrets = await this.#loadSecrets(false);
Expand Down Expand Up @@ -762,7 +763,8 @@ export class SecretsService implements OnUnlock {
if (
secrets.secretType === SecretType.Ledger ||
secrets.secretType === SecretType.Mnemonic ||
secrets.secretType === SecretType.Keystone
secrets.secretType === SecretType.Keystone ||
secrets.secretType === SecretType.Keystone3Pro
) {
// C-avax... this address uses the same public key as EVM
const cPubkey = getAddressPublicKeyFromXPub(secrets.xpub, index);
Expand Down
5 changes: 3 additions & 2 deletions src/background/services/secrets/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export enum SecretType {
Ledger = 'ledger',
LedgerLive = 'ledger-live',
Keystone = 'keystone',
Keystone3Pro = 'keystone3-pro',
Seedless = 'seedless',
// Importable wallets types
PrivateKey = 'private-key',
Expand Down Expand Up @@ -57,10 +58,10 @@ interface MnemonicSecrets extends PrimarySecretsBase {
}

interface KeystoneSecrets extends PrimarySecretsBase {
secretType: SecretType.Keystone;
secretType: SecretType.Keystone | SecretType.Keystone3Pro;
masterFingerprint: string;
xpub: string;
xpubXP?: never;
xpubXP?: string;
derivationPath: DerivationPath;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export function getSecretsType(walletKeys: WalletKeys) {
return SecretType.Keystone;
}

if (masterFingerprint && xpub && xpubXP) {
return SecretType.Keystone3Pro;
}

if (xpub) {
return SecretType.Ledger;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ const up = async (walletStorage: PreviousSchema) => {
? 'Recovery Phrase'
: secretType === SecretType.Seedless
? 'Seedless'
: secretType === SecretType.Keystone
: secretType === SecretType.Keystone ||
secretType === SecretType.Keystone3Pro
? 'Keystone'
: 'Ledger';

Expand Down
Loading