package lnwallet import ( "bytes" "sync" "time" "li.lan/labs/plasma/chainntfs" "li.lan/labs/plasma/revocation" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/txsort" "github.com/btcsuite/btcwallet/walletdb" ) const ( // TODO(roasbeef): make not random value MaxPendingPayments = 10 ) type nodeId [32]byte // OpenChannelState... // TODO(roasbeef): script gen methods on this? type OpenChannelState struct { fundingType FundingType // 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 capacity btcutil.Amount ourBalance btcutil.Amount theirBalance btcutil.Amount theirCommitTx *wire.MsgTx ourCommitTx *wire.MsgTx theirCommitSig []byte fundingTx *wire.MsgTx multiSigKey *btcec.PrivateKey fundingRedeemScript []byte // Current revocation for their commitment transaction. However, since // this is the hash, and not the pre-image, we can't yet verify that // it's actually in the chain. theirCurrentRevocation [wire.HashSize]byte theirShaChain *revocation.HyperShaChain ourShaChain *revocation.HyperShaChain // Final delivery address ourDeliveryAddress btcutil.Address theirDeliveryAddress btcutil.Address // In blocks htlcTimeout uint32 csvDelay uint32 numUpdates uint64 totalSatoshisSent uint64 totalSatoshisReceived uint64 // TODO(roasbeef): track fees? creationTime time.Time } 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... // TODO(roasbeef): future peer struct should embed this struct type LightningChannel struct { wallet *LightningWallet channelEvents *chainntnfs.ChainNotifier // TODO(roasbeef): Stores all previous R values + timeouts for each // commitment update, plus some other meta-data...Or just use OP_RETURN // to help out? // currently going for: nSequence/nLockTime overloading channelNamespace walletdb.Namespace // stateMtx protects concurrent access to the state struct. stateMtx sync.RWMutex channelState OpenChannelState // TODO(roasbeef): create and embed 'Service' interface w/ below? started int32 shutdown int32 quit chan struct{} wg sync.WaitGroup } // newLightningChannel... func newLightningChannel(wallet *LightningWallet, events *chainntnfs.ChainNotifier, dbNamespace walletdb.Namespace, state OpenChannelState) (*LightningChannel, error) { return &LightningChannel{ wallet: wallet, channelEvents: events, channelNamespace: dbNamespace, channelState: state, }, nil } // AddHTLC... func (lc *LightningChannel) AddHTLC() { } // SettleHTLC... func (lc *LightningChannel) SettleHTLC() { } // OurBalance... func (lc *LightningChannel) OurBalance() btcutil.Amount { return 0 } // TheirBalance... func (lc *LightningChannel) TheirBalance() btcutil.Amount { return 0 } // CurrentCommitTx... func (lc *LightningChannel) CurrentCommitTx() *btcutil.Tx { return nil } // SignTheirCommitTx... func (lc *LightningChannel) SignTheirCommitTx(commitTx *btcutil.Tx) error { return nil } // AddTheirSig... func (lc *LightningChannel) AddTheirSig(sig []byte) error { return nil } // VerifyCommitmentUpdate... func (lc *LightningChannel) VerifyCommitmentUpdate() error { return nil } // createCommitTx... func createCommitTx(fundingOutput *wire.TxIn, ourKey, theirKey *btcec.PublicKey, revokeHash [wire.HashSize]byte, csvTimeout int64, channelAmt 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() // If the pre-image for the revocation hash is presented, then allow a // spend provided the proper signature. 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) // Otherwise, we can re-claim our funds after a CSV delay of // 'csvTimeout' timeout blocks, and a valid signature. 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 := scriptHashPkScript(ourRedeemScript) if err != nil { return nil, err } // Next, we create the script paying to them. This is just a regular // P2PKH-ike output. However, we instead use P2SH. 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 := scriptHashPkScript(theirRedeemScript) if err != nil { return nil, err } // Now that both output scripts have been created, we can finally create // the transaction itself. commitTx := wire.NewMsgTx() commitTx.AddTxIn(fundingOutput) commitTx.AddTxOut(wire.NewTxOut(int64(channelAmt), payToUsScriptHash)) commitTx.AddTxOut(wire.NewTxOut(int64(channelAmt), payToThemScriptHash)) // Sort the transaction according to the agreed upon cannonical // ordering. This lets us skip sending the entire transaction over, // instead we'll just send signatures. txsort.InPlaceSort(commitTx) 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 }