Support forfeit with CHECKLOCKTIMEVERIFY (#389)

* explicit Timelock struct

* support & test CLTV forfeit path

* fix wasm pkg

* fix wasm

* fix liquid GetCurrentBlockTime

* cleaning

* move esplora URL check
This commit is contained in:
Louis Singer
2024-11-28 14:51:06 +01:00
committed by GitHub
parent a4ae439341
commit 02542c3634
51 changed files with 1007 additions and 257 deletions

View File

@@ -1,9 +1,11 @@
package tree_test
import (
"bytes"
"encoding/hex"
"testing"
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/common/tree"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/txscript"
@@ -19,7 +21,7 @@ func TestRoundTripCSV(t *testing.T) {
MultisigClosure: tree.MultisigClosure{
PubKeys: []*secp256k1.PublicKey{seckey.PubKey()},
},
Seconds: 1024,
Locktime: common.Locktime{Type: common.LocktimeTypeSecond, Value: 1024},
}
leaf, err := csvSig.Script()
@@ -31,7 +33,7 @@ func TestRoundTripCSV(t *testing.T) {
require.NoError(t, err)
require.True(t, valid)
require.Equal(t, csvSig.Seconds, cl.Seconds)
require.Equal(t, csvSig.Locktime.Value, cl.Locktime.Value)
}
func TestMultisigClosure(t *testing.T) {
@@ -194,7 +196,7 @@ func TestCSVSigClosure(t *testing.T) {
MultisigClosure: tree.MultisigClosure{
PubKeys: []*secp256k1.PublicKey{pubkey1},
},
Seconds: 1024,
Locktime: common.Locktime{Type: common.LocktimeTypeSecond, Value: 1024},
}
script, err := csvSig.Script()
@@ -204,7 +206,7 @@ func TestCSVSigClosure(t *testing.T) {
valid, err := decodedCSV.Decode(script)
require.NoError(t, err)
require.True(t, valid)
require.Equal(t, uint32(1024), uint32(decodedCSV.Seconds))
require.Equal(t, uint32(1024), uint32(decodedCSV.Locktime.Value))
require.Equal(t, 1, len(decodedCSV.PubKeys))
require.Equal(t,
schnorr.SerializePubKey(pubkey1),
@@ -217,7 +219,7 @@ func TestCSVSigClosure(t *testing.T) {
MultisigClosure: tree.MultisigClosure{
PubKeys: []*secp256k1.PublicKey{pubkey1, pubkey2},
},
Seconds: 2016, // ~2 weeks
Locktime: common.Locktime{Type: common.LocktimeTypeSecond, Value: 512 * 4}, // ~2 weeks
}
script, err := csvSig.Script()
@@ -227,7 +229,7 @@ func TestCSVSigClosure(t *testing.T) {
valid, err := decodedCSV.Decode(script)
require.NoError(t, err)
require.True(t, valid)
require.Equal(t, uint32(2016), uint32(decodedCSV.Seconds))
require.Equal(t, uint32(512*4), uint32(decodedCSV.Locktime.Value))
require.Equal(t, 2, len(decodedCSV.PubKeys))
require.Equal(t,
schnorr.SerializePubKey(pubkey1),
@@ -265,7 +267,7 @@ func TestCSVSigClosure(t *testing.T) {
MultisigClosure: tree.MultisigClosure{
PubKeys: []*secp256k1.PublicKey{pubkey1, pubkey2},
},
Seconds: 1024,
Locktime: common.Locktime{Type: common.LocktimeTypeSecond, Value: 1024},
}
// Should be same as multisig witness size (64 bytes per signature)
require.Equal(t, 128, csvSig.WitnessSize())
@@ -276,7 +278,7 @@ func TestCSVSigClosure(t *testing.T) {
MultisigClosure: tree.MultisigClosure{
PubKeys: []*secp256k1.PublicKey{pubkey1},
},
Seconds: 65535, // Maximum allowed value
Locktime: common.Locktime{Type: common.LocktimeTypeSecond, Value: common.SECONDS_MAX}, // Maximum allowed value
}
script, err := csvSig.Script()
@@ -286,7 +288,7 @@ func TestCSVSigClosure(t *testing.T) {
valid, err := decodedCSV.Decode(script)
require.NoError(t, err)
require.True(t, valid)
require.Equal(t, uint32(65535), uint32(decodedCSV.Seconds))
require.Equal(t, uint32(common.SECONDS_MAX), decodedCSV.Locktime.Value)
})
}
@@ -416,7 +418,7 @@ func TestCSVSigClosureWitness(t *testing.T) {
MultisigClosure: tree.MultisigClosure{
PubKeys: []*secp256k1.PublicKey{pub1},
},
Seconds: 144,
Locktime: common.Locktime{Type: common.LocktimeTypeBlock, Value: 144},
}
witness, err := closure.Witness(controlBlock, signatures)
@@ -469,3 +471,156 @@ func TestDecodeChecksigAdd(t *testing.T) {
require.Equal(t, tree.MultisigTypeChecksigAdd, multisigClosure.Type, "expected MultisigTypeChecksigAdd")
require.Equal(t, 3, len(multisigClosure.PubKeys), "expected 3 public keys")
}
func TestCLTVMultisigClosure(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()
locktime := common.Locktime{
Type: common.LocktimeTypeBlock,
Value: 100,
}
t.Run("valid single key with CLTV", func(t *testing.T) {
closure := &tree.CLTVMultisigClosure{
MultisigClosure: tree.MultisigClosure{
PubKeys: []*secp256k1.PublicKey{pubkey1},
Type: tree.MultisigTypeChecksig,
},
Locktime: locktime,
}
script, err := closure.Script()
require.NoError(t, err)
decodedClosure := &tree.CLTVMultisigClosure{}
valid, err := decodedClosure.Decode(script)
require.NoError(t, err)
require.True(t, valid)
require.Equal(t, closure.Locktime, decodedClosure.Locktime)
require.Equal(t, 1, len(decodedClosure.PubKeys))
require.True(t, closure.PubKeys[0].IsEqual(decodedClosure.PubKeys[0]))
})
t.Run("valid two keys with CLTV", func(t *testing.T) {
closure := &tree.CLTVMultisigClosure{
MultisigClosure: tree.MultisigClosure{
PubKeys: []*secp256k1.PublicKey{pubkey1, pubkey2},
Type: tree.MultisigTypeChecksig,
},
Locktime: locktime,
}
script, err := closure.Script()
require.NoError(t, err)
decodedClosure := &tree.CLTVMultisigClosure{}
valid, err := decodedClosure.Decode(script)
require.NoError(t, err)
require.True(t, valid)
require.Equal(t, closure.Locktime, decodedClosure.Locktime)
require.Equal(t, 2, len(decodedClosure.PubKeys))
})
t.Run("valid two keys with CLTV using checksigadd", func(t *testing.T) {
closure := &tree.CLTVMultisigClosure{
MultisigClosure: tree.MultisigClosure{
PubKeys: []*secp256k1.PublicKey{pubkey1, pubkey2},
Type: tree.MultisigTypeChecksigAdd,
},
Locktime: locktime,
}
script, err := closure.Script()
require.NoError(t, err)
decodedClosure := &tree.CLTVMultisigClosure{}
valid, err := decodedClosure.Decode(script)
require.NoError(t, err)
require.True(t, valid)
require.Equal(t, closure.Locktime, decodedClosure.Locktime)
require.Equal(t, closure.Type, decodedClosure.Type)
require.Equal(t, 2, len(decodedClosure.PubKeys))
})
t.Run("witness generation", func(t *testing.T) {
closure := &tree.CLTVMultisigClosure{
MultisigClosure: tree.MultisigClosure{
PubKeys: []*secp256k1.PublicKey{pubkey1, pubkey2},
Type: tree.MultisigTypeChecksig,
},
Locktime: locktime,
}
controlBlock := bytes.Repeat([]byte{0x00}, 32)
signatures := map[string][]byte{
hex.EncodeToString(schnorr.SerializePubKey(pubkey1)): bytes.Repeat([]byte{0x01}, 64),
hex.EncodeToString(schnorr.SerializePubKey(pubkey2)): bytes.Repeat([]byte{0x01}, 64),
}
witness, err := closure.Witness(controlBlock, signatures)
require.NoError(t, err)
require.Equal(t, 4, len(witness)) // 2 sigs + script + control block
script, err := closure.Script()
require.NoError(t, err)
require.Equal(t, script, witness[2])
require.Equal(t, controlBlock, witness[3])
})
t.Run("invalid cases", func(t *testing.T) {
validClosure := &tree.CLTVMultisigClosure{
MultisigClosure: tree.MultisigClosure{
PubKeys: []*secp256k1.PublicKey{pubkey1},
Type: tree.MultisigTypeChecksig,
},
Locktime: locktime,
}
script, err := validClosure.Script()
require.NoError(t, err)
emptyScriptErr := "empty script"
testCases := []struct {
name string
script []byte
err *string
}{
{
name: "empty script",
script: []byte{},
err: &emptyScriptErr,
},
{
name: "invalid CLTV index",
script: append([]byte{txscript.OP_CHECKLOCKTIMEVERIFY, txscript.OP_DROP}, script...),
},
{
name: "missing CLTV",
script: script[5:],
},
{
name: "invalid multisig after CLTV",
script: append(script[:len(script)-1], txscript.OP_CHECKSIGVERIFY),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
closure := &tree.CLTVMultisigClosure{}
valid, err := closure.Decode(tc.script)
require.False(t, valid)
if tc.err != nil {
require.Contains(t, err.Error(), *tc.err)
} else {
require.NoError(t, err)
}
})
}
})
}