@@ -2,13 +2,14 @@ import { hexToBytes } from '@noble/hashes/utils';
2
2
import { HDKey , Versions } from '@scure/bip32' ;
3
3
import { mnemonicToSeedSync } from '@scure/bip39' ;
4
4
import * as btc from '@scure/btc-signer' ;
5
+ import { TransactionInput , TransactionOutput } from '@scure/btc-signer/psbt' ;
5
6
6
7
import { DerivationPathDepth , extractAccountIndexFromPath } from '@leather-wallet/crypto' ;
7
8
import { BitcoinNetworkModes , NetworkModes } from '@leather-wallet/models' ;
8
9
import type { PaymentTypes } from '@leather-wallet/rpc' ;
9
- import { defaultWalletKeyId , whenNetwork } from '@leather-wallet/utils' ;
10
+ import { defaultWalletKeyId , isDefined , whenNetwork } from '@leather-wallet/utils' ;
10
11
11
- import { BtcSignerNetwork } from './bitcoin.network' ;
12
+ import { BtcSignerNetwork , getBtcSignerLibNetworkConfigByMode } from './bitcoin.network' ;
12
13
import { getTaprootPayment } from './p2tr-address-gen' ;
13
14
14
15
export interface BitcoinAccount {
@@ -18,8 +19,24 @@ export interface BitcoinAccount {
18
19
accountIndex : number ;
19
20
network : BitcoinNetworkModes ;
20
21
}
22
+ export function initBitcoinAccount ( derivationPath : string , policy : string ) : BitcoinAccount {
23
+ const xpub = extractExtendedPublicKeyFromPolicy ( policy ) ;
24
+ const network = inferNetworkFromPath ( derivationPath ) ;
25
+ return {
26
+ keychain : HDKey . fromExtendedKey ( xpub , getHdKeyVersionsFromNetwork ( network ) ) ,
27
+ network,
28
+ derivationPath,
29
+ type : inferPaymentTypeFromPath ( derivationPath ) ,
30
+ accountIndex : extractAccountIndexFromPath ( derivationPath ) ,
31
+ } ;
32
+ }
21
33
22
- const bitcoinNetworkToCoreNetworkMap : Record < BitcoinNetworkModes , NetworkModes > = {
34
+ /**
35
+ * Represents a map of `BitcoinNetworkModes` to `NetworkModes`. While Bitcoin
36
+ * has a number of networks, its often only necessary to consider the higher
37
+ * level concepts of mainnet and testnet
38
+ */
39
+ export const bitcoinNetworkToCoreNetworkMap : Record < BitcoinNetworkModes , NetworkModes > = {
23
40
mainnet : 'mainnet' ,
24
41
testnet : 'testnet' ,
25
42
regtest : 'testnet' ,
@@ -29,7 +46,13 @@ export function bitcoinNetworkModeToCoreNetworkMode(mode: BitcoinNetworkModes) {
29
46
return bitcoinNetworkToCoreNetworkMap [ mode ] ;
30
47
}
31
48
32
- const coinTypeMap : Record < NetworkModes , 0 | 1 > = {
49
+ /**
50
+ * Map representing the "Coin Type" section of a derivation path.
51
+ * Consider example below, Coin type is one, thus testnet
52
+ * @example
53
+ * `m/86'/1'/0'/0/0`
54
+ */
55
+ export const coinTypeMap : Record < NetworkModes , 0 | 1 > = {
33
56
mainnet : 0 ,
34
57
testnet : 1 ,
35
58
} ;
@@ -49,7 +72,7 @@ export function deriveAddressIndexZeroFromAccount(keychain: HDKey) {
49
72
return deriveAddressIndexKeychainFromAccount ( keychain ) ( 0 ) ;
50
73
}
51
74
52
- const ecdsaPublicKeyLength = 33 ;
75
+ export const ecdsaPublicKeyLength = 33 ;
53
76
54
77
export function ecdsaPublicKeyToSchnorr ( pubKey : Uint8Array ) {
55
78
if ( pubKey . byteLength !== ecdsaPublicKeyLength ) throw new Error ( 'Invalid public key length' ) ;
@@ -94,48 +117,61 @@ export function getAddressFromOutScript(script: Uint8Array, bitcoinNetwork: BtcS
94
117
} ) ;
95
118
}
96
119
97
- type BtcSignerLibPaymentTypeIdentifers = 'wpkh' | 'wsh' | 'tr' | 'pkh' | 'sh' ;
120
+ /**
121
+ * Payment type identifiers, as described by `@scure/btc-signer` library
122
+ */
123
+ export type BtcSignerLibPaymentTypeIdentifers = 'wpkh' | 'wsh' | 'tr' | 'pkh' | 'sh' ;
98
124
99
- const paymentTypeMap : Record < BtcSignerLibPaymentTypeIdentifers , PaymentTypes > = {
125
+ export const paymentTypeMap : Record < BtcSignerLibPaymentTypeIdentifers , PaymentTypes > = {
100
126
wpkh : 'p2wpkh' ,
101
127
wsh : 'p2wpkh-p2sh' ,
102
128
tr : 'p2tr' ,
103
129
pkh : 'p2pkh' ,
104
130
sh : 'p2sh' ,
105
131
} ;
106
132
107
- function btcSignerLibPaymentTypeToPaymentTypeMap ( payment : BtcSignerLibPaymentTypeIdentifers ) {
133
+ export function btcSignerLibPaymentTypeToPaymentTypeMap (
134
+ payment : BtcSignerLibPaymentTypeIdentifers
135
+ ) {
108
136
return paymentTypeMap [ payment ] ;
109
137
}
110
138
111
- function isBtcSignerLibPaymentType ( payment : string ) : payment is BtcSignerLibPaymentTypeIdentifers {
139
+ export function isBtcSignerLibPaymentType (
140
+ payment : string
141
+ ) : payment is BtcSignerLibPaymentTypeIdentifers {
112
142
return payment in paymentTypeMap ;
113
143
}
114
144
115
- function parseKnownPaymentType ( payment : BtcSignerLibPaymentTypeIdentifers | PaymentTypes ) {
145
+ export function parseKnownPaymentType ( payment : BtcSignerLibPaymentTypeIdentifers | PaymentTypes ) {
116
146
return isBtcSignerLibPaymentType ( payment )
117
147
? btcSignerLibPaymentTypeToPaymentTypeMap ( payment )
118
148
: payment ;
119
149
}
120
150
121
- type PaymentTypeMap < T > = Record < PaymentTypes , T > ;
151
+ export type PaymentTypeMap < T > = Record < PaymentTypes , T > ;
122
152
export function whenPaymentType ( mode : PaymentTypes | BtcSignerLibPaymentTypeIdentifers ) {
123
153
return < T extends unknown > ( paymentMap : PaymentTypeMap < T > ) : T =>
124
154
paymentMap [ parseKnownPaymentType ( mode ) ] ;
125
155
}
126
156
127
- function inferPaymentTypeFromPath ( path : string ) : PaymentTypes {
157
+ /**
158
+ * Infers the Bitcoin payment type from the derivation path.
159
+ * Below we see path has 86 in it, per convention, this refers to taproot payments
160
+ * @example
161
+ * `m/86'/1'/0'/0/0`
162
+ */
163
+ export function inferPaymentTypeFromPath ( path : string ) : PaymentTypes {
128
164
if ( path . startsWith ( 'm/84' ) ) return 'p2wpkh' ;
129
165
if ( path . startsWith ( 'm/86' ) ) return 'p2tr' ;
130
166
if ( path . startsWith ( 'm/44' ) ) return 'p2pkh' ;
131
167
throw new Error ( `Unable to infer payment type from path=${ path } ` ) ;
132
168
}
133
169
134
- function inferNetworkFromPath ( path : string ) : NetworkModes {
170
+ export function inferNetworkFromPath ( path : string ) : NetworkModes {
135
171
return path . split ( '/' ) [ 2 ] . startsWith ( '0' ) ? 'mainnet' : 'testnet' ;
136
172
}
137
173
138
- function extractExtendedPublicKeyFromPolicy ( policy : string ) {
174
+ export function extractExtendedPublicKeyFromPolicy ( policy : string ) {
139
175
return policy . split ( ']' ) [ 1 ] ;
140
176
}
141
177
@@ -155,6 +191,30 @@ export function getHdKeyVersionsFromNetwork(network: NetworkModes) {
155
191
} ) ;
156
192
}
157
193
194
+ export function getBitcoinInputAddress ( input : TransactionInput , bitcoinNetwork : BtcSignerNetwork ) {
195
+ if ( isDefined ( input . witnessUtxo ) )
196
+ return getAddressFromOutScript ( input . witnessUtxo . script , bitcoinNetwork ) ;
197
+ if ( isDefined ( input . nonWitnessUtxo ) && isDefined ( input . index ) )
198
+ return getAddressFromOutScript (
199
+ input . nonWitnessUtxo . outputs [ input . index ] ?. script ,
200
+ bitcoinNetwork
201
+ ) ;
202
+ return '' ;
203
+ }
204
+
205
+ export function getInputPaymentType (
206
+ input : TransactionInput ,
207
+ network : BitcoinNetworkModes
208
+ ) : PaymentTypes {
209
+ const address = getBitcoinInputAddress ( input , getBtcSignerLibNetworkConfigByMode ( network ) ) ;
210
+ if ( address === '' ) throw new Error ( 'Input address cannot be empty' ) ;
211
+ if ( address . startsWith ( 'bc1p' ) || address . startsWith ( 'tb1p' ) || address . startsWith ( 'bcrt1p' ) )
212
+ return 'p2tr' ;
213
+ if ( address . startsWith ( 'bc1q' ) || address . startsWith ( 'tb1q' ) || address . startsWith ( 'bcrt1q' ) )
214
+ return 'p2wpkh' ;
215
+ throw new Error ( 'Unable to infer payment type from input address' ) ;
216
+ }
217
+
158
218
// Ledger wallets are keyed by their derivation path. To reuse the look up logic
159
219
// between payment types, this factory fn accepts a fn that generates the path
160
220
export function lookUpLedgerKeysByPath (
@@ -173,19 +233,7 @@ export function lookUpLedgerKeysByPath(
173
233
} ;
174
234
}
175
235
176
- function initBitcoinAccount ( derivationPath : string , policy : string ) : BitcoinAccount {
177
- const xpub = extractExtendedPublicKeyFromPolicy ( policy ) ;
178
- const network = inferNetworkFromPath ( derivationPath ) ;
179
- return {
180
- keychain : HDKey . fromExtendedKey ( xpub , getHdKeyVersionsFromNetwork ( network ) ) ,
181
- network,
182
- derivationPath,
183
- type : inferPaymentTypeFromPath ( derivationPath ) ,
184
- accountIndex : extractAccountIndexFromPath ( derivationPath ) ,
185
- } ;
186
- }
187
-
188
- interface GetTaprootAddressArgs {
236
+ export interface GetTaprootAddressArgs {
189
237
index : number ;
190
238
keychain ?: HDKey ;
191
239
network : BitcoinNetworkModes ;
@@ -213,3 +261,17 @@ export function mnemonicToRootNode(secretKey: string) {
213
261
const seed = mnemonicToSeedSync ( secretKey ) ;
214
262
return HDKey . fromMasterSeed ( seed ) ;
215
263
}
264
+
265
+ export function getPsbtTxInputs ( psbtTx : btc . Transaction ) : TransactionInput [ ] {
266
+ const inputsLength = psbtTx . inputsLength ;
267
+ const inputs : TransactionInput [ ] = [ ] ;
268
+ for ( let i = 0 ; i < inputsLength ; i ++ ) inputs . push ( psbtTx . getInput ( i ) ) ;
269
+ return inputs ;
270
+ }
271
+
272
+ export function getPsbtTxOutputs ( psbtTx : btc . Transaction ) : TransactionOutput [ ] {
273
+ const outputsLength = psbtTx . outputsLength ;
274
+ const outputs : TransactionOutput [ ] = [ ] ;
275
+ for ( let i = 0 ; i < outputsLength ; i ++ ) outputs . push ( psbtTx . getOutput ( i ) ) ;
276
+ return outputs ;
277
+ }
0 commit comments