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

fix: show h instead of apostrophe in taproot xpub to be consistent with firmware #16070

Merged
merged 1 commit into from
Jan 2, 2025
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -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>/*)",
});
});
Expand Down
28 changes: 11 additions & 17 deletions packages/connect/src/device/resolveDescriptorForTaproot.ts
Original file line number Diff line number Diff line change
@@ -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 };
}
}

Expand Down
3 changes: 3 additions & 0 deletions packages/connect/src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 (
<ConfirmValueModal
account={account}
Expand All @@ -47,7 +55,7 @@ export const ConfirmXpubModal = (
confirmStepLabel={<Translation id="TR_XPUB_MATCH" />}
validateOnDevice={showXpub}
copyButtonText={<Translation id="TR_XPUB_MODAL_CLIPBOARD" />}
value={xpub}
value={xpubWithReplacedApostropheWithH ?? xpub}
displayMode={DisplayMode.PAGINATED_TEXT}
{...props}
/>
Expand Down
27 changes: 27 additions & 0 deletions packages/utils/src/convertTaprootXpub.ts
Original file line number Diff line number Diff line change
@@ -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;
};
1 change: 1 addition & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@ export * from './isFullPath';
export * from './asciiUtils';
export * from './resolveAfter';
export * from './zip';
export * from './convertTaprootXpub';
2 changes: 2 additions & 0 deletions packages/utils/src/parseElectrumUrl.ts
Original file line number Diff line number Diff line change
@@ -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])$/;

Expand Down
Loading