11import * as bitcoin from 'bitcoinjs-lib' ;
22import * as ecc from 'tiny-secp256k1' ;
3- import { ECPairAPI , ECPairFactory } from 'ecpair' ;
3+ import { ECPairAPI , ECPairFactory , ECPairInterface } from 'ecpair' ;
4+ import { coerceToBuffer } from './helpers' ;
45
5- export { ECPairInterface } from 'ecpair' ;
6+ export { ECPairInterface } ;
67
78export const ECPair : ECPairAPI = ECPairFactory ( ecc ) ;
89
@@ -16,27 +17,190 @@ const BITCOIN_NETWORKS = {
1617 regtest : bitcoin . networks . regtest ,
1718} as const ;
1819
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+
1949/**
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.
22131 */
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+ ) ;
31147 }
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+ ) ;
36166 }
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 ) ;
37201
38202 // x-only pubkey (remove 1 byte y parity)
39- const myXOnlyPubkey = pubKeyBuffer . slice ( 1 , 33 ) ;
203+ const myXOnlyPubkey = ecPair . publicKey . slice ( 1 , 33 ) ;
40204 const commitHash = bitcoin . crypto . taggedHash ( 'TapTweak' , myXOnlyPubkey ) ;
41205 const tweakResult = ecc . xOnlyPointAddTweak ( myXOnlyPubkey , commitHash ) ;
42206 if ( tweakResult === null ) {
@@ -50,30 +214,57 @@ export function p2trAddressFromPublicKey(
50214 tweaked ,
51215 ] ) ;
52216
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 } ;
55219}
56220
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 ;
64264 }
65- return p2trAddressFromPublicKey ( ecPair . publicKey , network ) ;
66265}
67266
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 ;
79270}
0 commit comments