diff --git a/lnwire/lnwire.go b/lnwire/lnwire.go index 5d941598..388ed62d 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -1,6 +1,7 @@ package lnwire import ( + "bytes" "encoding/binary" "fmt" "github.com/btcsuite/btcd/btcec" @@ -12,11 +13,21 @@ import ( //Actual pkScript, not redeemScript type PkScript []byte +//Subsatoshi amount +type MicroSatoshi int32 + type CreateChannel struct { - ChannelType uint8 - OurFundingAmount btcutil.Amount - OurReserveAmount btcutil.Amount - MinFeePerKb btcutil.Amount //higher of both parties + ChannelType uint8 + + OurFundingAmount btcutil.Amount + TheirFundingAmount btcutil.Amount + OurReserveAmount btcutil.Amount + TheirReserveAmount btcutil.Amount + OurMinFeePerKb btcutil.Amount + TheirMinFeePerKb btcutil.Amount + + //Either party can change. + //Should double-check the total funding later MinTotalFundingAmount btcutil.Amount //CLTV lock-time to use @@ -28,11 +39,12 @@ type CreateChannel struct { //2: channel responder FeePayer uint8 - OurRevocationHash [20]byte - TheirRevocationHash [20]byte - OurPubkey *btcec.PublicKey - TheirPubkey *btcec.PublicKey - DeliveryPkScript PkScript //*MUST* be either P2PKH or P2SH + OurRevocationHash [20]byte + TheirRevocationHash [20]byte + OurPubkey *btcec.PublicKey + TheirPubkey *btcec.PublicKey + OurDeliveryPkScript PkScript //*MUST* be either P2PKH or P2SH + TheirDeliveryPkScript PkScript //*MUST* be either P2PKH or P2SH OurInputs []*wire.TxIn TheirInputs []*wire.TxIn @@ -42,7 +54,7 @@ type CreateChannel struct { //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 *bytes.Buffer, element interface{}) error { var err error switch e := element.(type) { case uint8: @@ -136,6 +148,11 @@ func writeElement(w io.Writer, element interface{}) error { func readElement(r io.Reader, element interface{}) error { var err error switch e := element.(type) { + case *uint8: + err = binary.Read(r, binary.BigEndian, *e) + if err != nil { + return err + } case *uint32: var b [4]byte _, err = io.ReadFull(r, b[:]) @@ -143,6 +160,173 @@ func readElement(r io.Reader, element interface{}) error { return err } *e = binary.BigEndian.Uint32(b[:]) + case *btcutil.Amount: + var b [8]byte + _, err = io.ReadFull(r, b[:]) + if err != nil { + return err + } + *e = btcutil.Amount(binary.BigEndian.Uint64(b[:])) + case *btcec.PublicKey: + var b [33]byte + _, err = io.ReadFull(r, b[:]) + if err != nil { + return err + } + e, err = btcec.ParsePubKey(b[:], btcec.S256()) + if err != nil { + return err + } + case *[20]byte: + _, err = io.ReadFull(r, e[:]) + if err != nil { + return err + } + case *PkScript: + //Get the script length first + var scriptLength uint8 + err = binary.Read(r, binary.BigEndian, scriptLength) + if err != nil { + return err + } + if scriptLength > 25 { + return fmt.Errorf("PkScript too long!") + } + + //Read the script length + l := io.LimitReader(r, int64(scriptLength)) + if err != nil { + return err + } + l.Read(*e) + case []*wire.TxIn: + //Read the size (1-byte number of txins) + var numScripts uint8 + err = binary.Read(r, binary.BigEndian, numScripts) + if err != nil { + return err + } + if numScripts > 127 { + return fmt.Errorf("Too many txins") + } + + //Append the actual TxIns + var txins []*wire.TxIn + for i := uint8(0); i < numScripts; i++ { + //Hash + var h [32]byte + _, err = io.ReadFull(r, h[:]) + if err != nil { + return err + } + shaHash, err := wire.NewShaHash(h[:]) + if err != nil { + return err + } + //Index + var idxBytes [4]byte + _, err = io.ReadFull(r, idxBytes[:]) + if err != nil { + return err + } + index := binary.BigEndian.Uint32(idxBytes[:]) + outPoint := wire.NewOutPoint(shaHash, index) + + //Create TxIn + txins = append(txins, wire.NewTxIn(outPoint, nil)) + } + e = txins + + default: + return fmt.Errorf("Unknown type in readElement: %T", e) + } + + return nil +} + +func (c *CreateChannel) DeserializeFundingRequest(r io.Reader) error { + var err error + + //Message Type + var messageType uint8 + err = readElement(r, messageType) + if err != nil { + return err + } + if messageType != 0x30 { + return fmt.Errorf("Message type does not match FundingRequest") + } + + //Channel Type + err = readElement(r, c.ChannelType) + if err != nil { + return err + } + + //Their Funding Amount + err = readElement(r, c.TheirFundingAmount) + if err != nil { + return err + } + + //Their Channel Minimum Capacity + var theirMinimumFunding btcutil.Amount + err = readElement(r, theirMinimumFunding) + if err != nil { + return err + } + //Replace with their minimum if it's greater than ours + //We still need to locally validate that info + if theirMinimumFunding > c.MinTotalFundingAmount { + c.MinTotalFundingAmount = theirMinimumFunding + } + + //Their Revocation Hash + err = readElement(r, c.TheirRevocationHash) + if err != nil { + return err + } + + //Their Commitment Pubkey + err = readElement(r, c.TheirPubkey) + if err != nil { + return err + } + + //Their Reserve Amount + err = readElement(r, c.TheirReserveAmount) + if err != nil { + return err + } + + //Minimum Transaction Fee Per Kb + err = readElement(r, c.TheirMinFeePerKb) + if err != nil { + return err + } + + //LockTime + err = readElement(r, c.LockTime) + if err != nil { + return err + } + + //FeePayer + err = readElement(r, c.FeePayer) + if err != nil { + return err + } + + //Their Delivery PkScript + err = readElement(r, c.TheirDeliveryPkScript) + if err != nil { + return err + } + + //Create the TxIns + err = readElement(r, c.TheirInputs) + if err != nil { + return err } return nil @@ -150,11 +334,11 @@ func readElement(r io.Reader, element interface{}) error { //Serializes the fundingRequest from the CreateChannel struct //Writes the data to w -func (c *CreateChannel) SerializeFundingRequest(w io.Writer) error { +func (c *CreateChannel) SerializeFundingRequest(w *bytes.Buffer) error { var err error //Fund request byte - err = binary.Write(w, binary.BigEndian, uint8(0x30)) + err = writeElement(w, uint8(0x30)) if err != nil { return err } @@ -190,12 +374,6 @@ func (c *CreateChannel) SerializeFundingRequest(w io.Writer) error { return err } - //Our Delivery PkHash - err = writeElement(w, c.OurRevocationHash) - if err != nil { - return err - } - //Our Reserve Amount err = writeElement(w, c.OurReserveAmount) if err != nil { @@ -203,7 +381,7 @@ func (c *CreateChannel) SerializeFundingRequest(w io.Writer) error { } //Minimum Transaction Fee Per KB - err = writeElement(w, c.MinFeePerKb) + err = writeElement(w, c.OurMinFeePerKb) if err != nil { return err } @@ -220,13 +398,16 @@ func (c *CreateChannel) SerializeFundingRequest(w io.Writer) error { return err } - //Delivery PkScript - err = writeElement(w, c.DeliveryPkScript) + //Our Delivery PkScript + //First byte length then pkscript + err = writeElement(w, c.OurDeliveryPkScript) 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.OurInputs) if err != nil { return err diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go new file mode 100644 index 00000000..952a44b6 --- /dev/null +++ b/lnwire/lnwire_test.go @@ -0,0 +1,65 @@ +package lnwire + +import ( + "bytes" + "encoding/hex" + "fmt" + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + // "io/ioutil" + "testing" +) + +var ( + //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()) + + //Our Delivery PkScript + //Privkey: f2c00ead9cbcfec63098dc0a5f152c0165aff40a2ab92feb4e24869a284c32a7 + //PKhash: n2fkWVphUzw3zSigzPsv9GuDyg9mohzKpz + ourDeliveryPkScript, _ = hex.DecodeString("76a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac") + + //echo -n | openssl sha256 + shaHash1Bytes, _ = hex.DecodeString("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") + shaHash1, _ = wire.NewShaHash(shaHash1Bytes) + outpoint1 = wire.NewOutPoint(shaHash1, 0) + //echo | openssl sha256 + shaHash2Bytes, _ = hex.DecodeString("01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b") + shaHash2, _ = wire.NewShaHash(shaHash2Bytes) + outpoint2 = wire.NewOutPoint(shaHash2, 0) + //create inputs from outpoint1 and outpoint2 + ourInputs = []*wire.TxIn{wire.NewTxIn(outpoint1, nil), wire.NewTxIn(outpoint2, nil)} + + //funding request + createChannel = CreateChannel{ + ChannelType: uint8(0), + OurFundingAmount: btcutil.Amount(100000000), + OurReserveAmount: btcutil.Amount(131072), + OurMinFeePerKb: btcutil.Amount(20000), + MinTotalFundingAmount: btcutil.Amount(150000000), + LockTime: uint32(4320), //30 block-days + FeePayer: uint8(0), + OurRevocationHash: ourRevocationHash, + OurPubkey: ourPubKey, + OurDeliveryPkScript: ourDeliveryPkScript, + OurInputs: ourInputs, + } +) + +func TestFundingRequestSerializeDeserialize(t *testing.T) { + b := new(bytes.Buffer) + err := createChannel.SerializeFundingRequest(b) + if err != nil { + fmt.Println("ERR") + fmt.Println(err) + t.Error("Serialization error") + } + t.Logf("ASDF: %x\n", b.Bytes()) +}