lib/lightning/onion/sphinx.rb in lightning-onion-0.1.0 vs lib/lightning/onion/sphinx.rb in lightning-onion-0.2.0
- old
+ new
@@ -6,28 +6,43 @@
VERSION = "\x00"
PAYLOAD_LENGTH = 33
MAC_LENGTH = 32
MAX_HOPS = 20
HOP_LENGTH = PAYLOAD_LENGTH + MAC_LENGTH
+ MAX_ERROR_PAYLOAD_LENGTH = 256
+ ERROR_PACKET_LENGTH = MAC_LENGTH + MAX_ERROR_PAYLOAD_LENGTH + 2 + 2
ZERO_HOP = Lightning::Onion::HopData.parse("\x00" * HOP_LENGTH)
- LAST_PACKET = Lightning::Onion::Packet.new(VERSION, "\x00" * PAYLOAD_LENGTH, [ZERO_HOP] * MAX_HOPS, "\x00" * MAC_LENGTH)
+ LAST_PACKET = Lightning::Onion::Packet.new(VERSION, "\x00" * 33, [ZERO_HOP] * MAX_HOPS, "\x00" * MAC_LENGTH)
- def self.loop(hop_payloads, keys, shared_secrets, packet, associated_data)
- return packet if hop_payloads.empty?
- next_packet = make_next_packet(hop_payloads.last, associated_data, keys.last, shared_secrets.last, packet)
- loop(hop_payloads[0...-1], keys[0...-1], shared_secrets[0...-1], next_packet, associated_data)
- end
-
def self.make_packet(session_key, public_keys, payloads, associated_data)
- ephemereal_public_keys, shared_secrets = compute_ephemereal_public_keys_and_shared_secrets(session_key, public_keys)
+ ephemereal_public_keys, shared_secrets = compute_keys_and_secrets(session_key, public_keys)
filler = generate_filler('rho', shared_secrets[0...-1], HOP_LENGTH, MAX_HOPS)
- last_packet = make_next_packet(payloads.last, associated_data, ephemereal_public_keys.last, shared_secrets.last, LAST_PACKET, filler)
- packet = loop(payloads[0...-1], ephemereal_public_keys[0...-1], shared_secrets[0...-1], last_packet, associated_data)
+ last_packet = make_next_packet(
+ payloads.last,
+ associated_data,
+ ephemereal_public_keys.last,
+ shared_secrets.last,
+ LAST_PACKET,
+ filler
+ )
+ packet = internal_make_packet(
+ payloads[0...-1],
+ ephemereal_public_keys[0...-1],
+ shared_secrets[0...-1],
+ last_packet,
+ associated_data
+ )
[packet, shared_secrets.zip(public_keys)]
end
+ def self.internal_make_packet(hop_payloads, keys, shared_secrets, packet, associated_data)
+ return packet if hop_payloads.empty?
+ next_packet = make_next_packet(hop_payloads.last, associated_data, keys.last, shared_secrets.last, packet)
+ internal_make_packet(hop_payloads[0...-1], keys[0...-1], shared_secrets[0...-1], next_packet, associated_data)
+ end
+
def self.make_next_packet(payload, associated_data, ephemereal_public_key, shared_secret, packet, filler = '')
hops_data1 = payload.htb << packet.hmac << packet.hops_data.map(&:to_payload).join[0...-HOP_LENGTH]
stream = generate_cipher_stream(generate_key('rho', shared_secret), MAX_HOPS * HOP_LENGTH)
hops_data2 = xor(hops_data1.unpack('C*'), stream.unpack('C*'))
next_hops_data =
@@ -44,30 +59,48 @@
end
Lightning::Onion::Packet.new(VERSION, ephemereal_public_key, hops_data, next_hmac)
end
- def self.compute_ephemereal_public_keys_and_shared_secrets(session_key, public_keys)
+ def self.compute_keys_and_secrets(session_key, public_keys)
point = ECDSA::Group::Secp256k1.generator
generator_pubkey = ECDSA::Format::PointOctetString.encode(point, compression: true)
ephemereal_public_key0 = make_blind(generator_pubkey.bth, session_key)
secret0 = compute_shared_secret(public_keys[0], session_key)
blinding_factor0 = compute_blinding_factor(ephemereal_public_key0, secret0)
- internal_compute_ephemereal_public_keys_and_shared_secrets(session_key, public_keys[1..-1], [ephemereal_public_key0], [blinding_factor0], [secret0])
+ internal_compute_keys_and_secrets(
+ session_key,
+ public_keys[1..-1],
+ [ephemereal_public_key0],
+ [blinding_factor0],
+ [secret0]
+ )
end
- def self.internal_compute_ephemereal_public_keys_and_shared_secrets(session_key, public_keys, ephemereal_public_keys, blinding_factors, shared_secrets)
+ def self.internal_compute_keys_and_secrets(
+ session_key,
+ public_keys,
+ ephemereal_public_keys,
+ blinding_factors,
+ shared_secrets
+ )
if public_keys.empty?
[ephemereal_public_keys, shared_secrets]
else
ephemereal_public_key = make_blind(ephemereal_public_keys.last, blinding_factors.last)
secret = compute_shared_secret(make_blinds(public_keys.first, blinding_factors), session_key)
blinding_factor = compute_blinding_factor(ephemereal_public_key, secret)
ephemereal_public_keys << ephemereal_public_key
blinding_factors << blinding_factor
shared_secrets << secret
- internal_compute_ephemereal_public_keys_and_shared_secrets(session_key, public_keys[1..-1], ephemereal_public_keys, blinding_factors, shared_secrets)
+ internal_compute_keys_and_secrets(
+ session_key,
+ public_keys[1..-1],
+ ephemereal_public_keys,
+ blinding_factors,
+ shared_secrets
+ )
end
end
def self.make_blind(public_key, blinding_factor)
point = Bitcoin::Key.new(pubkey: public_key).to_point
@@ -101,16 +134,10 @@
new_padding = xor(padding1, stream)
new_padding
end.pack('C*').bth
end
- module KeyType
- RHO = 0x72686F
- MU = 0x6d75
- UM = 0x756d
- end
-
def self.hmac256(key, message)
OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, key, message)
end
# Key generation
@@ -147,9 +174,56 @@
20.times do |i|
hop_payload = next_hops_data.pack('C*')[i * HOP_LENGTH...(i + 1) * HOP_LENGTH]
hops_data << Lightning::Onion::HopData.parse(hop_payload)
end
[payload, Lightning::Onion::Packet.new(VERSION, next_public_key, hops_data, hmac), shared_secret]
+ end
+
+ def self.make_error_packet(shared_secret, failure)
+ message = failure.to_payload
+ um = generate_key('um', shared_secret)
+ padlen = MAX_ERROR_PAYLOAD_LENGTH - message.length
+ payload = +''
+ payload << [message.length].pack('n')
+ payload << message
+ payload << [padlen].pack('n')
+ payload << "\x00" * padlen
+ forward_error_packet(mac(um, payload.unpack('C*')) + payload, shared_secret)
+ end
+
+ def self.forward_error_packet(payload, shared_secret)
+ key = generate_key('ammag', shared_secret)
+ stream = generate_cipher_stream(key, ERROR_PACKET_LENGTH)
+ xor(payload.unpack('C*'), stream.unpack('C*')).pack('C*')
+ end
+
+ def self.parse_error(payload, node_shared_secrets)
+ raise "invalid length: #{payload.htb.bytesize}" unless payload.htb.bytesize == ERROR_PACKET_LENGTH
+ internal_parse_error(payload.htb, node_shared_secrets)
+ end
+
+ def self.internal_parse_error(payload, node_shared_secrets)
+ raise RuntimeError unless node_shared_secrets
+ node_shared_secret = node_shared_secrets.last
+ next_payload = forward_error_packet(payload, node_shared_secret[0])
+ if check_mac(node_shared_secret[0], next_payload)
+ ErrorPacket.new(node_shared_secret[1], extract_failure_message(next_payload))
+ else
+ internal_parse_error(next_payload, node_shared_secrets[0...-1])
+ end
+ end
+
+ def self.check_mac(secret, payload)
+ mac = payload[0...MAC_LENGTH]
+ payload1 = payload[MAC_LENGTH..-1]
+ um = generate_key('um', secret)
+ mac == mac(um, payload1.unpack('C*'))
+ end
+
+ def self.extract_failure_message(payload)
+ raise "invalid length: #{payload.bytesize}" unless payload.bytesize == ERROR_PACKET_LENGTH
+ _mac, len, rest = payload.unpack("a#{MAC_LENGTH}na*")
+ FailureMessages.load(rest[0...len])
end
end
end
end