lnd/lnwallet/script_utils.go
Olaoluwa Osuntokun 4b1a7091e6
lnwallet: update public key scripts for the HTLC receiver's outputs
This commit updates all the pkScripts used within the commitment
transaction of the party that is on the reign side of an HTLC.

The major difference in these scripts as the prior generation’s is that
the claim action is now spared into a distinct transition. This clause
is guarded by a 2-of-2 multi-sig op code, which effecting creates an
off-chain covenant forcing the party claiming the transaction to incur
a delay before the funds can be swept.
2017-07-30 17:48:50 -07:00

991 lines
37 KiB
Go

package lnwallet
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"fmt"
"math/big"
"golang.org/x/crypto/hkdf"
"golang.org/x/crypto/ripemd160"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg/chainhash"
"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 is the 22nd bit which indicates the lock
// time is in seconds.
SequenceLockTimeSeconds = uint32(1 << 22)
// TimelockShift is used to make sure the commitment transaction is
// spendable by setting the locktime with it so that it is larger than
// 500,000,000, thus interpreting it as Unix epoch timestamp and not
// a block height. It is also smaller than the current timestamp which
// has bit (1 << 30) set, so there is no risk of having the commitment
// transaction be rejected. This way we can safely use the lower 24 bits
// of the locktime field for part of the obscured commitment transaction
// number.
TimelockShift = uint32(1 << 29)
)
const (
// StateHintSize is the total number of bytes used between the sequence
// number and locktime of the commitment transaction use to encode a hint
// to the state number of a particular commitment transaction.
StateHintSize = 6
// maxStateHint is the maximum state number we're able to encode using
// StateHintSize bytes amongst the sequence number and locktime fields
// of the commitment transaction.
maxStateHint uint64 = (1 << 48) - 1
)
// 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(witnessScript []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder()
bldr.AddOp(txscript.OP_0)
scriptHash := sha256.Sum256(witnessScript)
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.
witnessScript, 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(witnessScript)
if err != nil {
return nil, nil, err
}
return witnessScript, wire.NewTxOut(amt, pkScript), nil
}
// SpendMultiSig generates the witness stack required to redeem the 2-of-2 p2wsh
// multi-sig output.
func SpendMultiSig(witnessScript, 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 witnessScript, we sorted the serialized
// public keys in descending order. So we do a quick comparison in order
// ensure the signatures appear on the Script Virtual 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 preimage as the last witness element.
witness[3] = witnessScript
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
}
// ripemd160H calculates the ripemd160 of the passed byte slice. This is used to
// calculate the intermediate hash for payment pre-images. Payment hashes are
// the result of ripemd160(sha256(paymentPreimage)). As a result, the value
// passed in should be the sha256 of the payment hash.
func ripemd160H(d []byte) []byte {
h := ripemd160.New()
h.Write(d)
return h.Sum(nil)
}
// senderHTLCScript constructs the public key script for an outgoing HTLC
// output payment for the sender's version of the commitment transaction. The
// possible script paths from this output include:
//
// * The sender timing out the HTLC using the second level HTLC timeout
// transaction.
// * The receiver of the HTLC claiming the output on-chain with the payment
// preimage.
// * The receiver of the HTLC sweeping all the funds in the case that a
// revoked commitment transaction bearing this HTLC was broadcast.
//
// Possible Input Scripts:
// SENDR: <0> <sendr sig> <recvr sig> <0> (spend using HTLC timeout transaction)
// RECVR: <recvr sig> <preimage>
// REVOK: <revoke sig> <revoke key>
// * receiver revoke
//
// OP_DUP OP_HASH160 <revocation key hash160> OP_EQUAL
// OP_IF
// OP_CHECKSIG
// OP_ELSE
// <recv key>
// OP_SWAP OP_SIZE 32 OP_EQUAL
// OP_NOTIF
// OP_DROP 2 OP_SWAP <sender key> 2 OP_CHECKMULTISIG
// OP_ELSE
// OP_HASH160 <ripemd160(payment hash)> OP_EQUALVERIFY
// OP_ENDIF
// OP_ENDIF
func senderHTLCScript(senderKey, receiverKey, revocationKey *btcec.PublicKey,
paymentHash []byte) ([]byte, error) {
builder := txscript.NewScriptBuilder()
// The opening operations are used to determine if this is the receiver
// of the HTLC attempting to sweep all the funds due to a contract
// breach. In this case, they'll place the revocation key at the top of
// the stack.
builder.AddOp(txscript.OP_DUP)
builder.AddOp(txscript.OP_HASH160)
builder.AddData(btcutil.Hash160(revocationKey.SerializeCompressed()))
builder.AddOp(txscript.OP_EQUAL)
// If the hash matches, then this is the revocation clause. The output
// can be spent if the check sig operation passes.
builder.AddOp(txscript.OP_IF)
builder.AddOp(txscript.OP_CHECKSIG)
// Otherwise, this may either be the receiver of the HTLC claiming with
// the pre-image, or the sender of the HTLC sweeping the output after
// it has timed out.
builder.AddOp(txscript.OP_ELSE)
// We'll do a bit of set up by pushing the receiver's key on the top of
// the stack. This will be needed later if we decide that this is the
// sender activating the time out clause with the HTLC timeout
// transaction.
builder.AddData(receiverKey.SerializeCompressed())
// Atm, the top item of the stack is the receiverKey's so we use a swap
// to expose what is either the payment pre-image or a signature.
builder.AddOp(txscript.OP_SWAP)
// With the top item swapped, check if it's 32 bytes. If so, then this
// *may* be the payment pre-image.
builder.AddOp(txscript.OP_SIZE)
builder.AddInt64(32)
builder.AddOp(txscript.OP_EQUAL)
// If it isn't then this might be the sender of the HTLC activating the
// time out clause.
builder.AddOp(txscript.OP_NOTIF)
// We'll drop the OP_IF return value off the top of the stack so we can
// reconstruct the multi-sig script used as an off-chain covenant. If
// two valid signatures are provided, ten then output will be deemed as
// spendable.
builder.AddOp(txscript.OP_DROP)
builder.AddOp(txscript.OP_2)
builder.AddOp(txscript.OP_SWAP)
builder.AddData(senderKey.SerializeCompressed())
builder.AddOp(txscript.OP_2)
builder.AddOp(txscript.OP_CHECKMULTISIG)
// Otherwise, then the only other case is that this is the receiver of
// the HTLC sweeping it on-chain with the payment pre-image.
builder.AddOp(txscript.OP_ELSE)
// Hash the top item of the stack and compare it with the hash160 of
// the payment hash, which is already the sha256 of the payment
// pre-image. By using this little trick we're able save space on-chain
// as the witness includes a 20-byte hash rather than a 32-byte hash.
builder.AddOp(txscript.OP_HASH160)
builder.AddData(ripemd160H(paymentHash))
builder.AddOp(txscript.OP_EQUALVERIFY)
// Close out the OP_IF statement above.
builder.AddOp(txscript.OP_ENDIF)
// Close out the OP_IF statement at the top of the script.
builder.AddOp(txscript.OP_ENDIF)
return builder.Script()
}
// senderHtlcSpendRevoke constructs a valid witness allowing the receiver of an
// HTLC to claim the output with knowledge of the revocation private key in the
// scenario that the sender of the HTLC broadcasts a previously revoked
// commitment transaction. A valid spend requires knowledge of the private key
// that corresponds to their revocation base point and also the private key fro
// the per commitment point, and a valid signature under the combined public
// key.
func senderHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor,
revokeKey *btcec.PublicKey, sweepTx *wire.MsgTx) (wire.TxWitness, error) {
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// The stack required to sweep a revoke HTLC output consists simply of
// the exact witness stack as one of a regular p2wkh spend. The only
// difference is that the keys used were derived in an adversarial
// manner in order to encode the revocation contract into a sig+key
// pair.
witnessStack := wire.TxWitness(make([][]byte, 3))
witnessStack[0] = append(sweepSig, byte(txscript.SigHashAll))
witnessStack[1] = revokeKey.SerializeCompressed()
witnessStack[2] = signDesc.WitnessScript
return witnessStack, nil
}
// senderHtlcSpendRedeem constructs a valid witness allowing the receiver of an
// HTLC to redeem the pending output in the scenario that the sender broadcasts
// their version of the commitment transaction. A valid spend requires
// knowledge of the payment preimage, and a valid signature under the receivers
// public key.
func senderHtlcSpendRedeem(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx, paymentPreimage []byte) (wire.TxWitness, error) {
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// The stack require to spend this output is simply the signature
// generated above under the receiver's public key, and the payment
// pre-image.
witnessStack := wire.TxWitness(make([][]byte, 3))
witnessStack[0] = append(sweepSig, byte(txscript.SigHashAll))
witnessStack[1] = paymentPreimage
witnessStack[2] = signDesc.WitnessScript
return witnessStack, nil
}
// senderHtlcSpendTimeout constructs a valid witness allowing the sender of an
// HTLC to activate the time locked covenant clause of a soon to be expired
// HTLC. This script simply spends the multi-sig output using the
// pre-generated HTLC timeout transaction.
func senderHtlcSpendTimeout(reciverSig []byte, signer Signer,
signDesc *SignDescriptor, htlcTimeoutTx *wire.MsgTx) (wire.TxWitness, error) {
sweepSig, err := signer.SignOutputRaw(htlcTimeoutTx, signDesc)
if err != nil {
return nil, err
}
// We place a zero as the first item of the evaluated witness stack in
// order to force Script execution to the HTLC timeout clause. The
// second zero is require to consume the extra pop due to a bug in the
// original OP_CHECKMULTISIG.
witnessStack := wire.TxWitness(make([][]byte, 5))
witnessStack[0] = nil
witnessStack[1] = append(reciverSig, byte(txscript.SigHashAll))
witnessStack[2] = append(sweepSig, byte(txscript.SigHashAll))
witnessStack[3] = nil
witnessStack[4] = signDesc.WitnessScript
return witnessStack, nil
}
// receiverHTLCScript constructs the public key script for an incoming HTLC
// output payment for the receiver's version of the commitment transaction. The
// possible execution paths from this script include:
// * The receiver of the HTLC uses it's second level HTLC transaction to
// advance the state of the HTLC into the delay+claim state.
// * The sender of the HTLC sweeps all the funds of the HTLC as a breached
// commitment was broadcast.
// * The sender of the HTLC sweeps the HTLC on-chain after the timeout period
// of the HTLC has passed.
//
// Possible Input Scripts:
// RECVR: <0> <sender sig> <recvr sig> <preimage>
// REVOK: <sig> <key>
// SENDR: <sig> 0
//
//
// OP_DUP OP_HASH160 <revocation key hash160> OP_EQUAL
// OP_IF
// OP_CHECKSIG
// OP_ELSE
// <sendr key>
// OP_SWAP OP_SIZE 32 OP_EQUAL
// OP_IF
// OP_HASH160 <ripemd160(payment hash)> OP_EQUALVERIFY
// 2 OP_SWAP <recvr key> 2 OP_CHECKMULTISIG
// OP_ELSE
// OP_DROP <cltv expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
// OP_CHECKSIG
// OP_ENDIF
// OP_ENDIF
func receiverHTLCScript(cltvExipiry uint32, senderKey,
receiverKey, revocationKey *btcec.PublicKey, paymentHash []byte) ([]byte, error) {
builder := txscript.NewScriptBuilder()
// The opening operations are used to determine if this is the sender
// of the HTLC attempting to sweep all the funds due to a contract
// breach. In this case, they'll place the revocation key at the top of
// the stack.
builder.AddOp(txscript.OP_DUP)
builder.AddOp(txscript.OP_HASH160)
builder.AddData(btcutil.Hash160(revocationKey.SerializeCompressed()))
builder.AddOp(txscript.OP_EQUAL)
// If the hash matches, then this is the revocation clause. The output
// can be spent if the check sig operation passes.
builder.AddOp(txscript.OP_IF)
builder.AddOp(txscript.OP_CHECKSIG)
// Otherwise, this may either be the receiver of the HTLC starting the
// claiming process via the second level HTLC success transaction and
// the pre-image, or the sender of the HTLC sweeping the output after
// it has timed out.
builder.AddOp(txscript.OP_ELSE)
// We'll do a bit of set up by pushing the sender's key on the top of
// the stack. This will be needed later if we decide that this is the
// receiver transitioning the output to the claim state using their
// second-level HTLC success transaction.
builder.AddData(senderKey.SerializeCompressed())
// Atm, the top item of the stack is the sender's key so we use a swap
// to expose what is either the payment pre-image or something else.
builder.AddOp(txscript.OP_SWAP)
// With the top item swapped, check if it's 32 bytes. If so, then this
// *may* be the payment pre-image.
builder.AddOp(txscript.OP_SIZE)
builder.AddInt64(32)
builder.AddOp(txscript.OP_EQUAL)
// If the item on the top of the stack is 32-bytes, then it is the
// proper size, so this indicates that the receiver of the HTLC is
// attempting to claim the output on-chain by transitioning the state
// of the HTLC to delay+claim.
builder.AddOp(txscript.OP_IF)
// Next we'll hash the item on the top of the stack, if it matches the
// payment pre-image, then we'll continue. Otherwise, we'll end the
// script here as this is the invalid payment pre-image.
builder.AddOp(txscript.OP_HASH160)
builder.AddData(ripemd160H(paymentHash))
builder.AddOp(txscript.OP_EQUALVERIFY)
// If the payment hash matches, then we'll also need to satisfy the
// multi-sig covenant by providing both signatures of the sender and
// receiver. If the convenient is met, then we'll allow the spending of
// this output, but only by the HTLC success transaction.
builder.AddOp(txscript.OP_2)
builder.AddOp(txscript.OP_SWAP)
builder.AddData(receiverKey.SerializeCompressed())
builder.AddOp(txscript.OP_2)
builder.AddOp(txscript.OP_CHECKMULTISIG)
// Otherwise, this might be the sender of the HTLC attempting to sweep
// it on-chain after the timeout.
builder.AddOp(txscript.OP_ELSE)
// We'll drop the extra item (which is the output from evaluating the
// OP_EQUAL) above from the stack.
builder.AddOp(txscript.OP_DROP)
// With that item dropped off, we can now enforce the absolute
// lock-time required to timeout the HTLC. If the time has passed, then
// we'll proceed with a checksig to ensure that this is actually the
// sender of he original HLTC.
builder.AddInt64(int64(cltvExipiry))
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
builder.AddOp(txscript.OP_DROP)
builder.AddOp(txscript.OP_CHECKSIG)
// Close out the inner if statement.
builder.AddOp(txscript.OP_ENDIF)
// Close out the outer if statement.
builder.AddOp(txscript.OP_ENDIF)
return builder.Script()
}
// receiverHtlcSpendRedeem constructs a valid witness allowing the receiver of
// an HTLC to redeem the conditional payment in the event that their commitment
// transaction is broadcast. Since this is a pay out to the receiving party as
// an output on their commitment transaction, a relative time delay is required
// before the output can be spent.
func receiverHtlcSpendRedeem(commitScript []byte, outputAmt btcutil.Amount,
reciverKey *btcec.PrivateKey, sweepTx *wire.MsgTx,
paymentPreimage []byte, relativeTimeout uint32) (wire.TxWitness, error) {
// In order to properly spend the transaction, we need to set the
// sequence number. We do this by converting the relative block delay
// into a sequence number value able to be interpreted by
// OP_CHECKSEQUENCEVERIFY.
sweepTx.TxIn[0].Sequence = lockTimeToSequence(false, relativeTimeout)
// Additionally, OP_CSV requires that the version of the transaction
// spending a pkscript with OP_CSV within it *must* be >= 2.
sweepTx.Version = 2
hashCache := txscript.NewTxSigHashes(sweepTx)
sweepSig, err := txscript.RawTxInWitnessSignature(
sweepTx, hashCache, 0, int64(outputAmt), commitScript,
txscript.SigHashAll, reciverKey)
if err != nil {
return nil, err
}
// Place a one as the first item in the evaluated witness stack to
// force script execution to the HTLC redemption clause.
witnessStack := wire.TxWitness(make([][]byte, 4))
witnessStack[0] = sweepSig
witnessStack[1] = paymentPreimage
witnessStack[2] = []byte{1}
witnessStack[3] = commitScript
return witnessStack, nil
}
// receiverHtlcSpendRevoke constructs a valid witness allowing the sender of an
// HTLC within a previously revoked commitment transaction to re-claim the
// pending funds in the case that the receiver broadcasts this revoked
// commitment transaction.
func receiverHtlcSpendRevoke(commitScript []byte, outputAmt btcutil.Amount,
senderKey *btcec.PrivateKey, sweepTx *wire.MsgTx,
revokePreimage []byte) (wire.TxWitness, error) {
// TODO(roasbeef): move sig generate outside func, or just factor out?
hashCache := txscript.NewTxSigHashes(sweepTx)
sweepSig, err := txscript.RawTxInWitnessSignature(
sweepTx, hashCache, 0, int64(outputAmt), commitScript,
txscript.SigHashAll, senderKey)
if err != nil {
return nil, err
}
// We place a zero, then one as the first items in the evaluated
// witness stack in order to force script execution to the HTLC
// revocation clause.
witnessStack := wire.TxWitness(make([][]byte, 5))
witnessStack[0] = sweepSig
witnessStack[1] = revokePreimage
witnessStack[2] = []byte{1}
witnessStack[3] = nil
witnessStack[4] = commitScript
return witnessStack, nil
}
// receiverHtlcSpendTimeout constructs a valid witness allowing the sender of
// an HTLC to recover the pending funds after an absolute timeout in the
// scenario that the receiver of the HTLC broadcasts their version of the
// commitment transaction.
func receiverHtlcSpendTimeout(commitScript []byte, outputAmt btcutil.Amount,
senderKey *btcec.PrivateKey, sweepTx *wire.MsgTx,
absoluteTimeout uint32) (wire.TxWitness, error) {
// The HTLC output has an absolute time period before we are permitted
// to recover the pending funds. Therefore we need to set the locktime
// on this sweeping transaction in order to pass Script verification.
sweepTx.LockTime = absoluteTimeout
hashCache := txscript.NewTxSigHashes(sweepTx)
sweepSig, err := txscript.RawTxInWitnessSignature(
sweepTx, hashCache, 0, int64(outputAmt), commitScript,
txscript.SigHashAll, senderKey)
if err != nil {
return nil, err
}
witnessStack := wire.TxWitness(make([][]byte, 4))
witnessStack[0] = sweepSig
witnessStack[1] = nil
witnessStack[2] = nil
witnessStack[3] = commitScript
return witnessStack, nil
}
// 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.
return 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 preimage to the revocation hash, then they
// can claim all the settled funds in the channel, plus the unsettled funds.
//
// Possible Input Scripts:
// REVOKE: <sig> 1
// SENDRSWEEP: <sig> <emptyvector>
//
// Output Script:
// OP_IF
// <revokeKey> OP_CHECKSIG
// OP_ELSE
// <timeKey> OP_CHECKSIGVERIFY
// <numRelativeBlocks> OP_CHECKSEQUENCEVERIFY
// OP_ENDIF
func commitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey) ([]byte, error) {
// This script is spendable under two conditions: either the
// 'csvTimeout' has passed and we can redeem our funds, or they can
// produce a valid signature with the revocation public key. The
// revocation public key will *only* be known to the other party if we
// have divulged the revocation hash, allowing them to homomorphically
// derive the proper private key which corresponds to the revoke public
// key.
builder := txscript.NewScriptBuilder()
builder.AddOp(txscript.OP_IF)
// If a valid signature using the revocation key is presented, then
// allow an immediate spend provided the proper signature.
builder.AddData(revokeKey.SerializeCompressed())
builder.AddOp(txscript.OP_CHECKSIG)
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.AddData(selfKey.SerializeCompressed())
builder.AddOp(txscript.OP_CHECKSIGVERIFY)
builder.AddInt64(int64(csvTimeout))
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_ENDIF)
return builder.Script()
}
// commitScriptUnencumbered constructs the public key script on the commitment
// transaction paying to the "other" party. The constructed output is a normal
// p2wkh output 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_0)
builder.AddData(btcutil.Hash160(key.SerializeCompressed()))
return builder.Script()
}
// CommitSpendTimeout constructs a valid witness allowing the owner of a
// particular commitment transaction to spend the output returning settled
// funds back to themselves after a relative block timeout. In order to
// properly spend the transaction, the target input's sequence number should be
// set accordingly based off of the target relative block timeout within the
// redeem script. Additionally, OP_CSV requires that the version of the
// transaction spending a pkscript with OP_CSV within it *must* be >= 2.
func CommitSpendTimeout(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
// Ensure the transaction version supports the validation of sequence
// locks and CSV semantics.
if sweepTx.Version < 2 {
return nil, fmt.Errorf("version of passed transaction MUST "+
"be >= 2, not %v", sweepTx.Version)
}
// With the sequence number in place, we're now able to properly sign
// off on the sweep transaction.
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// Place an empty byte as the first item in the evaluated witness stack
// to force script execution to the timeout spend clause. We need to
// place an empty byte in order to ensure our script is still valid
// from the PoV of nodes that are enforcing minimal OP_IF/OP_NOTIF.
witnessStack := wire.TxWitness(make([][]byte, 3))
witnessStack[0] = append(sweepSig, byte(txscript.SigHashAll))
witnessStack[1] = nil
witnessStack[2] = signDesc.WitnessScript
return witnessStack, nil
}
// CommitSpendRevoke constructs a valid witness allowing a node to sweep the
// settled output of a malicious counterparty who broadcasts a revoked
// commitment transaction.
func CommitSpendRevoke(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// Place a 1 as the first item in the evaluated witness stack to
// force script execution to the revocation clause.
witnessStack := wire.TxWitness(make([][]byte, 3))
witnessStack[0] = append(sweepSig, byte(txscript.SigHashAll))
witnessStack[1] = []byte{1}
witnessStack[2] = signDesc.WitnessScript
return witnessStack, nil
}
// CommitSpendNoDelay constructs a valid witness allowing a node to spend their
// settled no-delay output on the counterparty's commitment transaction.
func CommitSpendNoDelay(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
// This is just a regular p2wkh spend which looks something like:
// * witness: <sig> <pubkey>
inputScript, err := signer.ComputeInputScript(sweepTx, signDesc)
if err != nil {
return nil, err
}
return wire.TxWitness(inputScript.Witness), nil
}
// SingleTweakBytes computes set of bytes we call the single tweak. The purpose
// of the single tweak is to randomize all regular delay and payment base
// points. To do this, we generate a hash that binds the commitment point to
// the pay/delay base point. The end end results is that the basePoint is
// tweaked as follows:
//
// * key = basePoint + sha256(commitPoint || basePoint)*G
func SingleTweakBytes(commitPoint, basePoint *btcec.PublicKey) []byte {
h := sha256.New()
h.Write(commitPoint.SerializeCompressed())
h.Write(basePoint.SerializeCompressed())
return h.Sum(nil)
}
// TweakPubKey tweaks a public base point given a per commitment point. The per
// commitment point is a unique point on our target curve for each commitment
// transaction. When tweaking a local base point for use in a remote commitment
// transaction, the remote party's current per commitment point is to be used.
// The opposite applies for when tweaking remote keys. Precisely, the following
// operation is used to "tweak" public keys:
//
// tweakPub := basePoint + sha256(commitPoint || basePoint) * G
// := G*k + sha256(commitPoint || basePoint)*G
// := G*(k + sha256(commitPoint || basePoint)
//
// Therefore, if a party possess the value k, the private key of the base
// point, then they are able to derive the private key by computing: compute
// the proper private key for the revokeKey by computing:
//
// revokePriv := k + sha256(commitPoint || basePoint) mod N
//
// Where N is the order of the sub-group.
//
// The rational for tweaking all public keys used within the commitment
// contracts are to ensure that all keys are properly delinearized to avoid any
// funny business when jointly collaborating to compute public and private
// keys. Additionally, the use of the per commitment point ensures that each
// commitment state houses a unique set of keys which is useful when creating
// blinded channel outsourcing protocols.
//
// TODO(roasbeef): should be using double-scalar mult here
func TweakPubKey(basePoint, commitPoint *btcec.PublicKey) *btcec.PublicKey {
tweakBytes := SingleTweakBytes(commitPoint, basePoint)
tweakX, tweakY := btcec.S256().ScalarBaseMult(tweakBytes)
// TODO(roasbeef): check that both passed on curve?
x, y := btcec.S256().Add(basePoint.X, basePoint.Y, tweakX, tweakY)
return &btcec.PublicKey{
X: x,
Y: y,
Curve: btcec.S256(),
}
}
// TweakPrivKek tweaks the private key of a public base point given a per
// commitment point. The per commitment secret is the revealed revocation
// secret for the commitment state in question. This private key will only need
// to be generated in the case that a channel counter party broadcasts a
// revoked state. Precisely, the following operation is used to derive a
// tweaked private key:
//
// * tweakPriv := basePriv + sha256(commitment || basePub) mod N
//
// Where N is the order of the sub-group.
func TweakPrivKey(basePriv *btcec.PrivateKey, commitTweak []byte) *btcec.PrivateKey {
// tweakInt := sha256(commitPoint || basePub)
tweakInt := new(big.Int).SetBytes(commitTweak)
tweakInt = tweakInt.Add(tweakInt, basePriv.D)
tweakInt = tweakInt.Mod(tweakInt, btcec.S256().N)
tweakPriv, _ := btcec.PrivKeyFromBytes(btcec.S256(), tweakInt.Bytes())
return tweakPriv
}
// DeriveRevocationPubkey derives the revocation public key given the
// counterparty's commitment key, and revocation preimage derived via a
// pseudo-random-function. In the event that we (for some reason) broadcast a
// revoked commitment transaction, then if the other party knows the revocation
// preimage, then they'll be able to derive the corresponding private key to
// this private key by exploiting the homomorphism in the elliptic curve group:
// * https://en.wikipedia.org/wiki/Group_homomorphism#Homomorphisms_of_abelian_groups
//
// The derivation is performed as follows:
//
// revokeKey := revokeBase * sha256(revocationBase || commitPoint) +
// commitPoint * sha256(commitPoint || revocationBase)
//
// := G*(revokeBasePriv * sha256(revocationBase || commitPoint)) +
// G*(commitSecret * sha256(commitPoint || revocationBase))
//
// := G*(revokeBasePriv * sha256(revocationBase || commitPoint) +
// commitSecret * sha256(commitPoint || revocationBase))
//
// Therefore, once we divulge the revocation secret, the remote peer is able to
// compute the proper private key for the revokeKey by computing:
//
// revokePriv := (revokeBasePriv * sha256(revocationBase || commitPoint)) +
// (commitSecret * sha256(commitPoint || revocationBase)) mod N
//
// Where N is the order of the sub-group.
func DeriveRevocationPubkey(revokeBase, commitPoint *btcec.PublicKey) *btcec.PublicKey {
// R = revokeBase * sha256(revocationBase || commitPoint)
revokeTweakBytes := SingleTweakBytes(revokeBase, commitPoint)
rX, rY := btcec.S256().ScalarMult(revokeBase.X, revokeBase.Y,
revokeTweakBytes)
// C = commitPoint * sha256(commitPoint || revocationBase)
commitTweakBytes := SingleTweakBytes(commitPoint, revokeBase)
cX, cY := btcec.S256().ScalarMult(commitPoint.X, commitPoint.Y,
commitTweakBytes)
// Now that we have the revocation point, we add this to their commitment
// public key in order to obtain the revocation public key.
//
// P = R + C
revX, revY := btcec.S256().Add(rX, rY, cX, cY)
return &btcec.PublicKey{
X: revX,
Y: revY,
Curve: btcec.S256(),
}
}
// DeriveRevocationPrivKey derives the revocation private key given a node's
// commitment private key, and the preimage to a previously seen revocation
// hash. Using this derived private key, a node is able to claim the output
// within the commitment transaction of a node in the case that they broadcast
// a previously revoked commitment transaction.
//
// The private key is derived as follwos:
// revokePriv := (revokeBasePriv * sha256(revocationBase || commitPoint)) +
// (commitSecret * sha256(commitPoint || revocationBase)) mod N
//
// Where N is the order of the sub-group.
func DeriveRevocationPrivKey(revokeBasePriv *btcec.PrivateKey,
commitSecret *btcec.PrivateKey) *btcec.PrivateKey {
// r = sha256(revokeBasePub || commitPoint)
revokeTweakBytes := SingleTweakBytes(revokeBasePriv.PubKey(),
commitSecret.PubKey())
revokeTweakInt := new(big.Int).SetBytes(revokeTweakBytes)
// c = sha256(commitPoint || revokeBasePub)
commitTweakBytes := SingleTweakBytes(commitSecret.PubKey(),
revokeBasePriv.PubKey())
commitTweakInt := new(big.Int).SetBytes(commitTweakBytes)
// Finally to derive the revocation secret key we'll perform the
// following operation:
//
// k = (revocationPriv * r) + (commitSecret * c) mod N
//
// This works since:
// P = (G*a)*b + (G*c)*d
// P = G*(a*b) + G*(c*d)
// P = G*(a*b + c*d)
revokeHalfPriv := revokeTweakInt.Mul(revokeTweakInt, revokeBasePriv.D)
commitHalfPriv := commitTweakInt.Mul(commitTweakInt, commitSecret.D)
revocationPriv := revokeHalfPriv.Add(revokeHalfPriv, commitHalfPriv)
revocationPriv = revocationPriv.Mod(revocationPriv, btcec.S256().N)
priv, _ := btcec.PrivKeyFromBytes(btcec.S256(), revocationPriv.Bytes())
return priv
}
// DeriveRevocationRoot derives an root unique to a channel given the
// derivation root, and the blockhash that the funding process began at and the
// remote node's identity public key. The seed is derived using the HKDF[1][2]
// instantiated with sha-256. With this schema, once we know the block hash of
// the funding transaction, and who we funded the channel with, we can
// reconstruct all of our revocation state.
//
// [1]: https://eprint.iacr.org/2010/264.pdf
// [2]: https://tools.ietf.org/html/rfc5869
func DeriveRevocationRoot(derivationRoot *btcec.PrivateKey,
blockSalt chainhash.Hash, nodePubKey *btcec.PublicKey) chainhash.Hash {
secret := derivationRoot.Serialize()
salt := blockSalt[:]
info := nodePubKey.SerializeCompressed()
seedReader := hkdf.New(sha256.New, secret, salt, info)
// It's safe to ignore the error her as we know for sure that we won't
// be draining the HKDF past its available entropy horizon.
// TODO(roasbeef): revisit...
var root chainhash.Hash
seedReader.Read(root[:])
return root
}
// SetStateNumHint encodes the current state number within the passed
// commitment transaction by re-purposing the locktime and sequence fields in
// the commitment transaction to encode the obfuscated state number. The state
// number is encoded using 48 bits. The lower 24 bits of the lock time are the
// lower 24 bits of the obfuscated state number and the lower 24 bits of the
// sequence field are the higher 24 bits. Finally before encoding, the
// obfuscater is XOR'd against the state number in order to hide the exact
// state number from the PoV of outside parties.
func SetStateNumHint(commitTx *wire.MsgTx, stateNum uint64,
obsfucator [StateHintSize]byte) error {
// With the current schema we are only able able to encode state num
// hints up to 2^48. Therefore if the passed height is greater than our
// state hint ceiling, then exit early.
if stateNum > maxStateHint {
return fmt.Errorf("unable to encode state, %v is greater "+
"state num that max of %v", stateNum, maxStateHint)
}
if len(commitTx.TxIn) != 1 {
return fmt.Errorf("commitment tx must have exactly 1 input, "+
"instead has %v", len(commitTx.TxIn))
}
// Convert the obfuscator into a uint64, then XOR that against the
// targeted height in order to obfuscate the state number of the
// commitment transaction in the case that either commitment
// transaction is broadcast directly on chain.
var obfs [8]byte
copy(obfs[2:], obsfucator[:])
xorInt := binary.BigEndian.Uint64(obfs[:])
stateNum = stateNum ^ xorInt
// Set the height bit of the sequence number in order to disable any
// sequence locks semantics.
commitTx.TxIn[0].Sequence = uint32(stateNum>>24) | wire.SequenceLockTimeDisabled
commitTx.LockTime = uint32(stateNum&0xFFFFFF) | TimelockShift
return nil
}
// GetStateNumHint recovers the current state number given a commitment
// transaction which has previously had the state number encoded within it via
// setStateNumHint and a shared obsfucator.
//
// See setStateNumHint for further details w.r.t exactly how the state-hints
// are encoded.
func GetStateNumHint(commitTx *wire.MsgTx, obsfucator [StateHintSize]byte) uint64 {
// Convert the obfuscater into a uint64, this will be used to
// de-obfuscate the final recovered state number.
var obfs [8]byte
copy(obfs[2:], obsfucator[:])
xorInt := binary.BigEndian.Uint64(obfs[:])
// Retrieve the state hint from the sequence number and locktime
// of the transaction.
stateNumXor := uint64(commitTx.TxIn[0].Sequence&0xFFFFFF) << 24
stateNumXor |= uint64(commitTx.LockTime & 0xFFFFFF)
// Finally, to obtain the final state number, we XOR by the obfuscater
// value to de-obfuscate the state number.
return stateNumXor ^ xorInt
}
// ComputeCommitmentPoint generates a commitment point given a commitment
// secret. The commitment point for each state is used to randomize each key in
// the key-ring and also to used as a tweak to derive new public+private keys
// for the state.
func ComputeCommitmentPoint(commitSecret []byte) *btcec.PublicKey {
x, y := btcec.S256().ScalarBaseMult(commitSecret)
return &btcec.PublicKey{
X: x,
Y: y,
Curve: btcec.S256(),
}
}