Message interface and stuff.
* Added Message interface (similar to btcd's) * Moved Funding Request to its own file * Refacored Funding Request Code (*MUCH* better) * Various fixes
This commit is contained in:
parent
f51a5a6458
commit
161b1b5e4c
188
lnwire/funding_request.go
Normal file
188
lnwire/funding_request.go
Normal file
@ -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")
|
||||
}
|
127
lnwire/funding_request_test.go
Normal file
127
lnwire/funding_request_test.go
Normal file
@ -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())
|
||||
}
|
288
lnwire/lnwire.go
288
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
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
209
lnwire/message.go
Normal file
209
lnwire/message.go
Normal file
@ -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
|
||||
}
|
Loading…
Reference in New Issue
Block a user