From 7aa167bac1b8e38055fee942fce26c3ad5acd365 Mon Sep 17 00:00:00 2001 From: Peter Sanderson Date: Fri, 20 Dec 2024 01:17:15 +0100 Subject: [PATCH] fix: show h instead of apostrophe in taproot xpub to be consistent with firmware --- .../resolveDescriptorForTaproot.test.ts | 2 +- .../src/device/resolveDescriptorForTaproot.ts | 28 ++++++++----------- packages/connect/src/exports.ts | 3 ++ .../modals/ReduxModal/ConfirmXpubModal.tsx | 10 ++++++- packages/utils/src/convertTaprootXpub.ts | 27 ++++++++++++++++++ packages/utils/src/index.ts | 1 + packages/utils/src/parseElectrumUrl.ts | 2 ++ 7 files changed, 54 insertions(+), 19 deletions(-) create mode 100644 packages/utils/src/convertTaprootXpub.ts diff --git a/packages/connect/src/device/__tests__/resolveDescriptorForTaproot.test.ts b/packages/connect/src/device/__tests__/resolveDescriptorForTaproot.test.ts index b7e2a936a0a..4c3cc606504 100644 --- a/packages/connect/src/device/__tests__/resolveDescriptorForTaproot.test.ts +++ b/packages/connect/src/device/__tests__/resolveDescriptorForTaproot.test.ts @@ -85,7 +85,7 @@ describe(resolveDescriptorForTaproot.name, () => { }); expect(response).toEqual({ - checksum: undefined, // code is defensive, it will work but it wont provide checksum + checksum: undefined, // code is defensive, it will work but it won't provide checksum xpub: "tr([71d98c03/86'/0'/0']xpub6CXYpDGLuWpjqFXRTbo8LMYVsiiRjwWiDY7iwDkq1mk4GDYE7TWmSBCnNmbcVYQK4T56RZRRwhCAG7ucTBHAG2rhWHpXdMQtkZVDeVuv33p/<0;1>/*)", }); }); diff --git a/packages/connect/src/device/resolveDescriptorForTaproot.ts b/packages/connect/src/device/resolveDescriptorForTaproot.ts index 28bb8ec2e74..67e28dd6318 100644 --- a/packages/connect/src/device/resolveDescriptorForTaproot.ts +++ b/packages/connect/src/device/resolveDescriptorForTaproot.ts @@ -1,32 +1,26 @@ import { MessagesSchema as Messages } from '@trezor/protobuf'; +import { convertTaprootXpub } from '@trezor/utils'; import { HDNodeResponse } from '../types/api/getPublicKey'; -interface Params { +interface ResolveDescriptorForTaprootParams { response: HDNodeResponse; publicKey: Messages.PublicKey; } -export const resolveDescriptorForTaproot = ({ response, publicKey }: Params) => { +export const resolveDescriptorForTaproot = ({ + response, + publicKey, +}: ResolveDescriptorForTaprootParams) => { if (publicKey.descriptor !== null && publicKey.descriptor !== undefined) { const [xpub, checksum] = publicKey.descriptor.split('#'); - // This is here to keep backwards compatibility, suite and blockbooks are still using `'` over `h` - const openingSquareBracketSplit = xpub.split('['); - if (openingSquareBracketSplit.length === 2) { - const [beforeOpeningBracket, afterOpeningBracket] = openingSquareBracketSplit; + // This is here to keep backwards compatibility, suite and block-books + // are still using `'` over `h`. + const correctedXpub = convertTaprootXpub({ xpub, direction: 'h-to-apostrophe' }); - const closingSquareBracketSplit = afterOpeningBracket.split(']'); - if (closingSquareBracketSplit.length === 2) { - const [path, afterClosingBracket] = closingSquareBracketSplit; - - const correctedPath = path.replace(/h/g, "'"); // .replaceAll() - - return { - xpub: `${beforeOpeningBracket}[${correctedPath}]${afterClosingBracket}`, - checksum, - }; - } + if (correctedXpub !== null) { + return { xpub: correctedXpub, checksum }; } } diff --git a/packages/connect/src/exports.ts b/packages/connect/src/exports.ts index cffd8891f83..4664f6bb43f 100644 --- a/packages/connect/src/exports.ts +++ b/packages/connect/src/exports.ts @@ -3,3 +3,6 @@ export * from './events'; export * from './types'; export { parseConnectSettings } from './data/connectSettings'; + +// Do NOT add any code exports here. Only TrezorConnect and types shall be exported from +// `@trezor/connect` package. diff --git a/packages/suite/src/components/suite/modals/ReduxModal/ConfirmXpubModal.tsx b/packages/suite/src/components/suite/modals/ReduxModal/ConfirmXpubModal.tsx index 803c84ea8e7..86dfea63d68 100644 --- a/packages/suite/src/components/suite/modals/ReduxModal/ConfirmXpubModal.tsx +++ b/packages/suite/src/components/suite/modals/ReduxModal/ConfirmXpubModal.tsx @@ -1,4 +1,5 @@ import { selectSelectedDevice } from '@suite-common/wallet-core'; +import { convertTaprootXpub } from '@trezor/utils'; import { getNetworkDisplaySymbol } from '@suite-common/wallet-config'; import { Translation } from 'src/components/suite'; @@ -27,6 +28,13 @@ export const ConfirmXpubModal = ( ? `${account.descriptor}#${account.descriptorChecksum}` : account.descriptor; + // Suite internally uses apostrophe, but FW uses 'h' for taproot descriptors, + // and we want to show it correctly to the user + const xpubWithReplacedApostropheWithH = convertTaprootXpub({ + xpub, + direction: 'apostrophe-to-h', + }); + return ( } validateOnDevice={showXpub} copyButtonText={} - value={xpub} + value={xpubWithReplacedApostropheWithH ?? xpub} displayMode={DisplayMode.PAGINATED_TEXT} {...props} /> diff --git a/packages/utils/src/convertTaprootXpub.ts b/packages/utils/src/convertTaprootXpub.ts new file mode 100644 index 00000000000..fd1890d653b --- /dev/null +++ b/packages/utils/src/convertTaprootXpub.ts @@ -0,0 +1,27 @@ +// Todo: one day, we shall purify the @trezor/utils and remove domain-specific stuff from it + +export type ConvertTaprootXpubParams = { + xpub: string; + direction: 'h-to-apostrophe' | 'apostrophe-to-h'; +}; + +export const convertTaprootXpub = ({ xpub, direction }: ConvertTaprootXpubParams) => { + const find = direction === 'h-to-apostrophe' ? 'h' : "'"; + const replace = direction === 'h-to-apostrophe' ? "'" : 'h'; + + const openingSquareBracketSplit = xpub.split('['); + if (openingSquareBracketSplit.length === 2) { + const [beforeOpeningBracket, afterOpeningBracket] = openingSquareBracketSplit; + + const closingSquareBracketSplit = afterOpeningBracket.split(']'); + if (closingSquareBracketSplit.length === 2) { + const [path, afterClosingBracket] = closingSquareBracketSplit; + + const correctedPath = path.replace(new RegExp(find, 'g'), replace); // .replaceAll() + + return `${beforeOpeningBracket}[${correctedPath}]${afterClosingBracket}`; + } + } + + return null; +}; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index eddf99d124a..984ec8ec5ad 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -51,3 +51,4 @@ export * from './isFullPath'; export * from './asciiUtils'; export * from './resolveAfter'; export * from './zip'; +export * from './convertTaprootXpub'; diff --git a/packages/utils/src/parseElectrumUrl.ts b/packages/utils/src/parseElectrumUrl.ts index 9793f273af6..d6728849d21 100644 --- a/packages/utils/src/parseElectrumUrl.ts +++ b/packages/utils/src/parseElectrumUrl.ts @@ -1,3 +1,5 @@ +// Todo: one day, we shall purify the @trezor/utils and remove domain-specific stuff from it + // URL is in format host:port:[t|s] (t for tcp, s for ssl) const ELECTRUM_URL_REGEX = /^(?:([a-zA-Z0-9.-]+)|\[([a-f0-9:]+)\]):([0-9]{1,5}):([ts])$/;