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 }