Structs for Wire Protocol HTLCs and Commitments

* Structs and wire messages for HTLCs
* Wire protocol for a state machine with no blocking(!!!)
  (I will write the state machine)
  TL;DR: Can do multiple HTLC modifications in-flight, dead simple wire
  protocol. Both sides can update their Commitments unliaterally without
  waiting for the other party's signature. Will have basic/preliminary
  notes in the README
* Added **swp to .gitignore because of vim annoyances
This commit is contained in:
Joseph Poon 2016-01-05 08:19:22 -08:00 committed by Olaoluwa Osuntokun
parent 58f0bfe252
commit f3849f5c10
23 changed files with 1286 additions and 5 deletions

3
.gitignore vendored
View File

@ -34,3 +34,6 @@ cmd/lncli/lncli
cmd/lnshell/lnshell
test_wal/*
# vim
**.swp

View File

@ -0,0 +1,82 @@
package lnwire
import (
"fmt"
"io"
)
//Multiple Clearing Requests are possible by putting this inside an array of
//clearing requests
type CommitRevocation struct {
//We can use a different data type for this if necessary...
ChannelID uint64
//Height of the commitment
//You should have the most recent commitment height stored locally
//This should be validated!
//This is used for shachain.
//Each party increments their own CommitmentHeight, they can differ for
//each part of the Commitment.
CommitmentHeight uint64
//Revocation to use
RevocationProof [20]byte
}
func (c *CommitRevocation) Decode(r io.Reader, pver uint32) error {
//ChannelID(8)
//CommitmentHeight(8)
//RevocationProof(20)
err := readElements(r,
&c.ChannelID,
&c.CommitmentHeight,
&c.RevocationProof,
)
if err != nil {
return err
}
return nil
}
//Creates a new CommitRevocation
func NewCommitRevocation() *CommitRevocation {
return &CommitRevocation{}
}
//Serializes the item from the CommitRevocation struct
//Writes the data to w
func (c *CommitRevocation) Encode(w io.Writer, pver uint32) error {
err := writeElements(w,
c.ChannelID,
c.CommitmentHeight,
c.RevocationProof,
)
if err != nil {
return err
}
return nil
}
func (c *CommitRevocation) Command() uint32 {
return CmdCommitRevocation
}
func (c *CommitRevocation) MaxPayloadLength(uint32) uint32 {
return 36
}
//Makes sure the struct data is valid (e.g. no negatives or invalid pkscripts)
func (c *CommitRevocation) Validate() error {
//We're good!
return nil
}
func (c *CommitRevocation) String() string {
return fmt.Sprintf("\n--- Begin CommitRevocation ---\n") +
fmt.Sprintf("ChannelID:\t\t%d\n", c.ChannelID) +
fmt.Sprintf("CommitmentHeight:\t%d\n", c.CommitmentHeight) +
fmt.Sprintf("RevocationProof:\t%x\n", c.RevocationProof) +
fmt.Sprintf("--- End CommitRevocation ---\n")
}

View File

@ -0,0 +1,36 @@
package lnwire
import (
"testing"
)
var (
//Need to to do this here
_ = copy(revocationHash[:], revocationHashBytes)
commitRevocation = &CommitRevocation{
ChannelID: uint64(12345678),
CommitmentHeight: uint64(12345),
RevocationProof: revocationHash, //technically it's not a hash... fix later
}
commitRevocationSerializedString = "0000000000bc614e00000000000030394132b6b48371f7b022a16eacb9b2b0ebee134d41"
commitRevocationSerializedMessage = "0709110b000007da000000240000000000bc614e00000000000030394132b6b48371f7b022a16eacb9b2b0ebee134d41"
)
func TestCommitRevocationEncodeDecode(t *testing.T) {
//All of these types being passed are of the message interface type
//Test serialization, runs: message.Encode(b, 0)
//Returns bytes
//Compares the expected serialized string from the original
s := SerializeTest(t, commitRevocation, commitRevocationSerializedString, filename)
//Test deserialization, runs: message.Decode(s, 0)
//Makes sure the deserialized struct is the same as the original
newMessage := NewCommitRevocation()
DeserializeTest(t, s, newMessage, commitRevocation)
//Test message using Message interface
//Serializes into buf: WriteMessage(buf, message, uint32(1), wire.TestNet3)
//Deserializes into msg: _, msg, _ , err := ReadMessage(buf, uint32(1), wire.TestNet3)
MessageSerializeDeserializeTest(t, commitRevocation, commitRevocationSerializedMessage)
}

130
lnwire/commit_signature.go Normal file
View File

@ -0,0 +1,130 @@
package lnwire
import (
"fmt"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcutil"
"io"
)
//Multiple Clearing Requests are possible by putting this inside an array of
//clearing requests
type CommitSignature struct {
//We can use a different data type for this if necessary...
ChannelID uint64
//Height of the commitment
//You should have the most recent commitment height stored locally
//This should be validated!
//This is used for shachain.
//Each party increments their own CommitmentHeight, they can differ for
//each part of the Commitment.
CommitmentHeight uint64
//The last staging included by both parties
//Basically, the state is updated to this point on both parties'
//staging
//Staging inclusion is in order.
CommitterLastStaging uint64
ReceiverLastStaging uint64
//Hash of the revocation to use
RevocationHash [20]byte
//Total miners' fee that was used
Fee btcutil.Amount
//Signature for the new Commitment
CommitSig *btcec.Signature //Requester's Commitment
}
func (c *CommitSignature) Decode(r io.Reader, pver uint32) error {
//ChannelID(8)
//CommitmentHeight(8)
//CommiterLastStaging(8)
//ReceiverLastStaging(8)
//RevocationHash(20)
//Fee(8)
//RequesterCommitSig(73max+2)
err := readElements(r,
&c.ChannelID,
&c.CommitmentHeight,
&c.CommitterLastStaging,
&c.ReceiverLastStaging,
&c.RevocationHash,
&c.Fee,
&c.CommitSig,
)
if err != nil {
return err
}
return nil
}
//Creates a new CommitSignature
func NewCommitSignature() *CommitSignature {
return &CommitSignature{}
}
//Serializes the item from the CommitSignature struct
//Writes the data to w
func (c *CommitSignature) Encode(w io.Writer, pver uint32) error {
err := writeElements(w,
c.ChannelID,
c.CommitmentHeight,
c.CommitterLastStaging,
c.ReceiverLastStaging,
c.RevocationHash,
c.Fee,
c.CommitSig,
)
if err != nil {
return err
}
return nil
}
func (c *CommitSignature) Command() uint32 {
return CmdCommitSignature
}
func (c *CommitSignature) MaxPayloadLength(uint32) uint32 {
return 135
}
//Makes sure the struct data is valid (e.g. no negatives or invalid pkscripts)
func (c *CommitSignature) Validate() error {
if c.Fee < 0 {
//While fees can be negative, it's too confusing to allow
//negative payments. Maybe for some wallets, but not this one!
return fmt.Errorf("Amount paid cannot be negative.")
}
//We're good!
return nil
}
func (c *CommitSignature) String() string {
//c.ChannelID,
//c.CommitmentHeight,
//c.CommitterLastStaging,
//c.ReceiverLastStaging,
//c.RevocationHash,
//c.Fee,
//c.CommitSig,
var serializedSig []byte
if &c.CommitSig != nil && c.CommitSig.R != nil {
serializedSig = c.CommitSig.Serialize()
}
return fmt.Sprintf("\n--- Begin CommitSignature ---\n") +
fmt.Sprintf("ChannelID:\t\t%d\n", c.ChannelID) +
fmt.Sprintf("CommitmentHeight:\t%d\n", c.CommitmentHeight) +
fmt.Sprintf("CommitterLastStaging:\t%d\n", c.CommitterLastStaging) +
fmt.Sprintf("ReceiverLastStaging:\t%d\n", c.ReceiverLastStaging) +
fmt.Sprintf("RevocationHash:\t\t%x\n", c.RevocationHash) +
fmt.Sprintf("Fee:\t\t\t%s\n", c.Fee.String()) +
fmt.Sprintf("CommitSig:\t\t%x\n", serializedSig) +
fmt.Sprintf("--- End CommitSignature ---\n")
}

View File

@ -0,0 +1,41 @@
package lnwire
import (
"github.com/btcsuite/btcutil"
"testing"
)
var (
//Need to to do this here
_ = copy(revocationHash[:], revocationHashBytes)
commitSignature = &CommitSignature{
ChannelID: uint64(12345678),
CommitmentHeight: uint64(12345),
CommitterLastStaging: uint64(12345678),
ReceiverLastStaging: uint64(87654321),
RevocationHash: revocationHash,
Fee: btcutil.Amount(10000),
CommitSig: commitSig,
}
commitSignatureSerializedString = "0000000000bc614e00000000000030390000000000bc614e0000000005397fb14132b6b48371f7b022a16eacb9b2b0ebee134d4100000000000027104630440220333835e58e958f5e92b4ff4e6fa2470dac88094c97506b4d6d1f4e23e52cb481022057483ac18d6b9c9c14f0c626694c9ccf8b27b3dbbedfdf6b6c9a9fa9f427a1df"
commitSignatureSerializedMessage = "0709110b000007d0000000830000000000bc614e00000000000030390000000000bc614e0000000005397fb14132b6b48371f7b022a16eacb9b2b0ebee134d4100000000000027104630440220333835e58e958f5e92b4ff4e6fa2470dac88094c97506b4d6d1f4e23e52cb481022057483ac18d6b9c9c14f0c626694c9ccf8b27b3dbbedfdf6b6c9a9fa9f427a1df"
)
func TestCommitSignatureEncodeDecode(t *testing.T) {
//All of these types being passed are of the message interface type
//Test serialization, runs: message.Encode(b, 0)
//Returns bytes
//Compares the expected serialized string from the original
s := SerializeTest(t, commitSignature, commitSignatureSerializedString, filename)
//Test deserialization, runs: message.Decode(s, 0)
//Makes sure the deserialized struct is the same as the original
newMessage := NewCommitSignature()
DeserializeTest(t, s, newMessage, commitSignature)
//Test message using Message interface
//Serializes into buf: WriteMessage(buf, message, uint32(1), wire.TestNet3)
//Deserializes into msg: _, msg, _ , err := ReadMessage(buf, uint32(1), wire.TestNet3)
MessageSerializeDeserializeTest(t, commitSignature, commitSignatureSerializedMessage)
}

70
lnwire/htlc_addaccept.go Normal file
View File

@ -0,0 +1,70 @@
package lnwire
import (
"fmt"
"io"
)
type HTLCAddAccept struct {
ChannelID uint64
StagingID uint64
}
func (c *HTLCAddAccept) Decode(r io.Reader, pver uint32) error {
//ChannelID(8)
//CommitmentHeight(8)
//NextResponderCommitmentRevocationHash(20)
//ResponderRevocationPreimage(20)
//ResponderCommitSig(2+73max)
err := readElements(r,
&c.ChannelID,
&c.StagingID,
)
if err != nil {
return err
}
return nil
}
//Creates a new HTLCAddAccept
func NewHTLCAddAccept() *HTLCAddAccept {
return &HTLCAddAccept{}
}
//Serializes the item from the HTLCAddAccept struct
//Writes the data to w
func (c *HTLCAddAccept) Encode(w io.Writer, pver uint32) error {
err := writeElements(w,
c.ChannelID,
c.StagingID,
)
if err != nil {
return err
}
return nil
}
func (c *HTLCAddAccept) Command() uint32 {
return CmdHTLCAddAccept
}
func (c *HTLCAddAccept) MaxPayloadLength(uint32) uint32 {
//16 base size
return 16
}
//Makes sure the struct data is valid (e.g. no negatives or invalid pkscripts)
func (c *HTLCAddAccept) Validate() error {
//We're good!
return nil
}
func (c *HTLCAddAccept) String() string {
return fmt.Sprintf("\n--- Begin HTLCAddAccept ---\n") +
fmt.Sprintf("ChannelID:\t\t%d\n", c.ChannelID) +
fmt.Sprintf("StagingID:\t\t%d\n", c.StagingID) +
fmt.Sprintf("--- End HTLCAddAccept ---\n")
}

View File

@ -0,0 +1,32 @@
package lnwire
import (
"testing"
)
var (
htlcAddAccept = &HTLCAddAccept{
ChannelID: uint64(12345678),
StagingID: uint64(12345),
}
htlcAddAcceptSerializedString = "0000000000bc614e0000000000003039"
htlcAddAcceptSerializedMessage = "0709110b000003f2000000100000000000bc614e0000000000003039"
)
func TestHTLCAddAcceptEncodeDecode(t *testing.T) {
//All of these types being passed are of the message interface type
//Test serialization, runs: message.Encode(b, 0)
//Returns bytes
//Compares the expected serialized string from the original
s := SerializeTest(t, htlcAddAccept, htlcAddAcceptSerializedString, filename)
//Test deserialization, runs: message.Decode(s, 0)
//Makes sure the deserialized struct is the same as the original
newMessage := NewHTLCAddAccept()
DeserializeTest(t, s, newMessage, htlcAddAccept)
//Test message using Message interface
//Serializes into buf: WriteMessage(buf, message, uint32(1), wire.TestNet3)
//Deserializes into msg: _, msg, _ , err := ReadMessage(buf, uint32(1), wire.TestNet3)
MessageSerializeDeserializeTest(t, htlcAddAccept, htlcAddAcceptSerializedMessage)
}

70
lnwire/htlc_addreject.go Normal file
View File

@ -0,0 +1,70 @@
package lnwire
import (
"fmt"
"io"
)
type HTLCAddReject struct {
ChannelID uint64
StagingID uint64
}
func (c *HTLCAddReject) Decode(r io.Reader, pver uint32) error {
//ChannelID(8)
//CommitmentHeight(8)
//NextResponderCommitmentRevocationHash(20)
//ResponderRevocationPreimage(20)
//ResponderCommitSig(2+73max)
err := readElements(r,
&c.ChannelID,
&c.StagingID,
)
if err != nil {
return err
}
return nil
}
//Creates a new HTLCAddReject
func NewHTLCAddReject() *HTLCAddReject {
return &HTLCAddReject{}
}
//Serializes the item from the HTLCAddReject struct
//Writes the data to w
func (c *HTLCAddReject) Encode(w io.Writer, pver uint32) error {
err := writeElements(w,
c.ChannelID,
c.StagingID,
)
if err != nil {
return err
}
return nil
}
func (c *HTLCAddReject) Command() uint32 {
return CmdHTLCAddReject
}
func (c *HTLCAddReject) MaxPayloadLength(uint32) uint32 {
//16 base size
return 16
}
//Makes sure the struct data is valid (e.g. no negatives or invalid pkscripts)
func (c *HTLCAddReject) Validate() error {
//We're good!
return nil
}
func (c *HTLCAddReject) String() string {
return fmt.Sprintf("\n--- Begin HTLCAddReject ---\n") +
fmt.Sprintf("ChannelID:\t\t%d\n", c.ChannelID) +
fmt.Sprintf("StagingID:\t\t%d\n", c.StagingID) +
fmt.Sprintf("--- End HTLCAddReject ---\n")
}

View File

@ -0,0 +1,32 @@
package lnwire
import (
"testing"
)
var (
htlcAddReject = &HTLCAddReject{
ChannelID: uint64(12345678),
StagingID: uint64(12345),
}
htlcAddRejectSerializedString = "0000000000bc614e0000000000003039"
htlcAddRejectSerializedMessage = "0709110b000003fc000000100000000000bc614e0000000000003039"
)
func TestHTLCAddRejectEncodeDecode(t *testing.T) {
//All of these types being passed are of the message interface type
//Test serialization, runs: message.Encode(b, 0)
//Returns bytes
//Compares the expected serialized string from the original
s := SerializeTest(t, htlcAddReject, htlcAddRejectSerializedString, filename)
//Test deserialization, runs: message.Decode(s, 0)
//Makes sure the deserialized struct is the same as the original
newMessage := NewHTLCAddReject()
DeserializeTest(t, s, newMessage, htlcAddReject)
//Test message using Message interface
//Serializes into buf: WriteMessage(buf, message, uint32(1), wire.TestNet3)
//Deserializes into msg: _, msg, _ , err := ReadMessage(buf, uint32(1), wire.TestNet3)
MessageSerializeDeserializeTest(t, htlcAddReject, htlcAddRejectSerializedMessage)
}

129
lnwire/htlc_addrequest.go Normal file
View File

@ -0,0 +1,129 @@
package lnwire
import (
"fmt"
"io"
)
//Multiple Clearing Requests are possible by putting this inside an array of
//clearing requests
type HTLCAddRequest struct {
//We can use a different data type for this if necessary...
ChannelID uint64
//ID of this request
StagingID uint64
//When the HTLC expires
Expiry uint32
//Amount to pay in the hop
//Difference between hop and first item in blob is the fee to complete
Amount CreditsAmount
//Hash160 address of the next hop.
NextHop [20]byte
//Contract Type
//first 4 bits is n, second for is m, in n-of-m "multisig"
ContractType uint8
//Redemption Hashes
RedemptionHashes []*[20]byte
//Data to parse&pass on to the next node
//Eventually, we need to make this into a group of 2 nested structs?
Blob []byte
}
func (c *HTLCAddRequest) Decode(r io.Reader, pver uint32) error {
//ChannelID(8)
//StagingID(8)
//Expiry(4)
//Amount(4)
//NextHop(20)
//ContractType(1)
//RedemptionHashes (numOfHashes * 20 + numOfHashes)
//Blob(2+blobsize)
err := readElements(r,
&c.ChannelID,
&c.StagingID,
&c.Expiry,
&c.Amount,
&c.NextHop,
&c.ContractType,
&c.RedemptionHashes,
&c.Blob,
)
if err != nil {
return err
}
return nil
}
//Creates a new HTLCAddRequest
func NewHTLCAddRequest() *HTLCAddRequest {
return &HTLCAddRequest{}
}
//Serializes the item from the HTLCAddRequest struct
//Writes the data to w
func (c *HTLCAddRequest) Encode(w io.Writer, pver uint32) error {
err := writeElements(w,
c.ChannelID,
c.StagingID,
c.Expiry,
c.Amount,
c.NextHop,
c.ContractType,
c.RedemptionHashes,
c.Blob,
)
if err != nil {
return err
}
return nil
}
func (c *HTLCAddRequest) Command() uint32 {
return CmdHTLCAddRequest
}
func (c *HTLCAddRequest) MaxPayloadLength(uint32) uint32 {
//base size ~110, but blob can be variable.
//shouldn't be bigger than 8K though...
return 8192
}
//Makes sure the struct data is valid (e.g. no negatives or invalid pkscripts)
func (c *HTLCAddRequest) Validate() error {
if c.Amount < 0 {
//While fees can be negative, it's too confusing to allow
//negative payments. Maybe for some wallets, but not this one!
return fmt.Errorf("Amount paid cannot be negative.")
}
//We're good!
return nil
}
func (c *HTLCAddRequest) String() string {
var redemptionHashes string
for i, rh := range c.RedemptionHashes {
redemptionHashes += fmt.Sprintf("\n\tSlice\t%d\n", i)
redemptionHashes += fmt.Sprintf("\t\tRedemption Hash: %x\n", *rh)
}
return fmt.Sprintf("\n--- Begin HTLCAddRequest ---\n") +
fmt.Sprintf("ChannelID:\t%d\n", c.ChannelID) +
fmt.Sprintf("StagingID:\t%d\n", c.StagingID) +
fmt.Sprintf("Expiry:\t\t%d\n", c.Expiry) +
fmt.Sprintf("Amount\t\t%d\n", c.Amount) +
fmt.Sprintf("NextHop\t\t%x\n", c.NextHop) +
fmt.Sprintf("ContractType:\t%d (%b)\n", c.ContractType, c.ContractType) +
fmt.Sprintf("RedemptionHashes:") +
redemptionHashes +
fmt.Sprintf("Blob:\t\t\t\t%x\n", c.Blob) +
fmt.Sprintf("--- End HTLCAddRequest ---\n")
}

View File

@ -0,0 +1,46 @@
package lnwire
import (
"testing"
)
var (
//Need to to do this here
_ = copy(revocationHash[:], revocationHashBytes)
_ = copy(redemptionHash[:], redemptionHashBytes)
_ = copy(nextHop[:], nextHopBytes)
emptyRedemptionHashes = []*[20]byte{}
redemptionHashes = append(emptyRedemptionHashes, &redemptionHash)
htlcAddRequest = &HTLCAddRequest{
ChannelID: uint64(12345678),
StagingID: uint64(12345),
Expiry: uint32(144),
Amount: CreditsAmount(123456000),
NextHop: nextHop,
ContractType: uint8(17),
RedemptionHashes: redemptionHashes,
Blob: []byte{255, 0, 255, 0, 255, 0, 255, 0},
}
htlcAddRequestSerializedString = "0000000000bc614e000000000000303900000090075bca0094a9ded5a30fc5944cb1e2cbcd980f30616a14401100015b315ebabb0d8c0d94281caa2dfee69a1a00436e0008ff00ff00ff00ff00"
htlcAddRequestSerializedMessage = "0709110b000003e80000004d0000000000bc614e000000000000303900000090075bca0094a9ded5a30fc5944cb1e2cbcd980f30616a14401100015b315ebabb0d8c0d94281caa2dfee69a1a00436e0008ff00ff00ff00ff00"
)
func TestHTLCAddRequestEncodeDecode(t *testing.T) {
//All of these types being passed are of the message interface type
//Test serialization, runs: message.Encode(b, 0)
//Returns bytes
//Compares the expected serialized string from the original
s := SerializeTest(t, htlcAddRequest, htlcAddRequestSerializedString, filename)
//Test deserialization, runs: message.Decode(s, 0)
//Makes sure the deserialized struct is the same as the original
newMessage := NewHTLCAddRequest()
DeserializeTest(t, s, newMessage, htlcAddRequest)
//Test message using Message interface
//Serializes into buf: WriteMessage(buf, message, uint32(1), wire.TestNet3)
//Deserializes into msg: _, msg, _ , err := ReadMessage(buf, uint32(1), wire.TestNet3)
MessageSerializeDeserializeTest(t, htlcAddRequest, htlcAddRequestSerializedMessage)
}

View File

@ -0,0 +1,77 @@
package lnwire
import (
"fmt"
"io"
)
//Multiple Clearing Requests are possible by putting this inside an array of
//clearing requests
type HTLCSettleAccept struct {
//We can use a different data type for this if necessary...
ChannelID uint64
//ID of this request
StagingID uint64
}
func (c *HTLCSettleAccept) Decode(r io.Reader, pver uint32) error {
//ChannelID(8)
//StagingID(8)
//Expiry(4)
//Amount(4)
//NextHop(20)
//ContractType(1)
//RedemptionHashes (numOfHashes * 20 + numOfHashes)
//Blob(2+blobsize)
err := readElements(r,
&c.ChannelID,
&c.StagingID,
)
if err != nil {
return err
}
return nil
}
//Creates a new HTLCSettleAccept
func NewHTLCSettleAccept() *HTLCSettleAccept {
return &HTLCSettleAccept{}
}
//Serializes the item from the HTLCSettleAccept struct
//Writes the data to w
func (c *HTLCSettleAccept) Encode(w io.Writer, pver uint32) error {
err := writeElements(w,
c.ChannelID,
c.StagingID,
)
if err != nil {
return err
}
return nil
}
func (c *HTLCSettleAccept) Command() uint32 {
return CmdHTLCSettleAccept
}
func (c *HTLCSettleAccept) MaxPayloadLength(uint32) uint32 {
//16
return 16
}
//Makes sure the struct data is valid (e.g. no negatives or invalid pkscripts)
func (c *HTLCSettleAccept) Validate() error {
//We're good!
return nil
}
func (c *HTLCSettleAccept) String() string {
return fmt.Sprintf("\n--- Begin HTLCSettleAccept ---\n") +
fmt.Sprintf("ChannelID:\t%d\n", c.ChannelID) +
fmt.Sprintf("StagingID:\t%d\n", c.StagingID) +
fmt.Sprintf("--- End HTLCSettleAccept ---\n")
}

View File

@ -0,0 +1,32 @@
package lnwire
import (
"testing"
)
var (
htlcSettleAccept = &HTLCSettleAccept{
ChannelID: uint64(12345678),
StagingID: uint64(12345),
}
htlcSettleAcceptSerializedString = "0000000000bc614e0000000000003039"
htlcSettleAcceptSerializedMessage = "0709110b00000456000000100000000000bc614e0000000000003039"
)
func TestHTLCSettleAcceptEncodeDecode(t *testing.T) {
//All of these types being passed are of the message interface type
//Test serialization, runs: message.Encode(b, 0)
//Returns bytes
//Compares the expected serialized string from the original
s := SerializeTest(t, htlcSettleAccept, htlcSettleAcceptSerializedString, filename)
//Test deserialization, runs: message.Decode(s, 0)
//Makes sure the deserialized struct is the same as the original
newMessage := NewHTLCSettleAccept()
DeserializeTest(t, s, newMessage, htlcSettleAccept)
//Test message using Message interface
//Serializes into buf: WriteMessage(buf, message, uint32(1), wire.TestNet3)
//Deserializes into msg: _, msg, _ , err := ReadMessage(buf, uint32(1), wire.TestNet3)
MessageSerializeDeserializeTest(t, htlcSettleAccept, htlcSettleAcceptSerializedMessage)
}

View File

@ -0,0 +1,90 @@
package lnwire
import (
"fmt"
"io"
)
//Multiple Clearing Requests are possible by putting this inside an array of
//clearing requests
type HTLCSettleRequest struct {
//We can use a different data type for this if necessary...
ChannelID uint64
//ID of this request
StagingID uint64
//Redemption Proofs (R-Values)
RedemptionProofs []*[20]byte
}
func (c *HTLCSettleRequest) Decode(r io.Reader, pver uint32) error {
//ChannelID(8)
//StagingID(8)
//Expiry(4)
//Amount(4)
//NextHop(20)
//ContractType(1)
//RedemptionHashes (numOfHashes * 20 + numOfHashes)
//Blob(2+blobsize)
err := readElements(r,
&c.ChannelID,
&c.StagingID,
&c.RedemptionProofs,
)
if err != nil {
return err
}
return nil
}
//Creates a new HTLCSettleRequest
func NewHTLCSettleRequest() *HTLCSettleRequest {
return &HTLCSettleRequest{}
}
//Serializes the item from the HTLCSettleRequest struct
//Writes the data to w
func (c *HTLCSettleRequest) Encode(w io.Writer, pver uint32) error {
err := writeElements(w,
c.ChannelID,
c.StagingID,
c.RedemptionProofs,
)
if err != nil {
return err
}
return nil
}
func (c *HTLCSettleRequest) Command() uint32 {
return CmdHTLCSettleRequest
}
func (c *HTLCSettleRequest) MaxPayloadLength(uint32) uint32 {
//21*15+16
return 331
}
//Makes sure the struct data is valid (e.g. no negatives or invalid pkscripts)
func (c *HTLCSettleRequest) Validate() error {
//We're good!
return nil
}
func (c *HTLCSettleRequest) String() string {
var redemptionProofs string
for i, rh := range c.RedemptionProofs {
redemptionProofs += fmt.Sprintf("\n\tSlice\t%d\n", i)
redemptionProofs += fmt.Sprintf("\t\tRedemption Proof: %x\n", *rh)
}
return fmt.Sprintf("\n--- Begin HTLCSettleRequest ---\n") +
fmt.Sprintf("ChannelID:\t%d\n", c.ChannelID) +
fmt.Sprintf("StagingID:\t%d\n", c.StagingID) +
fmt.Sprintf("RedemptionHashes:") +
redemptionProofs +
fmt.Sprintf("--- End HTLCSettleRequest ---\n")
}

View File

@ -0,0 +1,38 @@
package lnwire
import (
"testing"
)
var (
//Need to to do this here
_ = copy(redemptionHash[:], redemptionHashBytes)
emptyRedemptionProofs = []*[20]byte{}
redemptionProofs = append(emptyRedemptionProofs, &redemptionHash)
htlcSettleRequest = &HTLCSettleRequest{
ChannelID: uint64(12345678),
StagingID: uint64(12345),
RedemptionProofs: redemptionProofs,
}
htlcSettleRequestSerializedString = "0000000000bc614e000000000000303900015b315ebabb0d8c0d94281caa2dfee69a1a00436e"
htlcSettleRequestSerializedMessage = "0709110b0000044c000000260000000000bc614e000000000000303900015b315ebabb0d8c0d94281caa2dfee69a1a00436e"
)
func TestHTLCSettleRequestEncodeDecode(t *testing.T) {
//All of these types being passed are of the message interface type
//Test serialization, runs: message.Encode(b, 0)
//Returns bytes
//Compares the expected serialized string from the original
s := SerializeTest(t, htlcSettleRequest, htlcSettleRequestSerializedString, filename)
//Test deserialization, runs: message.Decode(s, 0)
//Makes sure the deserialized struct is the same as the original
newMessage := NewHTLCSettleRequest()
DeserializeTest(t, s, newMessage, htlcSettleRequest)
//Test message using Message interface
//Serializes into buf: WriteMessage(buf, message, uint32(1), wire.TestNet3)
//Deserializes into msg: _, msg, _ , err := ReadMessage(buf, uint32(1), wire.TestNet3)
MessageSerializeDeserializeTest(t, htlcSettleRequest, htlcSettleRequestSerializedMessage)
}

View File

@ -0,0 +1,71 @@
package lnwire
import (
"fmt"
"io"
)
//Multiple Clearing Requests are possible by putting this inside an array of
//clearing requests
type HTLCTimeoutAccept struct {
//We can use a different data type for this if necessary...
ChannelID uint64
//ID of this request
StagingID uint64
}
func (c *HTLCTimeoutAccept) Decode(r io.Reader, pver uint32) error {
//ChannelID(8)
//StagingID(8)
err := readElements(r,
&c.ChannelID,
&c.StagingID,
)
if err != nil {
return err
}
return nil
}
//Creates a new HTLCTimeoutAccept
func NewHTLCTimeoutAccept() *HTLCTimeoutAccept {
return &HTLCTimeoutAccept{}
}
//Serializes the item from the HTLCTimeoutAccept struct
//Writes the data to w
func (c *HTLCTimeoutAccept) Encode(w io.Writer, pver uint32) error {
err := writeElements(w,
c.ChannelID,
c.StagingID,
)
if err != nil {
return err
}
return nil
}
func (c *HTLCTimeoutAccept) Command() uint32 {
return CmdHTLCTimeoutAccept
}
func (c *HTLCTimeoutAccept) MaxPayloadLength(uint32) uint32 {
//16
return 16
}
//Makes sure the struct data is valid (e.g. no negatives or invalid pkscripts)
func (c *HTLCTimeoutAccept) Validate() error {
//We're good!
return nil
}
func (c *HTLCTimeoutAccept) String() string {
return fmt.Sprintf("\n--- Begin HTLCTimeoutAccept ---\n") +
fmt.Sprintf("ChannelID:\t%d\n", c.ChannelID) +
fmt.Sprintf("StagingID:\t%d\n", c.StagingID) +
fmt.Sprintf("--- End HTLCTimeoutAccept ---\n")
}

View File

@ -0,0 +1,32 @@
package lnwire
import (
"testing"
)
var (
htlcTimeoutAccept = &HTLCTimeoutAccept{
ChannelID: uint64(12345678),
StagingID: uint64(12345),
}
htlcTimeoutAcceptSerializedString = "0000000000bc614e0000000000003039"
htlcTimeoutAcceptSerializedMessage = "0709110b0000051e000000100000000000bc614e0000000000003039"
)
func TestHTLCTimeoutAcceptEncodeDecode(t *testing.T) {
//All of these types being passed are of the message interface type
//Test serialization, runs: message.Encode(b, 0)
//Returns bytes
//Compares the expected serialized string from the original
s := SerializeTest(t, htlcTimeoutAccept, htlcTimeoutAcceptSerializedString, filename)
//Test deserialization, runs: message.Decode(s, 0)
//Makes sure the deserialized struct is the same as the original
newMessage := NewHTLCTimeoutAccept()
DeserializeTest(t, s, newMessage, htlcTimeoutAccept)
//Test message using Message interface
//Serializes into buf: WriteMessage(buf, message, uint32(1), wire.TestNet3)
//Deserializes into msg: _, msg, _ , err := ReadMessage(buf, uint32(1), wire.TestNet3)
MessageSerializeDeserializeTest(t, htlcTimeoutAccept, htlcTimeoutAcceptSerializedMessage)
}

View File

@ -0,0 +1,71 @@
package lnwire
import (
"fmt"
"io"
)
//Multiple Clearing Requests are possible by putting this inside an array of
//clearing requests
type HTLCTimeoutRequest struct {
//We can use a different data type for this if necessary...
ChannelID uint64
//ID of this request
StagingID uint64
}
func (c *HTLCTimeoutRequest) Decode(r io.Reader, pver uint32) error {
//ChannelID(8)
//StagingID(8)
err := readElements(r,
&c.ChannelID,
&c.StagingID,
)
if err != nil {
return err
}
return nil
}
//Creates a new HTLCTimeoutRequest
func NewHTLCTimeoutRequest() *HTLCTimeoutRequest {
return &HTLCTimeoutRequest{}
}
//Serializes the item from the HTLCTimeoutRequest struct
//Writes the data to w
func (c *HTLCTimeoutRequest) Encode(w io.Writer, pver uint32) error {
err := writeElements(w,
c.ChannelID,
c.StagingID,
)
if err != nil {
return err
}
return nil
}
func (c *HTLCTimeoutRequest) Command() uint32 {
return CmdHTLCTimeoutRequest
}
func (c *HTLCTimeoutRequest) MaxPayloadLength(uint32) uint32 {
//16
return 16
}
//Makes sure the struct data is valid (e.g. no negatives or invalid pkscripts)
func (c *HTLCTimeoutRequest) Validate() error {
//We're good!
return nil
}
func (c *HTLCTimeoutRequest) String() string {
return fmt.Sprintf("\n--- Begin HTLCTimeoutRequest ---\n") +
fmt.Sprintf("ChannelID:\t%d\n", c.ChannelID) +
fmt.Sprintf("StagingID:\t%d\n", c.StagingID) +
fmt.Sprintf("--- End HTLCTimeoutRequest ---\n")
}

View File

@ -0,0 +1,32 @@
package lnwire
import (
"testing"
)
var (
htlcTimeoutRequest = &HTLCTimeoutRequest{
ChannelID: uint64(12345678),
StagingID: uint64(12345),
}
htlcTimeoutRequestSerializedString = "0000000000bc614e0000000000003039"
htlcTimeoutRequestSerializedMessage = "0709110b00000514000000100000000000bc614e0000000000003039"
)
func TestHTLCTimeoutRequestEncodeDecode(t *testing.T) {
//All of these types being passed are of the message interface type
//Test serialization, runs: message.Encode(b, 0)
//Returns bytes
//Compares the expected serialized string from the original
s := SerializeTest(t, htlcTimeoutRequest, htlcTimeoutRequestSerializedString, filename)
//Test deserialization, runs: message.Decode(s, 0)
//Makes sure the deserialized struct is the same as the original
newMessage := NewHTLCTimeoutRequest()
DeserializeTest(t, s, newMessage, htlcTimeoutRequest)
//Test message using Message interface
//Serializes into buf: WriteMessage(buf, message, uint32(1), wire.TestNet3)
//Deserializes into msg: _, msg, _ , err := ReadMessage(buf, uint32(1), wire.TestNet3)
MessageSerializeDeserializeTest(t, htlcTimeoutRequest, htlcTimeoutRequestSerializedMessage)
}

View File

@ -11,11 +11,25 @@ import (
"io/ioutil"
)
var MAX_SLICE_LENGTH = 65535
//Actual pkScript, not redeemScript
type PkScript []byte
//Subsatoshi amount
type MicroSatoshi int32
//Subsatoshi amount (Micro-Satoshi, 1/1000th)
//Should be a signed int to account for negative fees
//
//"In any science-fiction movie, anywhere in the galaxy, currency is referred
//to as 'credits.'"
// --Sam Humphries. Ebert, Roger (1999). Ebert's bigger little movie
// glossary. Andrews McMeel. p. 172.
//
//https://en.wikipedia.org/wiki/List_of_fictional_currencies
//https://en.wikipedia.org/wiki/Fictional_currency#Trends_in_the_use_of_fictional_currencies
//http://tvtropes.org/pmwiki/pmwiki.php/Main/WeWillSpendCreditsInTheFuture
type CreditsAmount int32 //Credits (XCB, accountants should use XCB :^)
//US Display format: 1 BTC = 100,000,000'000 XCB
//Or in BTC = 1.00000000'000
//Writes the big endian representation of element
//Unified function to call when writing different types
@ -32,6 +46,20 @@ func writeElement(w io.Writer, element interface{}) error {
return err
}
return nil
case uint16:
var b [2]byte
binary.BigEndian.PutUint16(b[:], uint16(e))
_, err = w.Write(b[:])
if err != nil {
return err
}
return nil
case CreditsAmount:
err = binary.Write(w, binary.BigEndian, int32(e))
if err != nil {
return err
}
return nil
case uint32:
var b [4]byte
binary.BigEndian.PutUint32(b[:], uint32(e))
@ -107,6 +135,26 @@ func writeElement(w io.Writer, element interface{}) error {
return err
}
return nil
case []*[20]byte:
//Get size of slice and dump in slice
sliceSize := len(e)
err = writeElement(w, uint16(sliceSize))
if err != nil {
return err
}
//Write in each sequentially
for _, element := range e {
err = writeElement(w, &element)
if err != nil {
return err
}
}
return nil
case **[20]byte:
_, err = w.Write((*e)[:])
if err != nil {
return err
}
case [20]byte:
_, err = w.Write(e[:])
if err != nil {
@ -121,6 +169,22 @@ func writeElement(w io.Writer, element interface{}) error {
return err
}
return nil
case []byte:
sliceLength := len(e)
if sliceLength > MAX_SLICE_LENGTH {
return fmt.Errorf("Slice length too long!")
}
//Write the size
err = writeElement(w, uint16(sliceLength))
if err != nil {
return err
}
//Write the data
_, err = w.Write(e)
if err != nil {
return err
}
return nil
case PkScript:
scriptLength := len(e)
//Make sure it's P2PKH or P2SH size or less
@ -202,6 +266,22 @@ func readElement(r io.Reader, element interface{}) error {
}
*e = b[0]
return nil
case *uint16:
var b [2]byte
_, err = io.ReadFull(r, b[:])
if err != nil {
return err
}
*e = binary.BigEndian.Uint16(b[:])
return nil
case *CreditsAmount:
var b [4]byte
_, err = io.ReadFull(r, b[:])
if err != nil {
return err
}
*e = CreditsAmount(int32(binary.BigEndian.Uint32(b[:])))
return nil
case *uint32:
var b [4]byte
_, err = io.ReadFull(r, b[:])
@ -294,6 +374,25 @@ func readElement(r io.Reader, element interface{}) error {
}
*e = &*btcecSig
return nil
case *[]*[20]byte:
//How many to read
var sliceSize uint16
err = readElement(r, &sliceSize)
if err != nil {
return err
}
var data []*[20]byte
//Append the actual
for i := uint16(0); i < sliceSize; i++ {
var element [20]byte
err = readElement(r, &element)
if err != nil {
return err
}
data = append(data, &element)
}
*e = data
return nil
case *[20]byte:
_, err = io.ReadFull(r, e[:])
if err != nil {
@ -308,6 +407,30 @@ func readElement(r io.Reader, element interface{}) error {
}
*e = wire.BitcoinNet(binary.BigEndian.Uint32(b[:]))
return nil
case *[]byte:
//Get the blob length first
var blobLength uint16
err = readElement(r, &blobLength)
if err != nil {
return err
}
//Shouldn't need to do this, since it's uint16, but we
//might have a different value for MAX_SLICE_LENGTH...
if int(blobLength) > MAX_SLICE_LENGTH {
return fmt.Errorf("Slice length too long!")
}
//Read the slice length
l := io.LimitReader(r, int64(blobLength))
*e, err = ioutil.ReadAll(l)
if err != nil {
return err
}
if len(*e) != int(blobLength) {
return fmt.Errorf("EOF: Slice length mismatch.")
}
return nil
case *PkScript:
//Get the script length first
var scriptLength uint8
@ -323,12 +446,12 @@ func readElement(r io.Reader, element interface{}) error {
//Read the script length
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
}
if len(*e) != int(scriptLength) {
return fmt.Errorf("EOF: Signature length mismatch.")
}
return nil
case *[]*wire.TxIn:
//Read the size (1-byte number of txins)

View File

@ -24,8 +24,17 @@ var (
revocationHashBytes, _ = hex.DecodeString("4132b6b48371f7b022a16eacb9b2b0ebee134d41")
revocationHash [20]byte
//preimage: "hello world"
redemptionHashBytes, _ = hex.DecodeString("5b315ebabb0d8c0d94281caa2dfee69a1a00436e")
redemptionHash [20]byte
//preimage: "next hop"
nextHopBytes, _ = hex.DecodeString("94a9ded5a30fc5944cb1e2cbcd980f30616a1440")
nextHop [20]byte
privKeyBytes, _ = hex.DecodeString("9fa1d55217f57019a3c37f49465896b15836f54cb8ef6963870a52926420a2dd")
privKey, pubKey = btcec.PrivKeyFromBytes(btcec.S256(), privKeyBytes)
address = pubKey
// Delivery PkScript
//Privkey: f2c00ead9cbcfec63098dc0a5f152c0165aff40a2ab92feb4e24869a284c32a7

View File

@ -39,7 +39,24 @@ const (
CmdCloseRequest = uint32(300)
CmdCloseComplete = uint32(310)
//TODO Renumber to 1100
//HTLC payment
CmdHTLCAddRequest = uint32(1000)
CmdHTLCAddAccept = uint32(1010)
CmdHTLCAddReject = uint32(1020)
//TODO Renumber to 1200
//HTLC settlement
CmdHTLCSettleRequest = uint32(1100)
CmdHTLCSettleAccept = uint32(1110)
//HTLC timeout
CmdHTLCTimeoutRequest = uint32(1300)
CmdHTLCTimeoutAccept = uint32(1310)
//Commitments
CmdCommitSignature = uint32(2000)
CmdCommitRevocation = uint32(2010)
)
//Every message has these functions:
@ -68,6 +85,24 @@ func makeEmptyMessage(command uint32) (Message, error) {
msg = &CloseRequest{}
case CmdCloseComplete:
msg = &CloseComplete{}
case CmdHTLCAddRequest:
msg = &HTLCAddRequest{}
case CmdHTLCAddAccept:
msg = &HTLCAddAccept{}
case CmdHTLCAddReject:
msg = &HTLCAddReject{}
case CmdHTLCSettleRequest:
msg = &HTLCSettleRequest{}
case CmdHTLCSettleAccept:
msg = &HTLCSettleAccept{}
case CmdHTLCTimeoutRequest:
msg = &HTLCTimeoutRequest{}
case CmdHTLCTimeoutAccept:
msg = &HTLCTimeoutAccept{}
case CmdCommitSignature:
msg = &CommitSignature{}
case CmdCommitRevocation:
msg = &CommitRevocation{}
default:
return nil, fmt.Errorf("unhandled command [%d]", command)
}