mirror of
https://github.com/aljazceru/ark.git
synced 2026-02-23 12:12:49 +01:00
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:
@@ -9,7 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ark-network/ark/common"
|
||||
"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"
|
||||
@@ -149,29 +148,30 @@ func (s *covenantService) Stop() {
|
||||
close(s.eventsCh)
|
||||
}
|
||||
|
||||
func (s *covenantService) GetBoardingAddress(ctx context.Context, userPubkey *secp256k1.PublicKey) (string, string, error) {
|
||||
vtxoScript := &tree.DefaultVtxoScript{
|
||||
Asp: s.pubkey,
|
||||
Owner: userPubkey,
|
||||
ExitDelay: uint(s.boardingExitDelay),
|
||||
}
|
||||
func (s *covenantService) GetBoardingAddress(ctx context.Context, userPubkey *secp256k1.PublicKey) (string, []string, error) {
|
||||
vtxoScript := tree.NewDefaultVtxoScript(userPubkey, s.pubkey, 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)
|
||||
}
|
||||
|
||||
p2tr, err := payment.FromTweakedKey(tapKey, s.onchainNetwork(), nil)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
addr, err := p2tr.TaprootAddress()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return addr, vtxoScript.ToDescriptor(), nil
|
||||
scripts, err := vtxoScript.Encode()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return addr, scripts, nil
|
||||
}
|
||||
|
||||
func (s *covenantService) SpendNotes(_ context.Context, _ []note.Note) (string, error) {
|
||||
@@ -211,8 +211,18 @@ func (s *covenantService) SpendVtxos(ctx context.Context, inputs []ports.Input)
|
||||
return "", fmt.Errorf("tx %s not confirmed", input.Txid)
|
||||
}
|
||||
|
||||
vtxoScript, err := tree.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)
|
||||
}
|
||||
|
||||
@@ -242,7 +252,7 @@ func (s *covenantService) SpendVtxos(ctx context.Context, inputs []ports.Input)
|
||||
return "", fmt.Errorf("input %s:%d already swept", vtxo.Txid, vtxo.VOut)
|
||||
}
|
||||
|
||||
vtxoScript, err := tree.ParseVtxoScript(input.Descriptor)
|
||||
vtxoScript, err := tree.ParseVtxoScript(input.Tapscripts)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse boarding descriptor: %s", err)
|
||||
}
|
||||
@@ -290,7 +300,7 @@ func (s *covenantService) newBoardingInput(tx *transaction.Transaction, input po
|
||||
return nil, fmt.Errorf("failed to parse value: %s", err)
|
||||
}
|
||||
|
||||
boardingScript, err := tree.ParseVtxoScript(input.Descriptor)
|
||||
boardingScript, err := tree.ParseVtxoScript(input.Tapscripts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse boarding descriptor: %s", err)
|
||||
}
|
||||
@@ -309,16 +319,8 @@ func (s *covenantService) newBoardingInput(tx *transaction.Transaction, input po
|
||||
return nil, fmt.Errorf("descriptor does not match script in transaction output")
|
||||
}
|
||||
|
||||
if defaultVtxoScript, ok := boardingScript.(*tree.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{
|
||||
@@ -430,15 +432,7 @@ func (s *covenantService) GetInfo(ctx context.Context) (*ServiceInfo, error) {
|
||||
RoundInterval: s.roundInterval,
|
||||
Network: s.network.Name,
|
||||
Dust: dust,
|
||||
BoardingDescriptorTemplate: fmt.Sprintf(
|
||||
descriptor.DefaultVtxoDescriptorTemplate,
|
||||
hex.EncodeToString(tree.UnspendableKey().SerializeCompressed()),
|
||||
"USER",
|
||||
hex.EncodeToString(schnorr.SerializePubKey(s.pubkey)),
|
||||
s.boardingExitDelay,
|
||||
"USER",
|
||||
),
|
||||
ForfeitAddress: forfeitAddress,
|
||||
ForfeitAddress: forfeitAddress,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -852,6 +846,7 @@ func (s *covenantService) reactToFraud(ctx context.Context, vtxo domain.Vtxo, mu
|
||||
|
||||
forfeitTxid, err := s.wallet.BroadcastTransaction(ctx, forfeitTxHex)
|
||||
if err != nil {
|
||||
log.Debug(forfeitTxHex)
|
||||
return fmt.Errorf("failed to broadcast forfeit tx: %s", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -24,7 +24,7 @@ type OwnershipProof struct {
|
||||
|
||||
func (p OwnershipProof) validate(vtxo domain.Vtxo) error {
|
||||
// verify revealed script and extract user public key
|
||||
pubkey, err := decodeForfeitClosure(p.Script)
|
||||
pubkeys, err := decodeForfeitClosure(p.Script)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -49,24 +49,32 @@ func (p OwnershipProof) validate(vtxo domain.Vtxo) error {
|
||||
outpointBytes := append(txhash[:], voutBytes...)
|
||||
sigMsg := sha256.Sum256(outpointBytes)
|
||||
|
||||
if !p.Signature.Verify(sigMsg[:], pubkey) {
|
||||
valid := false
|
||||
for _, pubkey := range pubkeys {
|
||||
if p.Signature.Verify(sigMsg[:], pubkey) {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !valid {
|
||||
return fmt.Errorf("invalid signature")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeForfeitClosure(script []byte) (*secp256k1.PublicKey, error) {
|
||||
var covenantLessForfeitClosure bitcointree.MultisigClosure
|
||||
func decodeForfeitClosure(script []byte) ([]*secp256k1.PublicKey, error) {
|
||||
var forfeit tree.MultisigClosure
|
||||
|
||||
if valid, err := covenantLessForfeitClosure.Decode(script); err == nil && valid {
|
||||
return covenantLessForfeitClosure.Pubkey, nil
|
||||
valid, err := forfeit.Decode(script)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var covenantForfeitClosure tree.CSVSigClosure
|
||||
if valid, err := covenantForfeitClosure.Decode(script); err == nil && valid {
|
||||
return covenantForfeitClosure.Pubkey, nil
|
||||
if !valid {
|
||||
return nil, fmt.Errorf("invalid forfeit closure script")
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid forfeit closure script")
|
||||
return forfeit.PubKeys, nil
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ type Service interface {
|
||||
) error
|
||||
GetBoardingAddress(
|
||||
ctx context.Context, userPubkey *secp256k1.PublicKey,
|
||||
) (address string, descriptor string, err error)
|
||||
) (address string, scripts []string, err error)
|
||||
// Tree signing methods
|
||||
RegisterCosignerPubkey(ctx context.Context, paymentId string, ephemeralPublicKey string) error
|
||||
RegisterCosignerNonces(
|
||||
@@ -62,14 +62,13 @@ type Service interface {
|
||||
}
|
||||
|
||||
type ServiceInfo struct {
|
||||
PubKey string
|
||||
RoundLifetime int64
|
||||
UnilateralExitDelay int64
|
||||
RoundInterval int64
|
||||
Network string
|
||||
Dust uint64
|
||||
BoardingDescriptorTemplate string
|
||||
ForfeitAddress string
|
||||
PubKey string
|
||||
RoundLifetime int64
|
||||
UnilateralExitDelay int64
|
||||
RoundInterval int64
|
||||
Network string
|
||||
Dust uint64
|
||||
ForfeitAddress string
|
||||
}
|
||||
|
||||
type WalletStatus struct {
|
||||
|
||||
@@ -18,7 +18,7 @@ type SweepInput interface {
|
||||
|
||||
type Input struct {
|
||||
domain.VtxoKey
|
||||
Descriptor string
|
||||
Tapscripts []string
|
||||
}
|
||||
|
||||
type BoardingInput struct {
|
||||
@@ -49,12 +49,12 @@ type TxBuilder interface {
|
||||
BuildSweepTx(inputs []SweepInput) (signedSweepTx string, err error)
|
||||
GetSweepInput(node tree.Node) (lifetime int64, sweepInput SweepInput, err error)
|
||||
FinalizeAndExtract(tx string) (txhex string, err error)
|
||||
VerifyTapscriptPartialSigs(tx string) (valid bool, txid string, err error)
|
||||
VerifyTapscriptPartialSigs(tx string) (valid bool, err error)
|
||||
// FindLeaves returns all the leaves txs that are reachable from the given outpoint
|
||||
FindLeaves(congestionTree tree.CongestionTree, fromtxid string, vout uint32) (leaves []tree.Node, err error)
|
||||
BuildAsyncPaymentTransactions(
|
||||
vtxosToSpend []domain.Vtxo,
|
||||
descriptors map[domain.VtxoKey]string,
|
||||
scripts map[domain.VtxoKey][]string,
|
||||
forfeitsLeaves map[domain.VtxoKey]chainhash.Hash,
|
||||
receivers []domain.Receiver,
|
||||
) (string, error)
|
||||
|
||||
@@ -3,6 +3,7 @@ package txbuilder
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"github.com/ark-network/ark/server/internal/core/domain"
|
||||
"github.com/ark-network/ark/server/internal/core/ports"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
@@ -122,7 +124,7 @@ func (b *txBuilder) VerifyForfeitTxs(vtxos []domain.Vtxo, connectors []string, f
|
||||
return nil, fmt.Errorf("invalid forfeit tx, expect 2 inputs, got %d", len(pset.Inputs))
|
||||
}
|
||||
|
||||
valid, _, err := b.verifyTapscriptPartialSigs(pset)
|
||||
valid, err := b.verifyTapscriptPartialSigs(pset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -462,40 +464,66 @@ func (b *txBuilder) GetSweepInput(node tree.Node) (lifetime int64, sweepInput po
|
||||
|
||||
return lifetime, sweepInput, nil
|
||||
}
|
||||
func (b *txBuilder) VerifyTapscriptPartialSigs(tx string) (bool, string, error) {
|
||||
func (b *txBuilder) VerifyTapscriptPartialSigs(tx string) (bool, error) {
|
||||
pset, err := psetv2.NewPsetFromBase64(tx)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
return false, err
|
||||
}
|
||||
|
||||
return b.verifyTapscriptPartialSigs(pset)
|
||||
}
|
||||
|
||||
func (b *txBuilder) verifyTapscriptPartialSigs(pset *psetv2.Pset) (bool, string, error) {
|
||||
func (b *txBuilder) verifyTapscriptPartialSigs(pset *psetv2.Pset) (bool, error) {
|
||||
utx, _ := pset.UnsignedTx()
|
||||
txid := utx.TxHash().String()
|
||||
|
||||
aspPublicKey, err := b.wallet.GetPubkey(context.Background())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for index, input := range pset.Inputs {
|
||||
if len(input.TapLeafScript) == 0 {
|
||||
continue
|
||||
}
|
||||
if input.WitnessUtxo == nil {
|
||||
return false, txid, fmt.Errorf("missing witness utxo for input %d, cannot verify signature", index)
|
||||
return false, fmt.Errorf("missing witness utxo for input %d, cannot verify signature", index)
|
||||
}
|
||||
|
||||
// verify taproot leaf script
|
||||
tapLeaf := input.TapLeafScript[0]
|
||||
|
||||
closure, err := tree.DecodeClosure(tapLeaf.Script)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
keys := make(map[string]bool)
|
||||
|
||||
switch c := closure.(type) {
|
||||
case *tree.MultisigClosure:
|
||||
for _, key := range c.PubKeys {
|
||||
keys[hex.EncodeToString(schnorr.SerializePubKey(key))] = false
|
||||
}
|
||||
case *tree.CSVSigClosure:
|
||||
for _, key := range c.PubKeys {
|
||||
keys[hex.EncodeToString(schnorr.SerializePubKey(key))] = false
|
||||
}
|
||||
}
|
||||
|
||||
// we don't need to check if ASP signed
|
||||
keys[hex.EncodeToString(schnorr.SerializePubKey(aspPublicKey))] = true
|
||||
|
||||
rootHash := tapLeaf.ControlBlock.RootHash(tapLeaf.Script)
|
||||
tapKeyFromControlBlock := taproot.ComputeTaprootOutputKey(tree.UnspendableKey(), rootHash[:])
|
||||
|
||||
pkscript, err := common.P2TRScript(tapKeyFromControlBlock)
|
||||
if err != nil {
|
||||
return false, txid, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !bytes.Equal(pkscript, input.WitnessUtxo.Script) {
|
||||
return false, txid, fmt.Errorf("invalid control block for input %d", index)
|
||||
return false, fmt.Errorf("invalid control block for input %d", index)
|
||||
}
|
||||
|
||||
leafHash := taproot.NewBaseTapElementsLeaf(tapLeaf.Script).TapHash()
|
||||
@@ -506,41 +534,93 @@ func (b *txBuilder) verifyTapscriptPartialSigs(pset *psetv2.Pset) (bool, string,
|
||||
&leafHash,
|
||||
)
|
||||
if err != nil {
|
||||
return false, txid, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, tapScriptSig := range input.TapScriptSig {
|
||||
sig, err := schnorr.ParseSignature(tapScriptSig.Signature)
|
||||
if err != nil {
|
||||
return false, txid, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
pubkey, err := schnorr.ParsePubKey(tapScriptSig.PubKey)
|
||||
if err != nil {
|
||||
return false, txid, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !sig.Verify(preimage, pubkey) {
|
||||
return false, txid, fmt.Errorf("invalid signature for tx %s", txid)
|
||||
return false, fmt.Errorf("invalid signature for tx %s", txid)
|
||||
}
|
||||
|
||||
keys[hex.EncodeToString(schnorr.SerializePubKey(pubkey))] = true
|
||||
}
|
||||
|
||||
missingSigs := 0
|
||||
for key := range keys {
|
||||
if !keys[key] {
|
||||
missingSigs++
|
||||
}
|
||||
}
|
||||
|
||||
if missingSigs > 0 {
|
||||
return false, fmt.Errorf("missing %d signatures", missingSigs)
|
||||
}
|
||||
}
|
||||
|
||||
return true, txid, nil
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (b *txBuilder) FinalizeAndExtract(tx string) (string, error) {
|
||||
p, err := psetv2.NewPsetFromBase64(tx)
|
||||
ptx, err := psetv2.NewPsetFromBase64(tx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := psetv2.FinalizeAll(p); err != nil {
|
||||
return "", err
|
||||
for i, in := range ptx.Inputs {
|
||||
if in.WitnessUtxo == nil {
|
||||
return "", fmt.Errorf("missing witness utxo, cannot finalize tx")
|
||||
}
|
||||
|
||||
if len(in.TapLeafScript) > 0 {
|
||||
tapLeaf := in.TapLeafScript[0]
|
||||
|
||||
closure, err := tree.DecodeClosure(tapLeaf.Script)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
signatures := make(map[string][]byte)
|
||||
|
||||
for _, sig := range in.TapScriptSig {
|
||||
signatures[hex.EncodeToString(sig.PubKey)] = sig.Signature
|
||||
}
|
||||
|
||||
controlBlock, err := tapLeaf.ControlBlock.ToBytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
witness, err := closure.Witness(controlBlock, signatures)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var witnessBuf bytes.Buffer
|
||||
if err := psbt.WriteTxWitness(&witnessBuf, witness); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ptx.Inputs[i].FinalScriptWitness = witnessBuf.Bytes()
|
||||
continue
|
||||
}
|
||||
|
||||
if err := psetv2.Finalize(ptx, i); err != nil {
|
||||
return "", fmt.Errorf("failed to finalize signed pset: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// extract the forfeit tx
|
||||
extracted, err := psetv2.Extract(p)
|
||||
extracted, err := psetv2.Extract(ptx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -591,7 +671,7 @@ func (b *txBuilder) FindLeaves(
|
||||
|
||||
func (b *txBuilder) BuildAsyncPaymentTransactions(
|
||||
_ []domain.Vtxo,
|
||||
_ map[domain.VtxoKey]string,
|
||||
_ map[domain.VtxoKey][]string,
|
||||
_ map[domain.VtxoKey]chainhash.Hash,
|
||||
_ []domain.Receiver,
|
||||
) (string, error) {
|
||||
@@ -728,7 +808,7 @@ func (b *txBuilder) createPoolTx(
|
||||
return nil, fmt.Errorf("failed to convert value to bytes: %s", err)
|
||||
}
|
||||
|
||||
boardingVtxoScript, err := tree.ParseVtxoScript(in.Descriptor)
|
||||
boardingVtxoScript, err := tree.ParseVtxoScript(in.Tapscripts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
)
|
||||
|
||||
type txBuilder struct {
|
||||
@@ -47,47 +48,74 @@ func (b *txBuilder) GetTxID(tx string) (string, error) {
|
||||
return ptx.UnsignedTx.TxHash().String(), nil
|
||||
}
|
||||
|
||||
func (b *txBuilder) VerifyTapscriptPartialSigs(tx string) (bool, string, error) {
|
||||
func (b *txBuilder) VerifyTapscriptPartialSigs(tx string) (bool, error) {
|
||||
ptx, err := psbt.NewFromRawBytes(strings.NewReader(tx), true)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
return false, err
|
||||
}
|
||||
|
||||
return b.verifyTapscriptPartialSigs(ptx)
|
||||
}
|
||||
|
||||
func (b *txBuilder) verifyTapscriptPartialSigs(ptx *psbt.Packet) (bool, string, error) {
|
||||
func (b *txBuilder) verifyTapscriptPartialSigs(ptx *psbt.Packet) (bool, error) {
|
||||
txid := ptx.UnsignedTx.TxID()
|
||||
|
||||
aspPublicKey, err := b.wallet.GetPubkey(context.Background())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for index, input := range ptx.Inputs {
|
||||
if len(input.TaprootLeafScript) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if input.WitnessUtxo == nil {
|
||||
return false, txid, fmt.Errorf("missing witness utxo for input %d, cannot verify signature", index)
|
||||
return false, fmt.Errorf("missing witness utxo for input %d, cannot verify signature", index)
|
||||
}
|
||||
|
||||
// verify taproot leaf script
|
||||
tapLeaf := input.TaprootLeafScript[0]
|
||||
|
||||
closure, err := tree.DecodeClosure(tapLeaf.Script)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
keys := make(map[string]bool)
|
||||
|
||||
switch c := closure.(type) {
|
||||
case *tree.MultisigClosure:
|
||||
for _, key := range c.PubKeys {
|
||||
keys[hex.EncodeToString(schnorr.SerializePubKey(key))] = false
|
||||
}
|
||||
case *tree.CSVSigClosure:
|
||||
for _, key := range c.PubKeys {
|
||||
keys[hex.EncodeToString(schnorr.SerializePubKey(key))] = false
|
||||
}
|
||||
}
|
||||
|
||||
// we don't need to check if ASP signed
|
||||
keys[hex.EncodeToString(schnorr.SerializePubKey(aspPublicKey))] = true
|
||||
|
||||
if len(tapLeaf.ControlBlock) == 0 {
|
||||
return false, txid, fmt.Errorf("missing control block for input %d", index)
|
||||
return false, fmt.Errorf("missing control block for input %d", index)
|
||||
}
|
||||
|
||||
controlBlock, err := txscript.ParseControlBlock(tapLeaf.ControlBlock)
|
||||
if err != nil {
|
||||
return false, txid, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
rootHash := controlBlock.RootHash(tapLeaf.Script)
|
||||
tapKeyFromControlBlock := txscript.ComputeTaprootOutputKey(bitcointree.UnspendableKey(), rootHash[:])
|
||||
pkscript, err := common.P2TRScript(tapKeyFromControlBlock)
|
||||
if err != nil {
|
||||
return false, txid, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !bytes.Equal(pkscript, input.WitnessUtxo.PkScript) {
|
||||
return false, txid, fmt.Errorf("invalid control block for input %d", index)
|
||||
return false, fmt.Errorf("invalid control block for input %d", index)
|
||||
}
|
||||
|
||||
preimage, err := b.getTaprootPreimage(
|
||||
@@ -96,27 +124,40 @@ func (b *txBuilder) verifyTapscriptPartialSigs(ptx *psbt.Packet) (bool, string,
|
||||
tapLeaf.Script,
|
||||
)
|
||||
if err != nil {
|
||||
return false, txid, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, tapScriptSig := range input.TaprootScriptSpendSig {
|
||||
sig, err := schnorr.ParseSignature(tapScriptSig.Signature)
|
||||
if err != nil {
|
||||
return false, txid, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
pubkey, err := schnorr.ParsePubKey(tapScriptSig.XOnlyPubKey)
|
||||
if err != nil {
|
||||
return false, txid, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !sig.Verify(preimage, pubkey) {
|
||||
return false, txid, fmt.Errorf("invalid signature for tx %s", txid)
|
||||
return false, fmt.Errorf("invalid signature for tx %s", txid)
|
||||
}
|
||||
|
||||
keys[hex.EncodeToString(schnorr.SerializePubKey(pubkey))] = true
|
||||
}
|
||||
|
||||
missingSigs := 0
|
||||
for key := range keys {
|
||||
if !keys[key] {
|
||||
missingSigs++
|
||||
}
|
||||
}
|
||||
|
||||
if missingSigs > 0 {
|
||||
return false, fmt.Errorf("missing %d signatures", missingSigs)
|
||||
}
|
||||
}
|
||||
|
||||
return true, txid, nil
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (b *txBuilder) FinalizeAndExtract(tx string) (string, error) {
|
||||
@@ -128,47 +169,30 @@ func (b *txBuilder) FinalizeAndExtract(tx string) (string, error) {
|
||||
for i, in := range ptx.Inputs {
|
||||
isTaproot := txscript.IsPayToTaproot(in.WitnessUtxo.PkScript)
|
||||
if isTaproot && len(in.TaprootLeafScript) > 0 {
|
||||
closure, err := bitcointree.DecodeClosure(in.TaprootLeafScript[0].Script)
|
||||
closure, err := tree.DecodeClosure(in.TaprootLeafScript[0].Script)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
witness := make(wire.TxWitness, 4)
|
||||
signatures := make(map[string][]byte)
|
||||
|
||||
castClosure, isTaprootMultisig := closure.(*bitcointree.MultisigClosure)
|
||||
if isTaprootMultisig {
|
||||
ownerPubkey := schnorr.SerializePubKey(castClosure.Pubkey)
|
||||
aspKey := schnorr.SerializePubKey(castClosure.AspPubkey)
|
||||
|
||||
for _, sig := range in.TaprootScriptSpendSig {
|
||||
if bytes.Equal(sig.XOnlyPubKey, ownerPubkey) {
|
||||
witness[0] = sig.Signature
|
||||
}
|
||||
|
||||
if bytes.Equal(sig.XOnlyPubKey, aspKey) {
|
||||
witness[1] = sig.Signature
|
||||
}
|
||||
}
|
||||
|
||||
witness[2] = in.TaprootLeafScript[0].Script
|
||||
witness[3] = in.TaprootLeafScript[0].ControlBlock
|
||||
|
||||
for idw, w := range witness {
|
||||
if w == nil {
|
||||
return "", fmt.Errorf("missing witness element %d, cannot finalize taproot mutlisig input %d", idw, i)
|
||||
}
|
||||
}
|
||||
|
||||
var witnessBuf bytes.Buffer
|
||||
|
||||
if err := psbt.WriteTxWitness(&witnessBuf, witness); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ptx.Inputs[i].FinalScriptWitness = witnessBuf.Bytes()
|
||||
continue
|
||||
for _, sig := range in.TaprootScriptSpendSig {
|
||||
signatures[hex.EncodeToString(sig.XOnlyPubKey)] = sig.Signature
|
||||
}
|
||||
|
||||
witness, err := closure.Witness(in.TaprootLeafScript[0].ControlBlock, signatures)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var witnessBuf bytes.Buffer
|
||||
if err := psbt.WriteTxWitness(&witnessBuf, witness); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ptx.Inputs[i].FinalScriptWitness = witnessBuf.Bytes()
|
||||
continue
|
||||
|
||||
}
|
||||
|
||||
if err := psbt.Finalize(ptx, i); err != nil {
|
||||
@@ -265,7 +289,7 @@ func (b *txBuilder) VerifyForfeitTxs(vtxos []domain.Vtxo, connectors []string, f
|
||||
return nil, fmt.Errorf("invalid forfeit tx, expect 2 inputs, got %d", len(ptx.Inputs))
|
||||
}
|
||||
|
||||
valid, _, err := b.verifyTapscriptPartialSigs(ptx)
|
||||
valid, err := b.verifyTapscriptPartialSigs(ptx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -623,7 +647,7 @@ func (b *txBuilder) FindLeaves(congestionTree tree.CongestionTree, fromtxid stri
|
||||
|
||||
func (b *txBuilder) BuildAsyncPaymentTransactions(
|
||||
vtxos []domain.Vtxo,
|
||||
descriptors map[domain.VtxoKey]string,
|
||||
scripts map[domain.VtxoKey][]string,
|
||||
forfeitsLeaves map[domain.VtxoKey]chainhash.Hash,
|
||||
receivers []domain.Receiver,
|
||||
) (string, error) {
|
||||
@@ -638,9 +662,9 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
|
||||
|
||||
redeemTxWeightEstimator := &input.TxWeightEstimator{}
|
||||
for index, vtxo := range vtxos {
|
||||
desc, ok := descriptors[vtxo.VtxoKey]
|
||||
vtxoTapscripts, ok := scripts[vtxo.VtxoKey]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing descriptor for vtxo %s", vtxo.VtxoKey)
|
||||
return "", fmt.Errorf("missing scripts for vtxo %s", vtxo.VtxoKey)
|
||||
}
|
||||
|
||||
forfeitLeafHash, ok := forfeitsLeaves[vtxo.VtxoKey]
|
||||
@@ -662,7 +686,7 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
|
||||
Index: vtxo.VOut,
|
||||
}
|
||||
|
||||
vtxoScript, err := bitcointree.ParseVtxoScript(desc)
|
||||
vtxoScript, err := bitcointree.ParseVtxoScript(vtxoTapscripts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -698,7 +722,12 @@ func (b *txBuilder) BuildAsyncPaymentTransactions(
|
||||
return "", err
|
||||
}
|
||||
|
||||
redeemTxWeightEstimator.AddTapscriptInput(64*2+40, &waddrmgr.Tapscript{
|
||||
closure, err := tree.DecodeClosure(leafProof.Script)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
redeemTxWeightEstimator.AddTapscriptInput(lntypes.WeightUnit(closure.WitnessSize()), &waddrmgr.Tapscript{
|
||||
RevealedScript: leafProof.Script,
|
||||
ControlBlock: ctrlBlock,
|
||||
})
|
||||
@@ -940,7 +969,7 @@ func (b *txBuilder) createRoundTx(
|
||||
})
|
||||
nSequences = append(nSequences, wire.MaxTxInSequenceNum)
|
||||
|
||||
boardingVtxoScript, err := bitcointree.ParseVtxoScript(boardingInput.Descriptor)
|
||||
boardingVtxoScript, err := bitcointree.ParseVtxoScript(boardingInput.Tapscripts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1322,7 +1351,7 @@ func castToOutpoints(inputs []ports.TxInput) []ports.TxOutpoint {
|
||||
|
||||
func extractSweepLeaf(input psbt.PInput) (sweepLeaf *psbt.TaprootTapLeafScript, internalKey *secp256k1.PublicKey, lifetime int64, err error) {
|
||||
for _, leaf := range input.TaprootLeafScript {
|
||||
closure := &bitcointree.CSVSigClosure{}
|
||||
closure := &tree.CSVSigClosure{}
|
||||
valid, err := closure.Decode(leaf.Script)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ark-network/ark/common"
|
||||
"github.com/ark-network/ark/common/bitcointree"
|
||||
"github.com/ark-network/ark/common/tree"
|
||||
"github.com/ark-network/ark/server/internal/core/ports"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
@@ -27,7 +27,7 @@ func sweepTransaction(
|
||||
Index: input.GetIndex(),
|
||||
})
|
||||
|
||||
sweepClosure := bitcointree.CSVSigClosure{}
|
||||
sweepClosure := tree.CSVSigClosure{}
|
||||
valid, err := sweepClosure.Decode(input.GetLeafScript())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ark-network/ark/common"
|
||||
"github.com/ark-network/ark/common/bitcointree"
|
||||
"github.com/ark-network/ark/common/tree"
|
||||
"github.com/ark-network/ark/server/internal/core/domain"
|
||||
"github.com/ark-network/ark/server/internal/core/ports"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
@@ -745,46 +745,29 @@ func (s *service) SignTransaction(ctx context.Context, partialTx string, extract
|
||||
for i, in := range ptx.Inputs {
|
||||
isTaproot := txscript.IsPayToTaproot(in.WitnessUtxo.PkScript)
|
||||
if isTaproot && len(in.TaprootLeafScript) > 0 {
|
||||
closure, err := bitcointree.DecodeClosure(in.TaprootLeafScript[0].Script)
|
||||
closure, err := tree.DecodeClosure(in.TaprootLeafScript[0].Script)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
witness := make(wire.TxWitness, 4)
|
||||
signatures := make(map[string][]byte)
|
||||
|
||||
castClosure, isTaprootMultisig := closure.(*bitcointree.MultisigClosure)
|
||||
if isTaprootMultisig {
|
||||
ownerPubkey := schnorr.SerializePubKey(castClosure.Pubkey)
|
||||
aspKey := schnorr.SerializePubKey(castClosure.AspPubkey)
|
||||
|
||||
for _, sig := range in.TaprootScriptSpendSig {
|
||||
if bytes.Equal(sig.XOnlyPubKey, ownerPubkey) {
|
||||
witness[0] = sig.Signature
|
||||
}
|
||||
|
||||
if bytes.Equal(sig.XOnlyPubKey, aspKey) {
|
||||
witness[1] = sig.Signature
|
||||
}
|
||||
}
|
||||
|
||||
witness[2] = in.TaprootLeafScript[0].Script
|
||||
witness[3] = in.TaprootLeafScript[0].ControlBlock
|
||||
|
||||
for idw, w := range witness {
|
||||
if w == nil {
|
||||
return "", fmt.Errorf("missing witness element %d, cannot finalize taproot mutlisig input %d", idw, i)
|
||||
}
|
||||
}
|
||||
|
||||
var witnessBuf bytes.Buffer
|
||||
|
||||
if err := psbt.WriteTxWitness(&witnessBuf, witness); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ptx.Inputs[i].FinalScriptWitness = witnessBuf.Bytes()
|
||||
continue
|
||||
for _, sig := range in.TaprootScriptSpendSig {
|
||||
signatures[hex.EncodeToString(sig.XOnlyPubKey)] = sig.Signature
|
||||
}
|
||||
|
||||
witness, err := closure.Witness(in.TaprootLeafScript[0].ControlBlock, signatures)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var witnessBuf bytes.Buffer
|
||||
if err := psbt.WriteTxWitness(&witnessBuf, witness); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ptx.Inputs[i].FinalScriptWitness = witnessBuf.Bytes()
|
||||
continue
|
||||
}
|
||||
|
||||
if err := psbt.Finalize(ptx, i); err != nil {
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
pb "github.com/ark-network/ark/api-spec/protobuf/gen/ocean/v1"
|
||||
"github.com/ark-network/ark/common/tree"
|
||||
"github.com/ark-network/ark/server/internal/core/ports"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
@@ -58,43 +57,29 @@ func (s *service) SignTransaction(
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch c := closure.(type) {
|
||||
case *tree.MultisigClosure:
|
||||
asp := schnorr.SerializePubKey(c.AspPubkey)
|
||||
owner := schnorr.SerializePubKey(c.Pubkey)
|
||||
signatures := make(map[string][]byte)
|
||||
|
||||
witness := make([][]byte, 4)
|
||||
for _, sig := range in.TapScriptSig {
|
||||
if bytes.Equal(sig.PubKey, owner) {
|
||||
witness[0] = sig.Signature
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Equal(sig.PubKey, asp) {
|
||||
witness[1] = sig.Signature
|
||||
}
|
||||
}
|
||||
|
||||
witness[2] = tapLeaf.Script
|
||||
|
||||
controlBlock, err := tapLeaf.ControlBlock.ToBytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
witness[3] = controlBlock
|
||||
|
||||
var witnessBuf bytes.Buffer
|
||||
|
||||
if err := psbt.WriteTxWitness(&witnessBuf, witness); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ptx.Inputs[i].FinalScriptWitness = witnessBuf.Bytes()
|
||||
continue
|
||||
default:
|
||||
return "", fmt.Errorf("unexpected closure type %T", c)
|
||||
for _, sig := range in.TapScriptSig {
|
||||
signatures[hex.EncodeToString(sig.PubKey)] = sig.Signature
|
||||
}
|
||||
|
||||
controlBlock, err := tapLeaf.ControlBlock.ToBytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
witness, err := closure.Witness(controlBlock, signatures)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var witnessBuf bytes.Buffer
|
||||
if err := psbt.WriteTxWitness(&witnessBuf, witness); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ptx.Inputs[i].FinalScriptWitness = witnessBuf.Bytes()
|
||||
continue
|
||||
}
|
||||
|
||||
if err := psetv2.Finalize(ptx, i); err != nil {
|
||||
|
||||
@@ -3,9 +3,12 @@ package handlers
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
||||
"github.com/ark-network/ark/common/bitcointree"
|
||||
"github.com/ark-network/ark/common/descriptor"
|
||||
"github.com/ark-network/ark/server/internal/core/application"
|
||||
"github.com/ark-network/ark/server/internal/core/domain"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
@@ -43,6 +46,15 @@ func (h *handler) GetInfo(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
desc := fmt.Sprintf(
|
||||
descriptor.DefaultVtxoDescriptorTemplate,
|
||||
hex.EncodeToString(bitcointree.UnspendableKey().SerializeCompressed()),
|
||||
"USER",
|
||||
info.PubKey,
|
||||
info.UnilateralExitDelay,
|
||||
info.PubKey,
|
||||
)
|
||||
|
||||
return &arkv1.GetInfoResponse{
|
||||
Pubkey: info.PubKey,
|
||||
RoundLifetime: info.RoundLifetime,
|
||||
@@ -50,8 +62,9 @@ func (h *handler) GetInfo(
|
||||
RoundInterval: info.RoundInterval,
|
||||
Network: info.Network,
|
||||
Dust: int64(info.Dust),
|
||||
BoardingDescriptorTemplate: info.BoardingDescriptorTemplate,
|
||||
ForfeitAddress: info.ForfeitAddress,
|
||||
BoardingDescriptorTemplate: desc,
|
||||
VtxoDescriptorTemplates: []string{desc},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -73,14 +86,18 @@ func (h *handler) GetBoardingAddress(
|
||||
return nil, status.Error(codes.InvalidArgument, "invalid pubkey (parse error)")
|
||||
}
|
||||
|
||||
addr, descriptor, err := h.svc.GetBoardingAddress(ctx, userPubkey)
|
||||
addr, tapscripts, err := h.svc.GetBoardingAddress(ctx, userPubkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &arkv1.GetBoardingAddressResponse{
|
||||
Address: addr,
|
||||
Descriptor_: descriptor,
|
||||
Address: addr,
|
||||
TaprootTree: &arkv1.GetBoardingAddressResponse_Tapscripts{
|
||||
Tapscripts: &arkv1.Tapscripts{
|
||||
Scripts: tapscripts,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ func parseAsyncPaymentInputs(ins []*arkv1.AsyncPaymentInput) ([]application.Asyn
|
||||
Txid: input.GetInput().GetOutpoint().GetTxid(),
|
||||
VOut: input.GetInput().GetOutpoint().GetVout(),
|
||||
},
|
||||
Descriptor: input.GetInput().GetDescriptor_(),
|
||||
Tapscripts: input.GetInput().GetTapscripts().GetScripts(),
|
||||
},
|
||||
ForfeitLeafHash: *forfeitLeafHash,
|
||||
})
|
||||
@@ -82,7 +82,7 @@ func parseInputs(ins []*arkv1.Input) ([]ports.Input, error) {
|
||||
Txid: input.GetOutpoint().GetTxid(),
|
||||
VOut: input.GetOutpoint().GetVout(),
|
||||
},
|
||||
Descriptor: input.GetDescriptor_(),
|
||||
Tapscripts: input.GetTapscripts().GetScripts(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user