Messages for funding flow.

This is the most different due to segwit (the rest of the messages are
simple).

I still need to simplify/refactor the tests, they're "messy".
This commit is contained in:
Joseph Poon 2015-12-30 05:38:57 -08:00
parent a5f0d3e56e
commit a93b6dcee4
12 changed files with 986 additions and 71 deletions

View File

@ -30,7 +30,7 @@ timeout, the sender will be refunded. Sender and receiver can agree to
authorize payment in most cases where there is cooperation, escrow is only
contacted if there is non-cooperation.
Supported in the wire protocol for the unit8 (two 4-bit N-of-M):
Supported in the wire protocol for the uint8 (two 4-bit N-of-M):
17 (00010001): 1-of-1
34 (00100010): 2-of-2
35 (00100011): 2-of-3 [with Recipient being 1 of the two N parties]

41
lnwire/FLOW.md Normal file
View File

@ -0,0 +1,41 @@
Funding (segwit+CSV)
====================
This is two-party funder for a single Funding Transaction (more efficient and
makes the channel creation atomic, but doesn't work for
CSV-no-malleability-fix).
Funding Request
---------------
Someone wants to open a channel. The requester provides any inputs and relevant
information on how much they want to fund and the parameters, these paramters
are a proposal.
Funding Response
----------------
If the responder accepts the request, they also provide any inputs, and returns
with parameters as well. These parameters are now considered "Committed" and the
negotation has finished. If the requester doesn't agree with the new conditions,
they stop. The response also contains the first Commitment pubkey provided by the
responder, which refunds the initial balance back to both parties.
Funding SignAccept
------------
The requester now has sufficient information to get a refund if the transaction
is ever broadcast. The requester signs the Funding Transaction and this message
gives the signature to the responder. The requester also provides the signature
for the initial Commitment Transaction.
Funding SignComplete
---------------
The responder has sufficient information to broadcast the Funding Transaction
(with the ability to receive a refund), the responder broadcasts on the
blockchain and returns the txid to the requester, with the signature of the
Funding Transaction. This is provided as a courtesy, it cannot be relied upon
with non-cooperative channel counterparties and the Funding Transaction can be
braodcast without this message being received by the requester. After the
necessary number of confirmations, Lightning Network transactions can proceed.

View File

