-
Notifications
You must be signed in to change notification settings - Fork 127
/
Copy pathaux_leaf_signer.go
753 lines (640 loc) · 20.9 KB
/
aux_leaf_signer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
package tapchannel
import (
"bytes"
"fmt"
"sync"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/taproot-assets/address"
"github.com/lightninglabs/taproot-assets/asset"
"github.com/lightninglabs/taproot-assets/commitment"
"github.com/lightninglabs/taproot-assets/fn"
cmsg "github.com/lightninglabs/taproot-assets/tapchannelmsg"
"github.com/lightninglabs/taproot-assets/tapfreighter"
"github.com/lightninglabs/taproot-assets/tappsbt"
"github.com/lightninglabs/taproot-assets/tapscript"
"github.com/lightninglabs/taproot-assets/vm"
lfn "github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tlv"
)
// shutdownErr is used in multiple spots when exiting the sig batch processor.
var shutdownErr = fmt.Errorf("tapd is shutting down")
// VirtualPacketSigner is an interface that can be used to sign virtual packets.
type VirtualPacketSigner interface {
// SignVirtualPacket signs the virtual transaction of the given packet
// and returns the input indexes that were signed.
SignVirtualPacket(vPkt *tappsbt.VPacket,
signOpts ...tapfreighter.SignVirtualPacketOption) ([]uint32,
error)
}
// LeafSignerConfig defines the configuration for the auxiliary leaf signer.
type LeafSignerConfig struct {
// ChainParams are the chain parameters of the network the signer is
// operating on.
ChainParams *address.ChainParams
// Signer is the backing wallet that can sign virtual packets.
Signer VirtualPacketSigner
}
// AuxLeafSigner is a Taproot Asset auxiliary leaf signer that can be used to
// sign auxiliary leaves for Taproot Asset channels.
type AuxLeafSigner struct {
startOnce sync.Once
stopOnce sync.Once
cfg *LeafSignerConfig
// ContextGuard provides a wait group and main quit channel that can be
// used to create guarded contexts.
*fn.ContextGuard
}
// NewAuxLeafSigner creates a new Taproot Asset auxiliary leaf signer based on
// the passed config.
func NewAuxLeafSigner(cfg *LeafSignerConfig) *AuxLeafSigner {
return &AuxLeafSigner{
cfg: cfg,
ContextGuard: &fn.ContextGuard{
DefaultTimeout: DefaultTimeout,
Quit: make(chan struct{}),
},
}
}
// Start attempts to start a new aux leaf signer.
func (s *AuxLeafSigner) Start() error {
var startErr error
s.startOnce.Do(func() {
log.Info("Starting aux leaf signer")
})
return startErr
}
// Stop signals for a aux leaf signer to gracefully exit.
func (s *AuxLeafSigner) Stop() error {
var stopErr error
s.stopOnce.Do(func() {
log.Info("Stopping aux leaf signer")
close(s.Quit)
s.Wg.Wait()
})
return stopErr
}
// SubmitSecondLevelSigBatch takes a batch of aux sign jobs and processes them
// asynchronously.
func (s *AuxLeafSigner) SubmitSecondLevelSigBatch(
chanState lnwallet.AuxChanState, commitTx *wire.MsgTx,
jobs []lnwallet.AuxSigJob) error {
s.Wg.Add(1)
go s.processAuxSigBatch(chanState, commitTx, jobs)
return nil
}
// PackSigs takes a series of aux signatures and packs them into a single blob
// that can be sent alongside the CommitSig messages.
func PackSigs(sigBlob []lfn.Option[tlv.Blob]) lfn.Result[lfn.Option[tlv.Blob]] {
type returnType = lfn.Option[tlv.Blob]
htlcSigs := make([][]*cmsg.AssetSig, len(sigBlob))
for idx := range sigBlob {
err := lfn.MapOptionZ(
sigBlob[idx], func(sigBlob tlv.Blob) error {
assetSigs, err := cmsg.DecodeAssetSigListRecord(
sigBlob,
)
if err != nil {
return err
}
htlcSigs[idx] = assetSigs.Sigs
return nil
},
)
if err != nil {
return lfn.Err[returnType](fmt.Errorf("error "+
"decoding asset sig list record: %w", err))
}
}
commitSig := cmsg.NewCommitSig(htlcSigs)
var buf bytes.Buffer
if err := commitSig.Encode(&buf); err != nil {
return lfn.Err[returnType](fmt.Errorf("error encoding "+
"commit sig: %w", err))
}
return lfn.Ok(lfn.Some(buf.Bytes()))
}
// UnpackSigs takes a packed blob of signatures and returns the original
// signatures for each HTLC, keyed by HTLC index.
func UnpackSigs(blob lfn.Option[tlv.Blob]) lfn.Result[[]lfn.Option[tlv.Blob]] {
type returnType = []lfn.Option[tlv.Blob]
if blob.IsNone() {
return lfn.Ok[returnType](nil)
}
commitSig, err := cmsg.DecodeCommitSig(blob.UnsafeFromSome())
if err != nil {
return lfn.Err[returnType](fmt.Errorf("error decoding commit "+
"sig: %w", err))
}
htlcSigRec := commitSig.HtlcPartialSigs.Val.HtlcPartialSigs
htlcSigs := make([]lfn.Option[tlv.Blob], len(htlcSigRec))
for idx := range htlcSigRec {
htlcSigs[idx] = lfn.Some(htlcSigRec[idx].Bytes())
}
return lfn.Ok(htlcSigs)
}
// VerifySecondLevelSigs attempts to synchronously verify a batch of aux sig
// jobs.
func VerifySecondLevelSigs(chainParams *address.ChainParams,
chanState lnwallet.AuxChanState, commitTx *wire.MsgTx,
verifyJobs []lnwallet.AuxVerifyJob) error {
for idx := range verifyJobs {
verifyJob := verifyJobs[idx]
// If there is no signature blob, this isn't a custom channel.
if verifyJob.SigBlob.IsNone() {
continue
}
assetSigs, err := cmsg.DecodeAssetSigListRecord(
verifyJob.SigBlob.UnsafeFromSome(),
)
if err != nil {
return fmt.Errorf("error decoding asset sig list "+
"record: %w", err)
}
// If there is no commit blob, this isn't a custom channel.
if verifyJob.CommitBlob.IsNone() {
continue
}
com, err := cmsg.DecodeCommitment(
verifyJob.CommitBlob.UnsafeFromSome(),
)
if err != nil {
return fmt.Errorf("error decoding commitment: %w", err)
}
var (
htlcs = com.OutgoingHtlcAssets.Val.HtlcOutputs
htlcOutputs []*cmsg.AssetOutput
)
if verifyJob.Incoming {
htlcs = com.IncomingHtlcAssets.Val.HtlcOutputs
}
for outIndex := range htlcs {
if outIndex == verifyJob.HTLC.HtlcIndex {
htlcOutputs = htlcs[outIndex].Outputs
break
}
}
// If the HTLC doesn't have any asset outputs, it's not an
// asset HTLC, so we can skip it.
if len(htlcOutputs) == 0 {
continue
}
err = verifyHtlcSignature(
chainParams, chanState, commitTx,
verifyJobs[idx].KeyRing, assetSigs.Sigs, htlcOutputs,
verifyJobs[idx].BaseAuxJob,
)
if err != nil {
return fmt.Errorf("error verifying second level sig: "+
"%w", err)
}
}
return nil
}
// processAuxSigBatch processes a batch of aux sign jobs asynchronously.
//
// NOTE: This method must be called as a goroutine.
func (s *AuxLeafSigner) processAuxSigBatch(chanState lnwallet.AuxChanState,
commitTx *wire.MsgTx, sigJobs []lnwallet.AuxSigJob) {
defer s.Wg.Done()
log.Tracef("Processing %d aux sig jobs", len(sigJobs))
for idx := range sigJobs {
sigJob := sigJobs[idx]
respondErr := func(err error) {
log.Errorf("Error processing aux sig job: %v", err)
sigJob.Resp <- lnwallet.AuxSigJobResp{
Err: err,
}
}
// Check for cancel or quit signals before beginning the job.
select {
case <-sigJob.Cancel:
continue
case <-s.Quit:
respondErr(shutdownErr)
return
default:
}
// If there is no commit blob, this isn't a custom channel. We
// still need to signal the job as done though, even if we don't
// have a signature to return.
if sigJob.CommitBlob.IsNone() {
select {
case sigJob.Resp <- lnwallet.AuxSigJobResp{
HtlcIndex: sigJob.HTLC.HtlcIndex,
}:
continue
case <-sigJob.Cancel:
continue
case <-s.Quit:
respondErr(shutdownErr)
return
}
}
com, err := cmsg.DecodeCommitment(
sigJob.CommitBlob.UnsafeFromSome(),
)
if err != nil {
respondErr(fmt.Errorf("error decoding commitment: %w",
err))
return
}
var (
htlcs = com.OutgoingHtlcAssets.Val.HtlcOutputs
htlcOutputs []*cmsg.AssetOutput
)
if sigJob.Incoming {
htlcs = com.IncomingHtlcAssets.Val.HtlcOutputs
}
for outIndex := range htlcs {
if outIndex == sigJob.HTLC.HtlcIndex {
htlcOutputs = htlcs[outIndex].Outputs
break
}
}
// If the HTLC doesn't have any asset outputs, it's not an
// asset HTLC, so we can skip it.
if len(htlcOutputs) == 0 {
select {
case sigJob.Resp <- lnwallet.AuxSigJobResp{
HtlcIndex: sigJob.HTLC.HtlcIndex,
}:
continue
case <-sigJob.Cancel:
continue
case <-s.Quit:
respondErr(shutdownErr)
return
}
}
resp, err := s.generateHtlcSignature(
chanState, commitTx, htlcOutputs, sigJob.SignDesc,
sigJob.BaseAuxJob,
)
if err != nil {
respondErr(fmt.Errorf("error generating HTLC "+
"signature: %w", err))
return
}
// Success!
log.Tracef("Generated HTLC signature for HTLC with index %d",
sigJob.HTLC.HtlcIndex)
select {
case sigJob.Resp <- resp:
case <-sigJob.Cancel:
continue
case <-s.Quit:
respondErr(shutdownErr)
return
}
}
}
// verifyHtlcSignature verifies the HTLC signature in the commitment transaction
// described by the sign job.
func verifyHtlcSignature(chainParams *address.ChainParams,
chanState lnwallet.AuxChanState, commitTx *wire.MsgTx,
keyRing lnwallet.CommitmentKeyRing, sigs []*cmsg.AssetSig,
htlcOutputs []*cmsg.AssetOutput, baseJob lnwallet.BaseAuxJob) error {
// If we're validating a signature for an outgoing HTLC, then it's an
// outgoing HTLC for the remote party, so we'll need to sign it with the
// proper lock time.
var htlcTimeout fn.Option[uint32]
if !baseJob.Incoming {
htlcTimeout = fn.Some(baseJob.HTLC.Timeout)
}
vPackets, err := htlcSecondLevelPacketsFromCommit(
chainParams, chanState, commitTx, baseJob.KeyRing, htlcOutputs,
baseJob, htlcTimeout,
)
if err != nil {
return fmt.Errorf("error generating second level packets: %w",
err)
}
for idx, vPacket := range vPackets {
// This is a signature for a second-level HTLC, which always
// only has one input and one output. But there might be
// multiple asset IDs, which is why we might have multiple
// signatures. But the order of the signatures and virtual
// packets are expected to align.
vIn := vPacket.Inputs[0]
vOut := vPacket.Outputs[0]
sig := sigs[idx]
// Construct input set from the single input asset.
prevAssets := commitment.InputSet{
vIn.PrevID: vIn.Asset(),
}
newAsset := vOut.Asset
// Now that we know we're not dealing with a genesis state
// transition, we'll map our set of asset inputs and outputs to
// the 1-input 1-output virtual transaction.
virtualTx, _, err := tapscript.VirtualTx(newAsset, prevAssets)
if err != nil {
return err
}
// We are always verifying the signature of the remote party,
// which are for our commitment transaction.
const whoseCommit = lntypes.Local
htlcScript, err := lnwallet.GenTaprootHtlcScript(
baseJob.Incoming, whoseCommit, baseJob.HTLC.Timeout,
baseJob.HTLC.RHash, &keyRing,
lfn.None[txscript.TapLeaf](),
)
if err != nil {
return fmt.Errorf("error creating HTLC script to "+
"verify second level: %w", err)
}
leafToVerify := txscript.TapLeaf{
Script: htlcScript.WitnessScriptToSign(),
LeafVersion: txscript.BaseLeafVersion,
}
validator := &schnorrSigValidator{
pubKey: *keyRing.RemoteHtlcKey,
tapLeaf: lfn.Some(leafToVerify),
signMethod: input.TaprootScriptSpendSignMethod,
}
return validator.validateSchnorrSig(
virtualTx, vIn.Asset(), newAsset, uint32(idx),
txscript.SigHashType(sig.SigHashType.Val), sig.Sig.Val,
)
}
return nil
}
// applySignDescToVIn applies the sign descriptor to the virtual input. This
// entails updating all the input bip32, taproot, and witness fields with the
// information from the sign descriptor. This function returns the public key
// that should be used to verify the generated signature, and also the leaf to
// be signed.
func applySignDescToVIn(signDesc input.SignDescriptor, vIn *tappsbt.VInput,
chainParams *address.ChainParams,
tapscriptRoot []byte) (btcec.PublicKey, txscript.TapLeaf) {
leafToSign := txscript.TapLeaf{
Script: signDesc.WitnessScript,
LeafVersion: txscript.BaseLeafVersion,
}
vIn.TaprootLeafScript = []*psbt.TaprootTapLeafScript{
{
Script: leafToSign.Script,
LeafVersion: leafToSign.LeafVersion,
},
}
deriv, trDeriv := tappsbt.Bip32DerivationFromKeyDesc(
signDesc.KeyDesc, chainParams.HDCoinType,
)
vIn.Bip32Derivation = []*psbt.Bip32Derivation{deriv}
vIn.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{
trDeriv,
}
vIn.TaprootBip32Derivation[0].LeafHashes = [][]byte{
fn.ByteSlice(leafToSign.TapHash()),
}
vIn.SighashType = signDesc.HashType
vIn.TaprootMerkleRoot = tapscriptRoot
// Apply single or double tweaks if present in the sign
// descriptor. At the same time, we apply the tweaks to a copy
// of the public key, so we can validate the produced signature.
signingKey := signDesc.KeyDesc.PubKey
if len(signDesc.SingleTweak) > 0 {
key := btcwallet.PsbtKeyTypeInputSignatureTweakSingle
vIn.Unknowns = append(vIn.Unknowns, &psbt.Unknown{
Key: key,
Value: signDesc.SingleTweak,
})
signingKey = input.TweakPubKeyWithTweak(
signingKey, signDesc.SingleTweak,
)
}
if signDesc.DoubleTweak != nil {
key := btcwallet.PsbtKeyTypeInputSignatureTweakDouble
vIn.Unknowns = append(vIn.Unknowns, &psbt.Unknown{
Key: key,
Value: signDesc.DoubleTweak.Serialize(),
})
signingKey = input.DeriveRevocationPubkey(
signingKey, signDesc.DoubleTweak.PubKey(),
)
}
return *signingKey, leafToSign
}
// generateHtlcSignature generates the signature for the HTLC output in the
// commitment transaction described by the sign job.
func (s *AuxLeafSigner) generateHtlcSignature(chanState lnwallet.AuxChanState,
commitTx *wire.MsgTx, htlcOutputs []*cmsg.AssetOutput,
signDesc input.SignDescriptor,
baseJob lnwallet.BaseAuxJob) (lnwallet.AuxSigJobResp, error) {
// If we're generating a signature for an incoming HTLC, then it's an
// outgoing HTLC for the remote party, so we'll need to sign it with the
// proper lock time.
var htlcTimeout fn.Option[uint32]
if baseJob.Incoming {
htlcTimeout = fn.Some(baseJob.HTLC.Timeout)
}
vPackets, err := htlcSecondLevelPacketsFromCommit(
s.cfg.ChainParams, chanState, commitTx, baseJob.KeyRing,
htlcOutputs, baseJob, htlcTimeout,
)
if err != nil {
return lnwallet.AuxSigJobResp{}, fmt.Errorf("error generating "+
"second level packets: %w", err)
}
// We are always signing the commitment transaction of the remote party,
// which is why we set whoseCommit to remote.
const whoseCommit = lntypes.Remote
htlcScript, err := lnwallet.GenTaprootHtlcScript(
baseJob.Incoming, whoseCommit, baseJob.HTLC.Timeout,
baseJob.HTLC.RHash, &baseJob.KeyRing,
lfn.None[txscript.TapLeaf](),
)
if err != nil {
return lnwallet.AuxSigJobResp{}, fmt.Errorf("error creating "+
"HTLC script: %w", err)
}
tapscriptRoot := htlcScript.TapscriptRoot
var sigs []*cmsg.AssetSig
for _, vPacket := range vPackets {
vIn := vPacket.Inputs[0]
signingKey, leafToSign := applySignDescToVIn(
signDesc, vIn, s.cfg.ChainParams, tapscriptRoot,
)
// We can now sign this virtual packet, as we've given the
// wallet internal signer everything it needs to locate the key
// and decide how to sign. Since the signature is only one of
// two required, we can't use the default validator that would
// check the full witness. Instead, we use a custom Schnorr
// signature validator to validate the single signature we
// produced.
signed, err := s.cfg.Signer.SignVirtualPacket(
vPacket, tapfreighter.SkipInputProofVerify(),
tapfreighter.WithValidator(&schnorrSigValidator{
pubKey: signingKey,
tapLeaf: lfn.Some(leafToSign),
signMethod: input.TaprootScriptSpendSignMethod,
}),
)
if err != nil {
return lnwallet.AuxSigJobResp{}, fmt.Errorf("error "+
"signing virtual packet: %w", err)
}
if len(signed) != 1 || signed[0] != 0 {
return lnwallet.AuxSigJobResp{}, fmt.Errorf("error " +
"signing virtual packet, got no sig")
}
rawSig := vPacket.Outputs[0].Asset.Witnesses()[0].TxWitness[0]
if signDesc.HashType != txscript.SigHashDefault {
rawSig = rawSig[0:64]
}
sig, err := lnwire.NewSigFromSchnorrRawSignature(rawSig)
if err != nil {
return lnwallet.AuxSigJobResp{}, fmt.Errorf("error "+
"converting raw sig to Schnorr: %w", err)
}
sigs = append(sigs, cmsg.NewAssetSig(
vIn.PrevID.ID, sig, signDesc.HashType,
))
}
htlcSigRec := &cmsg.AssetSigListRecord{
Sigs: sigs,
}
return lnwallet.AuxSigJobResp{
SigBlob: lfn.Some(htlcSigRec.Bytes()),
HtlcIndex: baseJob.HTLC.HtlcIndex,
}, nil
}
// htlcSecondLevelPacketsFromCommit generates the HTLC second level packets from
// the commitment transaction. A bool is returned indicating if the HTLC was
// incoming or outgoing.
func htlcSecondLevelPacketsFromCommit(chainParams *address.ChainParams,
chanState lnwallet.AuxChanState, commitTx *wire.MsgTx,
keyRing lnwallet.CommitmentKeyRing, htlcOutputs []*cmsg.AssetOutput,
baseJob lnwallet.BaseAuxJob,
htlcTimeout fn.Option[uint32]) ([]*tappsbt.VPacket, error) {
packets, _, err := CreateSecondLevelHtlcPackets(
chanState, commitTx, baseJob.HTLC.Amount.ToSatoshis(),
keyRing, chainParams, htlcOutputs, htlcTimeout,
)
if err != nil {
return nil, fmt.Errorf("error creating second level HTLC "+
"packets: %w", err)
}
return packets, nil
}
// schnorrSigValidator validates a single Schnorr signature against the given
// public key.
type schnorrSigValidator struct {
pubKey btcec.PublicKey
tapLeaf lfn.Option[txscript.TapLeaf]
signMethod input.SignMethod
}
// ValidateWitnesses validates the generated witnesses of an asset transfer.
// This method explicitly expects a single signature to be present in the
// witness of each input, which must be valid for the state transition and the
// given public key. But the witness as a whole is not expected to be valid yet,
// as this might represent only a single signature of a multisig output. So the
// method name might be misleading, as the full witness is _not_ validated. But
// the interface we implement requires this method signature.
func (v *schnorrSigValidator) ValidateWitnesses(newAsset *asset.Asset,
_ []*commitment.SplitAsset, prevAssets commitment.InputSet) error {
// Now that we know we're not dealing with a genesis state
// transition, we'll map our set of asset inputs and outputs to
// the 1-input 1-output virtual transaction.
virtualTx, _, err := tapscript.VirtualTx(newAsset, prevAssets)
if err != nil {
return err
}
for idx := range newAsset.PrevWitnesses {
witness := newAsset.PrevWitnesses[idx]
prevAsset, ok := prevAssets[*witness.PrevID]
if !ok {
return fmt.Errorf("%w: no prev asset for "+
"input_prev_id=%v", vm.ErrNoInputs,
limitSpewer.Sdump(witness.PrevID))
}
var (
sigHashType = txscript.SigHashDefault
sigBytes []byte
)
switch {
case len(witness.TxWitness[0]) == 64:
sigBytes = witness.TxWitness[0]
case len(witness.TxWitness[0]) == 65:
sigBytes = witness.TxWitness[0][:64]
sigHashType = txscript.SigHashType(
witness.TxWitness[0][64],
)
default:
return fmt.Errorf("invalid signature length: len=%d",
len(witness.TxWitness[0]))
}
schnorrSig, err := lnwire.NewSigFromSchnorrRawSignature(
sigBytes,
)
if err != nil {
return err
}
return v.validateSchnorrSig(
virtualTx, prevAsset, newAsset, uint32(idx),
sigHashType, schnorrSig,
)
}
return nil
}
// validateSchnorrSig validates the given Schnorr signature against the public
// key of the validator and the sigHash of the asset transition.
func (v *schnorrSigValidator) validateSchnorrSig(virtualTx *wire.MsgTx,
prevAsset, newAsset *asset.Asset, idx uint32,
sigHashType txscript.SigHashType, sig lnwire.Sig) error {
prevOutFetcher, err := tapscript.InputPrevOutFetcher(*prevAsset)
if err != nil {
return err
}
// Update the virtual transaction input with details for the specific
// Taproot Asset input and proceed to validate its witness.
virtualTxCopy := asset.VirtualTxWithInput(
virtualTx, newAsset.LockTime, newAsset.RelativeLockTime, idx,
nil,
)
sigHashes := txscript.NewTxSigHashes(virtualTxCopy, prevOutFetcher)
var sigHash []byte
switch v.signMethod {
case input.TaprootKeySpendBIP0086SignMethod,
input.TaprootKeySpendSignMethod:
sigHash, err = txscript.CalcTaprootSignatureHash(
sigHashes, sigHashType, virtualTxCopy, 0,
prevOutFetcher,
)
if err != nil {
return err
}
case input.TaprootScriptSpendSignMethod:
mustLeaf := fmt.Errorf("must provide tapleaf for script spend")
tapLeaf, err := v.tapLeaf.UnwrapOrErr(mustLeaf)
if err != nil {
return err
}
sigHash, err = txscript.CalcTapscriptSignaturehash(
sigHashes, sigHashType, virtualTxCopy, 0,
prevOutFetcher, tapLeaf,
)
if err != nil {
return err
}
default:
return fmt.Errorf("unknown sign method: %v", v.signMethod)
}
signature, err := sig.ToSignature()
if err != nil {
return err
}
if !signature.Verify(sigHash, &v.pubKey) {
return fmt.Errorf("signature verification failed for sig %x, "+
"sighash: %x and public key %x", sig.RawBytes(),
sigHash, v.pubKey.SerializeCompressed())
}
return nil
}