lightning-onion 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/.ruby-version +1 -0
  4. data/README.md +47 -3
  5. data/lib/lightning/onion.rb +6 -1
  6. data/lib/lightning/onion/error_packet.rb +13 -0
  7. data/lib/lightning/onion/failure_messages.rb +181 -0
  8. data/lib/lightning/onion/failure_messages/amount_below_minimum.rb +18 -0
  9. data/lib/lightning/onion/failure_messages/channel_disabled.rb +18 -0
  10. data/lib/lightning/onion/failure_messages/expiry_too_far.rb +17 -0
  11. data/lib/lightning/onion/failure_messages/expiry_too_soon.rb +18 -0
  12. data/lib/lightning/onion/failure_messages/fee_insufficient.rb +18 -0
  13. data/lib/lightning/onion/failure_messages/final_expiry_too_soon.rb +17 -0
  14. data/lib/lightning/onion/failure_messages/final_incorrect_cltv_expiry.rb +17 -0
  15. data/lib/lightning/onion/failure_messages/final_incorrect_htlc_amount.rb +17 -0
  16. data/lib/lightning/onion/failure_messages/incorrect_cltv_expiry.rb +18 -0
  17. data/lib/lightning/onion/failure_messages/incorrect_payment_amount.rb +17 -0
  18. data/lib/lightning/onion/failure_messages/invalid_onion_hmac.rb +18 -0
  19. data/lib/lightning/onion/failure_messages/invalid_onion_key.rb +18 -0
  20. data/lib/lightning/onion/failure_messages/invalid_onion_version.rb +18 -0
  21. data/lib/lightning/onion/failure_messages/invalid_realm.rb +17 -0
  22. data/lib/lightning/onion/failure_messages/permanent_channel_failure.rb +17 -0
  23. data/lib/lightning/onion/failure_messages/permanent_node_failure.rb +17 -0
  24. data/lib/lightning/onion/failure_messages/required_channel_feature_missing.rb +17 -0
  25. data/lib/lightning/onion/failure_messages/required_node_feature_missing.rb +17 -0
  26. data/lib/lightning/onion/failure_messages/temporary_channel_failure.rb +18 -0
  27. data/lib/lightning/onion/failure_messages/temporary_node_failure.rb +17 -0
  28. data/lib/lightning/onion/failure_messages/unknown_next_peer.rb +17 -0
  29. data/lib/lightning/onion/failure_messages/unknown_payment_hash.rb +17 -0
  30. data/lib/lightning/onion/sphinx.rb +94 -20
  31. data/lib/lightning/onion/version.rb +1 -1
  32. data/lib/lightning/utils/string.rb +14 -0
  33. data/lightningrb-onion.gemspec +1 -0
  34. metadata +42 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1a47804ce3e79e397833b1e477eda7bf1531a025
4
- data.tar.gz: 1b790426dada286bf65e320870b0f8f65a95b758
3
+ metadata.gz: '079f7534c2a4cbce4c4f7c272b498f08433d2d09'
4
+ data.tar.gz: d99082b353d85e7c9a32bb7bb3c08ffaf614c094
5
5
  SHA512:
6
- metadata.gz: d993a816fed1fb15c3fc20e1b306397766289ad125e2bb08dd8f3eff7444e27cc0e24a6ff7efc04bbe193d79d95d641cefa4c7e0230b46b0a035858234206bfe
7
- data.tar.gz: 879b2eb37d5c7c87c026021c6a6b14cab285e876edd477dd13c4ac007ad24aeb5f2e6fafd3213eb341146b1d6a5e35b8115596d18f1f54a212d6b5f7687cafdc
6
+ metadata.gz: 4925aecf07e1782b9316b676e2a67c8e669a4fb528a3e6235f397d7e185a4c371d4be75cbd100af07f3668b8276719a1663028c72ba49c2e7987032f6781f8d3
7
+ data.tar.gz: 256905de4a28f346b9a49172fd0d2dac789230e2096db59e9f9a6328104a0994d8bc51d8d4d95a74df3619240e994fd1a208f29b098fb7dfb6e1fb9f5dc95e5e
@@ -10,6 +10,12 @@ Metrics/LineLength:
10
10
  Metrics/MethodLength:
11
11
  Max: 30
12
12
 
13
+ Metrics/ModuleLength:
14
+ Max: 500
15
+
16
+ Metrics/ParameterLists:
17
+ Max: 7
18
+
13
19
  Style/Documentation:
14
20
  Enabled: false
15
21
 
