Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Derive public key from a known good private key #14

Open
danielcrenna opened this issue Sep 24, 2017 · 5 comments
Open

Derive public key from a known good private key #14

danielcrenna opened this issue Sep 24, 2017 · 5 comments

Comments

@danielcrenna
Copy link

Hello,

Since libsodium-net does not currently work on .NET Standard (https://github.com/adamcaudill/libsodium-net/pull/155), I'm using your library for Ed25519.

However, I can't use it to recreate a public key given a known good private key, for the reasons listed here: https://bitcoin.stackexchange.com/a/42456; it may be that this implementation is just missing the method crypto_sign_ed25519_sk_to_seed, is that correct?

@jedisct1
Copy link

It doesn't work on .NET standard? /cc @BurningEnlightenment

@jedisct1
Copy link

@danielcrenna
Copy link
Author

danielcrenna commented Sep 25, 2017 via email

@danielcrenna
Copy link
Author

@jedisct1 Thanks for the pointers.

I was able to get @BurningEnlightenment's fork building with a few caveats. After that work, as it turns out, I didn't actually need crypto_sign_ed25519_sk_to_seed, since it seems to function as a simple array copy, and using the private key directly would produce the same result as Chaos.NaCl which is equivalent to crypto_sign_seed_keypair.

Caveats:

  • Chaos.NaCl produces a 64 byte extended private key when creating an Ed25519 keypair, but libsodium uses 32 bytes (unclear the impacts of this)
  • Chaos.NaCl supports ArraySegment for saving byte array allocations but libsodium-net does not
  • This fork currently supports multi-targeted net461/netstandard1.3 rather than netstandard2.0; attempting to target netstandard2.0 results in compiler warnings:
    "Package 'libsodium 1.0.12-preview-01' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETStandard,Version=v2.0'. This package may not be fully compatible with your project"
  • There doesn't appear to be a "WipeArray" feature in libsodium-net, it might be helpful to add Chaos.NaCl's simple version in Utilities
  • I understand the "black box" design philosophy of libsodium-net, though making Sodium access internal means I had to extend with a proxy class for Ed25519 to access the methods I was looking for.

So, it works, but it's far from something I could probably get away with shipping in production. I also don't know how libsodium-net will protect, for example, an attacker replacing libsodium itself dynamically with their own interop assemblies.

If there is a timeline for the .NET Standard stream reaching NuGet let me know, and if there are any work items I can pitch in to help on if there is something back this fork, since it appears mostly ready to go.

FYI, here is my hybrid Chaos.NaCl/libsodium-net class:

using System;
using Sodium.Interop;

namespace Sodium
{
  public static class Ed25519
  {
    public static readonly int PublicKeySizeInBytes = 32;
    public static readonly int SignatureSizeInBytes = 64;
    public static readonly int ExpandedPrivateKeySizeInBytes = 32 * 2;
    public static readonly int PrivateKeySeedSizeInBytes = 32; // 64 in Chaos.NaCl
    public static readonly int SharedKeySizeInBytes = 32;

    public static bool Verify(byte[] signature, byte[] message, byte[] publicKey)
    {
      if (signature == null)
        throw new ArgumentNullException(nameof(signature));
      if (message == null)
        throw new ArgumentNullException(nameof(message));
      if (publicKey == null)
        throw new ArgumentNullException(nameof(publicKey));
      if (signature.Length != SignatureSizeInBytes)
        throw new ArgumentException(string.Format("Signature size must be {0}", SignatureSizeInBytes), nameof(signature.Length));
      if (publicKey.Length != PublicKeySizeInBytes)
        throw new ArgumentException(string.Format("Public key size must be {0}", PublicKeySizeInBytes), nameof(publicKey.Length));

      long bufferLength = SignatureSizeInBytes;
      return SodiumLibrary.crypto_sign_open(signature, ref bufferLength, message, message.Length, publicKey) != 0;
    }

    public static byte[] Sign(byte[] message, byte[] expandedPrivateKey)
    {
      var signature = new byte[SignatureSizeInBytes];
      long bufferLength = SignatureSizeInBytes;
      SodiumLibrary.crypto_sign(signature, ref bufferLength, message, message.Length, expandedPrivateKey);
      return signature;
    }

    public static byte[] PublicKeyFromSeed(byte[] privateKeySeed)
    {
      KeyPairFromSeed(out var publicKey, out var _, privateKeySeed);
      return publicKey;
    }

    public static byte[] ExpandedPrivateKeyFromSeed(byte[] privateKeySeed)
    {
      KeyPairFromSeed(out var _, out var privateKey, privateKeySeed);
      return privateKey;
    }

    public static void KeyPairFromSeed(out byte[] publicKey, out byte[] expandedPrivateKey, byte[] privateKeySeed)
    {
      if (privateKeySeed == null)
        throw new ArgumentNullException(nameof(privateKeySeed));
      if (privateKeySeed.Length != PrivateKeySeedSizeInBytes)
        throw new ArgumentException(nameof(privateKeySeed));

      var pk = new byte[32]; // alloc
      var sk = new byte[32]; // alloc

      SodiumLibrary.crypto_sign_seed_keypair(pk, sk, privateKeySeed);

      publicKey = pk;
      expandedPrivateKey = sk;
    }

    public static void PublicKeyFromPrivateKey(out byte[] publicKey, byte[] privateKey)
    {
      var pk = new byte[32]; // alloc
      var sk = new byte[32]; // alloc
      SodiumLibrary.crypto_sign_seed_keypair(pk, sk, privateKey);
      publicKey = pk;
    }
  }
}

@jedisct1
Copy link

libsodium also requires 64 bytes for the secret key (sk in your example - it includes a copy of the public key in addition to the actual secret key).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants