Skip to content

Commit

Permalink
fix(ledger): inscription sends, non-index 0, closes #4680
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranjamie committed Dec 13, 2023
1 parent c4f6260 commit aae3221
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 60 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@
"react-intersection-observer": "9.5.2",
"react-lottie": "1.2.3",
"react-redux": "8.1.3",
"react-router-dom": "6.16.0",
"react-router-dom": "6.20.1",
"react-virtuoso": "4.6.0",
"redux-persist": "6.0.0",
"rxjs": "7.8.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
import toast from 'react-hot-toast';
import { Outlet, Route, useLocation } from 'react-router-dom';

import * as btc from '@scure/btc-signer';
Expand Down Expand Up @@ -47,6 +48,7 @@ function LedgerSignBitcoinTxContainer() {
const signLedger = useSignLedgerBitcoinTx();

const inputsToSign = useLocationStateWithCache<BitcoinInputSigningConfig[]>('inputsToSign');

const allowUserToGoBack = useLocationState<boolean>('goBack');

useEffect(() => {
Expand All @@ -63,6 +65,12 @@ function LedgerSignBitcoinTxContainer() {

const [awaitingDeviceConnection, setAwaitingDeviceConnection] = useState(false);

if (!inputsToSign) {
ledgerNavigate.cancelLedgerAction();
toast.error('No input signing config defined');
return null;
}

const signTransaction = async () => {
setAwaitingDeviceConnection(true);
const bitcoinApp = await connectLedgerBitcoinApp();
Expand All @@ -73,6 +81,7 @@ function LedgerSignBitcoinTxContainer() {
setAwaitingDeviceConnection(false);
setLatestDeviceResponse(versionInfo as any);
} catch (e) {
setLatestDeviceResponse(e as any);
logger.error('Unable to get Ledger app version info', e);
}

Expand All @@ -85,11 +94,7 @@ function LedgerSignBitcoinTxContainer() {
ledgerNavigate.toAwaitingDeviceOperation({ hasApprovedOperation: false });

try {
const btcTx = await signLedger(
bitcoinApp,
unsignedTransaction.toPSBT(),
inputsToSign?.map(x => x.index)
);
const btcTx = await signLedger(bitcoinApp, unsignedTransaction.toPSBT(), inputsToSign);

if (!btcTx || !unsignedTransactionRaw) throw new Error('No tx returned');
ledgerNavigate.toAwaitingDeviceOperation({ hasApprovedOperation: true });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ export function useSendInscriptionForm() {
inscription,
recipient: values.recipient,
utxo,
backgroundLocation: { pathname: RouteUrls.Home },
},
}
);
Expand Down Expand Up @@ -122,7 +121,6 @@ export function useSendInscriptionForm() {
time,
feeRowValue,
signedTx: signedTx.extract(),
backgroundLocation: { pathname: RouteUrls.Home },
},
});
},
Expand Down
2 changes: 1 addition & 1 deletion src/app/routes/app-routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ export function AppRoutes() {

export const homePageModalRoutes = (
<>
{sendOrdinalRoutes}
{settingsRoutes}
{receiveRoutes}
{ledgerStacksTxSigningRoutes}
{ledgerBitcoinTxSigningRoutes}
{requestBitcoinKeysRoutes}
{requestStacksKeysRoutes}
{sendOrdinalRoutes}
</>
);

Expand Down
20 changes: 11 additions & 9 deletions src/app/store/accounts/blockchain/bitcoin/bitcoin.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
getAssumedZeroIndexSigningConfig,
} from '@shared/crypto/bitcoin/signer-config';
import { allSighashTypes } from '@shared/rpc/methods/sign-psbt';
import { isNumber, makeNumberRange } from '@shared/utils';
import { isNumber } from '@shared/utils';

import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
import { useWalletType } from '@app/common/use-wallet-type';
Expand Down Expand Up @@ -113,7 +113,11 @@ export function useSignLedgerBitcoinTx() {

const updateTaprootLedgerInputs = useUpdateLedgerSpecificTaprootInputPropsForAdddressIndexZero();

return async (app: AppClient, rawPsbt: Uint8Array, inputsToSign?: number[]) => {
return async (
app: AppClient,
rawPsbt: Uint8Array,
signingConfig: BitcoinInputSigningConfig[]
) => {
const fingerprint = await app.getMasterFingerprint();

// BtcSigner not compatible with Ledger. Encoded format returns more terse
Expand All @@ -124,16 +128,14 @@ export function useSignLedgerBitcoinTx() {

const btcSignerPsbtClone = btc.Transaction.fromPSBT(psbt.toBuffer());

const inputByPaymentType = (
inputsToSign ?? makeNumberRange(btcSignerPsbtClone.inputsLength)
).map(index => [
index,
const inputByPaymentType = signingConfig.map(config => [
config,
getInputPaymentType(
index,
btcSignerPsbtClone.getInput(index),
config.index,
btcSignerPsbtClone.getInput(config.index),
network.chain.bitcoin.bitcoinNetwork
),
]) as readonly [number, PaymentTypes][];
]) as readonly [BitcoinInputSigningConfig, PaymentTypes][];

//
// Taproot
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import { Psbt } from 'bitcoinjs-lib';

import {
deriveAddressIndexZeroFromAccount,
extractAddressIndexFromPath,
lookUpLedgerKeysByPath,
} from '@shared/crypto/bitcoin/bitcoin.utils';
import {
deriveNativeSegwitAccountFromRootKeychain,
getNativeSegWitPaymentFromAddressIndex,
getNativeSegwitAccountDerivationPath,
} from '@shared/crypto/bitcoin/p2wpkh-address-gen';
import { makeNumberRange, reverseBytes } from '@shared/utils';
import { BitcoinInputSigningConfig } from '@shared/crypto/bitcoin/signer-config';
import { reverseBytes } from '@shared/utils';

import { mnemonicToRootNode } from '@app/common/keychain/keychain';
import { useBitcoinClient } from '@app/store/common/api-clients.hooks';
Expand Down Expand Up @@ -126,7 +128,7 @@ export function getNativeSegwitMainnetAddressFromMnemonic(secretKey: string) {
export function useUpdateLedgerSpecificNativeSegwitUtxoHexForAdddressIndexZero() {
const bitcoinClient = useBitcoinClient();

return async (tx: Psbt, inputsToUpdate: number[] = []) => {
return async (tx: Psbt, inputSigningConfig: BitcoinInputSigningConfig[]) => {
const inputsTxHex = await Promise.all(
tx.txInputs.map(input =>
bitcoinClient.transactionsApi.getBitcoinTransactionHex(
Expand All @@ -135,31 +137,32 @@ export function useUpdateLedgerSpecificNativeSegwitUtxoHexForAdddressIndexZero()
)
)
);

const inputsToSign =
inputsToUpdate.length > 0 ? inputsToUpdate : makeNumberRange(tx.inputCount);

inputsToSign.forEach(inputIndex => {
tx.updateInput(inputIndex, {
nonWitnessUtxo: Buffer.from(inputsTxHex[inputIndex], 'hex'),
inputSigningConfig.forEach(({ index }) => {
tx.updateInput(index, {
nonWitnessUtxo: Buffer.from(inputsTxHex[index], 'hex'),
});
});
};
}

export function useUpdateLedgerSpecificNativeSegwitBip32DerivationForAdddressIndexZero() {
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
const createNativeSegwitSigner = useCurrentAccountNativeSegwitSigner();

return async (tx: Psbt, fingerprint: string, inputSigningConfig: BitcoinInputSigningConfig[]) => {
inputSigningConfig.forEach(({ index, derivationPath }) => {
const nativeSegwitSigner = createNativeSegwitSigner?.(
extractAddressIndexFromPath(derivationPath)
);

return async (tx: Psbt, fingerprint: string, inputsToUpdate: number[] = []) => {
const inputsToSign =
inputsToUpdate.length > 0 ? inputsToUpdate : makeNumberRange(tx.inputCount);
if (!nativeSegwitSigner)
throw new Error(`Unable to update input for path ${derivationPath}}`);

inputsToSign.forEach(inputIndex => {
tx.updateInput(inputIndex, {
tx.updateInput(index, {
bip32Derivation: [
{
masterFingerprint: Buffer.from(fingerprint, 'hex'),
pubkey: Buffer.from(nativeSegwitSigner.publicKey),
path: nativeSegwitSigner.derivationPath,
path: derivationPath,
},
],
});
Expand Down
31 changes: 20 additions & 11 deletions src/app/store/accounts/blockchain/bitcoin/taproot-account.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import { Psbt } from 'bitcoinjs-lib';
import { BitcoinNetworkModes } from '@shared/constants';
import {
ecdsaPublicKeyToSchnorr,
extractAddressIndexFromPath,
lookUpLedgerKeysByPath,
} from '@shared/crypto/bitcoin/bitcoin.utils';
import {
deriveTaprootAccount,
getTaprootAccountDerivationPath,
getTaprootPaymentFromAddressIndex,
} from '@shared/crypto/bitcoin/p2tr-address-gen';
import { makeNumberRange } from '@shared/utils';
import { BitcoinInputSigningConfig } from '@shared/crypto/bitcoin/signer-config';

import { selectCurrentNetwork, useCurrentNetwork } from '@app/store/networks/networks.selectors';
import { selectCurrentAccountIndex } from '@app/store/software-keys/software-key.selectors';
Expand Down Expand Up @@ -99,19 +100,27 @@ export function useCurrentAccountTaprootSigner() {
}

export function useUpdateLedgerSpecificTaprootInputPropsForAdddressIndexZero() {
const taprootSigner = useCurrentAccountTaprootIndexZeroSigner();

return async (tx: Psbt, fingerprint: string, inputsToUpdate: number[] = []) => {
const inputsToSign =
inputsToUpdate.length > 0 ? inputsToUpdate : makeNumberRange(tx.inputCount);

inputsToSign.forEach(inputIndex => {
tx.updateInput(inputIndex, {
const createTaprootSigner = useCurrentAccountTaprootSigner();

return async (
tx: Psbt,
fingerprint: string,
inputsToUpdate: BitcoinInputSigningConfig[] = []
) => {
inputsToUpdate.forEach(({ index, derivationPath }) => {
const taprootAddressIndexSigner = createTaprootSigner?.(
extractAddressIndexFromPath(derivationPath)
);

if (!taprootAddressIndexSigner)
throw new Error(`Unable to update taproot input for path ${derivationPath}}`);

tx.updateInput(index, {
tapBip32Derivation: [
{
masterFingerprint: Buffer.from(fingerprint, 'hex'),
pubkey: Buffer.from(ecdsaPublicKeyToSchnorr(taprootSigner.publicKey)),
path: taprootSigner.derivationPath,
pubkey: Buffer.from(ecdsaPublicKeyToSchnorr(taprootAddressIndexSigner.publicKey)),
path: derivationPath,
leafHashes: [],
},
],
Expand Down
30 changes: 15 additions & 15 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3335,10 +3335,10 @@
redux-thunk "^2.4.2"
reselect "^4.1.8"

"@remix-run/router@1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.9.0.tgz#9033238b41c4cbe1e961eccb3f79e2c588328cf6"
integrity sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA==
"@remix-run/router@1.13.1":
version "1.13.1"
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.13.1.tgz#07e2a8006f23a3bc898b3f317e0a58cc8076b86e"
integrity sha512-so+DHzZKsoOcoXrILB4rqDkMDy7NLMErRdOxvzvOKb507YINKUP4Di+shbTZDhSE/pBZ+vr7XGIpcOO0VLSA+Q==

"@rjsf/core@^4.2.0":
version "4.2.3"
Expand Down Expand Up @@ -15677,20 +15677,20 @@ react-remove-scroll@2.5.5:
use-callback-ref "^1.3.0"
use-sidecar "^1.1.2"

react-router-dom@6.16.0:
version "6.16.0"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.16.0.tgz#86f24658da35eb66727e75ecbb1a029e33ee39d9"
integrity sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg==
react-router-dom@6.20.1:
version "6.20.1"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.20.1.tgz#e34f8075b9304221420de3609e072bb349824984"
integrity sha512-npzfPWcxfQN35psS7rJgi/EW0Gx6EsNjfdJSAk73U/HqMEJZ2k/8puxfwHFgDQhBGmS3+sjnGbMdMSV45axPQw==
dependencies:
"@remix-run/router" "1.9.0"
react-router "6.16.0"
"@remix-run/router" "1.13.1"
react-router "6.20.1"

react-router@6.16.0:
version "6.16.0"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.16.0.tgz#abbf3d5bdc9c108c9b822a18be10ee004096fb81"
integrity sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA==
react-router@6.20.1:
version "6.20.1"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.20.1.tgz#e8cc326031d235aaeec405bb234af77cf0fe75ef"
integrity sha512-ccvLrB4QeT5DlaxSFFYi/KR8UMQ4fcD8zBcR71Zp1kaYTC5oJKYAp1cbavzGrogwxca+ubjkd7XjFZKBW8CxPA==
dependencies:
"@remix-run/router" "1.9.0"
"@remix-run/router" "1.13.1"

react-select@^5.3.2:
version "5.8.0"
Expand Down

0 comments on commit aae3221

Please sign in to comment.