// Package script builds the output scripts the wallet owns and the addresses that name them. // // It is deliberately small: a P2WPKH output and a P2WSH output, the minimal push encoding, and // the handful of opcodes the swap's HTLC script is made of. Nothing here compiles a miniscript // descriptor — that is a separate, testable step, and it must be checked against a node's // `getdescriptorinfo` before any money is sent to a script it produced. package script import ( "crypto/sha256" "fmt" "git.smesh.lol/tinywallet/pkg/bech32" ) // Opcodes used by the wallet's scripts. const ( // OpZero pushes an empty vector, and is the witness-version byte of a segwit output. OpZero byte = 0x00 // OpOne pushes a single 1, and is also the true branch selector. OpOne byte = 0x51 // OpIf, OpElse and OpEndIf are the branch structure of the HTLC. OpIf byte = 0x63 OpElse byte = 0x67 OpEndIf byte = 0x68 // OpSha256 hashes the top stack item with SHA-256. OpSha256 byte = 0xa8 // OpEqual and OpEqualVerify compare the top two items. OpEqual byte = 0x87 OpEqualVerify byte = 0x88 // OpCheckSig and OpCheckSigVerify check a signature against a public key. OpCheckSig byte = 0xac OpCheckSigVerify byte = 0xad // OpCheckLockTimeVerify and OpDrop carry the refund's timelock. OpCheckLockTimeVerify byte = 0xb1 OpDrop byte = 0x75 ) // Push encodes a data push with the smallest form that fits. // // A script that pushes 33 bytes with a PUSHDATA1 would be a different script on chain, with a // different hash and a different address, so this must be the minimal form and nothing else. func Push(data []byte) ([]byte, error) { length := len(data) switch { case length == 0: return []byte{OpZero}, nil case length <= 75: return append([]byte{byte(length)}, data...), nil case length <= 0xff: return append([]byte{0x4c, byte(length)}, data...), nil case length <= 0xffff: return append([]byte{0x4d, byte(length), byte(length >> 8)}, data...), nil } return nil, fmt.Errorf("script: a %d-byte push is too long for one item", length) } // P2WPKH returns the scriptPubKey of a native segwit output paying to a key hash. func P2WPKH(program [20]byte) []byte { out := make([]byte, 0, 22) out = append(out, OpZero, 0x14) out = append(out, program[:]...) return out } // P2WSHProgram returns the 32-byte program of a witness-script hash. func P2WSHProgram(witnessScript []byte) [32]byte { return sha256.Sum256(witnessScript) } // P2WSH returns the scriptPubKey of a native segwit output paying to a script hash. func P2WSH(witnessScript []byte) []byte { program := P2WSHProgram(witnessScript) out := make([]byte, 0, 34) out = append(out, OpZero, 0x20) out = append(out, program[:]...) return out } // P2WPKHAddress renders an address for a key hash on a chain with this prefix. func P2WPKHAddress(hrp string, program [20]byte) (string, error) { return bech32.EncodeSegwit(hrp, 0, append([]byte{}, program[:]...)) } // P2WSHAddress renders an address for a witness script on a chain with this prefix. func P2WSHAddress(hrp string, witnessScript []byte) (string, error) { program := P2WSHProgram(witnessScript) return bech32.EncodeSegwit(hrp, 0, append([]byte{}, program[:]...)) }