mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-18 12:44:19 +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 {
|
||||
|
||||
Reference in New Issue
Block a user