CodesInChaos / Chaos.NaCl

Chaos.NaCl cryptography library
Other
131 stars 54 forks source link

Derive public key from a known good private key #14

Open danielcrenna opened 6 years ago

danielcrenna commented 6 years ago

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 commented 6 years ago

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

jedisct1 commented 6 years ago

Also: https://github.com/bfriesen/LibsodiumDotNetStandardSpike /cc @bfriesen

danielcrenna commented 6 years ago

Right, there's a lot of WIP but nothing concrete/importable for .NET Standard 2.0; I assume the challenge is transparent packaging and resolution of native libraries at runtime? If there's some specific set of tasks off of master that I can help with, let me know.

On Sep 24, 2017, at 6:29 PM, Frank Denis notifications@github.com wrote:

Also: https://github.com/bfriesen/LibsodiumDotNetStandardSpike /cc @bfriesen

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or mute the thread.

danielcrenna commented 6 years ago

@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:

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 commented 6 years ago

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).