diff --git a/.gitignore b/.gitignore index 47b35e36..1a426da3 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,6 @@ cmd/lncli/lncli cmd/lnshell/lnshell test_wal/* + +# vim +**.swp diff --git a/lnwire/FLOW.md b/lnwire/README.md similarity index 100% rename from lnwire/FLOW.md rename to lnwire/README.md diff --git a/lnwire/commit_revocation.go b/lnwire/commit_revocation.go new file mode 100644 index 00000000..fb72e0e4 --- /dev/null +++ b/lnwire/commit_revocation.go @@ -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") +} diff --git a/lnwire/commit_revocation_test.go b/lnwire/commit_revocation_test.go new file mode 100644 index 00000000..33909f47 --- /dev/null +++ b/lnwire/commit_revocation_test.go @@ -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) +} diff --git a/lnwire/commit_signature.go b/lnwire/commit_signature.go new file mode 100644 index 00000000..a43ef4bf --- /dev/null +++ b/lnwire/commit_signature.go @@ -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") +} diff --git a/lnwire/commit_signature_test.go b/lnwire/commit_signature_test.go new file mode 100644 index 00000000..f6d953c0 --- /dev/null +++ b/lnwire/commit_signature_test.go @@ -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) +} diff --git a/lnwire/htlc_addaccept.go b/lnwire/htlc_addaccept.go new file mode 100644 index 00000000..e40a4372 --- /dev/null +++ b/lnwire/htlc_addaccept.go @@ -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") +} diff --git a/lnwire/htlc_addaccept_test.go b/lnwire/htlc_addaccept_test.go new file mode 100644 index 00000000..6428440d --- /dev/null +++ b/lnwire/htlc_addaccept_test.go @@ -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) +} diff --git a/lnwire/htlc_addreject.go b/lnwire/htlc_addreject.go new file mode 100644 index 00000000..1cc70c38 --- /dev/null +++ b/lnwire/htlc_addreject.go @@ -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") +} diff --git a/lnwire/htlc_addreject_test.go b/lnwire/htlc_addreject_test.go new file mode 100644 index 00000000..7a587e1e --- /dev/null +++ b/lnwire/htlc_addreject_test.go @@ -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) +} diff --git a/lnwire/htlc_addrequest.go b/lnwire/htlc_addrequest.go new file mode 100644 index 00000000..8c8b5329 --- /dev/null +++ b/lnwire/htlc_addrequest.go @@ -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") +} diff --git a/lnwire/htlc_addrequest_test.go b/lnwire/htlc_addrequest_test.go new file mode 100644 index 00000000..047b0e92 --- /dev/null +++ b/lnwire/htlc_addrequest_test.go @@ -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) +} diff --git a/lnwire/htlc_settleaccept.go b/lnwire/htlc_settleaccept.go new file mode 100644 index 00000000..186128c9 --- /dev/null +++ b/lnwire/htlc_settleaccept.go @@ -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") +} diff --git a/lnwire/htlc_settleaccept_test.go b/lnwire/htlc_settleaccept_test.go new file mode 100644 index 00000000..691c149d --- /dev/null +++ b/lnwire/htlc_settleaccept_test.go @@ -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) +} diff --git a/lnwire/htlc_settlerequest.go b/lnwire/htlc_settlerequest.go new file mode 100644 index 00000000..c103c1b4 --- /dev/null +++ b/lnwire/htlc_settlerequest.go @@ -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") +} diff --git a/lnwire/htlc_settlerequest_test.go b/lnwire/htlc_settlerequest_test.go new file mode 100644 index 00000000..6ac79ab6 --- /dev/null +++ b/lnwire/htlc_settlerequest_test.go @@ -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) +} diff --git a/lnwire/htlc_timeoutaccept.go b/lnwire/htlc_timeoutaccept.go new file mode 100644 index 00000000..d7ab0f2d --- /dev/null +++ b/lnwire/htlc_timeoutaccept.go @@ -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") +} diff --git a/lnwire/htlc_timeoutaccept_test.go b/lnwire/htlc_timeoutaccept_test.go new file mode 100644 index 00000000..8f189f3a --- /dev/null +++ b/lnwire/htlc_timeoutaccept_test.go @@ -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) +} diff --git a/lnwire/htlc_timeoutrequest.go b/lnwire/htlc_timeoutrequest.go new file mode 100644 index 00000000..1d950410 --- /dev/null +++ b/lnwire/htlc_timeoutrequest.go @@ -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") +} diff --git a/lnwire/htlc_timeoutrequest_test.go b/lnwire/htlc_timeoutrequest_test.go new file mode 100644 index 00000000..fc61d5bb --- /dev/null +++ b/lnwire/htlc_timeoutrequest_test.go @@ -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) +} diff --git a/lnwire/lnwire.go b/lnwire/lnwire.go index 81400c94..49583545 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -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) diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index 13221161..3abd2592 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -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 diff --git a/lnwire/message.go b/lnwire/message.go index c38db2e3..4413ac45 100644 --- a/lnwire/message.go +++ b/lnwire/message.go @@ -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) }