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(OP_CHECKSEQUENCEVERIFY)
|
|
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()
|
|
}
|