@@ -0,0 +1 @@
1
+ 2.4.1
data/README.md CHANGED
@@ -9,7 +9,7 @@ TODO: Delete this and the text above, and describe your gem
9
9
  Add this line to your application's Gemfile:
10
10
 
11
11
  ```ruby
12
- gem 'lightningrb-onion'
12
+ gem 'lightning-onion'
13
13
  ```
14
14
 
15
15
  And then execute:
@@ -18,11 +18,55 @@ And then execute:
18
18
 
19
19
  Or install it yourself as:
20
20
 
21
- $ gem install lightningrb-onion
21
+ $ gem install lightning-onion
22
22
 
23
23
  ## Usage
24
24
 
25
- TODO: Write usage instructions here
25
+ ### Build Onion Packet
26
+
27
+ $ ./bin/console
28
+ irb(main):001:0> irb(main):002:0> session_key = '4141414141414141414141414141414141414141414141414141414141414141'
29
+ irb(main):003:0> public_keys = [
30
+ irb(main):004:1* '02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619',
31
+ irb(main):005:1* '0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c',
32
+ irb(main):006:1* '027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007',
33
+ irb(main):007:1* '032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991',
34
+ irb(main):008:1* '02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145'
35
+ irb(main):009:1> ]
36
+ irb(main):010:0> payloads = [
37
+ irb(main):011:1* '000000000000000000000000000000000000000000000000000000000000000000',
38
+ irb(main):012:1* '000101010101010101000000010000000100000000000000000000000000000000',
39
+ irb(main):013:1* '000202020202020202000000020000000200000000000000000000000000000000',
40
+ irb(main):014:1* '000303030303030303000000030000000300000000000000000000000000000000',
41
+ irb(main):015:1* '000404040404040404000000040000000400000000000000000000000000000000'
42
+ irb(main):016:1> ]
43
+ irb(main):017:0> associated_data = '4242424242424242424242424242424242424242424242424242424242424242'
44
+ irb(main):018:0> onion, secrets = Lightning::Onion::Sphinx.make_packet(session_key, public_keys, payloads, associated_data)
45
+
46
+ ### Parse Onion Packet
47
+
48
+ irb(main):019:0> private_keys = [
49
+ irb(main):020:1* '4141414141414141414141414141414141414141414141414141414141414141',
50
+ irb(main):021:1* '4242424242424242424242424242424242424242424242424242424242424242',
51
+ irb(main):022:1* '4343434343434343434343434343434343434343434343434343434343434343',
52
+ irb(main):023:1* '4444444444444444444444444444444444444444444444444444444444444444',
53
+ irb(main):024:1* '4545454545454545454545454545454545454545454545454545454545454545'
54
+ irb(main):025:1> ]
55
+ irb(main):028:0> payload0, next_packet0, = Lightning::Onion::Sphinx.parse(private_keys[0], onion.to_payload)
56
+ irb(main):029:0> payload1, next_packet1, = Lightning::Onion::Sphinx.parse(private_keys[1], next_packet0.to_payload)
57
+ irb(main):030:0> payload2, next_packet2, = Lightning::Onion::Sphinx.parse(private_keys[2], next_packet1.to_payload)
58
+ irb(main):031:0> payload3, next_packet3, = Lightning::Onion::Sphinx.parse(private_keys[3], next_packet2.to_payload)
59
+ irb(main):032:0> payload4, next_packet4, = Lightning::Onion::Sphinx.parse(private_keys[4], next_packet3.to_payload)
60
+ irb(main):035:0> payload0.bth
61
+ => "000000000000000000000000000000000000000000000000000000000000000000"
62
+ irb(main):036:0> payload1.bth
63
+ => "000101010101010101000000010000000100000000000000000000000000000000"
64
+ irb(main):037:0> payload2.bth
65
+ => "000202020202020202000000020000000200000000000000000000000000000000"
66
+ irb(main):038:0> payload3.bth
67
+ => "000303030303030303000000030000000300000000000000000000000000000000"
68
+ irb(main):039:0> payload4.bth
69
+ => "000404040404040404000000040000000400000000000000000000000000000000"
26
70
 
27
71
  ## Development
28
72
 
@@ -2,12 +2,17 @@
2
2
 
3
3
  require 'lightning/onion/version'
4
4
 
5
- require 'rbnacl'
5
+ require 'algebrick'
6
6
  require 'bitcoin'
7
+ require 'rbnacl'
8
+
9
+ require 'lightning/utils/string'
7
10
 
