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

@@ -89,7 +89,7 @@ func (o Outpoint) Equals(other Outpoint) bool {
type Input struct {
Outpoint
Descriptor string
Tapscripts []string
}
type AsyncPaymentInput struct {
@@ -129,9 +129,9 @@ func (v Vtxo) Address(asp *secp256k1.PublicKey, net common.Network) (string, err
return a.Encode()
}
type DescriptorVtxo struct {
type TapscriptsVtxo struct {
Vtxo
Descriptor string
Tapscripts []string
}
type Output struct {

View File

@@ -144,7 +144,11 @@ func toProtoInput(i client.Input) *arkv1.Input {
Txid: i.Txid,
Vout: i.VOut,
},
Descriptor_: i.Descriptor,
TaprootTree: &arkv1.Input_Tapscripts{
Tapscripts: &arkv1.Tapscripts{
Scripts: i.Tapscripts,
},
},
}
}

View File

@@ -118,7 +118,9 @@ func (a *restClient) RegisterInputsForNextRound(
Txid: i.Txid,
Vout: int64(i.VOut),
},
Descriptor: i.Descriptor,
Tapscripts: &models.V1Tapscripts{
Scripts: i.Tapscripts,
},
})
}
body := &models.V1RegisterInputsForNextRoundRequest{
@@ -402,7 +404,9 @@ func (a *restClient) CreatePayment(
Txid: i.Input.Txid,
Vout: int64(i.VOut),
},
Descriptor: i.Input.Descriptor,
Tapscripts: &models.V1Tapscripts{
Scripts: i.Input.Tapscripts,
},
},
ForfeitLeafHash: i.ForfeitLeafHash.String(),
})

View File

@@ -8,6 +8,7 @@ package models
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
@@ -22,15 +23,76 @@ type V1GetBoardingAddressResponse struct {
// descriptor
Descriptor string `json:"descriptor,omitempty"`
// tapscripts
Tapscripts *V1Tapscripts `json:"tapscripts,omitempty"`
}
// Validate validates this v1 get boarding address response
func (m *V1GetBoardingAddressResponse) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateTapscripts(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContextValidate validates this v1 get boarding address response based on context it is used
func (m *V1GetBoardingAddressResponse) validateTapscripts(formats strfmt.Registry) error {
if swag.IsZero(m.Tapscripts) { // not required
return nil
}
if m.Tapscripts != nil {
if err := m.Tapscripts.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("tapscripts")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("tapscripts")
}
return err
}
}
return nil
}
// ContextValidate validate this v1 get boarding address response based on the context it is used
func (m *V1GetBoardingAddressResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateTapscripts(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *V1GetBoardingAddressResponse) contextValidateTapscripts(ctx context.Context, formats strfmt.Registry) error {
if m.Tapscripts != nil {
if swag.IsZero(m.Tapscripts) { // not required
return nil
}
if err := m.Tapscripts.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("tapscripts")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("tapscripts")
}
return err
}
}
return nil
}

View File

@@ -23,6 +23,9 @@ type V1Input struct {
// outpoint
Outpoint *V1Outpoint `json:"outpoint,omitempty"`
// tapscripts
Tapscripts *V1Tapscripts `json:"tapscripts,omitempty"`
}
// Validate validates this v1 input
@@ -33,6 +36,10 @@ func (m *V1Input) Validate(formats strfmt.Registry) error {
res = append(res, err)
}
if err := m.validateTapscripts(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
@@ -58,6 +65,25 @@ func (m *V1Input) validateOutpoint(formats strfmt.Registry) error {
return nil
}
func (m *V1Input) validateTapscripts(formats strfmt.Registry) error {
if swag.IsZero(m.Tapscripts) { // not required
return nil
}
if m.Tapscripts != nil {
if err := m.Tapscripts.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("tapscripts")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("tapscripts")
}
return err
}
}
return nil
}
// ContextValidate validate this v1 input based on the context it is used
func (m *V1Input) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
@@ -66,6 +92,10 @@ func (m *V1Input) ContextValidate(ctx context.Context, formats strfmt.Registry)
res = append(res, err)
}
if err := m.contextValidateTapscripts(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
@@ -93,6 +123,27 @@ func (m *V1Input) contextValidateOutpoint(ctx context.Context, formats strfmt.Re
return nil
}
func (m *V1Input) contextValidateTapscripts(ctx context.Context, formats strfmt.Registry) error {
if m.Tapscripts != nil {
if swag.IsZero(m.Tapscripts) { // not required
return nil
}
if err := m.Tapscripts.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("tapscripts")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("tapscripts")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *V1Input) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -12,7 +12,7 @@ import (
"github.com/go-openapi/swag"
)
// V1OwnershipProof This message is used to prove to the ASP that the user controls the vtxo without revealing the whole VTXO descriptor.
// V1OwnershipProof This message is used to prove to the ASP that the user controls the vtxo without revealing the whole VTXO taproot tree.
//
// swagger:model v1OwnershipProof
type V1OwnershipProof struct {

View File

@@ -0,0 +1,50 @@
// Code generated by go-swagger; DO NOT EDIT.
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// V1Tapscripts v1 tapscripts
//
// swagger:model v1Tapscripts
type V1Tapscripts struct {
// scripts
Scripts []string `json:"scripts"`
}
// Validate validates this v1 tapscripts
func (m *V1Tapscripts) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this v1 tapscripts based on context it is used
func (m *V1Tapscripts) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *V1Tapscripts) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *V1Tapscripts) UnmarshalBinary(b []byte) error {
var res V1Tapscripts
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -22,7 +22,6 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
log "github.com/sirupsen/logrus"
"github.com/vulpemventures/go-elements/address"
@@ -518,7 +517,7 @@ func (a *covenantArkClient) CollaborativeRedeem(
},
}
vtxos := make([]client.DescriptorVtxo, 0)
vtxos := make([]client.TapscriptsVtxo, 0)
spendableVtxos, err := a.getVtxos(ctx, false, nil)
if err != nil {
return "", err
@@ -532,9 +531,9 @@ func (a *covenantArkClient) CollaborativeRedeem(
}
if vtxoAddr == offchainAddr.Address {
vtxos = append(vtxos, client.DescriptorVtxo{
vtxos = append(vtxos, client.TapscriptsVtxo{
Vtxo: v,
Descriptor: offchainAddr.Descriptor,
Tapscripts: offchainAddr.Tapscripts,
})
}
}
@@ -572,7 +571,7 @@ func (a *covenantArkClient) CollaborativeRedeem(
Txid: coin.Txid,
VOut: coin.VOut,
},
Descriptor: coin.Descriptor,
Tapscripts: coin.Tapscripts,
})
}
for _, coin := range selectedBoardingUtxos {
@@ -581,7 +580,7 @@ func (a *covenantArkClient) CollaborativeRedeem(
Txid: coin.Txid,
VOut: coin.VOut,
},
Descriptor: coin.Descriptor,
Tapscripts: coin.Tapscripts,
})
}
@@ -661,7 +660,7 @@ func (a *covenantArkClient) getAllBoardingUtxos(ctx context.Context) ([]types.Ut
VOut: uint32(i),
Amount: vout.Amount,
CreatedAt: createdAt,
Descriptor: addr.Descriptor,
Tapscripts: addr.Tapscripts,
})
}
}
@@ -680,17 +679,14 @@ func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context, opts
claimable := make([]types.Utxo, 0)
for _, addr := range boardingAddrs {
boardingScript, err := tree.ParseVtxoScript(addr.Descriptor)
boardingScript, err := tree.ParseVtxoScript(addr.Tapscripts)
if err != nil {
return nil, err
}
var boardingTimeout uint
if defaultVtxo, ok := boardingScript.(*tree.DefaultVtxoScript); ok {
boardingTimeout = defaultVtxo.ExitDelay
} else {
return nil, fmt.Errorf("unsupported boarding descriptor: %s", addr.Descriptor)
boardingTimeout, err := boardingScript.SmallestExitDelay()
if err != nil {
return nil, err
}
boardingUtxos, err := a.explorer.GetUtxos(addr.Address)
@@ -719,7 +715,7 @@ func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context, opts
}
}
u := utxo.ToUtxo(boardingTimeout, addr.Descriptor)
u := utxo.ToUtxo(boardingTimeout, addr.Tapscripts)
if u.SpendableAt.Before(now) {
continue
}
@@ -929,7 +925,7 @@ func (a *covenantArkClient) sendOffchain(
return "", fmt.Errorf("no offchain addresses found")
}
vtxos := make([]client.DescriptorVtxo, 0)
vtxos := make([]client.TapscriptsVtxo, 0)
spendableVtxos, err := a.getVtxos(ctx, withExpiryCoinselect, nil)
if err != nil {
@@ -944,9 +940,9 @@ func (a *covenantArkClient) sendOffchain(
}
if vtxoAddr == offchainAddr.Address {
vtxos = append(vtxos, client.DescriptorVtxo{
vtxos = append(vtxos, client.TapscriptsVtxo{
Vtxo: v,
Descriptor: offchainAddr.Descriptor,
Tapscripts: offchainAddr.Tapscripts,
})
}
}
@@ -958,7 +954,7 @@ func (a *covenantArkClient) sendOffchain(
}
var selectedBoardingCoins []types.Utxo
var selectedCoins []client.DescriptorVtxo
var selectedCoins []client.TapscriptsVtxo
var changeAmount uint64
// if no receivers, self send all selected coins
@@ -1009,7 +1005,7 @@ func (a *covenantArkClient) sendOffchain(
Txid: coin.Txid,
VOut: coin.VOut,
},
Descriptor: coin.Descriptor,
Tapscripts: coin.Tapscripts,
})
}
for _, coin := range selectedBoardingCoins {
@@ -1018,7 +1014,7 @@ func (a *covenantArkClient) sendOffchain(
Txid: coin.Txid,
VOut: coin.VOut,
},
Descriptor: coin.Descriptor,
Tapscripts: coin.Tapscripts,
})
}
@@ -1057,19 +1053,33 @@ func (a *covenantArkClient) addInputs(
return err
}
vtxoScript, err := tree.ParseVtxoScript(offchain.Descriptor)
vtxoScript, err := tree.ParseVtxoScript(offchain.Tapscripts)
if err != nil {
return err
}
var userPubkey, aspPubkey *secp256k1.PublicKey
forfeitClosure := vtxoScript.ForfeitClosures()[0]
switch s := vtxoScript.(type) {
case *tree.DefaultVtxoScript:
userPubkey = s.Owner
aspPubkey = s.Asp
default:
return fmt.Errorf("unsupported vtxo script: %T", s)
forfeitScript, err := forfeitClosure.Script()
if err != nil {
return err
}
forfeitLeaf := taproot.NewBaseTapElementsLeaf(forfeitScript)
_, taprootTree, err := vtxoScript.TapTree()
if err != nil {
return err
}
leafProof, err := taprootTree.GetTaprootMerkleProof(forfeitLeaf.TapHash())
if err != nil {
return err
}
controlBlock, err := taproot.ParseControlBlock(leafProof.Script)
if err != nil {
return err
}
for _, utxo := range utxos {
@@ -1088,37 +1098,6 @@ func (a *covenantArkClient) addInputs(
return err
}
vtxoScript := &tree.DefaultVtxoScript{
Owner: userPubkey,
Asp: aspPubkey,
ExitDelay: utxo.Delay,
}
forfeitClosure := &tree.MultisigClosure{
Pubkey: userPubkey,
AspPubkey: aspPubkey,
}
forfeitLeaf, err := forfeitClosure.Leaf()
if err != nil {
return err
}
_, taprootTree, err := vtxoScript.TapTree()
if err != nil {
return err
}
leafProof, err := taprootTree.GetTaprootMerkleProof(forfeitLeaf.TapHash())
if err != nil {
return err
}
controlBlock, err := taproot.ParseControlBlock(leafProof.Script)
if err != nil {
return err
}
inputIndex := len(updater.Pset.Inputs) - 1
if err := updater.AddInTapLeafScript(
@@ -1138,7 +1117,7 @@ func (a *covenantArkClient) addInputs(
func (a *covenantArkClient) handleRoundStream(
ctx context.Context,
paymentID string,
vtxosToSign []client.DescriptorVtxo,
vtxosToSign []client.TapscriptsVtxo,
boardingUtxos []types.Utxo,
receivers []client.Output,
) (string, error) {
@@ -1202,7 +1181,7 @@ func (a *covenantArkClient) handleRoundStream(
func (a *covenantArkClient) handleRoundFinalization(
ctx context.Context,
event client.RoundFinalizationEvent,
vtxos []client.DescriptorVtxo,
vtxos []client.TapscriptsVtxo,
boardingUtxos []types.Utxo,
receivers []client.Output,
) (signedForfeits []string, signedRoundTx string, err error) {
@@ -1233,28 +1212,20 @@ func (a *covenantArkClient) handleRoundFinalization(
}
for _, boardingUtxo := range boardingUtxos {
boardingVtxoScript, err := tree.ParseVtxoScript(boardingUtxo.Descriptor)
boardingVtxoScript, err := tree.ParseVtxoScript(boardingUtxo.Tapscripts)
if err != nil {
return nil, "", err
}
var forfeitClosure tree.Closure
forfeitClosure := boardingVtxoScript.ForfeitClosures()[0]
switch s := boardingVtxoScript.(type) {
case *tree.DefaultVtxoScript:
forfeitClosure = &tree.MultisigClosure{
Pubkey: s.Owner,
AspPubkey: a.AspPubkey,
}
default:
return nil, "", fmt.Errorf("unsupported boarding descriptor: %s", boardingUtxo.Descriptor)
}
forfeitLeaf, err := forfeitClosure.Leaf()
forfeitScript, err := forfeitClosure.Script()
if err != nil {
return nil, "", err
}
forfeitLeaf := taproot.NewBaseTapElementsLeaf(forfeitScript)
_, taprootTree, err := boardingVtxoScript.TapTree()
if err != nil {
return nil, "", err
@@ -1430,7 +1401,7 @@ func (a *covenantArkClient) validateOffChainReceiver(
func (a *covenantArkClient) createAndSignForfeits(
ctx context.Context,
vtxosToSign []client.DescriptorVtxo,
vtxosToSign []client.TapscriptsVtxo,
connectors []string,
feeRate chainfee.SatPerKVByte,
) ([]string, error) {
@@ -1452,7 +1423,7 @@ func (a *covenantArkClient) createAndSignForfeits(
}
for _, vtxo := range vtxosToSign {
vtxoScript, err := tree.ParseVtxoScript(vtxo.Descriptor)
vtxoScript, err := tree.ParseVtxoScript(vtxo.Tapscripts)
if err != nil {
return nil, err
}
@@ -1472,25 +1443,15 @@ func (a *covenantArkClient) createAndSignForfeits(
TxIndex: vtxo.VOut,
}
var forfeitClosure tree.Closure
var witnessSize int
forfeitClosure := vtxoScript.ForfeitClosures()[0]
switch s := vtxoScript.(type) {
case *tree.DefaultVtxoScript:
forfeitClosure = &tree.MultisigClosure{
Pubkey: s.Owner,
AspPubkey: a.AspPubkey,
}
witnessSize = 64 * 2
default:
return nil, fmt.Errorf("unsupported vtxo script: %T", s)
}
forfeitLeaf, err := forfeitClosure.Leaf()
forfeitScript, err := forfeitClosure.Script()
if err != nil {
return nil, err
}
forfeitLeaf := taproot.NewBaseTapElementsLeaf(forfeitScript)
leafProof, err := vtxoTapTree.GetTaprootMerkleProof(forfeitLeaf.TapHash())
if err != nil {
return nil, err
@@ -1512,7 +1473,7 @@ func (a *covenantArkClient) createAndSignForfeits(
RevealedScript: leafProof.Script,
ControlBlock: &ctrlBlock.ControlBlock,
},
witnessSize,
forfeitClosure.WitnessSize(),
txscript.WitnessV0PubKeyHashTy,
)
if err != nil {
@@ -1567,19 +1528,14 @@ func (a *covenantArkClient) coinSelectOnchain(
fetchedUtxos := make([]types.Utxo, 0)
for _, addr := range boardingAddrs {
boardingDescriptor := addr.Descriptor
boardingScript, err := tree.ParseVtxoScript(boardingDescriptor)
boardingScript, err := tree.ParseVtxoScript(addr.Tapscripts)
if err != nil {
return nil, 0, err
}
var boardingTimeout uint
if defaultVtxo, ok := boardingScript.(*tree.DefaultVtxoScript); ok {
boardingTimeout = defaultVtxo.ExitDelay
} else {
return nil, 0, fmt.Errorf("unsupported boarding descriptor: %s", boardingDescriptor)
boardingTimeout, err := boardingScript.SmallestExitDelay()
if err != nil {
return nil, 0, err
}
utxos, err := a.explorer.GetUtxos(addr.Address)
@@ -1588,7 +1544,7 @@ func (a *covenantArkClient) coinSelectOnchain(
}
for _, utxo := range utxos {
u := utxo.ToUtxo(boardingTimeout, addr.Descriptor)
u := utxo.ToUtxo(boardingTimeout, addr.Tapscripts)
if u.SpendableAt.Before(now) {
fetchedUtxos = append(fetchedUtxos, u)
}
@@ -1624,7 +1580,7 @@ func (a *covenantArkClient) coinSelectOnchain(
}
for _, utxo := range utxos {
u := utxo.ToUtxo(uint(a.UnilateralExitDelay), addr.Descriptor)
u := utxo.ToUtxo(uint(a.UnilateralExitDelay), addr.Tapscripts)
if u.SpendableAt.Before(now) {
fetchedUtxos = append(fetchedUtxos, u)
}

View File

@@ -857,7 +857,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
},
}
vtxos := make([]client.DescriptorVtxo, 0)
vtxos := make([]client.TapscriptsVtxo, 0)
spendableVtxos, err := a.getVtxos(ctx, nil)
if err != nil {
return "", err
@@ -871,9 +871,9 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
}
if vtxoAddr == offchainAddr.Address {
vtxos = append(vtxos, client.DescriptorVtxo{
vtxos = append(vtxos, client.TapscriptsVtxo{
Vtxo: v,
Descriptor: offchainAddr.Descriptor,
Tapscripts: offchainAddr.Tapscripts,
})
}
}
@@ -911,7 +911,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
Txid: coin.Txid,
VOut: coin.VOut,
},
Descriptor: coin.Descriptor,
Tapscripts: coin.Tapscripts,
})
}
for _, coin := range selectedBoardingCoins {
@@ -920,7 +920,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
Txid: coin.Txid,
VOut: coin.VOut,
},
Descriptor: coin.Descriptor,
Tapscripts: coin.Tapscripts,
})
}
@@ -1004,7 +1004,7 @@ func (a *covenantlessArkClient) SendAsync(
sumOfReceivers += receiver.Amount()
}
vtxos := make([]client.DescriptorVtxo, 0)
vtxos := make([]client.TapscriptsVtxo, 0)
opts := &CoinSelectOptions{
WithExpirySorting: withExpiryCoinselect,
}
@@ -1021,9 +1021,9 @@ func (a *covenantlessArkClient) SendAsync(
}
if vtxoAddr == offchainAddr.Address {
vtxos = append(vtxos, client.DescriptorVtxo{
vtxos = append(vtxos, client.TapscriptsVtxo{
Vtxo: v,
Descriptor: offchainAddr.Descriptor,
Tapscripts: offchainAddr.Tapscripts,
})
}
}
@@ -1048,35 +1048,27 @@ func (a *covenantlessArkClient) SendAsync(
inputs := make([]client.AsyncPaymentInput, 0, len(selectedCoins))
for _, coin := range selectedCoins {
vtxoScript, err := bitcointree.ParseVtxoScript(coin.Descriptor)
vtxoScript, err := bitcointree.ParseVtxoScript(coin.Tapscripts)
if err != nil {
return "", err
}
var forfeitClosure bitcointree.Closure
forfeitClosure := vtxoScript.ForfeitClosures()[0]
switch s := vtxoScript.(type) {
case *bitcointree.DefaultVtxoScript:
forfeitClosure = &bitcointree.MultisigClosure{
Pubkey: s.Owner,
AspPubkey: s.Asp,
}
default:
return "", fmt.Errorf("unsupported vtxo script: %T", s)
}
forfeitLeaf, err := forfeitClosure.Leaf()
forfeitScript, err := forfeitClosure.Script()
if err != nil {
return "", err
}
forfeitLeaf := txscript.NewBaseTapLeaf(forfeitScript)
inputs = append(inputs, client.AsyncPaymentInput{
Input: client.Input{
Outpoint: client.Outpoint{
Txid: coin.Txid,
VOut: coin.VOut,
},
Descriptor: coin.Descriptor,
Tapscripts: coin.Tapscripts,
},
ForfeitLeafHash: forfeitLeaf.TapHash(),
})
@@ -1158,7 +1150,7 @@ func (a *covenantlessArkClient) SetNostrNotificationRecipient(ctx context.Contex
return err
}
descriptorVtxos := make([]client.DescriptorVtxo, 0)
descriptorVtxos := make([]client.TapscriptsVtxo, 0)
for _, offchainAddr := range offchainAddrs {
for _, vtxo := range spendableVtxos {
vtxoAddr, err := vtxo.Address(a.AspPubkey, a.Network)
@@ -1167,9 +1159,9 @@ func (a *covenantlessArkClient) SetNostrNotificationRecipient(ctx context.Contex
}
if vtxoAddr == offchainAddr.Address {
descriptorVtxos = append(descriptorVtxos, client.DescriptorVtxo{
descriptorVtxos = append(descriptorVtxos, client.TapscriptsVtxo{
Vtxo: vtxo,
Descriptor: offchainAddr.Descriptor,
Tapscripts: offchainAddr.Tapscripts,
})
}
}
@@ -1187,35 +1179,24 @@ func (a *covenantlessArkClient) SetNostrNotificationRecipient(ctx context.Contex
}
// validate the vtxo script type
vtxoScript, err := bitcointree.ParseVtxoScript(v.Descriptor)
vtxoScript, err := bitcointree.ParseVtxoScript(v.Tapscripts)
if err != nil {
return err
}
var forfeitClosure bitcointree.Closure
var signingPubkey string
if defaultVtxoScript, ok := vtxoScript.(*bitcointree.DefaultVtxoScript); ok {
forfeitClosure = &bitcointree.MultisigClosure{
Pubkey: defaultVtxoScript.Owner,
AspPubkey: defaultVtxoScript.Asp,
}
signingPubkey = hex.EncodeToString(schnorr.SerializePubKey(defaultVtxoScript.Owner))
} else {
return fmt.Errorf("unsupported vtxo script: %T", vtxoScript)
}
forfeitClosure := vtxoScript.ForfeitClosures()[0]
_, tapTree, err := vtxoScript.TapTree()
if err != nil {
return err
}
forfeitLeaf, err := forfeitClosure.Leaf()
forfeitScript, err := forfeitClosure.Script()
if err != nil {
return err
}
forfeitLeaf := txscript.NewBaseTapLeaf(forfeitScript)
merkleProof, err := tapTree.GetTaprootMerkleProof(forfeitLeaf.TapHash())
if err != nil {
return err
@@ -1236,7 +1217,7 @@ func (a *covenantlessArkClient) SetNostrNotificationRecipient(ctx context.Contex
outpointBytes := append(txhash[:], voutBytes...)
sigMsg := sha256.Sum256(outpointBytes)
sig, err := a.wallet.SignMessage(ctx, sigMsg[:], signingPubkey)
sig, err := a.wallet.SignMessage(ctx, sigMsg[:])
if err != nil {
return err
}
@@ -1434,7 +1415,7 @@ func (a *covenantlessArkClient) sendOffchain(
return "", fmt.Errorf("no offchain addresses found")
}
vtxos := make([]client.DescriptorVtxo, 0)
vtxos := make([]client.TapscriptsVtxo, 0)
opts := &CoinSelectOptions{
WithExpirySorting: withExpiryCoinselect}
spendableVtxos, err := a.getVtxos(ctx, opts)
@@ -1450,9 +1431,9 @@ func (a *covenantlessArkClient) sendOffchain(
}
if vtxoAddr == offchainAddr.Address {
vtxos = append(vtxos, client.DescriptorVtxo{
vtxos = append(vtxos, client.TapscriptsVtxo{
Vtxo: v,
Descriptor: offchainAddr.Descriptor,
Tapscripts: offchainAddr.Tapscripts,
})
}
}
@@ -1464,7 +1445,7 @@ func (a *covenantlessArkClient) sendOffchain(
}
var selectedBoardingCoins []types.Utxo
var selectedCoins []client.DescriptorVtxo
var selectedCoins []client.TapscriptsVtxo
var changeAmount uint64
// if no receivers, self send all selected coins
@@ -1513,7 +1494,7 @@ func (a *covenantlessArkClient) sendOffchain(
Txid: coin.Txid,
VOut: coin.VOut,
},
Descriptor: coin.Descriptor,
Tapscripts: coin.Tapscripts,
})
}
for _, boardingUtxo := range selectedBoardingCoins {
@@ -1522,7 +1503,7 @@ func (a *covenantlessArkClient) sendOffchain(
Txid: boardingUtxo.Txid,
VOut: boardingUtxo.VOut,
},
Descriptor: boardingUtxo.Descriptor,
Tapscripts: boardingUtxo.Tapscripts,
})
}
@@ -1567,21 +1548,11 @@ func (a *covenantlessArkClient) addInputs(
return err
}
vtxoScript, err := tree.ParseVtxoScript(offchain.Descriptor)
vtxoScript, err := bitcointree.ParseVtxoScript(offchain.Tapscripts)
if err != nil {
return err
}
var userPubkey, aspPubkey *secp256k1.PublicKey
switch s := vtxoScript.(type) {
case *tree.DefaultVtxoScript:
userPubkey = s.Owner
aspPubkey = s.Asp
default:
return fmt.Errorf("unsupported vtxo script: %T", s)
}
for _, utxo := range utxos {
previousHash, err := chainhash.NewHashFromStr(utxo.Txid)
if err != nil {
@@ -1601,18 +1572,14 @@ func (a *covenantlessArkClient) addInputs(
Sequence: sequence,
})
vtxoScript := &bitcointree.DefaultVtxoScript{
Owner: userPubkey,
Asp: aspPubkey,
ExitDelay: utxo.Delay,
exitClosures := vtxoScript.ExitClosures()
if len(exitClosures) <= 0 {
return fmt.Errorf("no exit closures found")
}
exitClosure := &bitcointree.CSVSigClosure{
Pubkey: userPubkey,
Seconds: uint(utxo.Delay),
}
exitClosure := exitClosures[0]
exitLeaf, err := exitClosure.Leaf()
exitScript, err := exitClosure.Script()
if err != nil {
return err
}
@@ -1622,6 +1589,7 @@ func (a *covenantlessArkClient) addInputs(
return err
}
exitLeaf := txscript.NewBaseTapLeaf(exitScript)
leafProof, err := taprootTree.GetTaprootMerkleProof(exitLeaf.TapHash())
if err != nil {
return fmt.Errorf("failed to get taproot merkle proof: %s", err)
@@ -1644,7 +1612,7 @@ func (a *covenantlessArkClient) addInputs(
func (a *covenantlessArkClient) handleRoundStream(
ctx context.Context,
paymentID string,
vtxosToSign []client.DescriptorVtxo,
vtxosToSign []client.TapscriptsVtxo,
boardingUtxos []types.Utxo,
receivers []client.Output,
roundEphemeralKey *secp256k1.PrivateKey,
@@ -1763,12 +1731,12 @@ func (a *covenantlessArkClient) handleRoundStream(
func (a *covenantlessArkClient) handleRoundSigningStarted(
ctx context.Context, ephemeralKey *secp256k1.PrivateKey, event client.RoundSigningStartedEvent,
) (signerSession bitcointree.SignerSession, err error) {
sweepClosure := bitcointree.CSVSigClosure{
Pubkey: a.AspPubkey,
Seconds: uint(a.RoundLifetime),
sweepClosure := tree.CSVSigClosure{
MultisigClosure: tree.MultisigClosure{PubKeys: []*secp256k1.PublicKey{a.AspPubkey}},
Seconds: uint(a.RoundLifetime),
}
sweepTapLeaf, err := sweepClosure.Leaf()
script, err := sweepClosure.Script()
if err != nil {
return
}
@@ -1781,7 +1749,8 @@ func (a *covenantlessArkClient) handleRoundSigningStarted(
sharedOutput := roundTx.UnsignedTx.TxOut[0]
sharedOutputValue := sharedOutput.Value
sweepTapTree := txscript.AssembleTaprootScriptTree(*sweepTapLeaf)
sweepTapLeaf := txscript.NewBaseTapLeaf(script)
sweepTapTree := txscript.AssembleTaprootScriptTree(sweepTapLeaf)
root := sweepTapTree.RootNode.TapHash()
signerSession = bitcointree.NewTreeSignerSession(
@@ -1838,7 +1807,7 @@ func (a *covenantlessArkClient) handleRoundSigningNoncesGenerated(
func (a *covenantlessArkClient) handleRoundFinalization(
ctx context.Context,
event client.RoundFinalizationEvent,
vtxos []client.DescriptorVtxo,
vtxos []client.TapscriptsVtxo,
boardingUtxos []types.Utxo,
receivers []client.Output,
) ([]string, string, error) {
@@ -1870,27 +1839,20 @@ func (a *covenantlessArkClient) handleRoundFinalization(
}
for _, boardingUtxo := range boardingUtxos {
boardingVtxoScript, err := bitcointree.ParseVtxoScript(boardingUtxo.Descriptor)
boardingVtxoScript, err := bitcointree.ParseVtxoScript(boardingUtxo.Tapscripts)
if err != nil {
return nil, "", err
}
var myPubkey *secp256k1.PublicKey
switch v := boardingVtxoScript.(type) {
case *bitcointree.DefaultVtxoScript:
myPubkey = v.Owner
default:
return nil, "", fmt.Errorf("unsupported boarding descriptor: %s", boardingUtxo.Descriptor)
}
// add tapscript leaf
forfeitClosure := &bitcointree.MultisigClosure{
Pubkey: myPubkey,
AspPubkey: a.AspPubkey,
forfeitClosures := boardingVtxoScript.ForfeitClosures()
if len(forfeitClosures) <= 0 {
return nil, "", fmt.Errorf("no forfeit closures found")
}
forfeitLeaf, err := forfeitClosure.Leaf()
forfeitClosure := forfeitClosures[0]
forfeitScript, err := forfeitClosure.Script()
if err != nil {
return nil, "", err
}
@@ -1900,6 +1862,7 @@ func (a *covenantlessArkClient) handleRoundFinalization(
return nil, "", err
}
forfeitLeaf := txscript.NewBaseTapLeaf(forfeitScript)
forfeitProof, err := taprootTree.GetTaprootMerkleProof(forfeitLeaf.TapHash())
if err != nil {
return nil, "", fmt.Errorf("failed to get taproot merkle proof for boarding utxo: %s", err)
@@ -2070,7 +2033,7 @@ func (a *covenantlessArkClient) validateOffChainReceiver(
func (a *covenantlessArkClient) createAndSignForfeits(
ctx context.Context,
vtxosToSign []client.DescriptorVtxo,
vtxosToSign []client.TapscriptsVtxo,
connectors []string,
feeRate chainfee.SatPerKVByte,
) ([]string, error) {
@@ -2102,7 +2065,7 @@ func (a *covenantlessArkClient) createAndSignForfeits(
}
for _, vtxo := range vtxosToSign {
vtxoScript, err := bitcointree.ParseVtxoScript(vtxo.Descriptor)
vtxoScript, err := bitcointree.ParseVtxoScript(vtxo.Tapscripts)
if err != nil {
return nil, err
}
@@ -2127,25 +2090,19 @@ func (a *covenantlessArkClient) createAndSignForfeits(
Index: vtxo.VOut,
}
var forfeitClosure bitcointree.Closure
var witnessSize int
switch v := vtxoScript.(type) {
case *bitcointree.DefaultVtxoScript:
forfeitClosure = &bitcointree.MultisigClosure{
Pubkey: v.Owner,
AspPubkey: a.AspPubkey,
}
witnessSize = 64 * 2
default:
return nil, fmt.Errorf("unsupported vtxo script: %T", vtxoScript)
forfeitClosures := vtxoScript.ForfeitClosures()
if len(forfeitClosures) <= 0 {
return nil, fmt.Errorf("no forfeit closures found")
}
forfeitLeaf, err := forfeitClosure.Leaf()
forfeitClosure := forfeitClosures[0]
forfeitScript, err := forfeitClosure.Script()
if err != nil {
return nil, err
}
forfeitLeaf := txscript.NewBaseTapLeaf(forfeitScript)
leafProof, err := vtxoTapTree.GetTaprootMerkleProof(forfeitLeaf.TapHash())
if err != nil {
return nil, err
@@ -2168,7 +2125,7 @@ func (a *covenantlessArkClient) createAndSignForfeits(
RevealedScript: leafProof.Script,
ControlBlock: ctrlBlock,
},
witnessSize,
forfeitClosure.WitnessSize(),
parsedScript.Class(),
)
if err != nil {
@@ -2221,25 +2178,23 @@ func (a *covenantlessArkClient) coinSelectOnchain(
fetchedUtxos := make([]types.Utxo, 0)
for _, addr := range boardingAddrs {
boardingScript, err := bitcointree.ParseVtxoScript(addr.Descriptor)
boardingScript, err := bitcointree.ParseVtxoScript(addr.Tapscripts)
if err != nil {
return nil, 0, err
}
var boardingTimeout uint
if defaultVtxo, ok := boardingScript.(*bitcointree.DefaultVtxoScript); ok {
boardingTimeout = defaultVtxo.ExitDelay
} else {
return nil, 0, fmt.Errorf("unsupported boarding descriptor: %s", addr.Descriptor)
boardingTimeout, err := boardingScript.SmallestExitDelay()
if err != nil {
return nil, 0, err
}
utxos, err := a.explorer.GetUtxos(addr.Address)
if err != nil {
return nil, 0, err
}
for _, utxo := range utxos {
u := utxo.ToUtxo(boardingTimeout, addr.Descriptor)
u := utxo.ToUtxo(boardingTimeout, addr.Tapscripts)
if u.SpendableAt.Before(now) {
fetchedUtxos = append(fetchedUtxos, u)
}
@@ -2275,7 +2230,7 @@ func (a *covenantlessArkClient) coinSelectOnchain(
}
for _, utxo := range utxos {
u := utxo.ToUtxo(uint(a.UnilateralExitDelay), addr.Descriptor)
u := utxo.ToUtxo(uint(a.UnilateralExitDelay), addr.Tapscripts)
if u.SpendableAt.Before(now) {
fetchedUtxos = append(fetchedUtxos, u)
}
@@ -2407,7 +2362,7 @@ func (a *covenantlessArkClient) getAllBoardingUtxos(
VOut: uint32(i),
Amount: vout.Amount,
CreatedAt: createdAt,
Descriptor: addr.Descriptor,
Tapscripts: addr.Tapscripts,
Spent: spent,
})
}
@@ -2427,17 +2382,14 @@ func (a *covenantlessArkClient) getClaimableBoardingUtxos(ctx context.Context, o
claimable := make([]types.Utxo, 0)
for _, addr := range boardingAddrs {
boardingScript, err := bitcointree.ParseVtxoScript(addr.Descriptor)
boardingScript, err := bitcointree.ParseVtxoScript(addr.Tapscripts)
if err != nil {
return nil, err
}
var boardingTimeout uint
if defaultVtxo, ok := boardingScript.(*bitcointree.DefaultVtxoScript); ok {
boardingTimeout = defaultVtxo.ExitDelay
} else {
return nil, fmt.Errorf("unsupported boarding descriptor: %s", addr.Descriptor)
boardingTimeout, err := boardingScript.SmallestExitDelay()
if err != nil {
return nil, err
}
boardingUtxos, err := a.explorer.GetUtxos(addr.Address)
@@ -2466,7 +2418,7 @@ func (a *covenantlessArkClient) getClaimableBoardingUtxos(ctx context.Context, o
}
}
u := utxo.ToUtxo(boardingTimeout, addr.Descriptor)
u := utxo.ToUtxo(boardingTimeout, addr.Tapscripts)
if u.SpendableAt.Before(now) {
continue
}

View File

@@ -53,8 +53,8 @@ type SpentStatus struct {
SpentBy string `json:"txid,omitempty"`
}
func (e ExplorerUtxo) ToUtxo(delay uint, descriptor string) types.Utxo {
return newUtxo(e, delay, descriptor)
func (e ExplorerUtxo) ToUtxo(delay uint, tapscripts []string) types.Utxo {
return newUtxo(e, delay, tapscripts)
}
type Explorer interface {
@@ -415,7 +415,7 @@ func parseBitcoinTx(txStr string) (string, string, error) {
return txhex, txid, nil
}
func newUtxo(explorerUtxo ExplorerUtxo, delay uint, descriptor string) types.Utxo {
func newUtxo(explorerUtxo ExplorerUtxo, delay uint, tapscripts []string) types.Utxo {
utxoTime := explorerUtxo.Status.Blocktime
createdAt := time.Unix(utxoTime, 0)
if utxoTime == 0 {
@@ -431,6 +431,6 @@ func newUtxo(explorerUtxo ExplorerUtxo, delay uint, descriptor string) types.Utx
Delay: delay,
SpendableAt: time.Unix(utxoTime, 0).Add(time.Duration(delay) * time.Second),
CreatedAt: createdAt,
Descriptor: descriptor,
Tapscripts: tapscripts,
}
}

View File

@@ -24,12 +24,12 @@ import (
func CoinSelect(
boardingUtxos []types.Utxo,
vtxos []client.DescriptorVtxo,
vtxos []client.TapscriptsVtxo,
amount,
dust uint64,
sortByExpirationTime bool,
) ([]types.Utxo, []client.DescriptorVtxo, uint64, error) {
selected, notSelected := make([]client.DescriptorVtxo, 0), make([]client.DescriptorVtxo, 0)
) ([]types.Utxo, []client.TapscriptsVtxo, uint64, error) {
selected, notSelected := make([]client.TapscriptsVtxo, 0), make([]client.TapscriptsVtxo, 0)
selectedBoarding, notSelectedBoarding := make([]types.Utxo, 0), make([]types.Utxo, 0)
selectedAmount := uint64(0)

View File

@@ -7,7 +7,6 @@ import (
"strings"
"time"
"github.com/ark-network/ark/common/bitcointree"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/pkg/client-sdk/client"
"github.com/ark-network/ark/pkg/client-sdk/explorer"
@@ -172,7 +171,7 @@ func findCovenantlessSweepClosure(
var seconds uint
var sweepClosure *txscript.TapLeaf
for _, tapLeaf := range tx.Inputs[0].TaprootLeafScript {
closure := &bitcointree.CSVSigClosure{}
closure := &tree.CSVSigClosure{}
valid, err := closure.Decode(tapLeaf.Script)
if err != nil {
continue

View File

@@ -113,7 +113,7 @@ type Utxo struct {
Delay uint
SpendableAt time.Time
CreatedAt time.Time
Descriptor string
Tapscripts []string
Spent bool
}

View File

@@ -9,6 +9,7 @@ import (
"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/pkg/client-sdk/explorer"
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
"github.com/ark-network/ark/pkg/client-sdk/types"
@@ -43,7 +44,7 @@ func NewBitcoinWallet(
func (w *bitcoinWallet) GetAddresses(
ctx context.Context,
) ([]wallet.DescriptorAddress, []wallet.DescriptorAddress, []wallet.DescriptorAddress, error) {
) ([]wallet.TapscriptsAddress, []wallet.TapscriptsAddress, []wallet.TapscriptsAddress, error) {
offchainAddr, boardingAddr, err := w.getAddress(ctx)
if err != nil {
return nil, nil, nil, err
@@ -69,21 +70,21 @@ func (w *bitcoinWallet) GetAddresses(
return nil, nil, nil, err
}
offchainAddrs := []wallet.DescriptorAddress{
offchainAddrs := []wallet.TapscriptsAddress{
{
Descriptor: offchainAddr.Descriptor,
Tapscripts: offchainAddr.Tapscripts,
Address: encodedOffchainAddr,
},
}
boardingAddrs := []wallet.DescriptorAddress{
boardingAddrs := []wallet.TapscriptsAddress{
{
Descriptor: boardingAddr.Descriptor,
Tapscripts: boardingAddr.Tapscripts,
Address: boardingAddr.Address,
},
}
redemptionAddrs := []wallet.DescriptorAddress{
redemptionAddrs := []wallet.TapscriptsAddress{
{
Descriptor: offchainAddr.Descriptor,
Tapscripts: offchainAddr.Tapscripts,
Address: redemptionAddr.EncodeAddress(),
},
}
@@ -92,7 +93,7 @@ func (w *bitcoinWallet) GetAddresses(
func (w *bitcoinWallet) NewAddress(
ctx context.Context, _ bool,
) (*wallet.DescriptorAddress, *wallet.DescriptorAddress, error) {
) (*wallet.TapscriptsAddress, *wallet.TapscriptsAddress, error) {
offchainAddr, boardingAddr, err := w.getAddress(ctx)
if err != nil {
return nil, nil, err
@@ -103,34 +104,34 @@ func (w *bitcoinWallet) NewAddress(
return nil, nil, err
}
return &wallet.DescriptorAddress{
Descriptor: offchainAddr.Descriptor,
return &wallet.TapscriptsAddress{
Tapscripts: offchainAddr.Tapscripts,
Address: encodedOffchainAddr,
}, boardingAddr, nil
}
func (w *bitcoinWallet) NewAddresses(
ctx context.Context, _ bool, num int,
) ([]wallet.DescriptorAddress, []wallet.DescriptorAddress, error) {
) ([]wallet.TapscriptsAddress, []wallet.TapscriptsAddress, error) {
offchainAddr, boardingAddr, err := w.getAddress(ctx)
if err != nil {
return nil, nil, err
}
offchainAddrs := make([]wallet.DescriptorAddress, 0, num)
boardingAddrs := make([]wallet.DescriptorAddress, 0, num)
offchainAddrs := make([]wallet.TapscriptsAddress, 0, num)
boardingAddrs := make([]wallet.TapscriptsAddress, 0, num)
for i := 0; i < num; i++ {
encodedOffchainAddr, err := offchainAddr.Address.Encode()
if err != nil {
return nil, nil, err
}
offchainAddrs = append(offchainAddrs, wallet.DescriptorAddress{
Descriptor: offchainAddr.Descriptor,
offchainAddrs = append(offchainAddrs, wallet.TapscriptsAddress{
Tapscripts: offchainAddr.Tapscripts,
Address: encodedOffchainAddr,
})
boardingAddrs = append(boardingAddrs, wallet.DescriptorAddress{
Descriptor: boardingAddr.Descriptor,
boardingAddrs = append(boardingAddrs, wallet.TapscriptsAddress{
Tapscripts: boardingAddr.Tapscripts,
Address: boardingAddr.Address,
})
}
@@ -192,12 +193,12 @@ func (s *bitcoinWallet) SignTransaction(
)
txsighashes := txscript.NewTxSigHashes(updater.Upsbt.UnsignedTx, prevoutFetcher)
myPubkey := schnorr.SerializePubKey(s.walletData.Pubkey)
for i, input := range ptx.Inputs {
if len(input.TaprootLeafScript) > 0 {
pubkey := s.walletData.Pubkey
for _, leaf := range input.TaprootLeafScript {
closure, err := bitcointree.DecodeClosure(leaf.Script)
closure, err := tree.DecodeClosure(leaf.Script)
if err != nil {
return "", err
}
@@ -205,10 +206,20 @@ func (s *bitcoinWallet) SignTransaction(
sign := false
switch c := closure.(type) {
case *bitcointree.CSVSigClosure:
sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], pubkey.SerializeCompressed()[1:])
case *bitcointree.MultisigClosure:
sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], pubkey.SerializeCompressed()[1:])
case *tree.CSVSigClosure:
for _, key := range c.MultisigClosure.PubKeys {
if bytes.Equal(schnorr.SerializePubKey(key), myPubkey) {
sign = true
break
}
}
case *tree.MultisigClosure:
for _, key := range c.PubKeys {
if bytes.Equal(schnorr.SerializePubKey(key), myPubkey) {
sign = true
break
}
}
}
if sign {
@@ -235,7 +246,7 @@ func (s *bitcoinWallet) SignTransaction(
return "", err
}
if !sig.Verify(preimage, pubkey) {
if !sig.Verify(preimage, s.walletData.Pubkey) {
return "", fmt.Errorf("signature verification failed")
}
@@ -244,7 +255,7 @@ func (s *bitcoinWallet) SignTransaction(
}
updater.Upsbt.Inputs[i].TaprootScriptSpendSig = append(updater.Upsbt.Inputs[i].TaprootScriptSpendSig, &psbt.TaprootScriptSpendSig{
XOnlyPubKey: schnorr.SerializePubKey(pubkey),
XOnlyPubKey: myPubkey,
LeafHash: hash.CloneBytes(),
Signature: sig.Serialize(),
SigHash: txscript.SigHashDefault,
@@ -259,17 +270,12 @@ func (s *bitcoinWallet) SignTransaction(
}
func (w *bitcoinWallet) SignMessage(
ctx context.Context, message []byte, pubkey string,
ctx context.Context, message []byte,
) (string, error) {
if w.IsLocked() {
return "", fmt.Errorf("wallet is locked")
}
walletPubkeyHex := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey))
if walletPubkeyHex != pubkey {
return "", fmt.Errorf("pubkey mismatch, cannot sign message")
}
sig, err := schnorr.Sign(w.privateKey, message)
if err != nil {
return "", err
@@ -278,14 +284,16 @@ func (w *bitcoinWallet) SignMessage(
return hex.EncodeToString(sig.Serialize()), nil
}
type addressWithTapscripts struct {
Address common.Address
Tapscripts []string
}
func (w *bitcoinWallet) getAddress(
ctx context.Context,
) (
*struct {
Address common.Address
Descriptor string
},
*wallet.DescriptorAddress,
*addressWithTapscripts,
*wallet.TapscriptsAddress,
error,
) {
if w.walletData == nil {
@@ -299,11 +307,11 @@ func (w *bitcoinWallet) getAddress(
netParams := utils.ToBitcoinNetwork(data.Network)
defaultVtxoScript := &bitcointree.DefaultVtxoScript{
Asp: data.AspPubkey,
Owner: w.walletData.Pubkey,
ExitDelay: uint(data.UnilateralExitDelay),
}
defaultVtxoScript := bitcointree.NewDefaultVtxoScript(
w.walletData.Pubkey,
data.AspPubkey,
uint(data.UnilateralExitDelay),
)
vtxoTapKey, _, err := defaultVtxoScript.TapTree()
if err != nil {
@@ -316,16 +324,12 @@ func (w *bitcoinWallet) getAddress(
VtxoTapKey: vtxoTapKey,
}
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey))
descriptorStr := strings.ReplaceAll(
data.BoardingDescriptorTemplate, "USER", myPubkeyStr,
boardingVtxoScript := bitcointree.NewDefaultVtxoScript(
w.walletData.Pubkey,
data.AspPubkey,
uint(data.UnilateralExitDelay*2),
)
boardingVtxoScript, err := bitcointree.ParseVtxoScript(descriptorStr)
if err != nil {
return nil, nil, err
}
boardingTapKey, _, err := boardingVtxoScript.TapTree()
if err != nil {
return nil, nil, err
@@ -339,14 +343,22 @@ func (w *bitcoinWallet) getAddress(
return nil, nil, err
}
return &struct {
Address common.Address
Descriptor string
}{
*offchainAddress, defaultVtxoScript.ToDescriptor(),
tapscripts, err := defaultVtxoScript.Encode()
if err != nil {
return nil, nil, err
}
boardingTapscripts, err := boardingVtxoScript.Encode()
if err != nil {
return nil, nil, err
}
return &addressWithTapscripts{
Address: *offchainAddress,
Tapscripts: tapscripts,
},
&wallet.DescriptorAddress{
Descriptor: descriptorStr,
&wallet.TapscriptsAddress{
Tapscripts: boardingTapscripts,
Address: boardingAddr.EncodeAddress(),
},
nil

View File

@@ -5,7 +5,6 @@ import (
"context"
"encoding/hex"
"fmt"
"strings"
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/common/tree"
@@ -44,7 +43,7 @@ func NewLiquidWallet(
func (w *liquidWallet) GetAddresses(
ctx context.Context,
) ([]wallet.DescriptorAddress, []wallet.DescriptorAddress, []wallet.DescriptorAddress, error) {
) ([]wallet.TapscriptsAddress, []wallet.TapscriptsAddress, []wallet.TapscriptsAddress, error) {
offchainAddr, boardingAddr, err := w.getAddress(ctx)
if err != nil {
return nil, nil, nil, err
@@ -72,22 +71,22 @@ func (w *liquidWallet) GetAddresses(
return nil, nil, nil, err
}
offchainAddrs := []wallet.DescriptorAddress{
offchainAddrs := []wallet.TapscriptsAddress{
{
Descriptor: offchainAddr.Descriptor,
Tapscripts: offchainAddr.Tapscripts,
Address: encodedOffchainAddr,
},
}
boardingAddrs := []wallet.DescriptorAddress{
boardingAddrs := []wallet.TapscriptsAddress{
{
Descriptor: boardingAddr.Descriptor,
Tapscripts: boardingAddr.Tapscripts,
Address: boardingAddr.Address,
},
}
redemptionAddrs := []wallet.DescriptorAddress{
redemptionAddrs := []wallet.TapscriptsAddress{
{
Descriptor: offchainAddr.Descriptor,
Tapscripts: offchainAddr.Tapscripts,
Address: redemptionAddr,
},
}
@@ -97,7 +96,7 @@ func (w *liquidWallet) GetAddresses(
func (w *liquidWallet) NewAddress(
ctx context.Context, _ bool,
) (*wallet.DescriptorAddress, *wallet.DescriptorAddress, error) {
) (*wallet.TapscriptsAddress, *wallet.TapscriptsAddress, error) {
offchainAddr, boardingAddr, err := w.getAddress(ctx)
if err != nil {
return nil, nil, err
@@ -108,37 +107,37 @@ func (w *liquidWallet) NewAddress(
return nil, nil, err
}
return &wallet.DescriptorAddress{
Descriptor: offchainAddr.Descriptor,
return &wallet.TapscriptsAddress{
Tapscripts: offchainAddr.Tapscripts,
Address: encodedOffchainAddr,
}, &wallet.DescriptorAddress{
Descriptor: boardingAddr.Descriptor,
}, &wallet.TapscriptsAddress{
Tapscripts: boardingAddr.Tapscripts,
Address: boardingAddr.Address,
}, nil
}
func (w *liquidWallet) NewAddresses(
ctx context.Context, _ bool, num int,
) ([]wallet.DescriptorAddress, []wallet.DescriptorAddress, error) {
) ([]wallet.TapscriptsAddress, []wallet.TapscriptsAddress, error) {
offchainAddr, boardingAddr, err := w.getAddress(ctx)
if err != nil {
return nil, nil, err
}
offchainAddrs := make([]wallet.DescriptorAddress, 0, num)
boardingAddrs := make([]wallet.DescriptorAddress, 0, num)
offchainAddrs := make([]wallet.TapscriptsAddress, 0, num)
boardingAddrs := make([]wallet.TapscriptsAddress, 0, num)
for i := 0; i < num; i++ {
encodedOffchainAddr, err := offchainAddr.Address.Encode()
if err != nil {
return nil, nil, err
}
offchainAddrs = append(offchainAddrs, wallet.DescriptorAddress{
Descriptor: offchainAddr.Descriptor,
offchainAddrs = append(offchainAddrs, wallet.TapscriptsAddress{
Tapscripts: offchainAddr.Tapscripts,
Address: encodedOffchainAddr,
})
boardingAddrs = append(boardingAddrs, wallet.DescriptorAddress{
Descriptor: boardingAddr.Descriptor,
boardingAddrs = append(boardingAddrs, wallet.TapscriptsAddress{
Tapscripts: boardingAddr.Tapscripts,
Address: boardingAddr.Address,
})
}
@@ -212,7 +211,7 @@ func (s *liquidWallet) SignTransaction(
prevoutsAssets = append(prevoutsAssets, input.WitnessUtxo.Asset)
}
serializedPubKey := s.walletData.Pubkey.SerializeCompressed()
myPubkey := schnorr.SerializePubKey(s.walletData.Pubkey)
for i, input := range pset.Inputs {
if len(input.TapLeafScript) > 0 {
@@ -230,9 +229,19 @@ func (s *liquidWallet) SignTransaction(
sign := false
switch c := closure.(type) {
case *tree.CSVSigClosure:
sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], serializedPubKey[1:])
for _, key := range c.MultisigClosure.PubKeys {
if bytes.Equal(schnorr.SerializePubKey(key), myPubkey) {
sign = true
break
}
}
case *tree.MultisigClosure:
sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], serializedPubKey[1:])
for _, key := range c.PubKeys {
if bytes.Equal(schnorr.SerializePubKey(key), myPubkey) {
sign = true
break
}
}
}
if sign {
@@ -288,17 +297,12 @@ func (s *liquidWallet) SignTransaction(
}
func (w *liquidWallet) SignMessage(
ctx context.Context, message []byte, pubkey string,
ctx context.Context, message []byte,
) (string, error) {
if w.IsLocked() {
return "", fmt.Errorf("wallet is locked")
}
walletPubkeyHex := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey))
if walletPubkeyHex != pubkey {
return "", fmt.Errorf("pubkey mismatch, cannot sign message")
}
sig, err := schnorr.Sign(w.privateKey, message)
if err != nil {
return "", err
@@ -310,11 +314,8 @@ func (w *liquidWallet) SignMessage(
func (w *liquidWallet) getAddress(
ctx context.Context,
) (
*struct {
Address common.Address
Descriptor string
},
*wallet.DescriptorAddress,
*addressWithTapscripts,
*wallet.TapscriptsAddress,
error,
) {
if w.walletData == nil {
@@ -328,11 +329,11 @@ func (w *liquidWallet) getAddress(
liquidNet := utils.ToElementsNetwork(data.Network)
vtxoScript := &tree.DefaultVtxoScript{
Owner: w.walletData.Pubkey,
Asp: data.AspPubkey,
ExitDelay: uint(data.UnilateralExitDelay),
}
vtxoScript := tree.NewDefaultVtxoScript(
w.walletData.Pubkey,
data.AspPubkey,
uint(data.UnilateralExitDelay),
)
vtxoTapKey, _, err := vtxoScript.TapTree()
if err != nil {
@@ -345,22 +346,18 @@ func (w *liquidWallet) getAddress(
VtxoTapKey: vtxoTapKey,
}
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey))
descriptorStr := strings.ReplaceAll(
data.BoardingDescriptorTemplate, "USER", myPubkeyStr,
boardingVtxoScript := tree.NewDefaultVtxoScript(
w.walletData.Pubkey,
data.AspPubkey,
uint(data.UnilateralExitDelay*2),
)
onboardingScript, err := tree.ParseVtxoScript(descriptorStr)
boardingTapKey, _, err := boardingVtxoScript.TapTree()
if err != nil {
return nil, nil, err
}
tapKey, _, err := onboardingScript.TapTree()
if err != nil {
return nil, nil, err
}
p2tr, err := payment.FromTweakedKey(tapKey, &liquidNet, nil)
p2tr, err := payment.FromTweakedKey(boardingTapKey, &liquidNet, nil)
if err != nil {
return nil, nil, err
}
@@ -370,14 +367,21 @@ func (w *liquidWallet) getAddress(
return nil, nil, err
}
return &struct {
Address common.Address
Descriptor string
}{
tapscripts, err := vtxoScript.Encode()
if err != nil {
return nil, nil, err
}
boardingTapscripts, err := boardingVtxoScript.Encode()
if err != nil {
return nil, nil, err
}
return &addressWithTapscripts{
Address: *offchainAddr,
Descriptor: vtxoScript.ToDescriptor(),
}, &wallet.DescriptorAddress{
Descriptor: descriptorStr,
Tapscripts: tapscripts,
}, &wallet.TapscriptsAddress{
Tapscripts: boardingTapscripts,
Address: boardingAddr,
}, nil
}

View File

@@ -10,8 +10,8 @@ const (
SingleKeyWallet = "singlekey"
)
type DescriptorAddress struct {
Descriptor string
type TapscriptsAddress struct {
Tapscripts []string
Address string
}
@@ -25,18 +25,18 @@ type WalletService interface {
IsLocked() bool
GetAddresses(
ctx context.Context,
) (offchainAddresses, boardingAddresses, redemptionAddresses []DescriptorAddress, err error)
) (offchainAddresses, boardingAddresses, redemptionAddresses []TapscriptsAddress, err error)
NewAddress(
ctx context.Context, change bool,
) (offchainAddr, onchainAddr *DescriptorAddress, err error)
) (offchainAddr, onchainAddr *TapscriptsAddress, err error)
NewAddresses(
ctx context.Context, change bool, num int,
) (offchainAddresses, onchainAddresses []DescriptorAddress, err error)
) (offchainAddresses, onchainAddresses []TapscriptsAddress, err error)
SignTransaction(
ctx context.Context, explorerSvc explorer.Explorer, tx string,
) (signedTx string, err error)
SignMessage(
ctx context.Context, message []byte, pubkey string,
ctx context.Context, message []byte,
) (signature string, err error)
Dump(ctx context.Context) (seed string, err error)
}