From dbb85b49758ccd2b79b6daf8e1961dfbd79c83cd Mon Sep 17 00:00:00 2001 From: Tarik Gul <47201679+TarikGul@users.noreply.github.com> Date: Wed, 14 Aug 2024 06:38:04 -0700 Subject: [PATCH] Enable "Chain Specific App" for Ledger (#10826) * Bump ui to 3.9.1 * Enable "Chain Specific App" support for Ledger * Add Ledger query for accounts and modals --- packages/apps-config/package.json | 2 +- .../page-accounts/src/Accounts/Account.tsx | 17 +++++- packages/page-accounts/src/modals/Ledger.tsx | 22 ++++++- packages/react-components/package.json | 8 +-- packages/react-hooks/src/useLedger.ts | 14 +++-- packages/react-signer/src/TxSigned.tsx | 21 +++++-- .../react-signer/src/signers/LedgerSigner.ts | 53 +++++++++++------ yarn.lock | 58 +++++++++---------- 8 files changed, 128 insertions(+), 67 deletions(-) diff --git a/packages/apps-config/package.json b/packages/apps-config/package.json index 39950824c2..601e371996 100644 --- a/packages/apps-config/package.json +++ b/packages/apps-config/package.json @@ -40,7 +40,7 @@ "@polkadot/api": "^12.2.1", "@polkadot/api-derive": "^12.2.1", "@polkadot/networks": "^13.0.2", - "@polkadot/react-identicon": "^3.8.3", + "@polkadot/react-identicon": "^3.9.1", "@polkadot/types": "^12.2.1", "@polkadot/types-codec": "^12.2.1", "@polkadot/util": "^13.0.2", diff --git a/packages/page-accounts/src/Accounts/Account.tsx b/packages/page-accounts/src/Accounts/Account.tsx index 41990b6319..e52c9d50ed 100644 --- a/packages/page-accounts/src/Accounts/Account.tsx +++ b/packages/page-accounts/src/Accounts/Account.tsx @@ -1,10 +1,14 @@ // Copyright 2017-2024 @polkadot/app-accounts authors & contributors // SPDX-License-Identifier: Apache-2.0 +// This is for the use of `Ledger` +// +/* eslint-disable deprecation/deprecation */ + import type { ApiPromise } from '@polkadot/api'; import type { SubmittableExtrinsic } from '@polkadot/api/types'; import type { DeriveDemocracyLock, DeriveStakingAccount } from '@polkadot/api-derive/types'; -import type { LedgerGeneric } from '@polkadot/hw-ledger'; +import type { Ledger, LedgerGeneric } from '@polkadot/hw-ledger'; import type { ActionStatus } from '@polkadot/react-components/Status/types'; import type { Option } from '@polkadot/types'; import type { ProxyDefinition, RecoveryConfig } from '@polkadot/types/interfaces'; @@ -17,6 +21,7 @@ import useAccountLocks from '@polkadot/app-referenda/useAccountLocks'; import { AddressInfo, AddressSmall, Badge, Button, ChainLock, Columar, CryptoType, Forget, LinkExternal, Menu, Popup, styled, Table, Tags, TransferModal } from '@polkadot/react-components'; import { useAccountInfo, useApi, useBalancesAll, useBestNumber, useCall, useLedger, useQueue, useStakingInfo, useToggle } from '@polkadot/react-hooks'; import { keyring } from '@polkadot/ui-keyring'; +import { settings } from '@polkadot/ui-settings'; import { BN, BN_ZERO, formatBalance, formatNumber, isFunction } from '@polkadot/util'; import Backup from '../modals/Backup.js'; @@ -136,10 +141,16 @@ function createClearReferendaTx (api: ApiPromise, address: string, ids: [BN, BN] return api.tx.utility.batch(inner); } -async function showLedgerAddress (getLedger: () => LedgerGeneric, meta: KeyringJson$Meta, ss58Prefix: number): Promise { +async function showLedgerAddress (getLedger: () => LedgerGeneric | Ledger, meta: KeyringJson$Meta, ss58Prefix: number): Promise { + const currApp = settings.get().ledgerApp; const ledger = getLedger(); - await ledger.getAddress(ss58Prefix, true, meta.accountOffset || 0, meta.addressOffset || 0); + if (currApp === 'migration' || currApp === 'generic') { + await (ledger as LedgerGeneric).getAddress(ss58Prefix, true, meta.accountOffset || 0, meta.addressOffset || 0); + } else { + // This will always be the `chainSpecific` setting if the above condition is not met + await (ledger as Ledger).getAddress(true, meta.accountOffset || 0, meta.addressOffset || 0); + } } const transformRecovery = { diff --git a/packages/page-accounts/src/modals/Ledger.tsx b/packages/page-accounts/src/modals/Ledger.tsx index a418f828ad..62725ceb85 100644 --- a/packages/page-accounts/src/modals/Ledger.tsx +++ b/packages/page-accounts/src/modals/Ledger.tsx @@ -1,8 +1,12 @@ // Copyright 2017-2024 @polkadot/app-accounts authors & contributors // SPDX-License-Identifier: Apache-2.0 +// This is for the use of `Ledger` +// +/* eslint-disable deprecation/deprecation */ + import type { ApiPromise } from '@polkadot/api'; -import type { LedgerGeneric } from '@polkadot/hw-ledger'; +import type { Ledger, LedgerGeneric } from '@polkadot/hw-ledger'; import React, { useCallback, useRef, useState } from 'react'; @@ -28,8 +32,20 @@ interface Props { export const AVAIL_INDEXES = arrayRange(20); // query the ledger for the address, adding it to the keyring -async function queryLedger (api: ApiPromise, getLedger: () => LedgerGeneric, name: string, accountOffset: number, addressOffset: number, ss58Prefix: number): Promise { - const { address } = await getLedger().getAddress(ss58Prefix, false, accountOffset, addressOffset); +async function queryLedger (api: ApiPromise, getLedger: () => LedgerGeneric | Ledger, name: string, accountOffset: number, addressOffset: number, ss58Prefix: number): Promise { + let address: string; + const currApp = settings.get().ledgerApp; + + if (currApp === 'migration' || currApp === 'generic') { + const addr = await (getLedger() as LedgerGeneric).getAddress(ss58Prefix, false, accountOffset, addressOffset); + + address = addr.address; + } else { + // This will always be the `chainSpecific` setting if the above condition is not met + const addr = await (getLedger() as Ledger).getAddress(false, accountOffset, addressOffset); + + address = addr.address; + } keyring.addHardware(address, 'ledger', { accountOffset, diff --git a/packages/react-components/package.json b/packages/react-components/package.json index 9fde78e5a8..43bb6fcf8f 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -21,11 +21,11 @@ "@fortawesome/react-fontawesome": "^0.2.0", "@polkadot/keyring": "^13.0.2", "@polkadot/react-api": "^0.142.2-20-x", - "@polkadot/react-identicon": "^3.8.3", - "@polkadot/react-qr": "^3.8.3", + "@polkadot/react-identicon": "^3.9.1", + "@polkadot/react-qr": "^3.9.1", "@polkadot/react-query": "^0.142.2-20-x", - "@polkadot/ui-keyring": "^3.8.3", - "@polkadot/ui-settings": "^3.8.3", + "@polkadot/ui-keyring": "^3.9.1", + "@polkadot/ui-settings": "^3.9.1", "@polkadot/util": "^13.0.2", "@polkadot/util-crypto": "^13.0.2", "chart.js": "^4.4.1", diff --git a/packages/react-hooks/src/useLedger.ts b/packages/react-hooks/src/useLedger.ts index 293a5c137d..dc62965276 100644 --- a/packages/react-hooks/src/useLedger.ts +++ b/packages/react-hooks/src/useLedger.ts @@ -1,12 +1,16 @@ // Copyright 2017-2024 @polkadot/react-hooks authors & contributors // SPDX-License-Identifier: Apache-2.0 +// This is for the use of `Ledger` +// +/* eslint-disable deprecation/deprecation */ + import type { ApiPromise } from '@polkadot/api'; import type { TransportType } from '@polkadot/hw-ledger-transports/types'; import { useCallback, useMemo } from 'react'; -import { LedgerGeneric } from '@polkadot/hw-ledger'; +import { Ledger, LedgerGeneric } from '@polkadot/hw-ledger'; import { knownGenesis, knownLedger } from '@polkadot/networks/defaults'; import { settings } from '@polkadot/ui-settings'; import { assert } from '@polkadot/util'; @@ -22,7 +26,7 @@ interface StateBase { } interface State extends StateBase { - getLedger: () => LedgerGeneric; + getLedger: () => LedgerGeneric | Ledger; } const EMPTY_STATE: StateBase = { @@ -37,11 +41,11 @@ const ledgerChains = Object .keys(knownGenesis) .filter((n) => knownLedger[n]); const ledgerHashes = ledgerChains.reduce((all, n) => [...all, ...knownGenesis[n]], []); -let ledger: LedgerGeneric | null = null; +let ledger: LedgerGeneric | Ledger | null = null; let ledgerType: TransportType | null = null; let ledgerApp: string | null; -function retrieveLedger (api: ApiPromise): LedgerGeneric { +function retrieveLedger (api: ApiPromise): LedgerGeneric | Ledger { const currType = settings.get().ledgerConn as TransportType; const currApp = settings.get().ledgerApp; @@ -57,6 +61,8 @@ function retrieveLedger (api: ApiPromise): LedgerGeneric { ledger = new LedgerGeneric(currType, network, knownLedger.polkadot); } else if (currApp === 'migration') { ledger = new LedgerGeneric(currType, network, knownLedger[network]); + } else if (currApp === 'chainSpecific') { + ledger = new Ledger(currType, network); } else { // This will never get touched since it will always hit the above two. This satisfies the compiler. ledger = new LedgerGeneric(currType, network, knownLedger.polkadot); diff --git a/packages/react-signer/src/TxSigned.tsx b/packages/react-signer/src/TxSigned.tsx index 5fc2d5a2fc..8c79282593 100644 --- a/packages/react-signer/src/TxSigned.tsx +++ b/packages/react-signer/src/TxSigned.tsx @@ -1,10 +1,14 @@ // Copyright 2017-2024 @polkadot/react-signer authors & contributors // SPDX-License-Identifier: Apache-2.0 +// This is for the use of `Ledger` +// +/* eslint-disable deprecation/deprecation */ + import type { ApiPromise } from '@polkadot/api'; import type { SignerOptions } from '@polkadot/api/submittable/types'; import type { SubmittableExtrinsic } from '@polkadot/api/types'; -import type { LedgerGeneric } from '@polkadot/hw-ledger'; +import type { Ledger, LedgerGeneric } from '@polkadot/hw-ledger'; import type { KeyringPair } from '@polkadot/keyring/types'; import type { QueueTx, QueueTxMessageSetStatus } from '@polkadot/react-components/Status/types'; import type { Option } from '@polkadot/types'; @@ -19,6 +23,7 @@ import { web3FromSource } from '@polkadot/extension-dapp'; import { Button, ErrorBoundary, Modal, Output, styled, Toggle } from '@polkadot/react-components'; import { useApi, useLedger, useQueue, useToggle } from '@polkadot/react-hooks'; import { keyring } from '@polkadot/ui-keyring'; +import { settings } from '@polkadot/ui-settings'; import { assert, nextTick } from '@polkadot/util'; import { addressEq } from '@polkadot/util-crypto'; @@ -190,7 +195,7 @@ async function wrapTx (api: ApiPromise, currentItem: QueueTx, { isMultiCall, mul return tx; } -async function extractParams (api: ApiPromise, address: string, options: Partial, getLedger: () => LedgerGeneric, setQrState: (state: QrState) => void): Promise<['qr' | 'signing', string, Partial, boolean]> { +async function extractParams (api: ApiPromise, address: string, options: Partial, getLedger: () => LedgerGeneric | Ledger, setQrState: (state: QrState) => void): Promise<['qr' | 'signing', string, Partial, boolean]> { const pair = keyring.getPair(address); const { meta: { accountOffset, addressOffset, isExternal, isHardware, isInjected, isLocal, isProxied, source } } = pair; @@ -286,10 +291,18 @@ function TxSigned ({ className, currentItem, isQueueSubmit, queueSize, requestAd passwordError = unlockAccount(senderInfo); } else if (flags.isHardware) { try { + const currApp = settings.get().ledgerApp; const ledger = getLedger(); - const { address } = await ledger.getAddress(api.consts.system.ss58Prefix.toNumber(), false, flags.accountOffset, flags.addressOffset); - console.log(`Signing with Ledger address ${address}`); + if (currApp === 'migration' || currApp === 'generic') { + const { address } = await (ledger as LedgerGeneric).getAddress(api.consts.system.ss58Prefix.toNumber(), false, flags.accountOffset, flags.addressOffset); + + console.log(`Signing with Ledger address ${address}`); + } else { + const { address } = await (ledger as Ledger).getAddress(false, flags.accountOffset, flags.addressOffset); + + console.log(`Signing with Ledger address ${address}`); + } } catch (error) { console.error(error); diff --git a/packages/react-signer/src/signers/LedgerSigner.ts b/packages/react-signer/src/signers/LedgerSigner.ts index 2b31345631..ac4bcbf20c 100644 --- a/packages/react-signer/src/signers/LedgerSigner.ts +++ b/packages/react-signer/src/signers/LedgerSigner.ts @@ -1,11 +1,16 @@ // Copyright 2017-2024 @polkadot/react-signer authors & contributors // SPDX-License-Identifier: Apache-2.0 +// This is for the use of `Ledger` +// +/* eslint-disable deprecation/deprecation */ + import type { ApiPromise } from '@polkadot/api'; import type { Signer, SignerResult } from '@polkadot/api/types'; -import type { LedgerGeneric } from '@polkadot/hw-ledger'; +import type { Ledger, LedgerGeneric } from '@polkadot/hw-ledger'; import type { Registry, SignerPayloadJSON } from '@polkadot/types/types'; +import { settings } from '@polkadot/ui-settings'; import { objectSpread, u8aToHex } from '@polkadot/util'; import { merkleizeMetadata } from '@polkadot-api/merkleize-metadata'; @@ -14,11 +19,11 @@ let id = 0; export class LedgerSigner implements Signer { readonly #accountIndex: number; readonly #addressOffset: number; - readonly #getLedger: () => LedgerGeneric; + readonly #getLedger: () => LedgerGeneric | Ledger; readonly #registry: Registry; readonly #api: ApiPromise; - constructor (api: ApiPromise, getLedger: () => LedgerGeneric, accountIndex: number, addressOffset: number) { + constructor (api: ApiPromise, getLedger: () => LedgerGeneric | Ledger, accountIndex: number, addressOffset: number) { this.#accountIndex = accountIndex; this.#addressOffset = addressOffset; this.#getLedger = getLedger; @@ -47,25 +52,35 @@ export class LedgerSigner implements Signer { } public async signPayload (payload: SignerPayloadJSON): Promise { - const { address } = await this.#getLedger().getAddress( - this.#api.consts.system.ss58Prefix.toNumber(), - false, - this.#accountIndex, - this.#addressOffset - ); - const { raw, txMetadata } = await this.getMetadataProof(payload); + const currApp = settings.get().ledgerApp; - const buff = Buffer.from(txMetadata); - const { signature } = await this.#getLedger().signWithMetadata(raw.toU8a(true), this.#accountIndex, this.#addressOffset, { metadata: buff }); + if (currApp === 'migration' || currApp === 'generic') { + const { address } = await (this.#getLedger() as LedgerGeneric).getAddress( + this.#api.consts.system.ss58Prefix.toNumber(), + false, + this.#accountIndex, + this.#addressOffset + ); + const { raw, txMetadata } = await this.getMetadataProof(payload); - const extrinsic = this.#registry.createType( - 'Extrinsic', - { method: raw.method }, - { version: 4 } - ); + const buff = Buffer.from(txMetadata); + const { signature } = await (this.#getLedger() as LedgerGeneric).signWithMetadata(raw.toU8a(true), this.#accountIndex, this.#addressOffset, { metadata: buff }); - extrinsic.addSignature(address, signature, raw.toHex()); + const extrinsic = this.#registry.createType( + 'Extrinsic', + { method: raw.method }, + { version: 4 } + ); - return { id: ++id, signature, signedTransaction: extrinsic.toHex() }; + extrinsic.addSignature(address, signature, raw.toHex()); + + return { id: ++id, signature, signedTransaction: extrinsic.toHex() }; + } else { + // This is when the option is `chainSpecific` + const raw = this.#registry.createType('ExtrinsicPayload', payload, { version: payload.version }); + const { signature } = await this.#getLedger().sign(raw.toU8a(true), this.#accountIndex, this.#addressOffset); + + return { id: ++id, signature }; + } } } diff --git a/yarn.lock b/yarn.lock index 35130db13c..6d3eb4ab82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1782,7 +1782,7 @@ __metadata: "@polkadot/api": "npm:^12.2.1" "@polkadot/api-derive": "npm:^12.2.1" "@polkadot/networks": "npm:^13.0.2" - "@polkadot/react-identicon": "npm:^3.8.3" + "@polkadot/react-identicon": "npm:^3.9.1" "@polkadot/types": "npm:^12.2.1" "@polkadot/types-codec": "npm:^12.2.1" "@polkadot/util": "npm:^13.0.2" @@ -2116,11 +2116,11 @@ __metadata: "@fortawesome/react-fontawesome": "npm:^0.2.0" "@polkadot/keyring": "npm:^13.0.2" "@polkadot/react-api": "npm:^0.142.2-20-x" - "@polkadot/react-identicon": "npm:^3.8.3" - "@polkadot/react-qr": "npm:^3.8.3" + "@polkadot/react-identicon": "npm:^3.9.1" + "@polkadot/react-qr": "npm:^3.9.1" "@polkadot/react-query": "npm:^0.142.2-20-x" - "@polkadot/ui-keyring": "npm:^3.8.3" - "@polkadot/ui-settings": "npm:^3.8.3" + "@polkadot/ui-keyring": "npm:^3.9.1" + "@polkadot/ui-settings": "npm:^3.9.1" "@polkadot/util": "npm:^13.0.2" "@polkadot/util-crypto": "npm:^13.0.2" "@types/chartjs-plugin-crosshair": "npm:^1.1.4" @@ -2171,13 +2171,13 @@ __metadata: languageName: unknown linkType: soft -"@polkadot/react-identicon@npm:^3.8.3": - version: 3.8.3 - resolution: "@polkadot/react-identicon@npm:3.8.3" +"@polkadot/react-identicon@npm:^3.9.1": + version: 3.9.1 + resolution: "@polkadot/react-identicon@npm:3.9.1" dependencies: "@polkadot/keyring": "npm:^13.0.2" - "@polkadot/ui-settings": "npm:3.8.3" - "@polkadot/ui-shared": "npm:3.8.3" + "@polkadot/ui-settings": "npm:3.9.1" + "@polkadot/ui-shared": "npm:3.9.1" "@polkadot/util": "npm:^13.0.2" "@polkadot/util-crypto": "npm:^13.0.2" ethereum-blockies-base64: "npm:^1.0.2" @@ -2192,7 +2192,7 @@ __metadata: react: "*" react-dom: "*" react-is: "*" - checksum: 10/6dddfca42732d141acd2e460d25635845287e0e8bfae6c170f72a691c49b1f34d08e1e975d1a2c2216e1814f1928a29982b806d6bc30f3f08ddddad214b91867 + checksum: 10/6a3ad4e129af15e2d6833def626085e392c960211935cf901560f434247560ae6dcfb8d8ecde38b56f317ac3e0d84653ef9c5b68e258c2791467b1339b595bef languageName: node linkType: hard @@ -2209,11 +2209,11 @@ __metadata: languageName: unknown linkType: soft -"@polkadot/react-qr@npm:^3.8.3": - version: 3.8.3 - resolution: "@polkadot/react-qr@npm:3.8.3" +"@polkadot/react-qr@npm:^3.9.1": + version: 3.9.1 + resolution: "@polkadot/react-qr@npm:3.9.1" dependencies: - "@polkadot/ui-settings": "npm:3.8.3" + "@polkadot/ui-settings": "npm:3.9.1" "@polkadot/util": "npm:^13.0.2" "@polkadot/util-crypto": "npm:^13.0.2" "@zxing/browser": "npm:^0.1.5" @@ -2227,7 +2227,7 @@ __metadata: react: "*" react-dom: "*" react-is: "*" - checksum: 10/1afb77b45a4b6bf0cb110dbf405bfd8a1edbabb9e4639e3b34f0f7f0c1c706799f462b1590e18da8d8de369172f075bdbc9800e7d54e9b48041724203294c600 + checksum: 10/f196e19e6d5c11ef7f7824fa67ad41fcde790ad6505113a2e8383c4c79c5c2a8947a619c0513709d9983679ff60af7f8c0811e1a232772fb5438dcbecd442e21 languageName: node linkType: hard @@ -2396,12 +2396,12 @@ __metadata: languageName: node linkType: hard -"@polkadot/ui-keyring@npm:^3.8.3": - version: 3.8.3 - resolution: "@polkadot/ui-keyring@npm:3.8.3" +"@polkadot/ui-keyring@npm:^3.9.1": + version: 3.9.1 + resolution: "@polkadot/ui-keyring@npm:3.9.1" dependencies: "@polkadot/keyring": "npm:^13.0.2" - "@polkadot/ui-settings": "npm:3.8.3" + "@polkadot/ui-settings": "npm:3.9.1" "@polkadot/util": "npm:^13.0.2" "@polkadot/util-crypto": "npm:^13.0.2" mkdirp: "npm:^3.0.1" @@ -2412,13 +2412,13 @@ __metadata: "@polkadot/keyring": "*" "@polkadot/ui-settings": "*" "@polkadot/util": "*" - checksum: 10/d8b3ebf3da39ebbcabe1e0c41c9ed731a14c186c1698c598b4aac6217162c7174453860d06582200b10bc687fc75ec713238ce1687efb4672de989ab728ee99f + checksum: 10/eec438ef2ad9363d77f51d60df9802f1cad3379661883a9412f9ca1af44e31444fc7c04eec1ec7ecf2ae76af1718ffce8e335dcf572d89a6ffc6403bf6c36423 languageName: node linkType: hard -"@polkadot/ui-settings@npm:3.8.3, @polkadot/ui-settings@npm:^3.8.3": - version: 3.8.3 - resolution: "@polkadot/ui-settings@npm:3.8.3" +"@polkadot/ui-settings@npm:3.9.1, @polkadot/ui-settings@npm:^3.9.1": + version: 3.9.1 + resolution: "@polkadot/ui-settings@npm:3.9.1" dependencies: "@polkadot/networks": "npm:^13.0.2" "@polkadot/util": "npm:^13.0.2" @@ -2428,20 +2428,20 @@ __metadata: peerDependencies: "@polkadot/networks": "*" "@polkadot/util": "*" - checksum: 10/be6d9f5111ba94b9e86b3348bd0f7e68008611b54fcc159b3a163d7331b345f1552b2d1ba7b6b763387607110e06eecd799b7ad61ee4d5847056b128a85bfdcf + checksum: 10/d3a28af0233f2ee9a8453501de3dd2fabc46bc98cb4c0e46a79654c87a0a41f3148dd6949b796932a4c12fc974b2b924d9eb0e76f91f7ce9ea27e5012e1e606a languageName: node linkType: hard -"@polkadot/ui-shared@npm:3.8.3": - version: 3.8.3 - resolution: "@polkadot/ui-shared@npm:3.8.3" +"@polkadot/ui-shared@npm:3.9.1": + version: 3.9.1 + resolution: "@polkadot/ui-shared@npm:3.9.1" dependencies: colord: "npm:^2.9.3" tslib: "npm:^2.6.2" peerDependencies: "@polkadot/util": "*" "@polkadot/util-crypto": "*" - checksum: 10/a7cbeed7ccbe2f3212d20155787d3a7f4ec2a5662dfb950d965a1ac8ea05fd380f3b9456147cf2e15dc5adbf0df71eedfd94c0707db44a16f1bf2f84c0ebf01c + checksum: 10/80a3a59ca25ed0e572a2eb85533f54f9714c6fd21f85fcf46775fc57c5d55c048049378c94372260df9d860569197ea38dbf3a24cc8ed1e416f67c69408f3789 languageName: node linkType: hard