8
11
  module Lightning
9
12
  module Onion
10
13
  autoload :ChaCha20, 'lightning/onion/chacha20'
14
+ autoload :ErrorPacket, 'lightning/onion/error_packet'
15
+ autoload :FailureMessages, 'lightning/onion/failure_messages'
11
16
  autoload :HopData, 'lightning/onion/hop_data'
12
17
  autoload :PerHop, 'lightning/onion/per_hop'
13
18
  autoload :Packet, 'lightning/onion/packet'
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ class ErrorPacket
6
+ attr_accessor :onion_node, :failure_message
7
+ def initialize(onion_node, failure_message)
8
+ @onion_node = onion_node
9
+ @failure_message = failure_message
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ # unparsable onion encrypted by sending peer
7
+ BADONION = 0x8000
8
+
9
+ # permanent failure (otherwise transient)
10
+ PERM = 0x4000
11
+
12
+ # node failure (otherwise channel)
13
+ NODE = 0x2000
14
+
15
+ # new channel update enclosed
16
+ UPDATE = 0x1000
17
+
18
+ TYPES = {
19
+ invalid_realm: PERM | 1,
20
+ temporary_node_failure: NODE | 2,
21
+ permanent_node_failure: PERM | NODE | 2,
22
+ required_node_feature_missing: PERM | NODE | 3,
23
+ invalid_onion_version: BADONION | PERM | 4,
24
+ invalid_onion_hmac: BADONION | PERM | 5,
25
+ invalid_onion_key: BADONION | PERM | 6,
26
+ temporary_channel_failure: UPDATE | 7,
27
+ permanent_channel_failure: PERM | 8,
28
+ required_channel_feature_missing: PERM | 9,
29
+ unknown_next_peer: PERM | 10,
30
+ amount_below_minimum: UPDATE | 11,
31
+ fee_insufficient: UPDATE | 12,
32
+ incorrect_cltv_expiry: UPDATE | 13,
33
+ expiry_too_soon: UPDATE | 14,
34
+ unknown_payment_hash: PERM | 15,
35
+ incorrect_payment_amount: PERM | 16,
36
+ final_expiry_too_soon: 17,
37
+ final_incorrect_cltv_expiry: 18,
38
+ final_incorrect_htlc_amount: 19,
39
+ channel_disabled: 20,
40
+ expiry_too_far: 21
41
+ }.freeze
42
+
43
+ FailureMessage = Algebrick.type do
44
+ InvalidRealm = type do
45
+ fields! type_code: Numeric
46
+ end
47
+ TemporaryNodeFailure = type do
48
+ fields! type_code: Numeric
49
+ end
50
+ PermanentNodeFailure = type do
51
+ fields! type_code: Numeric
52
+ end
53
+ RequiredNodeFeatureMissing = type do
54
+ fields! type_code: Numeric
55
+ end
56
+ InvalidOnionVersion = type do
57
+ fields! type_code: Numeric,
58
+ sha256_of_onion: String
59
+ end
60
+ InvalidOnionHmac = type do
61
+ fields! type_code: Numeric,
62
+ sha256_of_onion: String
63
+ end
64
+ InvalidOnionKey = type do
65
+ fields! type_code: Numeric,
66
+ sha256_of_onion: String
67
+ end
68
+ TemporaryChannelFailure = type do
69
+ fields! type_code: Numeric,
70
+ channel_update: String
71
+ end
72
+ PermanentChannelFailure = type do
73
+ fields! type_code: Numeric
74
+ end
75
+ RequiredChannelFeatureMissing = type do
76
+ fields! type_code: Numeric
77
+ end
78
+ UnknownNextPeer = type do
79
+ fields! type_code: Numeric
80
+ end
81
+ AmountBelowMinimum = type do
82
+ fields! type_code: Numeric,
83
+ htlc_msat: Numeric,
84
+ channel_update: String
85
+ end
86
+ FeeInsufficient = type do
87
+ fields! type_code: Numeric,
88
+ htlc_msat: Numeric,
89
+ channel_update: String
90
+ end
91
+ IncorrectCltvExpiry = type do
92
+ fields! type_code: Numeric,
93
+ cltv_expiry: Numeric,
94
+ channel_update: String
95
+ end
96
+ ExpiryTooSoon = type do
97
+ fields! type_code: Numeric,
98
+ channel_update: String
99
+ end
100
+ UnknownPaymentHash = type do
101
+ fields! type_code: Numeric
102
+ end
103
+ IncorrectPaymentAmount = type do
104
+ fields! type_code: Numeric
105
+ end
106
+ FinalExpiryTooSoon = type do
107
+ fields! type_code: Numeric
108
+ end
109
+ FinalIncorrectCltvExpiry = type do
110
+ fields! type_code: Numeric,
111
+ cltv_expiry: Numeric
112
+ end
113
+ FinalIncorrectHtlcAmount = type do
114
+ fields! type_code: Numeric,
115
+ incoming_htlc_amt: Numeric
116
+ end
117
+ ChannelDisabled = type do
118
+ fields! type_code: Numeric,
119
+ flags: String,
120
+ channel_update: String
121
+ end
122
+ ExpiryTooFar = type do
123
+ fields! type_code: Numeric
124
+ end
125
+ variants InvalidRealm,
126
+ TemporaryNodeFailure,
127
+ PermanentNodeFailure,
128
+ RequiredNodeFeatureMissing,
129
+ InvalidOnionVersion,
130
+ InvalidOnionHmac,
131
+ InvalidOnionKey,
132
+ TemporaryChannelFailure,
133
+ PermanentChannelFailure,
134
+ RequiredChannelFeatureMissing,
135
+ UnknownNextPeer,
136
+ AmountBelowMinimum,
137
+ FeeInsufficient,
138
+ IncorrectCltvExpiry,
139
+ ExpiryTooSoon,
140
+ UnknownPaymentHash,
141
+ IncorrectPaymentAmount,
142
+ FinalExpiryTooSoon,
143
+ FinalIncorrectCltvExpiry,
144
+ FinalIncorrectHtlcAmount,
145
+ ChannelDisabled,
146
+ ExpiryTooFar
147
+ end
148
+
149
+ def self.load(payload)
150
+ type, = payload.unpack('na*')
151
+ message_class = FailureMessage.variants.find do |t|
152
+ TYPES[t.name.split('::').last.snake.to_sym] == type
153
+ end
154
+ message_class.load(payload)
155
+ end
156
+
157
+ require 'lightning/onion/failure_messages/invalid_realm'
158
+ require 'lightning/onion/failure_messages/temporary_node_failure'
159
+ require 'lightning/onion/failure_messages/permanent_node_failure'
160
+ require 'lightning/onion/failure_messages/required_node_feature_missing'
161
+ require 'lightning/onion/failure_messages/invalid_onion_version'
162
+ require 'lightning/onion/failure_messages/invalid_onion_hmac'
163
+ require 'lightning/onion/failure_messages/invalid_onion_key'
164
+ require 'lightning/onion/failure_messages/temporary_channel_failure'
165
+ require 'lightning/onion/failure_messages/permanent_channel_failure'
166
+ require 'lightning/onion/failure_messages/required_channel_feature_missing'
167
+ require 'lightning/onion/failure_messages/unknown_next_peer'
168
+ require 'lightning/onion/failure_messages/amount_below_minimum'
169
+ require 'lightning/onion/failure_messages/fee_insufficient'
170
+ require 'lightning/onion/failure_messages/incorrect_cltv_expiry'
171
+ require 'lightning/onion/failure_messages/expiry_too_soon'
172
+ require 'lightning/onion/failure_messages/unknown_payment_hash'
173
+ require 'lightning/onion/failure_messages/incorrect_payment_amount'
174
+ require 'lightning/onion/failure_messages/final_expiry_too_soon'
175
+ require 'lightning/onion/failure_messages/final_incorrect_cltv_expiry'
176
+ require 'lightning/onion/failure_messages/final_incorrect_htlc_amount'
177
+ require 'lightning/onion/failure_messages/channel_disabled'
178
+ require 'lightning/onion/failure_messages/expiry_too_far'
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module AmountBelowMinimum
7
+ def to_payload
8
+ [type_code, htlc_msat, channel_update.htb.bytesize].pack('nq>n') + channel_update.htb
9
+ end
10
+
11
+ def self.load(payload)
12
+ type_code, htlc_msat, len, rest = payload.unpack('nq>nH*')
13
+ new(type_code, htlc_msat, rest[0..len * 2])
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module ChannelDisabled
7
+ def to_payload
8
+ [type_code].pack('n') + flags + [channel_update.htb.bytesize].pack('n') + channel_update.htb
9
+ end
10
+
11
+ def self.load(payload)
12
+ type_code, flags, len, rest = payload.unpack('na4nH*')
13
+ new(type_code, flags, rest[0..len * 2])
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module ExpiryTooFar
7
+ def to_payload
8
+ [type_code].pack('n')
9
+ end
10
+
11
+ def self.load(payload)
12
+ new(*payload.unpack('n'))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module ExpiryTooSoon
7
+ def to_payload
8
+ [type_code, channel_update.htb.bytesize].pack('nn') + channel_update.htb
9
+ end
10
+
11
+ def self.load(payload)
12
+ type_code, len, rest = payload.unpack('nnH*')
13
+ new(type_code, rest[0..len * 2])
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module FeeInsufficient
7
+ def to_payload
8
+ [type_code, htlc_msat, channel_update.htb.bytesize].pack('nq>n') + channel_update.htb
9
+ end
10
+
11
+ def self.load(payload)
12
+ type_code, htlc_msat, len, rest = payload.unpack('nq>nH*')
13
+ new(type_code, htlc_msat, rest[0..len * 2])
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module FinalExpiryTooSoon
7
+ def to_payload
8
+ [type_code].pack('n')
9
+ end
10
+
11
+ def self.load(payload)
12
+ new(*payload.unpack('n'))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module FinalIncorrectCltvExpiry
7
+ def to_payload
8
+ [type_code, cltv_expiry].pack('nq>')
9
+ end
10
+
11
+ def self.load(payload)
12
+ new(*payload.unpack('nq>'))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module FinalIncorrectHtlcAmount
7
+ def to_payload
8
+ [type_code].pack('n')
9
+ end
10
+
11
+ def self.load(payload)
12
+ new(*payload.unpack('n'))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module IncorrectCltvExpiry
7
+ def to_payload
8
+ [type_code, cltv_expiry, channel_update.htb.bytesize].pack('nq>n') + channel_update.htb
9
+ end
10
+
11
+ def self.load(payload)
12
+ type_code, cltv_expiry, len, rest = payload.unpack('nq>nH*')
13
+ new(type_code, cltv_expiry, rest[0..len * 2])
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module IncorrectPaymentAmount
7
+ def to_payload
8
+ [type_code].pack('n')
9
+ end
10
+
11
+ def self.load(payload)
12
+ new(*payload.unpack('n'))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module InvalidOnionHmac
7
+ def to_payload
8
+ [type_code, sha256_of_onion.htb.bytesize].pack('nn') + sha256_of_onion.htb
9
+ end
10
+
11
+ def self.load(payload)
12
+ type_code, len, rest = payload.unpack('nnH*')
13
+ new(type_code, rest[0..len * 2])
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module InvalidOnionKey
7
+ def to_payload
8
+ [type_code, sha256_of_onion.htb.bytesize].pack('nn') + sha256_of_onion.htb
9
+ end
10
+
11
+ def self.load(payload)
12
+ type_code, len, rest = payload.unpack('nnH*')
13
+ new(type_code, rest[0..len * 2])
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module InvalidOnionVersion
7
+ def to_payload
8
+ [type_code, sha256_of_onion.htb.bytesize].pack('nn') + sha256_of_onion.htb
9
+ end
10
+
11
+ def self.load(payload)
12
+ type_code, len, rest = payload.unpack('nnH*')
13
+ new(type_code, rest[0..len * 2])
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module InvalidRealm
7
+ def to_payload
8
+ [type_code].pack('n')
9
+ end
10
+
11
+ def self.load(payload)
12
+ new(*payload.unpack('n'))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module PermanentChannelFailure
7
+ def to_payload
8
+ [type_code].pack('n')
9
+ end
10
+
11
+ def self.load(payload)
12
+ new(*payload.unpack('n'))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module PermanentNodeFailure
7
+ def to_payload
8
+ [type_code].pack('n')
9
+ end
10
+
11
+ def self.load(payload)
12
+ new(*payload.unpack('n'))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module RequiredChannelFeatureMissing
7
+ def to_payload
8
+ [type_code].pack('n')
9
+ end
10
+
11
+ def self.load(payload)
12
+ new(*payload.unpack('n'))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module RequiredNodeFeatureMissing
7
+ def to_payload
8
+ [type_code].pack('n')
9
+ end
10
+
11
+ def self.load(payload)
12
+ new(*payload.unpack('n'))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module TemporaryChannelFailure
7
+ def to_payload
8
+ [type_code, channel_update.bytesize].pack('nn') + channel_update.htb
9
+ end
10
+
11
+ def self.load(payload)
12
+ type_code, len, rest = payload.unpack('nnH*')
13
+ new(type_code, rest[0..len * 2])
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module TemporaryNodeFailure
7
+ def to_payload
8
+ [type_code].pack('n')
9
+ end
10
+
11
+ def self.load(payload)
12
+ new(*payload.unpack('n'))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module UnknownNextPeer
7
+ def to_payload
8
+ [type_code].pack('n')
9
+ end
10
+
11
+ def self.load(payload)
12
+ new(*payload.unpack('n'))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lightning
4
+ module Onion
5
+ module FailureMessages
6
+ module UnknownPaymentHash
7
+ def to_payload
8
+ [type_code].pack('n')
9
+ end
10
+
11
+ def self.load(payload)
12
+ new(*payload.unpack('n'))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -8,24 +8,39 @@ module Lightning
8
8
  MAC_LENGTH = 32
