Change representation of taproot trees & Internal fixes (#384)

* migrate descriptors --> tapscripts

* fix covenantless

* dynamic boarding exit delay

* remove duplicates in tree and bitcointree

* agnostic signatures validation

* revert GetInfo change

* renaming VtxoScript var

* Agnostic script server (#6)

* Hotfix: Prevent ZMQ-based bitcoin wallet to panic  (#383)

* Hotfix bct embedded wallet w/ ZMQ

* Fixes

* Rename vtxo is_oor to is_pending (#385)

* Rename vtxo is_oor > is_pending

* Clean swaggers

* Revert changes to client and sdk

* descriptor in oneof

* support CHECKSIG_ADD in MultisigClosure

* use right witness size in OOR tx fee estimation

* Revert changes

---------

Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com>
This commit is contained in:
Louis Singer
2024-11-20 18:51:03 +01:00
committed by GitHub
parent 403a82e25e
commit 06dd01ecb1
44 changed files with 2470 additions and 1718 deletions

View File

@@ -11,7 +11,6 @@ import (
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/common/bitcointree"
"github.com/ark-network/ark/common/descriptor"
"github.com/ark-network/ark/common/note"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/server/internal/core/domain"
@@ -259,7 +258,7 @@ func (s *covenantlessService) CompleteAsyncPayment(
}
// verify the tapscript signatures
if valid, _, err := s.builder.VerifyTapscriptPartialSigs(tx); err != nil || !valid {
if valid, err := s.builder.VerifyTapscriptPartialSigs(tx); err != nil || !valid {
return fmt.Errorf("invalid tx signature: %s", err)
}
}
@@ -329,12 +328,12 @@ func (s *covenantlessService) CreateAsyncPayment(
ctx context.Context, inputs []AsyncPaymentInput, receivers []domain.Receiver,
) (string, error) {
vtxosKeys := make([]domain.VtxoKey, 0, len(inputs))
descriptors := make(map[domain.VtxoKey]string)
scripts := make(map[domain.VtxoKey][]string)
forfeitLeaves := make(map[domain.VtxoKey]chainhash.Hash)
for _, in := range inputs {
vtxosKeys = append(vtxosKeys, in.VtxoKey)
descriptors[in.VtxoKey] = in.Descriptor
scripts[in.VtxoKey] = in.Tapscripts
forfeitLeaves[in.VtxoKey] = in.ForfeitLeafHash
}
@@ -373,7 +372,7 @@ func (s *covenantlessService) CreateAsyncPayment(
}
redeemTx, err := s.builder.BuildAsyncPaymentTransactions(
vtxosInputs, descriptors, forfeitLeaves, receivers,
vtxosInputs, scripts, forfeitLeaves, receivers,
)
if err != nil {
return "", fmt.Errorf("failed to build async payment txs: %s", err)
@@ -395,26 +394,29 @@ func (s *covenantlessService) CreateAsyncPayment(
func (s *covenantlessService) GetBoardingAddress(
ctx context.Context, userPubkey *secp256k1.PublicKey,
) (address string, descriptor string, err error) {
vtxoScript := &bitcointree.DefaultVtxoScript{
Asp: s.pubkey,
Owner: userPubkey,
ExitDelay: uint(s.boardingExitDelay),
}
) (address string, scripts []string, err error) {
vtxoScript := bitcointree.NewDefaultVtxoScript(s.pubkey, userPubkey, uint(s.boardingExitDelay))
tapKey, _, err := vtxoScript.TapTree()
if err != nil {
return "", "", fmt.Errorf("failed to get taproot key: %s", err)
return "", nil, fmt.Errorf("failed to get taproot key: %s", err)
}
addr, err := btcutil.NewAddressTaproot(
schnorr.SerializePubKey(tapKey), s.chainParams(),
)
if err != nil {
return "", "", fmt.Errorf("failed to get address: %s", err)
return "", nil, fmt.Errorf("failed to get address: %s", err)
}
return addr.EncodeAddress(), vtxoScript.ToDescriptor(), nil
scripts, err = vtxoScript.Encode()
if err != nil {
return "", nil, fmt.Errorf("failed to encode vtxo script: %s", err)
}
address = addr.EncodeAddress()
return
}
func (s *covenantlessService) SpendNotes(ctx context.Context, notes []note.Note) (string, error) {
@@ -489,8 +491,18 @@ func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []ports.Inp
return "", fmt.Errorf("tx %s not confirmed", input.Txid)
}
vtxoScript, err := bitcointree.ParseVtxoScript(input.Tapscripts)
if err != nil {
return "", fmt.Errorf("failed to parse boarding descriptor: %s", err)
}
exitDelay, err := vtxoScript.SmallestExitDelay()
if err != nil {
return "", fmt.Errorf("failed to get exit delay: %s", err)
}
// if the exit path is available, forbid registering the boarding utxo
if blocktime+int64(s.boardingExitDelay) < now {
if blocktime+int64(exitDelay) < now {
return "", fmt.Errorf("tx %s expired", input.Txid)
}
@@ -520,7 +532,7 @@ func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []ports.Inp
return "", fmt.Errorf("input %s:%d already swept", vtxo.Txid, vtxo.VOut)
}
vtxoScript, err := bitcointree.ParseVtxoScript(input.Descriptor)
vtxoScript, err := bitcointree.ParseVtxoScript(input.Tapscripts)
if err != nil {
return "", fmt.Errorf("failed to parse boarding descriptor: %s", err)
}
@@ -559,7 +571,7 @@ func (s *covenantlessService) newBoardingInput(tx wire.MsgTx, input ports.Input)
output := tx.TxOut[input.VtxoKey.VOut]
boardingScript, err := bitcointree.ParseVtxoScript(input.Descriptor)
boardingScript, err := bitcointree.ParseVtxoScript(input.Tapscripts)
if err != nil {
return nil, fmt.Errorf("failed to parse boarding descriptor: %s", err)
}
@@ -578,16 +590,8 @@ func (s *covenantlessService) newBoardingInput(tx wire.MsgTx, input ports.Input)
return nil, fmt.Errorf("descriptor does not match script in transaction output")
}
if defaultVtxoScript, ok := boardingScript.(*bitcointree.DefaultVtxoScript); ok {
if !bytes.Equal(schnorr.SerializePubKey(defaultVtxoScript.Asp), schnorr.SerializePubKey(s.pubkey)) {
return nil, fmt.Errorf("invalid boarding descriptor, ASP mismatch")
}
if defaultVtxoScript.ExitDelay != uint(s.boardingExitDelay) {
return nil, fmt.Errorf("invalid boarding descriptor, timeout mismatch")
}
} else {
return nil, fmt.Errorf("only default vtxo script is supported for boarding")
if err := boardingScript.Validate(s.pubkey, uint(s.unilateralExitDelay)); err != nil {
return nil, err
}
return &ports.BoardingInput{
@@ -691,15 +695,7 @@ func (s *covenantlessService) GetInfo(ctx context.Context) (*ServiceInfo, error)
RoundInterval: s.roundInterval,
Network: s.network.Name,
Dust: dust,
BoardingDescriptorTemplate: fmt.Sprintf(
descriptor.DefaultVtxoDescriptorTemplate,
hex.EncodeToString(bitcointree.UnspendableKey().SerializeCompressed()),
"USER",
hex.EncodeToString(schnorr.SerializePubKey(s.pubkey)),
s.boardingExitDelay,
"USER",
),
ForfeitAddress: forfeitAddr,
ForfeitAddress: forfeitAddr,
}, nil
}
@@ -921,7 +917,7 @@ func (s *covenantlessService) startFinalization() {
cosigners = append(cosigners, ephemeralKey.PubKey())
unsignedRoundTx, tree, connectorAddress, connectors, err := s.builder.BuildRoundTx(
unsignedRoundTx, vtxoTree, connectorAddress, connectors, err := s.builder.BuildRoundTx(
s.pubkey,
payments,
boardingInputs,
@@ -937,7 +933,7 @@ func (s *covenantlessService) startFinalization() {
s.forfeitTxs.init(connectors, payments)
if len(tree) > 0 {
if len(vtxoTree) > 0 {
log.Debugf("signing congestion tree for round %s", round.Id)
signingSession := newMusigSigningSession(len(cosigners))
@@ -947,14 +943,14 @@ func (s *covenantlessService) startFinalization() {
s.currentRound.UnsignedTx = unsignedRoundTx
// send back the unsigned tree & all cosigners pubkeys
s.propagateRoundSigningStartedEvent(tree, cosigners)
s.propagateRoundSigningStartedEvent(vtxoTree, cosigners)
sweepClosure := bitcointree.CSVSigClosure{
Pubkey: s.pubkey,
Seconds: uint(s.roundLifetime),
sweepClosure := tree.CSVSigClosure{
MultisigClosure: tree.MultisigClosure{PubKeys: []*secp256k1.PublicKey{s.pubkey}},
Seconds: uint(s.roundLifetime),
}
sweepTapLeaf, err := sweepClosure.Leaf()
sweepScript, err := sweepClosure.Script()
if err != nil {
return
}
@@ -968,10 +964,11 @@ func (s *covenantlessService) startFinalization() {
sharedOutputAmount := unsignedPsbt.UnsignedTx.TxOut[0].Value
sweepTapTree := txscript.AssembleTaprootScriptTree(*sweepTapLeaf)
sweepLeaf := txscript.NewBaseTapLeaf(sweepScript)
sweepTapTree := txscript.AssembleTaprootScriptTree(sweepLeaf)
root := sweepTapTree.RootNode.TapHash()
coordinator, err := bitcointree.NewTreeCoordinatorSession(sharedOutputAmount, tree, root.CloneBytes(), cosigners)
coordinator, err := bitcointree.NewTreeCoordinatorSession(sharedOutputAmount, vtxoTree, root.CloneBytes(), cosigners)
if err != nil {
round.Fail(fmt.Errorf("failed to create tree coordinator: %s", err))
log.WithError(err).Warn("failed to create tree coordinator")
@@ -979,7 +976,7 @@ func (s *covenantlessService) startFinalization() {
}
aspSignerSession := bitcointree.NewTreeSignerSession(
ephemeralKey, sharedOutputAmount, tree, root.CloneBytes(),
ephemeralKey, sharedOutputAmount, vtxoTree, root.CloneBytes(),
)
nonces, err := aspSignerSession.GetNonces()
@@ -1085,11 +1082,11 @@ func (s *covenantlessService) startFinalization() {
log.Debugf("congestion tree signed for round %s", round.Id)
tree = signedTree
vtxoTree = signedTree
}
if _, err := round.StartFinalization(
connectorAddress, connectors, tree, unsignedRoundTx,
connectorAddress, connectors, vtxoTree, unsignedRoundTx,
); err != nil {
round.Fail(fmt.Errorf("failed to start finalization: %s", err))
log.WithError(err).Warn("failed to start finalization")