From 17a8e1bbea1659843f4bb7e1a0c13d109d372544 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" <99172754+pegasusjcrawford@users.noreply.github.com> Date: Mon, 28 Feb 2022 01:15:38 -0800 Subject: [PATCH 01/34] add public key multiply to elliptic curve. need to get feedback --- src/Lyn.Protocol/Bolt8/EllipticCurveActions.cs | 8 +++++++- src/Lyn.Protocol/Bolt8/IEllipticCurveActions.cs | 9 +++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Lyn.Protocol/Bolt8/EllipticCurveActions.cs b/src/Lyn.Protocol/Bolt8/EllipticCurveActions.cs index ac40a15..7a575e5 100644 --- a/src/Lyn.Protocol/Bolt8/EllipticCurveActions.cs +++ b/src/Lyn.Protocol/Bolt8/EllipticCurveActions.cs @@ -1,4 +1,5 @@ using System; +using Lyn.Types.Fundamental; using NBitcoin; namespace Lyn.Protocol.Bolt8 @@ -8,5 +9,10 @@ public class EllipticCurveActions : IEllipticCurveActions public ReadOnlySpan Multiply(byte[] privateKey, ReadOnlySpan publicKey) => new PubKey(publicKey.ToArray()) .GetSharedSecret(new Key(privateKey)); - } + + + public ReadOnlySpan MultiplyPubKey(byte[] privateKey, ReadOnlySpan publicKey) + => new PubKey(publicKey.ToArray()) + .GetSharedPubkey(new Key(privateKey)).ToBytes(); + } } \ No newline at end of file diff --git a/src/Lyn.Protocol/Bolt8/IEllipticCurveActions.cs b/src/Lyn.Protocol/Bolt8/IEllipticCurveActions.cs index d5196a3..e23e42a 100644 --- a/src/Lyn.Protocol/Bolt8/IEllipticCurveActions.cs +++ b/src/Lyn.Protocol/Bolt8/IEllipticCurveActions.cs @@ -2,8 +2,9 @@ namespace Lyn.Protocol.Bolt8 { - public interface IEllipticCurveActions - { - ReadOnlySpan Multiply(byte[] privateKey, ReadOnlySpan publicKey); - } + public interface IEllipticCurveActions + { + ReadOnlySpan Multiply(byte[] privateKey, ReadOnlySpan publicKey); + ReadOnlySpan MultiplyPubKey(byte[] privateKey, ReadOnlySpan publicKey); + } } \ No newline at end of file From 4414e381911b03cd955813f2d6b02deaa9fe0f28 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" <99172754+pegasusjcrawford@users.noreply.github.com> Date: Mon, 28 Feb 2022 01:18:50 -0800 Subject: [PATCH 02/34] rename Hashing to Crypto to allow for Curve Actions --- src/Lyn.Protocol.Tests/Bolt3/Bolt3CommitmentTests.cs | 2 +- src/Lyn.Protocol/Bolt3/LightningScripts.cs | 2 +- src/Lyn.Protocol/Bolt3/Shachain/Shachain.cs | 2 +- .../Bolt7/AnnouncementSignaturesValidator.cs | 4 ++-- src/Lyn.Protocol/Bolt7/ChannelAnnouncementValidator.cs | 2 +- src/Lyn.Protocol/Bolt7/NodeAnnouncementValidator.cs | 2 +- .../Common/{Hashing => Crypto}/HashGenerator.cs | 10 +++++++++- .../{Hashing => Crypto}/HashGeneratorException.cs | 2 +- src/Lyn.Protocol/Common/TransactionHashCalculator.cs | 2 +- 9 files changed, 18 insertions(+), 10 deletions(-) rename src/Lyn.Protocol/Common/{Hashing => Crypto}/HashGenerator.cs (86%) rename src/Lyn.Protocol/Common/{Hashing => Crypto}/HashGeneratorException.cs (93%) diff --git a/src/Lyn.Protocol.Tests/Bolt3/Bolt3CommitmentTests.cs b/src/Lyn.Protocol.Tests/Bolt3/Bolt3CommitmentTests.cs index e0eaf04..85a6f1d 100644 --- a/src/Lyn.Protocol.Tests/Bolt3/Bolt3CommitmentTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt3/Bolt3CommitmentTests.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using Lyn.Protocol.Bolt3.Types; -using Lyn.Protocol.Common.Hashing; +using Lyn.Protocol.Common.Crypto; using Lyn.Types; using Lyn.Types.Bitcoin; using Lyn.Types.Fundamental; diff --git a/src/Lyn.Protocol/Bolt3/LightningScripts.cs b/src/Lyn.Protocol/Bolt3/LightningScripts.cs index 2bf9d7d..5c67a1d 100644 --- a/src/Lyn.Protocol/Bolt3/LightningScripts.cs +++ b/src/Lyn.Protocol/Bolt3/LightningScripts.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using Lyn.Protocol.Bolt3.Types; -using Lyn.Protocol.Common.Hashing; +using Lyn.Protocol.Common.Crypto; using Lyn.Types.Bitcoin; using Lyn.Types.Fundamental; using NBitcoin; diff --git a/src/Lyn.Protocol/Bolt3/Shachain/Shachain.cs b/src/Lyn.Protocol/Bolt3/Shachain/Shachain.cs index 9d2f975..79b4e41 100644 --- a/src/Lyn.Protocol/Bolt3/Shachain/Shachain.cs +++ b/src/Lyn.Protocol/Bolt3/Shachain/Shachain.cs @@ -1,6 +1,6 @@ using System; using System.Linq; -using Lyn.Protocol.Common.Hashing; +using Lyn.Protocol.Common.Crypto; using Lyn.Types; using Lyn.Types.Bitcoin; diff --git a/src/Lyn.Protocol/Bolt7/AnnouncementSignaturesValidator.cs b/src/Lyn.Protocol/Bolt7/AnnouncementSignaturesValidator.cs index 7e3daf5..9ef2217 100644 --- a/src/Lyn.Protocol/Bolt7/AnnouncementSignaturesValidator.cs +++ b/src/Lyn.Protocol/Bolt7/AnnouncementSignaturesValidator.cs @@ -1,11 +1,11 @@ using Lyn.Protocol.Bolt7.Messages; using Lyn.Protocol.Common; -using Lyn.Protocol.Common.Hashing; +using Lyn.Protocol.Common.Crypto; using Lyn.Protocol.Common.Messages; namespace Lyn.Protocol.Bolt7 { - public class AnnouncementSignaturesValidator : IMessageValidator + public class AnnouncementSignaturesValidator : IMessageValidator { private readonly ISerializationFactory _serializationFactory; private readonly IGossipRepository _repository; diff --git a/src/Lyn.Protocol/Bolt7/ChannelAnnouncementValidator.cs b/src/Lyn.Protocol/Bolt7/ChannelAnnouncementValidator.cs index 4e7a3e9..bd9fe2e 100644 --- a/src/Lyn.Protocol/Bolt7/ChannelAnnouncementValidator.cs +++ b/src/Lyn.Protocol/Bolt7/ChannelAnnouncementValidator.cs @@ -1,7 +1,7 @@ using System.Linq; using Lyn.Protocol.Bolt7.Messages; using Lyn.Protocol.Common; -using Lyn.Protocol.Common.Hashing; +using Lyn.Protocol.Common.Crypto; using Lyn.Protocol.Common.Messages; using Lyn.Types; using Lyn.Types.Fundamental; diff --git a/src/Lyn.Protocol/Bolt7/NodeAnnouncementValidator.cs b/src/Lyn.Protocol/Bolt7/NodeAnnouncementValidator.cs index 80a45f6..094c3f1 100644 --- a/src/Lyn.Protocol/Bolt7/NodeAnnouncementValidator.cs +++ b/src/Lyn.Protocol/Bolt7/NodeAnnouncementValidator.cs @@ -1,6 +1,6 @@ using Lyn.Protocol.Bolt7.Messages; using Lyn.Protocol.Common; -using Lyn.Protocol.Common.Hashing; +using Lyn.Protocol.Common.Crypto; using Lyn.Protocol.Common.Messages; using Lyn.Types.Fundamental; diff --git a/src/Lyn.Protocol/Common/Hashing/HashGenerator.cs b/src/Lyn.Protocol/Common/Crypto/HashGenerator.cs similarity index 86% rename from src/Lyn.Protocol/Common/Hashing/HashGenerator.cs rename to src/Lyn.Protocol/Common/Crypto/HashGenerator.cs index ec0be89..34f4269 100644 --- a/src/Lyn.Protocol/Common/Hashing/HashGenerator.cs +++ b/src/Lyn.Protocol/Common/Crypto/HashGenerator.cs @@ -3,7 +3,7 @@ using System.Security.Cryptography; using Lyn.Types.Bitcoin; -namespace Lyn.Protocol.Common.Hashing +namespace Lyn.Protocol.Common.Crypto { public static partial class HashGenerator { @@ -57,6 +57,14 @@ public static UInt256 DoubleSha512AsUInt256(ReadOnlySpan data) return new UInt256(result.Slice(0, 32)); } + public static ReadOnlySpan HmacSha256(byte[] key, ReadOnlySpan data) + { + using var hmac = new HMACSHA256(key); + Span result = new byte[32]; + if (!hmac.TryComputeHash(data, result, out _)) throw new HashGeneratorException($"Failed to perform {nameof(HmacSha256)}"); + return result; + } + [DoesNotReturn] public static void ThrowHashGeneratorException(string message) { diff --git a/src/Lyn.Protocol/Common/Hashing/HashGeneratorException.cs b/src/Lyn.Protocol/Common/Crypto/HashGeneratorException.cs similarity index 93% rename from src/Lyn.Protocol/Common/Hashing/HashGeneratorException.cs rename to src/Lyn.Protocol/Common/Crypto/HashGeneratorException.cs index 9a14772..bb5064d 100644 --- a/src/Lyn.Protocol/Common/Hashing/HashGeneratorException.cs +++ b/src/Lyn.Protocol/Common/Crypto/HashGeneratorException.cs @@ -1,6 +1,6 @@ using System; -namespace Lyn.Protocol.Common.Hashing +namespace Lyn.Protocol.Common.Crypto { [Serializable] public class HashGeneratorException : Exception diff --git a/src/Lyn.Protocol/Common/TransactionHashCalculator.cs b/src/Lyn.Protocol/Common/TransactionHashCalculator.cs index 195396a..89df156 100644 --- a/src/Lyn.Protocol/Common/TransactionHashCalculator.cs +++ b/src/Lyn.Protocol/Common/TransactionHashCalculator.cs @@ -1,6 +1,6 @@ using System; using System.Buffers; -using Lyn.Protocol.Common.Hashing; +using Lyn.Protocol.Common.Crypto; using Lyn.Types.Bitcoin; using Lyn.Types.Serialization; From ebce2cfe859e84e3c31e2b6a27e7ee07a8b1054d Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" <99172754+pegasusjcrawford@users.noreply.github.com> Date: Mon, 28 Feb 2022 01:21:44 -0800 Subject: [PATCH 03/34] move elliptic curve actions into common crypto folder --- .../Bolt8/Bolt8InitiatedNoiseProtocolTests.cs | 1 + .../Bolt8/HandshakeServiceTests.cs | 1 + .../Bolt3/LightningKeyDerivation.cs | 1 - src/Lyn.Protocol/Bolt8/EllipticCurveActions.cs | 18 ------------------ src/Lyn.Protocol/Bolt8/HandshakeService.cs | 1 + .../Common/Crypto/EllipticCurveActions.cs | 18 ++++++++++++++++++ .../Crypto}/IEllipticCurveActions.cs | 2 +- .../Common/DefaultIoCRegistrations.cs | 1 + 8 files changed, 23 insertions(+), 20 deletions(-) delete mode 100644 src/Lyn.Protocol/Bolt8/EllipticCurveActions.cs create mode 100644 src/Lyn.Protocol/Common/Crypto/EllipticCurveActions.cs rename src/Lyn.Protocol/{Bolt8 => Common/Crypto}/IEllipticCurveActions.cs (87%) diff --git a/src/Lyn.Protocol.Tests/Bolt8/Bolt8InitiatedNoiseProtocolTests.cs b/src/Lyn.Protocol.Tests/Bolt8/Bolt8InitiatedNoiseProtocolTests.cs index 85baefa..c3f1e99 100644 --- a/src/Lyn.Protocol.Tests/Bolt8/Bolt8InitiatedNoiseProtocolTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt8/Bolt8InitiatedNoiseProtocolTests.cs @@ -1,4 +1,5 @@ using Lyn.Protocol.Bolt8; +using Lyn.Protocol.Common.Crypto; using Microsoft.Extensions.Logging; using Moq; diff --git a/src/Lyn.Protocol.Tests/Bolt8/HandshakeServiceTests.cs b/src/Lyn.Protocol.Tests/Bolt8/HandshakeServiceTests.cs index f10ad5c..63cc1b9 100644 --- a/src/Lyn.Protocol.Tests/Bolt8/HandshakeServiceTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt8/HandshakeServiceTests.cs @@ -1,5 +1,6 @@ using System.Buffers; using Lyn.Protocol.Bolt8; +using Lyn.Protocol.Common.Crypto; using Lyn.Types; using Microsoft.Extensions.Logging; using Moq; diff --git a/src/Lyn.Protocol/Bolt3/LightningKeyDerivation.cs b/src/Lyn.Protocol/Bolt3/LightningKeyDerivation.cs index f87543b..020ac09 100644 --- a/src/Lyn.Protocol/Bolt3/LightningKeyDerivation.cs +++ b/src/Lyn.Protocol/Bolt3/LightningKeyDerivation.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using Lyn.Protocol.Bolt3.Types; -using Lyn.Protocol.Common.Hashing; using Lyn.Types.Bitcoin; using Lyn.Types.Fundamental; using NBitcoin; diff --git a/src/Lyn.Protocol/Bolt8/EllipticCurveActions.cs b/src/Lyn.Protocol/Bolt8/EllipticCurveActions.cs deleted file mode 100644 index 7a575e5..0000000 --- a/src/Lyn.Protocol/Bolt8/EllipticCurveActions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using Lyn.Types.Fundamental; -using NBitcoin; - -namespace Lyn.Protocol.Bolt8 -{ - public class EllipticCurveActions : IEllipticCurveActions - { - public ReadOnlySpan Multiply(byte[] privateKey, ReadOnlySpan publicKey) - => new PubKey(publicKey.ToArray()) - .GetSharedSecret(new Key(privateKey)); - - - public ReadOnlySpan MultiplyPubKey(byte[] privateKey, ReadOnlySpan publicKey) - => new PubKey(publicKey.ToArray()) - .GetSharedPubkey(new Key(privateKey)).ToBytes(); - } -} \ No newline at end of file diff --git a/src/Lyn.Protocol/Bolt8/HandshakeService.cs b/src/Lyn.Protocol/Bolt8/HandshakeService.cs index 48591cc..0e432c5 100644 --- a/src/Lyn.Protocol/Bolt8/HandshakeService.cs +++ b/src/Lyn.Protocol/Bolt8/HandshakeService.cs @@ -1,5 +1,6 @@ using System; using System.Buffers; +using Lyn.Protocol.Common.Crypto; using Microsoft.Extensions.Logging; namespace Lyn.Protocol.Bolt8 diff --git a/src/Lyn.Protocol/Common/Crypto/EllipticCurveActions.cs b/src/Lyn.Protocol/Common/Crypto/EllipticCurveActions.cs new file mode 100644 index 0000000..c86524b --- /dev/null +++ b/src/Lyn.Protocol/Common/Crypto/EllipticCurveActions.cs @@ -0,0 +1,18 @@ +using System; +using Lyn.Types.Fundamental; +using NBitcoin; + +namespace Lyn.Protocol.Common.Crypto +{ + public class EllipticCurveActions : IEllipticCurveActions + { + public ReadOnlySpan Multiply(byte[] privateKey, ReadOnlySpan publicKey) + => new PubKey(publicKey.ToArray()) + .GetSharedSecret(new Key(privateKey)); + + + public ReadOnlySpan MultiplyPubKey(byte[] privateKey, ReadOnlySpan publicKey) + => new PubKey(publicKey.ToArray()) + .GetSharedPubkey(new Key(privateKey)).ToBytes(); + } +} \ No newline at end of file diff --git a/src/Lyn.Protocol/Bolt8/IEllipticCurveActions.cs b/src/Lyn.Protocol/Common/Crypto/IEllipticCurveActions.cs similarity index 87% rename from src/Lyn.Protocol/Bolt8/IEllipticCurveActions.cs rename to src/Lyn.Protocol/Common/Crypto/IEllipticCurveActions.cs index e23e42a..63286e6 100644 --- a/src/Lyn.Protocol/Bolt8/IEllipticCurveActions.cs +++ b/src/Lyn.Protocol/Common/Crypto/IEllipticCurveActions.cs @@ -1,6 +1,6 @@ using System; -namespace Lyn.Protocol.Bolt8 +namespace Lyn.Protocol.Common.Crypto { public interface IEllipticCurveActions { diff --git a/src/Lyn.Protocol/Common/DefaultIoCRegistrations.cs b/src/Lyn.Protocol/Common/DefaultIoCRegistrations.cs index 231607c..60791b8 100644 --- a/src/Lyn.Protocol/Common/DefaultIoCRegistrations.cs +++ b/src/Lyn.Protocol/Common/DefaultIoCRegistrations.cs @@ -14,6 +14,7 @@ using Lyn.Protocol.Bolt8; using Lyn.Protocol.Bolt9; using Lyn.Protocol.Common.Blockchain; +using Lyn.Protocol.Common.Crypto; using Lyn.Protocol.Common.Messages; using Lyn.Protocol.Connection; using Lyn.Types.Serialization; From 070cbb2121c1de710bcb0cba99e42df1885501b5 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" <99172754+pegasusjcrawford@users.noreply.github.com> Date: Mon, 28 Feb 2022 01:22:14 -0800 Subject: [PATCH 04/34] updates to feature flags & messge type enums for onion routing --- src/Lyn.Protocol/Bolt1/Messages/Features.cs | 2 ++ src/Lyn.Protocol/Common/Messages/MessageType.cs | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/Lyn.Protocol/Bolt1/Messages/Features.cs b/src/Lyn.Protocol/Bolt1/Messages/Features.cs index 3081a4c..192951c 100644 --- a/src/Lyn.Protocol/Bolt1/Messages/Features.cs +++ b/src/Lyn.Protocol/Bolt1/Messages/Features.cs @@ -28,5 +28,7 @@ public enum Features : ulong OptionAnchorOutputs = 1 << 21, OptionAnchorsZeroFeeHtlcTxRequired = 1 << 22, OptionAnchorsZeroFeeHtlcTx = 1 << 23, + OptionOnionMessagesRequired = 1 << 38, + OptionOnionMessages = 1 << 39 } } \ No newline at end of file diff --git a/src/Lyn.Protocol/Common/Messages/MessageType.cs b/src/Lyn.Protocol/Common/Messages/MessageType.cs index 59aaf36..a4e83c5 100644 --- a/src/Lyn.Protocol/Common/Messages/MessageType.cs +++ b/src/Lyn.Protocol/Common/Messages/MessageType.cs @@ -34,5 +34,8 @@ public enum MessageType : ushort QueryShortChannelIds = 261, QueryChannelRange = 263, GossipTimestampFilter = 265, + + // Sphinx/Onion + OnionMessage = 513 } } \ No newline at end of file From d40867475d4275d5cc4ccce84c52248762f6328d Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" <99172754+pegasusjcrawford@users.noreply.github.com> Date: Mon, 28 Feb 2022 01:23:03 -0800 Subject: [PATCH 05/34] some base entities for onion routing. eclair inspired --- .../Bolt4/Entities/DecryptedOnionPacket.cs | 21 ++++++++++++++++++ .../Bolt4/Entities/OnionRoutingPacket.cs | 22 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 src/Lyn.Protocol/Bolt4/Entities/DecryptedOnionPacket.cs create mode 100644 src/Lyn.Protocol/Bolt4/Entities/OnionRoutingPacket.cs diff --git a/src/Lyn.Protocol/Bolt4/Entities/DecryptedOnionPacket.cs b/src/Lyn.Protocol/Bolt4/Entities/DecryptedOnionPacket.cs new file mode 100644 index 0000000..99a4044 --- /dev/null +++ b/src/Lyn.Protocol/Bolt4/Entities/DecryptedOnionPacket.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Lyn.Protocol.Bolt4.Entities +{ + public class DecryptedOnionPacket + { + + public byte[] Payload { get; set; } + + public OnionRoutingPacket NextPacket { get; set; } + + public byte[] SharedSecret { get; set; } + + public bool IsLastPacket => (NextPacket?.Hmac == null || NextPacket?.Hmac.Length == 0); + + } +} diff --git a/src/Lyn.Protocol/Bolt4/Entities/OnionRoutingPacket.cs b/src/Lyn.Protocol/Bolt4/Entities/OnionRoutingPacket.cs new file mode 100644 index 0000000..6fd6065 --- /dev/null +++ b/src/Lyn.Protocol/Bolt4/Entities/OnionRoutingPacket.cs @@ -0,0 +1,22 @@ +using Lyn.Types.Fundamental; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Lyn.Protocol.Bolt4.Entities +{ + public class OnionRoutingPacket + { + + public byte Version { get; set; } + + public PublicKey EphemeralKey { get; set; } + + public byte[] PayloadData { get; set; } + + public byte[] Hmac { get; set; } + + } +} From 972a6970d3a34eeec026003d247736a22b7b9569 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" <99172754+pegasusjcrawford@users.noreply.github.com> Date: Mon, 28 Feb 2022 01:23:42 -0800 Subject: [PATCH 06/34] stub out the beginnings of the onion message serializer. need to add tests --- .../Bolt4/Messages/OnionMessage.cs | 26 ++++++++++ .../Bolt4/Messages/OnionMessageSerializer.cs | 52 +++++++++++++++++++ .../Messages/TlvRecords/OnionMessageTlv.cs | 13 +++++ 3 files changed, 91 insertions(+) create mode 100644 src/Lyn.Protocol/Bolt4/Messages/OnionMessage.cs create mode 100644 src/Lyn.Protocol/Bolt4/Messages/OnionMessageSerializer.cs create mode 100644 src/Lyn.Protocol/Bolt4/Messages/TlvRecords/OnionMessageTlv.cs diff --git a/src/Lyn.Protocol/Bolt4/Messages/OnionMessage.cs b/src/Lyn.Protocol/Bolt4/Messages/OnionMessage.cs new file mode 100644 index 0000000..421bebe --- /dev/null +++ b/src/Lyn.Protocol/Bolt4/Messages/OnionMessage.cs @@ -0,0 +1,26 @@ +using Lyn.Protocol.Bolt4.Entities; +using Lyn.Protocol.Common.Messages; +using Lyn.Types.Fundamental; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Lyn.Protocol.Bolt4.Messages +{ + public class OnionMessage : MessagePayload + { + public override MessageType MessageType => MessageType.OnionMessage; + + public PublicKey BlindingKey { get; set; } + + public OnionRoutingPacket OnionPacket { get; set; } + + public OnionMessage() + { + OnionPacket = new OnionRoutingPacket(); + } + + } +} diff --git a/src/Lyn.Protocol/Bolt4/Messages/OnionMessageSerializer.cs b/src/Lyn.Protocol/Bolt4/Messages/OnionMessageSerializer.cs new file mode 100644 index 0000000..ee4f29b --- /dev/null +++ b/src/Lyn.Protocol/Bolt4/Messages/OnionMessageSerializer.cs @@ -0,0 +1,52 @@ +using Lyn.Types.Fundamental; +using Lyn.Types.Serialization; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Lyn.Protocol.Bolt4.Messages +{ + internal class OnionMessageSerializer : IProtocolTypeSerializer + { + public OnionMessage Deserialize(ref SequenceReader reader, ProtocolTypeSerializerOptions? options = null) + { + var onionMessage = new OnionMessage(); + + var blindingKeyBytes = reader.ReadBytes(33); + onionMessage.BlindingKey = new PublicKey(blindingKeyBytes.ToArray()); + + // read the onion packet data from the message + + var verByte = reader.ReadByte(); + onionMessage.OnionPacket.Version = verByte; + + var onionKeyBytes = reader.ReadBytes(33); + onionMessage.OnionPacket.EphemeralKey = new PublicKey(onionKeyBytes.ToArray()); + + // The payloadLength is computed by taking the total size of the packet and + // subtracting the length of the Sphinx header. The header fields are: + // - version - 1 byte + // - public key - 33 bytes + // - hmac - 32 bytes + // This value is cast to an integer, as the overall packet size should be + // Less than 32kb + int payloadLength = Convert.ToInt32(reader.Length - 66); + + var payloadData = reader.ReadBytes(payloadLength); + onionMessage.OnionPacket.PayloadData = payloadData.ToArray(); + + var hmacBytes = reader.ReadBytes(32); + onionMessage.OnionPacket.Hmac = hmacBytes.ToArray(); + + return onionMessage; + } + + public int Serialize(OnionMessage onionMessage, IBufferWriter writer, ProtocolTypeSerializerOptions? options = null) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Lyn.Protocol/Bolt4/Messages/TlvRecords/OnionMessageTlv.cs b/src/Lyn.Protocol/Bolt4/Messages/TlvRecords/OnionMessageTlv.cs new file mode 100644 index 0000000..c459af5 --- /dev/null +++ b/src/Lyn.Protocol/Bolt4/Messages/TlvRecords/OnionMessageTlv.cs @@ -0,0 +1,13 @@ +using Lyn.Protocol.Common.Messages; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Lyn.Protocol.Bolt4.Messages.TlvRecords +{ + public class OnionMessageTlv : TlvRecord + { + } +} From 6a290ae908b1f19881afd7e7a7fd56fdbafebe9b Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" <99172754+pegasusjcrawford@users.noreply.github.com> Date: Mon, 28 Feb 2022 01:24:27 -0800 Subject: [PATCH 07/34] Start building up some of the Sphinx primitives. Modeled after eclair's implementation --- src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs | 63 +++++++++ src/Lyn.Protocol/Bolt4/ISphinx.cs | 21 +++ src/Lyn.Protocol/Bolt4/Sphinx.cs | 138 ++++++++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs create mode 100644 src/Lyn.Protocol/Bolt4/ISphinx.cs create mode 100644 src/Lyn.Protocol/Bolt4/Sphinx.cs diff --git a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs new file mode 100644 index 0000000..377c213 --- /dev/null +++ b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs @@ -0,0 +1,63 @@ +using Lyn.Protocol.Bolt4; +using Lyn.Protocol.Common.Crypto; +using Lyn.Types.Fundamental; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Lyn.Protocol.Tests.Bolt4 +{ + public class SphinxTests + { + + private byte[] StringToByteArray(string hex) + { + return Enumerable.Range(0, hex.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + .ToArray(); + } + + [Fact] + public void ReferenceTestVector_GeneratesEphemeralKeysAndSecrets() + { + // todo: make this test not suck? + var curveActions = new EllipticCurveActions(); + var sphinx = new Sphinx(curveActions); + + var sessionKey = new PrivateKey(StringToByteArray("4141414141414141414141414141414141414141414141414141414141414141")); + var publicKeys = new List() + { + new PublicKey(StringToByteArray("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), + new PublicKey(StringToByteArray("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), + new PublicKey(StringToByteArray("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), + new PublicKey(StringToByteArray("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), + new PublicKey(StringToByteArray("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) + }; + + var (ephemeralKeys, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(sessionKey, publicKeys); + + Assert.NotNull(ephemeralKeys); + Assert.NotNull(sharedSecrets); + + var keyList = ephemeralKeys.ToList(); + var secretList = sharedSecrets.ToList(); + + Assert.Equal(StringToByteArray("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"), keyList.ElementAt(0).GetSpan().ToArray()); + Assert.Equal(StringToByteArray("53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66"), secretList.ElementAt(0)); + Assert.Equal(StringToByteArray("028f9438bfbf7feac2e108d677e3a82da596be706cc1cf342b75c7b7e22bf4e6e2"), keyList.ElementAt(1).GetSpan().ToArray()); + Assert.Equal(StringToByteArray("a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae"), secretList.ElementAt(1)); + Assert.Equal(StringToByteArray("03bfd8225241ea71cd0843db7709f4c222f62ff2d4516fd38b39914ab6b83e0da0"), keyList.ElementAt(2).GetSpan().ToArray()); + Assert.Equal(StringToByteArray("3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc"), secretList.ElementAt(2)); + Assert.Equal(StringToByteArray("031dde6926381289671300239ea8e57ffaf9bebd05b9a5b95beaf07af05cd43595"), keyList.ElementAt(3).GetSpan().ToArray()); + Assert.Equal(StringToByteArray("21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d"), secretList.ElementAt(3)); + Assert.Equal(StringToByteArray("03a214ebd875aab6ddfd77f22c5e7311d7f77f17a169e599f157bbcdae8bf071f4"), keyList.ElementAt(4).GetSpan().ToArray()); + Assert.Equal(StringToByteArray("b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328"), secretList.ElementAt(4)); + } + + } +} diff --git a/src/Lyn.Protocol/Bolt4/ISphinx.cs b/src/Lyn.Protocol/Bolt4/ISphinx.cs new file mode 100644 index 0000000..8566b9c --- /dev/null +++ b/src/Lyn.Protocol/Bolt4/ISphinx.cs @@ -0,0 +1,21 @@ +using Lyn.Types.Fundamental; +using System; +using System.Collections.Generic; + +namespace Lyn.Protocol.Bolt4 +{ + public interface ISphinx + { + PublicKey BlindKey(PublicKey pubKey, IEnumerable blindingFactors); + PublicKey BlindKey(PublicKey pubKey, ReadOnlySpan blindingFactor); + ReadOnlySpan ComputeBlindingFactor(PublicKey pubKey, ReadOnlySpan secret); + (IEnumerable, IEnumerable) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, ICollection publicKeys); + (IEnumerable, IEnumerable) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, ICollection publicKeys, IList ephemeralPublicKeys, IList blindingFactors, IList sharedSecrets); + ReadOnlySpan ComputeSharedSecret(PublicKey publicKey, PrivateKey secret); + PrivateKey DeriveBlindedPrivateKey(PrivateKey privateKey, PublicKey blindingEphemeralKey); + ReadOnlySpan ExclusiveOR(ReadOnlySpan left, ReadOnlySpan right); + ReadOnlySpan GenerateSphinxKey(byte[] keyType, ReadOnlySpan secret); + ReadOnlySpan GenerateSphinxKey(string keyType, ReadOnlySpan secret); + ReadOnlySpan GenerateStream(ReadOnlyMemory keyData, int streamLength); + } +} \ No newline at end of file diff --git a/src/Lyn.Protocol/Bolt4/Sphinx.cs b/src/Lyn.Protocol/Bolt4/Sphinx.cs new file mode 100644 index 0000000..46b2056 --- /dev/null +++ b/src/Lyn.Protocol/Bolt4/Sphinx.cs @@ -0,0 +1,138 @@ +using Lyn.Protocol.Common.Crypto; +using Lyn.Types.Fundamental; +using NaCl.Core; +using NBitcoin.Secp256k1; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Lyn.Protocol.Bolt4 +{ + public class Sphinx : ISphinx + { + private readonly IEllipticCurveActions _ellipticCurveActions; + + public Sphinx(IEllipticCurveActions ellipticCurveActions) + { + _ellipticCurveActions = ellipticCurveActions; + } + + + public ReadOnlySpan ComputeSharedSecret(PublicKey publicKey, PrivateKey secret) + { + return HashGenerator.Sha256(_ellipticCurveActions.MultiplyPubKey(secret, publicKey)); + } + + public ReadOnlySpan GenerateSphinxKey(byte[] keyType, ReadOnlySpan secret) + { + return HashGenerator.HmacSha256(keyType, secret); + } + + public ReadOnlySpan GenerateSphinxKey(string keyType, ReadOnlySpan secret) + { + var keyTypeBytes = Encoding.UTF8.GetBytes(keyType); + return GenerateSphinxKey(keyTypeBytes, secret); + } + + public PrivateKey DeriveBlindedPrivateKey(PrivateKey privateKey, PublicKey blindingEphemeralKey) + { + var sharedSecret = ComputeSharedSecret(blindingEphemeralKey, privateKey); + var generatedKey = GenerateSphinxKey("blinded_node_id", sharedSecret); + var newKeyBytes = _ellipticCurveActions.MultiplyPubKey(privateKey, generatedKey); + return new PrivateKey(newKeyBytes.ToArray()); + } + + // todo: util/helper + public ReadOnlySpan ExclusiveOR(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + throw new ArgumentException("inputs must be same length"); + + byte[] result = new byte[left.Length]; + + for (int i = 0; i < left.Length; i++) + { + result[i] = (byte)(left[i] ^ right[i]); + } + + return result; + } + + public ReadOnlySpan GenerateStream(ReadOnlyMemory keyData, int streamLength) + { + var cipher = new ChaCha20(keyData, 0); + var emptyPlainText = Enumerable.Range(0, streamLength).Select(x => 0x00).ToArray(); + var nonce = Enumerable.Range(0, 12).Select(x => 0x00).ToArray(); + return cipher.Encrypt(emptyPlainText, nonce); + } + + public ReadOnlySpan ComputeBlindingFactor(PublicKey pubKey, ReadOnlySpan secret) + { + // welcome to allocation city baby - population: BlindingFactor + return HashGenerator.Sha256(pubKey.GetSpan().ToArray().Concat(secret.ToArray()).ToArray()); + } + + public PublicKey BlindKey(PublicKey pubKey, ReadOnlySpan blindingFactor) + { + var blindKeyBytes = _ellipticCurveActions.MultiplyPubKey(new PrivateKey(blindingFactor.ToArray()), pubKey); + return new PublicKey(blindKeyBytes.ToArray()); + } + + public PublicKey BlindKey(PublicKey pubKey, IEnumerable blindingFactors) + { + return blindingFactors.Aggregate(pubKey, (key, blindingFactor) => BlindKey(key, blindingFactor)); + } + + public (IEnumerable, IEnumerable) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, + ICollection publicKeys) + { + // this seems inelegant as fuck? + var key = new ECPubKey(EC.G, null); + var ephemeralPublicKey0 = BlindKey(new PublicKey(key.ToBytes()), sessionKey); + var secret0 = ComputeSharedSecret(publicKeys.First(), sessionKey); + var blindingFactor0 = ComputeBlindingFactor(ephemeralPublicKey0, secret0); + + IList ephemeralPublicKeys = new List { ephemeralPublicKey0 }; + IList blindingFactors = new List { blindingFactor0.ToArray() }; + IList sharedSecrets = new List() { secret0.ToArray() }; + + return ComputeEphemeralPublicKeysAndSharedSecrets(sessionKey, + publicKeys.Skip(1).ToList(), + ephemeralPublicKeys, + blindingFactors, + sharedSecrets); + } + + public (IEnumerable, IEnumerable) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, + ICollection publicKeys, + IList ephemeralPublicKeys, + IList blindingFactors, + IList sharedSecrets) + { + if (publicKeys.Count == 0) + { + return (ephemeralPublicKeys, sharedSecrets); + } + else + { + var ephemeralPublicKey = BlindKey(ephemeralPublicKeys.Last(), blindingFactors.Last()); + var secret = ComputeSharedSecret(BlindKey(publicKeys.First(), blindingFactors), sessionKey); + var blindingFactor = ComputeBlindingFactor(ephemeralPublicKey, secret); + + ephemeralPublicKeys.Add(ephemeralPublicKey); + blindingFactors.Add(blindingFactor.ToArray()); + sharedSecrets.Add(secret.ToArray()); + + return ComputeEphemeralPublicKeysAndSharedSecrets(sessionKey, + publicKeys.Skip(1).ToList(), + ephemeralPublicKeys, + blindingFactors, + sharedSecrets); + } + } + + + } +} From 88bd97d0af2ad8ca3d76f38c973e694579adbb0f Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" <99172754+pegasusjcrawford@users.noreply.github.com> Date: Mon, 28 Feb 2022 01:36:46 -0800 Subject: [PATCH 08/34] fix files i broke with my silly crypto rename --- .../Bolt2/AcceptChannelMessageServiceTests.cs | 2 +- .../Bolt2/ChannelEstablishment/FullChannelEstablishmentTest.cs | 2 +- src/Lyn.Protocol.Tests/Bolt3/Bolt3CommitmentTestContext.cs | 2 +- .../Bolt2/ChannelEstablishment/AcceptChannelMessageService.cs | 2 +- src/Lyn.Protocol/Common/Crypto/ITransactionHashCalculator.cs | 2 +- src/Lyn.Protocol/Common/Crypto/TransactionHashCalculator.cs | 3 +-- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Lyn.Protocol.Tests/Bolt2/AcceptChannelMessageServiceTests.cs b/src/Lyn.Protocol.Tests/Bolt2/AcceptChannelMessageServiceTests.cs index c8c1e35..087fcbf 100644 --- a/src/Lyn.Protocol.Tests/Bolt2/AcceptChannelMessageServiceTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt2/AcceptChannelMessageServiceTests.cs @@ -9,7 +9,7 @@ using Lyn.Protocol.Bolt9; using Lyn.Protocol.Common; using Lyn.Protocol.Common.Blockchain; -using Lyn.Protocol.Common.Hashing; +using Lyn.Protocol.Common.Crypto; using Lyn.Protocol.Common.Messages; using Lyn.Protocol.Connection; using Lyn.Types; diff --git a/src/Lyn.Protocol.Tests/Bolt2/ChannelEstablishment/FullChannelEstablishmentTest.cs b/src/Lyn.Protocol.Tests/Bolt2/ChannelEstablishment/FullChannelEstablishmentTest.cs index 777a6d0..8129b4d 100644 --- a/src/Lyn.Protocol.Tests/Bolt2/ChannelEstablishment/FullChannelEstablishmentTest.cs +++ b/src/Lyn.Protocol.Tests/Bolt2/ChannelEstablishment/FullChannelEstablishmentTest.cs @@ -12,7 +12,7 @@ using Lyn.Protocol.Bolt9; using Lyn.Protocol.Common; using Lyn.Protocol.Common.Blockchain; -using Lyn.Protocol.Common.Hashing; +using Lyn.Protocol.Common.Crypto; using Lyn.Protocol.Common.Messages; using Lyn.Protocol.Connection; using Lyn.Types; diff --git a/src/Lyn.Protocol.Tests/Bolt3/Bolt3CommitmentTestContext.cs b/src/Lyn.Protocol.Tests/Bolt3/Bolt3CommitmentTestContext.cs index 9443320..2b6fdd5 100644 --- a/src/Lyn.Protocol.Tests/Bolt3/Bolt3CommitmentTestContext.cs +++ b/src/Lyn.Protocol.Tests/Bolt3/Bolt3CommitmentTestContext.cs @@ -5,7 +5,7 @@ using Lyn.Protocol.Bolt3; using Lyn.Protocol.Bolt3.Types; using Lyn.Protocol.Common; -using Lyn.Protocol.Common.Hashing; +using Lyn.Protocol.Common.Crypto; using Lyn.Protocol.Common.Messages; using Lyn.Types; using Lyn.Types.Bitcoin; diff --git a/src/Lyn.Protocol/Bolt2/ChannelEstablishment/AcceptChannelMessageService.cs b/src/Lyn.Protocol/Bolt2/ChannelEstablishment/AcceptChannelMessageService.cs index 31c515c..42d5360 100644 --- a/src/Lyn.Protocol/Bolt2/ChannelEstablishment/AcceptChannelMessageService.cs +++ b/src/Lyn.Protocol/Bolt2/ChannelEstablishment/AcceptChannelMessageService.cs @@ -16,7 +16,7 @@ using Lyn.Protocol.Bolt1.Messages; using Lyn.Protocol.Bolt2.Wallet; using Lyn.Protocol.Bolt9; -using Lyn.Protocol.Common.Hashing; +using Lyn.Protocol.Common.Crypto; using Lyn.Types; using Lyn.Types.Fundamental; using Microsoft.Extensions.DependencyInjection; diff --git a/src/Lyn.Protocol/Common/Crypto/ITransactionHashCalculator.cs b/src/Lyn.Protocol/Common/Crypto/ITransactionHashCalculator.cs index e7f7bbb..22f4a1e 100644 --- a/src/Lyn.Protocol/Common/Crypto/ITransactionHashCalculator.cs +++ b/src/Lyn.Protocol/Common/Crypto/ITransactionHashCalculator.cs @@ -1,6 +1,6 @@ using Lyn.Types.Bitcoin; -namespace Lyn.Protocol.Common.Hashing +namespace Lyn.Protocol.Common.Crypto { public interface ITransactionHashCalculator { diff --git a/src/Lyn.Protocol/Common/Crypto/TransactionHashCalculator.cs b/src/Lyn.Protocol/Common/Crypto/TransactionHashCalculator.cs index 480cda7..b157368 100644 --- a/src/Lyn.Protocol/Common/Crypto/TransactionHashCalculator.cs +++ b/src/Lyn.Protocol/Common/Crypto/TransactionHashCalculator.cs @@ -1,10 +1,9 @@ using System; using System.Buffers; -using Lyn.Protocol.Common.Crypto; using Lyn.Types.Bitcoin; using Lyn.Types.Serialization; -namespace Lyn.Protocol.Common.Hashing +namespace Lyn.Protocol.Common.Crypto { public class TransactionHashCalculator : ITransactionHashCalculator { From 7889243f10d24fd7cc54461a849f980562b6e538 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" <99172754+pegasusjcrawford@users.noreply.github.com> Date: Tue, 8 Mar 2022 21:05:48 -0800 Subject: [PATCH 09/34] save wip onion stuff for great migration to new VM :c --- .../Bolt4/Messages/OnionMessageSerializer.cs | 13 ++- src/Lyn.Protocol/Bolt4/OnionMessageService.cs | 87 +++++++++++++++++++ 2 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 src/Lyn.Protocol/Bolt4/OnionMessageService.cs diff --git a/src/Lyn.Protocol/Bolt4/Messages/OnionMessageSerializer.cs b/src/Lyn.Protocol/Bolt4/Messages/OnionMessageSerializer.cs index ee4f29b..4e9b67f 100644 --- a/src/Lyn.Protocol/Bolt4/Messages/OnionMessageSerializer.cs +++ b/src/Lyn.Protocol/Bolt4/Messages/OnionMessageSerializer.cs @@ -26,14 +26,11 @@ public OnionMessage Deserialize(ref SequenceReader reader, ProtocolTypeSer var onionKeyBytes = reader.ReadBytes(33); onionMessage.OnionPacket.EphemeralKey = new PublicKey(onionKeyBytes.ToArray()); - // The payloadLength is computed by taking the total size of the packet and - // subtracting the length of the Sphinx header. The header fields are: - // - version - 1 byte - // - public key - 33 bytes - // - hmac - 32 bytes - // This value is cast to an integer, as the overall packet size should be - // Less than 32kb - int payloadLength = Convert.ToInt32(reader.Length - 66); + // does this even belong here? + if(!reader.TryPeek(out var peekedLength)) + { + // puke + } var payloadData = reader.ReadBytes(payloadLength); onionMessage.OnionPacket.PayloadData = payloadData.ToArray(); diff --git a/src/Lyn.Protocol/Bolt4/OnionMessageService.cs b/src/Lyn.Protocol/Bolt4/OnionMessageService.cs new file mode 100644 index 0000000..65911b6 --- /dev/null +++ b/src/Lyn.Protocol/Bolt4/OnionMessageService.cs @@ -0,0 +1,87 @@ +using Lyn.Protocol.Bolt1.Messages; +using Lyn.Protocol.Bolt4.Entities; +using Lyn.Protocol.Bolt4.Messages; +using Lyn.Protocol.Bolt8; +using Lyn.Protocol.Bolt9; +using Lyn.Protocol.Common.Messages; +using Lyn.Protocol.Connection; +using Lyn.Types.Fundamental; +using NaCl.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using NBitcoin.Secp256k1; +using Lyn.Protocol.Common.Crypto; + +namespace Lyn.Protocol.Bolt4 +{ + public class OnionMessageService : IBoltMessageService + { + private readonly IBoltFeatures _boltFeatures; + private readonly IEllipticCurveActions _ellipticCurveActions; + private readonly ICipherFunction _cipherFunctions; + private readonly ISphinx _sphinx; + + // todo: need an actual way to surface this! + private PrivateKey _nodePrivatekey; + + public OnionMessageService(IBoltFeatures boltFeatures, + IEllipticCurveActions ellipticCurveActions, + ICipherFunction cipherFunctions, + ISphinx sphinx) + { + _boltFeatures = boltFeatures; + _ellipticCurveActions = ellipticCurveActions; + _cipherFunctions = cipherFunctions; + _sphinx = sphinx; + } + + + // TODO: return DecryptedOnionPacket? + private void PeelOnion(PrivateKey privateKey, byte[]? associatedData, OnionRoutingPacket packet) + { + + var sharedSecret = _sphinx.ComputeSharedSecret(packet.EphemeralKey, privateKey); + var mu = _sphinx.GenerateSphinxKey("mu", sharedSecret); + var payloadToSign = associatedData != null ? packet.PayloadData.Concat(associatedData).ToArray() : packet.PayloadData; + var computedHmac = HashGenerator.HmacSha256(mu.ToArray(), payloadToSign); + + if (computedHmac == packet.Hmac) + { + var rho = _sphinx.GenerateSphinxKey("rho", sharedSecret); + var cipherStream = _sphinx.GenerateStream(rho.ToArray(), 2 * packet.PayloadData.Length); + // todo: better variable name here + var paddedPayload = packet.PayloadData.Concat(Enumerable.Range(0, packet.PayloadData.Length).Select(x => 0x00)).ToArray(); + var binData = _sphinx.ExclusiveOR(paddedPayload, cipherStream); + + // todo: peek payload length + + // todo: extract payload bytes from xor'd byte stream using payload length and hmac + } + else + { + throw new Exception("bad hmac"); + } + + } + + public Task ProcessMessageAsync(PeerMessage message) + { + if (_boltFeatures.SupportedFeatures.HasFlag(Features.OptionOnionMessagesRequired)) + { + // todo: verify payload bytes aren't too large + + // else, if payload is correct sized + var blindedPrivateKey = _sphinx.DeriveBlindedPrivateKey(_nodePrivatekey, message.MessagePayload.BlindingKey); + + // peel the onion + PeelOnion(blindedPrivateKey, null, message.MessagePayload.OnionPacket); + } + + throw new NotImplementedException(); + } + } +} From d9fb3bab04bcbb426ffff35a9fa96028ba6d5187 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" <99172754+pegasusjcrawford@users.noreply.github.com> Date: Thu, 10 Mar 2022 23:58:26 -0800 Subject: [PATCH 10/34] rework some more of the onion tests --- .../Bolt4/ByteStringHelper.cs | 20 ++++++++++ .../OnionRoutingPacketSerializerTests.cs | 37 ++++++++++++++++++ src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs | 39 ++++++++----------- 3 files changed, 73 insertions(+), 23 deletions(-) create mode 100644 src/Lyn.Protocol.Tests/Bolt4/ByteStringHelper.cs create mode 100644 src/Lyn.Protocol.Tests/Bolt4/OnionRoutingPacketSerializerTests.cs diff --git a/src/Lyn.Protocol.Tests/Bolt4/ByteStringHelper.cs b/src/Lyn.Protocol.Tests/Bolt4/ByteStringHelper.cs new file mode 100644 index 0000000..f1ec3c5 --- /dev/null +++ b/src/Lyn.Protocol.Tests/Bolt4/ByteStringHelper.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Lyn.Protocol.Tests.Bolt4 +{ + public static class ByteStringHelper + { + + public static byte[] StringToByteArray(string hex) + { + return Enumerable.Range(0, hex.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + .ToArray(); + } + } +} diff --git a/src/Lyn.Protocol.Tests/Bolt4/OnionRoutingPacketSerializerTests.cs b/src/Lyn.Protocol.Tests/Bolt4/OnionRoutingPacketSerializerTests.cs new file mode 100644 index 0000000..bc5c5a3 --- /dev/null +++ b/src/Lyn.Protocol.Tests/Bolt4/OnionRoutingPacketSerializerTests.cs @@ -0,0 +1,37 @@ +using Lyn.Protocol.Bolt4.Entities; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Lyn.Protocol.Tests.Bolt4 +{ + public class OnionRoutingPacketSerializerTests + { + + [Fact] + public void SmallOnion_Serialization() + { + var onionToSerialize = new OnionRoutingPacket(); + onionToSerialize.Version = 1; + onionToSerialize.EphemeralKey = ByteStringHelper.StringToByteArray("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991"); + onionToSerialize.PayloadData = ByteStringHelper.StringToByteArray("0012345679abcdef"); + onionToSerialize.Hmac = ByteStringHelper.StringToByteArray("0000111122223333444455556666777788889999aaaabbbbccccddddeeee0000"); + + var bufferWriter = new ArrayBufferWriter(256); + + var serializer = new OnionRoutingPacketSerializer(); + int bytesWritten = serializer.Serialize(onionToSerialize, bufferWriter, null); + var onionBytes = bufferWriter.WrittenSpan.ToArray(); + + Assert.Equal(bytesWritten, onionBytes.Length); + // todo: figure out what to do about varint length here?? + Assert.Equal(ByteStringHelper.StringToByteArray("01032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910012345679abcdef0000111122223333444455556666777788889999aaaabbbbccccddddeeee0000"), onionBytes); + } + + } +} diff --git a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs index 377c213..b587b69 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs @@ -14,13 +14,6 @@ namespace Lyn.Protocol.Tests.Bolt4 public class SphinxTests { - private byte[] StringToByteArray(string hex) - { - return Enumerable.Range(0, hex.Length) - .Where(x => x % 2 == 0) - .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) - .ToArray(); - } [Fact] public void ReferenceTestVector_GeneratesEphemeralKeysAndSecrets() @@ -29,14 +22,14 @@ public void ReferenceTestVector_GeneratesEphemeralKeysAndSecrets() var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); - var sessionKey = new PrivateKey(StringToByteArray("4141414141414141414141414141414141414141414141414141414141414141")); + var sessionKey = new PrivateKey(ByteStringHelper.StringToByteArray("4141414141414141414141414141414141414141414141414141414141414141")); var publicKeys = new List() { - new PublicKey(StringToByteArray("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), - new PublicKey(StringToByteArray("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), - new PublicKey(StringToByteArray("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), - new PublicKey(StringToByteArray("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), - new PublicKey(StringToByteArray("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) + new PublicKey(ByteStringHelper.StringToByteArray("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), + new PublicKey(ByteStringHelper.StringToByteArray("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), + new PublicKey(ByteStringHelper.StringToByteArray("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), + new PublicKey(ByteStringHelper.StringToByteArray("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), + new PublicKey(ByteStringHelper.StringToByteArray("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) }; var (ephemeralKeys, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(sessionKey, publicKeys); @@ -47,16 +40,16 @@ public void ReferenceTestVector_GeneratesEphemeralKeysAndSecrets() var keyList = ephemeralKeys.ToList(); var secretList = sharedSecrets.ToList(); - Assert.Equal(StringToByteArray("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"), keyList.ElementAt(0).GetSpan().ToArray()); - Assert.Equal(StringToByteArray("53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66"), secretList.ElementAt(0)); - Assert.Equal(StringToByteArray("028f9438bfbf7feac2e108d677e3a82da596be706cc1cf342b75c7b7e22bf4e6e2"), keyList.ElementAt(1).GetSpan().ToArray()); - Assert.Equal(StringToByteArray("a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae"), secretList.ElementAt(1)); - Assert.Equal(StringToByteArray("03bfd8225241ea71cd0843db7709f4c222f62ff2d4516fd38b39914ab6b83e0da0"), keyList.ElementAt(2).GetSpan().ToArray()); - Assert.Equal(StringToByteArray("3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc"), secretList.ElementAt(2)); - Assert.Equal(StringToByteArray("031dde6926381289671300239ea8e57ffaf9bebd05b9a5b95beaf07af05cd43595"), keyList.ElementAt(3).GetSpan().ToArray()); - Assert.Equal(StringToByteArray("21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d"), secretList.ElementAt(3)); - Assert.Equal(StringToByteArray("03a214ebd875aab6ddfd77f22c5e7311d7f77f17a169e599f157bbcdae8bf071f4"), keyList.ElementAt(4).GetSpan().ToArray()); - Assert.Equal(StringToByteArray("b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328"), secretList.ElementAt(4)); + Assert.Equal(ByteStringHelper.StringToByteArray("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"), keyList.ElementAt(0).GetSpan().ToArray()); + Assert.Equal(ByteStringHelper.StringToByteArray("53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66"), secretList.ElementAt(0)); + Assert.Equal(ByteStringHelper.StringToByteArray("028f9438bfbf7feac2e108d677e3a82da596be706cc1cf342b75c7b7e22bf4e6e2"), keyList.ElementAt(1).GetSpan().ToArray()); + Assert.Equal(ByteStringHelper.StringToByteArray("a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae"), secretList.ElementAt(1)); + Assert.Equal(ByteStringHelper.StringToByteArray("03bfd8225241ea71cd0843db7709f4c222f62ff2d4516fd38b39914ab6b83e0da0"), keyList.ElementAt(2).GetSpan().ToArray()); + Assert.Equal(ByteStringHelper.StringToByteArray("3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc"), secretList.ElementAt(2)); + Assert.Equal(ByteStringHelper.StringToByteArray("031dde6926381289671300239ea8e57ffaf9bebd05b9a5b95beaf07af05cd43595"), keyList.ElementAt(3).GetSpan().ToArray()); + Assert.Equal(ByteStringHelper.StringToByteArray("21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d"), secretList.ElementAt(3)); + Assert.Equal(ByteStringHelper.StringToByteArray("03a214ebd875aab6ddfd77f22c5e7311d7f77f17a169e599f157bbcdae8bf071f4"), keyList.ElementAt(4).GetSpan().ToArray()); + Assert.Equal(ByteStringHelper.StringToByteArray("b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328"), secretList.ElementAt(4)); } } From 432d3a014889683b0bd70ad9c02194ef539adbc7 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" <99172754+pegasusjcrawford@users.noreply.github.com> Date: Thu, 10 Mar 2022 23:59:14 -0800 Subject: [PATCH 11/34] refactor message & packet serialization --- .../Entities/OnionRoutingPacketSerializer.cs | 49 +++++++++++++++++++ .../Bolt4/Messages/OnionMessageSerializer.cs | 40 +++++++-------- 2 files changed, 67 insertions(+), 22 deletions(-) create mode 100644 src/Lyn.Protocol/Bolt4/Entities/OnionRoutingPacketSerializer.cs diff --git a/src/Lyn.Protocol/Bolt4/Entities/OnionRoutingPacketSerializer.cs b/src/Lyn.Protocol/Bolt4/Entities/OnionRoutingPacketSerializer.cs new file mode 100644 index 0000000..59b11c8 --- /dev/null +++ b/src/Lyn.Protocol/Bolt4/Entities/OnionRoutingPacketSerializer.cs @@ -0,0 +1,49 @@ +using Lyn.Types.Fundamental; +using Lyn.Types.Serialization; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Lyn.Protocol.Bolt4.Entities +{ + public class OnionRoutingPacketSerializer : IProtocolTypeSerializer + { + public OnionRoutingPacket Deserialize(ref SequenceReader reader, ProtocolTypeSerializerOptions? options = null) + { + var packet = new OnionRoutingPacket(); + + var verByte = reader.ReadByte(); + packet.Version = verByte; + + var onionKeyBytes = reader.ReadBytes(33); + packet.EphemeralKey = new PublicKey(onionKeyBytes.ToArray()); + + // note: maybe this is a safe cast? need to confirm in spec + // The Sphinx packet header contains a version (1 byte), a public key (33 bytes) and a mac (32 bytes) -> total 66 bytes + var payloadLength = (int)reader.Length - 66; + var payloadBytes = reader.ReadBytes(payloadLength); + + packet.PayloadData = payloadBytes.ToArray(); + + var hmacBytes = reader.ReadBytes(32); + packet.Hmac = hmacBytes.ToArray(); + + return packet; + } + + public int Serialize(OnionRoutingPacket typeInstance, IBufferWriter writer, ProtocolTypeSerializerOptions? options = null) + { + int bytesWritten = 0; + + bytesWritten += writer.WriteByte(typeInstance.Version); + bytesWritten += writer.WriteBytes(typeInstance.EphemeralKey); + bytesWritten += writer.WriteBytes(typeInstance.PayloadData); + bytesWritten += writer.WriteBytes(typeInstance.Hmac); + + return bytesWritten; + } + } +} diff --git a/src/Lyn.Protocol/Bolt4/Messages/OnionMessageSerializer.cs b/src/Lyn.Protocol/Bolt4/Messages/OnionMessageSerializer.cs index 4e9b67f..ffd7031 100644 --- a/src/Lyn.Protocol/Bolt4/Messages/OnionMessageSerializer.cs +++ b/src/Lyn.Protocol/Bolt4/Messages/OnionMessageSerializer.cs @@ -1,4 +1,5 @@ -using Lyn.Types.Fundamental; +using Lyn.Protocol.Bolt4.Entities; +using Lyn.Types.Fundamental; using Lyn.Types.Serialization; using System; using System.Buffers; @@ -9,8 +10,15 @@ namespace Lyn.Protocol.Bolt4.Messages { - internal class OnionMessageSerializer : IProtocolTypeSerializer + public class OnionMessageSerializer : IProtocolTypeSerializer { + private readonly IProtocolTypeSerializer packetSerializer; + + public OnionMessageSerializer(IProtocolTypeSerializer _packetSerializer) + { + this.packetSerializer = _packetSerializer; + } + public OnionMessage Deserialize(ref SequenceReader reader, ProtocolTypeSerializerOptions? options = null) { var onionMessage = new OnionMessage(); @@ -19,31 +27,19 @@ public OnionMessage Deserialize(ref SequenceReader reader, ProtocolTypeSer onionMessage.BlindingKey = new PublicKey(blindingKeyBytes.ToArray()); // read the onion packet data from the message - - var verByte = reader.ReadByte(); - onionMessage.OnionPacket.Version = verByte; - - var onionKeyBytes = reader.ReadBytes(33); - onionMessage.OnionPacket.EphemeralKey = new PublicKey(onionKeyBytes.ToArray()); - - // does this even belong here? - if(!reader.TryPeek(out var peekedLength)) - { - // puke - } - - var payloadData = reader.ReadBytes(payloadLength); - onionMessage.OnionPacket.PayloadData = payloadData.ToArray(); - - var hmacBytes = reader.ReadBytes(32); - onionMessage.OnionPacket.Hmac = hmacBytes.ToArray(); + onionMessage.OnionPacket = packetSerializer.Deserialize(ref reader, options); return onionMessage; } - public int Serialize(OnionMessage onionMessage, IBufferWriter writer, ProtocolTypeSerializerOptions? options = null) + public int Serialize(OnionMessage typeInstance, IBufferWriter writer, ProtocolTypeSerializerOptions? options = null) { - throw new NotImplementedException(); + int bytesWritten = 0; + + bytesWritten += writer.WriteBytes(typeInstance.BlindingKey); + bytesWritten += packetSerializer.Serialize(typeInstance.OnionPacket, writer, options); + + return bytesWritten; } } } From c911e0319962764154a71561d99798404ff7281d Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" <99172754+pegasusjcrawford@users.noreply.github.com> Date: Fri, 11 Mar 2022 00:23:30 -0800 Subject: [PATCH 12/34] more wip for peeling the onion message --- src/Lyn.Protocol/Bolt4/OnionMessageService.cs | 51 +++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/src/Lyn.Protocol/Bolt4/OnionMessageService.cs b/src/Lyn.Protocol/Bolt4/OnionMessageService.cs index 65911b6..bbff70a 100644 --- a/src/Lyn.Protocol/Bolt4/OnionMessageService.cs +++ b/src/Lyn.Protocol/Bolt4/OnionMessageService.cs @@ -15,11 +15,16 @@ using NBitcoin.Secp256k1; using Lyn.Protocol.Common.Crypto; +using System.Buffers; +using Lyn.Types.Serialization; namespace Lyn.Protocol.Bolt4 { public class OnionMessageService : IBoltMessageService { + //todo: move somewhere better + private const int MAC_LENGTH = 32; + private readonly IBoltFeatures _boltFeatures; private readonly IEllipticCurveActions _ellipticCurveActions; private readonly ICipherFunction _cipherFunctions; @@ -41,7 +46,7 @@ public OnionMessageService(IBoltFeatures boltFeatures, // TODO: return DecryptedOnionPacket? - private void PeelOnion(PrivateKey privateKey, byte[]? associatedData, OnionRoutingPacket packet) + private DecryptedOnionPacket PeelOnion(PrivateKey privateKey, byte[]? associatedData, OnionRoutingPacket packet) { var sharedSecret = _sphinx.ComputeSharedSecret(packet.EphemeralKey, privateKey); @@ -55,17 +60,55 @@ private void PeelOnion(PrivateKey privateKey, byte[]? associatedData, OnionRouti var cipherStream = _sphinx.GenerateStream(rho.ToArray(), 2 * packet.PayloadData.Length); // todo: better variable name here var paddedPayload = packet.PayloadData.Concat(Enumerable.Range(0, packet.PayloadData.Length).Select(x => 0x00)).ToArray(); - var binData = _sphinx.ExclusiveOR(paddedPayload, cipherStream); + var binData = _sphinx.ExclusiveOR(paddedPayload, cipherStream).ToArray(); + + var sequence = new ReadOnlySequence(new ReadOnlyMemory(binData)); + var binReader = new SequenceReader(sequence); // todo: peek payload length - - // todo: extract payload bytes from xor'd byte stream using payload length and hmac + if (binReader.TryPeek(out var payloadLength)) + { + int perHopPayloadLength = 0; + + if (payloadLength == 0x00) + { + // todo: this might be deprecated? do we need to support legacy payloads in Lyn? + perHopPayloadLength = 65; + } + else + { + // safe to truncate because a packet will never be larger than 64KB + perHopPayloadLength = (int)binReader.ReadVarInt(); + } + + // todo: extract payload bytes from xor'd byte stream using payload length and hmac + var perHopPayload = binReader.ReadBytes(perHopPayloadLength); + var hopHMAC = binReader.ReadBytes(MAC_LENGTH); + + // truncated'd again but its safe? + var nextOnionPayload = binReader.ReadBytes((int)binReader.Remaining); + var nextPublicKey = _sphinx.BlindKey(packet.EphemeralKey, _sphinx.ComputeBlindingFactor(packet.EphemeralKey, sharedSecret)); + + return new DecryptedOnionPacket() + { + Payload = perHopPayload.ToArray(), + NextPacket = new OnionRoutingPacket() + { + Version = 0x01, + EphemeralKey = nextPublicKey, + PayloadData = nextOnionPayload.ToArray(), + Hmac = hopHMAC.ToArray() + }, + SharedSecret = sharedSecret.ToArray(), + }; + } } else { throw new Exception("bad hmac"); } + throw new Exception("Bah! Humbug!"); } public Task ProcessMessageAsync(PeerMessage message) From 6961486525d123a7b43053527fad54291c236b0b Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" <99172754+pegasusjcrawford@users.noreply.github.com> Date: Fri, 11 Mar 2022 00:27:21 -0800 Subject: [PATCH 13/34] extract peelonion into the sphinx class --- src/Lyn.Protocol/Bolt4/ISphinx.cs | 4 +- src/Lyn.Protocol/Bolt4/OnionMessageService.cs | 72 +----------------- src/Lyn.Protocol/Bolt4/Sphinx.cs | 73 ++++++++++++++++++- 3 files changed, 76 insertions(+), 73 deletions(-) diff --git a/src/Lyn.Protocol/Bolt4/ISphinx.cs b/src/Lyn.Protocol/Bolt4/ISphinx.cs index 8566b9c..1b92756 100644 --- a/src/Lyn.Protocol/Bolt4/ISphinx.cs +++ b/src/Lyn.Protocol/Bolt4/ISphinx.cs @@ -1,4 +1,5 @@ -using Lyn.Types.Fundamental; +using Lyn.Protocol.Bolt4.Entities; +using Lyn.Types.Fundamental; using System; using System.Collections.Generic; @@ -17,5 +18,6 @@ public interface ISphinx ReadOnlySpan GenerateSphinxKey(byte[] keyType, ReadOnlySpan secret); ReadOnlySpan GenerateSphinxKey(string keyType, ReadOnlySpan secret); ReadOnlySpan GenerateStream(ReadOnlyMemory keyData, int streamLength); + DecryptedOnionPacket PeelOnion(PrivateKey privateKey, byte[]? associatedData, OnionRoutingPacket packet); } } \ No newline at end of file diff --git a/src/Lyn.Protocol/Bolt4/OnionMessageService.cs b/src/Lyn.Protocol/Bolt4/OnionMessageService.cs index bbff70a..49a3c04 100644 --- a/src/Lyn.Protocol/Bolt4/OnionMessageService.cs +++ b/src/Lyn.Protocol/Bolt4/OnionMessageService.cs @@ -22,9 +22,6 @@ namespace Lyn.Protocol.Bolt4 { public class OnionMessageService : IBoltMessageService { - //todo: move somewhere better - private const int MAC_LENGTH = 32; - private readonly IBoltFeatures _boltFeatures; private readonly IEllipticCurveActions _ellipticCurveActions; private readonly ICipherFunction _cipherFunctions; @@ -44,73 +41,6 @@ public OnionMessageService(IBoltFeatures boltFeatures, _sphinx = sphinx; } - - // TODO: return DecryptedOnionPacket? - private DecryptedOnionPacket PeelOnion(PrivateKey privateKey, byte[]? associatedData, OnionRoutingPacket packet) - { - - var sharedSecret = _sphinx.ComputeSharedSecret(packet.EphemeralKey, privateKey); - var mu = _sphinx.GenerateSphinxKey("mu", sharedSecret); - var payloadToSign = associatedData != null ? packet.PayloadData.Concat(associatedData).ToArray() : packet.PayloadData; - var computedHmac = HashGenerator.HmacSha256(mu.ToArray(), payloadToSign); - - if (computedHmac == packet.Hmac) - { - var rho = _sphinx.GenerateSphinxKey("rho", sharedSecret); - var cipherStream = _sphinx.GenerateStream(rho.ToArray(), 2 * packet.PayloadData.Length); - // todo: better variable name here - var paddedPayload = packet.PayloadData.Concat(Enumerable.Range(0, packet.PayloadData.Length).Select(x => 0x00)).ToArray(); - var binData = _sphinx.ExclusiveOR(paddedPayload, cipherStream).ToArray(); - - var sequence = new ReadOnlySequence(new ReadOnlyMemory(binData)); - var binReader = new SequenceReader(sequence); - - // todo: peek payload length - if (binReader.TryPeek(out var payloadLength)) - { - int perHopPayloadLength = 0; - - if (payloadLength == 0x00) - { - // todo: this might be deprecated? do we need to support legacy payloads in Lyn? - perHopPayloadLength = 65; - } - else - { - // safe to truncate because a packet will never be larger than 64KB - perHopPayloadLength = (int)binReader.ReadVarInt(); - } - - // todo: extract payload bytes from xor'd byte stream using payload length and hmac - var perHopPayload = binReader.ReadBytes(perHopPayloadLength); - var hopHMAC = binReader.ReadBytes(MAC_LENGTH); - - // truncated'd again but its safe? - var nextOnionPayload = binReader.ReadBytes((int)binReader.Remaining); - var nextPublicKey = _sphinx.BlindKey(packet.EphemeralKey, _sphinx.ComputeBlindingFactor(packet.EphemeralKey, sharedSecret)); - - return new DecryptedOnionPacket() - { - Payload = perHopPayload.ToArray(), - NextPacket = new OnionRoutingPacket() - { - Version = 0x01, - EphemeralKey = nextPublicKey, - PayloadData = nextOnionPayload.ToArray(), - Hmac = hopHMAC.ToArray() - }, - SharedSecret = sharedSecret.ToArray(), - }; - } - } - else - { - throw new Exception("bad hmac"); - } - - throw new Exception("Bah! Humbug!"); - } - public Task ProcessMessageAsync(PeerMessage message) { if (_boltFeatures.SupportedFeatures.HasFlag(Features.OptionOnionMessagesRequired)) @@ -121,7 +51,7 @@ public Task ProcessMessageAsync(PeerMessage blindingFactors) } } + // TODO: return DecryptedOnionPacket? + public DecryptedOnionPacket PeelOnion(PrivateKey privateKey, byte[]? associatedData, OnionRoutingPacket packet) + { + + var sharedSecret = ComputeSharedSecret(packet.EphemeralKey, privateKey); + var mu = GenerateSphinxKey("mu", sharedSecret); + var payloadToSign = associatedData != null ? packet.PayloadData.Concat(associatedData).ToArray() : packet.PayloadData; + var computedHmac = HashGenerator.HmacSha256(mu.ToArray(), payloadToSign); + + if (computedHmac == packet.Hmac) + { + var rho = GenerateSphinxKey("rho", sharedSecret); + var cipherStream = GenerateStream(rho.ToArray(), 2 * packet.PayloadData.Length); + // todo: better variable name here + var paddedPayload = packet.PayloadData.Concat(Enumerable.Range(0, packet.PayloadData.Length).Select(x => 0x00)).ToArray(); + var binData = ExclusiveOR(paddedPayload, cipherStream).ToArray(); + + var sequence = new ReadOnlySequence(new ReadOnlyMemory(binData)); + var binReader = new SequenceReader(sequence); + + // todo: peek payload length + if (binReader.TryPeek(out var payloadLength)) + { + int perHopPayloadLength = 0; + + if (payloadLength == 0x00) + { + // todo: this might be deprecated? do we need to support legacy payloads in Lyn? + perHopPayloadLength = 65; + } + else + { + // safe to truncate because a packet will never be larger than 64KB + perHopPayloadLength = (int)binReader.ReadVarInt(); + } + + // todo: extract payload bytes from xor'd byte stream using payload length and hmac + var perHopPayload = binReader.ReadBytes(perHopPayloadLength); + var hopHMAC = binReader.ReadBytes(MAC_LENGTH); + + // truncated'd again but its safe? + var nextOnionPayload = binReader.ReadBytes((int)binReader.Remaining); + var nextPublicKey = BlindKey(packet.EphemeralKey, ComputeBlindingFactor(packet.EphemeralKey, sharedSecret)); + + return new DecryptedOnionPacket() + { + Payload = perHopPayload.ToArray(), + NextPacket = new OnionRoutingPacket() + { + Version = 0x01, + EphemeralKey = nextPublicKey, + PayloadData = nextOnionPayload.ToArray(), + Hmac = hopHMAC.ToArray() + }, + SharedSecret = sharedSecret.ToArray(), + }; + } + } + else + { + throw new Exception("bad hmac"); + } + + throw new Exception("Bah! Humbug!"); + } } } From 870e0156140864f6539eb3c7245d8a829a08e9f5 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" <99172754+pegasusjcrawford@users.noreply.github.com> Date: Sat, 12 Mar 2022 14:34:11 -0800 Subject: [PATCH 14/34] fix the peekpayloadlength function & get generate filler passing somewhat --- .../{ByteStringHelper.cs => ByteArray.cs} | 4 +- .../OnionRoutingPacketSerializerTests.cs | 8 +- src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs | 123 ++++++++++++++--- .../Bolt4/Entities/PacketAndSecrets.cs | 15 +++ src/Lyn.Protocol/Bolt4/ISphinx.cs | 8 +- src/Lyn.Protocol/Bolt4/Sphinx.cs | 126 ++++++++++++------ 6 files changed, 221 insertions(+), 63 deletions(-) rename src/Lyn.Protocol.Tests/Bolt4/{ByteStringHelper.cs => ByteArray.cs} (80%) create mode 100644 src/Lyn.Protocol/Bolt4/Entities/PacketAndSecrets.cs diff --git a/src/Lyn.Protocol.Tests/Bolt4/ByteStringHelper.cs b/src/Lyn.Protocol.Tests/Bolt4/ByteArray.cs similarity index 80% rename from src/Lyn.Protocol.Tests/Bolt4/ByteStringHelper.cs rename to src/Lyn.Protocol.Tests/Bolt4/ByteArray.cs index f1ec3c5..993d554 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/ByteStringHelper.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/ByteArray.cs @@ -6,10 +6,10 @@ namespace Lyn.Protocol.Tests.Bolt4 { - public static class ByteStringHelper + public static class ByteArray { - public static byte[] StringToByteArray(string hex) + public static byte[] FromHex(string hex) { return Enumerable.Range(0, hex.Length) .Where(x => x % 2 == 0) diff --git a/src/Lyn.Protocol.Tests/Bolt4/OnionRoutingPacketSerializerTests.cs b/src/Lyn.Protocol.Tests/Bolt4/OnionRoutingPacketSerializerTests.cs index bc5c5a3..e0fe93d 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/OnionRoutingPacketSerializerTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/OnionRoutingPacketSerializerTests.cs @@ -18,9 +18,9 @@ public void SmallOnion_Serialization() { var onionToSerialize = new OnionRoutingPacket(); onionToSerialize.Version = 1; - onionToSerialize.EphemeralKey = ByteStringHelper.StringToByteArray("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991"); - onionToSerialize.PayloadData = ByteStringHelper.StringToByteArray("0012345679abcdef"); - onionToSerialize.Hmac = ByteStringHelper.StringToByteArray("0000111122223333444455556666777788889999aaaabbbbccccddddeeee0000"); + onionToSerialize.EphemeralKey = ByteArray.FromHex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991"); + onionToSerialize.PayloadData = ByteArray.FromHex("0012345679abcdef"); + onionToSerialize.Hmac = ByteArray.FromHex("0000111122223333444455556666777788889999aaaabbbbccccddddeeee0000"); var bufferWriter = new ArrayBufferWriter(256); @@ -30,7 +30,7 @@ public void SmallOnion_Serialization() Assert.Equal(bytesWritten, onionBytes.Length); // todo: figure out what to do about varint length here?? - Assert.Equal(ByteStringHelper.StringToByteArray("01032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910012345679abcdef0000111122223333444455556666777788889999aaaabbbbccccddddeeee0000"), onionBytes); + Assert.Equal(ByteArray.FromHex("004a01032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910012345679abcdef0000111122223333444455556666777788889999aaaabbbbccccddddeeee0000"), onionBytes); } } diff --git a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs index b587b69..e6adc7c 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs @@ -22,14 +22,14 @@ public void ReferenceTestVector_GeneratesEphemeralKeysAndSecrets() var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); - var sessionKey = new PrivateKey(ByteStringHelper.StringToByteArray("4141414141414141414141414141414141414141414141414141414141414141")); + var sessionKey = new PrivateKey(ByteArray.FromHex("4141414141414141414141414141414141414141414141414141414141414141")); var publicKeys = new List() { - new PublicKey(ByteStringHelper.StringToByteArray("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), - new PublicKey(ByteStringHelper.StringToByteArray("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), - new PublicKey(ByteStringHelper.StringToByteArray("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), - new PublicKey(ByteStringHelper.StringToByteArray("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), - new PublicKey(ByteStringHelper.StringToByteArray("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) + new PublicKey(ByteArray.FromHex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), + new PublicKey(ByteArray.FromHex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), + new PublicKey(ByteArray.FromHex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), + new PublicKey(ByteArray.FromHex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), + new PublicKey(ByteArray.FromHex("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) }; var (ephemeralKeys, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(sessionKey, publicKeys); @@ -40,16 +40,107 @@ public void ReferenceTestVector_GeneratesEphemeralKeysAndSecrets() var keyList = ephemeralKeys.ToList(); var secretList = sharedSecrets.ToList(); - Assert.Equal(ByteStringHelper.StringToByteArray("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"), keyList.ElementAt(0).GetSpan().ToArray()); - Assert.Equal(ByteStringHelper.StringToByteArray("53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66"), secretList.ElementAt(0)); - Assert.Equal(ByteStringHelper.StringToByteArray("028f9438bfbf7feac2e108d677e3a82da596be706cc1cf342b75c7b7e22bf4e6e2"), keyList.ElementAt(1).GetSpan().ToArray()); - Assert.Equal(ByteStringHelper.StringToByteArray("a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae"), secretList.ElementAt(1)); - Assert.Equal(ByteStringHelper.StringToByteArray("03bfd8225241ea71cd0843db7709f4c222f62ff2d4516fd38b39914ab6b83e0da0"), keyList.ElementAt(2).GetSpan().ToArray()); - Assert.Equal(ByteStringHelper.StringToByteArray("3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc"), secretList.ElementAt(2)); - Assert.Equal(ByteStringHelper.StringToByteArray("031dde6926381289671300239ea8e57ffaf9bebd05b9a5b95beaf07af05cd43595"), keyList.ElementAt(3).GetSpan().ToArray()); - Assert.Equal(ByteStringHelper.StringToByteArray("21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d"), secretList.ElementAt(3)); - Assert.Equal(ByteStringHelper.StringToByteArray("03a214ebd875aab6ddfd77f22c5e7311d7f77f17a169e599f157bbcdae8bf071f4"), keyList.ElementAt(4).GetSpan().ToArray()); - Assert.Equal(ByteStringHelper.StringToByteArray("b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328"), secretList.ElementAt(4)); + Assert.Equal(ByteArray.FromHex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"), keyList.ElementAt(0).GetSpan().ToArray()); + Assert.Equal(ByteArray.FromHex("53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66"), secretList.ElementAt(0)); + Assert.Equal(ByteArray.FromHex("028f9438bfbf7feac2e108d677e3a82da596be706cc1cf342b75c7b7e22bf4e6e2"), keyList.ElementAt(1).GetSpan().ToArray()); + Assert.Equal(ByteArray.FromHex("a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae"), secretList.ElementAt(1)); + Assert.Equal(ByteArray.FromHex("03bfd8225241ea71cd0843db7709f4c222f62ff2d4516fd38b39914ab6b83e0da0"), keyList.ElementAt(2).GetSpan().ToArray()); + Assert.Equal(ByteArray.FromHex("3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc"), secretList.ElementAt(2)); + Assert.Equal(ByteArray.FromHex("031dde6926381289671300239ea8e57ffaf9bebd05b9a5b95beaf07af05cd43595"), keyList.ElementAt(3).GetSpan().ToArray()); + Assert.Equal(ByteArray.FromHex("21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d"), secretList.ElementAt(3)); + Assert.Equal(ByteArray.FromHex("03a214ebd875aab6ddfd77f22c5e7311d7f77f17a169e599f157bbcdae8bf071f4"), keyList.ElementAt(4).GetSpan().ToArray()); + Assert.Equal(ByteArray.FromHex("b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328"), secretList.ElementAt(4)); + } + + [Fact] + public void GenerateFilter_ReferenceTestVector_FixedSizePayloads() + { + var curveActions = new EllipticCurveActions(); + var sphinx = new Sphinx(curveActions); + + var sessionKey = new PrivateKey(ByteArray.FromHex("4141414141414141414141414141414141414141414141414141414141414141")); + var publicKeys = new List() + { + new PublicKey(ByteArray.FromHex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), + new PublicKey(ByteArray.FromHex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), + new PublicKey(ByteArray.FromHex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), + new PublicKey(ByteArray.FromHex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), + new PublicKey(ByteArray.FromHex("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) + }; + + var referenceFixedSizePaymentPayloads = new List() { + ByteArray.FromHex("000000000000000000000000000000000000000000000000000000000000000000"), + ByteArray.FromHex("000101010101010101000000000000000100000001000000000000000000000000"), + ByteArray.FromHex("000202020202020202000000000000000200000002000000000000000000000000"), + ByteArray.FromHex("000303030303030303000000000000000300000003000000000000000000000000"), + ByteArray.FromHex("000404040404040404000000000000000400000004000000000000000000000000") + }; + + var (_, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(sessionKey, publicKeys); + var filler = sphinx.GenerateFiller("rho", + 1300, + sharedSecrets.Take(sharedSecrets.Count - 1), + referenceFixedSizePaymentPayloads.Take(referenceFixedSizePaymentPayloads.Count - 1)).ToArray(); + + var expectedFiller = ByteArray.FromHex("c6b008cf6414ed6e4c42c291eb505e9f22f5fe7d0ecdd15a833f4d016ac974d33adc6ea3293e20859e87ebfb937ba406abd025d14af692b12e9c9c2adbe307a679779259676211c071e614fdb386d1ff02db223a5b2fae03df68d321c7b29f7c7240edd3fa1b7cb6903f89dc01abf41b2eb0b49b6b8d73bb0774b58204c0d0e96d3cce45ad75406be0bc009e327b3e712a4bd178609c00b41da2daf8a4b0e1319f07a492ab4efb056f0f599f75e6dc7e0d10ce1cf59088ab6e873de377343880f7a24f0e36731a0b72092f8d5bc8cd346762e93b2bf203d00264e4bc136fc142de8f7b69154deb05854ea88e2d7506222c95ba1aab065c8a851391377d3406a35a9af3ac"); + Assert.Equal(expectedFiller, filler); + } + + [Fact] + public void GenerateFilter_ReferenceTestVector_VariableSizePayloads() + { + var curveActions = new EllipticCurveActions(); + var sphinx = new Sphinx(curveActions); + + var sessionKey = new PrivateKey(ByteArray.FromHex("4141414141414141414141414141414141414141414141414141414141414141")); + var publicKeys = new List() + { + new PublicKey(ByteArray.FromHex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), + new PublicKey(ByteArray.FromHex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), + new PublicKey(ByteArray.FromHex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), + new PublicKey(ByteArray.FromHex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), + new PublicKey(ByteArray.FromHex("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) + }; + + var referenceVariableSizePaymentPayloads = new List() { + ByteArray.FromHex("000000000000000000000000000000000000000000000000000000000000000000"), + ByteArray.FromHex("140101010101010101000000000000000100000001"), + ByteArray.FromHex("fd0100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"), + ByteArray.FromHex("140303030303030303000000000000000300000003"), + ByteArray.FromHex("000404040404040404000000000000000400000004000000000000000000000000") + }; + + var (_, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(sessionKey, publicKeys); + var filler = sphinx.GenerateFiller("rho", + 1300, + sharedSecrets.Take(sharedSecrets.Count - 1), + referenceVariableSizePaymentPayloads.Take(referenceVariableSizePaymentPayloads.Count - 1)).ToArray(); + + var expectedFiller = ByteArray.FromHex("b77d99c935d3f32469844f7e09340a91ded147557bdd0456c369f7e449587c0f5666faab58040146db49024db88553729bce12b860391c29c1779f022ae48a9cb314ca35d73fc91addc92632bcf7ba6fd9f38e6fd30fabcedbd5407b6648073c38331ee7ab0332f41f550c180e1601f8c25809ed75b3a1e78635a2ef1b828e92c9658e76e49f995d72cf9781eec0c838901d0bdde3ac21c13b4979ac9e738a1c4d0b9741d58e777ad1aed01263ad1390d36a18a6b92f4f799dcf75edbb43b7515e8d72cb4f827a9af0e7b9338d07b1a24e0305b5535f5b851b1144bad6238b9d9482b5ba6413f1aafac3cdde5067966ed8b78f7c1c5f916a05f874d5f17a2b7d0ae75d66a5f1bb6ff932570dc5a0cf3ce04eb5d26bc55c2057af1f8326e20a7d6f0ae644f09d00fac80de60f20aceee85be41a074d3e1dda017db79d0070b99f54736396f206ee3777abd4c00a4bb95c871750409261e3b01e59a3793a9c20159aae4988c68397a1443be6370fd9614e46108291e615691729faea58537209fa668a172d066d0efff9bc77c2bd34bd77870ad79effd80140990e36731a0b72092f8d5bc8cd346762e93b2bf203d00264e4bc136fc142de8f7b69154deb05854ea88e2d7506222c95ba1aab065c8a"); + Assert.Equal(expectedFiller, filler); + } + + [Fact] + public void PeekPerHopPayloadLength() + { + var curveActions = new EllipticCurveActions(); + var sphinx = new Sphinx(curveActions); + + var testCases = new[] + { + (ByteArray.FromHex("01"), 34), + (ByteArray.FromHex("08"), 41), + (ByteArray.FromHex("00"), 65), + (ByteArray.FromHex("fc"), 285), + (ByteArray.FromHex("fd00fd"), 288), + (ByteArray.FromHex("fdffff"), 65570) + }; + + foreach(var (payload, expected) in testCases) + { + var actual = sphinx.PeekPayloadLength(payload); + Assert.Equal(expected, actual); + } } } diff --git a/src/Lyn.Protocol/Bolt4/Entities/PacketAndSecrets.cs b/src/Lyn.Protocol/Bolt4/Entities/PacketAndSecrets.cs new file mode 100644 index 0000000..6c9c313 --- /dev/null +++ b/src/Lyn.Protocol/Bolt4/Entities/PacketAndSecrets.cs @@ -0,0 +1,15 @@ +using Lyn.Types.Fundamental; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Lyn.Protocol.Bolt4.Entities +{ + public struct PacketAndSecrets + { + public OnionRoutingPacket Packet { get; set; } + public IEnumerable<(byte[], PublicKey)> SharedSecrets { get; set; } + } +} diff --git a/src/Lyn.Protocol/Bolt4/ISphinx.cs b/src/Lyn.Protocol/Bolt4/ISphinx.cs index 1b92756..6b6b1bc 100644 --- a/src/Lyn.Protocol/Bolt4/ISphinx.cs +++ b/src/Lyn.Protocol/Bolt4/ISphinx.cs @@ -10,14 +10,16 @@ public interface ISphinx PublicKey BlindKey(PublicKey pubKey, IEnumerable blindingFactors); PublicKey BlindKey(PublicKey pubKey, ReadOnlySpan blindingFactor); ReadOnlySpan ComputeBlindingFactor(PublicKey pubKey, ReadOnlySpan secret); - (IEnumerable, IEnumerable) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, ICollection publicKeys); - (IEnumerable, IEnumerable) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, ICollection publicKeys, IList ephemeralPublicKeys, IList blindingFactors, IList sharedSecrets); + (IList, IList) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, ICollection publicKeys); + (IList, IList) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, ICollection publicKeys, IList ephemeralPublicKeys, IList blindingFactors, IList sharedSecrets); ReadOnlySpan ComputeSharedSecret(PublicKey publicKey, PrivateKey secret); PrivateKey DeriveBlindedPrivateKey(PrivateKey privateKey, PublicKey blindingEphemeralKey); ReadOnlySpan ExclusiveOR(ReadOnlySpan left, ReadOnlySpan right); ReadOnlySpan GenerateSphinxKey(byte[] keyType, ReadOnlySpan secret); ReadOnlySpan GenerateSphinxKey(string keyType, ReadOnlySpan secret); - ReadOnlySpan GenerateStream(ReadOnlyMemory keyData, int streamLength); + ReadOnlySpan GenerateStream(ReadOnlySpan keyData, int streamLength); + ReadOnlySpan GenerateFiller(string keyType, int packetPayloadLength, IEnumerable sharedSecrets, IEnumerable payloads); DecryptedOnionPacket PeelOnion(PrivateKey privateKey, byte[]? associatedData, OnionRoutingPacket packet); + PacketAndSecrets CreateOnion(PrivateKey sessionKey, int packetPayloadLength, IEnumerable publicKeys, IEnumerable payloads, byte[]? associatedData); } } \ No newline at end of file diff --git a/src/Lyn.Protocol/Bolt4/Sphinx.cs b/src/Lyn.Protocol/Bolt4/Sphinx.cs index d22be2f..f68fd24 100644 --- a/src/Lyn.Protocol/Bolt4/Sphinx.cs +++ b/src/Lyn.Protocol/Bolt4/Sphinx.cs @@ -6,6 +6,7 @@ using NBitcoin.Secp256k1; using System; using System.Buffers; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; @@ -66,9 +67,9 @@ public ReadOnlySpan ExclusiveOR(ReadOnlySpan left, ReadOnlySpan GenerateStream(ReadOnlyMemory keyData, int streamLength) + public ReadOnlySpan GenerateStream(ReadOnlySpan keyData, int streamLength) { - var cipher = new ChaCha20(keyData, 0); + var cipher = new ChaCha20(new ReadOnlyMemory(keyData.ToArray()), 0); var emptyPlainText = Enumerable.Range(0, streamLength).Select(x => 0x00).ToArray(); var nonce = Enumerable.Range(0, 12).Select(x => 0x00).ToArray(); return cipher.Encrypt(emptyPlainText, nonce); @@ -91,7 +92,7 @@ public PublicKey BlindKey(PublicKey pubKey, IEnumerable blindingFactors) return blindingFactors.Aggregate(pubKey, (key, blindingFactor) => BlindKey(key, blindingFactor)); } - public (IEnumerable, IEnumerable) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, + public (IList, IList) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, ICollection publicKeys) { // this seems inelegant as fuck? @@ -111,7 +112,7 @@ public PublicKey BlindKey(PublicKey pubKey, IEnumerable blindingFactors) sharedSecrets); } - public (IEnumerable, IEnumerable) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, + public (IList, IList) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, ICollection publicKeys, IList ephemeralPublicKeys, IList blindingFactors, @@ -139,6 +140,60 @@ public PublicKey BlindKey(PublicKey pubKey, IEnumerable blindingFactors) } } + public int PeekPayloadLength(byte[] payloadData) + { + var sequence = new ReadOnlySequence(new ReadOnlyMemory(payloadData)); + var binReader = new SequenceReader(sequence); + int perHopPayloadLength = 0; + + if (binReader.TryPeek(out var firstByte)) + { + if (firstByte == 0x00) + { + // todo: this might be deprecated? do we need to support legacy payloads in Lyn? + perHopPayloadLength = 65; + } + else + { + // safe to truncate because a packet will never be larger than 64KB? + perHopPayloadLength = (int)binReader.ReadBigSize(); + perHopPayloadLength += (int)binReader.Consumed; //offset the length by the number of bytes consoomed + perHopPayloadLength += MAC_LENGTH; + } + } + + return perHopPayloadLength; + } + + public ReadOnlySpan GenerateFiller(string keyType, int packetPayloadLength, IEnumerable sharedSecrets, IEnumerable payloads) + { + // todo: asserts + var secretsAndPayloads = sharedSecrets.Zip(payloads); + var padding = new List(); + var filler = secretsAndPayloads.Aggregate(padding, (padding, secretAndPayload) => + { + var (secret, perHopPayload) = secretAndPayload; + + // todo: decide how the hmac comes into play... + var perHopPayloadLength = PeekPayloadLength(perHopPayload); + + if (perHopPayloadLength != perHopPayload.Length + MAC_LENGTH) + { + throw new Exception("invalid length"); + } + + // assert payload length + var fillerKey = GenerateSphinxKey(Encoding.ASCII.GetBytes(keyType), secret); + // todo: byte array matching payload length + padding.AddRange(Enumerable.Range(0, perHopPayloadLength).Select(x => 0x00)); + var stream = GenerateStream(fillerKey, packetPayloadLength + perHopPayloadLength).ToArray().TakeLast(padding.Count).ToArray(); + var filler = ExclusiveOR(padding.ToArray(), stream).ToArray(); + return filler.ToList(); + }); + + return filler.ToArray(); + } + // TODO: return DecryptedOnionPacket? public DecryptedOnionPacket PeelOnion(PrivateKey privateKey, byte[]? associatedData, OnionRoutingPacket packet) { @@ -156,46 +211,32 @@ public DecryptedOnionPacket PeelOnion(PrivateKey privateKey, byte[]? associatedD var paddedPayload = packet.PayloadData.Concat(Enumerable.Range(0, packet.PayloadData.Length).Select(x => 0x00)).ToArray(); var binData = ExclusiveOR(paddedPayload, cipherStream).ToArray(); + var perHopPayloadLength = PeekPayloadLength(binData); + var sequence = new ReadOnlySequence(new ReadOnlyMemory(binData)); var binReader = new SequenceReader(sequence); - // todo: peek payload length - if (binReader.TryPeek(out var payloadLength)) - { - int perHopPayloadLength = 0; - - if (payloadLength == 0x00) - { - // todo: this might be deprecated? do we need to support legacy payloads in Lyn? - perHopPayloadLength = 65; - } - else - { - // safe to truncate because a packet will never be larger than 64KB - perHopPayloadLength = (int)binReader.ReadVarInt(); - } - - // todo: extract payload bytes from xor'd byte stream using payload length and hmac - var perHopPayload = binReader.ReadBytes(perHopPayloadLength); - var hopHMAC = binReader.ReadBytes(MAC_LENGTH); + // todo: extract payload bytes from xor'd byte stream using payload length and hmac + var perHopPayload = binReader.ReadBytes(perHopPayloadLength); + var hopHMAC = binReader.ReadBytes(MAC_LENGTH); - // truncated'd again but its safe? - var nextOnionPayload = binReader.ReadBytes((int)binReader.Remaining); - var nextPublicKey = BlindKey(packet.EphemeralKey, ComputeBlindingFactor(packet.EphemeralKey, sharedSecret)); + // truncated'd again but its safe? + var nextOnionPayload = binReader.ReadBytes((int)binReader.Remaining); + var nextPublicKey = BlindKey(packet.EphemeralKey, ComputeBlindingFactor(packet.EphemeralKey, sharedSecret)); - return new DecryptedOnionPacket() + return new DecryptedOnionPacket() + { + Payload = perHopPayload.ToArray(), + NextPacket = new OnionRoutingPacket() { - Payload = perHopPayload.ToArray(), - NextPacket = new OnionRoutingPacket() - { - Version = 0x01, - EphemeralKey = nextPublicKey, - PayloadData = nextOnionPayload.ToArray(), - Hmac = hopHMAC.ToArray() - }, - SharedSecret = sharedSecret.ToArray(), - }; - } + Version = 0x01, + EphemeralKey = nextPublicKey, + PayloadData = nextOnionPayload.ToArray(), + Hmac = hopHMAC.ToArray() + }, + SharedSecret = sharedSecret.ToArray(), + }; + } else { @@ -205,5 +246,14 @@ public DecryptedOnionPacket PeelOnion(PrivateKey privateKey, byte[]? associatedD throw new Exception("Bah! Humbug!"); } + public PacketAndSecrets CreateOnion(PrivateKey sessionKey, int packetPayloadLength, IEnumerable publicKeys, IEnumerable payloads, byte[]? associatedData) + { + // todo: verify size + + return new PacketAndSecrets() + { + + }; + } } } From 9b3e3642b0f60487b29b65bfe16130a4ee807784 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Sat, 19 Mar 2022 22:58:58 -0700 Subject: [PATCH 15/34] Get the tests for the create & peel of an onion packet using the fixed length reference test vectors --- src/Lyn.Protocol.Tests/Bolt4/ByteArray.cs | 20 --- .../OnionRoutingPacketSerializerTests.cs | 8 +- src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs | 164 ++++++++++++------ .../Bolt4/Entities/OnionRoutingPacket.cs | 21 +++ src/Lyn.Protocol/Bolt4/ISphinx.cs | 4 +- src/Lyn.Protocol/Bolt4/Sphinx.cs | 121 +++++++++++-- .../Common/Crypto/HashGenerator.cs | 4 +- 7 files changed, 251 insertions(+), 91 deletions(-) delete mode 100644 src/Lyn.Protocol.Tests/Bolt4/ByteArray.cs diff --git a/src/Lyn.Protocol.Tests/Bolt4/ByteArray.cs b/src/Lyn.Protocol.Tests/Bolt4/ByteArray.cs deleted file mode 100644 index 993d554..0000000 --- a/src/Lyn.Protocol.Tests/Bolt4/ByteArray.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Lyn.Protocol.Tests.Bolt4 -{ - public static class ByteArray - { - - public static byte[] FromHex(string hex) - { - return Enumerable.Range(0, hex.Length) - .Where(x => x % 2 == 0) - .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) - .ToArray(); - } - } -} diff --git a/src/Lyn.Protocol.Tests/Bolt4/OnionRoutingPacketSerializerTests.cs b/src/Lyn.Protocol.Tests/Bolt4/OnionRoutingPacketSerializerTests.cs index e0fe93d..e4c5350 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/OnionRoutingPacketSerializerTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/OnionRoutingPacketSerializerTests.cs @@ -18,9 +18,9 @@ public void SmallOnion_Serialization() { var onionToSerialize = new OnionRoutingPacket(); onionToSerialize.Version = 1; - onionToSerialize.EphemeralKey = ByteArray.FromHex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991"); - onionToSerialize.PayloadData = ByteArray.FromHex("0012345679abcdef"); - onionToSerialize.Hmac = ByteArray.FromHex("0000111122223333444455556666777788889999aaaabbbbccccddddeeee0000"); + onionToSerialize.EphemeralKey = Convert.FromHexString("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991"); + onionToSerialize.PayloadData = Convert.FromHexString("0012345679abcdef"); + onionToSerialize.Hmac = Convert.FromHexString("0000111122223333444455556666777788889999aaaabbbbccccddddeeee0000"); var bufferWriter = new ArrayBufferWriter(256); @@ -30,7 +30,7 @@ public void SmallOnion_Serialization() Assert.Equal(bytesWritten, onionBytes.Length); // todo: figure out what to do about varint length here?? - Assert.Equal(ByteArray.FromHex("004a01032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910012345679abcdef0000111122223333444455556666777788889999aaaabbbbccccddddeeee0000"), onionBytes); + Assert.Equal(Convert.FromHexString("004a01032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e6686809910012345679abcdef0000111122223333444455556666777788889999aaaabbbbccccddddeeee0000"), onionBytes); } } diff --git a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs index e6adc7c..28455dd 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs @@ -22,14 +22,14 @@ public void ReferenceTestVector_GeneratesEphemeralKeysAndSecrets() var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); - var sessionKey = new PrivateKey(ByteArray.FromHex("4141414141414141414141414141414141414141414141414141414141414141")); + var sessionKey = new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")); var publicKeys = new List() { - new PublicKey(ByteArray.FromHex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), - new PublicKey(ByteArray.FromHex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), - new PublicKey(ByteArray.FromHex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), - new PublicKey(ByteArray.FromHex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), - new PublicKey(ByteArray.FromHex("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) + new PublicKey(Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), + new PublicKey(Convert.FromHexString("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), + new PublicKey(Convert.FromHexString("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), + new PublicKey(Convert.FromHexString("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), + new PublicKey(Convert.FromHexString("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) }; var (ephemeralKeys, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(sessionKey, publicKeys); @@ -40,16 +40,16 @@ public void ReferenceTestVector_GeneratesEphemeralKeysAndSecrets() var keyList = ephemeralKeys.ToList(); var secretList = sharedSecrets.ToList(); - Assert.Equal(ByteArray.FromHex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"), keyList.ElementAt(0).GetSpan().ToArray()); - Assert.Equal(ByteArray.FromHex("53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66"), secretList.ElementAt(0)); - Assert.Equal(ByteArray.FromHex("028f9438bfbf7feac2e108d677e3a82da596be706cc1cf342b75c7b7e22bf4e6e2"), keyList.ElementAt(1).GetSpan().ToArray()); - Assert.Equal(ByteArray.FromHex("a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae"), secretList.ElementAt(1)); - Assert.Equal(ByteArray.FromHex("03bfd8225241ea71cd0843db7709f4c222f62ff2d4516fd38b39914ab6b83e0da0"), keyList.ElementAt(2).GetSpan().ToArray()); - Assert.Equal(ByteArray.FromHex("3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc"), secretList.ElementAt(2)); - Assert.Equal(ByteArray.FromHex("031dde6926381289671300239ea8e57ffaf9bebd05b9a5b95beaf07af05cd43595"), keyList.ElementAt(3).GetSpan().ToArray()); - Assert.Equal(ByteArray.FromHex("21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d"), secretList.ElementAt(3)); - Assert.Equal(ByteArray.FromHex("03a214ebd875aab6ddfd77f22c5e7311d7f77f17a169e599f157bbcdae8bf071f4"), keyList.ElementAt(4).GetSpan().ToArray()); - Assert.Equal(ByteArray.FromHex("b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328"), secretList.ElementAt(4)); + Assert.Equal(Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"), keyList.ElementAt(0).GetSpan().ToArray()); + Assert.Equal(Convert.FromHexString("53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66"), secretList.ElementAt(0)); + Assert.Equal(Convert.FromHexString("028f9438bfbf7feac2e108d677e3a82da596be706cc1cf342b75c7b7e22bf4e6e2"), keyList.ElementAt(1).GetSpan().ToArray()); + Assert.Equal(Convert.FromHexString("a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae"), secretList.ElementAt(1)); + Assert.Equal(Convert.FromHexString("03bfd8225241ea71cd0843db7709f4c222f62ff2d4516fd38b39914ab6b83e0da0"), keyList.ElementAt(2).GetSpan().ToArray()); + Assert.Equal(Convert.FromHexString("3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc"), secretList.ElementAt(2)); + Assert.Equal(Convert.FromHexString("031dde6926381289671300239ea8e57ffaf9bebd05b9a5b95beaf07af05cd43595"), keyList.ElementAt(3).GetSpan().ToArray()); + Assert.Equal(Convert.FromHexString("21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d"), secretList.ElementAt(3)); + Assert.Equal(Convert.FromHexString("03a214ebd875aab6ddfd77f22c5e7311d7f77f17a169e599f157bbcdae8bf071f4"), keyList.ElementAt(4).GetSpan().ToArray()); + Assert.Equal(Convert.FromHexString("b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328"), secretList.ElementAt(4)); } [Fact] @@ -58,31 +58,31 @@ public void GenerateFilter_ReferenceTestVector_FixedSizePayloads() var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); - var sessionKey = new PrivateKey(ByteArray.FromHex("4141414141414141414141414141414141414141414141414141414141414141")); + var sessionKey = new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")); var publicKeys = new List() { - new PublicKey(ByteArray.FromHex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), - new PublicKey(ByteArray.FromHex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), - new PublicKey(ByteArray.FromHex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), - new PublicKey(ByteArray.FromHex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), - new PublicKey(ByteArray.FromHex("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) + new PublicKey(Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), + new PublicKey(Convert.FromHexString("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), + new PublicKey(Convert.FromHexString("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), + new PublicKey(Convert.FromHexString("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), + new PublicKey(Convert.FromHexString("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) }; var referenceFixedSizePaymentPayloads = new List() { - ByteArray.FromHex("000000000000000000000000000000000000000000000000000000000000000000"), - ByteArray.FromHex("000101010101010101000000000000000100000001000000000000000000000000"), - ByteArray.FromHex("000202020202020202000000000000000200000002000000000000000000000000"), - ByteArray.FromHex("000303030303030303000000000000000300000003000000000000000000000000"), - ByteArray.FromHex("000404040404040404000000000000000400000004000000000000000000000000") + Convert.FromHexString("000000000000000000000000000000000000000000000000000000000000000000"), + Convert.FromHexString("000101010101010101000000000000000100000001000000000000000000000000"), + Convert.FromHexString("000202020202020202000000000000000200000002000000000000000000000000"), + Convert.FromHexString("000303030303030303000000000000000300000003000000000000000000000000"), + Convert.FromHexString("000404040404040404000000000000000400000004000000000000000000000000") }; var (_, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(sessionKey, publicKeys); var filler = sphinx.GenerateFiller("rho", 1300, - sharedSecrets.Take(sharedSecrets.Count - 1), - referenceFixedSizePaymentPayloads.Take(referenceFixedSizePaymentPayloads.Count - 1)).ToArray(); + sharedSecrets.SkipLast(1), + referenceFixedSizePaymentPayloads.SkipLast(1)).ToArray(); - var expectedFiller = ByteArray.FromHex("c6b008cf6414ed6e4c42c291eb505e9f22f5fe7d0ecdd15a833f4d016ac974d33adc6ea3293e20859e87ebfb937ba406abd025d14af692b12e9c9c2adbe307a679779259676211c071e614fdb386d1ff02db223a5b2fae03df68d321c7b29f7c7240edd3fa1b7cb6903f89dc01abf41b2eb0b49b6b8d73bb0774b58204c0d0e96d3cce45ad75406be0bc009e327b3e712a4bd178609c00b41da2daf8a4b0e1319f07a492ab4efb056f0f599f75e6dc7e0d10ce1cf59088ab6e873de377343880f7a24f0e36731a0b72092f8d5bc8cd346762e93b2bf203d00264e4bc136fc142de8f7b69154deb05854ea88e2d7506222c95ba1aab065c8a851391377d3406a35a9af3ac"); + var expectedFiller = Convert.FromHexString("c6b008cf6414ed6e4c42c291eb505e9f22f5fe7d0ecdd15a833f4d016ac974d33adc6ea3293e20859e87ebfb937ba406abd025d14af692b12e9c9c2adbe307a679779259676211c071e614fdb386d1ff02db223a5b2fae03df68d321c7b29f7c7240edd3fa1b7cb6903f89dc01abf41b2eb0b49b6b8d73bb0774b58204c0d0e96d3cce45ad75406be0bc009e327b3e712a4bd178609c00b41da2daf8a4b0e1319f07a492ab4efb056f0f599f75e6dc7e0d10ce1cf59088ab6e873de377343880f7a24f0e36731a0b72092f8d5bc8cd346762e93b2bf203d00264e4bc136fc142de8f7b69154deb05854ea88e2d7506222c95ba1aab065c8a851391377d3406a35a9af3ac"); Assert.Equal(expectedFiller, filler); } @@ -92,31 +92,31 @@ public void GenerateFilter_ReferenceTestVector_VariableSizePayloads() var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); - var sessionKey = new PrivateKey(ByteArray.FromHex("4141414141414141414141414141414141414141414141414141414141414141")); + var sessionKey = new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")); var publicKeys = new List() { - new PublicKey(ByteArray.FromHex("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), - new PublicKey(ByteArray.FromHex("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), - new PublicKey(ByteArray.FromHex("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), - new PublicKey(ByteArray.FromHex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), - new PublicKey(ByteArray.FromHex("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) + new PublicKey(Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), + new PublicKey(Convert.FromHexString("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), + new PublicKey(Convert.FromHexString("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), + new PublicKey(Convert.FromHexString("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), + new PublicKey(Convert.FromHexString("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) }; var referenceVariableSizePaymentPayloads = new List() { - ByteArray.FromHex("000000000000000000000000000000000000000000000000000000000000000000"), - ByteArray.FromHex("140101010101010101000000000000000100000001"), - ByteArray.FromHex("fd0100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"), - ByteArray.FromHex("140303030303030303000000000000000300000003"), - ByteArray.FromHex("000404040404040404000000000000000400000004000000000000000000000000") + Convert.FromHexString("000000000000000000000000000000000000000000000000000000000000000000"), + Convert.FromHexString("140101010101010101000000000000000100000001"), + Convert.FromHexString("fd0100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"), + Convert.FromHexString("140303030303030303000000000000000300000003"), + Convert.FromHexString("000404040404040404000000000000000400000004000000000000000000000000") }; var (_, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(sessionKey, publicKeys); var filler = sphinx.GenerateFiller("rho", 1300, - sharedSecrets.Take(sharedSecrets.Count - 1), - referenceVariableSizePaymentPayloads.Take(referenceVariableSizePaymentPayloads.Count - 1)).ToArray(); + sharedSecrets.SkipLast(1), + referenceVariableSizePaymentPayloads.SkipLast(1)).ToArray(); - var expectedFiller = ByteArray.FromHex("b77d99c935d3f32469844f7e09340a91ded147557bdd0456c369f7e449587c0f5666faab58040146db49024db88553729bce12b860391c29c1779f022ae48a9cb314ca35d73fc91addc92632bcf7ba6fd9f38e6fd30fabcedbd5407b6648073c38331ee7ab0332f41f550c180e1601f8c25809ed75b3a1e78635a2ef1b828e92c9658e76e49f995d72cf9781eec0c838901d0bdde3ac21c13b4979ac9e738a1c4d0b9741d58e777ad1aed01263ad1390d36a18a6b92f4f799dcf75edbb43b7515e8d72cb4f827a9af0e7b9338d07b1a24e0305b5535f5b851b1144bad6238b9d9482b5ba6413f1aafac3cdde5067966ed8b78f7c1c5f916a05f874d5f17a2b7d0ae75d66a5f1bb6ff932570dc5a0cf3ce04eb5d26bc55c2057af1f8326e20a7d6f0ae644f09d00fac80de60f20aceee85be41a074d3e1dda017db79d0070b99f54736396f206ee3777abd4c00a4bb95c871750409261e3b01e59a3793a9c20159aae4988c68397a1443be6370fd9614e46108291e615691729faea58537209fa668a172d066d0efff9bc77c2bd34bd77870ad79effd80140990e36731a0b72092f8d5bc8cd346762e93b2bf203d00264e4bc136fc142de8f7b69154deb05854ea88e2d7506222c95ba1aab065c8a"); + var expectedFiller = Convert.FromHexString("b77d99c935d3f32469844f7e09340a91ded147557bdd0456c369f7e449587c0f5666faab58040146db49024db88553729bce12b860391c29c1779f022ae48a9cb314ca35d73fc91addc92632bcf7ba6fd9f38e6fd30fabcedbd5407b6648073c38331ee7ab0332f41f550c180e1601f8c25809ed75b3a1e78635a2ef1b828e92c9658e76e49f995d72cf9781eec0c838901d0bdde3ac21c13b4979ac9e738a1c4d0b9741d58e777ad1aed01263ad1390d36a18a6b92f4f799dcf75edbb43b7515e8d72cb4f827a9af0e7b9338d07b1a24e0305b5535f5b851b1144bad6238b9d9482b5ba6413f1aafac3cdde5067966ed8b78f7c1c5f916a05f874d5f17a2b7d0ae75d66a5f1bb6ff932570dc5a0cf3ce04eb5d26bc55c2057af1f8326e20a7d6f0ae644f09d00fac80de60f20aceee85be41a074d3e1dda017db79d0070b99f54736396f206ee3777abd4c00a4bb95c871750409261e3b01e59a3793a9c20159aae4988c68397a1443be6370fd9614e46108291e615691729faea58537209fa668a172d066d0efff9bc77c2bd34bd77870ad79effd80140990e36731a0b72092f8d5bc8cd346762e93b2bf203d00264e4bc136fc142de8f7b69154deb05854ea88e2d7506222c95ba1aab065c8a"); Assert.Equal(expectedFiller, filler); } @@ -128,12 +128,12 @@ public void PeekPerHopPayloadLength() var testCases = new[] { - (ByteArray.FromHex("01"), 34), - (ByteArray.FromHex("08"), 41), - (ByteArray.FromHex("00"), 65), - (ByteArray.FromHex("fc"), 285), - (ByteArray.FromHex("fd00fd"), 288), - (ByteArray.FromHex("fdffff"), 65570) + (Convert.FromHexString("01"), 34), + (Convert.FromHexString("08"), 41), + (Convert.FromHexString("00"), 65), + (Convert.FromHexString("fc"), 285), + (Convert.FromHexString("fd00fd"), 288), + (Convert.FromHexString("fdffff"), 65570) }; foreach(var (payload, expected) in testCases) @@ -143,5 +143,69 @@ public void PeekPerHopPayloadLength() } } + [Fact] + public void CreateOnion_ReferenceTestVector_FixedSizePayload() + { + var curveActions = new EllipticCurveActions(); + var sphinx = new Sphinx(curveActions); + + var sessionKey = new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")); + var publicKeys = new List() + { + new PublicKey(Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), + new PublicKey(Convert.FromHexString("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), + new PublicKey(Convert.FromHexString("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), + new PublicKey(Convert.FromHexString("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), + new PublicKey(Convert.FromHexString("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) + }; + + var privateKeys = new List() + { + new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")), + new PrivateKey(Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242")), + new PrivateKey(Convert.FromHexString("4343434343434343434343434343434343434343434343434343434343434343")), + new PrivateKey(Convert.FromHexString("4444444444444444444444444444444444444444444444444444444444444444")), + new PrivateKey(Convert.FromHexString("4545454545454545454545454545454545454545454545454545454545454545")) + }; + + var referenceFixedSizePaymentPayloads = new List() { + Convert.FromHexString("000000000000000000000000000000000000000000000000000000000000000000"), + Convert.FromHexString("000101010101010101000000000000000100000001000000000000000000000000"), + Convert.FromHexString("000202020202020202000000000000000200000002000000000000000000000000"), + Convert.FromHexString("000303030303030303000000000000000300000003000000000000000000000000"), + Convert.FromHexString("000404040404040404000000000000000400000004000000000000000000000000") + }; + + var associatedData = Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242"); + + var encryptedOnion = sphinx.CreateOnion(sessionKey, 1300, publicKeys, referenceFixedSizePaymentPayloads, associatedData); + var sharedSecrets = encryptedOnion.SharedSecrets.ToArray(); + + var decrypted0 = sphinx.PeelOnion(privateKeys[0], associatedData, encryptedOnion.Packet); + Assert.Equal(referenceFixedSizePaymentPayloads[0], decrypted0.Payload); + var (secret0, _) = sharedSecrets[0]; + Assert.Equal(secret0, decrypted0.SharedSecret); + + var decrypted1 = sphinx.PeelOnion(privateKeys[1], associatedData, decrypted0.NextPacket); + var (secret1, _) = sharedSecrets[1]; + Assert.Equal(referenceFixedSizePaymentPayloads[1], decrypted1.Payload); + Assert.Equal(secret1, decrypted1.SharedSecret); + + var decrypted2 = sphinx.PeelOnion(privateKeys[2], associatedData, decrypted1.NextPacket); + var (secret2, _) = sharedSecrets[2]; + Assert.Equal(referenceFixedSizePaymentPayloads[2], decrypted2.Payload); + Assert.Equal(secret2, decrypted2.SharedSecret); + + var decrypted3 = sphinx.PeelOnion(privateKeys[3], associatedData, decrypted2.NextPacket); + var (secret3, _) = sharedSecrets[3]; + Assert.Equal(referenceFixedSizePaymentPayloads[3], decrypted3.Payload); + Assert.Equal(secret3, decrypted3.SharedSecret); + + var decrypted4 = sphinx.PeelOnion(privateKeys[4], associatedData, decrypted3.NextPacket); + var (secret4, _) = sharedSecrets[4]; + Assert.Equal(referenceFixedSizePaymentPayloads[4], decrypted4.Payload); + Assert.Equal(secret4, decrypted4.SharedSecret); + } + } } diff --git a/src/Lyn.Protocol/Bolt4/Entities/OnionRoutingPacket.cs b/src/Lyn.Protocol/Bolt4/Entities/OnionRoutingPacket.cs index 6fd6065..e545634 100644 --- a/src/Lyn.Protocol/Bolt4/Entities/OnionRoutingPacket.cs +++ b/src/Lyn.Protocol/Bolt4/Entities/OnionRoutingPacket.cs @@ -1,5 +1,6 @@ using Lyn.Types.Fundamental; using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Text; @@ -18,5 +19,25 @@ public class OnionRoutingPacket public byte[] Hmac { get; set; } + public static implicit operator ReadOnlySpan(OnionRoutingPacket packet) + { + var packetBytes = new List() { packet.Version }; + packetBytes.AddRange(packet.EphemeralKey.GetSpan().ToArray()); + packetBytes.AddRange(packet.PayloadData); + packetBytes.AddRange(packet.Hmac); + return packetBytes.ToArray(); + } + + public static implicit operator OnionRoutingPacket(ReadOnlySpan packetData) + { + var parsedPacket = new OnionRoutingPacket(); + // note: fixed sphinx header sizes + var payloadLength = packetData.Length - 66; + parsedPacket.Version = packetData[0]; + parsedPacket.EphemeralKey = new PublicKey(packetData.Slice(1, 32).ToArray()); + parsedPacket.PayloadData = packetData.Slice(33, payloadLength).ToArray(); + parsedPacket.Hmac = packetData.Slice(payloadLength, 32).ToArray(); + return parsedPacket; + } } } diff --git a/src/Lyn.Protocol/Bolt4/ISphinx.cs b/src/Lyn.Protocol/Bolt4/ISphinx.cs index 6b6b1bc..71373d3 100644 --- a/src/Lyn.Protocol/Bolt4/ISphinx.cs +++ b/src/Lyn.Protocol/Bolt4/ISphinx.cs @@ -10,8 +10,8 @@ public interface ISphinx PublicKey BlindKey(PublicKey pubKey, IEnumerable blindingFactors); PublicKey BlindKey(PublicKey pubKey, ReadOnlySpan blindingFactor); ReadOnlySpan ComputeBlindingFactor(PublicKey pubKey, ReadOnlySpan secret); - (IList, IList) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, ICollection publicKeys); - (IList, IList) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, ICollection publicKeys, IList ephemeralPublicKeys, IList blindingFactors, IList sharedSecrets); + (IList, IList) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, IEnumerable publicKeys); + (IList, IList) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, IEnumerable publicKeys, IList ephemeralPublicKeys, IList blindingFactors, IList sharedSecrets); ReadOnlySpan ComputeSharedSecret(PublicKey publicKey, PrivateKey secret); PrivateKey DeriveBlindedPrivateKey(PrivateKey privateKey, PublicKey blindingEphemeralKey); ReadOnlySpan ExclusiveOR(ReadOnlySpan left, ReadOnlySpan right); diff --git a/src/Lyn.Protocol/Bolt4/Sphinx.cs b/src/Lyn.Protocol/Bolt4/Sphinx.cs index f68fd24..81465c8 100644 --- a/src/Lyn.Protocol/Bolt4/Sphinx.cs +++ b/src/Lyn.Protocol/Bolt4/Sphinx.cs @@ -8,6 +8,7 @@ using System.Buffers; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -17,6 +18,7 @@ namespace Lyn.Protocol.Bolt4 public class Sphinx : ISphinx { //todo: move somewhere better + private const int SPHINX_VERSION = 0; private const int MAC_LENGTH = 32; private readonly IEllipticCurveActions _ellipticCurveActions; @@ -93,7 +95,7 @@ public PublicKey BlindKey(PublicKey pubKey, IEnumerable blindingFactors) } public (IList, IList) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, - ICollection publicKeys) + IEnumerable publicKeys) { // this seems inelegant as fuck? var key = new ECPubKey(EC.G, null); @@ -113,12 +115,12 @@ public PublicKey BlindKey(PublicKey pubKey, IEnumerable blindingFactors) } public (IList, IList) ComputeEphemeralPublicKeysAndSharedSecrets(PrivateKey sessionKey, - ICollection publicKeys, - IList ephemeralPublicKeys, - IList blindingFactors, - IList sharedSecrets) + IEnumerable publicKeys, + IList ephemeralPublicKeys, + IList blindingFactors, + IList sharedSecrets) { - if (publicKeys.Count == 0) + if (!publicKeys.Any()) { return (ephemeralPublicKeys, sharedSecrets); } @@ -165,7 +167,10 @@ public int PeekPayloadLength(byte[] payloadData) return perHopPayloadLength; } - public ReadOnlySpan GenerateFiller(string keyType, int packetPayloadLength, IEnumerable sharedSecrets, IEnumerable payloads) + public ReadOnlySpan GenerateFiller(string keyType, + int packetPayloadLength, + IEnumerable sharedSecrets, + IEnumerable payloads) { // todo: asserts var secretsAndPayloads = sharedSecrets.Zip(payloads); @@ -202,8 +207,9 @@ public DecryptedOnionPacket PeelOnion(PrivateKey privateKey, byte[]? associatedD var mu = GenerateSphinxKey("mu", sharedSecret); var payloadToSign = associatedData != null ? packet.PayloadData.Concat(associatedData).ToArray() : packet.PayloadData; var computedHmac = HashGenerator.HmacSha256(mu.ToArray(), payloadToSign); - - if (computedHmac == packet.Hmac) + Debug.WriteLine($"computedHmac: {Convert.ToHexString(computedHmac)}"); + Debug.WriteLine($"packet.Hmac: {Convert.ToHexString(packet.Hmac)}"); + if (computedHmac.SequenceEqual(packet.Hmac)) { var rho = GenerateSphinxKey("rho", sharedSecret); var cipherStream = GenerateStream(rho.ToArray(), 2 * packet.PayloadData.Length); @@ -217,11 +223,11 @@ public DecryptedOnionPacket PeelOnion(PrivateKey privateKey, byte[]? associatedD var binReader = new SequenceReader(sequence); // todo: extract payload bytes from xor'd byte stream using payload length and hmac - var perHopPayload = binReader.ReadBytes(perHopPayloadLength); + var perHopPayload = binReader.ReadBytes(perHopPayloadLength - MAC_LENGTH); var hopHMAC = binReader.ReadBytes(MAC_LENGTH); // truncated'd again but its safe? - var nextOnionPayload = binReader.ReadBytes((int)binReader.Remaining); + var nextOnionPayload = binReader.ReadBytes((int)packet.PayloadData.Length); var nextPublicKey = BlindKey(packet.EphemeralKey, ComputeBlindingFactor(packet.EphemeralKey, sharedSecret)); return new DecryptedOnionPacket() @@ -246,13 +252,102 @@ public DecryptedOnionPacket PeelOnion(PrivateKey privateKey, byte[]? associatedD throw new Exception("Bah! Humbug!"); } - public PacketAndSecrets CreateOnion(PrivateKey sessionKey, int packetPayloadLength, IEnumerable publicKeys, IEnumerable payloads, byte[]? associatedData) + private OnionRoutingPacket WrapOnion(ReadOnlySpan payload, + PublicKey ephemeralPublicKey, + ReadOnlySpan sharedSecret, + T packet, + byte[]? associatedData, + byte[]? filler = null) { // todo: verify size + int packetPayloadLength = 0; + byte[] currentHmac = null; + byte[] currentPayload = null; + if (packet is OnionRoutingPacket onionPacket) + { + packetPayloadLength = onionPacket.PayloadData.Length; + currentPayload = onionPacket.PayloadData; + currentHmac = onionPacket.Hmac; + } + else if (packet is byte[] arr) + { + packetPayloadLength = arr.Length; + currentPayload = arr; + currentHmac = Enumerable.Range(0, MAC_LENGTH).Select(x => 0x00).ToArray(); + } + else + { + throw new Exception("unsupported packet type specified for T"); + } - return new PacketAndSecrets() + // onionPayload1 + var nextOnionPayload = payload.ToArray().Concat(currentHmac).Concat(currentPayload.SkipLast(payload.Length + MAC_LENGTH)).ToArray(); + var secondPayloadStream = GenerateStream(GenerateSphinxKey("rho", sharedSecret), packetPayloadLength); + nextOnionPayload = ExclusiveOR(nextOnionPayload, secondPayloadStream).ToArray(); + + if (filler != null) + { + nextOnionPayload = nextOnionPayload.SkipLast(filler.Length).Concat(filler).ToArray(); + } + + var hmacBytes = nextOnionPayload; + if(associatedData != null) + { + hmacBytes = nextOnionPayload.Concat(associatedData).ToArray(); + } + + var nextHmac = HashGenerator.HmacSha256(GenerateSphinxKey("mu", sharedSecret).ToArray(), hmacBytes); + Debug.WriteLine($"Computed HMAC: {Convert.ToHexString(nextHmac)}"); + var nextPacket = new OnionRoutingPacket() { + Version = SPHINX_VERSION, + EphemeralKey = ephemeralPublicKey, + PayloadData = nextOnionPayload, + Hmac = nextHmac.ToArray() + }; + return nextPacket; + } + + private OnionRoutingPacket RecursivelyCreateOnion(IEnumerable payloads, + IEnumerable publicKeys, + IEnumerable sharedSecrets, + OnionRoutingPacket packet, + byte[]? associatedData) + { + if (!payloads.Any()) + { + return packet; + } + else + { + var nextPacket = WrapOnion(payloads.Last(), publicKeys.Last(), sharedSecrets.Last(), packet, associatedData); + return RecursivelyCreateOnion(payloads.SkipLast(1), publicKeys.SkipLast(1), sharedSecrets.SkipLast(1), nextPacket, associatedData); + } + } + + public PacketAndSecrets CreateOnion(PrivateKey sessionKey, + int packetPayloadLength, + IEnumerable publicKeys, + IEnumerable payloads, + byte[]? associatedData) + { + // todo: verify size of inputs to make sure nothing is too long + var (ephemeralPublicKeys, sharedSecrets) = ComputeEphemeralPublicKeysAndSharedSecrets(sessionKey, publicKeys); + var filler = GenerateFiller("rho", + packetPayloadLength, + sharedSecrets.SkipLast(1), + payloads.SkipLast(1)).ToArray(); + // generate the last packet of the route + var startingBytes = GenerateStream(GenerateSphinxKey("pad", sessionKey), packetPayloadLength).ToArray(); + var lastPacket = WrapOnion(payloads.Last(), ephemeralPublicKeys.Last(), sharedSecrets.Last(), startingBytes, associatedData, filler); + + var onionPacket = RecursivelyCreateOnion(payloads.SkipLast(1), ephemeralPublicKeys.SkipLast(1), sharedSecrets.SkipLast(1), lastPacket, associatedData); + + return new PacketAndSecrets() + { + Packet = onionPacket, + SharedSecrets = sharedSecrets.Zip(ephemeralPublicKeys) }; } } diff --git a/src/Lyn.Protocol/Common/Crypto/HashGenerator.cs b/src/Lyn.Protocol/Common/Crypto/HashGenerator.cs index 34f4269..5c6544b 100644 --- a/src/Lyn.Protocol/Common/Crypto/HashGenerator.cs +++ b/src/Lyn.Protocol/Common/Crypto/HashGenerator.cs @@ -57,9 +57,9 @@ public static UInt256 DoubleSha512AsUInt256(ReadOnlySpan data) return new UInt256(result.Slice(0, 32)); } - public static ReadOnlySpan HmacSha256(byte[] key, ReadOnlySpan data) + public static ReadOnlySpan HmacSha256(ReadOnlySpan key, ReadOnlySpan data) { - using var hmac = new HMACSHA256(key); + using var hmac = new HMACSHA256(key.ToArray()); Span result = new byte[32]; if (!hmac.TryComputeHash(data, result, out _)) throw new HashGeneratorException($"Failed to perform {nameof(HmacSha256)}"); return result; From 0189011379691fe706fb0b2320ec97d082c1bf9c Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Sat, 19 Mar 2022 23:16:58 -0700 Subject: [PATCH 16/34] add two of the variable length sphinx tests --- src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs | 130 +++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs index 28455dd..70bd609 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs @@ -144,7 +144,7 @@ public void PeekPerHopPayloadLength() } [Fact] - public void CreateOnion_ReferenceTestVector_FixedSizePayload() + public void CreateOnion_ReferenceTestVector_FixedLengthPayloads() { var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); @@ -207,5 +207,133 @@ public void CreateOnion_ReferenceTestVector_FixedSizePayload() Assert.Equal(secret4, decrypted4.SharedSecret); } + [Fact] + public void CreateOnion_ReferenceTestVector_VariableLengthPayloads() + { + var curveActions = new EllipticCurveActions(); + var sphinx = new Sphinx(curveActions); + + var sessionKey = new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")); + var publicKeys = new List() + { + new PublicKey(Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), + new PublicKey(Convert.FromHexString("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), + new PublicKey(Convert.FromHexString("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), + new PublicKey(Convert.FromHexString("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), + new PublicKey(Convert.FromHexString("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) + }; + + var privateKeys = new List() + { + new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")), + new PrivateKey(Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242")), + new PrivateKey(Convert.FromHexString("4343434343434343434343434343434343434343434343434343434343434343")), + new PrivateKey(Convert.FromHexString("4444444444444444444444444444444444444444444444444444444444444444")), + new PrivateKey(Convert.FromHexString("4545454545454545454545454545454545454545454545454545454545454545")) + }; + + var referenceVariableSizePaymentPayloads = new List() { + Convert.FromHexString("000000000000000000000000000000000000000000000000000000000000000000"), + Convert.FromHexString("140101010101010101000000000000000100000001"), + Convert.FromHexString("fd0100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"), + Convert.FromHexString("140303030303030303000000000000000300000003"), + Convert.FromHexString("000404040404040404000000000000000400000004000000000000000000000000") + }; + + var associatedData = Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242"); + + var encryptedOnion = sphinx.CreateOnion(sessionKey, 1300, publicKeys, referenceVariableSizePaymentPayloads, associatedData); + var sharedSecrets = encryptedOnion.SharedSecrets.ToArray(); + + var decrypted0 = sphinx.PeelOnion(privateKeys[0], associatedData, encryptedOnion.Packet); + var (secret0, _) = sharedSecrets[0]; + Assert.Equal(referenceVariableSizePaymentPayloads[0], decrypted0.Payload); + Assert.Equal(secret0, decrypted0.SharedSecret); + + var decrypted1 = sphinx.PeelOnion(privateKeys[1], associatedData, decrypted0.NextPacket); + var (secret1, _) = sharedSecrets[1]; + Assert.Equal(referenceVariableSizePaymentPayloads[1], decrypted1.Payload); + Assert.Equal(secret1, decrypted1.SharedSecret); + + var decrypted2 = sphinx.PeelOnion(privateKeys[2], associatedData, decrypted1.NextPacket); + var (secret2, _) = sharedSecrets[2]; + Assert.Equal(referenceVariableSizePaymentPayloads[2], decrypted2.Payload); + Assert.Equal(secret2, decrypted2.SharedSecret); + + var decrypted3 = sphinx.PeelOnion(privateKeys[3], associatedData, decrypted2.NextPacket); + var (secret3, _) = sharedSecrets[3]; + Assert.Equal(referenceVariableSizePaymentPayloads[3], decrypted3.Payload); + Assert.Equal(secret3, decrypted3.SharedSecret); + + var decrypted4 = sphinx.PeelOnion(privateKeys[4], associatedData, decrypted3.NextPacket); + var (secret4, _) = sharedSecrets[4]; + Assert.Equal(referenceVariableSizePaymentPayloads[4], decrypted4.Payload); + Assert.Equal(secret4, decrypted4.SharedSecret); + } + + [Fact] + public void CreateOnion_ReferenceTestVector_VariableLengthFullPayloads() + { + var curveActions = new EllipticCurveActions(); + var sphinx = new Sphinx(curveActions); + + var sessionKey = new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")); + var publicKeys = new List() + { + new PublicKey(Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), + new PublicKey(Convert.FromHexString("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), + new PublicKey(Convert.FromHexString("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), + new PublicKey(Convert.FromHexString("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), + new PublicKey(Convert.FromHexString("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) + }; + + var privateKeys = new List() + { + new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")), + new PrivateKey(Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242")), + new PrivateKey(Convert.FromHexString("4343434343434343434343434343434343434343434343434343434343434343")), + new PrivateKey(Convert.FromHexString("4444444444444444444444444444444444444444444444444444444444444444")), + new PrivateKey(Convert.FromHexString("4545454545454545454545454545454545454545454545454545454545454545")) + }; + + var referenceVariableSizePaymentPayloadsFull = new List() { + Convert.FromHexString("8b09000000000000000030000000000000000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000250000000000000000000000000000000000000000000000002500000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000"), + Convert.FromHexString("fd012a08000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000000000000000000000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000000000000000000000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000000000000000000000000000000000000"), + Convert.FromHexString("620800000000000000900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + Convert.FromHexString("fc120000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000"), + Convert.FromHexString("fd01582200000000000000000000000000000000000000000022000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000") + }; + + var associatedData = Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242"); + + var encryptedOnion = sphinx.CreateOnion(sessionKey, 1300, publicKeys, referenceVariableSizePaymentPayloadsFull, associatedData); + var sharedSecrets = encryptedOnion.SharedSecrets.ToArray(); + + var decrypted0 = sphinx.PeelOnion(privateKeys[0], associatedData, encryptedOnion.Packet); + var (secret0, _) = sharedSecrets[0]; + Assert.Equal(referenceVariableSizePaymentPayloadsFull[0], decrypted0.Payload); + Assert.Equal(secret0, decrypted0.SharedSecret); + + var decrypted1 = sphinx.PeelOnion(privateKeys[1], associatedData, decrypted0.NextPacket); + var (secret1, _) = sharedSecrets[1]; + Assert.Equal(referenceVariableSizePaymentPayloadsFull[1], decrypted1.Payload); + Assert.Equal(secret1, decrypted1.SharedSecret); + + var decrypted2 = sphinx.PeelOnion(privateKeys[2], associatedData, decrypted1.NextPacket); + var (secret2, _) = sharedSecrets[2]; + Assert.Equal(referenceVariableSizePaymentPayloadsFull[2], decrypted2.Payload); + Assert.Equal(secret2, decrypted2.SharedSecret); + + var decrypted3 = sphinx.PeelOnion(privateKeys[3], associatedData, decrypted2.NextPacket); + var (secret3, _) = sharedSecrets[3]; + Assert.Equal(referenceVariableSizePaymentPayloadsFull[3], decrypted3.Payload); + Assert.Equal(secret3, decrypted3.SharedSecret); + + var decrypted4 = sphinx.PeelOnion(privateKeys[4], associatedData, decrypted3.NextPacket); + var (secret4, _) = sharedSecrets[4]; + Assert.Equal(referenceVariableSizePaymentPayloadsFull[4], decrypted4.Payload); + Assert.Equal(secret4, decrypted4.SharedSecret); + } + } } From 380e9bc7d90086b0ac09a8287429c9ed531789de Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Thu, 8 Sep 2022 22:44:25 -0700 Subject: [PATCH 17/34] refactor the sphinx tests to pull refernce vectors from a class for now --- .../Bolt4/Data/SphinxReferenceVectors.cs | 61 +++++ src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs | 233 ++++-------------- 2 files changed, 112 insertions(+), 182 deletions(-) create mode 100644 src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs diff --git a/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs b/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs new file mode 100644 index 0000000..58545f3 --- /dev/null +++ b/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Lyn.Types.Fundamental; + +namespace Lyn.Protocol.Tests.Bolt4.Data +{ + public static class SphinxReferenceVectors + { + public static PrivateKey SessionKey = new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")); + + public static List PrivateKeys = new List() { + new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")), + new PrivateKey(Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242")), + new PrivateKey(Convert.FromHexString("4343434343434343434343434343434343434343434343434343434343434343")), + new PrivateKey(Convert.FromHexString("4444444444444444444444444444444444444444444444444444444444444444")), + new PrivateKey(Convert.FromHexString("4545454545454545454545454545454545454545454545454545454545454545")) + }; + + public static List PublicKeys = new List() { + new PublicKey(Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), + new PublicKey(Convert.FromHexString("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), + new PublicKey(Convert.FromHexString("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), + new PublicKey(Convert.FromHexString("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), + new PublicKey(Convert.FromHexString("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) + }; + + public static List FixedSizePaymentPayloads = new List() { + Convert.FromHexString("000000000000000000000000000000000000000000000000000000000000000000"), + Convert.FromHexString("000101010101010101000000000000000100000001000000000000000000000000"), + Convert.FromHexString("000202020202020202000000000000000200000002000000000000000000000000"), + Convert.FromHexString("000303030303030303000000000000000300000003000000000000000000000000"), + Convert.FromHexString("000404040404040404000000000000000400000004000000000000000000000000") + }; + + public static List VariableSizePaymentPayloads = new List() { + Convert.FromHexString("000000000000000000000000000000000000000000000000000000000000000000"), + Convert.FromHexString("140101010101010101000000000000000100000001"), + Convert.FromHexString("fd0100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"), + Convert.FromHexString("140303030303030303000000000000000300000003"), + Convert.FromHexString("000404040404040404000000000000000400000004000000000000000000000000") + }; + + public static List VariableSizePaymentPayloadsFull = new List() { + Convert.FromHexString("8b09000000000000000030000000000000000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000250000000000000000000000000000000000000000000000002500000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000"), + Convert.FromHexString("fd012a08000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000000000000000000000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000000000000000000000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000000000000000000000000000000000000"), + Convert.FromHexString("620800000000000000900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + Convert.FromHexString("fc120000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000"), + Convert.FromHexString("fd01582200000000000000000000000000000000000000000022000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000") + }; + + public static byte[] AssociatedData = Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242"); + + public static byte[] FixedSizePayload_ExpectedFiller = Convert.FromHexString("c6b008cf6414ed6e4c42c291eb505e9f22f5fe7d0ecdd15a833f4d016ac974d33adc6ea3293e20859e87ebfb937ba406abd025d14af692b12e9c9c2adbe307a679779259676211c071e614fdb386d1ff02db223a5b2fae03df68d321c7b29f7c7240edd3fa1b7cb6903f89dc01abf41b2eb0b49b6b8d73bb0774b58204c0d0e96d3cce45ad75406be0bc009e327b3e712a4bd178609c00b41da2daf8a4b0e1319f07a492ab4efb056f0f599f75e6dc7e0d10ce1cf59088ab6e873de377343880f7a24f0e36731a0b72092f8d5bc8cd346762e93b2bf203d00264e4bc136fc142de8f7b69154deb05854ea88e2d7506222c95ba1aab065c8a851391377d3406a35a9af3ac"); + public static byte[] VariableSizePayload_ExpectedFiller = Convert.FromHexString("b77d99c935d3f32469844f7e09340a91ded147557bdd0456c369f7e449587c0f5666faab58040146db49024db88553729bce12b860391c29c1779f022ae48a9cb314ca35d73fc91addc92632bcf7ba6fd9f38e6fd30fabcedbd5407b6648073c38331ee7ab0332f41f550c180e1601f8c25809ed75b3a1e78635a2ef1b828e92c9658e76e49f995d72cf9781eec0c838901d0bdde3ac21c13b4979ac9e738a1c4d0b9741d58e777ad1aed01263ad1390d36a18a6b92f4f799dcf75edbb43b7515e8d72cb4f827a9af0e7b9338d07b1a24e0305b5535f5b851b1144bad6238b9d9482b5ba6413f1aafac3cdde5067966ed8b78f7c1c5f916a05f874d5f17a2b7d0ae75d66a5f1bb6ff932570dc5a0cf3ce04eb5d26bc55c2057af1f8326e20a7d6f0ae644f09d00fac80de60f20aceee85be41a074d3e1dda017db79d0070b99f54736396f206ee3777abd4c00a4bb95c871750409261e3b01e59a3793a9c20159aae4988c68397a1443be6370fd9614e46108291e615691729faea58537209fa668a172d066d0efff9bc77c2bd34bd77870ad79effd80140990e36731a0b72092f8d5bc8cd346762e93b2bf203d00264e4bc136fc142de8f7b69154deb05854ea88e2d7506222c95ba1aab065c8a"); + + } +} diff --git a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs index 70bd609..ea6f8f5 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs @@ -1,14 +1,11 @@ using Lyn.Protocol.Bolt4; using Lyn.Protocol.Common.Crypto; -using Lyn.Types.Fundamental; using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; +using Lyn.Protocol.Tests.Bolt4.Data; + namespace Lyn.Protocol.Tests.Bolt4 { public class SphinxTests @@ -18,21 +15,11 @@ public class SphinxTests [Fact] public void ReferenceTestVector_GeneratesEphemeralKeysAndSecrets() { - // todo: make this test not suck? var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); - var sessionKey = new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")); - var publicKeys = new List() - { - new PublicKey(Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), - new PublicKey(Convert.FromHexString("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), - new PublicKey(Convert.FromHexString("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), - new PublicKey(Convert.FromHexString("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), - new PublicKey(Convert.FromHexString("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) - }; - - var (ephemeralKeys, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(sessionKey, publicKeys); + var (ephemeralKeys, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(SphinxReferenceVectors.SessionKey, + SphinxReferenceVectors.PublicKeys); Assert.NotNull(ephemeralKeys); Assert.NotNull(sharedSecrets); @@ -58,32 +45,14 @@ public void GenerateFilter_ReferenceTestVector_FixedSizePayloads() var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); - var sessionKey = new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")); - var publicKeys = new List() - { - new PublicKey(Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), - new PublicKey(Convert.FromHexString("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), - new PublicKey(Convert.FromHexString("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), - new PublicKey(Convert.FromHexString("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), - new PublicKey(Convert.FromHexString("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) - }; - - var referenceFixedSizePaymentPayloads = new List() { - Convert.FromHexString("000000000000000000000000000000000000000000000000000000000000000000"), - Convert.FromHexString("000101010101010101000000000000000100000001000000000000000000000000"), - Convert.FromHexString("000202020202020202000000000000000200000002000000000000000000000000"), - Convert.FromHexString("000303030303030303000000000000000300000003000000000000000000000000"), - Convert.FromHexString("000404040404040404000000000000000400000004000000000000000000000000") - }; - - var (_, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(sessionKey, publicKeys); + var (_, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(SphinxReferenceVectors.SessionKey, + SphinxReferenceVectors.PublicKeys); var filler = sphinx.GenerateFiller("rho", 1300, - sharedSecrets.SkipLast(1), - referenceFixedSizePaymentPayloads.SkipLast(1)).ToArray(); + sharedSecrets.SkipLast(1), + SphinxReferenceVectors.FixedSizePaymentPayloads.SkipLast(1)).ToArray(); - var expectedFiller = Convert.FromHexString("c6b008cf6414ed6e4c42c291eb505e9f22f5fe7d0ecdd15a833f4d016ac974d33adc6ea3293e20859e87ebfb937ba406abd025d14af692b12e9c9c2adbe307a679779259676211c071e614fdb386d1ff02db223a5b2fae03df68d321c7b29f7c7240edd3fa1b7cb6903f89dc01abf41b2eb0b49b6b8d73bb0774b58204c0d0e96d3cce45ad75406be0bc009e327b3e712a4bd178609c00b41da2daf8a4b0e1319f07a492ab4efb056f0f599f75e6dc7e0d10ce1cf59088ab6e873de377343880f7a24f0e36731a0b72092f8d5bc8cd346762e93b2bf203d00264e4bc136fc142de8f7b69154deb05854ea88e2d7506222c95ba1aab065c8a851391377d3406a35a9af3ac"); - Assert.Equal(expectedFiller, filler); + Assert.Equal(SphinxReferenceVectors.FixedSizePayload_ExpectedFiller, filler); } [Fact] @@ -92,32 +61,14 @@ public void GenerateFilter_ReferenceTestVector_VariableSizePayloads() var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); - var sessionKey = new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")); - var publicKeys = new List() - { - new PublicKey(Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), - new PublicKey(Convert.FromHexString("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), - new PublicKey(Convert.FromHexString("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), - new PublicKey(Convert.FromHexString("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), - new PublicKey(Convert.FromHexString("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) - }; - - var referenceVariableSizePaymentPayloads = new List() { - Convert.FromHexString("000000000000000000000000000000000000000000000000000000000000000000"), - Convert.FromHexString("140101010101010101000000000000000100000001"), - Convert.FromHexString("fd0100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"), - Convert.FromHexString("140303030303030303000000000000000300000003"), - Convert.FromHexString("000404040404040404000000000000000400000004000000000000000000000000") - }; - - var (_, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(sessionKey, publicKeys); + var (_, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(SphinxReferenceVectors.SessionKey, + SphinxReferenceVectors.PublicKeys); var filler = sphinx.GenerateFiller("rho", 1300, sharedSecrets.SkipLast(1), - referenceVariableSizePaymentPayloads.SkipLast(1)).ToArray(); + SphinxReferenceVectors.VariableSizePaymentPayloads.SkipLast(1)).ToArray(); - var expectedFiller = Convert.FromHexString("b77d99c935d3f32469844f7e09340a91ded147557bdd0456c369f7e449587c0f5666faab58040146db49024db88553729bce12b860391c29c1779f022ae48a9cb314ca35d73fc91addc92632bcf7ba6fd9f38e6fd30fabcedbd5407b6648073c38331ee7ab0332f41f550c180e1601f8c25809ed75b3a1e78635a2ef1b828e92c9658e76e49f995d72cf9781eec0c838901d0bdde3ac21c13b4979ac9e738a1c4d0b9741d58e777ad1aed01263ad1390d36a18a6b92f4f799dcf75edbb43b7515e8d72cb4f827a9af0e7b9338d07b1a24e0305b5535f5b851b1144bad6238b9d9482b5ba6413f1aafac3cdde5067966ed8b78f7c1c5f916a05f874d5f17a2b7d0ae75d66a5f1bb6ff932570dc5a0cf3ce04eb5d26bc55c2057af1f8326e20a7d6f0ae644f09d00fac80de60f20aceee85be41a074d3e1dda017db79d0070b99f54736396f206ee3777abd4c00a4bb95c871750409261e3b01e59a3793a9c20159aae4988c68397a1443be6370fd9614e46108291e615691729faea58537209fa668a172d066d0efff9bc77c2bd34bd77870ad79effd80140990e36731a0b72092f8d5bc8cd346762e93b2bf203d00264e4bc136fc142de8f7b69154deb05854ea88e2d7506222c95ba1aab065c8a"); - Assert.Equal(expectedFiller, filler); + Assert.Equal(SphinxReferenceVectors.VariableSizePayload_ExpectedFiller, filler); } [Fact] @@ -149,61 +100,37 @@ public void CreateOnion_ReferenceTestVector_FixedLengthPayloads() var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); - var sessionKey = new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")); - var publicKeys = new List() - { - new PublicKey(Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), - new PublicKey(Convert.FromHexString("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), - new PublicKey(Convert.FromHexString("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), - new PublicKey(Convert.FromHexString("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), - new PublicKey(Convert.FromHexString("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) - }; - - var privateKeys = new List() - { - new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")), - new PrivateKey(Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242")), - new PrivateKey(Convert.FromHexString("4343434343434343434343434343434343434343434343434343434343434343")), - new PrivateKey(Convert.FromHexString("4444444444444444444444444444444444444444444444444444444444444444")), - new PrivateKey(Convert.FromHexString("4545454545454545454545454545454545454545454545454545454545454545")) - }; - - var referenceFixedSizePaymentPayloads = new List() { - Convert.FromHexString("000000000000000000000000000000000000000000000000000000000000000000"), - Convert.FromHexString("000101010101010101000000000000000100000001000000000000000000000000"), - Convert.FromHexString("000202020202020202000000000000000200000002000000000000000000000000"), - Convert.FromHexString("000303030303030303000000000000000300000003000000000000000000000000"), - Convert.FromHexString("000404040404040404000000000000000400000004000000000000000000000000") - }; - - var associatedData = Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242"); - - var encryptedOnion = sphinx.CreateOnion(sessionKey, 1300, publicKeys, referenceFixedSizePaymentPayloads, associatedData); + var encryptedOnion = sphinx.CreateOnion(SphinxReferenceVectors.SessionKey, + 1300, + SphinxReferenceVectors.PublicKeys, + SphinxReferenceVectors.FixedSizePaymentPayloads, + SphinxReferenceVectors.AssociatedData); + var sharedSecrets = encryptedOnion.SharedSecrets.ToArray(); - var decrypted0 = sphinx.PeelOnion(privateKeys[0], associatedData, encryptedOnion.Packet); - Assert.Equal(referenceFixedSizePaymentPayloads[0], decrypted0.Payload); + var decrypted0 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[0], SphinxReferenceVectors.AssociatedData, encryptedOnion.Packet); + Assert.Equal(SphinxReferenceVectors.FixedSizePaymentPayloads[0], decrypted0.Payload); var (secret0, _) = sharedSecrets[0]; Assert.Equal(secret0, decrypted0.SharedSecret); - var decrypted1 = sphinx.PeelOnion(privateKeys[1], associatedData, decrypted0.NextPacket); + var decrypted1 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[1], SphinxReferenceVectors.AssociatedData, decrypted0.NextPacket); var (secret1, _) = sharedSecrets[1]; - Assert.Equal(referenceFixedSizePaymentPayloads[1], decrypted1.Payload); + Assert.Equal(SphinxReferenceVectors.FixedSizePaymentPayloads[1], decrypted1.Payload); Assert.Equal(secret1, decrypted1.SharedSecret); - var decrypted2 = sphinx.PeelOnion(privateKeys[2], associatedData, decrypted1.NextPacket); + var decrypted2 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[2], SphinxReferenceVectors.AssociatedData, decrypted1.NextPacket); var (secret2, _) = sharedSecrets[2]; - Assert.Equal(referenceFixedSizePaymentPayloads[2], decrypted2.Payload); + Assert.Equal(SphinxReferenceVectors.FixedSizePaymentPayloads[2], decrypted2.Payload); Assert.Equal(secret2, decrypted2.SharedSecret); - var decrypted3 = sphinx.PeelOnion(privateKeys[3], associatedData, decrypted2.NextPacket); + var decrypted3 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[3], SphinxReferenceVectors.AssociatedData, decrypted2.NextPacket); var (secret3, _) = sharedSecrets[3]; - Assert.Equal(referenceFixedSizePaymentPayloads[3], decrypted3.Payload); + Assert.Equal(SphinxReferenceVectors.FixedSizePaymentPayloads[3], decrypted3.Payload); Assert.Equal(secret3, decrypted3.SharedSecret); - var decrypted4 = sphinx.PeelOnion(privateKeys[4], associatedData, decrypted3.NextPacket); + var decrypted4 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[4], SphinxReferenceVectors.AssociatedData, decrypted3.NextPacket); var (secret4, _) = sharedSecrets[4]; - Assert.Equal(referenceFixedSizePaymentPayloads[4], decrypted4.Payload); + Assert.Equal(SphinxReferenceVectors.FixedSizePaymentPayloads[4], decrypted4.Payload); Assert.Equal(secret4, decrypted4.SharedSecret); } @@ -213,61 +140,32 @@ public void CreateOnion_ReferenceTestVector_VariableLengthPayloads() var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); - var sessionKey = new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")); - var publicKeys = new List() - { - new PublicKey(Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), - new PublicKey(Convert.FromHexString("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), - new PublicKey(Convert.FromHexString("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), - new PublicKey(Convert.FromHexString("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), - new PublicKey(Convert.FromHexString("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) - }; - - var privateKeys = new List() - { - new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")), - new PrivateKey(Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242")), - new PrivateKey(Convert.FromHexString("4343434343434343434343434343434343434343434343434343434343434343")), - new PrivateKey(Convert.FromHexString("4444444444444444444444444444444444444444444444444444444444444444")), - new PrivateKey(Convert.FromHexString("4545454545454545454545454545454545454545454545454545454545454545")) - }; - - var referenceVariableSizePaymentPayloads = new List() { - Convert.FromHexString("000000000000000000000000000000000000000000000000000000000000000000"), - Convert.FromHexString("140101010101010101000000000000000100000001"), - Convert.FromHexString("fd0100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"), - Convert.FromHexString("140303030303030303000000000000000300000003"), - Convert.FromHexString("000404040404040404000000000000000400000004000000000000000000000000") - }; - - var associatedData = Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242"); - - var encryptedOnion = sphinx.CreateOnion(sessionKey, 1300, publicKeys, referenceVariableSizePaymentPayloads, associatedData); + var encryptedOnion = sphinx.CreateOnion(SphinxReferenceVectors.SessionKey, 1300, SphinxReferenceVectors.PublicKeys, SphinxReferenceVectors.VariableSizePaymentPayloads, SphinxReferenceVectors.AssociatedData); var sharedSecrets = encryptedOnion.SharedSecrets.ToArray(); - var decrypted0 = sphinx.PeelOnion(privateKeys[0], associatedData, encryptedOnion.Packet); + var decrypted0 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[0], SphinxReferenceVectors.AssociatedData, encryptedOnion.Packet); var (secret0, _) = sharedSecrets[0]; - Assert.Equal(referenceVariableSizePaymentPayloads[0], decrypted0.Payload); + Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloads[0], decrypted0.Payload); Assert.Equal(secret0, decrypted0.SharedSecret); - var decrypted1 = sphinx.PeelOnion(privateKeys[1], associatedData, decrypted0.NextPacket); + var decrypted1 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[1], SphinxReferenceVectors.AssociatedData, decrypted0.NextPacket); var (secret1, _) = sharedSecrets[1]; - Assert.Equal(referenceVariableSizePaymentPayloads[1], decrypted1.Payload); + Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloads[1], decrypted1.Payload); Assert.Equal(secret1, decrypted1.SharedSecret); - var decrypted2 = sphinx.PeelOnion(privateKeys[2], associatedData, decrypted1.NextPacket); + var decrypted2 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[2], SphinxReferenceVectors.AssociatedData, decrypted1.NextPacket); var (secret2, _) = sharedSecrets[2]; - Assert.Equal(referenceVariableSizePaymentPayloads[2], decrypted2.Payload); + Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloads[2], decrypted2.Payload); Assert.Equal(secret2, decrypted2.SharedSecret); - var decrypted3 = sphinx.PeelOnion(privateKeys[3], associatedData, decrypted2.NextPacket); + var decrypted3 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[3], SphinxReferenceVectors.AssociatedData, decrypted2.NextPacket); var (secret3, _) = sharedSecrets[3]; - Assert.Equal(referenceVariableSizePaymentPayloads[3], decrypted3.Payload); + Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloads[3], decrypted3.Payload); Assert.Equal(secret3, decrypted3.SharedSecret); - var decrypted4 = sphinx.PeelOnion(privateKeys[4], associatedData, decrypted3.NextPacket); + var decrypted4 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[4], SphinxReferenceVectors.AssociatedData, decrypted3.NextPacket); var (secret4, _) = sharedSecrets[4]; - Assert.Equal(referenceVariableSizePaymentPayloads[4], decrypted4.Payload); + Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloads[4], decrypted4.Payload); Assert.Equal(secret4, decrypted4.SharedSecret); } @@ -277,61 +175,32 @@ public void CreateOnion_ReferenceTestVector_VariableLengthFullPayloads() var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); - var sessionKey = new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")); - var publicKeys = new List() - { - new PublicKey(Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")), - new PublicKey(Convert.FromHexString("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c")), - new PublicKey(Convert.FromHexString("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), - new PublicKey(Convert.FromHexString("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")), - new PublicKey(Convert.FromHexString("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) - }; - - var privateKeys = new List() - { - new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")), - new PrivateKey(Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242")), - new PrivateKey(Convert.FromHexString("4343434343434343434343434343434343434343434343434343434343434343")), - new PrivateKey(Convert.FromHexString("4444444444444444444444444444444444444444444444444444444444444444")), - new PrivateKey(Convert.FromHexString("4545454545454545454545454545454545454545454545454545454545454545")) - }; - - var referenceVariableSizePaymentPayloadsFull = new List() { - Convert.FromHexString("8b09000000000000000030000000000000000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000250000000000000000000000000000000000000000000000002500000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000"), - Convert.FromHexString("fd012a08000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000000000000000000000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000000000000000000000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000000000000000000000000000000000000"), - Convert.FromHexString("620800000000000000900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), - Convert.FromHexString("fc120000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000"), - Convert.FromHexString("fd01582200000000000000000000000000000000000000000022000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000") - }; - - var associatedData = Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242"); - - var encryptedOnion = sphinx.CreateOnion(sessionKey, 1300, publicKeys, referenceVariableSizePaymentPayloadsFull, associatedData); + var encryptedOnion = sphinx.CreateOnion(SphinxReferenceVectors.SessionKey, 1300, SphinxReferenceVectors.PublicKeys, SphinxReferenceVectors.VariableSizePaymentPayloadsFull, SphinxReferenceVectors.AssociatedData); var sharedSecrets = encryptedOnion.SharedSecrets.ToArray(); - var decrypted0 = sphinx.PeelOnion(privateKeys[0], associatedData, encryptedOnion.Packet); + var decrypted0 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[0], SphinxReferenceVectors.AssociatedData, encryptedOnion.Packet); var (secret0, _) = sharedSecrets[0]; - Assert.Equal(referenceVariableSizePaymentPayloadsFull[0], decrypted0.Payload); + Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloadsFull[0], decrypted0.Payload); Assert.Equal(secret0, decrypted0.SharedSecret); - var decrypted1 = sphinx.PeelOnion(privateKeys[1], associatedData, decrypted0.NextPacket); + var decrypted1 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[1], SphinxReferenceVectors.AssociatedData, decrypted0.NextPacket); var (secret1, _) = sharedSecrets[1]; - Assert.Equal(referenceVariableSizePaymentPayloadsFull[1], decrypted1.Payload); + Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloadsFull[1], decrypted1.Payload); Assert.Equal(secret1, decrypted1.SharedSecret); - var decrypted2 = sphinx.PeelOnion(privateKeys[2], associatedData, decrypted1.NextPacket); + var decrypted2 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[2], SphinxReferenceVectors.AssociatedData, decrypted1.NextPacket); var (secret2, _) = sharedSecrets[2]; - Assert.Equal(referenceVariableSizePaymentPayloadsFull[2], decrypted2.Payload); + Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloadsFull[2], decrypted2.Payload); Assert.Equal(secret2, decrypted2.SharedSecret); - var decrypted3 = sphinx.PeelOnion(privateKeys[3], associatedData, decrypted2.NextPacket); + var decrypted3 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[3], SphinxReferenceVectors.AssociatedData, decrypted2.NextPacket); var (secret3, _) = sharedSecrets[3]; - Assert.Equal(referenceVariableSizePaymentPayloadsFull[3], decrypted3.Payload); + Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloadsFull[3], decrypted3.Payload); Assert.Equal(secret3, decrypted3.SharedSecret); - var decrypted4 = sphinx.PeelOnion(privateKeys[4], associatedData, decrypted3.NextPacket); + var decrypted4 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[4], SphinxReferenceVectors.AssociatedData, decrypted3.NextPacket); var (secret4, _) = sharedSecrets[4]; - Assert.Equal(referenceVariableSizePaymentPayloadsFull[4], decrypted4.Payload); + Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloadsFull[4], decrypted4.Payload); Assert.Equal(secret4, decrypted4.SharedSecret); } From f53206a6e926d15bcc647539d23c52cd2886bafd Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Thu, 8 Sep 2022 22:48:00 -0700 Subject: [PATCH 18/34] extract the payload length test vectors --- .../Bolt4/Data/SphinxReferenceVectors.cs | 9 +++++++++ src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs | 12 +----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs b/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs index 58545f3..78269ee 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs @@ -10,6 +10,15 @@ namespace Lyn.Protocol.Tests.Bolt4.Data { public static class SphinxReferenceVectors { + public static (byte[], int)[] PaylodLengths = new (byte[], int)[] { + (Convert.FromHexString("01"), 34), + (Convert.FromHexString("08"), 41), + (Convert.FromHexString("00"), 65), + (Convert.FromHexString("fc"), 285), + (Convert.FromHexString("fd00fd"), 288), + (Convert.FromHexString("fdffff"), 65570) + }; + public static PrivateKey SessionKey = new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")); public static List PrivateKeys = new List() { diff --git a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs index ea6f8f5..8f97468 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs @@ -77,17 +77,7 @@ public void PeekPerHopPayloadLength() var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); - var testCases = new[] - { - (Convert.FromHexString("01"), 34), - (Convert.FromHexString("08"), 41), - (Convert.FromHexString("00"), 65), - (Convert.FromHexString("fc"), 285), - (Convert.FromHexString("fd00fd"), 288), - (Convert.FromHexString("fdffff"), 65570) - }; - - foreach(var (payload, expected) in testCases) + foreach(var (payload, expected) in SphinxReferenceVectors.PaylodLengths) { var actual = sphinx.PeekPayloadLength(payload); Assert.Equal(expected, actual); From f6c3d0d9293de136a1864c5aa79cb8bde926a4d4 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Thu, 8 Sep 2022 22:53:57 -0700 Subject: [PATCH 19/34] refactor some of the test cases to use less boilerplate code --- src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs | 104 ++++++-------------- 1 file changed, 30 insertions(+), 74 deletions(-) diff --git a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs index 8f97468..8eac6d3 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs @@ -5,13 +5,13 @@ using Xunit; using Lyn.Protocol.Tests.Bolt4.Data; +using Lyn.Protocol.Bolt4.Entities; namespace Lyn.Protocol.Tests.Bolt4 { public class SphinxTests { - [Fact] public void ReferenceTestVector_GeneratesEphemeralKeysAndSecrets() { @@ -90,38 +90,24 @@ public void CreateOnion_ReferenceTestVector_FixedLengthPayloads() var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); - var encryptedOnion = sphinx.CreateOnion(SphinxReferenceVectors.SessionKey, + PacketAndSecrets encryptedOnion = sphinx.CreateOnion(SphinxReferenceVectors.SessionKey, 1300, SphinxReferenceVectors.PublicKeys, SphinxReferenceVectors.FixedSizePaymentPayloads, SphinxReferenceVectors.AssociatedData); var sharedSecrets = encryptedOnion.SharedSecrets.ToArray(); + Assert.Equal(5, sharedSecrets.Length); - var decrypted0 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[0], SphinxReferenceVectors.AssociatedData, encryptedOnion.Packet); - Assert.Equal(SphinxReferenceVectors.FixedSizePaymentPayloads[0], decrypted0.Payload); - var (secret0, _) = sharedSecrets[0]; - Assert.Equal(secret0, decrypted0.SharedSecret); - - var decrypted1 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[1], SphinxReferenceVectors.AssociatedData, decrypted0.NextPacket); - var (secret1, _) = sharedSecrets[1]; - Assert.Equal(SphinxReferenceVectors.FixedSizePaymentPayloads[1], decrypted1.Payload); - Assert.Equal(secret1, decrypted1.SharedSecret); - - var decrypted2 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[2], SphinxReferenceVectors.AssociatedData, decrypted1.NextPacket); - var (secret2, _) = sharedSecrets[2]; - Assert.Equal(SphinxReferenceVectors.FixedSizePaymentPayloads[2], decrypted2.Payload); - Assert.Equal(secret2, decrypted2.SharedSecret); - - var decrypted3 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[3], SphinxReferenceVectors.AssociatedData, decrypted2.NextPacket); - var (secret3, _) = sharedSecrets[3]; - Assert.Equal(SphinxReferenceVectors.FixedSizePaymentPayloads[3], decrypted3.Payload); - Assert.Equal(secret3, decrypted3.SharedSecret); - - var decrypted4 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[4], SphinxReferenceVectors.AssociatedData, decrypted3.NextPacket); - var (secret4, _) = sharedSecrets[4]; - Assert.Equal(SphinxReferenceVectors.FixedSizePaymentPayloads[4], decrypted4.Payload); - Assert.Equal(secret4, decrypted4.SharedSecret); + OnionRoutingPacket currentPacket = encryptedOnion.Packet; + for (var i = 0; i < sharedSecrets.Length; i++) + { + var decrypted = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[i], SphinxReferenceVectors.AssociatedData, currentPacket); + Assert.Equal(SphinxReferenceVectors.FixedSizePaymentPayloads[i], decrypted.Payload); + var (secret, _) = sharedSecrets[i]; + Assert.Equal(secret, decrypted.SharedSecret); + currentPacket = decrypted.NextPacket; + } } [Fact] @@ -133,30 +119,15 @@ public void CreateOnion_ReferenceTestVector_VariableLengthPayloads() var encryptedOnion = sphinx.CreateOnion(SphinxReferenceVectors.SessionKey, 1300, SphinxReferenceVectors.PublicKeys, SphinxReferenceVectors.VariableSizePaymentPayloads, SphinxReferenceVectors.AssociatedData); var sharedSecrets = encryptedOnion.SharedSecrets.ToArray(); - var decrypted0 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[0], SphinxReferenceVectors.AssociatedData, encryptedOnion.Packet); - var (secret0, _) = sharedSecrets[0]; - Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloads[0], decrypted0.Payload); - Assert.Equal(secret0, decrypted0.SharedSecret); - - var decrypted1 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[1], SphinxReferenceVectors.AssociatedData, decrypted0.NextPacket); - var (secret1, _) = sharedSecrets[1]; - Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloads[1], decrypted1.Payload); - Assert.Equal(secret1, decrypted1.SharedSecret); - - var decrypted2 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[2], SphinxReferenceVectors.AssociatedData, decrypted1.NextPacket); - var (secret2, _) = sharedSecrets[2]; - Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloads[2], decrypted2.Payload); - Assert.Equal(secret2, decrypted2.SharedSecret); - - var decrypted3 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[3], SphinxReferenceVectors.AssociatedData, decrypted2.NextPacket); - var (secret3, _) = sharedSecrets[3]; - Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloads[3], decrypted3.Payload); - Assert.Equal(secret3, decrypted3.SharedSecret); - - var decrypted4 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[4], SphinxReferenceVectors.AssociatedData, decrypted3.NextPacket); - var (secret4, _) = sharedSecrets[4]; - Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloads[4], decrypted4.Payload); - Assert.Equal(secret4, decrypted4.SharedSecret); + OnionRoutingPacket currentPacket = encryptedOnion.Packet; + for (var i = 0; i < sharedSecrets.Length; i++) + { + var decrypted = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[i], SphinxReferenceVectors.AssociatedData, currentPacket); + Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloads[i], decrypted.Payload); + var (secret, _) = sharedSecrets[i]; + Assert.Equal(secret, decrypted.SharedSecret); + currentPacket = decrypted.NextPacket; + } } [Fact] @@ -168,30 +139,15 @@ public void CreateOnion_ReferenceTestVector_VariableLengthFullPayloads() var encryptedOnion = sphinx.CreateOnion(SphinxReferenceVectors.SessionKey, 1300, SphinxReferenceVectors.PublicKeys, SphinxReferenceVectors.VariableSizePaymentPayloadsFull, SphinxReferenceVectors.AssociatedData); var sharedSecrets = encryptedOnion.SharedSecrets.ToArray(); - var decrypted0 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[0], SphinxReferenceVectors.AssociatedData, encryptedOnion.Packet); - var (secret0, _) = sharedSecrets[0]; - Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloadsFull[0], decrypted0.Payload); - Assert.Equal(secret0, decrypted0.SharedSecret); - - var decrypted1 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[1], SphinxReferenceVectors.AssociatedData, decrypted0.NextPacket); - var (secret1, _) = sharedSecrets[1]; - Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloadsFull[1], decrypted1.Payload); - Assert.Equal(secret1, decrypted1.SharedSecret); - - var decrypted2 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[2], SphinxReferenceVectors.AssociatedData, decrypted1.NextPacket); - var (secret2, _) = sharedSecrets[2]; - Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloadsFull[2], decrypted2.Payload); - Assert.Equal(secret2, decrypted2.SharedSecret); - - var decrypted3 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[3], SphinxReferenceVectors.AssociatedData, decrypted2.NextPacket); - var (secret3, _) = sharedSecrets[3]; - Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloadsFull[3], decrypted3.Payload); - Assert.Equal(secret3, decrypted3.SharedSecret); - - var decrypted4 = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[4], SphinxReferenceVectors.AssociatedData, decrypted3.NextPacket); - var (secret4, _) = sharedSecrets[4]; - Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloadsFull[4], decrypted4.Payload); - Assert.Equal(secret4, decrypted4.SharedSecret); + OnionRoutingPacket currentPacket = encryptedOnion.Packet; + for (var i = 0; i < sharedSecrets.Length; i++) + { + var decrypted = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[i], SphinxReferenceVectors.AssociatedData, currentPacket); + Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloadsFull[i], decrypted.Payload); + var (secret, _) = sharedSecrets[i]; + Assert.Equal(secret, decrypted.SharedSecret); + currentPacket = decrypted.NextPacket; + } } } From 743e6270264ad710c14f6a2f7c18bb7000d88368 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Thu, 8 Sep 2022 22:58:36 -0700 Subject: [PATCH 20/34] move ephemeral key reference data to class --- .../Bolt4/Data/SphinxReferenceVectors.cs | 16 ++++++++++++++++ src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs | 16 ++++++---------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs b/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs index 78269ee..5520bf9 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs @@ -10,6 +10,22 @@ namespace Lyn.Protocol.Tests.Bolt4.Data { public static class SphinxReferenceVectors { + public static List ExpectedEphemeralKeys = new List() { + Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"), + Convert.FromHexString("028f9438bfbf7feac2e108d677e3a82da596be706cc1cf342b75c7b7e22bf4e6e2"), + Convert.FromHexString("03bfd8225241ea71cd0843db7709f4c222f62ff2d4516fd38b39914ab6b83e0da0"), + Convert.FromHexString("031dde6926381289671300239ea8e57ffaf9bebd05b9a5b95beaf07af05cd43595"), + Convert.FromHexString("03a214ebd875aab6ddfd77f22c5e7311d7f77f17a169e599f157bbcdae8bf071f4") + }; + + public static List ExpectedEphemeralSecrets = new List() { + Convert.FromHexString("53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66"), + Convert.FromHexString("a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae"), + Convert.FromHexString("3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc"), + Convert.FromHexString("21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d"), + Convert.FromHexString("b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328") + }; + public static (byte[], int)[] PaylodLengths = new (byte[], int)[] { (Convert.FromHexString("01"), 34), (Convert.FromHexString("08"), 41), diff --git a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs index 8eac6d3..3c30800 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs @@ -23,20 +23,16 @@ public void ReferenceTestVector_GeneratesEphemeralKeysAndSecrets() Assert.NotNull(ephemeralKeys); Assert.NotNull(sharedSecrets); + Assert.Equal(5, sharedSecrets.Count); var keyList = ephemeralKeys.ToList(); var secretList = sharedSecrets.ToList(); - Assert.Equal(Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"), keyList.ElementAt(0).GetSpan().ToArray()); - Assert.Equal(Convert.FromHexString("53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66"), secretList.ElementAt(0)); - Assert.Equal(Convert.FromHexString("028f9438bfbf7feac2e108d677e3a82da596be706cc1cf342b75c7b7e22bf4e6e2"), keyList.ElementAt(1).GetSpan().ToArray()); - Assert.Equal(Convert.FromHexString("a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae"), secretList.ElementAt(1)); - Assert.Equal(Convert.FromHexString("03bfd8225241ea71cd0843db7709f4c222f62ff2d4516fd38b39914ab6b83e0da0"), keyList.ElementAt(2).GetSpan().ToArray()); - Assert.Equal(Convert.FromHexString("3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc"), secretList.ElementAt(2)); - Assert.Equal(Convert.FromHexString("031dde6926381289671300239ea8e57ffaf9bebd05b9a5b95beaf07af05cd43595"), keyList.ElementAt(3).GetSpan().ToArray()); - Assert.Equal(Convert.FromHexString("21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d"), secretList.ElementAt(3)); - Assert.Equal(Convert.FromHexString("03a214ebd875aab6ddfd77f22c5e7311d7f77f17a169e599f157bbcdae8bf071f4"), keyList.ElementAt(4).GetSpan().ToArray()); - Assert.Equal(Convert.FromHexString("b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328"), secretList.ElementAt(4)); + for (var i = 0; i < sharedSecrets.Count; i++) + { + Assert.Equal(SphinxReferenceVectors.ExpectedEphemeralKeys[i], keyList.ElementAt(i).GetSpan().ToArray()); + Assert.Equal(SphinxReferenceVectors.ExpectedEphemeralSecrets[i], secretList.ElementAt(i)); + } } [Fact] From 8bc4f9b91a37f23950b18a1316ad084f6cc39fd6 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Thu, 10 Nov 2022 01:17:58 -0800 Subject: [PATCH 21/34] add the islastpacket test and fix some logic --- src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs | 89 ++++++++++++++++--- .../Bolt4/Entities/DecryptedOnionPacket.cs | 2 +- 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs index 3c30800..cca016e 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs @@ -6,6 +6,7 @@ using Lyn.Protocol.Tests.Bolt4.Data; using Lyn.Protocol.Bolt4.Entities; +using System.Collections.Generic; namespace Lyn.Protocol.Tests.Bolt4 { @@ -18,7 +19,7 @@ public void ReferenceTestVector_GeneratesEphemeralKeysAndSecrets() var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); - var (ephemeralKeys, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(SphinxReferenceVectors.SessionKey, + var (ephemeralKeys, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(SphinxReferenceVectors.SessionKey, SphinxReferenceVectors.PublicKeys); Assert.NotNull(ephemeralKeys); @@ -36,15 +37,15 @@ public void ReferenceTestVector_GeneratesEphemeralKeysAndSecrets() } [Fact] - public void GenerateFilter_ReferenceTestVector_FixedSizePayloads() + public void GenerateFiller_ReferenceTestVector_FixedSizePayloads() { var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); var (_, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(SphinxReferenceVectors.SessionKey, SphinxReferenceVectors.PublicKeys); - var filler = sphinx.GenerateFiller("rho", - 1300, + var filler = sphinx.GenerateFiller("rho", + 1300, sharedSecrets.SkipLast(1), SphinxReferenceVectors.FixedSizePaymentPayloads.SkipLast(1)).ToArray(); @@ -52,7 +53,7 @@ public void GenerateFilter_ReferenceTestVector_FixedSizePayloads() } [Fact] - public void GenerateFilter_ReferenceTestVector_VariableSizePayloads() + public void GenerateFiller_ReferenceTestVector_VariableSizePayloads() { var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); @@ -73,7 +74,7 @@ public void PeekPerHopPayloadLength() var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); - foreach(var (payload, expected) in SphinxReferenceVectors.PaylodLengths) + foreach (var (payload, expected) in SphinxReferenceVectors.PaylodLengths) { var actual = sphinx.PeekPayloadLength(payload); Assert.Equal(expected, actual); @@ -86,16 +87,16 @@ public void CreateOnion_ReferenceTestVector_FixedLengthPayloads() var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); - PacketAndSecrets encryptedOnion = sphinx.CreateOnion(SphinxReferenceVectors.SessionKey, - 1300, - SphinxReferenceVectors.PublicKeys, - SphinxReferenceVectors.FixedSizePaymentPayloads, + PacketAndSecrets encryptedOnion = sphinx.CreateOnion(SphinxReferenceVectors.SessionKey, + 1300, + SphinxReferenceVectors.PublicKeys, + SphinxReferenceVectors.FixedSizePaymentPayloads, SphinxReferenceVectors.AssociatedData); - + var sharedSecrets = encryptedOnion.SharedSecrets.ToArray(); Assert.Equal(5, sharedSecrets.Length); - OnionRoutingPacket currentPacket = encryptedOnion.Packet; + OnionRoutingPacket currentPacket = encryptedOnion.Packet; for (var i = 0; i < sharedSecrets.Length; i++) { var decrypted = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[i], SphinxReferenceVectors.AssociatedData, currentPacket); @@ -146,5 +147,69 @@ public void CreateOnion_ReferenceTestVector_VariableLengthFullPayloads() } } + [Fact] + public void IsLastPacket() + { + var publicKeys = SphinxReferenceVectors.PublicKeys; + var testCases = new[] + { + // Bolt 1.0 payloads use the next packet's hmac to signal termination. + (true, new DecryptedOnionPacket() { Payload = new [] {(byte)0x00}, + NextPacket = new OnionRoutingPacket() { Version = 0, + EphemeralKey = publicKeys[0], + PayloadData = new byte[32], + Hmac = ByteVector32.Zeroes }, + SharedSecret = ByteVector32.One + } + ), + (false, new DecryptedOnionPacket() { Payload = new [] {(byte)0x00}, + NextPacket = new OnionRoutingPacket() { Version = 0, + EphemeralKey = publicKeys[0], + PayloadData = new byte[32], + Hmac = ByteVector32.One }, + SharedSecret = ByteVector32.One + } + ), + // Bolt 1.1 payloads currently also use the next packet's hmac to signal termination. + (true, new DecryptedOnionPacket() { Payload = Convert.FromHexString("0101"), + NextPacket = new OnionRoutingPacket() { Version = 0, + EphemeralKey = publicKeys[0], + PayloadData = new byte[32], + Hmac = ByteVector32.Zeroes }, + SharedSecret = ByteVector32.One + } + ), + (false, new DecryptedOnionPacket() { Payload = Convert.FromHexString("0101"), + NextPacket = new OnionRoutingPacket() { Version = 0, + EphemeralKey = publicKeys[0], + PayloadData = new byte[32], + Hmac = ByteVector32.One }, + SharedSecret = ByteVector32.One + } + ), + (false, new DecryptedOnionPacket() { Payload = Convert.FromHexString("0100"), + NextPacket = new OnionRoutingPacket() { Version = 0, + EphemeralKey = publicKeys[0], + PayloadData = new byte[32], + Hmac = ByteVector32.One }, + SharedSecret = ByteVector32.One + } + ) + }; + + foreach(var (expected, packet) in testCases) + { + Assert.Equal(expected, packet.IsLastPacket); + } + } + + } + + public static class ByteVector32 + { + public static byte[] Zeroes = Enumerable.Repeat((byte)0, 32).ToArray(); + public static byte[] One = Convert.FromHexString("0000000000000000000000000000000000000000000000000000000000000001"); + } + } diff --git a/src/Lyn.Protocol/Bolt4/Entities/DecryptedOnionPacket.cs b/src/Lyn.Protocol/Bolt4/Entities/DecryptedOnionPacket.cs index 99a4044..d92d5dc 100644 --- a/src/Lyn.Protocol/Bolt4/Entities/DecryptedOnionPacket.cs +++ b/src/Lyn.Protocol/Bolt4/Entities/DecryptedOnionPacket.cs @@ -15,7 +15,7 @@ public class DecryptedOnionPacket public byte[] SharedSecret { get; set; } - public bool IsLastPacket => (NextPacket?.Hmac == null || NextPacket?.Hmac.Length == 0); + public bool IsLastPacket => (NextPacket?.Hmac == null || NextPacket?.Hmac.Length == 0 || NextPacket?.Hmac.SequenceEqual(new byte[32]) == true); } } From 745b7d9a89dd112f59c9a03c41cb918bfacb32e4 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Sun, 13 Nov 2022 18:42:22 -0800 Subject: [PATCH 22/34] start deprecating the legacy onion format - PeekPayloadLength throws (incorrectly in some cases?) --- .../Bolt4/Data/SphinxReferenceVectors.cs | 2 +- src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs | 82 +++++++++++++------ src/Lyn.Protocol/Bolt4/Sphinx.cs | 34 ++++---- .../Onion/InvalidOnionVersionException.cs | 14 ++++ 4 files changed, 91 insertions(+), 41 deletions(-) create mode 100644 src/Lyn.Types/Onion/InvalidOnionVersionException.cs diff --git a/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs b/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs index 5520bf9..ab22cec 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs @@ -10,6 +10,7 @@ namespace Lyn.Protocol.Tests.Bolt4.Data { public static class SphinxReferenceVectors { + public static byte[] InvalidVersionPayload => new[] {(byte)0x00}; public static List ExpectedEphemeralKeys = new List() { Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"), Convert.FromHexString("028f9438bfbf7feac2e108d677e3a82da596be706cc1cf342b75c7b7e22bf4e6e2"), @@ -29,7 +30,6 @@ public static class SphinxReferenceVectors public static (byte[], int)[] PaylodLengths = new (byte[], int)[] { (Convert.FromHexString("01"), 34), (Convert.FromHexString("08"), 41), - (Convert.FromHexString("00"), 65), (Convert.FromHexString("fc"), 285), (Convert.FromHexString("fd00fd"), 288), (Convert.FromHexString("fdffff"), 65570) diff --git a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs index cca016e..9b58b3e 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs @@ -7,6 +7,8 @@ using Lyn.Protocol.Tests.Bolt4.Data; using Lyn.Protocol.Bolt4.Entities; using System.Collections.Generic; +using Lyn.Types.Fundamental; +using Lyn.Types.Onion; namespace Lyn.Protocol.Tests.Bolt4 { @@ -79,6 +81,8 @@ public void PeekPerHopPayloadLength() var actual = sphinx.PeekPayloadLength(payload); Assert.Equal(expected, actual); } + + Assert.Throws(() => sphinx.PeekPayloadLength(SphinxReferenceVectors.InvalidVersionPayload)); } [Fact] @@ -155,54 +159,70 @@ public void IsLastPacket() { // Bolt 1.0 payloads use the next packet's hmac to signal termination. (true, new DecryptedOnionPacket() { Payload = new [] {(byte)0x00}, - NextPacket = new OnionRoutingPacket() { Version = 0, - EphemeralKey = publicKeys[0], - PayloadData = new byte[32], - Hmac = ByteVector32.Zeroes }, - SharedSecret = ByteVector32.One + NextPacket = new OnionRoutingPacket() { Version = 0, + EphemeralKey = publicKeys[0], + PayloadData = new byte[32], + Hmac = ByteVector32.Zeroes }, + SharedSecret = ByteVector32.One } ), (false, new DecryptedOnionPacket() { Payload = new [] {(byte)0x00}, - NextPacket = new OnionRoutingPacket() { Version = 0, - EphemeralKey = publicKeys[0], - PayloadData = new byte[32], - Hmac = ByteVector32.One }, - SharedSecret = ByteVector32.One + NextPacket = new OnionRoutingPacket() { Version = 0, + EphemeralKey = publicKeys[0], + PayloadData = new byte[32], + Hmac = ByteVector32.One }, + SharedSecret = ByteVector32.One } ), // Bolt 1.1 payloads currently also use the next packet's hmac to signal termination. (true, new DecryptedOnionPacket() { Payload = Convert.FromHexString("0101"), - NextPacket = new OnionRoutingPacket() { Version = 0, - EphemeralKey = publicKeys[0], - PayloadData = new byte[32], - Hmac = ByteVector32.Zeroes }, - SharedSecret = ByteVector32.One + NextPacket = new OnionRoutingPacket() { Version = 0, + EphemeralKey = publicKeys[0], + PayloadData = new byte[32], + Hmac = ByteVector32.Zeroes }, + SharedSecret = ByteVector32.One } ), (false, new DecryptedOnionPacket() { Payload = Convert.FromHexString("0101"), - NextPacket = new OnionRoutingPacket() { Version = 0, - EphemeralKey = publicKeys[0], - PayloadData = new byte[32], - Hmac = ByteVector32.One }, - SharedSecret = ByteVector32.One + NextPacket = new OnionRoutingPacket() { Version = 0, + EphemeralKey = publicKeys[0], + PayloadData = new byte[32], + Hmac = ByteVector32.One }, + SharedSecret = ByteVector32.One } ), (false, new DecryptedOnionPacket() { Payload = Convert.FromHexString("0100"), - NextPacket = new OnionRoutingPacket() { Version = 0, - EphemeralKey = publicKeys[0], - PayloadData = new byte[32], - Hmac = ByteVector32.One }, - SharedSecret = ByteVector32.One + NextPacket = new OnionRoutingPacket() { Version = 0, + EphemeralKey = publicKeys[0], + PayloadData = new byte[32], + Hmac = ByteVector32.One }, + SharedSecret = ByteVector32.One } ) }; - foreach(var (expected, packet) in testCases) + foreach (var (expected, packet) in testCases) { Assert.Equal(expected, packet.IsLastPacket); } } + [Fact] + public void Peel_BadOnion_BadVersion_Throws() + { + var curveActions = new EllipticCurveActions(); + var sphinx = new Sphinx(curveActions); + + var packet = new OnionRoutingPacket() + { + Version = 1, + EphemeralKey = SphinxReferenceVectors.PublicKeys[0], + PayloadData = ByteVector.Fill(65, 0x01), + Hmac = Convert.FromHexString("C908EE9582217D3B58D75FAC05CD5DBBEF91C1841A5B59D521283F9F4C43B643") + }; + + Assert.Throws(() => sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[0], SphinxReferenceVectors.AssociatedData, packet)); + } } @@ -210,6 +230,16 @@ public static class ByteVector32 { public static byte[] Zeroes = Enumerable.Repeat((byte)0, 32).ToArray(); public static byte[] One = Convert.FromHexString("0000000000000000000000000000000000000000000000000000000000000001"); + + } + + public static class ByteVector + { + public static byte[] Fill(int length, byte value) + { + return Enumerable.Repeat(value, length).ToArray(); + } } + } diff --git a/src/Lyn.Protocol/Bolt4/Sphinx.cs b/src/Lyn.Protocol/Bolt4/Sphinx.cs index 81465c8..7ea970e 100644 --- a/src/Lyn.Protocol/Bolt4/Sphinx.cs +++ b/src/Lyn.Protocol/Bolt4/Sphinx.cs @@ -1,6 +1,7 @@ using Lyn.Protocol.Bolt4.Entities; using Lyn.Protocol.Common.Crypto; using Lyn.Types.Fundamental; +using Lyn.Types.Onion; using Lyn.Types.Serialization; using NaCl.Core; using NBitcoin.Secp256k1; @@ -146,25 +147,25 @@ public int PeekPayloadLength(byte[] payloadData) { var sequence = new ReadOnlySequence(new ReadOnlyMemory(payloadData)); var binReader = new SequenceReader(sequence); - int perHopPayloadLength = 0; if (binReader.TryPeek(out var firstByte)) { if (firstByte == 0x00) { - // todo: this might be deprecated? do we need to support legacy payloads in Lyn? - perHopPayloadLength = 65; + // todo: consider re-throwing in Peel? + throw new InvalidOnionVersionException(); } - else - { - // safe to truncate because a packet will never be larger than 64KB? - perHopPayloadLength = (int)binReader.ReadBigSize(); - perHopPayloadLength += (int)binReader.Consumed; //offset the length by the number of bytes consoomed - perHopPayloadLength += MAC_LENGTH; - } - } - return perHopPayloadLength; + // safe to truncate because a packet will never be larger than 64KB? + int perHopPayloadLength = (int)binReader.ReadBigSize(); + perHopPayloadLength += (int)binReader.Consumed; //offset the length by the number of bytes consoomed + perHopPayloadLength += MAC_LENGTH; + return perHopPayloadLength; + } + else + { + throw new ArgumentException("payloadData is empty"); + } } public ReadOnlySpan GenerateFiller(string keyType, @@ -202,6 +203,11 @@ public ReadOnlySpan GenerateFiller(string keyType, // TODO: return DecryptedOnionPacket? public DecryptedOnionPacket PeelOnion(PrivateKey privateKey, byte[]? associatedData, OnionRoutingPacket packet) { + if (packet.Version != 0) + { + // todo: this needs to contain the hash of the packet + throw new InvalidOnionVersionException(); + } var sharedSecret = ComputeSharedSecret(packet.EphemeralKey, privateKey); var mu = GenerateSphinxKey("mu", sharedSecret); @@ -214,7 +220,7 @@ public DecryptedOnionPacket PeelOnion(PrivateKey privateKey, byte[]? associatedD var rho = GenerateSphinxKey("rho", sharedSecret); var cipherStream = GenerateStream(rho.ToArray(), 2 * packet.PayloadData.Length); // todo: better variable name here - var paddedPayload = packet.PayloadData.Concat(Enumerable.Range(0, packet.PayloadData.Length).Select(x => 0x00)).ToArray(); + var paddedPayload = packet.PayloadData.Concat(new byte[packet.PayloadData.Length]).ToArray(); var binData = ExclusiveOR(paddedPayload, cipherStream).ToArray(); var perHopPayloadLength = PeekPayloadLength(binData); @@ -291,7 +297,7 @@ private OnionRoutingPacket WrapOnion(ReadOnlySpan payload, } var hmacBytes = nextOnionPayload; - if(associatedData != null) + if (associatedData != null) { hmacBytes = nextOnionPayload.Concat(associatedData).ToArray(); } diff --git a/src/Lyn.Types/Onion/InvalidOnionVersionException.cs b/src/Lyn.Types/Onion/InvalidOnionVersionException.cs new file mode 100644 index 0000000..9861445 --- /dev/null +++ b/src/Lyn.Types/Onion/InvalidOnionVersionException.cs @@ -0,0 +1,14 @@ +using System; + +namespace Lyn.Types.Onion +{ + [Serializable] + public class InvalidOnionVersionException : Exception + { + public InvalidOnionVersionException() : + base("Legacy Onion Format is not supported anymore") + { + + } + } +} From ce5338c538ed45c57677dbb7aa0cb56da831206f Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Sun, 13 Nov 2022 22:59:18 -0800 Subject: [PATCH 23/34] refactor a few more of the payload tests + flesh out Bad Onion details --- .../Bolt4/Data/SphinxReferenceVectors.cs | 31 ++-- src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs | 162 +++++++++--------- src/Lyn.Protocol/Bolt4/Sphinx.cs | 6 +- .../Onion/InvalidOnionHmacException.cs | 14 ++ .../Onion/InvalidOnionVersionException.cs | 6 +- 5 files changed, 108 insertions(+), 111 deletions(-) create mode 100644 src/Lyn.Types/Onion/InvalidOnionHmacException.cs diff --git a/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs b/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs index ab22cec..f3ca8cd 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs @@ -10,7 +10,7 @@ namespace Lyn.Protocol.Tests.Bolt4.Data { public static class SphinxReferenceVectors { - public static byte[] InvalidVersionPayload => new[] {(byte)0x00}; + public static byte[] InvalidVersionPayload => new[] { (byte)0x00 }; public static List ExpectedEphemeralKeys = new List() { Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"), Convert.FromHexString("028f9438bfbf7feac2e108d677e3a82da596be706cc1cf342b75c7b7e22bf4e6e2"), @@ -53,23 +53,18 @@ public static class SphinxReferenceVectors new PublicKey(Convert.FromHexString("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) }; - public static List FixedSizePaymentPayloads = new List() { - Convert.FromHexString("000000000000000000000000000000000000000000000000000000000000000000"), - Convert.FromHexString("000101010101010101000000000000000100000001000000000000000000000000"), - Convert.FromHexString("000202020202020202000000000000000200000002000000000000000000000000"), - Convert.FromHexString("000303030303030303000000000000000300000003000000000000000000000000"), - Convert.FromHexString("000404040404040404000000000000000400000004000000000000000000000000") + // origin -> node #0 -> node #1 -> node #2 -> node #3 -> node #4 + public static List ReferencePaymentPayloads = new List() { + Convert.FromHexString("1202023a98040205dc06080000000000000001"), + Convert.FromHexString("52020236b00402057806080000000000000002fd02013c0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f"), + Convert.FromHexString("12020230d4040204e206080000000000000003"), + Convert.FromHexString("1202022710040203e806080000000000000004"), + Convert.FromHexString("fd011002022710040203e8082224a33562c54507a9334e79f0dc4f17d407e6d7c61f0e2f3d0d38599502f617042710fd012de02a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a") }; - public static List VariableSizePaymentPayloads = new List() { - Convert.FromHexString("000000000000000000000000000000000000000000000000000000000000000000"), - Convert.FromHexString("140101010101010101000000000000000100000001"), - Convert.FromHexString("fd0100000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"), - Convert.FromHexString("140303030303030303000000000000000300000003"), - Convert.FromHexString("000404040404040404000000000000000400000004000000000000000000000000") - }; - - public static List VariableSizePaymentPayloadsFull = new List() { + // This test vector uses multiple payloads to fill the whole onion packet. + // origin -> node #0 -> node #1 -> node #2 -> node #3 -> node #4 + public static List PaymentPayloadsFull = new List() { Convert.FromHexString("8b09000000000000000030000000000000000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000250000000000000000000000000000000000000000000000002500000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000"), Convert.FromHexString("fd012a08000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000000000000000000000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000000000000000000000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000000000000000000000000000000000000"), Convert.FromHexString("620800000000000000900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), @@ -79,8 +74,6 @@ public static class SphinxReferenceVectors public static byte[] AssociatedData = Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242"); - public static byte[] FixedSizePayload_ExpectedFiller = Convert.FromHexString("c6b008cf6414ed6e4c42c291eb505e9f22f5fe7d0ecdd15a833f4d016ac974d33adc6ea3293e20859e87ebfb937ba406abd025d14af692b12e9c9c2adbe307a679779259676211c071e614fdb386d1ff02db223a5b2fae03df68d321c7b29f7c7240edd3fa1b7cb6903f89dc01abf41b2eb0b49b6b8d73bb0774b58204c0d0e96d3cce45ad75406be0bc009e327b3e712a4bd178609c00b41da2daf8a4b0e1319f07a492ab4efb056f0f599f75e6dc7e0d10ce1cf59088ab6e873de377343880f7a24f0e36731a0b72092f8d5bc8cd346762e93b2bf203d00264e4bc136fc142de8f7b69154deb05854ea88e2d7506222c95ba1aab065c8a851391377d3406a35a9af3ac"); - public static byte[] VariableSizePayload_ExpectedFiller = Convert.FromHexString("b77d99c935d3f32469844f7e09340a91ded147557bdd0456c369f7e449587c0f5666faab58040146db49024db88553729bce12b860391c29c1779f022ae48a9cb314ca35d73fc91addc92632bcf7ba6fd9f38e6fd30fabcedbd5407b6648073c38331ee7ab0332f41f550c180e1601f8c25809ed75b3a1e78635a2ef1b828e92c9658e76e49f995d72cf9781eec0c838901d0bdde3ac21c13b4979ac9e738a1c4d0b9741d58e777ad1aed01263ad1390d36a18a6b92f4f799dcf75edbb43b7515e8d72cb4f827a9af0e7b9338d07b1a24e0305b5535f5b851b1144bad6238b9d9482b5ba6413f1aafac3cdde5067966ed8b78f7c1c5f916a05f874d5f17a2b7d0ae75d66a5f1bb6ff932570dc5a0cf3ce04eb5d26bc55c2057af1f8326e20a7d6f0ae644f09d00fac80de60f20aceee85be41a074d3e1dda017db79d0070b99f54736396f206ee3777abd4c00a4bb95c871750409261e3b01e59a3793a9c20159aae4988c68397a1443be6370fd9614e46108291e615691729faea58537209fa668a172d066d0efff9bc77c2bd34bd77870ad79effd80140990e36731a0b72092f8d5bc8cd346762e93b2bf203d00264e4bc136fc142de8f7b69154deb05854ea88e2d7506222c95ba1aab065c8a"); - + public static byte[] GenerateFiller_ExpectedFiller = Convert.FromHexString("51c30cc8f20da0153ca3839b850bcbc8fefc7fd84802f3e78cb35a660e747b57aa5b0de555cbcf1e6f044a718cc34219b96597f3684eee7a0232e1754f638006cb15a14788217abdf1bdd67910dc1ca74a05dcce8b5ad841b0f939fca8935f6a3ff660e0efb409f1a24ce4aa16fc7dc074cd84422c10cc4dd4fc150dd6d1e4f50b36ce10fef29248dd0cec85c72eb3e4b2f4a7c03b5c9e0c9dd12976553ede3d0e295f842187b33ff743e6d685075e98e1bcab8a46bff0102ca8b2098ae91798d370b01ca7076d3d626952a03663fe8dc700d1358263b73ba30e36731a0b72092f8d5bc8cd346762e93b2bf203d00264e4bc136fc142de8f7b69154deb05854ea88e2d7506222c95ba1aab06"); } } diff --git a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs index 9b58b3e..4a78009 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs @@ -39,7 +39,7 @@ public void ReferenceTestVector_GeneratesEphemeralKeysAndSecrets() } [Fact] - public void GenerateFiller_ReferenceTestVector_FixedSizePayloads() + public void GenerateFiller() { var curveActions = new EllipticCurveActions(); var sphinx = new Sphinx(curveActions); @@ -49,25 +49,9 @@ public void GenerateFiller_ReferenceTestVector_FixedSizePayloads() var filler = sphinx.GenerateFiller("rho", 1300, sharedSecrets.SkipLast(1), - SphinxReferenceVectors.FixedSizePaymentPayloads.SkipLast(1)).ToArray(); + SphinxReferenceVectors.ReferencePaymentPayloads.SkipLast(1)).ToArray(); - Assert.Equal(SphinxReferenceVectors.FixedSizePayload_ExpectedFiller, filler); - } - - [Fact] - public void GenerateFiller_ReferenceTestVector_VariableSizePayloads() - { - var curveActions = new EllipticCurveActions(); - var sphinx = new Sphinx(curveActions); - - var (_, sharedSecrets) = sphinx.ComputeEphemeralPublicKeysAndSharedSecrets(SphinxReferenceVectors.SessionKey, - SphinxReferenceVectors.PublicKeys); - var filler = sphinx.GenerateFiller("rho", - 1300, - sharedSecrets.SkipLast(1), - SphinxReferenceVectors.VariableSizePaymentPayloads.SkipLast(1)).ToArray(); - - Assert.Equal(SphinxReferenceVectors.VariableSizePayload_ExpectedFiller, filler); + Assert.Equal(SphinxReferenceVectors.GenerateFiller_ExpectedFiller, filler); } [Fact] @@ -85,72 +69,6 @@ public void PeekPerHopPayloadLength() Assert.Throws(() => sphinx.PeekPayloadLength(SphinxReferenceVectors.InvalidVersionPayload)); } - [Fact] - public void CreateOnion_ReferenceTestVector_FixedLengthPayloads() - { - var curveActions = new EllipticCurveActions(); - var sphinx = new Sphinx(curveActions); - - PacketAndSecrets encryptedOnion = sphinx.CreateOnion(SphinxReferenceVectors.SessionKey, - 1300, - SphinxReferenceVectors.PublicKeys, - SphinxReferenceVectors.FixedSizePaymentPayloads, - SphinxReferenceVectors.AssociatedData); - - var sharedSecrets = encryptedOnion.SharedSecrets.ToArray(); - Assert.Equal(5, sharedSecrets.Length); - - OnionRoutingPacket currentPacket = encryptedOnion.Packet; - for (var i = 0; i < sharedSecrets.Length; i++) - { - var decrypted = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[i], SphinxReferenceVectors.AssociatedData, currentPacket); - Assert.Equal(SphinxReferenceVectors.FixedSizePaymentPayloads[i], decrypted.Payload); - var (secret, _) = sharedSecrets[i]; - Assert.Equal(secret, decrypted.SharedSecret); - currentPacket = decrypted.NextPacket; - } - } - - [Fact] - public void CreateOnion_ReferenceTestVector_VariableLengthPayloads() - { - var curveActions = new EllipticCurveActions(); - var sphinx = new Sphinx(curveActions); - - var encryptedOnion = sphinx.CreateOnion(SphinxReferenceVectors.SessionKey, 1300, SphinxReferenceVectors.PublicKeys, SphinxReferenceVectors.VariableSizePaymentPayloads, SphinxReferenceVectors.AssociatedData); - var sharedSecrets = encryptedOnion.SharedSecrets.ToArray(); - - OnionRoutingPacket currentPacket = encryptedOnion.Packet; - for (var i = 0; i < sharedSecrets.Length; i++) - { - var decrypted = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[i], SphinxReferenceVectors.AssociatedData, currentPacket); - Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloads[i], decrypted.Payload); - var (secret, _) = sharedSecrets[i]; - Assert.Equal(secret, decrypted.SharedSecret); - currentPacket = decrypted.NextPacket; - } - } - - [Fact] - public void CreateOnion_ReferenceTestVector_VariableLengthFullPayloads() - { - var curveActions = new EllipticCurveActions(); - var sphinx = new Sphinx(curveActions); - - var encryptedOnion = sphinx.CreateOnion(SphinxReferenceVectors.SessionKey, 1300, SphinxReferenceVectors.PublicKeys, SphinxReferenceVectors.VariableSizePaymentPayloadsFull, SphinxReferenceVectors.AssociatedData); - var sharedSecrets = encryptedOnion.SharedSecrets.ToArray(); - - OnionRoutingPacket currentPacket = encryptedOnion.Packet; - for (var i = 0; i < sharedSecrets.Length; i++) - { - var decrypted = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[i], SphinxReferenceVectors.AssociatedData, currentPacket); - Assert.Equal(SphinxReferenceVectors.VariableSizePaymentPayloadsFull[i], decrypted.Payload); - var (secret, _) = sharedSecrets[i]; - Assert.Equal(secret, decrypted.SharedSecret); - currentPacket = decrypted.NextPacket; - } - } - [Fact] public void IsLastPacket() { @@ -224,6 +142,80 @@ public void Peel_BadOnion_BadVersion_Throws() Assert.Throws(() => sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[0], SphinxReferenceVectors.AssociatedData, packet)); } + [Fact] + public void Peel_BadOnion_InvalidPublicKey_Throws() + { + var curveActions = new EllipticCurveActions(); + var sphinx = new Sphinx(curveActions); + + var packet = new OnionRoutingPacket() + { + Version = 0, + EphemeralKey = new byte[33], + PayloadData = ByteVector.Fill(65, 0x01), + Hmac = Convert.FromHexString("C908EE9582217D3B58D75FAC05CD5DBBEF91C1841A5B59D521283F9F4C43B643") + }; + + Assert.Throws(() => sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[0], SphinxReferenceVectors.AssociatedData, packet)); + } + + [Fact] + public void Peel_BadOnion_InvalidHmac_Throws() + { + var curveActions = new EllipticCurveActions(); + var sphinx = new Sphinx(curveActions); + + var packet = new OnionRoutingPacket() + { + Version = 0, + EphemeralKey = SphinxReferenceVectors.PublicKeys[0], + PayloadData = ByteVector.Fill(65, 0x01), + Hmac = Convert.FromHexString("2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a") + }; + + Assert.Throws(() => sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[0], SphinxReferenceVectors.AssociatedData, packet)); + } + + [Fact] + public void CreatePaymentPacket_ReferenceTestVector() + { + var curveActions = new EllipticCurveActions(); + var sphinx = new Sphinx(curveActions); + + var encryptedOnion = sphinx.CreateOnion(SphinxReferenceVectors.SessionKey, 1300, SphinxReferenceVectors.PublicKeys, SphinxReferenceVectors.ReferencePaymentPayloads, SphinxReferenceVectors.AssociatedData); + var sharedSecrets = encryptedOnion.SharedSecrets.ToArray(); + + OnionRoutingPacket currentPacket = encryptedOnion.Packet; + for (var i = 0; i < sharedSecrets.Length; i++) + { + var decrypted = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[i], SphinxReferenceVectors.AssociatedData, currentPacket); + Assert.Equal(SphinxReferenceVectors.ReferencePaymentPayloads[i], decrypted.Payload); + var (secret, _) = sharedSecrets[i]; + Assert.Equal(secret, decrypted.SharedSecret); + currentPacket = decrypted.NextPacket; + } + } + + [Fact] + public void CreatePaymentPacket_FullPayloads() + { + var curveActions = new EllipticCurveActions(); + var sphinx = new Sphinx(curveActions); + + var encryptedOnion = sphinx.CreateOnion(SphinxReferenceVectors.SessionKey, 1300, SphinxReferenceVectors.PublicKeys, SphinxReferenceVectors.PaymentPayloadsFull, SphinxReferenceVectors.AssociatedData); + var sharedSecrets = encryptedOnion.SharedSecrets.ToArray(); + + OnionRoutingPacket currentPacket = encryptedOnion.Packet; + for (var i = 0; i < sharedSecrets.Length; i++) + { + var decrypted = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[i], SphinxReferenceVectors.AssociatedData, currentPacket); + Assert.Equal(SphinxReferenceVectors.PaymentPayloadsFull[i], decrypted.Payload); + var (secret, _) = sharedSecrets[i]; + Assert.Equal(secret, decrypted.SharedSecret); + currentPacket = decrypted.NextPacket; + } + } + } public static class ByteVector32 diff --git a/src/Lyn.Protocol/Bolt4/Sphinx.cs b/src/Lyn.Protocol/Bolt4/Sphinx.cs index 7ea970e..09101b2 100644 --- a/src/Lyn.Protocol/Bolt4/Sphinx.cs +++ b/src/Lyn.Protocol/Bolt4/Sphinx.cs @@ -241,7 +241,7 @@ public DecryptedOnionPacket PeelOnion(PrivateKey privateKey, byte[]? associatedD Payload = perHopPayload.ToArray(), NextPacket = new OnionRoutingPacket() { - Version = 0x01, + Version = SPHINX_VERSION, EphemeralKey = nextPublicKey, PayloadData = nextOnionPayload.ToArray(), Hmac = hopHMAC.ToArray() @@ -252,10 +252,8 @@ public DecryptedOnionPacket PeelOnion(PrivateKey privateKey, byte[]? associatedD } else { - throw new Exception("bad hmac"); + throw new InvalidOnionHmacException(); } - - throw new Exception("Bah! Humbug!"); } private OnionRoutingPacket WrapOnion(ReadOnlySpan payload, diff --git a/src/Lyn.Types/Onion/InvalidOnionHmacException.cs b/src/Lyn.Types/Onion/InvalidOnionHmacException.cs new file mode 100644 index 0000000..988d3e1 --- /dev/null +++ b/src/Lyn.Types/Onion/InvalidOnionHmacException.cs @@ -0,0 +1,14 @@ +using System; + +namespace Lyn.Types.Onion +{ + [Serializable] + public class InvalidOnionHmacException : Exception + { + public InvalidOnionHmacException() : + base("Onion has an invalid HMAC") + { + + } + } +} diff --git a/src/Lyn.Types/Onion/InvalidOnionVersionException.cs b/src/Lyn.Types/Onion/InvalidOnionVersionException.cs index 9861445..cdad249 100644 --- a/src/Lyn.Types/Onion/InvalidOnionVersionException.cs +++ b/src/Lyn.Types/Onion/InvalidOnionVersionException.cs @@ -5,9 +5,9 @@ namespace Lyn.Types.Onion [Serializable] public class InvalidOnionVersionException : Exception { - public InvalidOnionVersionException() : - base("Legacy Onion Format is not supported anymore") - { + public InvalidOnionVersionException() : + base("Legacy Onion Format is not supported anymore") + { } } From 54e0f2e135e00c7d70bb611f0d22c578f910f5f5 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Mon, 14 Nov 2022 00:09:26 -0800 Subject: [PATCH 24/34] add test case for OneHopPayload --- .../Bolt4/Data/SphinxReferenceVectors.cs | 4 ++++ src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs b/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs index f3ca8cd..3db8b43 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/Data/SphinxReferenceVectors.cs @@ -72,6 +72,10 @@ public static class SphinxReferenceVectors Convert.FromHexString("fd01582200000000000000000000000000000000000000000022000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000") }; + public static List OneHopPaymentPayload = new List() { + Convert.FromHexString("fd04f16500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + }; + public static byte[] AssociatedData = Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242"); public static byte[] GenerateFiller_ExpectedFiller = Convert.FromHexString("51c30cc8f20da0153ca3839b850bcbc8fefc7fd84802f3e78cb35a660e747b57aa5b0de555cbcf1e6f044a718cc34219b96597f3684eee7a0232e1754f638006cb15a14788217abdf1bdd67910dc1ca74a05dcce8b5ad841b0f939fca8935f6a3ff660e0efb409f1a24ce4aa16fc7dc074cd84422c10cc4dd4fc150dd6d1e4f50b36ce10fef29248dd0cec85c72eb3e4b2f4a7c03b5c9e0c9dd12976553ede3d0e295f842187b33ff743e6d685075e98e1bcab8a46bff0102ca8b2098ae91798d370b01ca7076d3d626952a03663fe8dc700d1358263b73ba30e36731a0b72092f8d5bc8cd346762e93b2bf203d00264e4bc136fc142de8f7b69154deb05854ea88e2d7506222c95ba1aab06"); diff --git a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs index 4a78009..4093e66 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/SphinxTests.cs @@ -216,6 +216,24 @@ public void CreatePaymentPacket_FullPayloads() } } + [Fact] + public void CreatePaymentPacket_SinglePayloadFillsOnion() + { + var curveActions = new EllipticCurveActions(); + var sphinx = new Sphinx(curveActions); + + var encryptedOnion = sphinx.CreateOnion(SphinxReferenceVectors.SessionKey, 1300, SphinxReferenceVectors.PublicKeys.Take(1), SphinxReferenceVectors.OneHopPaymentPayload, SphinxReferenceVectors.AssociatedData); + + OnionRoutingPacket currentPacket = encryptedOnion.Packet; + var decrypted = sphinx.PeelOnion(SphinxReferenceVectors.PrivateKeys[0], SphinxReferenceVectors.AssociatedData, currentPacket); + + var payload = decrypted.Payload; + var nextPacket = decrypted.NextPacket; + + Assert.Equal(payload, SphinxReferenceVectors.OneHopPaymentPayload[0]); + Assert.Equal(nextPacket.Hmac, new byte[32]); + } + } public static class ByteVector32 From 900eddc7c4037f84a25a55a65e607990b357ffb8 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Sat, 10 Dec 2022 22:02:49 -0800 Subject: [PATCH 25/34] some improvements to the sphinx class based on gpt notes --- src/Lyn.Protocol/Bolt4/Sphinx.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Lyn.Protocol/Bolt4/Sphinx.cs b/src/Lyn.Protocol/Bolt4/Sphinx.cs index 09101b2..b6f24c4 100644 --- a/src/Lyn.Protocol/Bolt4/Sphinx.cs +++ b/src/Lyn.Protocol/Bolt4/Sphinx.cs @@ -72,6 +72,7 @@ public ReadOnlySpan ExclusiveOR(ReadOnlySpan left, ReadOnlySpan GenerateStream(ReadOnlySpan keyData, int streamLength) { + // This is an unfortunate allocation, but NaCl seems to require a heap allocation for the key? var cipher = new ChaCha20(new ReadOnlyMemory(keyData.ToArray()), 0); var emptyPlainText = Enumerable.Range(0, streamLength).Select(x => 0x00).ToArray(); var nonce = Enumerable.Range(0, 12).Select(x => 0x00).ToArray(); @@ -80,13 +81,23 @@ public ReadOnlySpan GenerateStream(ReadOnlySpan keyData, int streamL public ReadOnlySpan ComputeBlindingFactor(PublicKey pubKey, ReadOnlySpan secret) { - // welcome to allocation city baby - population: BlindingFactor - return HashGenerator.Sha256(pubKey.GetSpan().ToArray().Concat(secret.ToArray()).ToArray()); + // create an array to hold the concatenated data + var concatenatedData = new byte[pubKey.GetSpan().Length + secret.Length]; + + // copy the pubKey and secret data into the concatenated data array + pubKey.GetSpan().CopyTo(concatenatedData); + secret.CopyTo(concatenatedData.AsSpan(pubKey.GetSpan().Length)); + + // compute the SHA-256 hash of the concatenated data + return HashGenerator.Sha256(concatenatedData); } public PublicKey BlindKey(PublicKey pubKey, ReadOnlySpan blindingFactor) { - var blindKeyBytes = _ellipticCurveActions.MultiplyPubKey(new PrivateKey(blindingFactor.ToArray()), pubKey); + var blindingFactorArray = new byte[blindingFactor.Length]; + blindingFactor.CopyTo(blindingFactorArray); + + var blindKeyBytes = _ellipticCurveActions.MultiplyPubKey(new PrivateKey(blindingFactorArray), pubKey); return new PublicKey(blindKeyBytes.ToArray()); } From 756d5cfa831ee843a617badbb06bad94931f55c2 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Sun, 11 Dec 2022 20:24:27 -0800 Subject: [PATCH 26/34] first pass at porting the failure messages --- .../Bolt4/Messages/FailureMessage.cs | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/Lyn.Protocol/Bolt4/Messages/FailureMessage.cs diff --git a/src/Lyn.Protocol/Bolt4/Messages/FailureMessage.cs b/src/Lyn.Protocol/Bolt4/Messages/FailureMessage.cs new file mode 100644 index 0000000..d1e26fa --- /dev/null +++ b/src/Lyn.Protocol/Bolt4/Messages/FailureMessage.cs @@ -0,0 +1,89 @@ +using System; +using System.Buffers; +using System.Linq; + +namespace Lyn.Protocol.Bolt4 +{ + + [Flags] + internal enum FailureMessageFlags : int + { + // 0x8000 (BADONION) - the onion was invalid. + BadOnion = 0x8000, + // 0x4000 (PERM) - the failure is permanent. + Permenant = 0x4000, + // 0x2000 (NODE) - the failure was at the final node. + Node = 0x2000, + // 0x1000 (UPDATE) - there is a new channel_update enclosed. + Update = 0x1000, + // 0x0800 (IGNORED) - the processing node does not wish to reveal the reason for the failure. + Ignored = 0x0800, + // 0x0400 (CHANNEL) - the failure was caused by an invalid channel_update. + Channel = 0x0400 + } + + public record FailureMessage(string Message, int Code); + + public record PermenantFailureMessage(string Message, int Code) : FailureMessage(Message, Code); + + // note: all onion failures are permenant as far as I can tell... + // note: this is the closest we can get to the scala Perm and BadOnion traits + public record BadOnionMessage(string Message, int Code, byte[] OnionHash) : PermenantFailureMessage(Message, Code); + + public record NodeFailureMessage(string Message, int Code) : FailureMessage(Message, Code); + + public record PermenantNodeFailureMessage(string Message, int Code) : PermenantFailureMessage(Message, Code); + + // TODO: Add channel_update + // public record ChannelUpdateFailureMessage(string Message, int Code, ChannelUpdate update) : FailureMessage(Message, Code); + + public record InvalidRealmMessage() : PermenantFailureMessage("realm was not understood by the processing node", ((int)FailureMessageFlags.Permenant | 1)); + + public record TemporaryNodeFailureMessage() : NodeFailureMessage("general temporary failure of the processing node", ((int)FailureMessageFlags.Node | 2)); + + public record PermanentNodeFailureMessage() : PermenantNodeFailureMessage("general permanent failure of the processing node", ((int)FailureMessageFlags.Permenant | (int)FailureMessageFlags.Node | 2)); + + public record RequiredNodeFeatureMissingMessage() : PermenantNodeFailureMessage("the processing node does not support the required feature bit", ((int)FailureMessageFlags.Permenant | (int)FailureMessageFlags.Node | 3)); + + public record InvalidOnionVersionMessage(byte[] OnionHash) : BadOnionMessage("onion version was not understood by the processing node", ((int)FailureMessageFlags.BadOnion | (int)FailureMessageFlags.Permenant | 4), OnionHash); + + public record InvalidOnionHmacMessage(byte[] OnionHash) : BadOnionMessage("onion HMAC was incorrect when it reached the processing node", ((int)FailureMessageFlags.BadOnion | (int)FailureMessageFlags.Permenant | 5), OnionHash); + + public record InvalidOnionKeyMessage(byte[] OnionHash) : BadOnionMessage("onion key was unparsable by the processing node", ((int)FailureMessageFlags.BadOnion | (int)FailureMessageFlags.Permenant | 6), OnionHash); + + public record InvalidOnionBlindingMessage(byte[] OnionHash) : BadOnionMessage("the blinded onion didn't match the processing node's requirements", ((int)FailureMessageFlags.BadOnion | (int)FailureMessageFlags.Permenant | 7), OnionHash); + + // todo: we need to add the channel_update logic to Lyn before we can implement this + // public record TemporaryChannelFailureMessage(ChannelUpdate update) : ChannelUpdateFailureMessage("channel ${update.shortChannelId} is currently unavailable", ((int)FailureMessageFlags.Update | 8), update); + + public record PermanentChannelFailureMessage() : PermenantFailureMessage("channel is permanently unavailable", ((int)FailureMessageFlags.Permenant | (int)FailureMessageFlags.Channel | 9)); + + public record RequiredChannelFeatureMissingMessage() : PermenantFailureMessage("channel requires features not present in the onion", ((int)FailureMessageFlags.Permenant | (int)FailureMessageFlags.Channel | 10)); + + public record UnknownNextPeerMessage() : PermenantFailureMessage("the next peer in the route was not known", ((int)FailureMessageFlags.Permenant | (int)FailureMessageFlags.Channel | 11)); + + // todo: we need to add the channel_update logic to Lyn before we can implement this + // public record AmountBelowMinimumMessage(MilliSatoshis amount, ChannelUpdate update) : ChannelUpdateFailureMessage("amount is below the minimum amount allowed", ((int)FailureMessageFlags.Update | 12), update); + // public record FeeInsufficientMessage(MilliSatoshis amount, ChannelUpdate update) : ChannelUpdateFailureMessage("fee is insufficient", ((int)FailureMessageFlags.Update | 13), update); + + public record TrampolineFeeInsufficientMessage() : PermenantFailureMessage("payment fee was below the minimum required by the trampoline node", ((int)FailureMessageFlags.Permenant | (int)FailureMessageFlags.Channel | 14)); + + // public record ChannelUpdateFailureMessage() : PermenantFailureMessage("channel is currently disabled", ((int)FailureMessageFlags.Permenant | (int)FailureMessageFlags.Channel | 15)); + + // public record IncorrectCltvExpiryMessage(CltvExpiry expiry, ChannelUpdate update) : ChannelUpdateFailureMessage("incorrect CLTV expiry", ((int)FailureMessageFlags.Update | 16), update); + + // public record ExpiryTooSoonMessage(CltvExpiry expiry, ChannelUpdate update) : ChannelUpdateFailureMessage("expiry is too close to the current block height for safe handling by the relaying node", ((int)FailureMessageFlags.Update | 17), update); + + public record TrampolineExpiryTooSoonMessage() : NodeFailureMessage("expiry is too close to the current block height for safe handling by the relaying node", 18); + + // public record FinalIncorrectCltvExpiryMessage(CltvExpiry expiry) : NodeFailureMessage("payment expiry doesn't match the value in the onion", 19); + + // public record FinalIncorrectHtlcAmountMessage(MiliSatoshi amount) : NodeFailureMessage("payment amount doesn't match the value in the onion", 20); + + public record ExpiryTooFarMessage() : NodeFailureMessage("payment expiry is too fari nt he future", 21); + + public record InvaldOnionPayloadMessage(ulong Tag, int Offset) : PermenantFailureMessage("nion per-hop payload is invalid", ((int)FailureMessageFlags.Permenant | 22)); + + public record PaymentTimeoutMessage() : FailureMessage("the complete payment amount was not received within a reasonable time", 23); + +} \ No newline at end of file From 6df0494c7c2d1d0c7f12a6579d04193af2c2089d Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Fri, 16 Dec 2022 13:54:33 -0800 Subject: [PATCH 27/34] start to implement failure message serialization and get some tests started --- .../Bolt4/FailureMessageSerializerTests.cs | 49 ++++++++++++ .../Bolt4/Messages/FailureMessage.cs | 74 +++++++++++-------- .../Messages/FailureMessageSerializer.cs | 49 ++++++++++++ 3 files changed, 141 insertions(+), 31 deletions(-) create mode 100644 src/Lyn.Protocol.Tests/Bolt4/FailureMessageSerializerTests.cs create mode 100644 src/Lyn.Protocol/Bolt4/Messages/FailureMessageSerializer.cs diff --git a/src/Lyn.Protocol.Tests/Bolt4/FailureMessageSerializerTests.cs b/src/Lyn.Protocol.Tests/Bolt4/FailureMessageSerializerTests.cs new file mode 100644 index 0000000..afc0895 --- /dev/null +++ b/src/Lyn.Protocol.Tests/Bolt4/FailureMessageSerializerTests.cs @@ -0,0 +1,49 @@ +using Lyn.Protocol.Bolt4; +using Lyn.Protocol.Bolt4.Entities; +using Lyn.Protocol.Bolt4.Messages; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Lyn.Protocol.Tests.Bolt4 +{ + // need to implement tests so we have parity with FailureMessageCodecsSpec.scala from eclair + public class FailureMessageSerializerTests + { + + [Fact] + public void EncodeAllFailureMessages() + { + var failureMessages = new List() { + new InvalidRealmMessage(), + new TemporaryNodeFailureMessage(), + new PermanentNodeFailureMessage(), + new RequiredNodeFeatureMissingMessage() + }; + + var serializer = new FailureMessageSerializer(); + // loop through all failure messages, serialize them, and then deserialize them and compare the result + foreach (var failureMessage in failureMessages) + { + var bufferWriter = new ArrayBufferWriter(256); + int bytesWritten = serializer.Serialize(failureMessage, bufferWriter, null); + var failureMessageBytes = bufferWriter.WrittenSpan.ToArray(); + + Assert.Equal(bytesWritten, failureMessageBytes.Length); + + var failureMessaegSequence = new ReadOnlySequence(failureMessageBytes); + var failureMessageSequenceReader = new SequenceReader(failureMessaegSequence); + + var failureMessageDeserialized = serializer.Deserialize(ref failureMessageSequenceReader, null); + + Assert.Equal(failureMessage, failureMessageDeserialized); + } + } + + } +} diff --git a/src/Lyn.Protocol/Bolt4/Messages/FailureMessage.cs b/src/Lyn.Protocol/Bolt4/Messages/FailureMessage.cs index d1e26fa..a6c1805 100644 --- a/src/Lyn.Protocol/Bolt4/Messages/FailureMessage.cs +++ b/src/Lyn.Protocol/Bolt4/Messages/FailureMessage.cs @@ -1,12 +1,13 @@ using System; using System.Buffers; using System.Linq; +using Lyn.Types.Serialization; namespace Lyn.Protocol.Bolt4 { [Flags] - internal enum FailureMessageFlags : int + public enum FailureMessageFlags : ushort { // 0x8000 (BADONION) - the onion was invalid. BadOnion = 0x8000, @@ -15,64 +16,75 @@ internal enum FailureMessageFlags : int // 0x2000 (NODE) - the failure was at the final node. Node = 0x2000, // 0x1000 (UPDATE) - there is a new channel_update enclosed. - Update = 0x1000, - // 0x0800 (IGNORED) - the processing node does not wish to reveal the reason for the failure. - Ignored = 0x0800, - // 0x0400 (CHANNEL) - the failure was caused by an invalid channel_update. - Channel = 0x0400 + Update = 0x1000 } - public record FailureMessage(string Message, int Code); + public record FailureMessage(string Message, ushort Code) + { + // todo: is this the best way to do this? i prolly also need deserialize... + public virtual int Serialize(IBufferWriter writer, ProtocolTypeSerializerOptions? options = null) + { + return writer.WriteUShort(Code); + } + } - public record PermenantFailureMessage(string Message, int Code) : FailureMessage(Message, Code); + public record PermenantFailureMessage(string Message, ushort Code) : FailureMessage(Message, Code); // note: all onion failures are permenant as far as I can tell... // note: this is the closest we can get to the scala Perm and BadOnion traits - public record BadOnionMessage(string Message, int Code, byte[] OnionHash) : PermenantFailureMessage(Message, Code); + public record BadOnionMessage(string Message, ushort Code, byte[] OnionHash) : PermenantFailureMessage(Message, Code) + { + public override int Serialize(IBufferWriter writer, ProtocolTypeSerializerOptions? options = null) + { + var bytesWritten = base.Serialize(writer, options); + bytesWritten += writer.WriteBytes(OnionHash); + return bytesWritten; + } + } - public record NodeFailureMessage(string Message, int Code) : FailureMessage(Message, Code); + public record NodeFailureMessage(string Message, ushort Code) : FailureMessage(Message, Code); - public record PermenantNodeFailureMessage(string Message, int Code) : PermenantFailureMessage(Message, Code); + public record PermenantNodeFailureMessage(string Message, ushort Code) : PermenantFailureMessage(Message, Code); // TODO: Add channel_update - // public record ChannelUpdateFailureMessage(string Message, int Code, ChannelUpdate update) : FailureMessage(Message, Code); + // public record ChannelUpdateFailureMessage(string Message, ushort Code, ChannelUpdate update) : FailureMessage(Message, Code); - public record InvalidRealmMessage() : PermenantFailureMessage("realm was not understood by the processing node", ((int)FailureMessageFlags.Permenant | 1)); + public record InvalidRealmMessage() : PermenantFailureMessage("realm was not understood by the processing node", ((ushort)FailureMessageFlags.Permenant | 1)); - public record TemporaryNodeFailureMessage() : NodeFailureMessage("general temporary failure of the processing node", ((int)FailureMessageFlags.Node | 2)); + public record TemporaryNodeFailureMessage() : NodeFailureMessage("general temporary failure of the processing node", ((ushort)FailureMessageFlags.Node | 2)); - public record PermanentNodeFailureMessage() : PermenantNodeFailureMessage("general permanent failure of the processing node", ((int)FailureMessageFlags.Permenant | (int)FailureMessageFlags.Node | 2)); + public record PermanentNodeFailureMessage() : PermenantNodeFailureMessage("general permanent failure of the processing node", ((ushort)FailureMessageFlags.Permenant | (ushort)FailureMessageFlags.Node | 2)); - public record RequiredNodeFeatureMissingMessage() : PermenantNodeFailureMessage("the processing node does not support the required feature bit", ((int)FailureMessageFlags.Permenant | (int)FailureMessageFlags.Node | 3)); + public record RequiredNodeFeatureMissingMessage() : PermenantNodeFailureMessage("the processing node does not support the required feature bit", ((ushort)FailureMessageFlags.Permenant | (ushort)FailureMessageFlags.Node | 3)); - public record InvalidOnionVersionMessage(byte[] OnionHash) : BadOnionMessage("onion version was not understood by the processing node", ((int)FailureMessageFlags.BadOnion | (int)FailureMessageFlags.Permenant | 4), OnionHash); + public record InvalidOnionVersionMessage(byte[] OnionHash) : BadOnionMessage("onion version was not understood by the processing node", ((ushort)FailureMessageFlags.BadOnion | (ushort)FailureMessageFlags.Permenant | 4), OnionHash); - public record InvalidOnionHmacMessage(byte[] OnionHash) : BadOnionMessage("onion HMAC was incorrect when it reached the processing node", ((int)FailureMessageFlags.BadOnion | (int)FailureMessageFlags.Permenant | 5), OnionHash); + public record InvalidOnionHmacMessage(byte[] OnionHash) : BadOnionMessage("onion HMAC was incorrect when it reached the processing node", ((ushort)FailureMessageFlags.BadOnion | (ushort)FailureMessageFlags.Permenant | 5), OnionHash); - public record InvalidOnionKeyMessage(byte[] OnionHash) : BadOnionMessage("onion key was unparsable by the processing node", ((int)FailureMessageFlags.BadOnion | (int)FailureMessageFlags.Permenant | 6), OnionHash); + public record InvalidOnionKeyMessage(byte[] OnionHash) : BadOnionMessage("onion key was unparsable by the processing node", ((ushort)FailureMessageFlags.BadOnion | (ushort)FailureMessageFlags.Permenant | 6), OnionHash); - public record InvalidOnionBlindingMessage(byte[] OnionHash) : BadOnionMessage("the blinded onion didn't match the processing node's requirements", ((int)FailureMessageFlags.BadOnion | (int)FailureMessageFlags.Permenant | 7), OnionHash); + public record InvalidOnionBlindingMessage(byte[] OnionHash) : BadOnionMessage("the blinded onion didn't match the processing node's requirements", ((ushort)FailureMessageFlags.BadOnion | (ushort)FailureMessageFlags.Permenant | 7), OnionHash); // todo: we need to add the channel_update logic to Lyn before we can implement this - // public record TemporaryChannelFailureMessage(ChannelUpdate update) : ChannelUpdateFailureMessage("channel ${update.shortChannelId} is currently unavailable", ((int)FailureMessageFlags.Update | 8), update); + // public record TemporaryChannelFailureMessage(ChannelUpdate update) : ChannelUpdateFailureMessage("channel ${update.shortChannelId} is currently unavailable", ((ushort)FailureMessageFlags.Update | 8), update); - public record PermanentChannelFailureMessage() : PermenantFailureMessage("channel is permanently unavailable", ((int)FailureMessageFlags.Permenant | (int)FailureMessageFlags.Channel | 9)); + public record PermanentChannelFailureMessage() : PermenantFailureMessage("channel is permanently unavailable", ((ushort)FailureMessageFlags.Permenant | (ushort)FailureMessageFlags.Update | 9)); - public record RequiredChannelFeatureMissingMessage() : PermenantFailureMessage("channel requires features not present in the onion", ((int)FailureMessageFlags.Permenant | (int)FailureMessageFlags.Channel | 10)); + public record RequiredChannelFeatureMissingMessage() : PermenantFailureMessage("channel requires features not present in the onion", ((ushort)FailureMessageFlags.Permenant | (ushort)FailureMessageFlags.Update | 10)); - public record UnknownNextPeerMessage() : PermenantFailureMessage("the next peer in the route was not known", ((int)FailureMessageFlags.Permenant | (int)FailureMessageFlags.Channel | 11)); + public record UnknownNextPeerMessage() : PermenantFailureMessage("the next peer in the route was not known", ((ushort)FailureMessageFlags.Permenant | (ushort)FailureMessageFlags.Update | 11)); // todo: we need to add the channel_update logic to Lyn before we can implement this - // public record AmountBelowMinimumMessage(MilliSatoshis amount, ChannelUpdate update) : ChannelUpdateFailureMessage("amount is below the minimum amount allowed", ((int)FailureMessageFlags.Update | 12), update); - // public record FeeInsufficientMessage(MilliSatoshis amount, ChannelUpdate update) : ChannelUpdateFailureMessage("fee is insufficient", ((int)FailureMessageFlags.Update | 13), update); + // public record AmountBelowMinimumMessage(MilliSatoshis amount, ChannelUpdate update) : ChannelUpdateFailureMessage("amount is below the minimum amount allowed", ((ushort)FailureMessageFlags.Update | 12), update); + // public record FeeInsufficientMessage(MilliSatoshis amount, ChannelUpdate update) : ChannelUpdateFailureMessage("fee is insufficient", ((ushort)FailureMessageFlags.Update | 13), update); - public record TrampolineFeeInsufficientMessage() : PermenantFailureMessage("payment fee was below the minimum required by the trampoline node", ((int)FailureMessageFlags.Permenant | (int)FailureMessageFlags.Channel | 14)); + public record TrampolineFeeInsufficientMessage() : PermenantFailureMessage("payment fee was below the minimum required by the trampoline node", ((ushort)FailureMessageFlags.Permenant | (ushort)FailureMessageFlags.Update | 14)); - // public record ChannelUpdateFailureMessage() : PermenantFailureMessage("channel is currently disabled", ((int)FailureMessageFlags.Permenant | (int)FailureMessageFlags.Channel | 15)); + // public record ChannelUpdateFailureMessage() : PermenantFailureMessage("channel is currently disabled", ((ushort)FailureMessageFlags.Permenant | (ushort)FailureMessageFlags.Channel | 15)); - // public record IncorrectCltvExpiryMessage(CltvExpiry expiry, ChannelUpdate update) : ChannelUpdateFailureMessage("incorrect CLTV expiry", ((int)FailureMessageFlags.Update | 16), update); + // public record IncorrectCltvExpiryMessage(CltvExpiry expiry, ChannelUpdate update) : ChannelUpdateFailureMessage("incorrect CLTV expiry", ((ushort)FailureMessageFlags.Update | 16), update); - // public record ExpiryTooSoonMessage(CltvExpiry expiry, ChannelUpdate update) : ChannelUpdateFailureMessage("expiry is too close to the current block height for safe handling by the relaying node", ((int)FailureMessageFlags.Update | 17), update); + // public record ExpiryTooSoonMessage(CltvExpiry expiry, ChannelUpdate update) : ChannelUpdateFailureMessage("expiry is too close to the current block height for safe handling by the relaying node", ((ushort)FailureMessageFlags.Update | 17), update); public record TrampolineExpiryTooSoonMessage() : NodeFailureMessage("expiry is too close to the current block height for safe handling by the relaying node", 18); @@ -82,7 +94,7 @@ public record TrampolineFeeInsufficientMessage() : PermenantFailureMessage("paym public record ExpiryTooFarMessage() : NodeFailureMessage("payment expiry is too fari nt he future", 21); - public record InvaldOnionPayloadMessage(ulong Tag, int Offset) : PermenantFailureMessage("nion per-hop payload is invalid", ((int)FailureMessageFlags.Permenant | 22)); + public record InvaldOnionPayloadMessage(ulong Tag, ushort Offset) : PermenantFailureMessage("nion per-hop payload is invalid", ((ushort)FailureMessageFlags.Permenant | 22)); public record PaymentTimeoutMessage() : FailureMessage("the complete payment amount was not received within a reasonable time", 23); diff --git a/src/Lyn.Protocol/Bolt4/Messages/FailureMessageSerializer.cs b/src/Lyn.Protocol/Bolt4/Messages/FailureMessageSerializer.cs new file mode 100644 index 0000000..47d4266 --- /dev/null +++ b/src/Lyn.Protocol/Bolt4/Messages/FailureMessageSerializer.cs @@ -0,0 +1,49 @@ +using Lyn.Types.Serialization; +using System; +using System.Buffers; + +namespace Lyn.Protocol.Bolt4.Messages +{ + public class FailureMessageSerializer : IProtocolTypeSerializer + { + + public int Serialize(FailureMessage typeInstance, IBufferWriter writer, ProtocolTypeSerializerOptions? options = null) + { + int bytesWritten = 0; + bytesWritten += typeInstance.Serialize(writer); + return bytesWritten; + } + + public FailureMessage Deserialize(ref SequenceReader reader, ProtocolTypeSerializerOptions? options = null) + { + + var failureMessageType = reader.ReadUShort(); + + // todo: this seems so clunky? + switch (failureMessageType) + { + case ((ushort)FailureMessageFlags.Permenant | 1): + return new InvalidRealmMessage(); + case ((ushort)FailureMessageFlags.Node | 2): + return new TemporaryNodeFailureMessage(); + case ((ushort)FailureMessageFlags.Permenant | (ushort)FailureMessageFlags.Node | 2): + return new PermanentNodeFailureMessage(); + case ((ushort)FailureMessageFlags.Permenant | (ushort)FailureMessageFlags.Node | 3): + return new RequiredNodeFeatureMissingMessage(); + case ((ushort)FailureMessageFlags.BadOnion | (ushort)FailureMessageFlags.Permenant | 4): + return new InvalidOnionVersionMessage(reader.ReadBytes(32).ToArray()); + case ((ushort)FailureMessageFlags.BadOnion | (ushort)FailureMessageFlags.Permenant | 5): + return new InvalidOnionHmacMessage(reader.ReadBytes(32).ToArray()); + case ((ushort)FailureMessageFlags.BadOnion | (ushort)FailureMessageFlags.Permenant | 6): + return new InvalidOnionKeyMessage(reader.ReadBytes(32).ToArray()); + case ((ushort)FailureMessageFlags.BadOnion | (ushort)FailureMessageFlags.Permenant | 7): + return new InvalidOnionBlindingMessage(reader.ReadBytes(32).ToArray()); + default: + // todo: return UnknownFailureMessage + throw new NotImplementedException(); + } + } + + } + +} \ No newline at end of file From 0697a3319b405e62f30727a4bca6e9263a3a2258 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Sun, 18 Dec 2022 21:09:37 -0800 Subject: [PATCH 28/34] majorly incomplete failure packet work - still gluing things together here --- src/Lyn.Protocol/Bolt4/FailurePacket.cs | 56 ++++++++++++++++++ .../Bolt4/Messages/FailurePacketSerializer.cs | 57 +++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 src/Lyn.Protocol/Bolt4/FailurePacket.cs create mode 100644 src/Lyn.Protocol/Bolt4/Messages/FailurePacketSerializer.cs diff --git a/src/Lyn.Protocol/Bolt4/FailurePacket.cs b/src/Lyn.Protocol/Bolt4/FailurePacket.cs new file mode 100644 index 0000000..e48bb2a --- /dev/null +++ b/src/Lyn.Protocol/Bolt4/FailurePacket.cs @@ -0,0 +1,56 @@ +using System.Buffers; +using System.Linq; +using Lyn.Protocol.Bolt4.Messages; +using Lyn.Protocol.Common.Crypto; + +namespace Lyn.Protocol.Bolt4 +{ + + // todo: this class doesn't feel very C#-y. It's a mix of static and instance methods, + // and it's not clear what the purpose of the instance methods are. + public class FailurePacket + { + public const int MaxPayloadLength = 256; + public const int PacketLength = 32 + MaxPayloadLength + 2 + 2; + + private readonly ISphinx sphinx; + + // todo: Do not love this Sphinx layering, need to resolve + public FailurePacket(ISphinx sphinx) + { + this.sphinx = sphinx; + } + + public byte[] Create(byte[] sharedSecret, FailureMessage failureMessage) + { + var um = sphinx.GenerateSphinxKey("um", sharedSecret); + var messageSerializer = new FailureMessageSerializer(); + + // create a bufferwriter to write the failure message to + var bufferWriter = new ArrayBufferWriter(); + int bytesWritten = messageSerializer.Serialize(failureMessage, bufferWriter); + + // var failureOnion = new FailureOnion(); + + return null; + } + + public byte[] Wrap(byte[] packet, byte[] sharedSecret) + { + if (packet.Length != PacketLength) + { + // this is a warn in eclair + // throw new ArgumentException($"Invalid error packet length {packet.Length}, must be {PacketLength} (malicious or buggy downstream node)"); + } + + var key = sphinx.GenerateSphinxKey("ammag", sharedSecret); + var stream = sphinx.GenerateStream(key, packet.Length); + + var paddedPacket = new byte[packet.Length].Concat(packet).ToArray(); + var result = sphinx.ExclusiveOR(paddedPacket, stream).ToArray(); + return result; + } + + } + +} \ No newline at end of file diff --git a/src/Lyn.Protocol/Bolt4/Messages/FailurePacketSerializer.cs b/src/Lyn.Protocol/Bolt4/Messages/FailurePacketSerializer.cs new file mode 100644 index 0000000..27b1ce3 --- /dev/null +++ b/src/Lyn.Protocol/Bolt4/Messages/FailurePacketSerializer.cs @@ -0,0 +1,57 @@ +using Lyn.Types.Serialization; +using System; +using System.Buffers; + +namespace Lyn.Protocol.Bolt4.Messages +{ + + /** + * An onion-encrypted failure from an intermediate node: + * {{{ + * +----------------+----------------------------------+-----------------+----------------------+-----+ + * | HMAC(32 bytes) | failure message length (2 bytes) | failure message | pad length (2 bytes) | pad | + * +----------------+----------------------------------+-----------------+----------------------+-----+ + * }}} + * with failure message length + pad length = 256 + */ + public record FailureOnion(byte[] Hmac, ushort FailureMessageLength, byte[] FailureMessage, ushort PadLength, byte[] Pad); + + public class FailureOnionSerializer : IProtocolTypeSerializer + { + + public FailureOnionSerializer() + { + } + + public int Serialize(FailureOnion typeInstance, IBufferWriter writer, ProtocolTypeSerializerOptions? options = null) + { + int bytesWritten = 0; + bytesWritten += writer.WriteBytes(typeInstance.Hmac); + bytesWritten += writer.WriteUShort(typeInstance.FailureMessageLength); + bytesWritten += writer.WriteBytes(typeInstance.FailureMessage); + bytesWritten += writer.WriteUShort(typeInstance.PadLength); + bytesWritten += writer.WriteBytes(typeInstance.Pad); + return bytesWritten; + } + + public FailureOnion Deserialize(ref SequenceReader reader, ProtocolTypeSerializerOptions? options = null) + { + // readi in the hmac first + var hmac = reader.ReadBytes(32).ToArray(); + + // next read the failure message length + var failureMessageLength = reader.ReadUShort(); + + // next read the failure message + var failureMessageBytes = reader.ReadBytes(failureMessageLength).ToArray(); + + // next read the pad length + var padLength = reader.ReadUShort(); + + // next read the pad + var pad = reader.ReadBytes(padLength).ToArray(); + + return new FailureOnion(hmac, failureMessageLength, failureMessageBytes, padLength, pad); + } + } +} \ No newline at end of file From abd99b9f6fe86179295f2cc4ed9a9695ab61209c Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Sat, 24 Dec 2022 13:45:03 -0800 Subject: [PATCH 29/34] WIP RouteBlinding First hop of reference vector works, but session key logic is currently busted causing subsequent hops to be wrong --- .../Bolt4/RouteBlindingTests.cs | 53 ++++++++ src/Lyn.Protocol/Bolt4/RouteBlinding.cs | 127 ++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 src/Lyn.Protocol.Tests/Bolt4/RouteBlindingTests.cs create mode 100644 src/Lyn.Protocol/Bolt4/RouteBlinding.cs diff --git a/src/Lyn.Protocol.Tests/Bolt4/RouteBlindingTests.cs b/src/Lyn.Protocol.Tests/Bolt4/RouteBlindingTests.cs new file mode 100644 index 0000000..1abdc86 --- /dev/null +++ b/src/Lyn.Protocol.Tests/Bolt4/RouteBlindingTests.cs @@ -0,0 +1,53 @@ +using Lyn.Protocol.Bolt4; +using Lyn.Protocol.Common.Crypto; +using System; +using System.Linq; +using Xunit; + +using Lyn.Protocol.Tests.Bolt4.Data; +using Lyn.Protocol.Bolt4.Entities; +using System.Collections.Generic; +using Lyn.Types.Fundamental; +using Lyn.Types.Onion; +using Lyn.Protocol.Bolt3; + +namespace Lyn.Protocol.Tests.Bolt4 +{ + public class RouteBlindingTests + { + + [Fact] + public void RouteBlinding_CanCreatedBlindedRoute_ReferenceTestVector() + { + var alice = new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")); + var bob = new PrivateKey(Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242")); + var bobPayload = Convert.FromHexString("01200000000000000000000000000000000000000000000000000000000000000000020800000000000000010a0800320000000027100c05000b7246320e00"); + var carol = new PrivateKey(Convert.FromHexString("4343434343434343434343434343434343434343434343434343434343434343")); + var carolPayload = Convert.FromHexString("020800000000000000020821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0a07004b00000096640c05000b7214320e00"); + var dave = new PrivateKey(Convert.FromHexString("4444444444444444444444444444444444444444444444444444444444444444")); + var davePayload = Convert.FromHexString("012200000000000000000000000000000000000000000000000000000000000000000000020800000000000000030a060019000000640c05000b71c9320e00"); + var eve = new PrivateKey(Convert.FromHexString("4545454545454545454545454545454545454545454545454545454545454545")); + var evePayload = Convert.FromHexString("011c000000000000000000000000000000000000000000000000000000000616c9cf92f45ade68345bc20ae672e2012f4af487ed44150c05000b71b0320e00"); + + var curveActions = new EllipticCurveActions(); + var lightningKeyDerivation = new LightningKeyDerivation(); + var sphinx = new Sphinx(curveActions); + + var routeBlinding = new RouteBlinding(sphinx, lightningKeyDerivation, curveActions); + + // Eve creates a blinded route to herself through Dave + var eveSelfSessionKey = new PrivateKey(Convert.FromHexString("0101010101010101010101010101010101010101010101010101010101010101")); + var daveEveHops = new List<(PublicKey PublicKey, byte[] Payload)>() { + (PublicKey: lightningKeyDerivation.PublicKeyFromPrivateKey(dave), Payload: davePayload), + (PublicKey: lightningKeyDerivation.PublicKeyFromPrivateKey(eve), Payload: evePayload) + }; + var (BlindedRoute, LastBlinding) = routeBlinding.Create(eveSelfSessionKey, daveEveHops); + // this passes, hooray! + Assert.Equal(new PublicKey(Convert.FromHexString("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")), BlindedRoute.BlindingKey); + // this fails, boo! + // the failure stems from the result of ```e*PrivKey(sha256(blindingPubKeyBytes.Concat(sharedSecret)))``` being incorrect + Assert.Equal(new PublicKey(Convert.FromHexString("03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a")), LastBlinding); + } + + } +} \ No newline at end of file diff --git a/src/Lyn.Protocol/Bolt4/RouteBlinding.cs b/src/Lyn.Protocol/Bolt4/RouteBlinding.cs new file mode 100644 index 0000000..d429140 --- /dev/null +++ b/src/Lyn.Protocol/Bolt4/RouteBlinding.cs @@ -0,0 +1,127 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Lyn.Protocol.Bolt3; +using Lyn.Protocol.Common.Crypto; +using Lyn.Types.Fundamental; +using NaCl.Core; + +namespace Lyn.Protocol.Bolt4 +{ + + public class RouteBlinding + { + + public record IntroductionNode(PublicKey PublicKey, PublicKey BlindedPublicKey, PublicKey BlindingEphemeralKey, byte[] EncryptedPayload); + + public record BlindedNode(PublicKey BlindedPublicKey, byte[] EncryptedPayload); + + public record BlindedRoute + { + + // todo: Are these two properties needed? The other properties contain the same information? + public PublicKey IntroductionNodeId { get; init; } + public PublicKey BlindingKey { get; init; } + + public IntroductionNode IntroductionNode { get; init; } + public IEnumerable SubsequentNodes { get; private set; } + public IEnumerable BlindedNodeIds { get; private set; } + public IEnumerable EncryptedPayloads { get; private set; } + + + public BlindedNode[] BlindedNodes { get; init; } + + public BlindedRoute(PublicKey introductionNodeId, PublicKey blindingKey, BlindedNode[] blindedNodes) + { + if (blindedNodes.Length == 0) + { + throw new ArgumentException(nameof(blindedNodes), "blinded route must not be empty"); + } + + IntroductionNodeId = introductionNodeId; + BlindingKey = blindingKey; + BlindedNodes = blindedNodes; + + IntroductionNode = new IntroductionNode(IntroductionNodeId, BlindedNodes.First().BlindedPublicKey, BlindingKey, BlindedNodes.First().EncryptedPayload); + SubsequentNodes = BlindedNodes.Skip(1); + BlindedNodeIds = BlindedNodes.Select(x => x.BlindedPublicKey); + EncryptedPayloads = BlindedNodes.Select(x => x.EncryptedPayload); + } + } + + public record BlindedRouteDetails(BlindedRoute Route, PublicKey LastBlinding); + + private readonly ISphinx _sphinx = null; + private readonly ILightningKeyDerivation _lightningKeyDerivation = null; + private readonly IEllipticCurveActions _ellipticCureActions = null; + + public RouteBlinding(ISphinx sphinx, ILightningKeyDerivation lightningKeyDerivation, IEllipticCurveActions ellipticCurveActions) + { + this._sphinx = sphinx; + this._lightningKeyDerivation = lightningKeyDerivation; + this._ellipticCureActions = ellipticCurveActions; + } + + // note: eclair has separate lists of public keys and encrypted payloads, yet asserts that they are the same length + // todo: should we do the same? i feel this enumerable of tuples is more elegant but i wonder if there's a 'muh security' reason for the separate lists + public BlindedRouteDetails Create(PrivateKey sessionKey, IEnumerable<(PublicKey PublicKey, byte[] Payload)> hops) + { + var e = sessionKey; + var blindedHopsAndKeys = hops.Select((hop) => { + var (publicKey, payload) = hop; + var blindingKey = _lightningKeyDerivation.PublicKeyFromPrivateKey(e); + var sharedSecret = _sphinx.ComputeSharedSecret(publicKey, sessionKey); + var blindedPublicKey = _sphinx.BlindKey(publicKey, _sphinx.GenerateSphinxKey("blinded_node_id", sharedSecret)); + var rho = _sphinx.GenerateSphinxKey("rho", sharedSecret); + + // is this right? who knows! i hsould ask chatgpt later + // note: i didn't use the ChaCha20Poly1305CipherFunction bc the nonce increments by 1 for each call, and this requires a nonce of 0(?) + var cipher = new ChaCha20Poly1305(rho.ToArray()); + Span encryptedPayload = stackalloc byte[payload.Length]; + Span mac = stackalloc byte[16]; + cipher.Encrypt(new byte[12], payload.AsSpan(), encryptedPayload, mac, new byte[0]); + + // broken: the value for e is not being calculated correctly + // broken: this is also likely massively inefficient + var keyBytes = blindingKey.GetSpan().ToArray(); + var sharedSecretArr = sharedSecret.ToArray(); + var hash = HashGenerator.Sha256(keyBytes.Concat(sharedSecretArr).ToArray()); + Debug.WriteLine($"hash: {Convert.ToHexString(hash.ToArray())}"); + var newPrivKey = new PrivateKey(hash.ToArray()); + e = new PrivateKey(_ellipticCureActions.Multiply(newPrivKey, blindingKey).ToArray()); + Debug.WriteLine($"e*PrivKey(hash): {Convert.ToHexString(e)}"); + return (blindedHop: new BlindedNode(blindedPublicKey, encryptedPayload.ToArray()), blindingKey: blindingKey); + }).ToList(); + + return new BlindedRouteDetails(new BlindedRoute(hops.First().PublicKey, + blindedHopsAndKeys.First().blindingKey, + blindedHopsAndKeys.Select(x => x.blindedHop).ToArray()), + blindedHopsAndKeys.Last().blindingKey + ); + } + + public PrivateKey DerivePrivateKey(PrivateKey privateKey, PublicKey blindingEphemeralKey) + { + // todo: i need some test coverage to verify the multiply logic here + // note: my guess? way off like in Create ¯\_(ツ)_/¯ + var sharedSecret = _sphinx.ComputeSharedSecret(blindingEphemeralKey, privateKey); + var generatedKey = new PrivateKey(_sphinx.GenerateSphinxKey("blinded_node_id", sharedSecret).ToArray()); + var pubKeyForPriv = _lightningKeyDerivation.PublicKeyFromPrivateKey(privateKey); + return new PrivateKey(_ellipticCureActions.Multiply(generatedKey, pubKeyForPriv).ToArray()); + } + + public (byte[], PublicKey) DecryptPayload(PrivateKey privateKey, PublicKey blindingEphemeralKey, byte[] encryptedPayload) + { + var sharedSecret = _sphinx.ComputeSharedSecret(blindingEphemeralKey, privateKey); + var rho = _sphinx.GenerateSphinxKey("rho", sharedSecret); + var cipher = new ChaCha20Poly1305(rho.ToArray()); + Span decryptedPayload = stackalloc byte[encryptedPayload.Length - 16]; + Span mac = stackalloc byte[16]; + cipher.Decrypt(new byte[12], encryptedPayload.AsSpan(), decryptedPayload, mac, new byte[0]); + var nextBlindingEphemeralKey = _sphinx.BlindKey(blindingEphemeralKey, _sphinx.ComputeBlindingFactor(blindingEphemeralKey, sharedSecret)); + return (decryptedPayload.ToArray(), nextBlindingEphemeralKey); + } + } +} From 15bfe531ec5bffc79ecdcaa4a5ea0d21675213be Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Sun, 25 Dec 2022 23:35:18 -0800 Subject: [PATCH 30/34] add new multiply with private key to try and fix the first hop --- src/Lyn.Protocol/Bolt4/RouteBlinding.cs | 2 +- src/Lyn.Protocol/Common/Crypto/EllipticCurveActions.cs | 8 ++++++++ src/Lyn.Protocol/Common/Crypto/IEllipticCurveActions.cs | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Lyn.Protocol/Bolt4/RouteBlinding.cs b/src/Lyn.Protocol/Bolt4/RouteBlinding.cs index d429140..4d678cf 100644 --- a/src/Lyn.Protocol/Bolt4/RouteBlinding.cs +++ b/src/Lyn.Protocol/Bolt4/RouteBlinding.cs @@ -90,7 +90,7 @@ public BlindedRouteDetails Create(PrivateKey sessionKey, IEnumerable<(PublicKey var hash = HashGenerator.Sha256(keyBytes.Concat(sharedSecretArr).ToArray()); Debug.WriteLine($"hash: {Convert.ToHexString(hash.ToArray())}"); var newPrivKey = new PrivateKey(hash.ToArray()); - e = new PrivateKey(_ellipticCureActions.Multiply(newPrivKey, blindingKey).ToArray()); + e = new PrivateKey(_ellipticCureActions.MultiplyWithPrivateKey(newPrivKey, e).ToArray()); Debug.WriteLine($"e*PrivKey(hash): {Convert.ToHexString(e)}"); return (blindedHop: new BlindedNode(blindedPublicKey, encryptedPayload.ToArray()), blindingKey: blindingKey); }).ToList(); diff --git a/src/Lyn.Protocol/Common/Crypto/EllipticCurveActions.cs b/src/Lyn.Protocol/Common/Crypto/EllipticCurveActions.cs index c86524b..96fb851 100644 --- a/src/Lyn.Protocol/Common/Crypto/EllipticCurveActions.cs +++ b/src/Lyn.Protocol/Common/Crypto/EllipticCurveActions.cs @@ -10,6 +10,14 @@ public ReadOnlySpan Multiply(byte[] privateKey, ReadOnlySpan publicK => new PubKey(publicKey.ToArray()) .GetSharedSecret(new Key(privateKey)); + public ReadOnlySpan MultiplyWithPrivateKey(byte[] privateKey, ReadOnlySpan blindingKey) + { + NBitcoin.Secp256k1.ECPrivKey.TryCreate(privateKey, out var privKey); + byte[] result = new byte[32]; + privKey.TweakMul(blindingKey).WriteToSpan(result.AsSpan()); + + return result; + } public ReadOnlySpan MultiplyPubKey(byte[] privateKey, ReadOnlySpan publicKey) => new PubKey(publicKey.ToArray()) diff --git a/src/Lyn.Protocol/Common/Crypto/IEllipticCurveActions.cs b/src/Lyn.Protocol/Common/Crypto/IEllipticCurveActions.cs index 63286e6..2eece23 100644 --- a/src/Lyn.Protocol/Common/Crypto/IEllipticCurveActions.cs +++ b/src/Lyn.Protocol/Common/Crypto/IEllipticCurveActions.cs @@ -5,6 +5,7 @@ namespace Lyn.Protocol.Common.Crypto public interface IEllipticCurveActions { ReadOnlySpan Multiply(byte[] privateKey, ReadOnlySpan publicKey); + ReadOnlySpan MultiplyWithPrivateKey(byte[] privateKey, ReadOnlySpan blindingKey); ReadOnlySpan MultiplyPubKey(byte[] privateKey, ReadOnlySpan publicKey); } } \ No newline at end of file From 946aed2ddf1d5bb0759b661c195aa3c204663ce0 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Wed, 28 Dec 2022 13:11:33 -0800 Subject: [PATCH 31/34] nit cleanup --- src/Lyn.Protocol/Bolt4/Sphinx.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Lyn.Protocol/Bolt4/Sphinx.cs b/src/Lyn.Protocol/Bolt4/Sphinx.cs index b6f24c4..26cbac2 100644 --- a/src/Lyn.Protocol/Bolt4/Sphinx.cs +++ b/src/Lyn.Protocol/Bolt4/Sphinx.cs @@ -57,8 +57,10 @@ public PrivateKey DeriveBlindedPrivateKey(PrivateKey privateKey, PublicKey blind // todo: util/helper public ReadOnlySpan ExclusiveOR(ReadOnlySpan left, ReadOnlySpan right) { - if (left.Length != right.Length) + if (left.Length != right.Length) + { throw new ArgumentException("inputs must be same length"); + } byte[] result = new byte[left.Length]; From d57730e0973dbb1524d828f5dc8a4b9fea1196ce Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Wed, 28 Dec 2022 13:51:46 -0800 Subject: [PATCH 32/34] get testr to the point where i can concatenate routes and verify the payloads & keys look appropriate --- .../Bolt4/RouteBlindingTests.cs | 56 +++++++-- src/Lyn.Protocol/Bolt4/RouteBlinding.cs | 106 +++++++++--------- 2 files changed, 100 insertions(+), 62 deletions(-) diff --git a/src/Lyn.Protocol.Tests/Bolt4/RouteBlindingTests.cs b/src/Lyn.Protocol.Tests/Bolt4/RouteBlindingTests.cs index 1abdc86..21bb76a 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/RouteBlindingTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/RouteBlindingTests.cs @@ -10,6 +10,7 @@ using Lyn.Types.Fundamental; using Lyn.Types.Onion; using Lyn.Protocol.Bolt3; +using System.Diagnostics; namespace Lyn.Protocol.Tests.Bolt4 { @@ -19,34 +20,67 @@ public class RouteBlindingTests [Fact] public void RouteBlinding_CanCreatedBlindedRoute_ReferenceTestVector() { + // note: this is really ugly rn, will clean up once e2e works + + var lightningKeyDerivation = new LightningKeyDerivation(); + var alice = new PrivateKey(Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141")); + var alicePubKey = lightningKeyDerivation.PublicKeyFromPrivateKey(alice); var bob = new PrivateKey(Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242")); + var bobPubKey = lightningKeyDerivation.PublicKeyFromPrivateKey(bob); var bobPayload = Convert.FromHexString("01200000000000000000000000000000000000000000000000000000000000000000020800000000000000010a0800320000000027100c05000b7246320e00"); var carol = new PrivateKey(Convert.FromHexString("4343434343434343434343434343434343434343434343434343434343434343")); + var carolPubKey = lightningKeyDerivation.PublicKeyFromPrivateKey(carol); var carolPayload = Convert.FromHexString("020800000000000000020821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0a07004b00000096640c05000b7214320e00"); var dave = new PrivateKey(Convert.FromHexString("4444444444444444444444444444444444444444444444444444444444444444")); + var davePubKey = lightningKeyDerivation.PublicKeyFromPrivateKey(dave); var davePayload = Convert.FromHexString("012200000000000000000000000000000000000000000000000000000000000000000000020800000000000000030a060019000000640c05000b71c9320e00"); var eve = new PrivateKey(Convert.FromHexString("4545454545454545454545454545454545454545454545454545454545454545")); + var evePubKey = lightningKeyDerivation.PublicKeyFromPrivateKey(eve); var evePayload = Convert.FromHexString("011c000000000000000000000000000000000000000000000000000000000616c9cf92f45ade68345bc20ae672e2012f4af487ed44150c05000b71b0320e00"); var curveActions = new EllipticCurveActions(); - var lightningKeyDerivation = new LightningKeyDerivation(); var sphinx = new Sphinx(curveActions); var routeBlinding = new RouteBlinding(sphinx, lightningKeyDerivation, curveActions); // Eve creates a blinded route to herself through Dave - var eveSelfSessionKey = new PrivateKey(Convert.FromHexString("0101010101010101010101010101010101010101010101010101010101010101")); - var daveEveHops = new List<(PublicKey PublicKey, byte[] Payload)>() { - (PublicKey: lightningKeyDerivation.PublicKeyFromPrivateKey(dave), Payload: davePayload), - (PublicKey: lightningKeyDerivation.PublicKeyFromPrivateKey(eve), Payload: evePayload) + var eveSessionKey = new PrivateKey(Convert.FromHexString("0101010101010101010101010101010101010101010101010101010101010101")); + var daveEveHops = new List<(PublicKey PublicKey, byte[] Payload)>() { + (PublicKey: davePubKey, Payload: davePayload), + (PublicKey: evePubKey, Payload: evePayload) }; - var (BlindedRoute, LastBlinding) = routeBlinding.Create(eveSelfSessionKey, daveEveHops); - // this passes, hooray! - Assert.Equal(new PublicKey(Convert.FromHexString("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")), BlindedRoute.BlindingKey); - // this fails, boo! - // the failure stems from the result of ```e*PrivKey(sha256(blindingPubKeyBytes.Concat(sharedSecret)))``` being incorrect - Assert.Equal(new PublicKey(Convert.FromHexString("03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a")), LastBlinding); + var (blindedRouteEnd, lastBlinding) = routeBlinding.Create(eveSessionKey, daveEveHops); + Assert.Equal(new PublicKey(Convert.FromHexString("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")), blindedRouteEnd.BlindingKey); + Assert.Equal(new PublicKey(Convert.FromHexString("03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a")), lastBlinding); + + // Bob also wants to use route blinding: + var bobSessionKey = new PrivateKey(Convert.FromHexString("0202020202020202020202020202020202020202020202020202020202020202")); + var bobCarolHops = new List<(PublicKey PublicKey, byte[] Payload)>() { + (PublicKey: lightningKeyDerivation.PublicKeyFromPrivateKey(bob), Payload: bobPayload), + (PublicKey: lightningKeyDerivation.PublicKeyFromPrivateKey(carol), Payload: carolPayload) + }; + var blindedRouteStart = routeBlinding.Create(bobSessionKey, bobCarolHops).Route; + Assert.Equal(new PublicKey(Convert.FromHexString("024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766")), blindedRouteStart.BlindingKey); + + // We now have a blinded route Bob -> Carol -> Dave -> Eve + var blindedRoute = new BlindedRoute(bobPubKey, + blindedRouteStart.BlindingKey, + blindedRouteStart.BlindedNodes.Concat(blindedRouteEnd.BlindedNodes).ToArray()); + + Assert.Equal(new List() { + new PublicKey(Convert.FromHexString("03da173ad2aee2f701f17e59fbd16cb708906d69838a5f088e8123fb36e89a2c25")), + new PublicKey(Convert.FromHexString("02e466727716f044290abf91a14a6d90e87487da160c2a3cbd0d465d7a78eb83a7")), + new PublicKey(Convert.FromHexString("036861b366f284f0a11738ffbf7eda46241a8977592878fe3175ae1d1e4754eccf")), + new PublicKey(Convert.FromHexString("021982a48086cb8984427d3727fe35a03d396b234f0701f5249daa12e8105c8dae")), + }, blindedRoute.BlindedNodeIds); + + Assert.Equal(new List() { + Convert.FromHexString("cd7b00ff9c09ed28102b210ac73aa12d63e90852cebc496c49f57c499a2888b49f2e72b19446f7e60a818aa2938d8c625415b992b8928a7321edb8f7cea40de362bed082ad51acc6156dca5532fb68"), + Convert.FromHexString("cc0f16524fd7f8bb0f4e8d40ad71709ef140174c76faa574cac401bb8992fef76c4d004aa485dd599ed1cf2715f570f656a5aaecaf1ee8dc9d0fa1d424759be1932a8f29fac08bc2d2a1ed7159f28b"), + Convert.FromHexString("0fa1a72cff3b64a3d6e1e4903cf8c8b0a17144aeb249dcb86561adee1f679ee8db3e561d9e49895fd4bcebf6f58d6f61a6d41a9bf5aa4b0453437856632e8255c351873143ddf2bb2b0832b091e1b4"), + Convert.FromHexString("da1c7e5f7881219884beae6ae68971de73bab4c3055d9865b1afb60722a63c688768042ade22f2c22f5724767d171fd221d3e579e43b354cc72e3ef146ada91a892d95fc48662f5b158add0af457da") + }, blindedRoute.EncryptedPayloads.ToList()); } } diff --git a/src/Lyn.Protocol/Bolt4/RouteBlinding.cs b/src/Lyn.Protocol/Bolt4/RouteBlinding.cs index 4d678cf..df87577 100644 --- a/src/Lyn.Protocol/Bolt4/RouteBlinding.cs +++ b/src/Lyn.Protocol/Bolt4/RouteBlinding.cs @@ -11,47 +11,50 @@ namespace Lyn.Protocol.Bolt4 { - public class RouteBlinding - { + public record IntroductionNode(PublicKey PublicKey, PublicKey BlindedPublicKey, PublicKey BlindingEphemeralKey, byte[] EncryptedPayload); - public record IntroductionNode(PublicKey PublicKey, PublicKey BlindedPublicKey, PublicKey BlindingEphemeralKey, byte[] EncryptedPayload); + public record BlindedNode(PublicKey BlindedPublicKey, byte[] EncryptedPayload); - public record BlindedNode(PublicKey BlindedPublicKey, byte[] EncryptedPayload); + public record BlindedRoute + { - public record BlindedRoute - { + // todo: Are these two properties needed? The other properties contain the same information? + public PublicKey IntroductionNodeId { get; init; } + public PublicKey BlindingKey { get; init; } - // todo: Are these two properties needed? The other properties contain the same information? - public PublicKey IntroductionNodeId { get; init; } - public PublicKey BlindingKey { get; init; } - - public IntroductionNode IntroductionNode { get; init; } - public IEnumerable SubsequentNodes { get; private set; } - public IEnumerable BlindedNodeIds { get; private set; } - public IEnumerable EncryptedPayloads { get; private set; } + public IntroductionNode IntroductionNode { get; init; } + public IEnumerable SubsequentNodes { get; private set; } + public IEnumerable BlindedNodeIds { get; private set; } + public IEnumerable EncryptedPayloads { get; private set; } - public BlindedNode[] BlindedNodes { get; init; } + public BlindedNode[] BlindedNodes { get; init; } - public BlindedRoute(PublicKey introductionNodeId, PublicKey blindingKey, BlindedNode[] blindedNodes) + public BlindedRoute(PublicKey introductionNodeId, PublicKey blindingKey, BlindedNode[] blindedNodes) + { + if (blindedNodes.Length == 0) { - if (blindedNodes.Length == 0) - { - throw new ArgumentException(nameof(blindedNodes), "blinded route must not be empty"); - } - - IntroductionNodeId = introductionNodeId; - BlindingKey = blindingKey; - BlindedNodes = blindedNodes; - - IntroductionNode = new IntroductionNode(IntroductionNodeId, BlindedNodes.First().BlindedPublicKey, BlindingKey, BlindedNodes.First().EncryptedPayload); - SubsequentNodes = BlindedNodes.Skip(1); - BlindedNodeIds = BlindedNodes.Select(x => x.BlindedPublicKey); - EncryptedPayloads = BlindedNodes.Select(x => x.EncryptedPayload); + throw new ArgumentException(nameof(blindedNodes), "blinded route must not be empty"); } + + IntroductionNodeId = introductionNodeId; + BlindingKey = blindingKey; + BlindedNodes = blindedNodes; + + IntroductionNode = new IntroductionNode(IntroductionNodeId, BlindedNodes.First().BlindedPublicKey, BlindingKey, BlindedNodes.First().EncryptedPayload); + SubsequentNodes = BlindedNodes.Skip(1); + BlindedNodeIds = BlindedNodes.Select(x => x.BlindedPublicKey); + EncryptedPayloads = BlindedNodes.Select(x => x.EncryptedPayload); } + } + + public record BlindedRouteDetails(BlindedRoute Route, PublicKey LastBlinding); + + public class RouteBlinding + { + + - public record BlindedRouteDetails(BlindedRoute Route, PublicKey LastBlinding); private readonly ISphinx _sphinx = null; private readonly ILightningKeyDerivation _lightningKeyDerivation = null; @@ -66,50 +69,51 @@ public RouteBlinding(ISphinx sphinx, ILightningKeyDerivation lightningKeyDerivat // note: eclair has separate lists of public keys and encrypted payloads, yet asserts that they are the same length // todo: should we do the same? i feel this enumerable of tuples is more elegant but i wonder if there's a 'muh security' reason for the separate lists - public BlindedRouteDetails Create(PrivateKey sessionKey, IEnumerable<(PublicKey PublicKey, byte[] Payload)> hops) + public BlindedRouteDetails Create(PrivateKey sessionKey, IEnumerable<(PublicKey PublicKey, byte[] Payload)> hops) { var e = sessionKey; - var blindedHopsAndKeys = hops.Select((hop) => { + var blindedHopsAndKeys = hops.Select((hop) => + { var (publicKey, payload) = hop; var blindingKey = _lightningKeyDerivation.PublicKeyFromPrivateKey(e); - var sharedSecret = _sphinx.ComputeSharedSecret(publicKey, sessionKey); + var sharedSecret = _sphinx.ComputeSharedSecret(publicKey, e); var blindedPublicKey = _sphinx.BlindKey(publicKey, _sphinx.GenerateSphinxKey("blinded_node_id", sharedSecret)); var rho = _sphinx.GenerateSphinxKey("rho", sharedSecret); // is this right? who knows! i hsould ask chatgpt later // note: i didn't use the ChaCha20Poly1305CipherFunction bc the nonce increments by 1 for each call, and this requires a nonce of 0(?) var cipher = new ChaCha20Poly1305(rho.ToArray()); - Span encryptedPayload = stackalloc byte[payload.Length]; - Span mac = stackalloc byte[16]; - cipher.Encrypt(new byte[12], payload.AsSpan(), encryptedPayload, mac, new byte[0]); - - // broken: the value for e is not being calculated correctly - // broken: this is also likely massively inefficient - var keyBytes = blindingKey.GetSpan().ToArray(); - var sharedSecretArr = sharedSecret.ToArray(); - var hash = HashGenerator.Sha256(keyBytes.Concat(sharedSecretArr).ToArray()); - Debug.WriteLine($"hash: {Convert.ToHexString(hash.ToArray())}"); - var newPrivKey = new PrivateKey(hash.ToArray()); + Span encryptedPayload = stackalloc byte[payload.Length + 16]; + Span ciphertext = stackalloc byte[payload.Length]; + Span mac = stackalloc byte[16]; + cipher.Encrypt(new byte[12], payload.AsSpan(), ciphertext, mac, new byte[0]); + + ciphertext.CopyTo(encryptedPayload); + mac.CopyTo(encryptedPayload.Slice(payload.Length)); + + // todo: is blindingKey length a fixed 32 bytes? + Span bytesToHash = stackalloc byte[blindingKey.GetSpan().Length + sharedSecret.Length]; + blindingKey.GetSpan().CopyTo(bytesToHash); + sharedSecret.CopyTo(bytesToHash.Slice(blindingKey.GetSpan().Length)); + var newPrivKey = new PrivateKey(HashGenerator.Sha256(bytesToHash).ToArray()); e = new PrivateKey(_ellipticCureActions.MultiplyWithPrivateKey(newPrivKey, e).ToArray()); - Debug.WriteLine($"e*PrivKey(hash): {Convert.ToHexString(e)}"); return (blindedHop: new BlindedNode(blindedPublicKey, encryptedPayload.ToArray()), blindingKey: blindingKey); }).ToList(); - return new BlindedRouteDetails(new BlindedRoute(hops.First().PublicKey, - blindedHopsAndKeys.First().blindingKey, - blindedHopsAndKeys.Select(x => x.blindedHop).ToArray()), + return new BlindedRouteDetails(new BlindedRoute(hops.First().PublicKey, + blindedHopsAndKeys.First().blindingKey, + blindedHopsAndKeys.Select(x => x.blindedHop).ToArray()), blindedHopsAndKeys.Last().blindingKey ); } - + public PrivateKey DerivePrivateKey(PrivateKey privateKey, PublicKey blindingEphemeralKey) { // todo: i need some test coverage to verify the multiply logic here // note: my guess? way off like in Create ¯\_(ツ)_/¯ var sharedSecret = _sphinx.ComputeSharedSecret(blindingEphemeralKey, privateKey); var generatedKey = new PrivateKey(_sphinx.GenerateSphinxKey("blinded_node_id", sharedSecret).ToArray()); - var pubKeyForPriv = _lightningKeyDerivation.PublicKeyFromPrivateKey(privateKey); - return new PrivateKey(_ellipticCureActions.Multiply(generatedKey, pubKeyForPriv).ToArray()); + return new PrivateKey(_ellipticCureActions.MultiplyWithPrivateKey(generatedKey, privateKey).ToArray()); } public (byte[], PublicKey) DecryptPayload(PrivateKey privateKey, PublicKey blindingEphemeralKey, byte[] encryptedPayload) From 904904d6bef3ca200804c1fb1f114971d2cc9bc4 Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Wed, 28 Dec 2022 13:55:30 -0800 Subject: [PATCH 33/34] add Eve's blinded key validation --- src/Lyn.Protocol.Tests/Bolt4/RouteBlindingTests.cs | 5 +++++ src/Lyn.Protocol/Bolt4/RouteBlinding.cs | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Lyn.Protocol.Tests/Bolt4/RouteBlindingTests.cs b/src/Lyn.Protocol.Tests/Bolt4/RouteBlindingTests.cs index 21bb76a..63067c1 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/RouteBlindingTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/RouteBlindingTests.cs @@ -81,6 +81,11 @@ public void RouteBlinding_CanCreatedBlindedRoute_ReferenceTestVector() Convert.FromHexString("0fa1a72cff3b64a3d6e1e4903cf8c8b0a17144aeb249dcb86561adee1f679ee8db3e561d9e49895fd4bcebf6f58d6f61a6d41a9bf5aa4b0453437856632e8255c351873143ddf2bb2b0832b091e1b4"), Convert.FromHexString("da1c7e5f7881219884beae6ae68971de73bab4c3055d9865b1afb60722a63c688768042ade22f2c22f5724767d171fd221d3e579e43b354cc72e3ef146ada91a892d95fc48662f5b158add0af457da") }, blindedRoute.EncryptedPayloads.ToList()); + + // After generating the blinded route, Eve is able to derive the private key corresponding to her blinded payload + var eveBlindedPrivKey = routeBlinding.DerivePrivateKey(eve, lastBlinding); + var eveBlindedPubKey = lightningKeyDerivation.PublicKeyFromPrivateKey(eveBlindedPrivKey); + Assert.Equal(eveBlindedPubKey, blindedRoute.BlindedNodeIds.LastOrDefault()); } } diff --git a/src/Lyn.Protocol/Bolt4/RouteBlinding.cs b/src/Lyn.Protocol/Bolt4/RouteBlinding.cs index df87577..4c53ce3 100644 --- a/src/Lyn.Protocol/Bolt4/RouteBlinding.cs +++ b/src/Lyn.Protocol/Bolt4/RouteBlinding.cs @@ -53,9 +53,6 @@ public record BlindedRouteDetails(BlindedRoute Route, PublicKey LastBlinding); public class RouteBlinding { - - - private readonly ISphinx _sphinx = null; private readonly ILightningKeyDerivation _lightningKeyDerivation = null; private readonly IEllipticCurveActions _ellipticCureActions = null; From db247a72911d99e83167b09e3efd46854cd5c03a Mon Sep 17 00:00:00 2001 From: "Pegasus J. Crawford II" Date: Thu, 29 Dec 2022 00:10:44 -0800 Subject: [PATCH 34/34] get route blinding e2e validated up until it's time for Tlv Packets --- .../Bolt4/RouteBlindingTests.cs | 43 +++++++++++++++++++ src/Lyn.Protocol/Bolt4/RouteBlinding.cs | 41 ++++++++++++------ 2 files changed, 70 insertions(+), 14 deletions(-) diff --git a/src/Lyn.Protocol.Tests/Bolt4/RouteBlindingTests.cs b/src/Lyn.Protocol.Tests/Bolt4/RouteBlindingTests.cs index 63067c1..e11cd32 100644 --- a/src/Lyn.Protocol.Tests/Bolt4/RouteBlindingTests.cs +++ b/src/Lyn.Protocol.Tests/Bolt4/RouteBlindingTests.cs @@ -54,6 +54,9 @@ public void RouteBlinding_CanCreatedBlindedRoute_ReferenceTestVector() Assert.Equal(new PublicKey(Convert.FromHexString("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")), blindedRouteEnd.BlindingKey); Assert.Equal(new PublicKey(Convert.FromHexString("03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a")), lastBlinding); + // Save a blinding override for later + var blindingOverride = blindedRouteEnd.BlindingKey; + // Bob also wants to use route blinding: var bobSessionKey = new PrivateKey(Convert.FromHexString("0202020202020202020202020202020202020202020202020202020202020202")); var bobCarolHops = new List<(PublicKey PublicKey, byte[] Payload)>() { @@ -86,6 +89,46 @@ public void RouteBlinding_CanCreatedBlindedRoute_ReferenceTestVector() var eveBlindedPrivKey = routeBlinding.DerivePrivateKey(eve, lastBlinding); var eveBlindedPubKey = lightningKeyDerivation.PublicKeyFromPrivateKey(eveBlindedPrivKey); Assert.Equal(eveBlindedPubKey, blindedRoute.BlindedNodeIds.LastOrDefault()); + + // Every node in the route is able to decrypt its payload and extract the blinding point for the next node: + { + // Bob (the introduction point) is able to decrypt its encrypted payload and obtain the next ephemeral public key + var (payload0, ephKey1) = routeBlinding.DecryptPayload(bob, blindedRoute.BlindingKey, blindedRoute.EncryptedPayloads[0]); + Assert.Equal(bobPayload, payload0); + Assert.Equal(new PublicKey(Convert.FromHexString("034e09f450a80c3d252b258aba0a61215bf60dda3b0dc78ffb0736ea1259dfd8a0")), ephKey1); + + // Carol can derive the private key used to unwrap the onion and decrypt its payload + var carolBlindedPrivKey = routeBlinding.DerivePrivateKey(carol, ephKey1); + var carolBlindedPubKey = lightningKeyDerivation.PublicKeyFromPrivateKey(carolBlindedPrivKey); + Assert.Equal(carolBlindedPubKey, blindedRoute.BlindedNodeIds[1]); + var (payload1, ephKey2) = routeBlinding.DecryptPayload(carol, ephKey1, blindedRoute.EncryptedPayloads[1]); + Assert.Equal(carolPayload, payload1); + Assert.Equal(new PublicKey(Convert.FromHexString("03af5ccc91851cb294e3a364ce63347709a08cdffa58c672e9a5c587ddd1bbca60")), ephKey2); + // NB: Carol finds a blinding override and will transmit that instead of ephKey2 to the next node. + // HACK: Really ugly way to check if the payload contains the blinding override + var payload1Str = Convert.ToHexString(payload1); + var blindingOverrideStr = Convert.ToHexString(blindingOverride.GetSpan().ToArray()); + Assert.True(payload1Str.Contains(blindingOverrideStr)); + + // Dave must be given the blinding override to derive the private key used to unwrap the onion and decrypt its payload + // TODO: This should probably be a specific exception + Assert.ThrowsAny(() => routeBlinding.DecryptPayload(dave, ephKey2, blindedRoute.EncryptedPayloads[2])); + var overridePrivKey = routeBlinding.DerivePrivateKey(dave, blindingOverride); + var overridePubKey = lightningKeyDerivation.PublicKeyFromPrivateKey(overridePrivKey); + Assert.Equal(overridePubKey, blindedRoute.BlindedNodeIds[2]); + var (payload2, ephKey3) = routeBlinding.DecryptPayload(dave, blindingOverride, blindedRoute.EncryptedPayloads[2]); + Assert.Equal(davePayload, payload2); + Assert.Equal(new PublicKey(Convert.FromHexString("03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a")), ephKey3); + Assert.Equal(lastBlinding, ephKey3); + + // Eve is able to derive the private key used to unwrap the onion and decrypt its payload + var eveFinalBlindedPrivKey = routeBlinding.DerivePrivateKey(eve, ephKey3); + var eveFinalBlindedPubKey = lightningKeyDerivation.PublicKeyFromPrivateKey(eveFinalBlindedPrivKey); + Assert.Equal(eveFinalBlindedPubKey, blindedRoute.BlindedNodeIds[3]); + var (payload4, ephKey5) = routeBlinding.DecryptPayload(eve, ephKey3, blindedRoute.EncryptedPayloads[3]); + Assert.Equal(evePayload, payload4); + Assert.Equal(new PublicKey(Convert.FromHexString("038fc6859a402b96ce4998c537c823d6ab94d1598fca02c788ba5dd79fbae83589")), ephKey5); + } } } diff --git a/src/Lyn.Protocol/Bolt4/RouteBlinding.cs b/src/Lyn.Protocol/Bolt4/RouteBlinding.cs index 4c53ce3..bcf0b07 100644 --- a/src/Lyn.Protocol/Bolt4/RouteBlinding.cs +++ b/src/Lyn.Protocol/Bolt4/RouteBlinding.cs @@ -23,9 +23,9 @@ public record BlindedRoute public PublicKey BlindingKey { get; init; } public IntroductionNode IntroductionNode { get; init; } - public IEnumerable SubsequentNodes { get; private set; } - public IEnumerable BlindedNodeIds { get; private set; } - public IEnumerable EncryptedPayloads { get; private set; } + public BlindedNode[] SubsequentNodes { get; private set; } + public PublicKey[] BlindedNodeIds { get; private set; } + public byte[][] EncryptedPayloads { get; private set; } public BlindedNode[] BlindedNodes { get; init; } @@ -42,9 +42,9 @@ public BlindedRoute(PublicKey introductionNodeId, PublicKey blindingKey, Blinded BlindedNodes = blindedNodes; IntroductionNode = new IntroductionNode(IntroductionNodeId, BlindedNodes.First().BlindedPublicKey, BlindingKey, BlindedNodes.First().EncryptedPayload); - SubsequentNodes = BlindedNodes.Skip(1); - BlindedNodeIds = BlindedNodes.Select(x => x.BlindedPublicKey); - EncryptedPayloads = BlindedNodes.Select(x => x.EncryptedPayload); + SubsequentNodes = BlindedNodes.Skip(1).ToArray(); + BlindedNodeIds = BlindedNodes.Select(x => x.BlindedPublicKey).ToArray(); + EncryptedPayloads = BlindedNodes.Select(x => x.EncryptedPayload).ToArray(); } } @@ -72,22 +72,26 @@ public BlindedRouteDetails Create(PrivateKey sessionKey, IEnumerable<(PublicKey var blindedHopsAndKeys = hops.Select((hop) => { var (publicKey, payload) = hop; - var blindingKey = _lightningKeyDerivation.PublicKeyFromPrivateKey(e); + + // Compute a shared secret, use it to derive ablinding key and rho for the ChaCha20Poly1305 cipher var sharedSecret = _sphinx.ComputeSharedSecret(publicKey, e); + var blindingKey = _lightningKeyDerivation.PublicKeyFromPrivateKey(e); var blindedPublicKey = _sphinx.BlindKey(publicKey, _sphinx.GenerateSphinxKey("blinded_node_id", sharedSecret)); var rho = _sphinx.GenerateSphinxKey("rho", sharedSecret); + var cipher = new ChaCha20Poly1305(rho.ToArray()); - // is this right? who knows! i hsould ask chatgpt later + // Next, allocate some buffers and encrypt the payload for this hop // note: i didn't use the ChaCha20Poly1305CipherFunction bc the nonce increments by 1 for each call, and this requires a nonce of 0(?) - var cipher = new ChaCha20Poly1305(rho.ToArray()); Span encryptedPayload = stackalloc byte[payload.Length + 16]; Span ciphertext = stackalloc byte[payload.Length]; - Span mac = stackalloc byte[16]; + Span mac = stackalloc byte[16]; + cipher.Encrypt(new byte[12], payload.AsSpan(), ciphertext, mac, new byte[0]); ciphertext.CopyTo(encryptedPayload); mac.CopyTo(encryptedPayload.Slice(payload.Length)); + // Before we move onto the next hop we need to derive the next hop's e value // todo: is blindingKey length a fixed 32 bytes? Span bytesToHash = stackalloc byte[blindingKey.GetSpan().Length + sharedSecret.Length]; blindingKey.GetSpan().CopyTo(bytesToHash); @@ -105,9 +109,7 @@ public BlindedRouteDetails Create(PrivateKey sessionKey, IEnumerable<(PublicKey } public PrivateKey DerivePrivateKey(PrivateKey privateKey, PublicKey blindingEphemeralKey) - { - // todo: i need some test coverage to verify the multiply logic here - // note: my guess? way off like in Create ¯\_(ツ)_/¯ + { var sharedSecret = _sphinx.ComputeSharedSecret(blindingEphemeralKey, privateKey); var generatedKey = new PrivateKey(_sphinx.GenerateSphinxKey("blinded_node_id", sharedSecret).ToArray()); return new PrivateKey(_ellipticCureActions.MultiplyWithPrivateKey(generatedKey, privateKey).ToArray()); @@ -115,12 +117,23 @@ public PrivateKey DerivePrivateKey(PrivateKey privateKey, PublicKey blindingEphe public (byte[], PublicKey) DecryptPayload(PrivateKey privateKey, PublicKey blindingEphemeralKey, byte[] encryptedPayload) { + // Derive rho from our shared secret and instantiate a ChaCha20Poly1305 cipher var sharedSecret = _sphinx.ComputeSharedSecret(blindingEphemeralKey, privateKey); var rho = _sphinx.GenerateSphinxKey("rho", sharedSecret); var cipher = new ChaCha20Poly1305(rho.ToArray()); + + // Create some buffers to handle the decryption Span decryptedPayload = stackalloc byte[encryptedPayload.Length - 16]; + Span ciphertext = stackalloc byte[encryptedPayload.Length - 16]; Span mac = stackalloc byte[16]; - cipher.Decrypt(new byte[12], encryptedPayload.AsSpan(), decryptedPayload, mac, new byte[0]); + + // Split the encrypted payload into the ciphertext and the MAC for decryption + encryptedPayload.AsSpan().Slice(0, encryptedPayload.Length - 16).CopyTo(ciphertext); + encryptedPayload.AsSpan().Slice(encryptedPayload.Length - 16).CopyTo(mac); + + // Decrypt the payload + // Note: Should figure out what the right error handling is here + cipher.Decrypt(new byte[12], ciphertext, mac, decryptedPayload, new byte[0]); var nextBlindingEphemeralKey = _sphinx.BlindKey(blindingEphemeralKey, _sphinx.ComputeBlindingFactor(blindingEphemeralKey, sharedSecret)); return (decryptedPayload.ToArray(), nextBlindingEphemeralKey); }