Skip to content

Commit

Permalink
Add quic (NethermindEth#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
flcl42 authored Sep 29, 2023
1 parent 10c3a15 commit 4161470
Show file tree
Hide file tree
Showing 33 changed files with 662 additions and 347 deletions.
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,36 +31,41 @@ dotnet test

## Roadmap

From the beginning, the target is to provide a performant well-tested implementation that works on multiple platforms, with high throughput and low memory profile.
See the [milestones](https://github.com/NethermindEth/dotnet-libp2p/milestones?direction=asc&sort=due_date&state=open).
🚧 The library is not stable and under heavy development. Consider the [beta](https://github.com/NethermindEth/dotnet-libp2p/milestone/5) milestone as a reflection of readiness for production 🚧

The target is to provide a performant well-tested implementation of a wide range of protocols that works on multiple platforms, with high throughput and low memory profile.


| Protocol | Version | Status |
|--------------------|--------------------|-----------------|
| **Transports**
| TCP | tcp ||
| QUIC | quic | 🚧 |
| | quic-v1 | 🚧 |
| **Protocols**
| QUIC | quic-v1 ||
| multistream-select | /multistream/1.0.0 ||
| plaintext | /plaintext/2.0.0 ||
| noise | /noise ||
| tls | /tls/1.0.0 | ⬜ help wanted |
| WebTransport | | ⬜ help wanted |
| yamux | /yamux/1.0.0 ||
| Circuit Relay | /libp2p/circuit/relay/0.2.0/* | ⬜ help wanted |
| hole punching | | ⬜ help wanted |
| **Application layer**
| Identify | /ipfs/id/1.0.0 ||
| ping | /ipfs/ping/1.0.0 ||
| pubsub | /floodsub/1.0.0 ||
| | /meshsub/1.0.0 ||
| | /meshsub/1.1.0 | 🚧 |
| | /meshsub/1.2.0 ||
| Circuit Relay | /libp2p/circuit/relay/0.2.0/* | ⬜ help wanted |
| **Discovery**
| mDns | basic ||
| | DNS-SD | 🚧 |
| [discv5](https://github.com/Pier-Two/Lantern.Discv5) | 5.1 | help wanted |
| [discv5](https://github.com/Pier-Two/Lantern.Discv5) | 5.1 | 🚧 help wanted |

⬜ - not yet implemented<br>
🚧 - work in progress<br>
✅ - basic support implemented

No plans for: mplex, quic(draft-29)

## License

dotnet-libp2p is an open-source software licensed under the [MIT](https://github.com/nethermindeth/dotnet-libp2p/blob/main/LICENSE).
2 changes: 1 addition & 1 deletion docs/development/transport-layer.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Each protocol should implement `IProtocol` interface, which enforces the `Listen
```csharp
namespace Nethermind.Libp2p.Core;

public abstract class MyCustomProtocol
public class MyCustomProtocol : IProtocol
{
public Task DialAsync(IChannel downChannel, IChannelFactory upChannelFactory, IPeerContext context)
{
Expand Down
2 changes: 1 addition & 1 deletion src/libp2p/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@
<PackageVersion Include="SimpleBase" Version="4.0.0" />
<PackageVersion Include="System.Runtime.Caching" Version="7.0.0" />
</ItemGroup>
</Project>
</Project>
23 changes: 21 additions & 2 deletions src/libp2p/Libp2p.Core.Tests/IdentityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@

using Google.Protobuf;
using Nethermind.Libp2p.Core.Dto;

namespace Nethermind.Libp2p.Core.Tests;
public class IdentityTests
{
[TestCaseSource(nameof(ExpectedKeysEncoded))]
[TestCaseSource(nameof(KeysEncoded))]
public void Test_KeyEncoding(byte[] privateKey, byte[] publicKey)
{
Identity identity = new(PrivateKey.Parser.ParseFrom(privateKey));
Assert.That(identity.PublicKey.ToByteArray(), Is.EquivalentTo(publicKey));
}

public static IEnumerable<TestCaseData> ExpectedKeysEncoded()
public static IEnumerable<TestCaseData> KeysEncoded()
{
yield return new TestCaseData(
Convert.FromHexString("08031279307702010104203E5B1FE9712E6C314942A750BD67485DE3C1EFE85B1BFB520AE8F9AE3DFA4A4CA00A06082A8648CE3D030107A14403420004DE3D300FA36AE0E8F5D530899D83ABAB44ABF3161F162A4BC901D8E6ECDA020E8B6D5F8DA30525E71D6851510C098E5C47C646A597FB4DCEC034E9F77C409E62"),
Expand All @@ -36,4 +37,22 @@ public static IEnumerable<TestCaseData> ExpectedKeysEncoded()
)
{ TestName = "Rsa" };
}

[TestCaseSource(nameof(KeyTypes))]
public void Test_KeyGeneration(KeyType keyType)
=> _ = new Identity(keyType: keyType);

public static IEnumerable<TestCaseData> KeyTypes()
=> Enum.GetValues<KeyType>().Select(kt => new TestCaseData(kt) { TestName = kt.ToString() });

[TestCaseSource(nameof(KeyTypes))]
public void Test_Signing(KeyType keyType)
{
Identity id = new(keyType: keyType);
byte[] message = Enumerable.Range(0, 100).Select(i => (byte)i).ToArray();

byte[] signature = id.Sign(message);

Assert.That(id.VerifySignature(message, signature), Is.True);
}
}
2 changes: 1 addition & 1 deletion src/libp2p/Libp2p.Core.TestsBase/TestPeers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ public static Multiaddr Multiaddr(int i) => testPeerAddrs.GetOrAdd(i, i =>

public static PeerId PeerId(Multiaddr addr) => new(addr.At(Enums.Multiaddr.P2p)!);

public static Identity Identity(Multiaddr addr) => new Identity(Core.PeerId.ExtractPublicKey(PeerId(addr).Bytes));
public static Identity Identity(Multiaddr addr) => new(Core.PeerId.ExtractPublicKey(PeerId(addr).Bytes));

}
186 changes: 167 additions & 19 deletions src/libp2p/Libp2p.Core/Identity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
using System.Security.Cryptography;
using System.Buffers;
using Org.BouncyCastle.Crypto.Parameters;
using ECPoint = Org.BouncyCastle.Math.EC.ECPoint;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.EC;
using Org.BouncyCastle.Crypto.Generators;

namespace Nethermind.Libp2p.Core;

Expand All @@ -18,28 +22,79 @@ namespace Nethermind.Libp2p.Core;
/// </summary>
public class Identity
{
private const KeyType DefaultKeyType = KeyType.Ed25519;

public PublicKey PublicKey { get; }
public PrivateKey? PrivateKey { get; }

public Identity(byte[]? privateKey = default, KeyType keyType = KeyType.Ed25519)
: this(privateKey is null ? null : new PrivateKey { Data = ByteString.CopyFrom(privateKey), Type = keyType })
public Identity(byte[]? privateKey = default, KeyType keyType = DefaultKeyType)
{
if (privateKey is null)
{
(PrivateKey, PublicKey) = GeneratePrivateKeyPair(keyType);
}
else
{
PrivateKey = new PrivateKey { Data = ByteString.CopyFrom(privateKey), Type = keyType };
PublicKey = GetPublicKey(PrivateKey);
}
}

public Identity(PrivateKey? privateKey)
public Identity(PrivateKey privateKey)
{
if (privateKey is null)
PrivateKey = privateKey;
PublicKey = GetPublicKey(PrivateKey);
}

private (PrivateKey, PublicKey) GeneratePrivateKeyPair(KeyType type)
{
ByteString privateKeyData;
ByteString? publicKeyData = null;
switch (type)
{
byte[] rented = ArrayPool<byte>.Shared.Rent(Ed25519.SecretKeySize);
Span<byte> privateKeyBytesSpan = rented.AsSpan(0, Ed25519.SecretKeySize);
SecureRandom rnd = new();
Ed25519.GeneratePrivateKey(rnd, privateKeyBytesSpan);
ArrayPool<byte>.Shared.Return(rented, true);
privateKey = new PrivateKey { Data = ByteString.CopyFrom(privateKeyBytesSpan), Type = KeyType.Ed25519 };
case KeyType.Ed25519:
{
Span<byte> privateKeyBytes = stackalloc byte[Ed25519.SecretKeySize];
SecureRandom rnd = new();
Ed25519.GeneratePrivateKey(rnd, privateKeyBytes);
privateKeyData = ByteString.CopyFrom(privateKeyBytes);
}
break;
case KeyType.Rsa:
{
using RSA rsa = RSA.Create(1024);
privateKeyData = ByteString.CopyFrom(rsa.ExportRSAPrivateKey());
}
break;
case KeyType.Secp256K1:
{
X9ECParameters curve = ECNamedCurveTable.GetByName("secp256k1");
ECDomainParameters domainParams = new(curve);

SecureRandom secureRandom = new();
ECKeyGenerationParameters keyParams = new(domainParams, secureRandom);

ECKeyPairGenerator generator = new("ECDSA");
generator.Init(keyParams);
AsymmetricCipherKeyPair keyPair = generator.GenerateKeyPair();
Span<byte> privateKeySpan = stackalloc byte[32];
((ECPrivateKeyParameters)keyPair.Private).D.ToByteArrayUnsigned(privateKeySpan);
privateKeyData = ByteString.CopyFrom(privateKeySpan);
publicKeyData = ByteString.CopyFrom(((ECPublicKeyParameters)keyPair.Public).Q.GetEncoded(true));
}
break;
case KeyType.Ecdsa:
{
using ECDsa rsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
privateKeyData = ByteString.CopyFrom(rsa.ExportECPrivateKey());
}
break;
default:
throw new NotImplementedException($"{type} generation is not supported");
}
PrivateKey = privateKey;
PublicKey = GetPublicKey(privateKey);

PrivateKey privateKey = new() { Type = type, Data = privateKeyData };
return (privateKey, publicKeyData is not null ? new PublicKey { Type = type, Data = publicKeyData } : GetPublicKey(privateKey));
}

public Identity(PublicKey publicKey)
Expand All @@ -64,26 +119,25 @@ private static PublicKey GetPublicKey(PrivateKey privateKey)

case KeyType.Rsa:
{
RSA rsa = RSA.Create();
using RSA rsa = RSA.Create();
rsa.ImportRSAPrivateKey(privateKey.Data.Span, out int bytesRead);
publicKeyData = ByteString.CopyFrom(rsa.ExportSubjectPublicKeyInfo());
}
break;

case KeyType.Secp256K1:
{
X9ECParameters curve = ECNamedCurveTable.GetByName("secp256k1");
Org.BouncyCastle.Math.EC.ECPoint pointQ
= curve.G.Multiply(new BigInteger(1, privateKey.Data.Span));
X9ECParameters curve = CustomNamedCurves.GetByName("secp256k1");
ECPoint pointQ = curve.G.Multiply(new BigInteger(privateKey.Data.ToArray()));
publicKeyData = ByteString.CopyFrom(pointQ.GetEncoded(true));
}
break;

case KeyType.Ecdsa:
{
ECDsa rsa = ECDsa.Create();
rsa.ImportECPrivateKey(privateKey.Data.Span, out int _);
publicKeyData = ByteString.CopyFrom(rsa.ExportSubjectPublicKeyInfo());
using ECDsa ecdsa = ECDsa.Create();
ecdsa.ImportECPrivateKey(privateKey.Data.Span, out int _);
publicKeyData = ByteString.CopyFrom(ecdsa.ExportSubjectPublicKeyInfo());
}
break;
default:
Expand All @@ -93,5 +147,99 @@ Org.BouncyCastle.Math.EC.ECPoint pointQ
return new() { Type = privateKey.Type, Data = publicKeyData };
}

public bool VerifySignature(byte[] message, byte[] signature)
{
if (PublicKey is null)
{
throw new ArgumentNullException(nameof(PublicKey));
}

switch (PublicKey.Type)
{
case KeyType.Ed25519:
{
return Ed25519.Verify(signature, 0, PublicKey.Data.ToByteArray(), 0, message, 0, message.Length);
}
case KeyType.Rsa:
{
using RSA rsa = RSA.Create();
rsa.ImportSubjectPublicKeyInfo(PublicKey.Data.Span, out _);
return rsa.VerifyData(message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
case KeyType.Secp256K1:
{
X9ECParameters curve = CustomNamedCurves.GetByName("secp256k1");
ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");

ECPublicKeyParameters publicKeyParamters = new(
"ECDSA",
curve.Curve.DecodePoint(PublicKey.Data.ToArray()),
new ECDomainParameters(curve)
);

signer.Init(false, publicKeyParamters);
signer.BlockUpdate(message, 0, message.Length);
return signer.VerifySignature(signature);
}
case KeyType.Ecdsa:
{
using ECDsa ecdsa = ECDsa.Create();
ecdsa.ImportSubjectPublicKeyInfo(PublicKey.Data.Span, out _);
return ecdsa.VerifyData(message, signature, HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence);
}
default:
throw new NotImplementedException($"{PublicKey.Type} is not supported");
}
}

public byte[] Sign(byte[] message)
{
if (PrivateKey is null)
{
throw new ArgumentException(nameof(PrivateKey));
}

switch (PublicKey.Type)
{
case KeyType.Ed25519:
{
byte[] sig = new byte[Ed25519.SignatureSize];
Ed25519.Sign(PrivateKey.Data.ToByteArray(), 0, PublicKey.Data.ToByteArray(), 0,
message, 0, message.Length, sig, 0);
return sig;
}
case KeyType.Ecdsa:
{
ECDsa e = ECDsa.Create();
e.ImportECPrivateKey(PrivateKey.Data.Span, out _);
return e.SignData(message, HashAlgorithmName.SHA256,
DSASignatureFormat.Rfc3279DerSequence);
}
case KeyType.Rsa:
{
using RSA rsa = RSA.Create();
rsa.ImportRSAPrivateKey(PrivateKey.Data.Span, out _);
return rsa.SignData(message, 0, message.Length, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
case KeyType.Secp256K1:
{
X9ECParameters curve = CustomNamedCurves.GetByName("secp256k1");
ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");

ECPrivateKeyParameters privateKeyParams = new(
"ECDSA",
new BigInteger(1, PrivateKey.Data.ToArray()),
new ECDomainParameters(curve)
);

signer.Init(true, privateKeyParams);
signer.BlockUpdate(message, 0, message.Length);
return signer.GenerateSignature();
}
default:
throw new NotImplementedException($"{PublicKey.Type} is not supported");
}
}

public PeerId PeerId => new(PublicKey);
}
Loading

0 comments on commit 4161470

Please sign in to comment.