lnd/lnwallet/script_utils.go

282 lines
9.7 KiB
Go
Raw Normal View History

package lnwallet
import (
"bytes"
"fmt"
"github.com/btcsuite/fastsha256"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
)
var (
// TODO(roasbeef): remove these and use the one's defined in txscript
// within testnet-L.
SequenceLockTimeSeconds = uint32(1 << 22)
SequenceLockTimeMask = uint32(0x0000ffff)
OP_CHECKSEQUENCEVERIFY byte = txscript.OP_NOP3
)
// witnessScriptHash generates a pay-to-witness-script-hash public key script
// paying to a version 0 witness program paying to the passed redeem script.
func witnessScriptHash(redeemScript []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder()
bldr.AddOp(txscript.OP_0)
scriptHash := fastsha256.Sum256(redeemScript)
bldr.AddData(scriptHash[:])
return bldr.Script()
}
// genMultiSigScript generates the non-p2sh'd multisig script for 2 of 2
// pubkeys.
func genMultiSigScript(aPub, bPub []byte) ([]byte, error) {
if len(aPub) != 33 || len(bPub) != 33 {
return nil, fmt.Errorf("Pubkey size error. Compressed pubkeys only")
}
// Swap to sort pubkeys if needed. Keys are sorted in lexicographical
// order. The signatures within the scriptSig must also adhere to the
// order, ensuring that the signatures for each public key appears
// in the proper order on the stack.
if bytes.Compare(aPub, bPub) == -1 {
aPub, bPub = bPub, aPub
}
bldr := txscript.NewScriptBuilder()
bldr.AddOp(txscript.OP_2)
bldr.AddData(aPub) // Add both pubkeys (sorted).
bldr.AddData(bPub)
bldr.AddOp(txscript.OP_2)
bldr.AddOp(txscript.OP_CHECKMULTISIG)
return bldr.Script()
}
// genFundingPkScript creates a redeem script, and its matching p2wsh
// output for the funding transaction.
func genFundingPkScript(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, error) {
// As a sanity check, ensure that the passed amount is above zero.
if amt <= 0 {
return nil, nil, fmt.Errorf("can't create FundTx script with " +
"zero, or negative coins")
}
// First, create the 2-of-2 multi-sig script itself.
redeemScript, err := genMultiSigScript(aPub, bPub)
if err != nil {
return nil, nil, err
}
// With the 2-of-2 script in had, generate a p2wsh script which pays
// to the funding script.
pkScript, err := witnessScriptHash(redeemScript)
if err != nil {
return nil, nil, err
}
return redeemScript, wire.NewTxOut(amt, pkScript), nil
}
// spendMultiSig generates the witness stack required to redeem the 2-of-2 p2wsh
// multi-sig output.
func spendMultiSig(redeemScript, pubA, sigA, pubB, sigB []byte) [][]byte {
witness := make([][]byte, 4)
// When spending a p2wsh multi-sig script, rather than an OP_0, we add
// a nil stack element to eat the extra pop.
witness[0] = nil
// When initially generating the redeemScript, we sorted the serialized
// public keys in descending order. So we do a quick comparison in order
// ensure the signatures appear on the Script Virual Machine stack in
// the correct order.
if bytes.Compare(pubA, pubB) == -1 {
witness[1] = sigB
witness[2] = sigA
} else {
witness[1] = sigA
witness[2] = sigB
}
// Finally, add the pre-image as the last witness element.
witness[3] = redeemScript
return witness
}
// findScriptOutputIndex finds the index of the public key script output
// matching 'script'. Additionally, a boolean is returned indicating if
// a matching output was found at all.
// NOTE: The search stops after the first matching script is found.
func findScriptOutputIndex(tx *wire.MsgTx, script []byte) (bool, uint32) {
found := false
index := uint32(0)
for i, txOut := range tx.TxOut {
if bytes.Equal(txOut.PkScript, script) {
found = true
index = uint32(i)
break
}
}
return found, index
}
// senderHTLCScript constructs the public key script for an outgoing HTLC
// output payment for the sender's commitment transaction.
func senderHTLCScript(absoluteTimeout, relativeTimeout uint32, senderKey,
receiverKey *btcec.PublicKey, revokeHash, paymentHash []byte) ([]byte, error) {
builder := txscript.NewScriptBuilder()
// Was the pre-image to the payment hash presented?
builder.AddOp(txscript.OP_HASH160)
builder.AddOp(txscript.OP_DUP)
builder.AddData(paymentHash)
builder.AddOp(txscript.OP_EQUAL)
// How about the pre-image for our commitment revocation hash?
builder.AddOp(txscript.OP_SWAP)
builder.AddData(revokeHash)
builder.AddOp(txscript.OP_EQUAL)
builder.AddOp(txscript.OP_SWAP)
builder.AddOp(txscript.OP_ADD)
// If either is present, then the receiver can claim immediately.
builder.AddOp(txscript.OP_IF)
builder.AddData(receiverKey.SerializeCompressed())
// Otherwise, we (the sender) need to wait for an absolute HTLC
// timeout, then afterwards a relative timeout before we claim re-claim
// the unsettled funds. This delay gives the other party a chance to
// present the pre-image to the revocation hash in the event that the
// sender (at this time) broadcasts this commitment transaction after
// it has been revoked.
builder.AddOp(txscript.OP_ELSE)
builder.AddInt64(int64(absoluteTimeout))
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
builder.AddInt64(int64(relativeTimeout))
builder.AddOp(OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_2DROP)
builder.AddData(senderKey.SerializeCompressed())
builder.AddOp(txscript.OP_ENDIF)
builder.AddOp(txscript.OP_CHECKSIG)
return builder.Script()
}
// senderHTLCScript constructs the public key script for an incoming HTLC
// output payment for the receiver's commitment transaction.
func receiverHTLCScript(absoluteTimeout, relativeTimeout uint32, senderKey,
receiverKey *btcec.PublicKey, revokeHash, paymentHash []byte) ([]byte, error) {
builder := txscript.NewScriptBuilder()
// Was the pre-image to the payment hash presented?
builder.AddOp(txscript.OP_HASH160)
builder.AddOp(txscript.OP_DUP)
builder.AddData(paymentHash)
builder.AddOp(txscript.OP_EQUAL)
builder.AddOp(txscript.OP_IF)
// If so, let the receiver redeem after a relative timeout. This added
// delay gives the sender (at this time) an opportunity to re-claim the
// pending HTLC in the event that the receiver (at this time) broadcasts
// this old commitment transaction after it has been revoked.
builder.AddInt64(int64(relativeTimeout))
builder.AddOp(OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_2DROP)
builder.AddData(receiverKey.SerializeCompressed())
// Otherwise, if the sender has the revocation pre-image to the
// receiver's commitment transactions, then let them claim the
// funds immediately.
builder.AddOp(txscript.OP_ELSE)
builder.AddData(revokeHash)
builder.AddOp(txscript.OP_EQUAL)
// If not, then the sender needs to wait for the HTLC timeout. This
// clause may be executed if the receiver fails to present the r-value
// in time. This prevents the pending funds from being locked up
// indefinately.
builder.AddOp(txscript.OP_NOTIF)
builder.AddInt64(int64(absoluteTimeout))
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
builder.AddOp(txscript.OP_DROP)
builder.AddOp(txscript.OP_ENDIF)
builder.AddData(senderKey.SerializeCompressed())
builder.AddOp(txscript.OP_ENDIF)
builder.AddOp(txscript.OP_CHECKSIG)
return builder.Script()
}
// lockTimeToSequence converts the passed relative locktime to a sequence
// number in accordance to BIP-68.
// See: https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki
// * (Compatibility)
func lockTimeToSequence(isSeconds bool, locktime uint32) uint32 {
if !isSeconds {
// The locktime is to be expressed in confirmations. Apply the
// mask to restrict the number of confirmations to 65,535 or
// 1.25 years.
return SequenceLockTimeMask & locktime
}
// Set the 22nd bit which indicates the lock time is in seconds, then
// shift the locktime over by 9 since the time granularity is in
// 512-second intervals (2^9). This results in a max lock-time of
// 33,554,431 seconds, or 1.06 years.
return SequenceLockTimeSeconds | (locktime >> 9)
}
// commitScriptToSelf constructs the public key script for the output on the
// commitment transaction paying to the "owner" of said commitment transaction.
// If the other party learns of the pre-image to the revocation hash, then they
// can claim all the settled funds in the channel, plus the unsettled funds.
func commitScriptToSelf(csvTimeout uint32, selfKey, theirKey *btcec.PublicKey, revokeHash []byte) ([]byte, error) {
// This script is spendable under two conditions: either the 'csvTimeout'
// has passed and we can redeem our funds, or they have the pre-image
// to 'revokeHash'.
builder := txscript.NewScriptBuilder()
// If the pre-image for the revocation hash is presented, then allow a
// spend provided the proper signature.
builder.AddOp(txscript.OP_HASH160)
builder.AddData(revokeHash)
builder.AddOp(txscript.OP_EQUAL)
builder.AddOp(txscript.OP_IF)
builder.AddData(theirKey.SerializeCompressed())
builder.AddOp(txscript.OP_ELSE)
// Otherwise, we can re-claim our funds after a CSV delay of
// 'csvTimeout' timeout blocks, and a valid signature.
builder.AddInt64(int64(csvTimeout))
builder.AddOp(OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
builder.AddData(selfKey.SerializeCompressed())
builder.AddOp(txscript.OP_ENDIF)
builder.AddOp(txscript.OP_CHECKSIG)
return builder.Script()
}
// commitScriptUnencumbered constructs the public key script on the commitment
// transaction paying to the "other" party. This output is spendable
// immediately, requiring no contestation period.
func commitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) {
// This script goes to the "other" party, and it spendable immediately.
builder := txscript.NewScriptBuilder()
builder.AddOp(txscript.OP_DUP)
builder.AddOp(txscript.OP_HASH160)
builder.AddData(btcutil.Hash160(key.SerializeCompressed()))
builder.AddOp(txscript.OP_EQUALVERIFY)
builder.AddOp(txscript.OP_CHECKSIG)
return builder.Script()
}