mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 20:24:21 +01:00
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:
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user