1
1
import * as bitcoin from 'bitcoinjs-lib' ;
2
2
import * as ecc from 'tiny-secp256k1' ;
3
- import { ECPairAPI , ECPairFactory } from 'ecpair' ;
3
+ import { ECPairAPI , ECPairFactory , ECPairInterface } from 'ecpair' ;
4
+ import { coerceToBuffer } from './helpers' ;
4
5
5
- export { ECPairInterface } from 'ecpair' ;
6
+ export { ECPairInterface } ;
6
7
7
8
export const ECPair : ECPairAPI = ECPairFactory ( ecc ) ;
8
9
@@ -16,27 +17,190 @@ const BITCOIN_NETWORKS = {
16
17
regtest : bitcoin . networks . regtest ,
17
18
} as const ;
18
19
20
+ type KeyInputArgs = { network : keyof typeof BITCOIN_NETWORKS } & (
21
+ | { privateKey : Buffer | string }
22
+ | { publicKey : Buffer | string }
23
+ ) ;
24
+
25
+ interface KeyOutput {
26
+ address : string ;
27
+ ecPair : ECPairInterface ;
28
+ }
29
+
30
+ function ecPairFromKeyInputArgs ( args : KeyInputArgs , allowXOnlyPubkey = false ) : ECPairInterface {
31
+ const network = BITCOIN_NETWORKS [ args . network ] ;
32
+ if ( 'privateKey' in args ) {
33
+ let keyBuff = coerceToBuffer ( args . privateKey ) ;
34
+ if ( keyBuff . length === 33 && keyBuff [ 32 ] === 0x01 ) {
35
+ keyBuff = keyBuff . slice ( 0 , 32 ) ; // Drop the compression byte suffix
36
+ }
37
+ return ECPair . fromPrivateKey ( keyBuff , { compressed : true , network } ) ;
38
+ } else {
39
+ let keyBuff = coerceToBuffer ( args . publicKey ) ;
40
+ if ( allowXOnlyPubkey && keyBuff . length === 32 ) {
41
+ // Allow x-only pubkeys, defined in BIP340 (no y parity byte prefix)
42
+ const X_ONLY_PUB_KEY_TIE_BREAKER = 0x02 ;
43
+ keyBuff = Buffer . concat ( [ Buffer . from ( [ X_ONLY_PUB_KEY_TIE_BREAKER ] ) , keyBuff ] ) ;
44
+ }
45
+ return ECPair . fromPublicKey ( keyBuff , { compressed : true , network } ) ;
46
+ }
47
+ }
48
+
19
49
/**
20
- * Function for creating a tweaked p2tr key-spend only address (this is recommended by BIP341)
21
- * @see https://github.com/bitcoinjs/bitcoinjs-lib/blob/424abf2376772bb57b7668bc35b29ed18879fa0a/test/integration/taproot.md
50
+ * Creates a P2PKH "Pay To Public Key Hash" address.
51
+ * `hashbytes` is the 20-byte hash160 of a single public key.
52
+ * Encoded as base58.
53
+ */
54
+ function p2pkhAddressFromKey ( args : KeyInputArgs ) : KeyOutput {
55
+ const network = BITCOIN_NETWORKS [ args . network ] ;
56
+ const ecPair = ecPairFromKeyInputArgs ( args , true ) ;
57
+
58
+ const p2pkhhResult = bitcoin . payments . p2pkh ( { pubkey : ecPair . publicKey , network } ) ;
59
+ if ( ! p2pkhhResult . address ) {
60
+ throw new Error (
61
+ `Could not create P2PKH address from pubkey ${ ecPair . publicKey . toString ( 'hex' ) } `
62
+ ) ;
63
+ }
64
+ return { ecPair, address : p2pkhhResult . address } ;
65
+ }
66
+
67
+ /**
68
+ * Creates a P2SH "Pay To Script Hash" address.
69
+ * Typically used to generate multi-signature wallets, however, this function creates a P2PKH wrapped in P2SH address.
70
+ * `hashbytes` is the 20-byte hash160 of a redeemScript script.
71
+ * Encoded as base58.
72
+ */
73
+ function p2shAddressFromKey ( args : KeyInputArgs ) : KeyOutput {
74
+ const network = BITCOIN_NETWORKS [ args . network ] ;
75
+ const ecPair = ecPairFromKeyInputArgs ( args , true ) ;
76
+
77
+ // P2SH(P2PKH) address example '3D4sXNTgnVbEWaU58pDgBD82zDkthVWazv' from https://matheo.uliege.be/bitstream/2268.2/11236/4/Master_Thesis.pdf
78
+ const p2sh_p2pkh_Result = bitcoin . payments . p2sh ( {
79
+ redeem : bitcoin . payments . p2pkh ( { pubkey : ecPair . publicKey , network } ) ,
80
+ network,
81
+ } ) ;
82
+
83
+ // P2SH(P2PK) address example '3EuJgd52Tme58nZewZa39svoDtSUgL4Mgn' from https://matheo.uliege.be/bitstream/2268.2/11236/4/Master_Thesis.pdf
84
+ // const p2sh_p2pk_Result = bitcoin.payments.p2sh({
85
+ // redeem: bitcoin.payments.p2pk({ pubkey: ecPair.publicKey, network }),
86
+ // network,
87
+ // });
88
+
89
+ // 1-of-1 multisig, not sure if valid ...
90
+ // const p2shResult1 = bitcoin.payments.p2sh({
91
+ // redeem: bitcoin.payments.p2ms({ pubkeys: [ecPair.publicKey], m: 1, network }),
92
+ // network,
93
+ // });
94
+
95
+ if ( ! p2sh_p2pkh_Result . address ) {
96
+ throw new Error (
97
+ `Could not create P2SH address from pubkey ${ ecPair . publicKey . toString ( 'hex' ) } `
98
+ ) ;
99
+ }
100
+ return { ecPair, address : p2sh_p2pkh_Result . address } ;
101
+ }
102
+
103
+ /**
104
+ * Creates a P2SH-P2WPHK "Pay To Witness Public Key Hash Wrapped In P2SH" address.
105
+ * Used to generate a segwit P2WPKH address nested in a legacy legacy P2SH address.
106
+ * Allows non-SegWit wallets to generate a SegWit transaction, and allows non-SegWit client accept SegWit transaction.
107
+ * `hashbytes` is the 20-byte hash160 of a p2wpkh witness script
108
+ * Encoded as base58.
109
+ */
110
+ function p2shp2wpkhAddressFromKey ( args : KeyInputArgs ) : KeyOutput {
111
+ const network = BITCOIN_NETWORKS [ args . network ] ;
112
+ const ecPair = ecPairFromKeyInputArgs ( args , true ) ;
113
+
114
+ const p2shResult = bitcoin . payments . p2sh ( {
115
+ redeem : bitcoin . payments . p2wpkh ( { pubkey : ecPair . publicKey , network } ) ,
116
+ network,
117
+ } ) ;
118
+ if ( ! p2shResult . address ) {
119
+ throw new Error (
120
+ `Could not create P2SH-P2WPHK address from pubkey ${ ecPair . publicKey . toString ( 'hex' ) } `
121
+ ) ;
122
+ }
123
+ return { ecPair, address : p2shResult . address } ;
124
+ }
125
+
126
+ /**
127
+ * Creates a P2SH-P2WSH "Pay To Witness Script Hash Wrapped In P2SH" address.
128
+ * Used to generate a segwit P2WSH address nested in a legacy legacy P2SH address.
129
+ * Typically used for multi-signature wallets, however, this function creates a 1-of-1 "multisig" address.
130
+ * Allows non-SegWit wallets to generate a SegWit transaction, and allows non-SegWit client accept SegWit transaction.
22
131
*/
23
- export function p2trAddressFromPublicKey (
24
- publicKey : Buffer ,
25
- network : keyof typeof BITCOIN_NETWORKS
26
- ) : string {
27
- if ( publicKey . length === 32 ) {
28
- // Defined in BIP340
29
- const X_ONLY_PUB_KEY_TIE_BREAKER = 0x02 ;
30
- publicKey = Buffer . concat ( [ Buffer . from ( [ X_ONLY_PUB_KEY_TIE_BREAKER ] ) , publicKey ] ) ;
132
+ function p2shp2wshAddressFromKeys ( args : KeyInputArgs ) : KeyOutput {
133
+ const network = BITCOIN_NETWORKS [ args . network ] ;
134
+ const ecPair = ecPairFromKeyInputArgs ( args , true ) ;
135
+
136
+ const p2shResult = bitcoin . payments . p2sh ( {
137
+ redeem : bitcoin . payments . p2wsh ( {
138
+ redeem : bitcoin . payments . p2ms ( { m : 1 , pubkeys : [ ecPair . publicKey ] , network } ) ,
139
+ network,
140
+ } ) ,
141
+ network,
142
+ } ) ;
143
+ if ( ! p2shResult . address ) {
144
+ throw new Error (
145
+ `Could not create P2SH-P2WPHK address from pubkey ${ ecPair . publicKey . toString ( 'hex' ) } `
146
+ ) ;
31
147
}
32
- const ecPair = ECPair . fromPublicKey ( publicKey , { compressed : true } ) ;
33
- const pubKeyBuffer = ecPair . publicKey ;
34
- if ( ! pubKeyBuffer ) {
35
- throw new Error ( `Could not get public key` ) ;
148
+ return { ecPair, address : p2shResult . address } ;
149
+ }
150
+
151
+ /**
152
+ * Creates a P2WPKH "Pay To Witness Public Key Hash" address.
153
+ * Used to generated standard segwit addresses.
154
+ * `hashbytes` is the 20-byte hash160 of the witness script.
155
+ * Encoded as SEGWIT_V0 / bech32.
156
+ */
157
+ function p2wpkhAddressFromKey ( args : KeyInputArgs ) : KeyOutput {
158
+ const network = BITCOIN_NETWORKS [ args . network ] ;
159
+ const ecPair = ecPairFromKeyInputArgs ( args , true ) ;
160
+
161
+ const p2wpkhResult = bitcoin . payments . p2wpkh ( { pubkey : ecPair . publicKey , network } ) ;
162
+ if ( ! p2wpkhResult . address ) {
163
+ throw new Error (
164
+ `Could not create p2wpkh address from pubkey ${ ecPair . publicKey . toString ( 'hex' ) } `
165
+ ) ;
36
166
}
167
+ return { ecPair, address : p2wpkhResult . address } ;
168
+ }
169
+
170
+ /**
171
+ * Creates a P2WSH "Pay To Witness Script Hash" address.
172
+ * Typically used to generate multi-signature segwit wallets, however, this function creates a 1-of-1 "multisig" address.
173
+ * `hashbytes` is the 32-byte sha256 of the witness script.
174
+ * Encoded as SEGWIT_V0 / bech32.
175
+ */
176
+ function p2wshAddressFromKey ( args : KeyInputArgs ) : KeyOutput {
177
+ const network = BITCOIN_NETWORKS [ args . network ] ;
178
+ const ecPair = ecPairFromKeyInputArgs ( args , true ) ;
179
+
180
+ const p2wshResult = bitcoin . payments . p2wsh ( {
181
+ redeem : bitcoin . payments . p2ms ( { m : 1 , pubkeys : [ ecPair . publicKey ] , network } ) ,
182
+ network,
183
+ } ) ;
184
+ if ( ! p2wshResult . address ) {
185
+ throw new Error (
186
+ `Could not create p2wpkh address from pubkey ${ ecPair . publicKey . toString ( 'hex' ) } `
187
+ ) ;
188
+ }
189
+ return { ecPair, address : p2wshResult . address } ;
190
+ }
191
+
192
+ /**
193
+ * Creates a P2TR "Pay To Taproot" address.
194
+ * Uses the tweaked p2tr key-spend only address encoding recommended by BIP341.
195
+ * Encoded as SEGWIT_V1 / bech32m.
196
+ * @see https://github.com/bitcoinjs/bitcoinjs-lib/blob/424abf2376772bb57b7668bc35b29ed18879fa0a/test/integration/taproot.md
197
+ */
198
+ function p2trAddressFromKey ( args : KeyInputArgs ) : KeyOutput {
199
+ const network = BITCOIN_NETWORKS [ args . network ] ;
200
+ const ecPair = ecPairFromKeyInputArgs ( args , true ) ;
37
201
38
202
// x-only pubkey (remove 1 byte y parity)
39
- const myXOnlyPubkey = pubKeyBuffer . slice ( 1 , 33 ) ;
203
+ const myXOnlyPubkey = ecPair . publicKey . slice ( 1 , 33 ) ;
40
204
const commitHash = bitcoin . crypto . taggedHash ( 'TapTweak' , myXOnlyPubkey ) ;
41
205
const tweakResult = ecc . xOnlyPointAddTweak ( myXOnlyPubkey , commitHash ) ;
42
206
if ( tweakResult === null ) {
@@ -50,30 +214,57 @@ export function p2trAddressFromPublicKey(
50
214
tweaked ,
51
215
] ) ;
52
216
53
- const address = bitcoin . address . fromOutputScript ( scriptPubkey , BITCOIN_NETWORKS [ network ] ) ;
54
- return address ;
217
+ const address = bitcoin . address . fromOutputScript ( scriptPubkey , network ) ;
218
+ return { ecPair , address } ;
55
219
}
56
220
57
- export function p2trAddressFromPrivateKey (
58
- privateKey : Buffer ,
59
- network : keyof typeof BITCOIN_NETWORKS
60
- ) : string {
61
- const ecPair = ECPair . fromPrivateKey ( privateKey , { compressed : true } ) ;
62
- if ( ! ecPair . publicKey ) {
63
- throw new Error ( `Could not get public key` ) ;
221
+ export interface VerboseKeyOutput {
222
+ address : string ;
223
+ wif : string ;
224
+ privateKey : Buffer ;
225
+ publicKey : Buffer ;
226
+ }
227
+
228
+ export function getBitcoinAddressFromKey < TVerbose extends boolean = false > (
229
+ args : KeyInputArgs & {
230
+ addressFormat : 'p2pkh' | 'p2sh' | 'p2sh-p2wpkh' | 'p2sh-p2wsh' | 'p2wpkh' | 'p2wsh' | 'p2tr' ;
231
+ verbose ?: TVerbose ;
232
+ }
233
+ ) : TVerbose extends true ? VerboseKeyOutput : string {
234
+ const keyOutput : KeyOutput = ( ( ) => {
235
+ switch ( args . addressFormat ) {
236
+ case 'p2pkh' :
237
+ return p2pkhAddressFromKey ( args ) ;
238
+ case 'p2sh' :
239
+ return p2shAddressFromKey ( args ) ;
240
+ case 'p2sh-p2wpkh' :
241
+ return p2shp2wpkhAddressFromKey ( args ) ;
242
+ case 'p2sh-p2wsh' :
243
+ return p2shp2wshAddressFromKeys ( args ) ;
244
+ case 'p2wpkh' :
245
+ return p2wpkhAddressFromKey ( args ) ;
246
+ case 'p2wsh' :
247
+ return p2wshAddressFromKey ( args ) ;
248
+ case 'p2tr' :
249
+ return p2trAddressFromKey ( args ) ;
250
+ }
251
+ throw new Error ( `Unexpected address format: ${ args . addressFormat } ` ) ;
252
+ } ) ( ) ;
253
+
254
+ if ( args . verbose ) {
255
+ const output : VerboseKeyOutput = {
256
+ address : keyOutput . address ,
257
+ wif : keyOutput . ecPair . toWIF ( ) ,
258
+ privateKey : keyOutput . ecPair . privateKey as Buffer ,
259
+ publicKey : keyOutput . ecPair . publicKey ,
260
+ } ;
261
+ return output as TVerbose extends true ? VerboseKeyOutput : string ;
262
+ } else {
263
+ return keyOutput . address as TVerbose extends true ? VerboseKeyOutput : string ;
64
264
}
65
- return p2trAddressFromPublicKey ( ecPair . publicKey , network ) ;
66
265
}
67
266
68
- export function generateRandomP2TRAccount (
69
- network : keyof typeof BITCOIN_NETWORKS
70
- ) : {
71
- address : string ;
72
- privateKey : Buffer ;
73
- } {
74
- const ecPair = ECPair . makeRandom ( { compressed : true } ) ;
75
- return {
76
- address : p2trAddressFromPublicKey ( ecPair . publicKey , network ) ,
77
- privateKey : ecPair . privateKey as Buffer ,
78
- } ;
267
+ export function privateToPublicKey ( privateKey : string | Buffer ) : Buffer {
268
+ const ecPair = ecPairFromKeyInputArgs ( { privateKey, network : 'mainnet' } ) ;
269
+ return ecPair . publicKey ;
79
270
}
0 commit comments