9
9
  MAX_HOPS = 20
10
10
  HOP_LENGTH = PAYLOAD_LENGTH + MAC_LENGTH
11
+ MAX_ERROR_PAYLOAD_LENGTH = 256
12
+ ERROR_PACKET_LENGTH = MAC_LENGTH + MAX_ERROR_PAYLOAD_LENGTH + 2 + 2
11
13
 
12
14
  ZERO_HOP = Lightning::Onion::HopData.parse("\x00" * HOP_LENGTH)
13
- LAST_PACKET = Lightning::Onion::Packet.new(VERSION, "\x00" * PAYLOAD_LENGTH, [ZERO_HOP] * MAX_HOPS, "\x00" * MAC_LENGTH)
14
-
15
- def self.loop(hop_payloads, keys, shared_secrets, packet, associated_data)
16
- return packet if hop_payloads.empty?
17
- next_packet = make_next_packet(hop_payloads.last, associated_data, keys.last, shared_secrets.last, packet)
18
- loop(hop_payloads[0...-1], keys[0...-1], shared_secrets[0...-1], next_packet, associated_data)
19
- end
15
+ LAST_PACKET = Lightning::Onion::Packet.new(VERSION, "\x00" * 33, [ZERO_HOP] * MAX_HOPS, "\x00" * MAC_LENGTH)
20
16
 
