88 "errors"
99
1010 "github.com/btcsuite/btcd/btcec/v2"
11+ "github.com/btcsuite/btcd/btcec/v2/schnorr"
1112 "github.com/btcsuite/btcd/btcutil/psbt"
1213 "github.com/btcsuite/btcd/chaincfg"
1314 "github.com/btcsuite/btcd/txscript"
@@ -21,12 +22,26 @@ type SignTaprootParams struct {
2122 PrivateKey * btcec.PrivateKey
2223}
2324
25+ // SignTaprootMultiParams defines parameters for SignTaprootMulti method.
26+ //
27+ // NOTE: TapScriptPrivateKeys must be in reverse order relatively to public keys in locking script!!!
28+ // Ex.: Locking script pub keys order: {<pub1>, <pub2>, ..., <pubN>}, then private keys order must be: {<prN>, ... <pr2>, <pr1>}.
29+ //
30+ // INFO: Either MasterPrivateKey or TapScriptPrivateKeys must be provided.
31+ type SignTaprootMultiParams struct {
32+ SerializedPSBT []byte
33+ Inputs []int // inputs indexes.
34+ MasterPrivateKey * btcec.PrivateKey // primary key which is used to create taproot public key (not tweaked).
35+ TapScriptPrivateKeys []* btcec.PrivateKey // holds private keys needed to unlock MultiSig tapScript. Key-spend path will be used in case of empty array.
36+ }
37+
2438// signTaprootInputParams defines parameters for signTaprootInput method.
2539type signTaprootInputParams struct {
26- packet * psbt.Packet
27- input int
28- inputFetcher txscript.PrevOutputFetcher
29- privateKey * btcec.PrivateKey
40+ packet * psbt.Packet
41+ input int
42+ inputFetcher txscript.PrevOutputFetcher
43+ masterPrivateKey * btcec.PrivateKey
44+ tapScriptPrivateKeys []* btcec.PrivateKey
3045}
3146
3247// Signer provides transaction signing related logic.
@@ -63,10 +78,11 @@ func (signer *Signer) SignTaproot(params SignTaprootParams) ([]byte, error) {
6378 }
6479
6580 err = signer .signTaprootInput (signTaprootInputParams {
66- packet : packet ,
67- input : input ,
68- inputFetcher : prevOutputFetcher ,
69- privateKey : params .PrivateKey ,
81+ packet : packet ,
82+ input : input ,
83+ inputFetcher : prevOutputFetcher ,
84+ masterPrivateKey : params .PrivateKey ,
85+ tapScriptPrivateKeys : []* btcec.PrivateKey {params .PrivateKey },
7086 })
7187 if err != nil {
7288 return nil , err
@@ -82,7 +98,49 @@ func (signer *Signer) SignTaproot(params SignTaprootParams) ([]byte, error) {
8298 return w .Bytes (), nil
8399}
84100
85- // signTaprootInput signs taproot input with or without witness script.
101+ // SignTaprootMulti signs taproot inputs by provided indexes using 1+ private keys, returns updated serialized PSBT.
102+ func (signer * Signer ) SignTaprootMulti (params SignTaprootMultiParams ) ([]byte , error ) {
103+ packet , err := psbt .NewFromRawBytes (bytes .NewBuffer (params .SerializedPSBT ), false )
104+ if err != nil {
105+ return nil , err
106+ }
107+
108+ var (
109+ tx = packet .UnsignedTx
110+ prevOutputFetcherMap = make (map [wire.OutPoint ]* wire.TxOut , len (tx .TxIn ))
111+ )
112+ for idx , in := range packet .Inputs {
113+ prevOutputFetcherMap [tx .TxIn [idx ].PreviousOutPoint ] = in .WitnessUtxo
114+ }
115+
116+ var prevOutputFetcher = txscript .NewMultiPrevOutFetcher (prevOutputFetcherMap )
117+ for _ , input := range params .Inputs {
118+ if len (packet .Inputs ) <= input {
119+ return nil , errors .New ("invalid input index" )
120+ }
121+
122+ err = signer .signTaprootInput (signTaprootInputParams {
123+ packet : packet ,
124+ input : input ,
125+ inputFetcher : prevOutputFetcher ,
126+ masterPrivateKey : params .MasterPrivateKey ,
127+ tapScriptPrivateKeys : params .TapScriptPrivateKeys ,
128+ })
129+ if err != nil {
130+ return nil , err
131+ }
132+ }
133+
134+ w := bytes .NewBuffer (nil )
135+ err = packet .Serialize (w )
136+ if err != nil {
137+ return nil , err
138+ }
139+
140+ return w .Bytes (), nil
141+ }
142+
143+ // signTaprootInput signs taproot input with or without witness script with provided private keys.
86144func (signer * Signer ) signTaprootInput (params signTaprootInputParams ) error {
87145 var (
88146 input = & params .packet .Inputs [params .input ]
@@ -96,49 +154,53 @@ func (signer *Signer) signTaprootInput(params signTaprootInputParams) error {
96154
97155 if len (input .WitnessScript ) != 0 {
98156 var (
99- tapLeaf = txscript .NewBaseTapLeaf (input .WitnessScript )
100- tapScriptTree = txscript .AssembleTaprootScriptTree (tapLeaf )
101- ctrlBlock = tapScriptTree .LeafMerkleProofs [0 ].ToControlBlock (params .privateKey .PubKey ())
102- ctrlBlockBytes []byte
103- sig []byte
104- leafHash = tapLeaf .TapHash ()
157+ sig []byte
158+ tsrd * taprootSignatureRequiredData
105159 )
106160
107- ctrlBlockBytes , err = ctrlBlock . ToBytes ( )
161+ tsrd , err = recoverTaprootSignatureRequiredData ( input )
108162 if err != nil {
109163 return err
110164 }
111165
112- sig , err = txscript .RawTxInTapscriptSignature (
113- params .packet .UnsignedTx , sigHashes , params .input ,
114- value , pkScript , tapLeaf , sigHashType , params .privateKey ,
115- )
116- if err != nil {
166+ if len (params .tapScriptPrivateKeys ) == 0 {
167+ if params .masterPrivateKey == nil {
168+ return errors .New ("either master private key or tapScript private keys list was expected" )
169+ }
170+
171+ input .TaprootKeySpendSig , err = txscript .RawTxInTaprootSignature (
172+ params .packet .UnsignedTx , sigHashes , params .input , value , pkScript ,
173+ input .TaprootMerkleRoot , sigHashType , params .masterPrivateKey )
174+
117175 return err
118176 }
119177
120- if len (sig ) > 64 {
121- sig = sig [:64 ]
178+ for _ , privateKey := range params .tapScriptPrivateKeys {
179+ sig , err = txscript .RawTxInTapscriptSignature (
180+ params .packet .UnsignedTx , sigHashes , params .input ,
181+ value , pkScript , tsrd .tapLeaf , sigHashType , privateKey ,
182+ )
183+ if err != nil {
184+ return err
185+ }
186+
187+ if len (sig ) > 64 {
188+ sig = sig [:64 ]
189+ }
190+ input .TaprootScriptSpendSig = append (input .TaprootScriptSpendSig , & psbt.TaprootScriptSpendSig {
191+ XOnlyPubKey : privateKey .PubKey ().SerializeCompressed ()[1 :],
192+ LeafHash : tsrd .leafHash ,
193+ Signature : sig ,
194+ SigHash : sigHashType ,
195+ })
122196 }
123- input .TaprootScriptSpendSig = []* psbt.TaprootScriptSpendSig {{
124- XOnlyPubKey : params .privateKey .PubKey ().SerializeCompressed ()[1 :],
125- LeafHash : leafHash .CloneBytes (),
126- Signature : sig ,
127- SigHash : sigHashType ,
128- }}
129-
130- input .TaprootLeafScript = []* psbt.TaprootTapLeafScript {{
131- ControlBlock : ctrlBlockBytes ,
132- Script : tapLeaf .Script ,
133- LeafVersion : tapLeaf .LeafVersion ,
134- }}
135197
136198 return nil
137199 }
138200
139201 witness , err = txscript .TaprootWitnessSignature (
140202 params .packet .UnsignedTx , sigHashes , params .input ,
141- value , pkScript , sigHashType , params .privateKey )
203+ value , pkScript , sigHashType , params .masterPrivateKey )
142204 if err != nil {
143205 return err
144206 }
@@ -147,3 +209,56 @@ func (signer *Signer) signTaprootInput(params signTaprootInputParams) error {
147209
148210 return nil
149211}
212+
213+ type taprootSignatureRequiredData struct {
214+ ctrlBlock * txscript.ControlBlock
215+ tapLeaf txscript.TapLeaf
216+ leafHash []byte
217+ }
218+
219+ // recoverTaprootSignatureRequiredData parses all needed data from PSBT or recover from WitnessScript if not found and updates provided input with it.
220+ func recoverTaprootSignatureRequiredData (input * psbt.PInput ) (tsrd * taprootSignatureRequiredData , err error ) {
221+ if len (input .TaprootInternalKey ) == 0 {
222+ return nil , errors .New ("taproot internal key is empty" )
223+ }
224+
225+ var masterPublicKey * btcec.PublicKey
226+ masterPublicKey , err = schnorr .ParsePubKey (input .TaprootInternalKey )
227+ if err != nil {
228+ return nil , err
229+ }
230+
231+ tsrd = new (taprootSignatureRequiredData )
232+ if len (input .TaprootLeafScript ) > 0 && input .TaprootLeafScript [0 ] != nil {
233+ leafScriptData := input .TaprootLeafScript [0 ]
234+ tsrd .tapLeaf = txscript .NewTapLeaf (leafScriptData .LeafVersion , leafScriptData .Script )
235+ tsrd .ctrlBlock , err = txscript .ParseControlBlock (leafScriptData .ControlBlock )
236+ if err != nil {
237+ return nil , err
238+ }
239+ } else {
240+ tsrd .tapLeaf = txscript .NewBaseTapLeaf (input .WitnessScript )
241+ tapScriptTree := txscript .AssembleTaprootScriptTree (tsrd .tapLeaf )
242+ ctrlBlock := tapScriptTree .LeafMerkleProofs [0 ].ToControlBlock (masterPublicKey )
243+ tsrd .ctrlBlock = & ctrlBlock
244+
245+ tapLeafScript := & psbt.TaprootTapLeafScript {
246+ Script : tsrd .tapLeaf .Script ,
247+ LeafVersion : tsrd .tapLeaf .LeafVersion ,
248+ }
249+ tapLeafScript .ControlBlock , err = tsrd .ctrlBlock .ToBytes ()
250+ if err != nil {
251+ return nil , err
252+ }
253+
254+ input .TaprootLeafScript = []* psbt.TaprootTapLeafScript {tapLeafScript }
255+ }
256+ leafHash := tsrd .tapLeaf .TapHash ()
257+ tsrd .leafHash = leafHash [:]
258+
259+ if len (input .TaprootMerkleRoot ) == 0 {
260+ input .TaprootMerkleRoot = tsrd .ctrlBlock .RootHash (tsrd .tapLeaf .Script )
261+ }
262+
263+ return tsrd , nil
264+ }
0 commit comments