refactor: keychain

This commit is contained in:
kyranjamie
2023-07-04 14:54:44 +02:00
committed by kyranjamie
parent cb0e260458
commit c5d476176a
15 changed files with 269 additions and 252 deletions

View File

@@ -23,7 +23,7 @@ import {
} from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
import {
useCurrentAccountTaprootSigner,
useTaprootCurrentAccountPrivateKeychain,
useTaprootCurrentPrivateAccount,
} from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
@@ -114,11 +114,11 @@ function useSignBip322MessageFactory({ address, signPsbt }: SignBip322MessageFac
function useSignBip322MessageTaproot() {
const createTaprootSigner = useCurrentAccountTaprootSigner();
if (!createTaprootSigner) throw new Error('No taproot signer for current account');
const currentAccountTaprootKeychain = useTaprootCurrentAccountPrivateKeychain();
if (!currentAccountTaprootKeychain) throw new Error('No keychain for current account');
const currentTaprootAccount = useTaprootCurrentPrivateAccount();
if (!currentTaprootAccount) throw new Error('No keychain for current account');
const signer = createTaprootSigner(0);
const keychain = deriveAddressIndexZeroFromAccount(currentAccountTaprootKeychain);
const keychain = deriveAddressIndexZeroFromAccount(currentTaprootAccount.keychain);
function signPsbt(psbt: bitcoin.Psbt) {
psbt.data.inputs.forEach(
@@ -137,10 +137,10 @@ function useSignBip322MessageNativeSegwit() {
const createNativeSegwitSigner = useCurrentAccountNativeSegwitSigner();
if (!createNativeSegwitSigner) throw new Error('No native segwit signer for current account');
const currentAccountNativeSegwitKeychain = useNativeSegwitCurrentAccountPrivateKeychain();
if (!currentAccountNativeSegwitKeychain) throw new Error('No keychain for current account');
const currentNativeSegwitAccount = useNativeSegwitCurrentAccountPrivateKeychain();
if (!currentNativeSegwitAccount) throw new Error('No keychain for current account');
const keychain = deriveAddressIndexZeroFromAccount(currentAccountNativeSegwitKeychain);
const keychain = deriveAddressIndexZeroFromAccount(currentNativeSegwitAccount.keychain);
const signer = createNativeSegwitSigner(0);
function signPsbt(psbt: bitcoin.Psbt) {

View File

@@ -8,7 +8,7 @@ import { ensureArray } from '@shared/utils';
import { createNumArrayOfRange } from '@app/common/utils';
import { QueryPrefixes } from '@app/query/query-prefixes';
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
import { useTaprootCurrentAccountPrivateKeychain } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';
import { useTaprootCurrentPrivateAccount } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
import { getTaprootAddress } from './utils';
@@ -52,7 +52,7 @@ async function fetchInscriptions(addresses: string | string[], offset = 0, limit
*/
export function useTaprootInscriptionsInfiniteQuery() {
const network = useCurrentNetwork();
const keychain = useTaprootCurrentAccountPrivateKeychain();
const account = useTaprootCurrentPrivateAccount();
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
const currentBitcoinAddress = nativeSegwitSigner.address;
@@ -62,7 +62,7 @@ export function useTaprootInscriptionsInfiniteQuery() {
(acc: Record<string, number>, i: number) => {
const address = getTaprootAddress({
index: i,
keychain,
keychain: account?.keychain,
network: network.chain.bitcoin.network,
});
acc[address] = i;
@@ -71,7 +71,7 @@ export function useTaprootInscriptionsInfiniteQuery() {
{}
);
},
[keychain, network.chain.bitcoin.network]
[account, network.chain.bitcoin.network]
);
const query = useInfiniteQuery({

View File

@@ -14,7 +14,7 @@ import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
export function useNextFreshTaprootAddressQuery(accIndex?: number) {
const network = useCurrentNetwork();
const currentAccountIndex = useCurrentAccountIndex();
const keychain = useTaprootAccountKeychain(accIndex ?? currentAccountIndex);
const account = useTaprootAccountKeychain(accIndex ?? currentAccountIndex);
const client = useBitcoinClient();
const [highestKnownAccountActivity, setHighestKnownAccountActivity] = useState(0);
@@ -22,12 +22,12 @@ export function useNextFreshTaprootAddressQuery(accIndex?: number) {
return useQuery(
['next-taproot-address', currentAccountIndex, network.id] as const,
async () => {
if (!keychain) throw new Error('Expected keychain to be provided');
if (!account) throw new Error('Expected keychain to be provided');
async function taprootAddressIndexActivity(index: number) {
const address = getTaprootAddress({
index,
keychain,
keychain: account?.keychain,
network: network.chain.bitcoin.network,
});
const utxos = await client.addressApi.getUtxosByAddress(address);

View File

@@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query';
import { createCounter } from '@app/common/utils/counter';
import { useCurrentAccountIndex } from '@app/store/accounts/account';
import { useTaprootCurrentAccountPrivateKeychain } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';
import { useTaprootCurrentPrivateAccount } from '@app/store/accounts/blockchain/bitcoin/taproot-account.hooks';
import { useBitcoinClient } from '@app/store/common/api-clients.hooks';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
@@ -22,7 +22,7 @@ export interface TaprootUtxo extends UtxoResponseItem {
*/
export function useTaprootAccountUtxosQuery() {
const network = useCurrentNetwork();
const keychain = useTaprootCurrentAccountPrivateKeychain();
const account = useTaprootCurrentPrivateAccount();
const client = useBitcoinClient();
const currentAccountIndex = useCurrentAccountIndex();
@@ -38,7 +38,7 @@ export function useTaprootAccountUtxosQuery() {
) {
const address = getTaprootAddress({
index: addressIndexCounter.getValue(),
keychain,
keychain: account?.keychain,
network: network.chain.bitcoin.network,
});

View File

@@ -7,13 +7,13 @@ import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
export function useZeroIndexTaprootAddress(accIndex?: number) {
const network = useCurrentNetwork();
const currentAccountIndex = useCurrentAccountIndex();
const keychain = useTaprootAccountKeychain(accIndex ?? currentAccountIndex);
const account = useTaprootAccountKeychain(accIndex ?? currentAccountIndex);
if (!keychain) throw new Error('Expected keychain to be provided');
if (!account) throw new Error('Expected keychain to be provided');
const address = getTaprootAddress({
index: 0,
keychain,
keychain: account.keychain,
network: network.chain.bitcoin.network,
});

View File

@@ -1,27 +1,10 @@
import { useCallback } from 'react';
import { createSelector } from '@reduxjs/toolkit';
import { HDKey } from '@scure/bip32';
import * as btc from '@scure/btc-signer';
import { BitcoinNetworkModes, NetworkModes } from '@shared/constants';
import { NetworkModes } from '@shared/constants';
import { getBtcSignerLibNetworkConfigByMode } from '@shared/crypto/bitcoin/bitcoin.network';
import {
deriveAddressIndexKeychainFromAccount,
deriveAddressIndexZeroFromAccount,
whenPaymentType,
} from '@shared/crypto/bitcoin/bitcoin.utils';
import {
deriveTaprootAccountFromRootKeychain,
getTaprootAddressIndexDerivationPath,
} from '@shared/crypto/bitcoin/p2tr-address-gen';
import {
deriveNativeSegWitAccountKeychain,
getNativeSegWitPaymentFromAddressIndex,
getNativeSegwitAddressIndexDerivationPath,
} from '@shared/crypto/bitcoin/p2wpkh-address-gen';
import { BitcoinAccount } from '@shared/crypto/bitcoin/bitcoin.utils';
import { mnemonicToRootNode } from '@app/common/keychain/keychain';
import { selectRootKeychain } from '@app/store/in-memory-key/in-memory-key.selectors';
import { selectCurrentKey } from '@app/store/keys/key.selectors';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
@@ -31,8 +14,8 @@ import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
// accepting an account index, to further derive a nested layer of derivation.
// This approach allow us to reuse code between both native segwit and taproot
// keychains.
function bitcoinKeychainSelectorFactory(
keychainFn: (hdkey: HDKey, network: NetworkModes) => (index: number) => HDKey,
export function bitcoinKeychainSelectorFactory(
keychainFn: (hdkey: HDKey, network: NetworkModes) => (accountIndex: number) => BitcoinAccount,
network: NetworkModes
) {
return createSelector(selectCurrentKey, selectRootKeychain, (currentKey, rootKeychain) => {
@@ -42,144 +25,7 @@ function bitcoinKeychainSelectorFactory(
});
}
export function getNativeSegwitMainnetAddressFromMnemonic(secretKey: string) {
return (accountIndex: number) => {
const rootNode = mnemonicToRootNode(secretKey);
const account = deriveNativeSegWitAccountKeychain(rootNode, 'mainnet')(accountIndex);
return getNativeSegWitPaymentFromAddressIndex(
deriveAddressIndexZeroFromAccount(account),
'mainnet'
);
};
}
export const selectMainnetNativeSegWitKeychain = bitcoinKeychainSelectorFactory(
deriveNativeSegWitAccountKeychain,
'mainnet'
);
export const selectTestnetNativeSegWitKeychain = bitcoinKeychainSelectorFactory(
deriveNativeSegWitAccountKeychain,
'testnet'
);
export const selectMainnetTaprootKeychain = bitcoinKeychainSelectorFactory(
deriveTaprootAccountFromRootKeychain,
'mainnet'
);
export const selectTestnetTaprootKeychain = bitcoinKeychainSelectorFactory(
deriveTaprootAccountFromRootKeychain,
'testnet'
);
export function useBitcoinScureLibNetworkConfig() {
const network = useCurrentNetwork();
return getBtcSignerLibNetworkConfigByMode(network.chain.bitcoin.network);
}
interface BitcoinSignerFactoryArgs {
accountIndex: number;
accountKeychain: HDKey;
paymentFn(keychain: HDKey, network: BitcoinNetworkModes): any;
network: BitcoinNetworkModes;
}
export function bitcoinSignerFactory<T extends BitcoinSignerFactoryArgs>(args: T) {
const { accountIndex, network, paymentFn, accountKeychain } = args;
return (addressIndex: number) => {
const addressIndexKeychain =
deriveAddressIndexKeychainFromAccount(accountKeychain)(addressIndex);
const payment = paymentFn(addressIndexKeychain, network) as ReturnType<T['paymentFn']>;
const publicKeychain = HDKey.fromExtendedKey(addressIndexKeychain.publicExtendedKey);
return {
network,
payment,
addressIndex,
publicKeychain,
derivationPath: whenPaymentType(payment.type)({
p2wpkh: getNativeSegwitAddressIndexDerivationPath(network, accountIndex, addressIndex),
p2tr: getTaprootAddressIndexDerivationPath(network, accountIndex, addressIndex),
'p2wpkh-p2sh': 'Not supported',
p2pkh: 'Not supported',
p2sh: 'Not supported',
}),
get address() {
if (!payment.address) throw new Error('Unable to get address from payment');
return payment.address;
},
get publicKey() {
if (!publicKeychain.publicKey) throw new Error('Unable to get publicKey from keychain');
return publicKeychain.publicKey;
},
sign(tx: btc.Transaction) {
if (!addressIndexKeychain.privateKey)
throw new Error('Unable to sign taproot transaction, no private key found');
tx.sign(addressIndexKeychain.privateKey);
},
signIndex(tx: btc.Transaction, index: number, allowedSighash?: btc.SignatureHash[]) {
if (!addressIndexKeychain.privateKey)
throw new Error('Unable to sign taproot transaction, no private key found');
tx.signIdx(addressIndexKeychain.privateKey, index, allowedSighash);
},
};
};
}
interface CreateSignersForAllNetworkTypesArgs {
mainnetKeychainFn: (accountIndex: number) => HDKey;
testnetKeychainFn: (accountIndex: number) => HDKey;
paymentFn: (keychain: HDKey, network: BitcoinNetworkModes) => unknown;
}
function createSignersForAllNetworkTypes<T extends CreateSignersForAllNetworkTypesArgs>({
mainnetKeychainFn,
testnetKeychainFn,
paymentFn,
}: T) {
return ({ accountIndex, addressIndex }: { accountIndex: number; addressIndex: number }) => {
const mainnetAccount = mainnetKeychainFn(accountIndex);
const testnetAccount = testnetKeychainFn(accountIndex);
function makeNetworkSigner(keychain: HDKey, network: BitcoinNetworkModes) {
return bitcoinSignerFactory({
accountIndex,
accountKeychain: keychain,
paymentFn: paymentFn as T['paymentFn'],
network,
})(addressIndex);
}
return {
mainnet: makeNetworkSigner(mainnetAccount, 'mainnet'),
testnet: makeNetworkSigner(testnetAccount, 'testnet'),
regtest: makeNetworkSigner(testnetAccount, 'regtest'),
signet: makeNetworkSigner(testnetAccount, 'signet'),
};
};
}
export function useMakeBitcoinNetworkSignersForPaymentType<T>(
mainnetKeychainFn: ((index: number) => HDKey) | undefined,
testnetKeychainFn: ((index: number) => HDKey) | undefined,
paymentFn: (keychain: HDKey, network: BitcoinNetworkModes) => T
) {
return useCallback(
(accountIndex: number) => {
if (!mainnetKeychainFn || !testnetKeychainFn)
throw new Error('Cannot derive addresses in non-software mode');
const zeroIndex = 0;
return createSignersForAllNetworkTypes({
mainnetKeychainFn,
testnetKeychainFn,
paymentFn,
})({ accountIndex, addressIndex: zeroIndex });
},
[mainnetKeychainFn, paymentFn, testnetKeychainFn]
);
}

View File

@@ -0,0 +1,119 @@
import { useCallback } from 'react';
import { HDKey } from '@scure/bip32';
import * as btc from '@scure/btc-signer';
import { BitcoinNetworkModes } from '@shared/constants';
import {
BitcoinAccount,
deriveAddressIndexKeychainFromAccount,
whenPaymentType,
} from '@shared/crypto/bitcoin/bitcoin.utils';
import { getTaprootAddressIndexDerivationPath } from '@shared/crypto/bitcoin/p2tr-address-gen';
import { getNativeSegwitAddressIndexDerivationPath } from '@shared/crypto/bitcoin/p2wpkh-address-gen';
interface BitcoinSignerFactoryArgs {
accountIndex: number;
accountKeychain: HDKey;
paymentFn(keychain: HDKey, network: BitcoinNetworkModes): any;
network: BitcoinNetworkModes;
}
export function bitcoinSignerFactory<T extends BitcoinSignerFactoryArgs>(args: T) {
const { accountIndex, network, paymentFn, accountKeychain } = args;
return (addressIndex: number) => {
const addressIndexKeychain =
deriveAddressIndexKeychainFromAccount(accountKeychain)(addressIndex);
const payment = paymentFn(addressIndexKeychain, network) as ReturnType<T['paymentFn']>;
const publicKeychain = HDKey.fromExtendedKey(addressIndexKeychain.publicExtendedKey);
return {
network,
payment,
addressIndex,
publicKeychain,
derivationPath: whenPaymentType(payment.type)({
p2wpkh: getNativeSegwitAddressIndexDerivationPath(network, accountIndex, addressIndex),
p2tr: getTaprootAddressIndexDerivationPath(network, accountIndex, addressIndex),
'p2wpkh-p2sh': 'Not supported',
p2pkh: 'Not supported',
p2sh: 'Not supported',
}),
get address() {
if (!payment.address) throw new Error('Unable to get address from payment');
return payment.address;
},
get publicKey() {
if (!publicKeychain.publicKey) throw new Error('Unable to get publicKey from keychain');
return publicKeychain.publicKey;
},
sign(tx: btc.Transaction) {
if (!addressIndexKeychain.privateKey)
throw new Error('Unable to sign taproot transaction, no private key found');
tx.sign(addressIndexKeychain.privateKey);
},
signIndex(tx: btc.Transaction, index: number, allowedSighash?: btc.SignatureHash[]) {
if (!addressIndexKeychain.privateKey)
throw new Error('Unable to sign taproot transaction, no private key found');
tx.signIdx(addressIndexKeychain.privateKey, index, allowedSighash);
},
};
};
}
interface CreateSignersForAllNetworkTypesArgs {
mainnetKeychainFn: (accountIndex: number) => BitcoinAccount;
testnetKeychainFn: (accountIndex: number) => BitcoinAccount;
paymentFn: (keychain: HDKey, network: BitcoinNetworkModes) => unknown;
}
function createSignersForAllNetworkTypes<T extends CreateSignersForAllNetworkTypesArgs>({
mainnetKeychainFn,
testnetKeychainFn,
paymentFn,
}: T) {
return ({ accountIndex, addressIndex }: { accountIndex: number; addressIndex: number }) => {
const mainnetAccount = mainnetKeychainFn(accountIndex);
const testnetAccount = testnetKeychainFn(accountIndex);
function makeNetworkSigner(keychain: HDKey, network: BitcoinNetworkModes) {
return bitcoinSignerFactory({
accountIndex,
accountKeychain: keychain,
paymentFn: paymentFn as T['paymentFn'],
network,
})(addressIndex);
}
return {
mainnet: makeNetworkSigner(mainnetAccount.keychain, 'mainnet'),
testnet: makeNetworkSigner(testnetAccount.keychain, 'testnet'),
regtest: makeNetworkSigner(testnetAccount.keychain, 'regtest'),
signet: makeNetworkSigner(testnetAccount.keychain, 'signet'),
};
};
}
export function useMakeBitcoinNetworkSignersForPaymentType<T>(
mainnetKeychainFn: ((index: number) => BitcoinAccount) | undefined,
testnetKeychainFn: ((index: number) => BitcoinAccount) | undefined,
paymentFn: (keychain: HDKey, network: BitcoinNetworkModes) => T
) {
return useCallback(
(accountIndex: number) => {
if (!mainnetKeychainFn || !testnetKeychainFn)
throw new Error('Cannot derive addresses in non-software mode');
const zeroIndex = 0;
return createSignersForAllNetworkTypes({
mainnetKeychainFn,
testnetKeychainFn,
paymentFn,
})({ accountIndex, addressIndex: zeroIndex });
},
[mainnetKeychainFn, paymentFn, testnetKeychainFn]
);
}

View File

@@ -1,31 +1,59 @@
import { useMemo } from 'react';
import { useSelector } from 'react-redux';
import { bitcoinNetworkModeToCoreNetworkMode } from '@shared/crypto/bitcoin/bitcoin.utils';
import { getNativeSegWitPaymentFromAddressIndex } from '@shared/crypto/bitcoin/p2wpkh-address-gen';
import { createSelector } from '@reduxjs/toolkit';
import {
bitcoinNetworkModeToCoreNetworkMode,
deriveAddressIndexZeroFromAccount,
} from '@shared/crypto/bitcoin/bitcoin.utils';
import {
deriveNativeSegwitAccount,
getNativeSegWitPaymentFromAddressIndex,
} from '@shared/crypto/bitcoin/p2wpkh-address-gen';
import { mnemonicToRootNode } from '@app/common/keychain/keychain';
import { whenNetwork } from '@app/common/utils';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
import { RootState } from '@app/store';
import { selectCurrentNetwork } from '@app/store/networks/networks.selectors';
import { useCurrentAccountIndex } from '../../account';
import {
bitcoinSignerFactory,
selectMainnetNativeSegWitKeychain,
selectTestnetNativeSegWitKeychain,
useMakeBitcoinNetworkSignersForPaymentType,
} from './bitcoin-keychain';
import { bitcoinKeychainSelectorFactory } from './bitcoin-keychain';
import { bitcoinSignerFactory, useMakeBitcoinNetworkSignersForPaymentType } from './bitcoin-signer';
export function getNativeSegwitMainnetAddressFromMnemonic(secretKey: string) {
return (accountIndex: number) => {
const rootNode = mnemonicToRootNode(secretKey);
const account = deriveNativeSegwitAccount(rootNode, 'mainnet')(accountIndex);
return getNativeSegWitPaymentFromAddressIndex(
deriveAddressIndexZeroFromAccount(account.keychain),
'mainnet'
);
};
}
const selectMainnetNativeSegwitAccount = bitcoinKeychainSelectorFactory(
deriveNativeSegwitAccount,
'mainnet'
);
const selectTestnetNativeSegwitAccount = bitcoinKeychainSelectorFactory(
deriveNativeSegwitAccount,
'testnet'
);
const selectNativeSegwitActiveNetworkAccountPrivateKeychain = createSelector(
(state: RootState) => state,
selectCurrentNetwork,
(rootState, network) =>
whenNetwork(bitcoinNetworkModeToCoreNetworkMode(network.chain.bitcoin.network))({
mainnet: selectMainnetNativeSegwitAccount(rootState),
testnet: selectTestnetNativeSegwitAccount(rootState),
})
);
function useNativeSegwitActiveNetworkAccountPrivateKeychain() {
const network = useCurrentNetwork();
const selector = useMemo(
() =>
whenNetwork(bitcoinNetworkModeToCoreNetworkMode(network.chain.bitcoin.network))({
mainnet: selectMainnetNativeSegWitKeychain,
testnet: selectTestnetNativeSegWitKeychain,
}),
[network.chain.bitcoin.network]
);
return useSelector(selector);
return useSelector(selectNativeSegwitActiveNetworkAccountPrivateKeychain);
}
export function useNativeSegwitCurrentAccountPrivateKeychain() {
@@ -35,8 +63,8 @@ export function useNativeSegwitCurrentAccountPrivateKeychain() {
}
export function useNativeSegwitNetworkSigners() {
const mainnetKeychainFn = useSelector(selectMainnetNativeSegWitKeychain);
const testnetKeychainFn = useSelector(selectTestnetNativeSegWitKeychain);
const mainnetKeychainFn = useSelector(selectMainnetNativeSegwitAccount);
const testnetKeychainFn = useSelector(selectTestnetNativeSegwitAccount);
return useMakeBitcoinNetworkSignersForPaymentType(
mainnetKeychainFn,
@@ -46,18 +74,17 @@ export function useNativeSegwitNetworkSigners() {
}
function useNativeSegwitSigner(accountIndex: number) {
const network = useCurrentNetwork();
const accountKeychain = useNativeSegwitActiveNetworkAccountPrivateKeychain()?.(accountIndex);
const account = useNativeSegwitActiveNetworkAccountPrivateKeychain()?.(accountIndex);
return useMemo(() => {
if (!accountKeychain) return;
if (!account) return;
return bitcoinSignerFactory({
accountIndex,
accountKeychain,
accountKeychain: account.keychain,
paymentFn: getNativeSegWitPaymentFromAddressIndex,
network: network.chain.bitcoin.network,
network: account.network,
});
}, [accountIndex, accountKeychain, network.chain.bitcoin.network]);
}, [accountIndex, account]);
}
export function useCurrentAccountNativeSegwitSigner() {

View File

@@ -1,47 +1,56 @@
import { useMemo } from 'react';
import { useSelector } from 'react-redux';
import { createSelector } from '@reduxjs/toolkit';
import { BitcoinNetworkModes } from '@shared/constants';
import { bitcoinNetworkModeToCoreNetworkMode } from '@shared/crypto/bitcoin/bitcoin.utils';
import { getTaprootPaymentFromAddressIndex } from '@shared/crypto/bitcoin/p2tr-address-gen';
import {
deriveTaprootAccount,
getTaprootPaymentFromAddressIndex,
} from '@shared/crypto/bitcoin/p2tr-address-gen';
import { whenNetwork } from '@app/common/utils';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
import { RootState } from '@app/store';
import { selectCurrentAccountIndex } from '@app/store/keys/key.selectors';
import { selectCurrentNetwork, useCurrentNetwork } from '@app/store/networks/networks.selectors';
import { useCurrentAccountIndex } from '../../account';
import {
bitcoinSignerFactory,
selectMainnetTaprootKeychain,
selectTestnetTaprootKeychain,
useMakeBitcoinNetworkSignersForPaymentType,
} from './bitcoin-keychain';
import { bitcoinKeychainSelectorFactory } from './bitcoin-keychain';
import { bitcoinSignerFactory, useMakeBitcoinNetworkSignersForPaymentType } from './bitcoin-signer';
function useTaprootActiveNetworkAccountPrivateKeychain() {
const network = useCurrentNetwork();
return useSelector(
const selectMainnetTaprootAccount = bitcoinKeychainSelectorFactory(deriveTaprootAccount, 'mainnet');
const selectTestnetTaprootAccount = bitcoinKeychainSelectorFactory(deriveTaprootAccount, 'testnet');
const selectTaprootActiveNetworkAccountPrivateKeychain = createSelector(
(state: RootState) => state,
selectCurrentNetwork,
(rootState, network) =>
whenNetwork(bitcoinNetworkModeToCoreNetworkMode(network.chain.bitcoin.network))({
mainnet: selectMainnetTaprootKeychain,
testnet: selectTestnetTaprootKeychain,
mainnet: selectMainnetTaprootAccount(rootState),
testnet: selectTestnetTaprootAccount(rootState),
})
);
}
);
export function useTaprootCurrentAccountPrivateKeychain() {
const currentAccountIndex = useCurrentAccountIndex();
return useTaprootAccountKeychain(currentAccountIndex);
}
const selectCurrentTaprootAccountKeychain = createSelector(
selectTaprootActiveNetworkAccountPrivateKeychain,
selectCurrentAccountIndex,
(taprootKeychain, accountIndex) => taprootKeychain?.(accountIndex)
);
export function useTaprootAccountKeychain(accountIndex: number) {
const accountKeychain = useTaprootActiveNetworkAccountPrivateKeychain();
return useMemo(() => {
if (!accountKeychain) return; // TODO: Revisit this return early
return accountKeychain(accountIndex);
}, [accountIndex, accountKeychain]);
const taprootKeychain = useSelector(selectTaprootActiveNetworkAccountPrivateKeychain);
return useMemo(() => taprootKeychain?.(accountIndex), [taprootKeychain, accountIndex]);
}
export function useTaprootCurrentPrivateAccount() {
return useSelector(selectCurrentTaprootAccountKeychain);
}
export function useTaprootNetworkSigners() {
const mainnetKeychainFn = useSelector(selectMainnetTaprootKeychain);
const testnetKeychainFn = useSelector(selectTestnetTaprootKeychain);
const mainnetKeychainFn = useSelector(selectMainnetTaprootAccount);
const testnetKeychainFn = useSelector(selectTestnetTaprootAccount);
return useMakeBitcoinNetworkSignersForPaymentType(
mainnetKeychainFn,
@@ -51,17 +60,17 @@ export function useTaprootNetworkSigners() {
}
function useTaprootSigner(accountIndex: number, network: BitcoinNetworkModes) {
const accountKeychain = useTaprootAccountKeychain(accountIndex);
const account = useTaprootAccountKeychain(accountIndex);
return useMemo(() => {
if (!accountKeychain) return; // TODO: Revisit this return early
if (!account) return; // TODO: Revisit this return early
return bitcoinSignerFactory({
accountIndex,
accountKeychain,
accountKeychain: account.keychain,
paymentFn: getTaprootPaymentFromAddressIndex,
network,
});
}, [accountIndex, accountKeychain, network]);
}, [account, accountIndex, network]);
}
export function useCurrentAccountTaprootIndexZeroSigner() {

View File

@@ -11,7 +11,7 @@ import { StacksClient } from '@app/query/stacks/stacks-client';
import { AppThunk } from '@app/store';
import { initalizeWalletSession } from '@app/store/session-restore';
import { getNativeSegwitMainnetAddressFromMnemonic } from '../accounts/blockchain/bitcoin/bitcoin-keychain';
import { getNativeSegwitMainnetAddressFromMnemonic } from '../accounts/blockchain/bitcoin/native-segwit-account.hooks';
import { getStacksAddressByIndex } from '../accounts/blockchain/stacks/stacks-keychain';
import { stxChainSlice } from '../chains/stx-chain.slice';
import { selectDefaultWalletKey } from '../in-memory-key/in-memory-key.selectors';

View File

@@ -48,7 +48,7 @@ export const selectAppRequestedNetworkId = createSelector(selectNetworks, networ
return findMatchingNetworkKey({ coreApiUrl, networkChainId, networks });
});
const selectCurrentNetwork = createSelector(
export const selectCurrentNetwork = createSelector(
selectNetworks,
selectCurrentNetworkId,
selectAppRequestedNetworkId,

View File

@@ -10,6 +10,13 @@ import { logger } from '@shared/logger';
import { DerivationPathDepth } from '../derivation-path.utils';
import { BtcSignerNetwork, getBtcSignerLibNetworkConfigByMode } from './bitcoin.network';
export interface BitcoinAccount {
derivationPath: string;
keychain: HDKey;
accountIndex: number;
network: BitcoinNetworkModes;
}
const bitcoinNetworkToCoreNetworkMap: Record<BitcoinNetworkModes, NetworkModes> = {
mainnet: 'mainnet',
testnet: 'testnet',

View File

@@ -3,10 +3,7 @@ import { mnemonicToSeedSync } from '@scure/bip39';
import { SECRET_KEY } from '@tests-legacy/mocks';
import { deriveAddressIndexKeychainFromAccount } from './bitcoin.utils';
import {
deriveTaprootAccountFromRootKeychain,
getTaprootPaymentFromAddressIndex,
} from './p2tr-address-gen';
import { deriveTaprootAccount, getTaprootPaymentFromAddressIndex } from './p2tr-address-gen';
// Source:
// generated in Sparrow with same secret key used in tests
@@ -23,13 +20,13 @@ describe('taproot address gen', () => {
test.each(addresses)('should generate taproot addresses', address => {
const keychain = HDKey.fromMasterSeed(mnemonicToSeedSync(SECRET_KEY));
const index = addresses.indexOf(address);
const accountZeroKeychain = deriveTaprootAccountFromRootKeychain(keychain, 'testnet')(0);
const accountZero = deriveTaprootAccount(keychain, 'testnet')(0);
const addressIndexDetails = getTaprootPaymentFromAddressIndex(
deriveAddressIndexKeychainFromAccount(accountZeroKeychain)(index),
deriveAddressIndexKeychainFromAccount(accountZero.keychain)(index),
'testnet'
);
if (!accountZeroKeychain.privateKey) throw new Error('No private key found');
if (!accountZero.keychain.privateKey) throw new Error('No private key found');
expect(addressIndexDetails.address).toEqual(address);
});

View File

@@ -5,7 +5,11 @@ import { BitcoinNetworkModes } from '@shared/constants';
import { DerivationPathDepth } from '../derivation-path.utils';
import { getBtcSignerLibNetworkConfigByMode } from './bitcoin.network';
import { ecdsaPublicKeyToSchnorr, getBitcoinCoinTypeIndexByNetwork } from './bitcoin.utils';
import {
BitcoinAccount,
ecdsaPublicKeyToSchnorr,
getBitcoinCoinTypeIndexByNetwork,
} from './bitcoin.utils';
function getTaprootAccountDerivationPath(network: BitcoinNetworkModes, accountIndex: number) {
return `m/86'/${getBitcoinCoinTypeIndexByNetwork(network)}'/${accountIndex}'`;
@@ -19,14 +23,16 @@ export function getTaprootAddressIndexDerivationPath(
return getTaprootAccountDerivationPath(network, accountIndex) + `/0/${addressIndex}`;
}
export function deriveTaprootAccountFromRootKeychain(
keychain: HDKey,
network: BitcoinNetworkModes
) {
export function deriveTaprootAccount(keychain: HDKey, network: BitcoinNetworkModes) {
if (keychain.depth !== DerivationPathDepth.Root)
throw new Error('Keychain passed is not an account');
return (index: number) => keychain.derive(getTaprootAccountDerivationPath(network, index));
return (accountIndex: number): BitcoinAccount => ({
network,
accountIndex,
derivationPath: getTaprootAccountDerivationPath(network, accountIndex),
keychain: keychain.derive(getTaprootAccountDerivationPath(network, accountIndex)),
});
}
export function getTaprootPayment(publicKey: Uint8Array, network: BitcoinNetworkModes) {

View File

@@ -6,6 +6,7 @@ import { BitcoinNetworkModes, NetworkModes } from '@shared/constants';
import { DerivationPathDepth } from '../derivation-path.utils';
import { getBtcSignerLibNetworkConfigByMode } from './bitcoin.network';
import {
BitcoinAccount,
deriveAddressIndexZeroFromAccount,
getBitcoinCoinTypeIndexByNetwork,
} from './bitcoin.utils';
@@ -22,9 +23,14 @@ export function getNativeSegwitAddressIndexDerivationPath(
return getNativeSegwitAccountDerivationPath(network, accountIndex) + `/0/${addressIndex}`;
}
export function deriveNativeSegWitAccountKeychain(keychain: HDKey, network: NetworkModes) {
export function deriveNativeSegwitAccount(keychain: HDKey, network: NetworkModes) {
if (keychain.depth !== DerivationPathDepth.Root) throw new Error('Keychain passed is not a root');
return (index: number) => keychain.derive(getNativeSegwitAccountDerivationPath(network, index));
return (accountIndex: number): BitcoinAccount => ({
network,
accountIndex,
derivationPath: getNativeSegwitAccountDerivationPath(network, accountIndex),
keychain: keychain.derive(getNativeSegwitAccountDerivationPath(network, accountIndex)),
});
}
export function getNativeSegWitPaymentFromAddressIndex(