lightning-onion 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: