/ input / script_utils_template_equiv_test.go
script_utils_template_equiv_test.go
  1  package input
  2  
  3  import (
  4  	"crypto/sha256"
  5  	"encoding/hex"
  6  	"testing"
  7  
  8  	"github.com/btcsuite/btcd/btcec/v2"
  9  	"github.com/stretchr/testify/require"
 10  )
 11  
 12  // testKeyBytes returns deterministic key bytes for testing. The index parameter
 13  // produces different keys for different roles by deriving private keys from a
 14  // hash and computing the corresponding public key on secp256k1.
 15  func testKeyBytes(t *testing.T, index byte) *btcec.PublicKey {
 16  	t.Helper()
 17  
 18  	hash := sha256.Sum256([]byte{index})
 19  	privKey, _ := btcec.PrivKeyFromBytes(hash[:])
 20  
 21  	return privKey.PubKey()
 22  }
 23  
 24  // testPaymentHash returns a deterministic 32-byte payment hash.
 25  func testPaymentHash() []byte {
 26  	h := sha256.Sum256([]byte("test-payment-preimage"))
 27  	return h[:]
 28  }
 29  
 30  // TestTemplateVsBuilderEquivalence verifies that the new ScriptTemplate-based
 31  // functions produce byte-for-byte identical output to the old ScriptBuilder
 32  // versions for all script types.
 33  func TestTemplateVsBuilderEquivalence(t *testing.T) {
 34  	t.Parallel()
 35  
 36  	// Set up test keys for various roles.
 37  	senderKey := testKeyBytes(t, 1)
 38  	receiverKey := testKeyBytes(t, 2)
 39  	revokeKey := testKeyBytes(t, 3)
 40  	selfKey := testKeyBytes(t, 4)
 41  	delayKey := testKeyBytes(t, 5)
 42  	remoteKey := testKeyBytes(t, 6)
 43  
 44  	payHash := testPaymentHash()
 45  
 46  	const (
 47  		csvDelay    uint32 = 144
 48  		cltvExpiry  uint32 = 800000
 49  		leaseExpiry uint32 = 900000
 50  	)
 51  
 52  	t.Run("WitnessScriptHash", func(t *testing.T) {
 53  		t.Parallel()
 54  		witnessScript := []byte("test-witness-script")
 55  
 56  		got, err := WitnessScriptHash(witnessScript)
 57  		require.NoError(t, err)
 58  
 59  		want, err := legacyWitnessScriptHash(witnessScript)
 60  		require.NoError(t, err)
 61  
 62  		require.Equal(t, want, got,
 63  			"WitnessScriptHash mismatch:\n"+
 64  				"  legacy:   %x\n  template: %x",
 65  			want, got,
 66  		)
 67  	})
 68  
 69  	t.Run("WitnessPubKeyHash", func(t *testing.T) {
 70  		t.Parallel()
 71  		pubkey := senderKey.SerializeCompressed()
 72  
 73  		got, err := WitnessPubKeyHash(pubkey)
 74  		require.NoError(t, err)
 75  
 76  		want, err := legacyWitnessPubKeyHash(pubkey)
 77  		require.NoError(t, err)
 78  
 79  		require.Equal(t, want, got)
 80  	})
 81  
 82  	t.Run("GenerateP2SH", func(t *testing.T) {
 83  		t.Parallel()
 84  		script := []byte("test-redeem-script")
 85  
 86  		got, err := GenerateP2SH(script)
 87  		require.NoError(t, err)
 88  
 89  		want, err := legacyGenerateP2SH(script)
 90  		require.NoError(t, err)
 91  
 92  		require.Equal(t, want, got)
 93  	})
 94  
 95  	t.Run("GenerateP2PKH", func(t *testing.T) {
 96  		t.Parallel()
 97  		pubkey := senderKey.SerializeCompressed()
 98  
 99  		got, err := GenerateP2PKH(pubkey)
100  		require.NoError(t, err)
101  
102  		want, err := legacyGenerateP2PKH(pubkey)
103  		require.NoError(t, err)
104  
105  		require.Equal(t, want, got)
106  	})
107  
108  	t.Run("GenMultiSigScript", func(t *testing.T) {
109  		t.Parallel()
110  		aPub := senderKey.SerializeCompressed()
111  		bPub := receiverKey.SerializeCompressed()
112  
113  		got, err := GenMultiSigScript(aPub, bPub)
114  		require.NoError(t, err)
115  
116  		want, err := legacyGenMultiSigScript(aPub, bPub)
117  		require.NoError(t, err)
118  
119  		require.Equal(t, want, got)
120  	})
121  
122  	t.Run("SenderHTLCScript/confirmed", func(t *testing.T) {
123  		t.Parallel()
124  
125  		got, err := SenderHTLCScript(
126  			senderKey, receiverKey, revokeKey, payHash, true,
127  		)
128  		require.NoError(t, err)
129  
130  		want, err := legacySenderHTLCScript(
131  			senderKey, receiverKey, revokeKey, payHash, true,
132  		)
133  		require.NoError(t, err)
134  
135  		require.Equal(t, want, got,
136  			"SenderHTLCScript(confirmed) mismatch:\n"+
137  				"  legacy:   %x\n  template: %x",
138  			want, got,
139  		)
140  	})
141  
142  	t.Run("SenderHTLCScript/unconfirmed", func(t *testing.T) {
143  		t.Parallel()
144  
145  		got, err := SenderHTLCScript(
146  			senderKey, receiverKey, revokeKey, payHash, false,
147  		)
148  		require.NoError(t, err)
149  
150  		want, err := legacySenderHTLCScript(
151  			senderKey, receiverKey, revokeKey, payHash, false,
152  		)
153  		require.NoError(t, err)
154  
155  		require.Equal(t, want, got)
156  	})
157  
158  	t.Run("ReceiverHTLCScript/confirmed", func(t *testing.T) {
159  		t.Parallel()
160  
161  		got, err := ReceiverHTLCScript(
162  			cltvExpiry, senderKey, receiverKey, revokeKey,
163  			payHash, true,
164  		)
165  		require.NoError(t, err)
166  
167  		want, err := legacyReceiverHTLCScript(
168  			cltvExpiry, senderKey, receiverKey, revokeKey,
169  			payHash, true,
170  		)
171  		require.NoError(t, err)
172  
173  		require.Equal(t, want, got,
174  			"ReceiverHTLCScript(confirmed) mismatch:\n"+
175  				"  legacy:   %x\n  template: %x",
176  			want, got,
177  		)
178  	})
179  
180  	t.Run("ReceiverHTLCScript/unconfirmed", func(t *testing.T) {
181  		t.Parallel()
182  
183  		got, err := ReceiverHTLCScript(
184  			cltvExpiry, senderKey, receiverKey, revokeKey,
185  			payHash, false,
186  		)
187  		require.NoError(t, err)
188  
189  		want, err := legacyReceiverHTLCScript(
190  			cltvExpiry, senderKey, receiverKey, revokeKey,
191  			payHash, false,
192  		)
193  		require.NoError(t, err)
194  
195  		require.Equal(t, want, got)
196  	})
197  
198  	t.Run("SecondLevelHtlcScript", func(t *testing.T) {
199  		t.Parallel()
200  
201  		got, err := SecondLevelHtlcScript(
202  			revokeKey, delayKey, csvDelay,
203  		)
204  		require.NoError(t, err)
205  
206  		want, err := legacySecondLevelHtlcScript(
207  			revokeKey, delayKey, csvDelay,
208  		)
209  		require.NoError(t, err)
210  
211  		require.Equal(t, want, got)
212  	})
213  
214  	t.Run("CommitScriptToSelf", func(t *testing.T) {
215  		t.Parallel()
216  
217  		got, err := CommitScriptToSelf(csvDelay, selfKey, revokeKey)
218  		require.NoError(t, err)
219  
220  		want, err := legacyCommitScriptToSelf(
221  			csvDelay, selfKey, revokeKey,
222  		)
223  		require.NoError(t, err)
224  
225  		require.Equal(t, want, got)
226  	})
227  
228  	t.Run("LeaseCommitScriptToSelf", func(t *testing.T) {
229  		t.Parallel()
230  
231  		got, err := LeaseCommitScriptToSelf(
232  			selfKey, revokeKey, csvDelay, leaseExpiry,
233  		)
234  		require.NoError(t, err)
235  
236  		want, err := legacyLeaseCommitScriptToSelf(
237  			selfKey, revokeKey, csvDelay, leaseExpiry,
238  		)
239  		require.NoError(t, err)
240  
241  		require.Equal(t, want, got)
242  	})
243  
244  	t.Run("CommitScriptUnencumbered", func(t *testing.T) {
245  		t.Parallel()
246  
247  		got, err := CommitScriptUnencumbered(remoteKey)
248  		require.NoError(t, err)
249  
250  		want, err := legacyCommitScriptUnencumbered(remoteKey)
251  		require.NoError(t, err)
252  
253  		require.Equal(t, want, got)
254  	})
255  
256  	t.Run("CommitScriptToRemoteConfirmed", func(t *testing.T) {
257  		t.Parallel()
258  
259  		got, err := CommitScriptToRemoteConfirmed(remoteKey)
260  		require.NoError(t, err)
261  
262  		want, err := legacyCommitScriptToRemoteConfirmed(remoteKey)
263  		require.NoError(t, err)
264  
265  		require.Equal(t, want, got)
266  	})
267  
268  	t.Run("LeaseCommitScriptToRemoteConfirmed", func(t *testing.T) {
269  		t.Parallel()
270  
271  		got, err := LeaseCommitScriptToRemoteConfirmed(
272  			remoteKey, leaseExpiry,
273  		)
274  		require.NoError(t, err)
275  
276  		want, err := legacyLeaseCommitScriptToRemoteConfirmed(
277  			remoteKey, leaseExpiry,
278  		)
279  		require.NoError(t, err)
280  
281  		require.Equal(t, want, got)
282  	})
283  
284  	t.Run("CommitScriptAnchor", func(t *testing.T) {
285  		t.Parallel()
286  
287  		got, err := CommitScriptAnchor(senderKey)
288  		require.NoError(t, err)
289  
290  		want, err := legacyCommitScriptAnchor(senderKey)
291  		require.NoError(t, err)
292  
293  		require.Equal(t, want, got)
294  	})
295  
296  	t.Run("LeaseSecondLevelHtlcScript", func(t *testing.T) {
297  		t.Parallel()
298  
299  		got, err := LeaseSecondLevelHtlcScript(
300  			revokeKey, delayKey, csvDelay, cltvExpiry,
301  		)
302  		require.NoError(t, err)
303  
304  		want, err := legacyLeaseSecondLevelHtlcScript(
305  			revokeKey, delayKey, csvDelay, cltvExpiry,
306  		)
307  		require.NoError(t, err)
308  
309  		require.Equal(t, want, got)
310  	})
311  
312  	// Taproot script equivalence tests. These compare the non-prod
313  	// (default) variant of the template functions against the old builder
314  	// code which also produced the non-prod scripts.
315  	t.Run("SenderHTLCTapLeafTimeout", func(t *testing.T) {
316  		t.Parallel()
317  
318  		got, err := SenderHTLCTapLeafTimeout(senderKey, receiverKey)
319  		require.NoError(t, err)
320  
321  		want, err := legacySenderHTLCTapLeafTimeout(
322  			senderKey, receiverKey,
323  		)
324  		require.NoError(t, err)
325  
326  		require.Equal(t, want.Script, got.Script)
327  	})
328  
329  	t.Run("SenderHTLCTapLeafSuccess", func(t *testing.T) {
330  		t.Parallel()
331  
332  		got, err := SenderHTLCTapLeafSuccess(receiverKey, payHash)
333  		require.NoError(t, err)
334  
335  		want, err := legacySenderHTLCTapLeafSuccess(
336  			receiverKey, payHash,
337  		)
338  		require.NoError(t, err)
339  
340  		require.Equal(t, want.Script, got.Script)
341  	})
342  
343  	t.Run("ReceiverHtlcTapLeafTimeout", func(t *testing.T) {
344  		t.Parallel()
345  
346  		got, err := ReceiverHtlcTapLeafTimeout(
347  			senderKey, cltvExpiry,
348  		)
349  		require.NoError(t, err)
350  
351  		want, err := legacyReceiverHtlcTapLeafTimeout(
352  			senderKey, cltvExpiry,
353  		)
354  		require.NoError(t, err)
355  
356  		require.Equal(t, want.Script, got.Script)
357  	})
358  
359  	t.Run("ReceiverHtlcTapLeafSuccess", func(t *testing.T) {
360  		t.Parallel()
361  
362  		got, err := ReceiverHtlcTapLeafSuccess(
363  			receiverKey, senderKey, payHash,
364  		)
365  		require.NoError(t, err)
366  
367  		want, err := legacyReceiverHtlcTapLeafSuccess(
368  			receiverKey, senderKey, payHash,
369  		)
370  		require.NoError(t, err)
371  
372  		require.Equal(t, want.Script, got.Script)
373  	})
374  
375  	t.Run("TaprootSecondLevelTapLeaf", func(t *testing.T) {
376  		t.Parallel()
377  
378  		got, err := TaprootSecondLevelTapLeaf(delayKey, csvDelay)
379  		require.NoError(t, err)
380  
381  		want, err := legacyTaprootSecondLevelTapLeaf(
382  			delayKey, csvDelay,
383  		)
384  		require.NoError(t, err)
385  
386  		require.Equal(t, want.Script, got.Script)
387  	})
388  
389  	t.Run("TaprootLocalCommitDelayScript", func(t *testing.T) {
390  		t.Parallel()
391  
392  		got, err := TaprootLocalCommitDelayScript(
393  			csvDelay, selfKey,
394  		)
395  		require.NoError(t, err)
396  
397  		want, err := legacyTaprootLocalCommitDelayScript(
398  			csvDelay, selfKey,
399  		)
400  		require.NoError(t, err)
401  
402  		require.Equal(t, want, got,
403  			"TaprootLocalCommitDelayScript mismatch:\n"+
404  				"  legacy:   %x\n  template: %x",
405  			want, got,
406  		)
407  	})
408  
409  	t.Run("TaprootLocalCommitRevokeScript", func(t *testing.T) {
410  		t.Parallel()
411  
412  		got, err := TaprootLocalCommitRevokeScript(
413  			selfKey, revokeKey,
414  		)
415  		require.NoError(t, err)
416  
417  		want, err := legacyTaprootLocalCommitRevokeScript(
418  			selfKey, revokeKey,
419  		)
420  		require.NoError(t, err)
421  
422  		require.Equal(t, want, got)
423  	})
424  
425  	// Log a summary of all scripts tested for visual inspection.
426  	t.Log("All 22 template vs builder script equivalence checks passed")
427  }
428  
429  // TestTemplateScriptDisassembly provides human-readable output of a few key
430  // scripts to make it easy to verify correctness visually.
431  func TestTemplateScriptDisassembly(t *testing.T) {
432  	t.Parallel()
433  
434  	senderKey := testKeyBytes(t, 1)
435  	receiverKey := testKeyBytes(t, 2)
436  	revokeKey := testKeyBytes(t, 3)
437  	payHash := testPaymentHash()
438  
439  	// SenderHTLCScript with confirmed spend.
440  	script, err := SenderHTLCScript(
441  		senderKey, receiverKey, revokeKey, payHash, true,
442  	)
443  	require.NoError(t, err)
444  	t.Logf("SenderHTLCScript (confirmed):\n  %s",
445  		hex.EncodeToString(script))
446  
447  	// ReceiverHTLCScript with confirmed spend.
448  	script, err = ReceiverHTLCScript(
449  		800000, senderKey, receiverKey, revokeKey, payHash, true,
450  	)
451  	require.NoError(t, err)
452  	t.Logf("ReceiverHTLCScript (confirmed):\n  %s",
453  		hex.EncodeToString(script))
454  }