Skip to content

Commit 59e5460

Browse files
authored
Merge pull request #690 from hieblmi/static-addr-script
script: static address taproot script
2 parents 5201b49 + 7c2640d commit 59e5460

File tree

3 files changed

+509
-0
lines changed

3 files changed

+509
-0
lines changed

staticaddr/script/script.go

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package script
2+
3+
import (
4+
"github.com/btcsuite/btcd/btcec/v2"
5+
"github.com/btcsuite/btcd/btcec/v2/schnorr"
6+
"github.com/btcsuite/btcd/chaincfg/chainhash"
7+
"github.com/btcsuite/btcd/txscript"
8+
"github.com/btcsuite/btcd/wire"
9+
"github.com/lightningnetwork/lnd/input"
10+
)
11+
12+
const (
13+
// TaprootMultiSigWitnessSize evaluates to 66 bytes:
14+
// - num_witness_elements: 1 byte
15+
// - sig_varint_len: 1 byte
16+
// - <sig>: 64 bytes
17+
TaprootMultiSigWitnessSize = 1 + 1 + 64
18+
19+
// TaprootExpiryScriptSize evaluates to 39 bytes:
20+
// - OP_DATA: 1 byte (client_key length)
21+
// - <client_key>: 32 bytes
22+
// - OP_CHECKSIGVERIFY: 1 byte
23+
// - <address_expiry>: 4 bytes
24+
// - OP_CHECKSEQUENCEVERIFY: 1 byte
25+
TaprootExpiryScriptSize = 1 + 32 + 1 + 4 + 1
26+
27+
// TaprootExpiryWitnessSize evaluates to 140 bytes:
28+
// - num_witness_elements: 1 byte
29+
// - client_sig_varint_len: 1 byte (client_sig length)
30+
// - <trader_sig>: 64 bytes
31+
// - witness_script_varint_len: 1 byte (script length)
32+
// - <witness_script>: 39 bytes
33+
// - control_block_varint_len: 1 byte (control block length)
34+
// - <control_block>: 33 bytes
35+
TaprootExpiryWitnessSize = 1 + 1 + 64 + 1 + TaprootExpiryScriptSize + 1 + 33
36+
)
37+
38+
// StaticAddress encapsulates the static address script.
39+
type StaticAddress struct {
40+
// TimeoutScript is the final locking script for the timeout path which
41+
// is available to the sender after the set block height.
42+
TimeoutScript []byte
43+
44+
// TimeoutLeaf is the timeout leaf.
45+
TimeoutLeaf *txscript.TapLeaf
46+
47+
// ScriptTree is the assembled script tree from our timeout leaf.
48+
ScriptTree *txscript.IndexedTapScriptTree
49+
50+
// InternalPubKey is the public key for the keyspend path which bypasses
51+
// the timeout script locking.
52+
InternalPubKey *btcec.PublicKey
53+
54+
// TaprootKey is the taproot public key which is created with the above
55+
// 3 inputs.
56+
TaprootKey *btcec.PublicKey
57+
58+
// RootHash is the root hash of the taptree.
59+
RootHash chainhash.Hash
60+
}
61+
62+
// NewStaticAddress constructs a static address script.
63+
func NewStaticAddress(muSig2Version input.MuSig2Version, csvExpiry int64,
64+
clientPubKey, serverPubKey *btcec.PublicKey) (*StaticAddress, error) {
65+
66+
// Create our timeout path leaf, we'll use this separately to generate
67+
// the timeout path leaf.
68+
timeoutPathScript, err := GenTimeoutPathScript(clientPubKey, csvExpiry)
69+
if err != nil {
70+
return nil, err
71+
}
72+
73+
// Assemble our taproot script tree from our leaves.
74+
timeoutLeaf := txscript.NewBaseTapLeaf(timeoutPathScript)
75+
tree := txscript.AssembleTaprootScriptTree(timeoutLeaf)
76+
77+
rootHash := tree.RootNode.TapHash()
78+
79+
// Calculate the internal aggregate key.
80+
aggregateKey, err := input.MuSig2CombineKeys(
81+
muSig2Version,
82+
[]*btcec.PublicKey{clientPubKey, serverPubKey},
83+
true,
84+
&input.MuSig2Tweaks{
85+
TaprootTweak: rootHash[:],
86+
},
87+
)
88+
if err != nil {
89+
return nil, err
90+
}
91+
92+
return &StaticAddress{
93+
TimeoutScript: timeoutPathScript,
94+
TimeoutLeaf: &timeoutLeaf,
95+
ScriptTree: tree,
96+
InternalPubKey: aggregateKey.PreTweakedKey,
97+
TaprootKey: aggregateKey.FinalKey,
98+
RootHash: rootHash,
99+
}, nil
100+
}
101+
102+
// StaticAddressScript creates a MuSig2 2-of-2 multisig script with CSV timeout
103+
// path for the clientKey. This script represents a static loop-in address.
104+
func (s *StaticAddress) StaticAddressScript() ([]byte, error) {
105+
builder := txscript.NewScriptBuilder()
106+
107+
builder.AddOp(txscript.OP_1)
108+
builder.AddData(schnorr.SerializePubKey(s.TaprootKey))
109+
110+
return builder.Script()
111+
}
112+
113+
// GenTimeoutPathScript constructs a csv timeout script for the client.
114+
//
115+
// <clientKey> OP_CHECKSIGVERIFY <csvExpiry> OP_CHECKSEQUENCEVERIFY
116+
func GenTimeoutPathScript(clientKey *btcec.PublicKey, csvExpiry int64) ([]byte,
117+
error) {
118+
119+
builder := txscript.NewScriptBuilder()
120+
121+
builder.AddData(schnorr.SerializePubKey(clientKey))
122+
builder.AddOp(txscript.OP_CHECKSIGVERIFY)
123+
builder.AddInt64(csvExpiry)
124+
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
125+
126+
return builder.Script()
127+
}
128+
129+
// GenSuccessWitness returns the success witness to spend the static address
130+
// output with a combined signature.
131+
func (s *StaticAddress) GenSuccessWitness(combinedSig []byte) (wire.TxWitness,
132+
error) {
133+
134+
return wire.TxWitness{
135+
combinedSig,
136+
}, nil
137+
}
138+
139+
// GenTimeoutWitness returns the witness to spend the taproot timeout leaf.
140+
func (s *StaticAddress) GenTimeoutWitness(senderSig []byte) (wire.TxWitness,
141+
error) {
142+
143+
ctrlBlock := s.ScriptTree.LeafMerkleProofs[0].ToControlBlock(
144+
s.InternalPubKey,
145+
)
146+
147+
ctrlBlockBytes, err := ctrlBlock.ToBytes()
148+
if err != nil {
149+
return nil, err
150+
}
151+
152+
return wire.TxWitness{
153+
senderSig,
154+
s.TimeoutScript,
155+
ctrlBlockBytes,
156+
}, nil
157+
}
158+
159+
// ExpirySpendWeight returns the weight of the expiry path spend.
160+
func ExpirySpendWeight() int64 {
161+
var weightEstimator input.TxWeightEstimator
162+
weightEstimator.AddWitnessInput(TaprootExpiryWitnessSize)
163+
164+
weightEstimator.AddP2TROutput()
165+
166+
return int64(weightEstimator.Weight())
167+
}

0 commit comments

Comments
 (0)