diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 8856cf3d..231cc70c 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -1,6 +1,7 @@ package wallet import ( + "bytes" "sync" "li.lan/labs/plasma/chainntfs" @@ -18,142 +19,20 @@ const ( MaxPendingPayments = 10 ) -// P2SHify... -func P2SHify(scriptBytes []byte) ([]byte, error) { - bldr := txscript.NewScriptBuilder() - bldr.AddOp(txscript.OP_HASH160) - bldr.AddData(btcutil.Hash160(scriptBytes)) - bldr.AddOp(txscript.OP_EQUAL) - return bldr.Script() -} - -//TODO(j): Creates a CLTV-only funding Tx (reserve is *REQUIRED*) -//This works for only CLTV soft-fork (no CSV/segwit soft-fork in yet) -// -//Commit funds to Funding Tx, will timeout after the fundingTimeLock and refund -//back using CLTV. As there is no way to enforce HTLCs, we rely upon a reserve -//and have each party's HTLCs in-transit be less than their Commitment reserve. -//In the event that someone incorrectly broadcasts an old Commitment TX, then -//the counterparty claims the full reserve. It may be possible for either party -//to claim the HTLC(!!! But it's okay because the "honest" party is made whole -//via the reserve). If it's two-funder there are two outputs and the -//Commitments spends from both outputs in the Funding Tx. Two-funder requires -//the ourKey/theirKey sig positions to be swapped (should be in 1 funding tx). -// -//Quick note before I forget: The revocation hash is used in CLTV-only for -//single-funder (without an initial payment) *as part of an additional output -//in the Commitment Tx for the reserve*. This is to establish a unidirectional -//channel UNITL the recipient has sufficient funds. When the recipient has -//sufficient funds, the revocation is exchanged and allows the recipient to -//claim the full reserve as penalty if the incorrect Commitment is broadcast -//(otherwise it's timelocked refunded back to the sender). From then on, there -//is no additional output in Commitment Txes. [side caveat, first payment must -//be above minimum UTXO output size in single-funder] For now, let's keep it -//simple and assume dual funder (with both funding above reserve) -func createCLTVFundingTx(fundingTimeLock int64, ourKey *btcec.PublicKey, theirKey *btcec.PublicKey) (*wire.MsgTx, error) { - script := txscript.NewScriptBuilder() - //See how many entries there are - //2: it's a 2-of-2 multisig - //anything else: assume it's a CLTV-timeout 1-sig only - script.AddOp(txscript.OP_DEPTH) - script.AddInt64(2) - script.AddOp(txscript.OP_EQUAL) - - //If this is a 2-of-2 multisig, read the first sig - script.AddOp(txscript.OP_IF) - //Sig2 (not P2PKH, the pubkey is in the redeemScript) - script.AddData(ourKey.SerializeCompressed()) - script.AddOp(txscript.OP_CHECKSIGVERIFY) //gotta be verify! - - //If this is timed out - script.AddOp(txscript.OP_ELSE) - script.AddInt64(fundingTimeLock) - script.AddOp(txscript.OP_NOP2) //CLTV - //Sig (not P2PKH, the pubkey is in the redeemScript) - script.AddOp(txscript.OP_CHECKSIG) - script.AddOp(txscript.OP_DROP) - script.AddOp(txscript.OP_ENDIF) - - //Read the other sig if it's 2-of-2, only one if it's timed out - script.AddData(theirKey.SerializeCompressed()) - script.AddOp(txscript.OP_CHECKSIG) - - fundingTx := wire.NewMsgTx() - //TODO(j) Add the inputs/outputs - - return fundingTx, nil -} - -// createCommitTx... -func createCommitTx(fundingOutput *wire.TxIn, ourKey, theirKey *btcec.PublicKey, - revokeHash [32]byte, csvTimeout int64, amtToUs, - amtToThem btcutil.Amount) (*wire.MsgTx, error) { - - // First, we create the script paying to us. This script is spendable - // under two conditions: either the 'csvTimeout' has passed and we can - // redeem our funds, or they have the pre-image to 'revokeHash'. - scriptToUs := txscript.NewScriptBuilder() - scriptToUs.AddOp(txscript.OP_HASH160) - scriptToUs.AddData(revokeHash[:]) - scriptToUs.AddOp(txscript.OP_EQUAL) - scriptToUs.AddOp(txscript.OP_IF) - scriptToUs.AddData(theirKey.SerializeCompressed()) - scriptToUs.AddOp(txscript.OP_ELSE) - scriptToUs.AddInt64(csvTimeout) - scriptToUs.AddOp(txscript.OP_NOP3) // CSV - scriptToUs.AddOp(txscript.OP_DROP) - scriptToUs.AddData(ourKey.SerializeCompressed()) - scriptToUs.AddOp(txscript.OP_ENDIF) - scriptToUs.AddOp(txscript.OP_CHECKSIG) - - // TODO(roasbeef): store - ourRedeemScript, err := scriptToUs.Script() - if err != nil { - return nil, err - } - payToUsScriptHash, err := P2SHify(ourRedeemScript) - if err != nil { - return nil, err - } - - // Next, we create the script paying to them. This is just a regular - // P2PKH (wrapped in P2SH) with their key. - scriptToThem := txscript.NewScriptBuilder() - scriptToThem.AddOp(txscript.OP_DUP) - scriptToThem.AddOp(txscript.OP_HASH160) - scriptToThem.AddData(btcutil.Hash160(theirKey.SerializeCompressed())) - scriptToThem.AddOp(txscript.OP_EQUALVERIFY) - scriptToThem.AddOp(txscript.OP_CHECKSIG) - - theirRedeemScript, err := scriptToThem.Script() - if err != nil { - return nil, err - } - payToThemScriptHash, err := P2SHify(theirRedeemScript) - if err != nil { - return nil, err - } - - // TODO(roasbeef): sort outputs? - commitTx := wire.NewMsgTx() - commitTx.AddTxIn(fundingOutput) - commitTx.AddTxOut(wire.NewTxOut(int64(amtToUs), payToUsScriptHash)) - commitTx.AddTxOut(wire.NewTxOut(int64(amtToThem), payToThemScriptHash)) - - return commitTx, nil -} - type nodeId [32]byte // OpenChannelState... // TODO(roasbeef): script gen methods on this? type OpenChannelState struct { - // Hash? or Their current pubKey? - theirLNID [32]byte - fundingType FundingType - fundingAmount btcutil.Amount + // Hash? or Their current pubKey? + // TODO(roasbeef): switch to Tadge's LNId + theirLNID [32]byte + + minFeePerKb btcutil.Amount + //Our reserve. assume symmetric reserve amounts + reserveAmount btcutil.Amount ourCommitKey *btcec.PrivateKey theirCommitKey *btcec.PublicKey @@ -165,17 +44,35 @@ type OpenChannelState struct { theirCommitTx *wire.MsgTx ourCommitTx *wire.MsgTx - finalFundingTx *wire.MsgTx + fundingTx *wire.MsgTx - ourMultiSigKey *btcec.PrivateKey + multiSigKey *btcec.PrivateKey fundingRedeemScript []byte ourShaChain *revocation.HyperShaChain theirShaChain *revocation.HyperShaChain + // Final delivery address + ourDeliveryAddress btcutil.Address + theirDeliveryAddress btcutil.Address + // In blocks htlcTimeout uint32 csvDelay uint32 + + numUpdates uint64 +} + +func (o *OpenChannelState) Encode(b bytes.Buffer) error { + return nil +} + +func (o *OpenChannelState) Decode(b bytes.Buffer) error { + return nil +} + +func newOpenChannelState(fType FundingType, ID [32]byte) *OpenChannelState { + return &OpenChannelState{fundingType: fType, theirLNID: ID} } // LightningChannel... @@ -251,3 +148,128 @@ func (lc *LightningChannel) AddTheirSig(sig []byte) error { func (lc *LightningChannel) VerifyCommitmentUpdate() error { return nil } + +// P2SHify... +func P2SHify(scriptBytes []byte) ([]byte, error) { + bldr := txscript.NewScriptBuilder() + bldr.AddOp(txscript.OP_HASH160) + bldr.AddData(btcutil.Hash160(scriptBytes)) + bldr.AddOp(txscript.OP_EQUAL) + return bldr.Script() +} + +// createCommitTx... +func createCommitTx(fundingOutput *wire.TxIn, ourKey, theirKey *btcec.PublicKey, + revokeHash [32]byte, csvTimeout int64, amtToUs, + amtToThem btcutil.Amount) (*wire.MsgTx, error) { + + // First, we create the script paying to us. This script is spendable + // under two conditions: either the 'csvTimeout' has passed and we can + // redeem our funds, or they have the pre-image to 'revokeHash'. + scriptToUs := txscript.NewScriptBuilder() + scriptToUs.AddOp(txscript.OP_HASH160) + scriptToUs.AddData(revokeHash[:]) + scriptToUs.AddOp(txscript.OP_EQUAL) + scriptToUs.AddOp(txscript.OP_IF) + scriptToUs.AddData(theirKey.SerializeCompressed()) + scriptToUs.AddOp(txscript.OP_ELSE) + scriptToUs.AddInt64(csvTimeout) + scriptToUs.AddOp(txscript.OP_NOP3) // CSV + scriptToUs.AddOp(txscript.OP_DROP) + scriptToUs.AddData(ourKey.SerializeCompressed()) + scriptToUs.AddOp(txscript.OP_ENDIF) + scriptToUs.AddOp(txscript.OP_CHECKSIG) + + // TODO(roasbeef): store + ourRedeemScript, err := scriptToUs.Script() + if err != nil { + return nil, err + } + payToUsScriptHash, err := P2SHify(ourRedeemScript) + if err != nil { + return nil, err + } + + // Next, we create the script paying to them. This is just a regular + // P2PKH (wrapped in P2SH) with their key. + scriptToThem := txscript.NewScriptBuilder() + scriptToThem.AddOp(txscript.OP_DUP) + scriptToThem.AddOp(txscript.OP_HASH160) + scriptToThem.AddData(btcutil.Hash160(theirKey.SerializeCompressed())) + scriptToThem.AddOp(txscript.OP_EQUALVERIFY) + scriptToThem.AddOp(txscript.OP_CHECKSIG) + + theirRedeemScript, err := scriptToThem.Script() + if err != nil { + return nil, err + } + payToThemScriptHash, err := P2SHify(theirRedeemScript) + if err != nil { + return nil, err + } + + // TODO(roasbeef): sort outputs? + commitTx := wire.NewMsgTx() + commitTx.AddTxIn(fundingOutput) + commitTx.AddTxOut(wire.NewTxOut(int64(amtToUs), payToUsScriptHash)) + commitTx.AddTxOut(wire.NewTxOut(int64(amtToThem), payToThemScriptHash)) + + return commitTx, nil +} + +//TODO(j): Creates a CLTV-only funding Tx (reserve is *REQUIRED*) +//This works for only CLTV soft-fork (no CSV/segwit soft-fork in yet) +// +//Commit funds to Funding Tx, will timeout after the fundingTimeLock and refund +//back using CLTV. As there is no way to enforce HTLCs, we rely upon a reserve +//and have each party's HTLCs in-transit be less than their Commitment reserve. +//In the event that someone incorrectly broadcasts an old Commitment TX, then +//the counterparty claims the full reserve. It may be possible for either party +//to claim the HTLC(!!! But it's okay because the "honest" party is made whole +//via the reserve). If it's two-funder there are two outputs and the +//Commitments spends from both outputs in the Funding Tx. Two-funder requires +//the ourKey/theirKey sig positions to be swapped (should be in 1 funding tx). +// +//Quick note before I forget: The revocation hash is used in CLTV-only for +//single-funder (without an initial payment) *as part of an additional output +//in the Commitment Tx for the reserve*. This is to establish a unidirectional +//channel UNITL the recipient has sufficient funds. When the recipient has +//sufficient funds, the revocation is exchanged and allows the recipient to +//claim the full reserve as penalty if the incorrect Commitment is broadcast +//(otherwise it's timelocked refunded back to the sender). From then on, there +//is no additional output in Commitment Txes. [side caveat, first payment must +//be above minimum UTXO output size in single-funder] For now, let's keep it +//simple and assume dual funder (with both funding above reserve) +func createCLTVFundingTx(fundingTimeLock int64, ourKey *btcec.PublicKey, theirKey *btcec.PublicKey) (*wire.MsgTx, error) { + script := txscript.NewScriptBuilder() + //See how many entries there are + //2: it's a 2-of-2 multisig + //anything else: assume it's a CLTV-timeout 1-sig only + script.AddOp(txscript.OP_DEPTH) + script.AddInt64(2) + script.AddOp(txscript.OP_EQUAL) + + //If this is a 2-of-2 multisig, read the first sig + script.AddOp(txscript.OP_IF) + //Sig2 (not P2PKH, the pubkey is in the redeemScript) + script.AddData(ourKey.SerializeCompressed()) + script.AddOp(txscript.OP_CHECKSIGVERIFY) //gotta be verify! + + //If this is timed out + script.AddOp(txscript.OP_ELSE) + script.AddInt64(fundingTimeLock) + script.AddOp(txscript.OP_NOP2) //CLTV + //Sig (not P2PKH, the pubkey is in the redeemScript) + script.AddOp(txscript.OP_CHECKSIG) + script.AddOp(txscript.OP_DROP) + script.AddOp(txscript.OP_ENDIF) + + //Read the other sig if it's 2-of-2, only one if it's timed out + script.AddData(theirKey.SerializeCompressed()) + script.AddOp(txscript.OP_CHECKSIG) + + fundingTx := wire.NewMsgTx() + //TODO(j) Add the inputs/outputs + + return fundingTx, nil +}