21
17
  def self.make_packet(session_key, public_keys, payloads, associated_data)
22
- ephemereal_public_keys, shared_secrets = compute_ephemereal_public_keys_and_shared_secrets(session_key, public_keys)
18
+ ephemereal_public_keys, shared_secrets = compute_keys_and_secrets(session_key, public_keys)
23
19
  filler = generate_filler('rho', shared_secrets[0...-1], HOP_LENGTH, MAX_HOPS)
24
- last_packet = make_next_packet(payloads.last, associated_data, ephemereal_public_keys.last, shared_secrets.last, LAST_PACKET, filler)
25
- packet = loop(payloads[0...-1], ephemereal_public_keys[0...-1], shared_secrets[0...-1], last_packet, associated_data)
20
+ last_packet = make_next_packet(
21
+ payloads.last,
22
+ associated_data,
23
+ ephemereal_public_keys.last,
24
+ shared_secrets.last,
25
+ LAST_PACKET,
26
+ filler
27
+ )
28
+ packet = internal_make_packet(
29
+ payloads[0...-1],
30
+ ephemereal_public_keys[0...-1],
31
+ shared_secrets[0...-1],
32
+ last_packet,
33
+ associated_data
34
+ )
26
35
  [packet, shared_secrets.zip(public_keys)]
