1 package script
2 3 import (
4 "encoding/binary"
5 "errors"
6 )
7 8 // The HTLC the swap uses:
9 //
10 // OP_IF
11 // <claimer> OP_CHECKSIGVERIFY OP_SIZE <32> OP_EQUALVERIFY OP_SHA256 <hash> OP_EQUAL
12 // OP_ELSE
13 // <locktime> OP_CHECKLOCKTIMEVERIFY OP_VERIFY <refunder> OP_CHECKSIG
14 // OP_ENDIF
15 //
16 // That is what Bitcoin Core compiles `wsh(or_i(and_v(v:pk(C),sha256(H)),
17 // and_v(v:after(T),pk(R))))` to, byte for byte, and the test pins the compilation against a
18 // script the node itself produced. Two details are not what the descriptor's text suggests:
19 // the hash compare leaves its result on the stack (`OP_EQUAL`, not `OP_EQUALVERIFY`), and the
20 // timelock's result is consumed by `OP_VERIFY`, not `OP_DROP`. A byte wrong here is a different
21 // address, so neither is written from first principles.
22 const (
23 // OpSize pushes the size of the top stack item.
24 OpSize byte = 0x82
25 // OpVerify fails the script unless the top item is true, and removes it.
26 OpVerify byte = 0x69
27 // pushThirtyTwo is the one-byte push of 32, the preimage size the HTLC demands.
28 pushThirtyTwo byte = 0x20
29 // compressedKeyLen is the length of a SEC1 compressed public key.
30 compressedKeyLen = 33
31 )
32 33 // ErrHTLC is a script this builder cannot express.
34 var ErrHTLC = errors.New("script: the HTLC needs 33-byte public keys, a secret hash and a locktime")
35 36 // HTLC builds the witness script of a swap contract.
37 //
38 // claimer signs and takes the money with the preimage; refunder takes it back after locktime
39 // has passed. Both keys are SEC1 compressed, because that is what the swap hands around, and
40 // the locktime is a little-endian number with no padding, because a padded push would be a
41 // different script.
42 func HTLC(claimer, refunder []byte, secretHash [32]byte, locktime uint32) ([]byte, error) {
43 if len(claimer) != compressedKeyLen || len(refunder) != compressedKeyLen || locktime == 0 {
44 return nil, ErrHTLC
45 }
46 47 out := []byte{OpIf}
48 49 out = append(out, pushOf(claimer)...)
50 out = append(out, OpCheckSigVerify, OpSize, 0x01, pushThirtyTwo, OpEqualVerify, OpSha256)
51 out = append(out, pushOf(secretHash[:])...)
52 out = append(out, OpEqual, OpElse)
53 out = append(out, pushOf(locktimeBytes(locktime))...)
54 out = append(out, OpCheckLockTimeVerify, OpVerify)
55 out = append(out, pushOf(refunder)...)
56 out = append(out, OpCheckSig, OpEndIf)
57 58 return out, nil
59 }
60 61 // HTLCAddress builds the script and names it on a chain with this prefix.
62 func HTLCAddress(hrp string, claimer, refunder []byte, secretHash [32]byte, locktime uint32) (string, error) {
63 witness, err := HTLC(claimer, refunder, secretHash, locktime)
64 65 if err != nil {
66 return "", err
67 }
68 69 return P2WSHAddress(hrp, witness)
70 }
71 72 // pushOf pushes a value that is already known to be short enough.
73 func pushOf(data []byte) []byte {
74 return append([]byte{byte(len(data))}, data...)
75 }
76 77 // locktimeBytes renders a locktime with no leading zero bytes.
78 func locktimeBytes(locktime uint32) []byte {
79 buffer := make([]byte, 4)
80 binary.LittleEndian.PutUint32(buffer, locktime)
81 82 trimmed := buffer[:4]
83 84 for len(trimmed) > 1 && trimmed[len(trimmed)-1] == 0 {
85 trimmed = trimmed[:len(trimmed)-1]
86 }
87 88 return trimmed
89 }
90