mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 04:04:21 +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:
471
common/tree/script_test.go
Normal file
471
common/tree/script_test.go
Normal file
@@ -0,0 +1,471 @@
|
||||
package tree_test
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/ark-network/ark/common/tree"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRoundTripCSV(t *testing.T) {
|
||||
seckey, err := secp256k1.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
csvSig := &tree.CSVSigClosure{
|
||||
MultisigClosure: tree.MultisigClosure{
|
||||
PubKeys: []*secp256k1.PublicKey{seckey.PubKey()},
|
||||
},
|
||||
Seconds: 1024,
|
||||
}
|
||||
|
||||
leaf, err := csvSig.Script()
|
||||
require.NoError(t, err)
|
||||
|
||||
var cl tree.CSVSigClosure
|
||||
|
||||
valid, err := cl.Decode(leaf)
|
||||
require.NoError(t, err)
|
||||
require.True(t, valid)
|
||||
|
||||
require.Equal(t, csvSig.Seconds, cl.Seconds)
|
||||
}
|
||||
|
||||
func TestMultisigClosure(t *testing.T) {
|
||||
// Generate some test keys
|
||||
privKey1, err := secp256k1.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
pubKey1 := privKey1.PubKey()
|
||||
|
||||
privKey2, err := secp256k1.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
pubKey2 := privKey2.PubKey()
|
||||
|
||||
t.Run("valid 2-of-2 multisig", func(t *testing.T) {
|
||||
closure := &tree.MultisigClosure{
|
||||
PubKeys: []*secp256k1.PublicKey{pubKey1, pubKey2},
|
||||
}
|
||||
|
||||
// Generate script
|
||||
script, err := closure.Script()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test decoding
|
||||
decodedClosure := &tree.MultisigClosure{}
|
||||
valid, err := decodedClosure.Decode(script)
|
||||
require.NoError(t, err)
|
||||
require.True(t, valid)
|
||||
require.Equal(t, 2, len(decodedClosure.PubKeys))
|
||||
|
||||
// Compare serialized pubkeys
|
||||
require.Equal(t,
|
||||
schnorr.SerializePubKey(pubKey1),
|
||||
schnorr.SerializePubKey(decodedClosure.PubKeys[0]),
|
||||
)
|
||||
require.Equal(t,
|
||||
schnorr.SerializePubKey(pubKey2),
|
||||
schnorr.SerializePubKey(decodedClosure.PubKeys[1]),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("valid single key multisig", func(t *testing.T) {
|
||||
closure := &tree.MultisigClosure{
|
||||
PubKeys: []*secp256k1.PublicKey{pubKey1},
|
||||
}
|
||||
|
||||
script, err := closure.Script()
|
||||
require.NoError(t, err)
|
||||
|
||||
decodedClosure := &tree.MultisigClosure{}
|
||||
valid, err := decodedClosure.Decode(script)
|
||||
require.NoError(t, err)
|
||||
require.True(t, valid)
|
||||
require.Equal(t, 1, len(decodedClosure.PubKeys))
|
||||
|
||||
// Compare serialized pubkey
|
||||
require.Equal(t,
|
||||
schnorr.SerializePubKey(pubKey1),
|
||||
schnorr.SerializePubKey(decodedClosure.PubKeys[0]),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("invalid empty script", func(t *testing.T) {
|
||||
closure := &tree.MultisigClosure{}
|
||||
valid, err := closure.Decode([]byte{})
|
||||
require.Error(t, err)
|
||||
require.False(t, valid)
|
||||
})
|
||||
|
||||
t.Run("invalid script - wrong data push", func(t *testing.T) {
|
||||
script := []byte{
|
||||
txscript.OP_DATA_33, // Wrong size for schnorr pubkey
|
||||
}
|
||||
closure := &tree.MultisigClosure{}
|
||||
valid, err := closure.Decode(script)
|
||||
require.NoError(t, err)
|
||||
require.False(t, valid)
|
||||
})
|
||||
|
||||
t.Run("invalid script - missing checksig", func(t *testing.T) {
|
||||
pubKeyBytes := schnorr.SerializePubKey(pubKey1)
|
||||
script := []byte{txscript.OP_DATA_32}
|
||||
script = append(script, pubKeyBytes...)
|
||||
// Missing OP_CHECKSIG
|
||||
|
||||
closure := &tree.MultisigClosure{}
|
||||
valid, err := closure.Decode(script)
|
||||
require.NoError(t, err)
|
||||
require.False(t, valid)
|
||||
})
|
||||
|
||||
t.Run("invalid script - extra data after checksig", func(t *testing.T) {
|
||||
pubKeyBytes := schnorr.SerializePubKey(pubKey1)
|
||||
script := []byte{txscript.OP_DATA_32}
|
||||
script = append(script, pubKeyBytes...)
|
||||
script = append(script, txscript.OP_CHECKSIG)
|
||||
script = append(script, 0x00) // Extra unwanted byte
|
||||
|
||||
closure := &tree.MultisigClosure{}
|
||||
valid, err := closure.Decode(script)
|
||||
require.NoError(t, err)
|
||||
require.False(t, valid)
|
||||
})
|
||||
|
||||
t.Run("witness size", func(t *testing.T) {
|
||||
closure := &tree.MultisigClosure{
|
||||
PubKeys: []*secp256k1.PublicKey{pubKey1, pubKey2},
|
||||
}
|
||||
|
||||
require.Equal(t, 128, closure.WitnessSize()) // 64 * 2 bytes
|
||||
})
|
||||
|
||||
t.Run("valid 12-of-12 multisig", func(t *testing.T) {
|
||||
// Generate 12 keys
|
||||
pubKeys := make([]*secp256k1.PublicKey, 12)
|
||||
for i := 0; i < 12; i++ {
|
||||
privKey, err := secp256k1.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
pubKeys[i] = privKey.PubKey()
|
||||
}
|
||||
|
||||
closure := &tree.MultisigClosure{
|
||||
PubKeys: pubKeys,
|
||||
}
|
||||
|
||||
// Generate script
|
||||
script, err := closure.Script()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test decoding
|
||||
decodedClosure := &tree.MultisigClosure{}
|
||||
valid, err := decodedClosure.Decode(script)
|
||||
require.NoError(t, err)
|
||||
require.True(t, valid)
|
||||
require.Equal(t, 12, len(decodedClosure.PubKeys))
|
||||
|
||||
// Compare all serialized pubkeys
|
||||
for i := 0; i < 12; i++ {
|
||||
require.Equal(t,
|
||||
schnorr.SerializePubKey(pubKeys[i]),
|
||||
schnorr.SerializePubKey(decodedClosure.PubKeys[i]),
|
||||
)
|
||||
}
|
||||
|
||||
// Verify witness size is correct for 12 signatures
|
||||
require.Equal(t, 64*12, closure.WitnessSize())
|
||||
})
|
||||
}
|
||||
|
||||
func TestCSVSigClosure(t *testing.T) {
|
||||
// Generate test keys
|
||||
privKey1, err := secp256k1.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
pubKey1 := privKey1.PubKey()
|
||||
|
||||
privKey2, err := secp256k1.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
pubKey2 := privKey2.PubKey()
|
||||
|
||||
t.Run("valid single key CSV", func(t *testing.T) {
|
||||
csvSig := &tree.CSVSigClosure{
|
||||
MultisigClosure: tree.MultisigClosure{
|
||||
PubKeys: []*secp256k1.PublicKey{pubKey1},
|
||||
},
|
||||
Seconds: 1024,
|
||||
}
|
||||
|
||||
script, err := csvSig.Script()
|
||||
require.NoError(t, err)
|
||||
|
||||
decodedCSV := &tree.CSVSigClosure{}
|
||||
valid, err := decodedCSV.Decode(script)
|
||||
require.NoError(t, err)
|
||||
require.True(t, valid)
|
||||
require.Equal(t, uint32(1024), uint32(decodedCSV.Seconds))
|
||||
require.Equal(t, 1, len(decodedCSV.PubKeys))
|
||||
require.Equal(t,
|
||||
schnorr.SerializePubKey(pubKey1),
|
||||
schnorr.SerializePubKey(decodedCSV.PubKeys[0]),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("valid 2-of-2 CSV", func(t *testing.T) {
|
||||
csvSig := &tree.CSVSigClosure{
|
||||
MultisigClosure: tree.MultisigClosure{
|
||||
PubKeys: []*secp256k1.PublicKey{pubKey1, pubKey2},
|
||||
},
|
||||
Seconds: 2016, // ~2 weeks
|
||||
}
|
||||
|
||||
script, err := csvSig.Script()
|
||||
require.NoError(t, err)
|
||||
|
||||
decodedCSV := &tree.CSVSigClosure{}
|
||||
valid, err := decodedCSV.Decode(script)
|
||||
require.NoError(t, err)
|
||||
require.True(t, valid)
|
||||
require.Equal(t, uint32(2016), uint32(decodedCSV.Seconds))
|
||||
require.Equal(t, 2, len(decodedCSV.PubKeys))
|
||||
require.Equal(t,
|
||||
schnorr.SerializePubKey(pubKey1),
|
||||
schnorr.SerializePubKey(decodedCSV.PubKeys[0]),
|
||||
)
|
||||
require.Equal(t,
|
||||
schnorr.SerializePubKey(pubKey2),
|
||||
schnorr.SerializePubKey(decodedCSV.PubKeys[1]),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("invalid empty script", func(t *testing.T) {
|
||||
csvSig := &tree.CSVSigClosure{}
|
||||
valid, err := csvSig.Decode([]byte{})
|
||||
require.Error(t, err)
|
||||
require.False(t, valid)
|
||||
})
|
||||
|
||||
t.Run("invalid CSV value", func(t *testing.T) {
|
||||
// Create a script with invalid CSV value
|
||||
pubKeyBytes := schnorr.SerializePubKey(pubKey1)
|
||||
script := []byte{txscript.OP_DATA_32}
|
||||
script = append(script, pubKeyBytes...)
|
||||
script = append(script, txscript.OP_CHECKSIG)
|
||||
script = append(script, 0xFF) // Invalid CSV value
|
||||
|
||||
csvSig := &tree.CSVSigClosure{}
|
||||
valid, err := csvSig.Decode(script)
|
||||
require.NoError(t, err)
|
||||
require.False(t, valid)
|
||||
})
|
||||
|
||||
t.Run("witness size", func(t *testing.T) {
|
||||
csvSig := &tree.CSVSigClosure{
|
||||
MultisigClosure: tree.MultisigClosure{
|
||||
PubKeys: []*secp256k1.PublicKey{pubKey1, pubKey2},
|
||||
},
|
||||
Seconds: 1024,
|
||||
}
|
||||
// Should be same as multisig witness size (64 bytes per signature)
|
||||
require.Equal(t, 128, csvSig.WitnessSize())
|
||||
})
|
||||
|
||||
t.Run("max timelock", func(t *testing.T) {
|
||||
csvSig := &tree.CSVSigClosure{
|
||||
MultisigClosure: tree.MultisigClosure{
|
||||
PubKeys: []*secp256k1.PublicKey{pubKey1},
|
||||
},
|
||||
Seconds: 65535, // Maximum allowed value
|
||||
}
|
||||
|
||||
script, err := csvSig.Script()
|
||||
require.NoError(t, err)
|
||||
|
||||
decodedCSV := &tree.CSVSigClosure{}
|
||||
valid, err := decodedCSV.Decode(script)
|
||||
require.NoError(t, err)
|
||||
require.True(t, valid)
|
||||
require.Equal(t, uint32(65535), uint32(decodedCSV.Seconds))
|
||||
})
|
||||
}
|
||||
|
||||
func TestMultisigClosureWitness(t *testing.T) {
|
||||
// Generate test keys
|
||||
priv1, err := secp256k1.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
pub1 := priv1.PubKey()
|
||||
|
||||
priv2, err := secp256k1.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
pub2 := priv2.PubKey()
|
||||
|
||||
// Mock control block
|
||||
controlBlock := []byte("control block")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
closure *tree.MultisigClosure
|
||||
signatures map[string][]byte
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "single signature success",
|
||||
closure: &tree.MultisigClosure{
|
||||
PubKeys: []*secp256k1.PublicKey{pub1},
|
||||
},
|
||||
signatures: map[string][]byte{
|
||||
hex.EncodeToString(schnorr.SerializePubKey(pub1)): []byte("signature1"),
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "multiple signatures success",
|
||||
closure: &tree.MultisigClosure{
|
||||
PubKeys: []*secp256k1.PublicKey{pub1, pub2},
|
||||
},
|
||||
signatures: map[string][]byte{
|
||||
hex.EncodeToString(schnorr.SerializePubKey(pub1)): []byte("signature1"),
|
||||
hex.EncodeToString(schnorr.SerializePubKey(pub2)): []byte("signature2"),
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "missing signature",
|
||||
closure: &tree.MultisigClosure{
|
||||
PubKeys: []*secp256k1.PublicKey{pub1, pub2},
|
||||
},
|
||||
signatures: map[string][]byte{
|
||||
hex.EncodeToString(schnorr.SerializePubKey(pub1)): []byte("signature1"),
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
witness, err := tc.closure.Witness(controlBlock, tc.signatures)
|
||||
if tc.expectError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
// Total witness stack should be: signatures + script + control block
|
||||
expectedLen := len(tc.closure.PubKeys) + 2
|
||||
require.Equal(t, expectedLen, len(witness))
|
||||
|
||||
// Verify signatures are in correct order (reverse order of pubkeys)
|
||||
for i := len(tc.closure.PubKeys) - 1; i >= 0; i-- {
|
||||
expectedSig := tc.signatures[hex.EncodeToString(schnorr.SerializePubKey(tc.closure.PubKeys[i]))]
|
||||
witnessIndex := len(witness) - 3 - i
|
||||
require.Equal(t, expectedSig, witness[:len(witness)-2][witnessIndex])
|
||||
}
|
||||
|
||||
// Verify script is present
|
||||
script, err := tc.closure.Script()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, script, witness[len(witness)-2])
|
||||
|
||||
// Verify control block is last
|
||||
require.Equal(t, controlBlock, witness[len(witness)-1])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnrollClosureWitness(t *testing.T) {
|
||||
closure := &tree.UnrollClosure{
|
||||
LeftKey: secp256k1.NewPublicKey(new(secp256k1.FieldVal), new(secp256k1.FieldVal)),
|
||||
RightKey: secp256k1.NewPublicKey(new(secp256k1.FieldVal), new(secp256k1.FieldVal)),
|
||||
LeftAmount: 1000,
|
||||
RightAmount: 2000,
|
||||
}
|
||||
|
||||
controlBlock := []byte("control block")
|
||||
witness, err := closure.Witness(controlBlock, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should contain script and control block
|
||||
require.Equal(t, 2, len(witness))
|
||||
|
||||
// Verify script is first
|
||||
script, err := closure.Script()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, script, witness[0])
|
||||
|
||||
// Verify control block is last
|
||||
require.Equal(t, controlBlock, witness[1])
|
||||
}
|
||||
|
||||
func TestCSVSigClosureWitness(t *testing.T) {
|
||||
// Generate test keys
|
||||
priv1, err := secp256k1.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
pub1 := priv1.PubKey()
|
||||
|
||||
// Create test signature
|
||||
testSig := []byte("signature1")
|
||||
signatures := map[string][]byte{
|
||||
hex.EncodeToString(schnorr.SerializePubKey(pub1)): testSig,
|
||||
}
|
||||
|
||||
controlBlock := []byte("control block")
|
||||
|
||||
closure := &tree.CSVSigClosure{
|
||||
MultisigClosure: tree.MultisigClosure{
|
||||
PubKeys: []*secp256k1.PublicKey{pub1},
|
||||
},
|
||||
Seconds: 144,
|
||||
}
|
||||
|
||||
witness, err := closure.Witness(controlBlock, signatures)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should contain: signature + script + control block
|
||||
require.Equal(t, 3, len(witness))
|
||||
require.Equal(t, testSig, witness[0])
|
||||
|
||||
script, err := closure.Script()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, script, witness[1])
|
||||
require.Equal(t, controlBlock, witness[2])
|
||||
|
||||
// Test missing signature
|
||||
_, err = closure.Witness(controlBlock, nil)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestDecodeChecksigAdd(t *testing.T) {
|
||||
// Generate some test public keys
|
||||
pubKey1, err := secp256k1.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
pubKey2, err := secp256k1.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
pubKey3, err := secp256k1.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
pubKeys := []*secp256k1.PublicKey{pubKey1.PubKey(), pubKey2.PubKey(), pubKey3.PubKey()}
|
||||
|
||||
// Create a script for 3-of-3 multisig using CHECKSIGADD
|
||||
scriptBuilder := txscript.NewScriptBuilder().
|
||||
AddData(schnorr.SerializePubKey(pubKeys[0])).
|
||||
AddOp(txscript.OP_CHECKSIG).
|
||||
AddData(schnorr.SerializePubKey(pubKeys[1])).
|
||||
AddOp(txscript.OP_CHECKSIGADD).
|
||||
AddData(schnorr.SerializePubKey(pubKeys[2])).
|
||||
AddOp(txscript.OP_CHECKSIGADD).
|
||||
AddInt64(3).
|
||||
AddOp(txscript.OP_EQUAL)
|
||||
|
||||
script, err := scriptBuilder.Script()
|
||||
require.NoError(t, err, "failed to build script")
|
||||
|
||||
// Decode the script
|
||||
multisigClosure := &tree.MultisigClosure{}
|
||||
valid, err := multisigClosure.Decode(script)
|
||||
require.NoError(t, err, "failed to decode script")
|
||||
require.True(t, valid, "script should be valid")
|
||||
require.Equal(t, tree.MultisigTypeChecksigAdd, multisigClosure.Type, "expected MultisigTypeChecksigAdd")
|
||||
require.Equal(t, 3, len(multisigClosure.PubKeys), "expected 3 public keys")
|
||||
}
|
||||
Reference in New Issue
Block a user