script.go raw

   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