Skip to content

Commit

Permalink
refactor: keychain
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranjamie committed Jul 5, 2023
1 parent cb0e260 commit c5d4761
Show file tree
Hide file tree
Showing 15 changed files with 269 additions and 252 deletions.
14 changes: 7 additions & 7 deletions src/app/pages/rpc-sign-bip322-message/use-sign-bip322-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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(
Expand All @@ -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) {
Expand Down
8 changes: 4 additions & 4 deletions src/app/query/bitcoin/ordinals/use-inscriptions.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -71,7 +71,7 @@ export function useTaprootInscriptionsInfiniteQuery() {
{}
);
},
[keychain, network.chain.bitcoin.network]
[account, network.chain.bitcoin.network]
);

const query = useInfiniteQuery({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ 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);

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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();
Expand All @@ -38,7 +38,7 @@ export function useTaprootAccountUtxosQuery() {
) {
const address = getTaprootAddress({
index: addressIndexCounter.getValue(),
keychain,
keychain: account?.keychain,
network: network.chain.bitcoin.network,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});

Expand Down
162 changes: 4 additions & 158 deletions src/app/store/accounts/blockchain/bitcoin/bitcoin-keychain.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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) => {
Expand All @@ -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]
);
}
Loading

0 comments on commit c5d4761

Please sign in to comment.