package script import ( "encoding/binary" "errors" ) // The HTLC the swap uses: // // OP_IF // OP_CHECKSIGVERIFY OP_SIZE <32> OP_EQUALVERIFY OP_SHA256 OP_EQUAL // OP_ELSE // OP_CHECKLOCKTIMEVERIFY OP_VERIFY OP_CHECKSIG // OP_ENDIF // // That is what Bitcoin Core compiles `wsh(or_i(and_v(v:pk(C),sha256(H)), // and_v(v:after(T),pk(R))))` to, byte for byte, and the test pins the compilation against a // script the node itself produced. Two details are not what the descriptor's text suggests: // the hash compare leaves its result on the stack (`OP_EQUAL`, not `OP_EQUALVERIFY`), and the // timelock's result is consumed by `OP_VERIFY`, not `OP_DROP`. A byte wrong here is a different // address, so neither is written from first principles. const ( // OpSize pushes the size of the top stack item. OpSize byte = 0x82 // OpVerify fails the script unless the top item is true, and removes it. OpVerify byte = 0x69 // pushThirtyTwo is the one-byte push of 32, the preimage size the HTLC demands. pushThirtyTwo byte = 0x20 // compressedKeyLen is the length of a SEC1 compressed public key. compressedKeyLen = 33 ) // ErrHTLC is a script this builder cannot express. var ErrHTLC = errors.New("script: the HTLC needs 33-byte public keys, a secret hash and a locktime") // HTLC builds the witness script of a swap contract. // // claimer signs and takes the money with the preimage; refunder takes it back after locktime // has passed. Both keys are SEC1 compressed, because that is what the swap hands around, and // the locktime is a little-endian number with no padding, because a padded push would be a // different script. func HTLC(claimer, refunder []byte, secretHash [32]byte, locktime uint32) ([]byte, error) { if len(claimer) != compressedKeyLen || len(refunder) != compressedKeyLen || locktime == 0 { return nil, ErrHTLC } out := []byte{OpIf} out = append(out, pushOf(claimer)...) out = append(out, OpCheckSigVerify, OpSize, 0x01, pushThirtyTwo, OpEqualVerify, OpSha256) out = append(out, pushOf(secretHash[:])...) out = append(out, OpEqual, OpElse) out = append(out, pushOf(locktimeBytes(locktime))...) out = append(out, OpCheckLockTimeVerify, OpVerify) out = append(out, pushOf(refunder)...) out = append(out, OpCheckSig, OpEndIf) return out, nil } // HTLCAddress builds the script and names it on a chain with this prefix. func HTLCAddress(hrp string, claimer, refunder []byte, secretHash [32]byte, locktime uint32) (string, error) { witness, err := HTLC(claimer, refunder, secretHash, locktime) if err != nil { return "", err } return P2WSHAddress(hrp, witness) } // pushOf pushes a value that is already known to be short enough. func pushOf(data []byte) []byte { return append([]byte{byte(len(data))}, data...) } // locktimeBytes renders a locktime with no leading zero bytes. func locktimeBytes(locktime uint32) []byte { buffer := make([]byte, 4) binary.LittleEndian.PutUint32(buffer, locktime) trimmed := buffer[:4] for len(trimmed) > 1 && trimmed[len(trimmed)-1] == 0 { trimmed = trimmed[:len(trimmed)-1] } return trimmed }