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> <0> (spend using HTLC timeout transaction) // RECVR: // REVOK: // * receiver revoke // // OP_DUP OP_HASH160 OP_EQUAL // OP_IF // OP_CHECKSIG // OP_ELSE // // OP_SWAP OP_SIZE 32 OP_EQUAL // OP_NOTIF // OP_DROP 2 OP_SWAP 2 OP_CHECKMULTISIG // OP_ELSE // OP_HASH160 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) // This checks the receiver's signature so that a third party with // knowledge of the payment preimage still cannot steal the output. builder.AddOp(txscript.OP_CHECKSIG) // 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(signDesc.HashType)) witnessStack[1] = revokeKey.SerializeCompressed() witnessStack[2] = signDesc.WitnessScript return witnessStack, nil } // 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. This method first derives the appropriate revocation // key, and requires that the provided SignDescriptor has a local revocation // basepoint and commitment secret in the PubKey and DoubleTweak fields, // respectively. func SenderHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor, sweepTx *wire.MsgTx) (wire.TxWitness, error) { // Derive the revocation key using the local revocation base point and // commitment point. revokeKey := DeriveRevocationPubkey(signDesc.PubKey, signDesc.DoubleTweak.PubKey()) return senderHtlcSpendRevoke(signer, signDesc, revokeKey, sweepTx) } // 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(signDesc.HashType)) 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(receiverSig []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(receiverSig, byte(txscript.SigHashAll)) witnessStack[2] = append(sweepSig, byte(signDesc.HashType)) 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> // REVOK: // SENDR: 0 // // // OP_DUP OP_HASH160 OP_EQUAL // OP_IF // OP_CHECKSIG // OP_ELSE // // OP_SWAP OP_SIZE 32 OP_EQUAL // OP_IF // OP_HASH160 OP_EQUALVERIFY // 2 OP_SWAP 2 OP_CHECKMULTISIG // OP_ELSE // OP_DROP 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(signDesc.HashType)) 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(signDesc.HashType)) witnessStack[1] = revokeKey.SerializeCompressed() witnessStack[2] = 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. This method first derives the appropriate revocation // key, and requires that the provided SignDescriptor has a local revocation // basepoint and commitment secret in the PubKey and DoubleTweak fields, // respectively. func ReceiverHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor, sweepTx *wire.MsgTx) (wire.TxWitness, error) { // Derive the revocation key using the local revocation base point and // commitment point. revokeKey := DeriveRevocationPubkey(signDesc.PubKey, signDesc.DoubleTweak.PubKey()) return receiverHtlcSpendRevoke(signer, signDesc, revokeKey, sweepTx) } // 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(signDesc.HashType)) 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> <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> 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: // * 1 // // * To claim and HTLC output, either with a pre-image or due to a timeout: // * 0 // // OP_IF // // OP_ELSE // // OP_CHECKSEQUENCEVERIFY // OP_DROP // // 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(signDesc.HashType)) 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(signDesc.HashType)) 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: 1 // SENDRSWEEP: // // Output Script: // OP_IF // // OP_ELSE // OP_CHECKSEQUENCEVERIFY OP_DROP // // 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(signDesc.HashType)) 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(signDesc.HashType)) 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: 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(signDesc.HashType)) 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 rationale for tweaking all public keys used within the commitment // contracts is 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) return TweakPubKeyWithTweak(basePoint, tweakBytes) } // 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 { curve := btcec.S256() tweakX, tweakY := curve.ScalarBaseMult(tweakBytes) // TODO(roasbeef): check that both passed on curve? x, y := curve.Add(pubKey.X, pubKey.Y, tweakX, tweakY) return &btcec.PublicKey{ X: x, Y: y, Curve: curve, } } // 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, obfuscator [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:], obfuscator[:]) 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 obfuscator. // // See setStateNumHint for further details w.r.t exactly how the state-hints // are encoded. func GetStateNumHint(commitTx *wire.MsgTx, obfuscator [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:], obfuscator[:]) 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(), } }