diff --git a/lnwire/funding_request.go b/lnwire/funding_request.go new file mode 100644 index 00000000..aa8c5527 --- /dev/null +++ b/lnwire/funding_request.go @@ -0,0 +1,188 @@ +package lnwire + +import ( + "bytes" + "fmt" + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + "io" +) + +type FundingRequest struct { + ChannelType uint8 + + FundingAmount btcutil.Amount + ReserveAmount btcutil.Amount + MinFeePerKb btcutil.Amount + + //Should double-check the total funding later + MinTotalFundingAmount btcutil.Amount + + //CLTV lock-time to use + LockTime uint32 + + //Who pays the fees + //0: (default) channel initiator + //1: split + //2: channel responder + FeePayer uint8 + + RevocationHash [20]byte + Pubkey *btcec.PublicKey + DeliveryPkScript PkScript //*MUST* be either P2PKH or P2SH + + Inputs []*wire.TxIn +} + +func (c *FundingRequest) Decode(r io.Reader, pver uint32) error { + //Channel Type (0/1) + // default to 0 for CLTV-only + //Funding Amount (1/8) + //Channel Minimum Capacity (9/8) + //Revocation Hash (17/20) + //Commitment Pubkey (37/32) + //Reserve Amount (69/8) + //Minimum Transaction Fee Per Kb (77/8) + //LockTime (85/4) + //FeePayer (89/1) + //DeliveryPkScript + // First byte length then pkscript + //Inputs: Create the TxIns + // First byte is number of inputs + // For each input, it's 32bytes txin & 4bytes index + err := readElements(r, false, + &c.ChannelType, + &c.FundingAmount, + &c.MinTotalFundingAmount, + &c.RevocationHash, + &c.Pubkey, + &c.ReserveAmount, + &c.MinFeePerKb, + &c.LockTime, + &c.FeePayer, + &c.DeliveryPkScript, + &c.Inputs) + if err != nil { + return err + } + + return c.Validate() +} + +//Creates a new FundingRequest +func NewFundingRequest() *FundingRequest { + return &FundingRequest{} +} + +//Serializes the item from the FundingRequest struct +//Writes the data to w +func (c *FundingRequest) Encode(w io.Writer, pver uint32) error { + //Channel Type + //Funding Amont + //Channel Minimum Capacity + //Revocation Hash + //Commitment Pubkey + //Reserve Amount + //Minimum Transaction Fee Per KB + //LockTime + //FeePayer + //DeliveryPkScript + //Inputs: Append the actual Txins + err := writeElements(w, false, + c.ChannelType, + c.FundingAmount, + c.MinTotalFundingAmount, + c.RevocationHash, + c.Pubkey, + c.ReserveAmount, + c.MinFeePerKb, + c.LockTime, + c.FeePayer, + c.DeliveryPkScript, + c.Inputs) + if err != nil { + return err + } + + return nil +} + +func (c *FundingRequest) Command() uint32 { + return CmdFundingRequest +} + +func (c *FundingRequest) MaxPayloadLength(uint32) uint32 { + //90 (base size) + 26 (pkscript) + 1 (numTxes) + 127*36(127 inputs * sha256+idx) + //4690 + return 4689 +} + +//Makes sure the struct data is valid (e.g. no negatives or invalid pkscripts) +func (c *FundingRequest) Validate() error { + //No negative values + if c.FundingAmount < 0 { + return fmt.Errorf("FundingAmount cannot be negative") + } + + if c.ReserveAmount < 0 { + return fmt.Errorf("ReserveAmount cannot be negative") + } + + if c.MinFeePerKb < 0 { + return fmt.Errorf("MinFeePerKb cannot be negative") + } + if c.MinTotalFundingAmount < 0 { + return fmt.Errorf("MinTotalFundingAmount cannot be negative") + } + + //PkScript is either P2SH or P2PKH + //P2PKH + if len(c.DeliveryPkScript) == 25 { + //Begins with OP_DUP OP_HASH160 PUSHDATA(20) + if !(bytes.Equal(c.DeliveryPkScript[0:3], []byte{118, 169, 20}) && + //Ends with OP_EQUALVERIFY OP_CHECKSIG + bytes.Equal(c.DeliveryPkScript[23:25], []byte{136, 172})) { + //If it's not correct, return error + return fmt.Errorf("PkScript only allows P2SH or P2PKH") + } + //P2SH + } else if len(c.DeliveryPkScript) == 23 { + //Begins with OP_HASH160 PUSHDATA(20) + if !(bytes.Equal(c.DeliveryPkScript[0:2], []byte{169, 20}) && + //Ends with OP_EQUAL + bytes.Equal(c.DeliveryPkScript[22:23], []byte{135})) { + //If it's not correct, return error + return fmt.Errorf("PkScript only allows P2SH or P2PKH") + } + } else { + //Length not 23 or 25 + return fmt.Errorf("PkScript only allows P2SH or P2PKH") + } + + //We're good! + return nil +} + +func (c *FundingRequest) String() string { + var inputs string + for i, in := range c.Inputs { + inputs += fmt.Sprintf("\n Slice\t%d\n", i) + inputs += fmt.Sprintf("\tHash\t%s\n", in.PreviousOutPoint.Hash) + inputs += fmt.Sprintf("\tIndex\t%d\n", in.PreviousOutPoint.Index) + } + return fmt.Sprintf("\n--- Begin FundingRequest ---\n") + + fmt.Sprintf("ChannelType:\t\t%x\n", c.ChannelType) + + fmt.Sprintf("FundingAmount:\t\t%s\n", c.FundingAmount.String()) + + fmt.Sprintf("ReserveAmount:\t\t%s\n", c.ReserveAmount.String()) + + fmt.Sprintf("MinFeePerKb:\t\t%s\n", c.MinFeePerKb.String()) + + fmt.Sprintf("MinTotalFundingAmount\t%s\n", c.MinTotalFundingAmount.String()) + + fmt.Sprintf("LockTime\t\t%d\n", c.LockTime) + + fmt.Sprintf("FeePayer\t\t%x\n", c.FeePayer) + + fmt.Sprintf("RevocationHash\t\t%x\n", c.RevocationHash) + + fmt.Sprintf("Pubkey\t\t\t%x\n", c.Pubkey.SerializeCompressed()) + + fmt.Sprintf("DeliveryPkScript\t%x\n", c.DeliveryPkScript) + + fmt.Sprintf("Inputs:") + + inputs + + fmt.Sprintf("--- End FundingRequest ---\n") +} diff --git a/lnwire/funding_request_test.go b/lnwire/funding_request_test.go new file mode 100644 index 00000000..6292bafa --- /dev/null +++ b/lnwire/funding_request_test.go @@ -0,0 +1,127 @@ +package lnwire + +import ( + "bytes" + "encoding/hex" + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + // "io" + "io/ioutil" + "reflect" + "testing" +) + +var ( + //For debugging, writes to /dev/shm/ + //Maybe in the future do it if you do "go test -v" + WRITE_FILE = false + FILENAME = "/dev/shm/fundingRequest.raw" + + //preimage: 9a2cbd088763db88dd8ba79e5726daa6aba4aa7e + //echo -n | openssl sha256 | openssl ripemd160 | openssl sha256 | openssl ripemd160 + revocationHashBytes, _ = hex.DecodeString("4132b6b48371f7b022a16eacb9b2b0ebee134d41") + revocationHash [20]byte + _ = copy(revocationHash[:], revocationHashBytes) + + //privkey: 9fa1d55217f57019a3c37f49465896b15836f54cb8ef6963870a52926420a2dd + pubKeyBytes, _ = hex.DecodeString("02f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee") + pubKey, _ = btcec.ParsePubKey(pubKeyBytes, btcec.S256()) + + // Delivery PkScript + //Privkey: f2c00ead9cbcfec63098dc0a5f152c0165aff40a2ab92feb4e24869a284c32a7 + //PKhash: n2fkWVphUzw3zSigzPsv9GuDyg9mohzKpz + deliveryPkScript, _ = hex.DecodeString("76a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac") + + //echo -n | openssl sha256 + //This stuff gets reversed!!! + shaHash1Bytes, _ = hex.DecodeString("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") + shaHash1, _ = wire.NewShaHash(shaHash1Bytes) + outpoint1 = wire.NewOutPoint(shaHash1, 0) + //echo | openssl sha256 + //This stuff gets reversed!!! + shaHash2Bytes, _ = hex.DecodeString("01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b") + shaHash2, _ = wire.NewShaHash(shaHash2Bytes) + outpoint2 = wire.NewOutPoint(shaHash2, 1) + //create inputs from outpoint1 and outpoint2 + inputs = []*wire.TxIn{wire.NewTxIn(outpoint1, nil), wire.NewTxIn(outpoint2, nil)} + + //funding request + fundingRequest = &FundingRequest{ + ChannelType: uint8(0), + FundingAmount: btcutil.Amount(100000000), + ReserveAmount: btcutil.Amount(131072), + MinFeePerKb: btcutil.Amount(20000), + MinTotalFundingAmount: btcutil.Amount(150000000), + LockTime: uint32(4320), //30 block-days + FeePayer: uint8(0), + RevocationHash: revocationHash, + Pubkey: pubKey, + DeliveryPkScript: deliveryPkScript, + Inputs: inputs, + } + serializedString = "000000000005f5e1000000000008f0d1804132b6b48371f7b022a16eacb9b2b0ebee134d4102f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee00000000000200000000000000004e20000010e0001976a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac02e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8550000000001ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b00000001" + serializedMessage = "0709110b00000014000000be000000000005f5e1000000000008f0d1804132b6b48371f7b022a16eacb9b2b0ebee134d4102f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee00000000000200000000000000004e20000010e0001976a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac02e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8550000000001ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b00000001" +) + +func TestFundingRequestEncodeDecode(t *testing.T) { + //Test serialization + b := new(bytes.Buffer) + err := fundingRequest.Encode(b, 0) + if err != nil { + t.Error("Serialization error") + t.Error(err.Error()) + } + t.Logf("Encoded Funding Request: %x\n", b.Bytes()) + //Check if we serialized correctly + if serializedString != hex.EncodeToString(b.Bytes()) { + t.Error("Serialization does not match expected") + } + + //So I can do: hexdump -C /dev/shm/fundingRequest.raw + if WRITE_FILE { + err = ioutil.WriteFile(FILENAME, b.Bytes(), 0644) + if err != nil { + t.Error("File write error") + t.Error(err.Error()) + } + } + + //Test deserialization + //Make a new buffer just to be clean + c := new(bytes.Buffer) + c.Write(b.Bytes()) + + newFunding := NewFundingRequest() + err = newFunding.Decode(c, 0) + if err != nil { + t.Error("Decoding Error") + t.Error(err.Error()) + } + + if !reflect.DeepEqual(newFunding, fundingRequest) { + t.Error("Decoding does not match!") + } + //Show the struct + t.Log(newFunding.String()) + + //Test message using Message interface + //Serialize/Encode + b = new(bytes.Buffer) + _, err = WriteMessage(b, fundingRequest, uint32(1), wire.TestNet3) + t.Logf("%x\n", b.Bytes()) + if hex.EncodeToString(b.Bytes()) != serializedMessage { + t.Error("Message encoding error") + } + //Deserialize/Decode + c = new(bytes.Buffer) + c.Write(b.Bytes()) + _, msg, _, err := ReadMessage(c, uint32(1), wire.TestNet3) + if err != nil { + t.Errorf(err.Error()) + } + if !reflect.DeepEqual(msg, fundingRequest) { + t.Error("Message decoding does not match!") + } + t.Logf(msg.String()) +} diff --git a/lnwire/lnwire.go b/lnwire/lnwire.go index 3c58a155..5f9273e4 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -16,61 +16,11 @@ type PkScript []byte //Subsatoshi amount type MicroSatoshi int32 -type CreateChannel struct { - ChannelType uint8 - - FundingAmount btcutil.Amount - ReserveAmount btcutil.Amount - MinFeePerKb btcutil.Amount - - //Should double-check the total funding later - MinTotalFundingAmount btcutil.Amount - - //CLTV lock-time to use - LockTime uint32 - - //Who pays the fees - //0: (default) channel initiator - //1: split - //2: channel responder - FeePayer uint8 - - RevocationHash [20]byte - Pubkey *btcec.PublicKey - DeliveryPkScript PkScript //*MUST* be either P2PKH or P2SH - - Inputs []*wire.TxIn -} - -func (c *CreateChannel) String() string { - - var inputs string - for i, in := range c.Inputs { - inputs += fmt.Sprintf("\n Slice\t%d\n", i) - inputs += fmt.Sprintf("\tHash\t%s\n", in.PreviousOutPoint.Hash) - inputs += fmt.Sprintf("\tIndex\t%d\n", in.PreviousOutPoint.Index) - } - return fmt.Sprintf("\n--- Begin CreateChannel ---\n") + - fmt.Sprintf("ChannelType:\t\t%x\n", c.ChannelType) + - fmt.Sprintf("FundingAmount:\t\t%s\n", c.FundingAmount.String()) + - fmt.Sprintf("ReserveAmount:\t\t%s\n", c.ReserveAmount.String()) + - fmt.Sprintf("MinFeePerKb:\t\t%s\n", c.MinFeePerKb.String()) + - fmt.Sprintf("MinTotalFundingAmount\t%s\n", c.MinTotalFundingAmount.String()) + - fmt.Sprintf("LockTime\t\t%d\n", c.LockTime) + - fmt.Sprintf("FeePayer\t\t%x\n", c.FeePayer) + - fmt.Sprintf("RevocationHash\t\t%x\n", c.RevocationHash) + - fmt.Sprintf("Pubkey\t\t\t%x\n", c.Pubkey.SerializeCompressed()) + - fmt.Sprintf("DeliveryPkScript\t%x\n", c.DeliveryPkScript) + - fmt.Sprintf("Inputs:") + - inputs + - fmt.Sprintf("--- End CreateChannel ---\n") -} - //Writes the big endian representation of element //Unified function to call when writing different types //Pre-allocate a byte-array of the correct size for cargo-cult security //More copies but whatever... -func writeElement(w io.Writer, element interface{}) error { +func writeElement(w io.Writer, includeSig bool, element interface{}) error { var err error switch e := element.(type) { case uint8: @@ -80,6 +30,7 @@ func writeElement(w io.Writer, element interface{}) error { if err != nil { return err } + return nil case uint32: var b [4]byte binary.BigEndian.PutUint32(b[:], uint32(e)) @@ -87,11 +38,13 @@ func writeElement(w io.Writer, element interface{}) error { if err != nil { return err } + return nil case btcutil.Amount: err = binary.Write(w, binary.BigEndian, int64(e)) if err != nil { return err } + return nil case *btcec.PublicKey: var b [33]byte serializedPubkey := e.SerializeCompressed() @@ -103,11 +56,21 @@ func writeElement(w io.Writer, element interface{}) error { if err != nil { return err } + return nil case [20]byte: _, err = w.Write(e[:]) if err != nil { return err } + return nil + case wire.BitcoinNet: + var b [4]byte + binary.BigEndian.PutUint32(b[:], uint32(e)) + _, err := w.Write(b[:]) + if err != nil { + return err + } + return nil case PkScript: scriptLength := len(e) //Make sure it's P2PKH or P2SH size or less @@ -124,6 +87,7 @@ func writeElement(w io.Writer, element interface{}) error { if err != nil { return err } + return nil case []*wire.TxIn: //Append the unsigned(!!!) txins //Write the size (1-byte) @@ -151,7 +115,18 @@ func writeElement(w io.Writer, element interface{}) error { if err != nil { return err } + //Signature(optional) + if includeSig { + var sig [33]byte + copy(sig[:], in.SignatureScript) + _, err = w.Write(sig[:]) + if err != nil { + return err + } + + } } + return nil default: return fmt.Errorf("Unknown type in writeElement: %T", e) } @@ -159,7 +134,17 @@ func writeElement(w io.Writer, element interface{}) error { return nil } -func readElement(r io.Reader, element interface{}) error { +func writeElements(w io.Writer, includeSig bool, elements ...interface{}) error { + for _, element := range elements { + err := writeElement(w, includeSig, element) + if err != nil { + return err + } + } + return nil +} + +func readElement(r io.Reader, includeSig bool, element interface{}) error { var err error switch e := element.(type) { case *uint8: @@ -169,6 +154,7 @@ func readElement(r io.Reader, element interface{}) error { return err } *e = b[0] + return nil case *uint32: var b [4]byte _, err = io.ReadFull(r, b[:]) @@ -176,6 +162,7 @@ func readElement(r io.Reader, element interface{}) error { return err } *e = binary.BigEndian.Uint32(b[:]) + return nil case *btcutil.Amount: var b [8]byte _, err = io.ReadFull(r, b[:]) @@ -183,6 +170,7 @@ func readElement(r io.Reader, element interface{}) error { return err } *e = btcutil.Amount(int64(binary.BigEndian.Uint64(b[:]))) + return nil case **btcec.PublicKey: var b [33]byte _, err = io.ReadFull(r, b[:]) @@ -194,11 +182,21 @@ func readElement(r io.Reader, element interface{}) error { if err != nil { return err } + return nil case *[20]byte: _, err = io.ReadFull(r, e[:]) if err != nil { return err } + return nil + case *wire.BitcoinNet: + var b [4]byte + _, err := io.ReadFull(r, b[:]) + if err != nil { + return err + } + *e = wire.BitcoinNet(binary.BigEndian.Uint32(b[:])) + return nil case *PkScript: //Get the script length first var scriptLength [1]uint8 @@ -217,6 +215,7 @@ func readElement(r io.Reader, element interface{}) error { if err != nil { return err } + return nil case *[]*wire.TxIn: //Read the size (1-byte number of txins) var numScripts [1]uint8 @@ -249,12 +248,23 @@ func readElement(r io.Reader, element interface{}) error { } index := binary.BigEndian.Uint32(idxBytes[:]) outPoint := wire.NewOutPoint(shaHash, index) + //Signature(optional) + if includeSig { + var sig [33]byte + _, err = io.ReadFull(r, sig[:]) + if err != nil { + return err + } + //Create TxIn + txins = append(txins, wire.NewTxIn(outPoint, sig[:])) + } else { //no signature + //Create TxIn + txins = append(txins, wire.NewTxIn(outPoint, nil)) + } - //Create TxIn - txins = append(txins, wire.NewTxIn(outPoint, nil)) } *e = *&txins - + return nil default: return fmt.Errorf("Unknown type in readElement: %T", e) } @@ -262,168 +272,12 @@ func readElement(r io.Reader, element interface{}) error { return nil } -func (c *CreateChannel) DeserializeFundingRequest(r io.Reader) error { - var err error - - //Message Type (0/1) - var messageType uint8 - err = readElement(r, &messageType) - if err != nil { - return err +func readElements(r io.Reader, includeSig bool, elements ...interface{}) error { + for _, element := range elements { + err := readElement(r, includeSig, element) + if err != nil { + return err + } } - if messageType != 0x30 { - return fmt.Errorf("Message type does not match FundingRequest") - } - - //Channel Type (1/1) - err = readElement(r, &c.ChannelType) - if err != nil { - return err - } - - // Funding Amount (2/8) - err = readElement(r, &c.FundingAmount) - if err != nil { - return err - } - - // Channel Minimum Capacity (10/8) - err = readElement(r, &c.MinTotalFundingAmount) - if err != nil { - return err - } - - // Revocation Hash (18/20) - err = readElement(r, &c.RevocationHash) - if err != nil { - return err - } - - // Commitment Pubkey (38/32) - err = readElement(r, &c.Pubkey) - if err != nil { - return err - } - - // Reserve Amount (70/8) - err = readElement(r, &c.ReserveAmount) - if err != nil { - return err - } - - //Minimum Transaction Fee Per Kb (78/8) - err = readElement(r, &c.MinFeePerKb) - if err != nil { - return err - } - - //LockTime (86/4) - err = readElement(r, &c.LockTime) - if err != nil { - return err - } - - //FeePayer (90/1) - err = readElement(r, &c.FeePayer) - if err != nil { - return err - } - - // Delivery PkScript - err = readElement(r, &c.DeliveryPkScript) - if err != nil { - return err - } - - //Create the TxIns - err = readElement(r, &c.Inputs) - if err != nil { - return err - } - - return nil -} - -//Serializes the fundingRequest from the CreateChannel struct -//Writes the data to w -func (c *CreateChannel) SerializeFundingRequest(w io.Writer) error { - var err error - - //Fund request byte - err = writeElement(w, uint8(0x30)) - if err != nil { - return err - } - - //Channel Type - //default to 0 for CLTV-only - err = writeElement(w, c.ChannelType) - if err != nil { - return err - } - - //Funding Amont - err = writeElement(w, c.FundingAmount) - if err != nil { - return err - } - - // Channel Minimum Capacity - err = writeElement(w, c.MinTotalFundingAmount) - if err != nil { - return err - } - - // Revocation Hash - err = writeElement(w, c.RevocationHash) - if err != nil { - return err - } - - // Commitment Pubkey - err = writeElement(w, c.Pubkey) - if err != nil { - return err - } - - // Reserve Amount - err = writeElement(w, c.ReserveAmount) - if err != nil { - return err - } - - //Minimum Transaction Fee Per KB - err = writeElement(w, c.MinFeePerKb) - if err != nil { - return err - } - - //LockTime - err = writeElement(w, c.LockTime) - if err != nil { - return err - } - - //FeePayer - err = writeElement(w, c.FeePayer) - if err != nil { - return err - } - - // Delivery PkScript - //First byte length then pkscript - err = writeElement(w, c.DeliveryPkScript) - if err != nil { - return err - } - - //Append the actual Txins - //First byte is number of inputs - //For each input, it's 32bytes txin & 4bytes index - err = writeElement(w, c.Inputs) - if err != nil { - return err - } - return nil } diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go deleted file mode 100644 index eff697a6..00000000 --- a/lnwire/lnwire_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package lnwire - -import ( - "bytes" - "encoding/hex" - "fmt" - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - // "io" - "io/ioutil" - "reflect" - "testing" -) - -var ( - //For debugging, writes to /dev/shm/ - //Maybe in the future do it if you do "go test -v" - WRITE_FILE = false - FILENAME = "/dev/shm/fundingRequest.raw" - - //echo -n | openssl sha256 | openssl ripemd160 - ourRevocationHashBytes, _ = hex.DecodeString("9a2cbd088763db88dd8ba79e5726daa6aba4aa7e") - ourRevocationHash [20]byte - _ = copy(ourRevocationHash[:], ourRevocationHashBytes) - - //privkey: 9fa1d55217f57019a3c37f49465896b15836f54cb8ef6963870a52926420a2dd - ourPubKeyBytes, _ = hex.DecodeString("02f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee") - ourPubKey, _ = btcec.ParsePubKey(ourPubKeyBytes, btcec.S256()) - - // Delivery PkScript - //Privkey: f2c00ead9cbcfec63098dc0a5f152c0165aff40a2ab92feb4e24869a284c32a7 - //PKhash: n2fkWVphUzw3zSigzPsv9GuDyg9mohzKpz - ourDeliveryPkScript, _ = hex.DecodeString("76a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac") - - //echo -n | openssl sha256 - //This stuff gets reversed!!! - shaHash1Bytes, _ = hex.DecodeString("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") - shaHash1, _ = wire.NewShaHash(shaHash1Bytes) - outpoint1 = wire.NewOutPoint(shaHash1, 0) - //echo | openssl sha256 - //This stuff gets reversed!!! - shaHash2Bytes, _ = hex.DecodeString("01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b") - shaHash2, _ = wire.NewShaHash(shaHash2Bytes) - outpoint2 = wire.NewOutPoint(shaHash2, 1) - //create inputs from outpoint1 and outpoint2 - ourInputs = []*wire.TxIn{wire.NewTxIn(outpoint1, nil), wire.NewTxIn(outpoint2, nil)} - - //funding request - createChannel = CreateChannel{ - ChannelType: uint8(0), - FundingAmount: btcutil.Amount(100000000), - ReserveAmount: btcutil.Amount(131072), - MinFeePerKb: btcutil.Amount(20000), - MinTotalFundingAmount: btcutil.Amount(150000000), - LockTime: uint32(4320), //30 block-days - FeePayer: uint8(0), - RevocationHash: ourRevocationHash, - Pubkey: ourPubKey, - DeliveryPkScript: ourDeliveryPkScript, - Inputs: ourInputs, - } - serializedString = "30000000000005f5e1000000000008f0d1809a2cbd088763db88dd8ba79e5726daa6aba4aa7e02f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee00000000000200000000000000004e20000010e0001976a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac02e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8550000000001ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b00000001" -) - -func TestFundingRequestSerializeDeserialize(t *testing.T) { - b := new(bytes.Buffer) - err := createChannel.SerializeFundingRequest(b) - if err != nil { - fmt.Println(err) - t.Error("Serialization error") - } - t.Logf("Serialized Funding Request: %x\n", b.Bytes()) - - //Check if we serialized correctly - if serializedString != hex.EncodeToString(b.Bytes()) { - t.Error("Serialization does not match expected") - } - - //So I can do: hexdump -C /dev/shm/fundingRequest.raw - if WRITE_FILE { - err = ioutil.WriteFile(FILENAME, b.Bytes(), 0644) - if err != nil { - t.Error("File write error") - } - } - - //Test deserialization - //Make a new buffer just to be clean - c := new(bytes.Buffer) - c.Write(b.Bytes()) - - var newChannel CreateChannel - err = newChannel.DeserializeFundingRequest(c) - if err != nil { - fmt.Println(err) - t.Error("Deserialiation Error") - } - - if !reflect.DeepEqual(newChannel, createChannel) { - t.Error("Deserialization does not match!") - } - - t.Log(newChannel.String()) -} diff --git a/lnwire/message.go b/lnwire/message.go new file mode 100644 index 00000000..e87dc04e --- /dev/null +++ b/lnwire/message.go @@ -0,0 +1,209 @@ +//Code derived from https://github.com/btcsuite/btcd/blob/master/wire/message.go +package lnwire + +import ( + "bytes" + "fmt" + "github.com/btcsuite/btcd/wire" + "io" +) + +//4-byte network + 4-byte message id + payload-length 4-byte +//Maybe add checksum or something? +const MessageHeaderSize = 12 + +const MaxMessagePayload = 1024 * 1024 * 32 // 32MB + +const ( + CmdFundingRequest = uint32(20) +) + +//Every message has these functions: +type Message interface { + Decode(io.Reader, uint32) error //(io, protocol version) + Encode(io.Writer, uint32) error //(io, protocol version) + Command() uint32 //returns ID of the message + MaxPayloadLength(uint32) uint32 //(version) maxpayloadsize + Validate() error //Validates the data struct + String() string +} + +func makeEmptyMessage(command uint32) (Message, error) { + var msg Message + + switch command { + case CmdFundingRequest: + msg = &FundingRequest{} + default: + return nil, fmt.Errorf("unhandled command [%x]", command) + } + + return msg, nil +} + +type messageHeader struct { + //NOTE(j): We don't need to worry about the magic overlapping with + //bitcoin since this is inside encrypted comms anyway, but maybe we + //should use the XOR (^wire.TestNet3) just in case??? + magic wire.BitcoinNet + command uint32 + length uint32 + //Do we need a checksum here? +} + +func readMessageHeader(r io.Reader) (int, *messageHeader, error) { + var headerBytes [MessageHeaderSize]byte + n, err := io.ReadFull(r, headerBytes[:]) + if err != nil { + return n, nil, err + } + hr := bytes.NewReader(headerBytes[:]) + + hdr := messageHeader{} + + err = readElements(hr, false, + &hdr.magic, + &hdr.command, + &hdr.length) + if err != nil { + return n, nil, err + } + + return n, &hdr, nil +} + +// discardInput reads n bytes from reader r in chunks and discards the read +// bytes. This is used to skip payloads when various errors occur and helps +// prevent rogue nodes from causing massive memory allocation through forging +// header length. +func discardInput(r io.Reader, n uint32) { + maxSize := uint32(10 * 1024) //10k at a time + numReads := n / maxSize + bytesRemaining := n % maxSize + if n > 0 { + buf := make([]byte, maxSize) + for i := uint32(0); i < numReads; i++ { + io.ReadFull(r, buf) + } + } + if bytesRemaining > 0 { + buf := make([]byte, bytesRemaining) + io.ReadFull(r, buf) + } +} + +func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet wire.BitcoinNet) (int, error) { + totalBytes := 0 + + cmd := msg.Command() + + //Encode the message payload + var bw bytes.Buffer + err := msg.Encode(&bw, pver) + if err != nil { + return totalBytes, err + } + payload := bw.Bytes() + lenp := len(payload) + + //Enforce maximum overall message payload + if lenp > MaxMessagePayload { + return totalBytes, fmt.Errorf("message payload is too large - encoded %d bytes, but maximum message payload is %d bytes", lenp, MaxMessagePayload) + } + + //Enforce maximum message payload on the message type + mpl := msg.MaxPayloadLength(pver) + if uint32(lenp) > mpl { + return totalBytes, fmt.Errorf("message payload is too large - encoded %d bytes, but maximum message payload of type %x is %d bytes", lenp, cmd, mpl) + } + + //Create header for the message + hdr := messageHeader{} + hdr.magic = btcnet + hdr.command = cmd + hdr.length = uint32(lenp) + //Checksum goes here if needed... and also add to writeElements + + // Encode the header for the message. This is done to a buffer + // rather than directly to the writer since writeElements doesn't + // return the number of bytes written. + hw := bytes.NewBuffer(make([]byte, 0, MessageHeaderSize)) + writeElements(hw, false, hdr.magic, hdr.command, hdr.length) + + //Write header + n, err := w.Write(hw.Bytes()) + totalBytes += n + if err != nil { + return totalBytes, err + } + + //Write payload + n, err = w.Write(payload) + totalBytes += n + if err != nil { + return totalBytes, err + } + + return totalBytes, nil +} + +func ReadMessage(r io.Reader, pver uint32, btcnet wire.BitcoinNet) (int, Message, []byte, error) { + totalBytes := 0 + n, hdr, err := readMessageHeader(r) + totalBytes += n + if err != nil { + return totalBytes, nil, nil, err + } + + //Enforce maximum message payload + if hdr.length > MaxMessagePayload { + return totalBytes, nil, nil, fmt.Errorf("message payload is too large - header indicates %d bytes, but max message payload is %d bytes.", hdr.length, MaxMessagePayload) + } + + //Check for messages in the wrong bitcoin network + if hdr.magic != btcnet { + discardInput(r, hdr.length) + return totalBytes, nil, nil, fmt.Errorf("message from other network [%v]", hdr.magic) + } + + //Create struct of appropriate message type based on the command + command := hdr.command + msg, err := makeEmptyMessage(command) + if err != nil { + discardInput(r, hdr.length) + return totalBytes, nil, nil, fmt.Errorf("ReadMessage %s", err.Error()) + } + + //Check for maximum length based on the message type + mpl := msg.MaxPayloadLength(pver) + if hdr.length > mpl { + discardInput(r, hdr.length) + return totalBytes, nil, nil, fmt.Errorf("payload exceeds max length. indicates %v bytes, but max of message type %v is %v.", hdr.length, command, mpl) + } + + //Read payload + payload := make([]byte, hdr.length) + n, err = io.ReadFull(r, payload) + totalBytes += n + if err != nil { + return totalBytes, nil, nil, err + } + + //If we want to use checksums, test it here + + //Unmarshal message + pr := bytes.NewBuffer(payload) + err = msg.Decode(pr, pver) + if err != nil { + return totalBytes, nil, nil, err + } + + //Validate the data + msg.Validate() + if err != nil { + return totalBytes, nil, nil, err + } + + //We're good! + return totalBytes, msg, payload, nil +}