diff --git a/lnwire/single_funding_request.go b/lnwire/single_funding_request.go index a8f01b1e..8e8abc4a 100644 --- a/lnwire/single_funding_request.go +++ b/lnwire/single_funding_request.go @@ -1,71 +1,104 @@ package lnwire import ( + "bytes" "fmt" - "github.com/roasbeef/btcd/btcec" - "github.com/roasbeef/btcd/wire" - "github.com/roasbeef/btcutil" "io" + + "github.com/roasbeef/btcd/btcec" + "github.com/roasbeef/btcutil" ) +// SingleFundingRequest is the message Alice sends to Bob if we should like +// to create a channel with Bob where she's the sole provider of funds to the +// channel. Single funder channels simplify the initial funding workflow, are +// supported by nodes backed by SPV Bitcoin clients, and have a simpler +// security models than dual funded channels. +// +// NOTE: In order to avoid a slow loris like resource exhaustion attack, the +// responder of a single funding channel workflow *should not* watch the +// blockchain for the funding transaction. Instead, it is the initiator's job +// to provide the responder with an SPV proof of funding transaction inclusion +// after a sufficient number of confirmations. type SingleFundingRequest struct { - //ChannelID + // ChannelID serves to uniquely identify the future channel created by + // the initiated single funder workflow. ChannelID uint64 - //Default 0 + // ChannelType represents the type of channel this request would like + // to open. At this point, the only supported channels are type 0 + // channels, which are channels with regular commitment transactions + // utilizing HTLC's for payments. ChannelType uint8 - //Bitcoin: 0. + // CoinType represents which blockchain the channel will be opened + // using. By default, this field should be set to 0, indicating usage + // of the Bitcoin blockchain. CoinType uint64 - //Amount of fees per kb - //Assumes requester pays + // FeePerKb is the required number of satoshis per KB that the + // requester will pay at all timers, for both the funding transaction + // and commitment transaction. This value can later be updated once the + // channel is open. FeePerKb btcutil.Amount - // The funding requester can request payment - // This wallet only allows positive values, - // which is a payment to the responder - // (This can be used to fund the Reserve) - // If the responder disagrees, then the funding request fails - // THIS VALUE GOES INTO THE RESPONDER'S FUNDING AMOUNT - // total requester input value = RequesterFundingAmount + PaymentAmount + "Total Change" + Fees(?) - // RequesterFundingAmount = "Available Balance" + RequesterReserveAmount - // Payment SHOULD NOT be acknowledged until the minimum confirmation has elapsed - // (Due to double-spend risks the recipient will not want to acknolwedge confirmation until later) - // This is to make a payment as part of opening the channel - PaymentAmount btcutil.Amount + // FundingAmount is the number of satoshis the the initiator would like + // to commit to the channel. + FundingAmount btcutil.Amount - // CLTV/CSV lock-time to use - LockTime uint32 + // CsvDelay is the number of blocks to use for the relative time lock + // in the pay-to-self output of both commitment transactions. + // TODO(roasbeef): bool for seconds or blocks? + CsvDelay uint32 - RevocationHash [20]byte - Pubkey *btcec.PublicKey - DeliveryPkScript PkScript // *MUST* be either P2PKH or P2SH - ChangePkScript PkScript // *MUST* be either P2PKH or P2SH + // ChannelDerivationPoint is an secp256k1 point which will be used to + // derive the public key the initiator will use for the half of the + // 2-of-2 multi-sig. Using the channel derivation point (CDP), and the + // initiators identity public key (A), the channel public key is + // computed as: C = A + CDP. In order to be valid all CDP's MUST have + // an odd y-coordinate. + ChannelDerivationPoint *btcec.PublicKey + + // RevocationHash is the initial revocation hash to be used for the + // initiator's commitment transaction to derive their revocation public + // key as: P + G*revocationHash, where P is the initiator's channel + // public key. + RevocationHash [20]byte + + // DeliveryPkScript defines the public key script that the initiator + // would like to use to receive their balance in the case of a + // cooperative close. Only the following script templates are + // supported: P2PKH, P2WKH, P2SH, and P2WSH. + DeliveryPkScript PkScript + + // TODO(roasbeef): confirmation depth } +// Decode deserializes the serialized SingleFundingRequest stored in the passed +// io.Reader into the target SingleFundingRequest using the deserialization +// rules defined by the passed protocol version. +// +// This is part of the lnwire.Message interface. func (c *SingleFundingRequest) Decode(r io.Reader, pver uint32) error { // ChannelID (8) // ChannelType (1) // CoinType (8) // FeePerKb (8) - // PaymentAmount (8) - // LockTime (4) + // FundingAmount (8) + // CsvDelay (4) + // Channel Derivation Point (32) // Revocation Hash (20) - // Pubkey (32) // DeliveryPkScript (final delivery) - // ChangePkScript (change for extra from inputs) err := readElements(r, &c.ChannelID, &c.ChannelType, &c.CoinType, &c.FeePerKb, - &c.PaymentAmount, - &c.LockTime, + &c.FundingAmount, + &c.CsvDelay, + &c.ChannelDerivationPoint, &c.RevocationHash, - &c.Pubkey, - &c.DeliveryPkScript, - &c.ChangePkScript) + &c.DeliveryPkScript) if err != nil { return err } @@ -73,13 +106,16 @@ func (c *SingleFundingRequest) Decode(r io.Reader, pver uint32) error { return nil } -// Creates a new SingleFundingRequest +// NewSingleFundingRequest creates, and returns a new empty SingleFundingRequest. func NewSingleFundingRequest() *SingleFundingRequest { return &SingleFundingRequest{} } -// Serializes the item from the SingleFundingRequest struct -// Writes the data to w +// Encode serializes the target SingleFundingRequest into the passed io.Writer +// implementation. Serialization will observe the rules defined by the passed +// protocol version. +// +// This is part of the lnwire.Message interface. func (c *SingleFundingRequest) Encode(w io.Writer, pver uint32) error { // ChannelID (8) // ChannelType (1) @@ -90,18 +126,16 @@ func (c *SingleFundingRequest) Encode(w io.Writer, pver uint32) error { // Revocation Hash (20) // Pubkey (32) // DeliveryPkScript (final delivery) - // ChangePkScript (change for extra from inputs) err := writeElements(w, c.ChannelID, c.ChannelType, c.CoinType, c.FeePerKb, - c.PaymentAmount, - c.LockTime, + c.FundingAmount, + c.CsvDelay, + c.ChannelDerivationPoint, c.RevocationHash, - c.Pubkey, - c.DeliveryPkScript, - c.ChangePkScript) + c.DeliveryPkScript) if err != nil { return err } @@ -109,72 +143,80 @@ func (c *SingleFundingRequest) Encode(w io.Writer, pver uint32) error { return nil } +// Command returns the uint32 code which uniquely identifies this message as a +// SingleFundingRequest on the wire. +// +// This is part of the lnwire.Message interface. func (c *SingleFundingRequest) Command() uint32 { return CmdSingleFundingRequest } +// MaxPayloadLength returns the maximum allowed payload length for a +// SingleFundingRequest. This is calculated by summing the max length of all +// the fields within a SingleFundingRequest. To enforce a maximum +// DeliveryPkScript size, the size of a P2PKH public key script is used. +// Therefore, the final breakdown is: 8 + 1 + 8 + 8 + 8 + 4 + 32 + 20 + 25 = 114. +// +// This is part of the lnwire.Message interface. func (c *SingleFundingRequest) MaxPayloadLength(uint32) uint32 { - return 141 + return 114 } -// Makes sure the struct data is valid (e.g. no negatives or invalid pkscripts) +// Validate examines each populated field within the SingleFundingRequest for +// field sanity. For example, all fields MUST NOT be negative, and all pkScripts +// must belong to the allowed set of public key scripts. +// +// This is part of the lnwire.Message interface. func (c *SingleFundingRequest) Validate() error { - var err error - - // No negative values - if c.RequesterFundingAmount < 0 { - return fmt.Errorf("RequesterFundingAmount cannot be negative") - } - - if c.RequesterReserveAmount < 0 { - return fmt.Errorf("RequesterReserveAmount cannot be negative") - } - - if c.MinFeePerKb < 0 { + // Negative values is are allowed. + if c.FeePerKb < 0 { return fmt.Errorf("MinFeePerKb cannot be negative") } - if c.MinTotalFundingAmount < 0 { - return fmt.Errorf("MinTotalFundingAmount cannot be negative") + if c.FundingAmount < 0 { + return fmt.Errorf("FundingAmount cannot be negative") } - // Validation of what makes sense... - if c.MinTotalFundingAmount < c.RequesterFundingAmount { - return fmt.Errorf("Requester's minimum too low.") - } - if c.RequesterFundingAmount < c.RequesterReserveAmount { - return fmt.Errorf("Reserve must be below Funding Amount") + // The CSV delay MUST be non-zero. + if c.CsvDelay == 0 { + return fmt.Errorf("Commitment transaction must have non-zero " + + "CSV delay") } - // This wallet only allows payment from the requester to responder - if c.PaymentAmount < 0 { - return fmt.Errorf("This wallet requieres payment to be greater than zero.") + // The channel derivation point must be non-nil, and have an odd + // y-coordinate. + if c.ChannelDerivationPoint == nil { + return fmt.Errorf("The channel derivation point must be non-nil") + } + if c.ChannelDerivationPoint.Y.Bit(0) != 1 { + return fmt.Errorf("The channel derivation point must have an odd " + + "y-coordinate") } - // Make sure there's not more than 127 inputs - if len(c.Inputs) > 127 { - return fmt.Errorf("Too many inputs") + // The revocation hash MUST be non-zero. + var zeroHash [20]byte + if bytes.Equal(c.RevocationHash[:], zeroHash[:]) { + return fmt.Errorf("Initial revocation hash must be non-zero") } - // DeliveryPkScript is either P2SH or P2PKH - err = ValidatePkScript(c.DeliveryPkScript) - if err != nil { - return err - } - - // ChangePkScript is either P2SH or P2PKH - err = ValidatePkScript(c.ChangePkScript) - if err != nil { - return err + // The delivery pkScript must be amongst the supported script + // templates. + if !isValidPkScript(c.DeliveryPkScript) { + // TODO(roasbeef): move into actual error + return fmt.Errorf("Valid delivery public key scripts MUST be: " + + "P2PKH, P2WKH, P2SH, or P2WSH.") } // We're good! return nil } +// String returns the string representation of the SingleFundingRequest. +// +// This is part of the lnwire.Message interface. func (c *SingleFundingRequest) String() string { var serializedPubkey []byte - if &c.Pubkey != nil && c.Pubkey.X != nil { - serializedPubkey = c.Pubkey.SerializeCompressed() + if &c.ChannelDerivationPoint != nil && c.ChannelDerivationPoint.X != nil { + serializedPubkey = c.ChannelDerivationPoint.SerializeCompressed() } return fmt.Sprintf("\n--- Begin SingleFundingRequest ---\n") + @@ -182,11 +224,10 @@ func (c *SingleFundingRequest) String() string { fmt.Sprintf("ChannelType:\t\t\t%x\n", c.ChannelType) + fmt.Sprintf("CoinType:\t\t\t%d\n", c.CoinType) + fmt.Sprintf("FeePerKb:\t\t\t%s\n", c.FeePerKb.String()) + - fmt.Sprintf("PaymentAmount:\t\t\t%s\n", c.PaymentAmount.String()) + - fmt.Sprintf("LockTime\t\t\t%d\n", c.LockTime) + + fmt.Sprintf("FundingAmount:\t\t\t%s\n", c.FundingAmount.String()) + + fmt.Sprintf("CsvDelay\t\t\t%d\n", c.CsvDelay) + + fmt.Sprintf("ChannelDerivationPoint\t\t\t\t%x\n", serializedPubkey) + fmt.Sprintf("RevocationHash\t\t\t%x\n", c.RevocationHash) + - fmt.Sprintf("Pubkey\t\t\t\t%x\n", serializedPubkey) + fmt.Sprintf("DeliveryPkScript\t\t%x\n", c.DeliveryPkScript) + - fmt.Sprintf("ChangePkScript\t\t%x\n", c.ChangePkScript) + fmt.Sprintf("--- End SingleFundingRequest ---\n") }