package tx import ( "errors" "github.com/btcsuite/btcd/btcec/v2" btcecdsa "github.com/btcsuite/btcd/btcec/v2/ecdsa" ) // ErrKey is a secret key that is not a valid scalar. var ErrKey = errors.New("tx: not a usable secp256k1 key") // SignP2WPKH signs one input with a key and assembles its witness. // // The signature is deterministic (RFC 6979), which is what makes it comparable: sign the same // digest with the same key and the bytes are the same every time, on this machine and on the // node that signed the fixture. Low-S is btcec's default and the network's rule, and the // sighash type is appended to the DER encoding because that is where a verifier reads it. func SignP2WPKH(transaction *Tx, index int, secret []byte, program [20]byte, amount uint64) error { if len(secret) != 32 { return ErrKey } key, _ := btcec.PrivKeyFromBytes(secret) if key == nil { return ErrKey } digest, err := transaction.SighashV0(index, P2WPKHScriptCode(program), amount, SighashAll) if err != nil { return err } signature := btcecdsa.Sign(key, digest[:]) der := append(signature.Serialize(), byte(SighashAll)) transaction.Inputs[index].Witness = [][]byte{der, key.PubKey().SerializeCompressed()} return nil } // SignP2WSH signs one P2WSH input with a key and the script it is spending, and leaves the // witness's last item for the caller. // // The scriptCode of a P2WSH input is the witness script itself, which is why the script is an // argument here and a program is not: the signature commits to the script that will run, so a // different HTLC — a different key, hash or locktime — is a different digest. func SignP2WSH(transaction *Tx, index int, secret, witnessScript []byte, amount uint64) ([]byte, error) { if len(secret) != 32 { return nil, ErrKey } if len(witnessScript) == 0 { return nil, ErrScriptTooLong } key, _ := btcec.PrivKeyFromBytes(secret) if key == nil { return nil, ErrKey } digest, err := transaction.SighashV0(index, witnessScript, amount, SighashAll) if err != nil { return nil, err } signature := btcecdsa.Sign(key, digest[:]) return append(signature.Serialize(), byte(SighashAll)), nil } // SignHTLCClaim spends the claim branch: the preimage, then the signature, then a true // selector, then the script. // // That order is the stack as the script reads it, not a convention: OP_IF pops the selector, // OP_CHECKSIGVERIFY pops the signature, and OP_SHA256 pops the preimage. func SignHTLCClaim(transaction *Tx, index int, secret, witnessScript, preimage []byte, amount uint64) error { signature, err := SignP2WSH(transaction, index, secret, witnessScript, amount) if err != nil { return err } transaction.Inputs[index].Witness = [][]byte{preimage, signature, {0x01}, witnessScript} return nil } // SignHTLCRefund spends the refund branch: the signature, an empty selector, then the script. func SignHTLCRefund(transaction *Tx, index int, secret, witnessScript []byte, amount uint64) error { signature, err := SignP2WSH(transaction, index, secret, witnessScript, amount) if err != nil { return err } transaction.Inputs[index].Witness = [][]byte{signature, {}, witnessScript} return nil }