27
36
  end
28
37
 
38
+ def self.internal_make_packet(hop_payloads, keys, shared_secrets, packet, associated_data)
39
+ return packet if hop_payloads.empty?
40
+ next_packet = make_next_packet(hop_payloads.last, associated_data, keys.last, shared_secrets.last, packet)
41
+ internal_make_packet(hop_payloads[0...-1], keys[0...-1], shared_secrets[0...-1], next_packet, associated_data)
42
+ end
43
+
29
44
  def self.make_next_packet(payload, associated_data, ephemereal_public_key, shared_secret, packet, filler = '')
30
45
  hops_data1 = payload.htb << packet.hmac << packet.hops_data.map(&:to_payload).join[0...-HOP_LENGTH]
31
46
  stream = generate_cipher_stream(generate_key('rho', shared_secret), MAX_HOPS * HOP_LENGTH)
@@ -46,16 +61,28 @@ module Lightning
46
61
  Lightning::Onion::Packet.new(VERSION, ephemereal_public_key, hops_data, next_hmac)
47
62
  end
48
63
 
49
- def self.compute_ephemereal_public_keys_and_shared_secrets(session_key, public_keys)
64
+ def self.compute_keys_and_secrets(session_key, public_keys)
50
65
  point = ECDSA::Group::Secp256k1.generator