@ -1,7 +1,6 @@
package lnwire
import (
"bytes"
"fmt"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
@ -19,7 +18,7 @@ type FundingRequest struct {
//Should double-check the total funding later
MinTotalFundingAmount btcutil.Amount
//CLTV lock-time to use
//CLTV/CSV lock-time to use
LockTime uint32
//Who pays the fees
@ -31,14 +30,13 @@ type FundingRequest struct {
RevocationHash [20]byte
Pubkey *btcec.PublicKey
DeliveryPkScript PkScript //*MUST* be either P2PKH or P2SH
//FIXME: Need a ChangePkScript PkScript for dual-funder CLTV?
ChangePkScript 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)
@ -47,7 +45,9 @@ func (c *FundingRequest) Decode(r io.Reader, pver uint32) error {
//Minimum Transaction Fee Per Kb (77/8)
//LockTime (85/4)
//FeePayer (89/1)
//DeliveryPkScript
//DeliveryPkScript (final delivery)
// First byte length then pkscript
//ChangePkScript (change for extra from inputs)
// First byte length then pkscript
//Inputs: Create the TxIns
// First byte is number of inputs
@ -63,12 +63,13 @@ func (c *FundingRequest) Decode(r io.Reader, pver uint32) error {
&c.LockTime,
&c.FeePayer,
&c.DeliveryPkScript,
&c.ChangePkScript,
&c.Inputs)
if err != nil {
return err
}
return c.Validate()
return nil
}
//Creates a new FundingRequest
@ -89,6 +90,7 @@ func (c *FundingRequest) Encode(w io.Writer, pver uint32) error {
//LockTime
//FeePayer
//DeliveryPkScript
//ChangePkScript
//Inputs: Append the actual Txins
err := writeElements(w, false,
c.ChannelType,
@ -101,6 +103,7 @@ func (c *FundingRequest) Encode(w io.Writer, pver uint32) error {
c.LockTime,
c.FeePayer,
c.DeliveryPkScript,
c.ChangePkScript,
c.Inputs)
if err != nil {
return err
@ -114,13 +117,14 @@ func (c *FundingRequest) Command() uint32 {
}
func (c *FundingRequest) MaxPayloadLength(uint32) uint32 {
//90 (base size) + 26 (pkscript) + 1 (numTxes) + 127*36(127 inputs * sha256+idx)
//4690
return 4689
//90 (base size) + 26 (pkscript) + 26 (pkscript) + 1 (numTxes) + 127*36(127 inputs * sha256+idx)
return 4715
}
//Makes sure the struct data is valid (e.g. no negatives or invalid pkscripts)
func (c *FundingRequest) Validate() error {
var err error
//No negative values
if c.FundingAmount < 0 {
return fmt.Errorf("FundingAmount cannot be negative")
@ -137,28 +141,16 @@ func (c *FundingRequest) Validate() error {
return fmt.Errorf("MinTotalFundingAmount cannot be negative")
}
//PkScript is either P2SH or P2PKH
if len(c.DeliveryPkScript) == 25 {
//P2PKH
//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")
//DeliveryPkScript is either P2SH or P2PKH
err = ValidatePkScript(c.DeliveryPkScript)
if err != nil {
return err
}
} else if len(c.DeliveryPkScript) == 23 {
//P2SH
//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")
//ChangePkScript is either P2SH or P2PKH
err = ValidatePkScript(c.ChangePkScript)
if err != nil {
return err
}
//We're good!

View File

@ -33,6 +33,11 @@ var (
//PKhash: n2fkWVphUzw3zSigzPsv9GuDyg9mohzKpz
deliveryPkScript, _ = hex.DecodeString("76a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac")
// Change PkScript
//Privkey: 5b18f5049efd9d3aff1fb9a06506c0b809fb71562b6ecd02f6c5b3ab298f3b0f
//PKhash: miky84cHvLuk6jcT6GsSbgHR8d7eZCu9Qc
changePkScript, _ = hex.DecodeString("76a914238ee44bb5c8c1314dd03974a17ec6c406fdcb8388ac")
//echo -n | openssl sha256
//This stuff gets reversed!!!
shaHash1Bytes, _ = hex.DecodeString("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
@ -58,10 +63,11 @@ var (
RevocationHash: revocationHash,
Pubkey: pubKey,
DeliveryPkScript: deliveryPkScript,
ChangePkScript: changePkScript,
Inputs: inputs,
}
serializedString = "000000000005f5e1000000000008f0d1804132b6b48371f7b022a16eacb9b2b0ebee134d4102f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee00000000000200000000000000004e20000010e0001976a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac02e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8550000000001ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b00000001"
serializedMessage = "0709110b00000014000000be000000000005f5e1000000000008f0d1804132b6b48371f7b022a16eacb9b2b0ebee134d4102f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee00000000000200000000000000004e20000010e0001976a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac02e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8550000000001ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b00000001"
serializedString = "000000000005f5e1000000000008f0d1804132b6b48371f7b022a16eacb9b2b0ebee134d4102f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee00000000000200000000000000004e20000010e0001976a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac1976a914238ee44bb5c8c1314dd03974a17ec6c406fdcb8388ac02e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8550000000001ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b00000001"
serializedMessage = "0709110b000000c8000000d8000000000005f5e1000000000008f0d1804132b6b48371f7b022a16eacb9b2b0ebee134d4102f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee00000000000200000000000000004e20000010e0001976a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac1976a914238ee44bb5c8c1314dd03974a17ec6c406fdcb8388ac02e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8550000000001ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b00000001"
)
func TestFundingRequestEncodeDecode(t *testing.T) {
@ -71,7 +77,7 @@ func TestFundingRequestEncodeDecode(t *testing.T) {
if err != nil {
t.Error("Serialization error")
t.Error(err.Error())
}
} else {
t.Logf("Encoded Funding Request: %x\n", b.Bytes())
//Check if we serialized correctly
if serializedString != hex.EncodeToString(b.Bytes()) {
@ -86,6 +92,7 @@ func TestFundingRequestEncodeDecode(t *testing.T) {
t.Error(err.Error())
}
}
}
//Test deserialization
//Make a new buffer just to be clean
@ -97,13 +104,13 @@ func TestFundingRequestEncodeDecode(t *testing.T) {
if err != nil {
t.Error("Decoding Error")
t.Error(err.Error())
}
} else {
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
@ -119,9 +126,10 @@ func TestFundingRequestEncodeDecode(t *testing.T) {
_, msg, _, err := ReadMessage(c, uint32(1), wire.TestNet3)
if err != nil {
t.Errorf(err.Error())
}
} else {
if !reflect.DeepEqual(msg, fundingRequest) {
t.Error("Message decoding does not match!")
}
t.Logf(msg.String())
}
}

185
lnwire/funding_response.go Normal file
View File

@ -0,0 +1,185 @@
package lnwire
import (
"fmt"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"io"
)
type FundingResponse struct {
ChannelType uint8
ReservationID uint64
FundingAmount btcutil.Amount
ReserveAmount btcutil.Amount
MinFeePerKb btcutil.Amount //Lock-in min fee
//CLTV/CSV 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
CommitSig *btcec.Signature //Requester's Commitment
DeliveryPkScript PkScript //*MUST* be either P2PKH or P2SH
ChangePkScript PkScript //*MUST* be either P2PKH or P2SH
Inputs []*wire.TxIn
}
func (c *FundingResponse) Decode(r io.Reader, pver uint32) error {
//ReservationID (0/8)
//Channel Type (8/1)
//Funding Amount (9/8)
//Revocation Hash (29/20)
//Commitment Pubkey (61/32)
//Reserve Amount (69/8)
//Minimum Transaction Fee Per Kb (77/8)
//LockTime (81/4)
//FeePayer (82/1)
//DeliveryPkScript (final delivery)
// First byte length then pkscript
//ChangePkScript (change for extra from inputs)
// First byte length then pkscript
//CommitSig
// First byte length then sig
//Inputs: Create the TxIns
// First byte is number of inputs
// For each input, it's 32bytes txin & 4bytes index
err := readElements(r, false,
&c.ReservationID,
&c.ChannelType,
&c.FundingAmount,
&c.RevocationHash,
&c.Pubkey,
&c.ReserveAmount,
&c.MinFeePerKb,
&c.LockTime,
&c.FeePayer,
&c.DeliveryPkScript,
&c.ChangePkScript,
&c.CommitSig,
&c.Inputs)
if err != nil {
return err
}
return nil
}
//Creates a new FundingResponse
func NewFundingResponse() *FundingResponse {
return &FundingResponse{}
}
//Serializes the item from the FundingResponse struct
//Writes the data to w
func (c *FundingResponse) Encode(w io.Writer, pver uint32) error {
//ReservationID (8)
//Channel Type (1)
//Funding Amount (8)
//Revocation Hash (20)
//Commitment Pubkey (32)
//Reserve Amount (8)
//Minimum Transaction Fee Per Kb (8)
//LockTime (4)
//FeePayer (1)
//DeliveryPkScript (final delivery)
//ChangePkScript (change for extra from inputs)
//CommitSig
//Inputs
err := writeElements(w, false,
c.ReservationID,
c.ChannelType,
c.FundingAmount,
c.RevocationHash,
c.Pubkey,
c.ReserveAmount,
c.MinFeePerKb,
c.LockTime,
c.FeePayer,
c.DeliveryPkScript,
c.ChangePkScript,
c.CommitSig,
c.Inputs)
if err != nil {
return err
}
return nil
}
func (c *FundingResponse) Command() uint32 {
return CmdFundingResponse
}
func (c *FundingResponse) MaxPayloadLength(uint32) uint32 {
//82 (base size) + 26 (pkscript) + 26 (pkscript) + 74sig + 1 (numTxes) + 127*36(127 inputs * sha256+idx)
return 4781
}
//Makes sure the struct data is valid (e.g. no negatives or invalid pkscripts)
func (c *FundingResponse) Validate() error {
var err 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")
}
//Delivery PkScript is either P2SH or P2PKH
err = ValidatePkScript(c.DeliveryPkScript)
if err != nil {
return err
}
//Change PkScript is either P2SH or P2PKH
err = ValidatePkScript(c.ChangePkScript)
if err != nil {
return err
}
//We're good!
return nil
}
func (c *FundingResponse) 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 FundingResponse ---\n") +
fmt.Sprintf("ChannelType:\t\t%x\n", c.ChannelType) +
fmt.Sprintf("ReservationID:\t\t%d\n", c.ReservationID) +
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("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("CommitSig\t\t%x\n", c.CommitSig.Serialize()) +
fmt.Sprintf("DeliveryPkScript\t%x\n", c.DeliveryPkScript) +
fmt.Sprintf("ChangePkScript\t%x\n", c.ChangePkScript) +
fmt.Sprintf("Inputs:") +
inputs +
fmt.Sprintf("--- End FundingResponse ---\n")
}

View File

@ -0,0 +1,143 @@
package lnwire
import (
"bytes"
"encoding/hex"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
// "io"
"io/ioutil"
"reflect"
"testing"
)
func TestFundingResponseEncodeDecode(t *testing.T) {
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/fundingResponse.raw"
//preimage: 9a2cbd088763db88dd8ba79e5726daa6aba4aa7e
//echo -n | openssl sha256 | openssl ripemd160 | openssl sha256 | openssl ripemd160
revocationHashBytes, _ = hex.DecodeString("4132b6b48371f7b022a16eacb9b2b0ebee134d41")
revocationHash [20]byte
_ = copy(revocationHash[:], revocationHashBytes)
privKeyBytes, _ = hex.DecodeString("9fa1d55217f57019a3c37f49465896b15836f54cb8ef6963870a52926420a2dd")
privKey, pubKey = btcec.PrivKeyFromBytes(btcec.S256(), privKeyBytes)
//pubKeyBytes, _ = hex.DecodeString("02f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee")
//pubKey, _ = btcec.ParsePubKey(pubKeyBytes, btcec.S256())
// Delivery PkScript
//Privkey: f2c00ead9cbcfec63098dc0a5f152c0165aff40a2ab92feb4e24869a284c32a7
//PKhash: n2fkWVphUzw3zSigzPsv9GuDyg9mohzKpz
deliveryPkScript, _ = hex.DecodeString("76a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac")
// Change PkScript
//Privkey: 5b18f5049efd9d3aff1fb9a06506c0b809fb71562b6ecd02f6c5b3ab298f3b0f
//PKhash: miky84cHvLuk6jcT6GsSbgHR8d7eZCu9Qc
changePkScript, _ = hex.DecodeString("76a914238ee44bb5c8c1314dd03974a17ec6c406fdcb8388ac")
//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)}
//Commitment Signature
tx = wire.NewMsgTx()
emptybytes = new([]byte)
sigStr, _ = txscript.RawTxInSignature(tx, 0, *emptybytes, txscript.SigHashAll, privKey)
commitSig, _ = btcec.ParseSignature(sigStr, btcec.S256())
//funding response
fundingResponse = &FundingResponse{
ChannelType: uint8(1),
ReservationID: uint64(12345678),
FundingAmount: btcutil.Amount(100000000),
ReserveAmount: btcutil.Amount(131072),
MinFeePerKb: btcutil.Amount(20000),
LockTime: uint32(4320), //30 block-days
FeePayer: uint8(1),
RevocationHash: revocationHash,
Pubkey: pubKey,
CommitSig: commitSig,
DeliveryPkScript: deliveryPkScript,
ChangePkScript: changePkScript,
Inputs: inputs,
}
serializedString = "0000000000bc614e010000000005f5e1004132b6b48371f7b022a16eacb9b2b0ebee134d4102f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee00000000000200000000000000004e20000010e0011976a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac1976a914238ee44bb5c8c1314dd03974a17ec6c406fdcb8388ac4630440220333835e58e958f5e92b4ff4e6fa2470dac88094c97506b4d6d1f4e23e52cb481022057483ac18d6b9c9c14f0c626694c9ccf8b27b3dbbedfdf6b6c9a9fa9f427a1df02e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8550000000001ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b00000001"
serializedMessage = "0709110b000000d20000011f0000000000bc614e010000000005f5e1004132b6b48371f7b022a16eacb9b2b0ebee134d4102f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee00000000000200000000000000004e20000010e0011976a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac1976a914238ee44bb5c8c1314dd03974a17ec6c406fdcb8388ac4630440220333835e58e958f5e92b4ff4e6fa2470dac88094c97506b4d6d1f4e23e52cb481022057483ac18d6b9c9c14f0c626694c9ccf8b27b3dbbedfdf6b6c9a9fa9f427a1df02e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8550000000001ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b00000001"
)
//Test serialization
b := new(bytes.Buffer)
err := fundingResponse.Encode(b, 0)
if err != nil {
t.Error("Serialization error")
t.Error(err.Error())
} else {
t.Logf("Encoded Funding Response: %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/fundingResponse.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 := NewFundingResponse()
err = newFunding.Decode(c, 0)
if err != nil {
t.Error("Decoding Error")
t.Error(err.Error())
} else {
if !reflect.DeepEqual(newFunding, fundingResponse) {
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, fundingResponse, 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())
} else {
if !reflect.DeepEqual(msg, fundingResponse) {
t.Error("Message decoding does not match!")
}
t.Logf(msg.String())
}
}

View File

@ -0,0 +1,84 @@
package lnwire
import (
"fmt"
"github.com/btcsuite/btcd/btcec"
"io"
)
type FundingSignAccept struct {
ReservationID uint64
CommitSig *btcec.Signature //Requester's Commitment
FundingTXSigs *[]btcec.Signature
}
func (c *FundingSignAccept) Decode(r io.Reader, pver uint32) error {
//ReservationID (0/8)
//CommitSig
// First byte length then sig
//FundingTXSigs
// First byte is number of FundingTxSigs
// Sorted list of the requester's input signatures
// (originally provided in the Funding Request)
err := readElements(r, false,
&c.ReservationID,
&c.CommitSig,
&c.FundingTXSigs)
if err != nil {
return err
}
return nil
}
//Creates a new FundingSignAccept
func NewFundingSignAccept() *FundingSignAccept {
return &FundingSignAccept{}
}
//Serializes the item from the FundingSignAccept struct
//Writes the data to w
func (c *FundingSignAccept) Encode(w io.Writer, pver uint32) error {
//ReservationID (8)
//CommitSig
//FundingTxSigs
err := writeElements(w, false,
c.ReservationID,
c.CommitSig,
c.FundingTXSigs)
if err != nil {
return err
}
return nil
}
func (c *FundingSignAccept) Command() uint32 {
return CmdFundingSignAccept
}
func (c *FundingSignAccept) MaxPayloadLength(uint32) uint32 {
//8 (base size) + 73 + (73maxSigSize*127maxInputs)
return 9352
}
//Makes sure the struct data is valid (e.g. no negatives or invalid pkscripts)
func (c *FundingSignAccept) Validate() error {
//We're good!
return nil
}
func (c *FundingSignAccept) String() string {
var sigs string
for i, in := range *c.FundingTXSigs {
sigs += fmt.Sprintf("\n Slice\t%d\n", i)
sigs += fmt.Sprintf("\tSig\t%x\n", in.Serialize())
}
return fmt.Sprintf("\n--- Begin FundingSignAccept ---\n") +
fmt.Sprintf("ReservationID:\t\t%d\n", c.ReservationID) +
fmt.Sprintf("CommitSig\t\t%x\n", c.CommitSig.Serialize()) +
fmt.Sprintf("FundingTxSigs:") +
sigs +
fmt.Sprintf("--- End FundingSignAccept ---\n")
}

View File

@ -0,0 +1,115 @@
package lnwire
import (
"bytes"
"encoding/hex"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
// "io"
"io/ioutil"
"reflect"
"testing"
)
func TestFundingSignAcceptEncodeDecode(t *testing.T) {
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/fundingSignAccept.raw"
privKeyBytes, _ = hex.DecodeString("9fa1d55217f57019a3c37f49465896b15836f54cb8ef6963870a52926420a2dd")
privKey, _ = btcec.PrivKeyFromBytes(btcec.S256(), privKeyBytes)
//pubKeyBytes, _ = hex.DecodeString("02f977808cb9577897582d7524b562691e180953dd0008eb44e09594c539d6daee")
//pubKey, _ = btcec.ParsePubKey(pubKeyBytes, btcec.S256())
//Commitment Signature
tx = wire.NewMsgTx()
emptybytes = new([]byte)
sigStr, _ = txscript.RawTxInSignature(tx, 0, *emptybytes, txscript.SigHashAll, privKey)
commitSig, _ = btcec.ParseSignature(sigStr, btcec.S256())
//Funding TX Sig 1
sig1privKeyBytes, _ = hex.DecodeString("927f5827d75dd2addeb532c0fa5ac9277565f981dd6d0d037b422be5f60bdbef")
sig1privKey, _ = btcec.PrivKeyFromBytes(btcec.S256(), sig1privKeyBytes)
sigStr1, _ = txscript.RawTxInSignature(tx, 0, *emptybytes, txscript.SigHashAll, sig1privKey)
commitSig1, _ = btcec.ParseSignature(sigStr1, btcec.S256())
//Funding TX Sig 2
sig2privKeyBytes, _ = hex.DecodeString("8a4ad188f6f4000495b765cfb6ffa591133a73019c45428ddd28f53bab551847")
sig2privKey, _ = btcec.PrivKeyFromBytes(btcec.S256(), sig2privKeyBytes)
sigStr2, _ = txscript.RawTxInSignature(tx, 0, *emptybytes, txscript.SigHashAll, sig2privKey)
commitSig2, _ = btcec.ParseSignature(sigStr2, btcec.S256())
fundingTXSigs = append(*new([]btcec.Signature), *commitSig1, *commitSig2)
//funding response
fundingSignAccept = &FundingSignAccept{
ReservationID: uint64(12345678),
CommitSig: commitSig,
FundingTXSigs: &fundingTXSigs,
}
serializedString = "0000000000bc614e4630440220333835e58e958f5e92b4ff4e6fa2470dac88094c97506b4d6d1f4e23e52cb481022057483ac18d6b9c9c14f0c626694c9ccf8b27b3dbbedfdf6b6c9a9fa9f427a1df02473045022100e7946d057c0b4cc4d3ea525ba156b429796858ebc543d75a6c6c2cbca732db6902202fea377c1f9fb98cd103cf5a4fba276a074b378d4227d15f5fa6439f1a6685bb4630440220235ee55fed634080089953048c3e3f7dc3a154fd7ad18f31dc08e05b7864608a02203bdd7d4e4d9a8162d4b511faf161f0bb16c45181187125017cd0c620c53876ca"
serializedMessage = "0709110b000000dc000000df0000000000bc614e4630440220333835e58e958f5e92b4ff4e6fa2470dac88094c97506b4d6d1f4e23e52cb481022057483ac18d6b9c9c14f0c626694c9ccf8b27b3dbbedfdf6b6c9a9fa9f427a1df02473045022100e7946d057c0b4cc4d3ea525ba156b429796858ebc543d75a6c6c2cbca732db6902202fea377c1f9fb98cd103cf5a4fba276a074b378d4227d15f5fa6439f1a6685bb4630440220235ee55fed634080089953048c3e3f7dc3a154fd7ad18f31dc08e05b7864608a02203bdd7d4e4d9a8162d4b511faf161f0bb16c45181187125017cd0c620c53876ca"
)
//Test serialization
b := new(bytes.Buffer)
err := fundingSignAccept.Encode(b, 0)
if err != nil {
t.Error("Serialization error")
t.Error(err.Error())
} else {
t.Logf("Encoded Funding SignAccept: %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/fundingSignAccept.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 := NewFundingSignAccept()
err = newFunding.Decode(c, 0)
if err != nil {
t.Error("Decoding Error")
t.Error(err.Error())
} else {
if !reflect.DeepEqual(newFunding, fundingSignAccept) {
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, fundingSignAccept, 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())
} else {
if !reflect.DeepEqual(msg, fundingSignAccept) {
t.Error("Message decoding does not match!")
}
t.Logf(msg.String())
}
}

View File

@ -0,0 +1,84 @@
package lnwire
import (
"fmt"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"io"
)
type FundingSignComplete struct {
ReservationID uint64
TxID *wire.ShaHash
FundingTXSigs *[]btcec.Signature
}
func (c *FundingSignComplete) Decode(r io.Reader, pver uint32) error {
//ReservationID (0/8)
//TxID
//FundingTXSigs
// First byte is number of FundingTxSigs
// Sorted list of the requester's input signatures
// (originally provided in the Funding Request)
err := readElements(r, false,
&c.ReservationID,
&c.TxID,
&c.FundingTXSigs)
if err != nil {
return err
}
return nil
}
//Creates a new FundingSignComplete
func NewFundingSignComplete() *FundingSignComplete {
return &FundingSignComplete{}
}
//Serializes the item from the FundingSignComplete struct
//Writes the data to w
func (c *FundingSignComplete) Encode(w io.Writer, pver uint32) error {
//ReservationID (8)
//CommitSig
//FundingTxSigs
err := writeElements(w, false,
c.ReservationID,
c.TxID,
c.FundingTXSigs)
if err != nil {
return err
}
return nil
}
func (c *FundingSignComplete) Command() uint32 {
return CmdFundingSignComplete
}
func (c *FundingSignComplete) MaxPayloadLength(uint32) uint32 {
//8 (base size) + 32 + (73maxSigSize*127maxInputs)
return 9311
}
//Makes sure the struct data is valid (e.g. no negatives or invalid pkscripts)
func (c *FundingSignComplete) Validate() error {
//We're good!
return nil
}
func (c *FundingSignComplete) String() string {
var sigs string
for i, in := range *c.FundingTXSigs {
sigs += fmt.Sprintf("\n Slice\t%d\n", i)
sigs += fmt.Sprintf("\tSig\t%x\n", in.Serialize())
}
return fmt.Sprintf("\n--- Begin FundingSignComplete ---\n") +
fmt.Sprintf("ReservationID:\t\t%d\n", c.ReservationID) +
fmt.Sprintf("TxID\t\t%s\n", c.TxID.String()) +
fmt.Sprintf("FundingTxSigs:") +
sigs +
fmt.Sprintf("--- End FundingSignComplete ---\n")
}

View File

@ -0,0 +1,112 @@
package lnwire
import (
"bytes"
"encoding/hex"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
// "io"
"io/ioutil"
"reflect"
"testing"
)
func TestFundingSignCompleteEncodeDecode(t *testing.T) {
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/fundingSignComplete.raw"
//TxID
txid = new(wire.ShaHash)
//Reversed when displayed
txidBytes, _ = hex.DecodeString("fd95c6e5c9d5bcf9cfc7231b6a438e46c518c724d0b04b75cc8fddf84a254e3a")
_ = copy(txid[:], txidBytes)
//Funding TX Sig 1
tx = wire.NewMsgTx()
emptybytes = new([]byte)
sig1privKeyBytes, _ = hex.DecodeString("927f5827d75dd2addeb532c0fa5ac9277565f981dd6d0d037b422be5f60bdbef")
sig1privKey, _ = btcec.PrivKeyFromBytes(btcec.S256(), sig1privKeyBytes)
sigStr1, _ = txscript.RawTxInSignature(tx, 0, *emptybytes, txscript.SigHashAll, sig1privKey)
commitSig1, _ = btcec.ParseSignature(sigStr1, btcec.S256())
//Funding TX Sig 2
sig2privKeyBytes, _ = hex.DecodeString("8a4ad188f6f4000495b765cfb6ffa591133a73019c45428ddd28f53bab551847")
sig2privKey, _ = btcec.PrivKeyFromBytes(btcec.S256(), sig2privKeyBytes)
sigStr2, _ = txscript.RawTxInSignature(tx, 0, *emptybytes, txscript.SigHashAll, sig2privKey)
commitSig2, _ = btcec.ParseSignature(sigStr2, btcec.S256())
fundingTXSigs = append(*new([]btcec.Signature), *commitSig1, *commitSig2)
//funding response
fundingSignComplete = &FundingSignComplete{
ReservationID: uint64(12345678),
TxID: txid,
FundingTXSigs: &fundingTXSigs,
}
serializedString = "0000000000bc614efd95c6e5c9d5bcf9cfc7231b6a438e46c518c724d0b04b75cc8fddf84a254e3a02473045022100e7946d057c0b4cc4d3ea525ba156b429796858ebc543d75a6c6c2cbca732db6902202fea377c1f9fb98cd103cf5a4fba276a074b378d4227d15f5fa6439f1a6685bb4630440220235ee55fed634080089953048c3e3f7dc3a154fd7ad18f31dc08e05b7864608a02203bdd7d4e4d9a8162d4b511faf161f0bb16c45181187125017cd0c620c53876ca"
serializedMessage = "0709110b000000e6000000b80000000000bc614efd95c6e5c9d5bcf9cfc7231b6a438e46c518c724d0b04b75cc8fddf84a254e3a02473045022100e7946d057c0b4cc4d3ea525ba156b429796858ebc543d75a6c6c2cbca732db6902202fea377c1f9fb98cd103cf5a4fba276a074b378d4227d15f5fa6439f1a6685bb4630440220235ee55fed634080089953048c3e3f7dc3a154fd7ad18f31dc08e05b7864608a02203bdd7d4e4d9a8162d4b511faf161f0bb16c45181187125017cd0c620c53876ca"
)
//Test serialization
b := new(bytes.Buffer)
err := fundingSignComplete.Encode(b, 0)
if err != nil {
t.Error("Serialization error")
t.Error(err.Error())
} else {
t.Logf("Encoded FundingSignComplete: %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/fundingSignComplete.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 := NewFundingSignComplete()
err = newFunding.Decode(c, 0)
if err != nil {
t.Error("Decoding Error")
t.Error(err.Error())
} else {
if !reflect.DeepEqual(newFunding, fundingSignComplete) {
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, fundingSignComplete, 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())
} else {
if !reflect.DeepEqual(msg, fundingSignComplete) {
t.Error("Message decoding does not match!")
}
t.Logf(msg.String())
}
}

View File

@ -1,6 +1,7 @@
package lnwire
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/btcsuite/btcd/btcec"
@ -39,6 +40,14 @@ func writeElement(w io.Writer, includeSig bool, element interface{}) error {
return err
}
return nil
case uint64:
var b [8]byte
binary.BigEndian.PutUint64(b[:], uint64(e))
_, err = w.Write(b[:])
if err != nil {
return err
}
return nil
case btcutil.Amount:
err = binary.Write(w, binary.BigEndian, int64(e))
if err != nil {
@ -57,6 +66,47 @@ func writeElement(w io.Writer, includeSig bool, element interface{}) error {
return err
}
return nil
case *[]btcec.Signature:
numSigs := len(*e)
if numSigs > 127 {
return fmt.Errorf("Too many signatures!")
}
//Write the size
err = writeElement(w, false, uint8(numSigs))
if err != nil {
return err
}
//Write the data
for i := 0; i < numSigs; i++ {
err = writeElement(w, false, &(*e)[i])
if err != nil {
return err
}
}
return nil
case *btcec.Signature:
sig := e.Serialize()
sigLength := len(sig)
if sigLength > 73 {
return fmt.Errorf("Signature too long!")
}
//Write the size
err = writeElement(w, false, uint8(sigLength))
if err != nil {
return err
}
//Write the data
_, err = w.Write(sig)
if err != nil {
return err
}
return nil
case *wire.ShaHash:
_, err = w.Write(e[:])
if err != nil {
return err
}
return nil
case [20]byte:
_, err = w.Write(e[:])
if err != nil {
@ -78,7 +128,7 @@ func writeElement(w io.Writer, includeSig bool, element interface{}) error {
return fmt.Errorf("PkScript too long!")
}
//Write the size (1-byte)
err = binary.Write(w, binary.BigEndian, uint8(scriptLength))
err = writeElement(w, false, uint8(scriptLength))
if err != nil {
return err
}
@ -94,7 +144,7 @@ func writeElement(w io.Writer, includeSig bool, element interface{}) error {
if len(e) > 127 {
return fmt.Errorf("Too many txins")
}
err = binary.Write(w, binary.BigEndian, uint8(len(e)))
err = writeElement(w, false, uint8(len(e)))
if err != nil {
return err
}
@ -163,6 +213,14 @@ func readElement(r io.Reader, includeSig bool, element interface{}) error {
}
*e = binary.BigEndian.Uint32(b[:])
return nil
case *uint64:
var b [8]byte
_, err = io.ReadFull(r, b[:])
if err != nil {
return err
}
*e = binary.BigEndian.Uint64(b[:])
return nil
case *btcutil.Amount:
var b [8]byte
_, err = io.ReadFull(r, b[:])
@ -171,6 +229,14 @@ func readElement(r io.Reader, includeSig bool, element interface{}) error {
}
*e = btcutil.Amount(int64(binary.BigEndian.Uint64(b[:])))
return nil
case **wire.ShaHash:
var b wire.ShaHash
_, err = io.ReadFull(r, b[:])
if err != nil {
return err
}
*e = &b
return nil
case **btcec.PublicKey:
var b [33]byte
_, err = io.ReadFull(r, b[:])
@ -178,10 +244,55 @@ func readElement(r io.Reader, includeSig bool, element interface{}) error {
return err
}
x, err := btcec.ParsePubKey(b[:], btcec.S256())
*e = &*x
if err != nil {
return err
}
*e = &*x
return nil
case **[]btcec.Signature:
var numSigs uint8
err = readElement(r, false, &numSigs)
if err != nil {
return err
}
if numSigs > 127 {
return fmt.Errorf("Too many signatures!")
}
//Read that number of signatures
var sigs []btcec.Signature
for i := uint8(0); i < numSigs; i++ {
sig := new(btcec.Signature)
readElement(r, false, &sig)
sigs = append(sigs, *sig)
}
*e = &sigs
return nil
case **btcec.Signature:
var sigLength uint8
err = readElement(r, false, &sigLength)
if err != nil {
return err
}
if sigLength > 73 {
return fmt.Errorf("Signature too long!")
}
//Read the sig length
l := io.LimitReader(r, int64(sigLength))
sig, err := ioutil.ReadAll(l)
if err != nil {
return err
}
if len(sig) != int(sigLength) {
return fmt.Errorf("EOF: Signature length mismatch.")
}
btcecSig, err := btcec.ParseSignature(sig, btcec.S256())
if err != nil {
return err
}
*e = &*btcecSig
return nil
case *[20]byte:
_, err = io.ReadFull(r, e[:])
@ -199,37 +310,40 @@ func readElement(r io.Reader, includeSig bool, element interface{}) error {
return nil
case *PkScript:
//Get the script length first
var scriptLength [1]uint8
_, err = r.Read(scriptLength[:])
var scriptLength uint8
err = readElement(r, false, &scriptLength)
if err != nil {
return err
}
if scriptLength[0] > 25 {
if scriptLength > 25 {
return fmt.Errorf("PkScript too long!")
}
//Read the script length
l := io.LimitReader(r, int64(scriptLength[0]))
*e, _ = ioutil.ReadAll(l)
l := io.LimitReader(r, int64(scriptLength))
*e, err = ioutil.ReadAll(l)
if len(*e) != int(scriptLength) {
return fmt.Errorf("EOF: Signature length mismatch.")
}
if err != nil {
return err
}
return nil
case *[]*wire.TxIn:
//Read the size (1-byte number of txins)
var numScripts [1]uint8
_, err = r.Read(numScripts[:])
var numScripts uint8
err = readElement(r, false, &numScripts)
if err != nil {
return err
}
if numScripts[0] > 127 {
if numScripts > 127 {
return fmt.Errorf("Too many txins")
}
//Append the actual TxIns
var txins []*wire.TxIn
for i := uint8(0); i < numScripts[0]; i++ {
for i := uint8(0); i < numScripts; i++ {
//Hash
var h [32]byte
_, err = io.ReadFull(r, h[:])
@ -281,3 +395,31 @@ func readElements(r io.Reader, includeSig bool, elements ...interface{}) error {
}
return nil
}
//Validates whether a PkScript byte array is P2SH or P2PKH
func ValidatePkScript(pkScript PkScript) error {
if len(pkScript) == 25 {
//P2PKH
//Begins with OP_DUP OP_HASH160 PUSHDATA(20)
if !bytes.Equal(pkScript[0:3], []byte{118, 169, 20}) ||
//Ends with OP_EQUALVERIFY OP_CHECKSIG
!bytes.Equal(pkScript[23:25], []byte{136, 172}) {
//If it's not correct, return error
return fmt.Errorf("PkScript only allows P2SH or P2PKH")
}
} else if len(pkScript) == 23 {
//P2SH
//Begins with OP_HASH160 PUSHDATA(20)
if !bytes.Equal(pkScript[0:2], []byte{169, 20}) ||
//Ends with OP_EQUAL
!bytes.Equal(pkScript[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")
}
return nil
}

View File

@ -14,7 +14,10 @@ const MessageHeaderSize = 12
const MaxMessagePayload = 1024 * 1024 * 32 // 32MB
const (
CmdFundingRequest = uint32(20)
CmdFundingRequest = uint32(200)
CmdFundingResponse = uint32(210)
CmdFundingSignAccept = uint32(220)
CmdFundingSignComplete = uint32(230)
)
//Every message has these functions:
@ -33,8 +36,14 @@ func makeEmptyMessage(command uint32) (Message, error) {
switch command {
case CmdFundingRequest:
msg = &FundingRequest{}
case CmdFundingResponse:
msg = &FundingResponse{}
case CmdFundingSignAccept:
msg = &FundingSignAccept{}
case CmdFundingSignComplete:
msg = &FundingSignComplete{}
default:
return nil, fmt.Errorf("unhandled command [%x]", command)
return nil, fmt.Errorf("unhandled command [%d]", command)
}
return msg, nil
@ -194,7 +203,7 @@ func ReadMessage(r io.Reader, pver uint32, btcnet wire.BitcoinNet) (int, Message
}
//Validate the data
msg.Validate()
err = msg.Validate()
if err != nil {
return totalBytes, nil, nil, err
}