lnwallet: add new functions to generate + claim second-level HTLC txns

This commit adds a series of new functions that can be used to generate
the second level HTLC transactions, and also to claim the output
created by the transaction after a delay. The details of the scripts
and transaction format can be found in BOLT #3.
This commit is contained in:
Olaoluwa Osuntokun 2017-07-29 18:18:16 -07:00
parent f70697bf71
commit 360876944e
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2
1 changed files with 230 additions and 8 deletions

View File

@ -561,20 +561,234 @@ func receiverHtlcSpendTimeout(signer Signer, signDesc *SignDescriptor,
return witnessStack, nil 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) {
hashCache := txscript.NewTxSigHashes(sweepTx) // Create a version two transaction (as the success version of this
sweepSig, err := txscript.RawTxInWitnessSignature( // spends an output with a CSV timeout), and set the lock-time to the
sweepTx, hashCache, 0, int64(outputAmt), commitScript, // specified absolute lock-time in blocks.
txscript.SigHashAll, senderKey) 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 { if err != nil {
return nil, err return nil, err
} }
witnessStack := wire.TxWitness(make([][]byte, 4)) // Finally, the output is simply the amount of the HTLC (minus the
witnessStack[0] = sweepSig // 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()
}
// htlcSuccessSpend 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.o
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[1] = nil
witnessStack[2] = nil witnessStack[2] = signDesc.WitnessScript
witnessStack[3] = commitScript
return witnessStack, nil
}
// htlcTimeoutSpend 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 return witnessStack, nil
} }
@ -694,6 +908,10 @@ func CommitSpendTimeout(signer Signer, signDesc *SignDescriptor,
// CommitSpendRevoke constructs a valid witness allowing a node to sweep the // CommitSpendRevoke constructs a valid witness allowing a node to sweep the
// settled output of a malicious counterparty who broadcasts a revoked // settled output of a malicious counterparty who broadcasts a revoked
// commitment transaction. // 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, func CommitSpendRevoke(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) { sweepTx *wire.MsgTx) (wire.TxWitness, error) {
@ -714,6 +932,10 @@ func CommitSpendRevoke(signer Signer, signDesc *SignDescriptor,
// CommitSpendNoDelay constructs a valid witness allowing a node to spend their // CommitSpendNoDelay constructs a valid witness allowing a node to spend their
// settled no-delay output on the counterparty's commitment transaction. // 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, func CommitSpendNoDelay(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) { sweepTx *wire.MsgTx) (wire.TxWitness, error) {