mirror of
https://github.com/zhigang1992/wallet.git
synced 2026-01-12 22:53:27 +08:00
refactor: keychain
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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]
|
||||
);
|
||||
}
|
||||
|
||||
119
src/app/store/accounts/blockchain/bitcoin/bitcoin-signer.ts
Normal file
119
src/app/store/accounts/blockchain/bitcoin/bitcoin-signer.ts
Normal 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]
|
||||
);
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user