d96cf0ec64
* Fixes a bug in script_utils.go. CSV instead of CLTV was being used within the timeout clause of the receivers HTLC (sender can reclaim the HTLC).
252 lines
8.5 KiB
Go
252 lines
8.5 KiB
Go
package lnwallet
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
|
|
"github.com/btcsuite/btcd/btcec"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/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
|
|
)
|
|
|
|
// scriptHashPkScript generates a pay-to-script-hash public key script paying
|
|
// to the hash160 of the passed redeem script.
|
|
func scriptHashPkScript(redeemScript []byte) ([]byte, error) {
|
|
bldr := txscript.NewScriptBuilder()
|
|
bldr.AddOp(txscript.OP_HASH160)
|
|
bldr.AddData(btcutil.Hash160(redeemScript))
|
|
bldr.AddOp(txscript.OP_EQUAL)
|
|
return bldr.Script()
|
|
}
|
|
|
|
// getFundingPkScript generates the non-p2sh'd multisig script for 2 of 2
|
|
// pubkeys.
|
|
func genFundingPkScript(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()
|
|
}
|
|
|
|
// fundMultiSigOut create the redeemScript for the funding transaction, and
|
|
// also a TxOut paying to the p2sh of the multi-sig redeemScript. Give it the
|
|
// two pubkeys and it'll give you the p2sh'd txout. You don't have to remember
|
|
// the p2sh preimage, as long as you remember the pubkeys involved.
|
|
func fundMultiSigOut(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, error) {
|
|
if amt < 0 {
|
|
return nil, nil, fmt.Errorf("can't create FundTx script with " +
|
|
"negative coins")
|
|
}
|
|
|
|
// p2shify
|
|
redeemScript, err := genFundingPkScript(aPub, bPub)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
pkScript, err := scriptHashPkScript(redeemScript)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return redeemScript, wire.NewTxOut(amt, pkScript), nil
|
|
}
|
|
|
|
// spendMultiSig generates the scriptSig required to redeem the 2-of-2 p2sh
|
|
// multi-sig output.
|
|
func spendMultiSig(redeemScript, sigA, sigB []byte) ([]byte, error) {
|
|
bldr := txscript.NewScriptBuilder()
|
|
|
|
// add a 0 for some multisig fun
|
|
bldr.AddOp(txscript.OP_0)
|
|
|
|
// add sigA
|
|
bldr.AddData(sigA)
|
|
// add sigB
|
|
bldr.AddData(sigB)
|
|
|
|
// preimage goes on AT THE ENDDDD
|
|
bldr.AddData(redeemScript)
|
|
|
|
// that's all, get bytes
|
|
return bldr.Script()
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
|
|
// 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()
|
|
}
|