htlc.go raw

   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