6e17c34229
This commit updates the script we use to match the current specification. The change is minor: we can say an extra byte by moving the OP_CHECKSIG to the end of the script, and swapping the checks and seqverify operations in the second clause. However, the witness remains the same!
1255 lines
48 KiB
Go
1255 lines
48 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. This clause transitions the state of the HLTC
|
|
// output into the delay+claim state by activating the off-chain covenant bound
|
|
// by the 2-of-2 multi-sig output. The HTLC success timeout transaction being
|
|
// signed has a relative timelock delay enforced by its sequence number. This
|
|
// delay give the sender of the HTLC enough time to revoke the output if this
|
|
// is a breach commitment transaction.
|
|
func receiverHtlcSpendRedeem(senderSig, paymentPreimage []byte,
|
|
signer Signer, signDesc *SignDescriptor,
|
|
htlcSuccessTx *wire.MsgTx) (wire.TxWitness, error) {
|
|
|
|
// First, we'll generate a signature for the HTLC success transaction.
|
|
// The signDesc should be signing with the public key used as the
|
|
// receiver's public key and also the correct single tweak.
|
|
sweepSig, err := signer.SignOutputRaw(htlcSuccessTx, signDesc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// The final witness stack is used the provide the script with the
|
|
// payment pre-image, and also execute the multi-sig clause after the
|
|
// pre-images matches. We add a nil item at the bottom of the stack in
|
|
// order to consume the extra pop within OP_CHECKMULTISIG.
|
|
witnessStack := wire.TxWitness(make([][]byte, 5))
|
|
witnessStack[0] = nil
|
|
witnessStack[1] = append(senderSig, byte(txscript.SigHashAll))
|
|
witnessStack[2] = append(sweepSig, byte(txscript.SigHashAll))
|
|
witnessStack[3] = paymentPreimage
|
|
witnessStack[4] = signDesc.WitnessScript
|
|
|
|
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(signer Signer, signDesc *SignDescriptor,
|
|
revokeKey *btcec.PublicKey, sweepTx *wire.MsgTx) (wire.TxWitness, error) {
|
|
|
|
// First, we'll generate a signature for the sweep transaction. The
|
|
// signDesc should be signing with the public key used as the fully
|
|
// derived revocation public key and also the correct double tweak
|
|
// value.
|
|
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
|
|
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, 3))
|
|
witnessStack[0] = append(sweepSig, byte(txscript.SigHashAll))
|
|
witnessStack[1] = revokeKey.SerializeCompressed()
|
|
witnessStack[2] = signDesc.WitnessScript
|
|
|
|
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.
|
|
//
|
|
// NOTE: The target input of the passed transaction MUST NOT have a final
|
|
// sequence number. Otherwise, the OP_CHECKLOCKTIMEVERIFY check will fail.
|
|
func receiverHtlcSpendTimeout(signer Signer, signDesc *SignDescriptor,
|
|
sweepTx *wire.MsgTx, cltvExpiry 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 = cltvExpiry
|
|
|
|
// With the lock time on the transaction set, we'll not generate a
|
|
// signature for the sweep transaction. The passed sign descriptor
|
|
// should be created using the raw public key of the sender (w/o the
|
|
// single tweak applied), and the single tweak set to the proper value
|
|
// taking into account the current state's point.
|
|
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
witnessStack := wire.TxWitness(make([][]byte, 3))
|
|
witnessStack[0] = append(sweepSig, byte(txscript.SigHashAll))
|
|
witnessStack[1] = nil
|
|
witnessStack[2] = signDesc.WitnessScript
|
|
|
|
return witnessStack, nil
|
|
}
|
|
|
|
// createHtlcTimeoutTx creates a transaction that spends the HTLC output on the
|
|
// commitment transaction of the peer that created an HTLC (the sender). This
|
|
// transaction essentially acts as an off-chain covenant as it spends a 2-of-2
|
|
// multi-sig output. This output requires a signature from both the sender and
|
|
// receiver of the HTLC. By using a distinct transaction, we're able to
|
|
// uncouple the timeout and delay clauses of the HTLC contract. This
|
|
// transaction is locked with an absolute lock-time so the sender can only
|
|
// attempt to claim the output using it after the lock time has passed.
|
|
//
|
|
// In order to spend the HTLC output, the witness for the passed transaction
|
|
// should be:
|
|
// * <0> <sender sig> <receiver sig> <0>
|
|
//
|
|
// NOTE: The passed amount for the HTLC should take into account the required
|
|
// fee rate at the time the HTLC was created. The fee should be able to
|
|
// entirely pay for this (tiny: 1-in 1-out) transaction.
|
|
func createHtlcTimeoutTx(htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
|
|
cltvExpiry, csvDelay uint32,
|
|
revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) {
|
|
|
|
// Create a version two transaction (as the success version of this
|
|
// spends an output with a CSV timeout), and set the lock-time to the
|
|
// specified absolute lock-time in blocks.
|
|
timeoutTx := wire.NewMsgTx(2)
|
|
timeoutTx.LockTime = cltvExpiry
|
|
|
|
// The input to the transaction is the outpoint that creates the
|
|
// original HTLC on the sender's commitment transaction.
|
|
timeoutTx.AddTxIn(&wire.TxIn{
|
|
PreviousOutPoint: htlcOutput,
|
|
})
|
|
|
|
// Next, we'll generate the script used as the output for all second
|
|
// level HTLC which forces a covenant w.r.t what can be done with all
|
|
// HTLC outputs.
|
|
witnessScript, err := secondLevelHtlcScript(revocationKey, delayKey,
|
|
csvDelay)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pkScript, err := witnessScriptHash(witnessScript)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Finally, the output is simply the amount of the HTLC (minus the
|
|
// required fees), paying to the regular second level HTLC script.
|
|
timeoutTx.AddTxOut(&wire.TxOut{
|
|
Value: int64(htlcAmt),
|
|
PkScript: pkScript,
|
|
})
|
|
|
|
return timeoutTx, nil
|
|
}
|
|
|
|
// createHtlcSuccessTx creats a transaction that spends the output on the
|
|
// commitment transaction of the peer that receives an HTLC. This transaction
|
|
// essentially acts as an off-chain covenant as it's only permitted to spend
|
|
// the designated HTLC output, and also that spend can _only_ be used as a
|
|
// state transition to create another output which actually allows redemption
|
|
// or revocation of an HTLC.
|
|
//
|
|
// In order to spend the HTLC output, the witness for the passed transaction
|
|
// should be:
|
|
// * <0> <sender sig> <recvr sig> <preimage>
|
|
func createHtlcSuccessTx(htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
|
|
csvDelay uint32,
|
|
revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) {
|
|
|
|
// Create a version two transaction (as the success version of this
|
|
// spends an output with a CSV timeout).
|
|
successTx := wire.NewMsgTx(2)
|
|
|
|
// The input to the transaction is the outpoint that creates the
|
|
// original HTLC on the sender's commitment transaction.
|
|
successTx.AddTxIn(&wire.TxIn{
|
|
PreviousOutPoint: htlcOutput,
|
|
})
|
|
|
|
// Next, we'll generate the script used as the output for all second
|
|
// level HTLC which forces a covenant w.r.t what can be done with all
|
|
// HTLC outputs.
|
|
witnessScript, err := secondLevelHtlcScript(revocationKey, delayKey,
|
|
csvDelay)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pkScript, err := witnessScriptHash(witnessScript)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Finally, the output is simply the amount of the HTLC (minus the
|
|
// required fees), paying to the timeout script.
|
|
successTx.AddTxOut(&wire.TxOut{
|
|
Value: int64(htlcAmt),
|
|
PkScript: pkScript,
|
|
})
|
|
|
|
return successTx, nil
|
|
}
|
|
|
|
// secondLevelHtlcScript is the uniform script that's used as the output for
|
|
// the second-level HTLC transactions. The second level transaction act as a
|
|
// sort of covenant, ensuring that an 2-of-2 multi-sig output can only be
|
|
// spent in a particular way, and to a particular output.
|
|
//
|
|
// Possible Input Scripts:
|
|
// * To revoke an HTLC output that has been transitioned to the claim+delay
|
|
// state:
|
|
// * <revoke sig> 1
|
|
//
|
|
// * To claim and HTLC output, either with a pre-image or due to a timeout:
|
|
// * <delay sig> 0
|
|
//
|
|
// OP_IF
|
|
// <revoke key>
|
|
// OP_ELSE
|
|
// <delay in blocks>
|
|
// OP_CHECKSEQUENCEVERIFY
|
|
// OP_DROP
|
|
// <delay key>
|
|
// OP_ENDIF
|
|
// OP_CHECKSIG
|
|
//
|
|
// TODO(roasbeef): possible renames for second-level
|
|
// * transition?
|
|
// * covenant output
|
|
func secondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey,
|
|
csvDelay uint32) ([]byte, error) {
|
|
|
|
builder := txscript.NewScriptBuilder()
|
|
|
|
// If this is the revocation clause for this script is to be executed,
|
|
// the spender will push a 1, forcing us to hit the true clause of this
|
|
// if statement.
|
|
builder.AddOp(txscript.OP_IF)
|
|
|
|
// If this this is the revocation case, then we'll push the revocation
|
|
// public key on the stack.
|
|
builder.AddData(revocationKey.SerializeCompressed())
|
|
|
|
// Otherwise, this is either the sender or receiver of the HTLC
|
|
// attempting to claim the HTLC output.
|
|
builder.AddOp(txscript.OP_ELSE)
|
|
|
|
// In order to give the other party time to execute the revocation
|
|
// clause above, we require a relative timeout to pass before the
|
|
// output can be spent.
|
|
builder.AddInt64(int64(csvDelay))
|
|
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
|
|
builder.AddOp(txscript.OP_DROP)
|
|
|
|
// If the relative timelock passes, then we'll add the delay key to the
|
|
// stack to ensure that we properly authenticate the spending party.
|
|
builder.AddData(delayKey.SerializeCompressed())
|
|
|
|
// Close out the if statement.
|
|
builder.AddOp(txscript.OP_ENDIF)
|
|
|
|
// In either case, we'll ensure that only either the party possessing
|
|
// the revocation private key, or the delay private key is able to
|
|
// spend this output.
|
|
builder.AddOp(txscript.OP_CHECKSIG)
|
|
|
|
return builder.Script()
|
|
}
|
|
|
|
// htlcSpendSuccess spends a second-level HTLC output. This function is to be
|
|
// used by the sender of an HTLC to claim the output after a relative timeout
|
|
// or the receiver of the HTLC to claim on-chain with the pre-image.
|
|
func htlcSpendSuccess(signer Signer, signDesc *SignDescriptor,
|
|
sweepTx *wire.MsgTx, csvDelay uint32) (wire.TxWitness, error) {
|
|
|
|
// We're required to wait a relative period of time before we can sweep
|
|
// the output in order to allow the other party to contest our claim of
|
|
// validity to this version of the commitment transaction.
|
|
sweepTx.TxIn[0].Sequence = lockTimeToSequence(false, csvDelay)
|
|
|
|
// Finally, OP_CSV requires that the version of the transaction
|
|
// spending a pkscript with OP_CSV within it *must* be >= 2.
|
|
sweepTx.Version = 2
|
|
|
|
// As we mutated the transaction, we'll re-calculate the sighashes for
|
|
// this instance.
|
|
signDesc.SigHashes = txscript.NewTxSigHashes(sweepTx)
|
|
|
|
// With the proper sequence an version set, we'll now sign the timeout
|
|
// transaction using the passed signed descriptor. In order to generate
|
|
// a valid signature, then signDesc should be using the base delay
|
|
// public key, and the proper single tweak bytes.
|
|
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// We set a zero as the first element the witness stack (ignoring the
|
|
// witness script), in order to force execution to the second portion
|
|
// of the if clause.
|
|
witnessStack := wire.TxWitness(make([][]byte, 3))
|
|
witnessStack[0] = append(sweepSig, byte(txscript.SigHashAll))
|
|
witnessStack[1] = nil
|
|
witnessStack[2] = signDesc.WitnessScript
|
|
|
|
return witnessStack, nil
|
|
}
|
|
|
|
// htlcTimeoutRevoke spends a second-level HTLC output. This function is to be
|
|
// used by the sender or receiver of an HTLC to claim the HTLC after a revoked
|
|
// commitment transaction was broadcast.
|
|
func htlcSpendRevoke(signer Signer, signDesc *SignDescriptor,
|
|
revokeTx *wire.MsgTx) (wire.TxWitness, error) {
|
|
|
|
// We don't need any spacial modifications to the transaction as this
|
|
// is just sweeping a revoked HTLC output. So we'll generate a regular
|
|
// witness signature.
|
|
sweepSig, err := signer.SignOutputRaw(revokeTx, signDesc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// We set a one as the first element the witness stack (ignoring the
|
|
// witness script), in order to force execution to the revocation
|
|
// clause in the second level HTLC script.
|
|
witnessStack := wire.TxWitness(make([][]byte, 3))
|
|
witnessStack[0] = append(sweepSig, byte(txscript.SigHashAll))
|
|
witnessStack[1] = []byte{1}
|
|
witnessStack[2] = signDesc.WitnessScript
|
|
|
|
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_ELSE
|
|
// <numRelativeBlocks> OP_CHECKSEQUENCEVERIFY OP_DROP
|
|
// <timeKey>
|
|
// OP_ENDIF
|
|
// OP_CHECKSIG
|
|
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_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(txscript.OP_CHECKSEQUENCEVERIFY)
|
|
builder.AddOp(txscript.OP_DROP)
|
|
builder.AddData(selfKey.SerializeCompressed())
|
|
|
|
builder.AddOp(txscript.OP_ENDIF)
|
|
|
|
// Finally, we'll validate the signature against the public key that's
|
|
// left on the top of the stack.
|
|
builder.AddOp(txscript.OP_CHECKSIG)
|
|
|
|
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.
|
|
//
|
|
// NOTE: The passed SignDescriptor should include the raw (untweaked)
|
|
// revocation base public key of the receiver and also the proper double tweak
|
|
// value based on the commitment secret of the revoked commitment.
|
|
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.
|
|
//
|
|
// NOTE: The passed SignDescriptor should include the raw (untweaked) public
|
|
// key of the receiver and also the proper single tweak value based on the
|
|
// current commitment point.
|
|
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>
|
|
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Finally, we'll manually craft the witness. The witness here is the
|
|
// exact same as a regular p2wkh witness, but we'll need to ensure that
|
|
// we use the tweaked public key as the last item in the witness stack
|
|
// which was originally used to created the pkScript we're spending.
|
|
witness := make([][]byte, 2)
|
|
witness[0] = append(sweepSig, byte(txscript.SigHashAll))
|
|
witness[1] = TweakPubKeyWithTweak(
|
|
signDesc.PubKey, signDesc.SingleTweak,
|
|
).SerializeCompressed()
|
|
|
|
return 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(),
|
|
}
|
|
}
|
|
|
|
// TweakPubKeyWithTweak is the exact same as the TweakPubKey function, however
|
|
// it accepts the raw tweak bytes directly rather than the commitment point.
|
|
func TweakPubKeyWithTweak(pubKey *btcec.PublicKey, tweakBytes []byte) *btcec.PublicKey {
|
|
tweakX, tweakY := btcec.S256().ScalarBaseMult(tweakBytes)
|
|
|
|
x, y := btcec.S256().Add(pubKey.X, pubKey.Y, tweakX, tweakY)
|
|
|
|
return &btcec.PublicKey{
|
|
X: x,
|
|
Y: y,
|
|
Curve: btcec.S256(),
|
|
}
|
|
}
|
|
|
|
// TweakPrivKey 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(),
|
|
}
|
|
}
|