Skip to content

Commit

Permalink
Initial xkeys support
Browse files Browse the repository at this point in the history
  • Loading branch information
mtmk committed Jan 17, 2025
1 parent ce381dc commit 65d2dfa
Show file tree
Hide file tree
Showing 7 changed files with 1,152 additions and 3 deletions.
5 changes: 4 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf

[src/NATS.Client.Core/NaCl/**.cs]
[NATS.NKeys/NaCl/**.cs]
generated_code = true

[NATS.NKeys/X25519/**.cs]
generated_code = true

[*.cs]
Expand Down
19 changes: 19 additions & 0 deletions NATS.NKeys.Tests/NKeysTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,25 @@ public static IEnumerable<object[]> PrefixData
}
}

[Fact]
public void XKey_from_seed()
{
var kp = KeyPair.FromSeed("SXALBV3GJABONHXVSTRWL2NXYVUF5LY66CGVF4INX2XBMMVYT2KCZXRXWA".ToCharArray());
Assert.Equal("XBKXUQXILUXDDHTWEDECINN24IUYFQAYG737MB5PMEAVMUMHCIWRA3UD", kp.GetPublicKey());
var exception = Assert.Throws<NKeysException>(() => kp.Sign(default, default));
Assert.Equal("Invalid curve key operation", exception.Message);
}

[Fact]
public void XKey_create()
{
var kp = KeyPair.CreatePair(PrefixByte.Curve, new FixedRng());
Assert.Equal("SXAD4F52S2XAJTJ3TGDJ4VXQVW7TU35XJUSVKF25ZRXIWCIUK6NLANRHVY", kp.GetSeed());
Assert.Equal("XDS6PE7IMMUA7XXUZLWQIPUCRS3J2IGXVMI3ZEOWX7LY4IMBPH2XECZM", kp.GetPublicKey());
var exception = Assert.Throws<NKeysException>(() => kp.Sign(default, default));
Assert.Equal("Invalid curve key operation", exception.Message);
}

[MemberData(nameof(PrefixData))]
[Theory]
public void Create_key_pair(PrefixByte prefix, char initial)
Expand Down
31 changes: 29 additions & 2 deletions NATS.NKeys/KeyPair.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Security.Cryptography;
using NATS.NKeys.Internal;
using NATS.NKeys.NaCl;
using X25519;

namespace NATS.NKeys;

Expand Down Expand Up @@ -63,7 +64,17 @@ public static KeyPair FromSeed(ReadOnlySpan<char> encodedSeed)
var sk = new ArraySegment<byte>(new byte[64]);

seedSpan.CopyTo(seed.Array);
Ed25519.KeyPairFromSeed(pk, sk, seed);

if (type == PrefixByte.Curve)
{
var publicKey = Curve25519.ScalarMultiplication(seed.Array, Curve25519.Basepoint);
publicKey.AsSpan().CopyTo(pk.Array);
}
else
{
Ed25519.KeyPairFromSeed(pk, sk, seed);
}

return new KeyPair(type, seed.Array!, sk.Array!, pk.Array!);
}

Expand Down Expand Up @@ -93,7 +104,15 @@ public static KeyPair CreatePair(PrefixByte prefix, RandomNumberGenerator rng)
var seed = new byte[32];
rng.GetBytes(seed);
var sk = Ed25519.ExpandedPrivateKeyFromSeed(seed);
var pk = Ed25519.PublicKeyFromSeed(seed);
byte[] pk;
if (prefix == PrefixByte.Curve)
{
pk = Curve25519.ScalarMultiplication(seed, Curve25519.Basepoint);
}
else
{
pk = Ed25519.PublicKeyFromSeed(seed);
}

return new KeyPair(prefix, seed, sk, pk);
}
Expand Down Expand Up @@ -124,6 +143,9 @@ public string GetSeed()
/// <exception cref="NKeysException">Thrown if the private key is not valid or there is an error during the signing process.</exception>
public void Sign(ReadOnlyMemory<byte> message, Memory<byte> signature)
{
if (_type == PrefixByte.Curve)
ThrowInvalidCurveKeyOperationException();

if (_sk.Length == 0)
ThrowNoSecretKeyException();

Expand Down Expand Up @@ -272,6 +294,8 @@ private static void DecodeSeed(ReadOnlySpan<byte> raw, out ReadOnlySpan<byte> bu
return PrefixByte.Account;
case NKeysConstants.PrefixByteUser:
return PrefixByte.User;
case NKeysConstants.PrefixByteCurve:
return PrefixByte.Curve;
}

return null;
Expand Down Expand Up @@ -308,4 +332,7 @@ or NKeysConstants.PrefixByteUser

[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowNoSecretKeyException() => throw new NKeysException("No secret key");

[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowInvalidCurveKeyOperationException() => throw new NKeysException("Invalid curve key operation");
}
6 changes: 6 additions & 0 deletions NATS.NKeys/PrefixByte.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,10 @@ public enum PrefixByte : byte
/// </summary>
/// <remarks>When encoded as base32, it encodes to 'U...'</remarks>
User = NKeysConstants.PrefixByteUser,

/// <summary>
/// The version byte used for encoded CurveKeys (X25519).
/// </summary>
/// <remarks>When encoded as base32, it encodes to 'X...'</remarks>
Curve = NKeysConstants.PrefixByteCurve,
}
1 change: 1 addition & 0 deletions NATS.NKeys/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ NATS.NKeys.NKeysException.NKeysException(string! message) -> void
NATS.NKeys.PrefixByte
NATS.NKeys.PrefixByte.Account = 0 -> NATS.NKeys.PrefixByte
NATS.NKeys.PrefixByte.Cluster = 16 -> NATS.NKeys.PrefixByte
NATS.NKeys.PrefixByte.Curve = 184 -> NATS.NKeys.PrefixByte
NATS.NKeys.PrefixByte.Operator = 112 -> NATS.NKeys.PrefixByte
NATS.NKeys.PrefixByte.Server = 104 -> NATS.NKeys.PrefixByte
NATS.NKeys.PrefixByte.User = 160 -> NATS.NKeys.PrefixByte
Expand Down
Loading

0 comments on commit 65d2dfa

Please sign in to comment.