1 // Package script builds the output scripts the wallet owns and the addresses that name them.
2 //
3 // It is deliberately small: a P2WPKH output and a P2WSH output, the minimal push encoding, and
4 // the handful of opcodes the swap's HTLC script is made of. Nothing here compiles a miniscript
5 // descriptor — that is a separate, testable step, and it must be checked against a node's
6 // `getdescriptorinfo` before any money is sent to a script it produced.
7 package script
8 9 import (
10 "crypto/sha256"
11 "fmt"
12 13 "git.smesh.lol/tinywallet/pkg/bech32"
14 )
15 16 // Opcodes used by the wallet's scripts.
17 const (
18 // OpZero pushes an empty vector, and is the witness-version byte of a segwit output.
19 OpZero byte = 0x00
20 // OpOne pushes a single 1, and is also the true branch selector.
21 OpOne byte = 0x51
22 // OpIf, OpElse and OpEndIf are the branch structure of the HTLC.
23 OpIf byte = 0x63
24 OpElse byte = 0x67
25 OpEndIf byte = 0x68
26 // OpSha256 hashes the top stack item with SHA-256.
27 OpSha256 byte = 0xa8
28 // OpEqual and OpEqualVerify compare the top two items.
29 OpEqual byte = 0x87
30 OpEqualVerify byte = 0x88
31 // OpCheckSig and OpCheckSigVerify check a signature against a public key.
32 OpCheckSig byte = 0xac
33 OpCheckSigVerify byte = 0xad
34 // OpCheckLockTimeVerify and OpDrop carry the refund's timelock.
35 OpCheckLockTimeVerify byte = 0xb1
36 OpDrop byte = 0x75
37 )
38 39 // Push encodes a data push with the smallest form that fits.
40 //
41 // A script that pushes 33 bytes with a PUSHDATA1 would be a different script on chain, with a
42 // different hash and a different address, so this must be the minimal form and nothing else.
43 func Push(data []byte) ([]byte, error) {
44 length := len(data)
45 46 switch {
47 case length == 0:
48 return []byte{OpZero}, nil
49 case length <= 75:
50 return append([]byte{byte(length)}, data...), nil
51 case length <= 0xff:
52 return append([]byte{0x4c, byte(length)}, data...), nil
53 case length <= 0xffff:
54 return append([]byte{0x4d, byte(length), byte(length >> 8)}, data...), nil
55 }
56 57 return nil, fmt.Errorf("script: a %d-byte push is too long for one item", length)
58 }
59 60 // P2WPKH returns the scriptPubKey of a native segwit output paying to a key hash.
61 func P2WPKH(program [20]byte) []byte {
62 out := make([]byte, 0, 22)
63 out = append(out, OpZero, 0x14)
64 out = append(out, program[:]...)
65 66 return out
67 }
68 69 // P2WSHProgram returns the 32-byte program of a witness-script hash.
70 func P2WSHProgram(witnessScript []byte) [32]byte {
71 return sha256.Sum256(witnessScript)
72 }
73 74 // P2WSH returns the scriptPubKey of a native segwit output paying to a script hash.
75 func P2WSH(witnessScript []byte) []byte {
76 program := P2WSHProgram(witnessScript)
77 78 out := make([]byte, 0, 34)
79 out = append(out, OpZero, 0x20)
80 out = append(out, program[:]...)
81 82 return out
83 }
84 85 // P2WPKHAddress renders an address for a key hash on a chain with this prefix.
86 func P2WPKHAddress(hrp string, program [20]byte) (string, error) {
87 return bech32.EncodeSegwit(hrp, 0, append([]byte{}, program[:]...))
88 }
89 90 // P2WSHAddress renders an address for a witness script on a chain with this prefix.
91 func P2WSHAddress(hrp string, witnessScript []byte) (string, error) {
92 program := P2WSHProgram(witnessScript)
93 94 return bech32.EncodeSegwit(hrp, 0, append([]byte{}, program[:]...))
95 }
96