51
66
  generator_pubkey = ECDSA::Format::PointOctetString.encode(point, compression: true)
52
67
  ephemereal_public_key0 = make_blind(generator_pubkey.bth, session_key)
53
68
  secret0 = compute_shared_secret(public_keys[0], session_key)
54
69
  blinding_factor0 = compute_blinding_factor(ephemereal_public_key0, secret0)
55
- internal_compute_ephemereal_public_keys_and_shared_secrets(session_key, public_keys[1..-1], [ephemereal_public_key0], [blinding_factor0], [secret0])
70
+ internal_compute_keys_and_secrets(
71
+ session_key,
72
+ public_keys[1..-1],
73
+ [ephemereal_public_key0],
74
+ [blinding_factor0],
75
+ [secret0]
76
+ )
56
77
  end
57
78
 
58
- def self.internal_compute_ephemereal_public_keys_and_shared_secrets(session_key, public_keys, ephemereal_public_keys, blinding_factors, shared_secrets)
79
+ def self.internal_compute_keys_and_secrets(
80
+ session_key,
81
+ public_keys,
82
+ ephemereal_public_keys,
83
+ blinding_factors,
84
+ shared_secrets
85
+ )
59
86
  if public_keys.empty?
60
87
  [ephemereal_public_keys, shared_secrets]
61
88
  else
@@ -65,7 +92,13 @@ module Lightning
65
92
  ephemereal_public_keys << ephemereal_public_key
66
93
  blinding_factors << blinding_factor
67
94
  shared_secrets << secret
68
- internal_compute_ephemereal_public_keys_and_shared_secrets(session_key, public_keys[1..-1], ephemereal_public_keys, blinding_factors, shared_secrets)
95
+ internal_compute_keys_and_secrets(
96
+ session_key,
97
+ public_keys[1..-1],
98
+ ephemereal_public_keys,
99
+ blinding_factors,
100
+ shared_secrets
101
+ )
69
102
  end
70
103
  end
71
104
 
@@ -103,12 +136,6 @@ module Lightning
103
136
  end.pack('C*').bth
104
137
  end
105
138
 
106
- module KeyType
107
- RHO = 0x72686F
108
- MU = 0x6d75
109
- UM = 0x756d
110
- end
111
-
112
139
  def self.hmac256(key, message)
113
140
  OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, key, message)
114
141
  end
@@ -150,6 +177,53 @@ module Lightning
150
177
  end
151
178
  [payload, Lightning::Onion::Packet.new(VERSION, next_public_key, hops_data, hmac), shared_secret]
152
179
  end
180
+
181
+ def self.make_error_packet(shared_secret, failure)
182
+ message = failure.to_payload
183
+ um = generate_key('um', shared_secret)
184
+ padlen = MAX_ERROR_PAYLOAD_LENGTH - message.length
185
+ payload = +''
186
+ payload << [message.length].pack('n')
187
+ payload << message
188
+ payload << [padlen].pack('n')
189
+ payload << "\x00" * padlen
190
+ forward_error_packet(mac(um, payload.unpack('C*')) + payload, shared_secret)
191
+ end
192
+
193
+ def self.forward_error_packet(payload, shared_secret)
194
+ key = generate_key('ammag', shared_secret)
195
+ stream = generate_cipher_stream(key, ERROR_PACKET_LENGTH)
196
+ xor(payload.unpack('C*'), stream.unpack('C*')).pack('C*')
197
+ end
198
+
199
+ def self.parse_error(payload, node_shared_secrets)
200
+ raise "invalid length: #{payload.htb.bytesize}" unless payload.htb.bytesize == ERROR_PACKET_LENGTH
201
+ internal_parse_error(payload.htb, node_shared_secrets)
202
+ end
203
+
204
+ def self.internal_parse_error(payload, node_shared_secrets)
205
+ raise RuntimeError unless node_shared_secrets
206
+ node_shared_secret = node_shared_secrets.last
207
+ next_payload = forward_error_packet(payload, node_shared_secret[0])
208
+ if check_mac(node_shared_secret[0], next_payload)
209
+ ErrorPacket.new(node_shared_secret[1], extract_failure_message(next_payload))
210
+ else
211
+ internal_parse_error(next_payload, node_shared_secrets[0...-1])
212
+ end
213
+ end
214
+
215
+ def self.check_mac(secret, payload)
216
+ mac = payload[0...MAC_LENGTH]
217
+ payload1 = payload[MAC_LENGTH..-1]
218
+ um = generate_key('um', secret)
219
+ mac == mac(um, payload1.unpack('C*'))
220
+ end
221
+
222
+ def self.extract_failure_message(payload)
223
+ raise "invalid length: #{payload.bytesize}" unless payload.bytesize == ERROR_PACKET_LENGTH
224
+ _mac, len, rest = payload.unpack("a#{MAC_LENGTH}na*")
225
+ FailureMessages.load(rest[0...len])
226
+ end
153
227
  end
