Files
ark/common/bitcointree/builder.go
Pietralberto Mazza 72a7f29bab Add CreatePayment and CompletePayment (#229)
Co-authored-by: Marco Argentieri <tiero@users.noreply.github.com>

* Add claim command

* Persist pending data in sqlite repo

* Remove debug log

* Return pending data at interface level

* Fix unlocking btc wallet after restart

* Lint & Fix whitelist permissions

* Fix send command for covenant

* Update client/covenantless/claim.go

Signed-off-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>

* Fix

* Pay for min relay fee instead of estimating fees for redeem and unconf forfeit txs

* Add support for pending payments (coventanless)

* Fixes

* Fixes

* Improve verbosity

* Fix coin selection

* Fix

---------

Signed-off-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
Co-authored-by: louisinger <louis@vulpem.com>
Co-authored-by: Marco Argentieri <tiero@users.noreply.github.com>
Co-authored-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
2024-08-10 19:18:02 +02:00

387 lines
8.9 KiB
Go

package bitcointree
import (
"encoding/hex"
"fmt"
"github.com/ark-network/ark/common/tree"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)
// CraftSharedOutput returns the taproot script and the amount of the initial root output
func CraftSharedOutput(
cosigners []*secp256k1.PublicKey, aspPubkey *secp256k1.PublicKey, receivers []Receiver,
feeSatsPerNode uint64, roundLifetime, unilateralExitDelay int64,
) ([]byte, int64, error) {
aggregatedKey, _, err := createAggregatedKeyWithSweep(
cosigners, aspPubkey, roundLifetime,
)
if err != nil {
return nil, 0, err
}
root, err := createRootNode(aggregatedKey, cosigners, aspPubkey, receivers, feeSatsPerNode, unilateralExitDelay)
if err != nil {
return nil, 0, err
}
amount := root.getAmount() + int64(feeSatsPerNode)
scriptPubKey, err := taprootOutputScript(aggregatedKey.FinalKey)
if err != nil {
return nil, 0, err
}
return scriptPubKey, amount, err
}
// CraftCongestionTree creates all the tree's transactions
func CraftCongestionTree(
initialInput *wire.OutPoint, cosigners []*secp256k1.PublicKey, aspPubkey *secp256k1.PublicKey, receivers []Receiver,
feeSatsPerNode uint64, roundLifetime, unilateralExitDelay int64,
) (tree.CongestionTree, error) {
aggregatedKey, sweepTapLeaf, err := createAggregatedKeyWithSweep(
cosigners, aspPubkey, roundLifetime,
)
if err != nil {
return nil, err
}
root, err := createRootNode(aggregatedKey, cosigners, aspPubkey, receivers, feeSatsPerNode, unilateralExitDelay)
if err != nil {
return nil, err
}
congestionTree := make(tree.CongestionTree, 0)
ins := []*wire.OutPoint{initialInput}
nodes := []node{root}
for len(nodes) > 0 {
nextNodes := make([]node, 0)
nextInputsArgs := make([]*wire.OutPoint, 0)
treeLevel := make([]tree.Node, 0)
for i, node := range nodes {
treeNode, err := getTreeNode(node, ins[i], schnorr.SerializePubKey(aggregatedKey.PreTweakedKey), sweepTapLeaf, cosigners)
if err != nil {
return nil, err
}
nodeTxHash, err := chainhash.NewHashFromStr(treeNode.Txid)
if err != nil {
return nil, err
}
treeLevel = append(treeLevel, treeNode)
children := node.getChildren()
for i, child := range children {
nextNodes = append(nextNodes, child)
nextInputsArgs = append(nextInputsArgs, &wire.OutPoint{
Hash: *nodeTxHash,
Index: uint32(i),
})
}
}
congestionTree = append(congestionTree, treeLevel)
nodes = append([]node{}, nextNodes...)
ins = append([]*wire.OutPoint{}, nextInputsArgs...)
}
return congestionTree, nil
}
type node interface {
getAmount() int64 // returns the input amount of the node = sum of all receivers' amounts + fees
getOutputs() ([]*wire.TxOut, error)
getChildren() []node
}
type leaf struct {
aspKey *secp256k1.PublicKey
vtxoKey *secp256k1.PublicKey
exitDelay int64
amount int64
}
type branch struct {
aggregatedKey *musig2.AggregateKey
cosigners []*secp256k1.PublicKey
children []node
feeAmount int64
}
func (b *branch) getChildren() []node {
return b.children
}
func (l *leaf) getChildren() []node {
return []node{}
}
func (b *branch) getAmount() int64 {
amount := int64(0)
for _, child := range b.children {
amount += child.getAmount()
amount += b.feeAmount
}
return amount
}
func (l *leaf) getAmount() int64 {
return l.amount
}
func (l *leaf) getOutputs() ([]*wire.TxOut, error) {
redeemClosure := &CSVSigClosure{
Pubkey: l.vtxoKey,
Seconds: uint(l.exitDelay),
}
redeemLeaf, err := redeemClosure.Leaf()
if err != nil {
return nil, err
}
forfeitClosure := &MultisigClosure{
Pubkey: l.vtxoKey,
AspPubkey: l.aspKey,
}
forfeitLeaf, err := forfeitClosure.Leaf()
if err != nil {
return nil, err
}
leafTaprootTree := txscript.AssembleTaprootScriptTree(
*redeemLeaf, *forfeitLeaf,
)
root := leafTaprootTree.RootNode.TapHash()
taprootKey := txscript.ComputeTaprootOutputKey(
UnspendableKey(),
root[:],
)
script, err := taprootOutputScript(taprootKey)
if err != nil {
return nil, err
}
output := &wire.TxOut{
Value: l.amount,
PkScript: script,
}
return []*wire.TxOut{output}, nil
}
func (b *branch) getOutputs() ([]*wire.TxOut, error) {
sharedOutputScript, err := taprootOutputScript(b.aggregatedKey.FinalKey)
if err != nil {
return nil, err
}
outputs := make([]*wire.TxOut, 0)
for _, child := range b.children {
outputs = append(outputs, &wire.TxOut{
Value: child.getAmount() + b.feeAmount,
PkScript: sharedOutputScript,
})
}
return outputs, nil
}
func getTreeNode(
n node,
input *wire.OutPoint,
inputTapInternalKey []byte,
inputSweepTapLeaf *psbt.TaprootTapLeafScript,
cosigners []*secp256k1.PublicKey,
) (tree.Node, error) {
partialTx, err := getTx(n, input, inputTapInternalKey, inputSweepTapLeaf, cosigners)
if err != nil {
return tree.Node{}, err
}
txid := partialTx.UnsignedTx.TxHash().String()
tx, err := partialTx.B64Encode()
if err != nil {
return tree.Node{}, err
}
return tree.Node{
Txid: txid,
Tx: tx,
ParentTxid: input.Hash.String(),
Leaf: len(n.getChildren()) == 0,
}, nil
}
func getTx(
n node,
input *wire.OutPoint,
inputTapInternalKey []byte,
inputSweepTapLeaf *psbt.TaprootTapLeafScript,
cosigners []*secp256k1.PublicKey,
) (*psbt.Packet, error) {
outputs, err := n.getOutputs()
if err != nil {
return nil, err
}
tx, err := psbt.New([]*wire.OutPoint{input}, outputs, 2, 0, []uint32{wire.MaxTxInSequenceNum})
if err != nil {
return nil, err
}
updater, err := psbt.NewUpdater(tx)
if err != nil {
return nil, err
}
if err := updater.AddInSighashType(0, int(txscript.SigHashDefault)); err != nil {
return nil, err
}
tx.Inputs[0].TaprootInternalKey = inputTapInternalKey
tx.Inputs[0].TaprootLeafScript = []*psbt.TaprootTapLeafScript{inputSweepTapLeaf}
for _, cosigner := range cosigners {
if err := AddCosignerKey(0, tx, cosigner); err != nil {
return nil, err
}
}
return tx, nil
}
func createRootNode(
aggregatedKey *musig2.AggregateKey, cosigners []*secp256k1.PublicKey,
aspPubkey *secp256k1.PublicKey, receivers []Receiver,
feeSatsPerNode uint64, unilateralExitDelay int64,
) (root node, err error) {
if len(receivers) == 0 {
return nil, fmt.Errorf("no receivers provided")
}
nodes := make([]node, 0, len(receivers))
for _, r := range receivers {
pubkeyBytes, err := hex.DecodeString(r.Pubkey)
if err != nil {
return nil, err
}
receiverKey, err := secp256k1.ParsePubKey(pubkeyBytes)
if err != nil {
return nil, err
}
leafNode := &leaf{
aspKey: aspPubkey,
vtxoKey: receiverKey,
exitDelay: unilateralExitDelay,
amount: int64(r.Amount),
}
nodes = append(nodes, leafNode)
}
for len(nodes) > 1 {
nodes, err = createUpperLevel(nodes, aggregatedKey, cosigners, int64(feeSatsPerNode))
if err != nil {
return
}
}
return nodes[0], nil
}
func createAggregatedKeyWithSweep(
cosigners []*secp256k1.PublicKey, aspPubkey *secp256k1.PublicKey, roundLifetime int64,
) (*musig2.AggregateKey, *psbt.TaprootTapLeafScript, error) {
sweepClosure := &CSVSigClosure{
Pubkey: aspPubkey,
Seconds: uint(roundLifetime),
}
sweepLeaf, err := sweepClosure.Leaf()
if err != nil {
return nil, nil, err
}
tapTree := txscript.AssembleTaprootScriptTree(*sweepLeaf)
tapTreeRoot := tapTree.RootNode.TapHash()
aggregatedKey, err := AggregateKeys(
cosigners, tapTreeRoot[:],
)
if err != nil {
return nil, nil, err
}
index := tapTree.LeafProofIndex[sweepLeaf.TapHash()]
proof := tapTree.LeafMerkleProofs[index]
controlBlock := proof.ToControlBlock(aggregatedKey.PreTweakedKey)
controlBlockBytes, err := controlBlock.ToBytes()
if err != nil {
return nil, nil, err
}
tapLeaf := &psbt.TaprootTapLeafScript{
ControlBlock: controlBlockBytes,
Script: sweepLeaf.Script,
LeafVersion: sweepLeaf.LeafVersion,
}
return aggregatedKey, tapLeaf, nil
}
func createUpperLevel(nodes []node, aggregatedKey *musig2.AggregateKey, cosigners []*secp256k1.PublicKey, feeAmount int64) ([]node, error) {
if len(nodes)%2 != 0 {
last := nodes[len(nodes)-1]
pairs, err := createUpperLevel(nodes[:len(nodes)-1], aggregatedKey, cosigners, feeAmount)
if err != nil {
return nil, err
}
return append(pairs, last), nil
}
pairs := make([]node, 0, len(nodes)/2)
for i := 0; i < len(nodes); i += 2 {
left := nodes[i]
right := nodes[i+1]
branchNode := &branch{
aggregatedKey: aggregatedKey,
cosigners: cosigners,
feeAmount: feeAmount,
children: []node{left, right},
}
pairs = append(pairs, branchNode)
}
return pairs, nil
}
func taprootOutputScript(taprootKey *secp256k1.PublicKey) ([]byte, error) {
return txscript.NewScriptBuilder().AddOp(txscript.OP_1).AddData(
schnorr.SerializePubKey(taprootKey),
).Script()
}