Skip to content

Commit 6014e8b

Browse files
authored
[Server] Optmization of embedded wallet and forfeit tx validation (#453)
* Cache forfeit addr at unlock * Move forfeit txs sigs verification at round finalization * Fixes * Add latency logs * Make forfeit txs sigs validation concurrent
1 parent 50c22e7 commit 6014e8b

File tree

6 files changed

+238
-233
lines changed

6 files changed

+238
-233
lines changed

server/internal/core/application/covenant.go

+53
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"encoding/hex"
77
"fmt"
8+
"runtime"
89
"sync"
910
"time"
1011

@@ -707,6 +708,12 @@ func (s *covenantService) finalizeRound() {
707708
return
708709
}
709710

711+
if err := s.verifyForfeitTxsSigs(forfeitTxs); err != nil {
712+
changes = round.Fail(err)
713+
log.WithError(err).Warn("failed to validate forfeit txs")
714+
return
715+
}
716+
710717
log.Debugf("signing round transaction %s\n", round.Id)
711718

712719
boardingInputs := make([]int, 0)
@@ -1218,6 +1225,52 @@ func (s *covenantService) extractVtxosScripts(vtxos []domain.Vtxo) ([]string, er
12181225
return scripts, nil
12191226
}
12201227

1228+
func (s *covenantService) verifyForfeitTxsSigs(txs []string) error {
1229+
nbWorkers := runtime.NumCPU()
1230+
jobs := make(chan string, len(txs))
1231+
errChan := make(chan error, 1)
1232+
wg := sync.WaitGroup{}
1233+
wg.Add(nbWorkers)
1234+
1235+
for i := 0; i < nbWorkers; i++ {
1236+
go func() {
1237+
defer wg.Done()
1238+
1239+
for tx := range jobs {
1240+
valid, txid, err := s.builder.VerifyTapscriptPartialSigs(tx)
1241+
if err != nil {
1242+
errChan <- fmt.Errorf("failed to validate forfeit tx %s: %s", txid, err)
1243+
return
1244+
}
1245+
1246+
if !valid {
1247+
errChan <- fmt.Errorf("invalid signature for forfeit tx %s", txid)
1248+
return
1249+
}
1250+
}
1251+
}()
1252+
}
1253+
1254+
for _, tx := range txs {
1255+
select {
1256+
case err := <-errChan:
1257+
return err
1258+
default:
1259+
jobs <- tx
1260+
}
1261+
}
1262+
close(jobs)
1263+
wg.Wait()
1264+
1265+
select {
1266+
case err := <-errChan:
1267+
return err
1268+
default:
1269+
close(errChan)
1270+
return nil
1271+
}
1272+
}
1273+
12211274
func (s *covenantService) saveEvents(
12221275
ctx context.Context, id string, events []domain.RoundEvent,
12231276
) error {

server/internal/core/application/covenantless.go

+54-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"encoding/hex"
77
"fmt"
8+
"runtime"
89
"strings"
910
"sync"
1011
"time"
@@ -417,7 +418,7 @@ func (s *covenantlessService) SubmitRedeemTx(
417418
}
418419

419420
// verify the tapscript signatures
420-
if valid, err := s.builder.VerifyTapscriptPartialSigs(redeemTx); err != nil || !valid {
421+
if valid, _, err := s.builder.VerifyTapscriptPartialSigs(redeemTx); err != nil || !valid {
421422
return "", "", fmt.Errorf("invalid tx signature: %s", err)
422423
}
423424

@@ -1401,6 +1402,12 @@ func (s *covenantlessService) finalizeRound(notes []note.Note) {
14011402
return
14021403
}
14031404

1405+
if err := s.verifyForfeitTxsSigs(forfeitTxs); err != nil {
1406+
changes = round.Fail(err)
1407+
log.WithError(err).Warn("failed to validate forfeit txs")
1408+
return
1409+
}
1410+
14041411
log.Debugf("signing round transaction %s\n", round.Id)
14051412

14061413
boardingInputsIndexes := make([]int, 0)
@@ -1939,6 +1946,52 @@ func (s *covenantlessService) markAsRedeemed(ctx context.Context, vtxo domain.Vt
19391946
return nil
19401947
}
19411948

1949+
func (s *covenantlessService) verifyForfeitTxsSigs(txs []string) error {
1950+
nbWorkers := runtime.NumCPU()
1951+
jobs := make(chan string, len(txs))
1952+
errChan := make(chan error, 1)
1953+
wg := sync.WaitGroup{}
1954+
wg.Add(nbWorkers)
1955+
1956+
for i := 0; i < nbWorkers; i++ {
1957+
go func() {
1958+
defer wg.Done()
1959+
1960+
for tx := range jobs {
1961+
valid, txid, err := s.builder.VerifyTapscriptPartialSigs(tx)
1962+
if err != nil {
1963+
errChan <- fmt.Errorf("failed to validate forfeit tx %s: %s", txid, err)
1964+
return
1965+
}
1966+
1967+
if !valid {
1968+
errChan <- fmt.Errorf("invalid signature for forfeit tx %s", txid)
1969+
return
1970+
}
1971+
}
1972+
}()
1973+
}
1974+
1975+
for _, tx := range txs {
1976+
select {
1977+
case err := <-errChan:
1978+
return err
1979+
default:
1980+
jobs <- tx
1981+
}
1982+
}
1983+
close(jobs)
1984+
wg.Wait()
1985+
1986+
select {
1987+
case err := <-errChan:
1988+
return err
1989+
default:
1990+
close(errChan)
1991+
return nil
1992+
}
1993+
}
1994+
19421995
func findForfeitTxBitcoin(
19431996
forfeits []string, connectorTxid string, connectorVout uint32, vtxo domain.VtxoKey,
19441997
) (string, error) {

server/internal/core/ports/tx_builder.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ type TxBuilder interface {
5252
BuildSweepTx(inputs []SweepInput) (signedSweepTx string, err error)
5353
GetSweepInput(node tree.Node) (vtxoTreeExpiry *common.RelativeLocktime, sweepInput SweepInput, err error)
5454
FinalizeAndExtract(tx string) (txhex string, err error)
55-
VerifyTapscriptPartialSigs(tx string) (valid bool, err error)
55+
VerifyTapscriptPartialSigs(tx string) (valid bool, txid string, err error)
5656
// FindLeaves returns all the leaves txs that are reachable from the given outpoint
5757
FindLeaves(vtxoTree tree.VtxoTree, fromtxid string, vout uint32) (leaves []tree.Node, err error)
5858
VerifyAndCombinePartialTx(dest string, src string) (string, error)

server/internal/infrastructure/tx-builder/covenant/builder.go

+17-25
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,6 @@ func (b *txBuilder) VerifyForfeitTxs(vtxos []domain.Vtxo, connectors []string, f
125125
return nil, fmt.Errorf("invalid forfeit tx, expect 2 inputs, got %d", len(pset.Inputs))
126126
}
127127

128-
valid, err := b.verifyTapscriptPartialSigs(pset)
129-
if err != nil {
130-
return nil, err
131-
}
132-
if !valid {
133-
return nil, fmt.Errorf("invalid forfeit tx signature")
134-
}
135-
136128
vtxoInput := pset.Inputs[1]
137129

138130
vtxoKey := domain.VtxoKey{
@@ -512,38 +504,38 @@ func (b *txBuilder) GetSweepInput(node tree.Node) (vtxoTreeExpiry *common.Relati
512504

513505
return vtxoTreeExpiry, sweepInput, nil
514506
}
515-
func (b *txBuilder) VerifyTapscriptPartialSigs(tx string) (bool, error) {
507+
func (b *txBuilder) VerifyTapscriptPartialSigs(tx string) (bool, string, error) {
516508
pset, err := psetv2.NewPsetFromBase64(tx)
517509
if err != nil {
518-
return false, err
510+
return false, "", err
519511
}
520512

521513
return b.verifyTapscriptPartialSigs(pset)
522514
}
523515

524-
func (b *txBuilder) verifyTapscriptPartialSigs(pset *psetv2.Pset) (bool, error) {
516+
func (b *txBuilder) verifyTapscriptPartialSigs(pset *psetv2.Pset) (bool, string, error) {
525517
utx, _ := pset.UnsignedTx()
526518
txid := utx.TxHash().String()
527519

528520
serverPubkey, err := b.wallet.GetPubkey(context.Background())
529521
if err != nil {
530-
return false, err
522+
return false, "", err
531523
}
532524

533525
for index, input := range pset.Inputs {
534526
if len(input.TapLeafScript) == 0 {
535527
continue
536528
}
537529
if input.WitnessUtxo == nil {
538-
return false, fmt.Errorf("missing witness utxo for input %d, cannot verify signature", index)
530+
return false, txid, fmt.Errorf("missing prevout for input %d", index)
539531
}
540532

541533
// verify taproot leaf script
542534
tapLeaf := input.TapLeafScript[0]
543535

544536
closure, err := tree.DecodeClosure(tapLeaf.Script)
545537
if err != nil {
546-
return false, err
538+
return false, txid, err
547539
}
548540

549541
keys := make(map[string]bool)
@@ -564,16 +556,16 @@ func (b *txBuilder) verifyTapscriptPartialSigs(pset *psetv2.Pset) (bool, error)
564556
case *tree.ConditionMultisigClosure:
565557
witness, err := tree.GetConditionWitness(input)
566558
if err != nil {
567-
return false, err
559+
return false, txid, err
568560
}
569561

570562
result, err := tree.ExecuteBoolScript(c.Condition, witness)
571563
if err != nil {
572-
return false, err
564+
return false, txid, err
573565
}
574566

575567
if !result {
576-
return false, fmt.Errorf("condition not met for input %d", index)
568+
return false, txid, fmt.Errorf("condition not met for input %d", index)
577569
}
578570

579571
for _, key := range c.PubKeys {
@@ -589,11 +581,11 @@ func (b *txBuilder) verifyTapscriptPartialSigs(pset *psetv2.Pset) (bool, error)
589581

590582
pkscript, err := common.P2TRScript(tapKeyFromControlBlock)
591583
if err != nil {
592-
return false, err
584+
return false, txid, err
593585
}
594586

595587
if !bytes.Equal(pkscript, input.WitnessUtxo.Script) {
596-
return false, fmt.Errorf("invalid control block for input %d", index)
588+
return false, txid, fmt.Errorf("invalid control block for input %d", index)
597589
}
598590

599591
leafHash := taproot.NewBaseTapElementsLeaf(tapLeaf.Script).TapHash()
@@ -604,22 +596,22 @@ func (b *txBuilder) verifyTapscriptPartialSigs(pset *psetv2.Pset) (bool, error)
604596
&leafHash,
605597
)
606598
if err != nil {
607-
return false, err
599+
return false, txid, err
608600
}
609601

610602
for _, tapScriptSig := range input.TapScriptSig {
611603
sig, err := schnorr.ParseSignature(tapScriptSig.Signature)
612604
if err != nil {
613-
return false, err
605+
return false, txid, err
614606
}
615607

616608
pubkey, err := schnorr.ParsePubKey(tapScriptSig.PubKey)
617609
if err != nil {
618-
return false, err
610+
return false, txid, err
619611
}
620612

621613
if !sig.Verify(preimage, pubkey) {
622-
return false, fmt.Errorf("invalid signature for tx %s", txid)
614+
return false, txid, nil
623615
}
624616

625617
keys[hex.EncodeToString(schnorr.SerializePubKey(pubkey))] = true
@@ -633,11 +625,11 @@ func (b *txBuilder) verifyTapscriptPartialSigs(pset *psetv2.Pset) (bool, error)
633625
}
634626

635627
if missingSigs > 0 {
636-
return false, fmt.Errorf("missing %d signatures", missingSigs)
628+
return false, txid, fmt.Errorf("missing %d signatures", missingSigs)
637629
}
638630
}
639631

640-
return true, nil
632+
return true, txid, nil
641633
}
642634

643635
func (b *txBuilder) FinalizeAndExtract(tx string) (string, error) {

0 commit comments

Comments
 (0)