154
228
  end
155
229
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lightning
4
4
  module Onion
5
- VERSION = '0.1.0'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class String
4
+ def camel
5
+ split('_').map { |w| w[0].upcase + w[1..-1] }.join
6
+ end
7
+
8
+ def snake
9
+ gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
10
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
11
+ .tr('-', '_')
12
+ .downcase
13
+ end
14
+ end
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
21
  spec.require_paths = ['lib']
22
22
 
23
+ spec.add_runtime_dependency 'algebrick'
23
24
  spec.add_runtime_dependency 'bitcoinrb'
24
25
  spec.add_runtime_dependency 'rbnacl'
25
26
 
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lightning-onion
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hajime Yamaguchi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-03-17 00:00:00.000000000 Z
11
+ date: 2018-04-01 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: algebrick
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bitcoinrb
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -90,6 +104,7 @@ files:
90
104
  - ".gitignore"
91
105
  - ".rspec"
92
106
  - ".rubocop.yml"
107
+ - ".ruby-version"
93
108
  - ".travis.yml"
94
109
  - CODE_OF_CONDUCT.md
95
110
  - Gemfile
@@ -100,11 +115,36 @@ files:
100
115
  - bin/setup
101
116
  - lib/lightning/onion.rb
102
117
  - lib/lightning/onion/chacha20.rb
118
+ - lib/lightning/onion/error_packet.rb
119
+ - lib/lightning/onion/failure_messages.rb
120
+ - lib/lightning/onion/failure_messages/amount_below_minimum.rb
121
+ - lib/lightning/onion/failure_messages/channel_disabled.rb
122
+ - lib/lightning/onion/failure_messages/expiry_too_far.rb
123
+ - lib/lightning/onion/failure_messages/expiry_too_soon.rb
124
+ - lib/lightning/onion/failure_messages/fee_insufficient.rb
125
+ - lib/lightning/onion/failure_messages/final_expiry_too_soon.rb
126
+ - lib/lightning/onion/failure_messages/final_incorrect_cltv_expiry.rb
127
+ - lib/lightning/onion/failure_messages/final_incorrect_htlc_amount.rb
128
+ - lib/lightning/onion/failure_messages/incorrect_cltv_expiry.rb
129
+ - lib/lightning/onion/failure_messages/incorrect_payment_amount.rb
130
+ - lib/lightning/onion/failure_messages/invalid_onion_hmac.rb
131
+ - lib/lightning/onion/failure_messages/invalid_onion_key.rb
132
+ - lib/lightning/onion/failure_messages/invalid_onion_version.rb
133
+ - lib/lightning/onion/failure_messages/invalid_realm.rb
134
+ - lib/lightning/onion/failure_messages/permanent_channel_failure.rb
135
+ - lib/lightning/onion/failure_messages/permanent_node_failure.rb
136
+ - lib/lightning/onion/failure_messages/required_channel_feature_missing.rb
137
+ - lib/lightning/onion/failure_messages/required_node_feature_missing.rb
138
+ - lib/lightning/onion/failure_messages/temporary_channel_failure.rb
139
+ - lib/lightning/onion/failure_messages/temporary_node_failure.rb
140
+ - lib/lightning/onion/failure_messages/unknown_next_peer.rb
141
+ - lib/lightning/onion/failure_messages/unknown_payment_hash.rb
103
142
  - lib/lightning/onion/hop_data.rb
104
143
  - lib/lightning/onion/packet.rb
105
144
  - lib/lightning/onion/per_hop.rb
106
145
  - lib/lightning/onion/sphinx.rb
107
146
  - lib/lightning/onion/version.rb
147
+ - lib/lightning/utils/string.rb
108
148
  - lightningrb-onion.gemspec
109
149
  homepage: https://github.com/Yamaguchi/lightningrb-onion
110
150
  licenses: