Closed NN-Binary closed 5 years ago
I generally recommend storing the seed
(value before hashing and bit clearing) which was used to generate private keys, not the 32-byte part of the key itself. Then you can use nacl.sign.keypair.fromSeed
to get the key pair back.
However, if you already have it stored, the following addition (modified crypto_sign_keypair
) to nacl.js
or nacl-fast.js
should work:
// somewhere below crypto_sign_keypair:
function crypto_sign_keypair_from_sk(pk, sk) {
var d = new Uint8Array(64);
var p = [gf(), gf(), gf(), gf()];
var i;
for (i = 0; i < 32; i++) d[i] = sk[i];
d[0] &= 248;
d[31] &= 127;
d[31] |= 64;
scalarbase(p, d);
pack(pk, p);
return 0;
}
// and somewhere below nacl.sign.keyPair.fromSecretKey
nacl.sign.keyPair.fromPartialSecretKey = function(secretKey) {
checkArrayTypes(secretKey);
if (secretKey.length !== 32)
throw new Error('bad secret key size');
var pk = new Uint8Array(crypto_sign_PUBLICKEYBYTES);
crypto_sign_keypair_from_sk(pk, secretKey);
return {publicKey: pk, secretKey: new Uint8Array(secretKey)};
};
(Haven't tested this code)
I'm not sure if I'd want to add this function into the library (it adds quite a big modification to TweetNaCl internals, and this library is a port of TweetNaCl), but please leave the issue open so that we can have a vote for it.
Thank you so much!
I truly believe you should have the 2 options because half the libraries are 32 bytes while the remaining ones are 64 bytes. And i'm convinced lot of people can't switch on this library because of this specific reason.
Your example wasn't working, I fixed it:
function crypto_sign_keypair_from_sk(pk, sk) {
var d = new Uint8Array(64);
var p = [gf(), gf(), gf(), gf()];
var i;
for (i = 0; i < 32; i++) d[i] = sk[i];
crypto_hash(d, sk, 32);
d[0] &= 248;
d[31] &= 127;
d[31] |= 64;
scalarbase(p, d);
pack(pk, p);
return 0;
}
Ehm, this is what nacl.sign.keyPair.fromSeed
does (see the internal crypto_sign_keypair
implementation), so what you want then is already in TweetNaCl-js :) This is called "seed" rather than private key, because the actual private key is the hashed (and bit-cleared) version of the seed.
Even a reason more I think to let people choose between 32 or 64, it means that your output (as a private key) isn't compatible with most of the libs we find online if it extract just the seed.
I'm not sure I'm following.
What you consider a 32-byte private key is in fact a seed
in TweetNaCl-js terminology. The actual private key as per original Ed25519 and NaCl/TweetNaCl implementation is a concatenation of seed and public key. RFC 8032, which has 32-byte keys was released later than our releases. nacl.sign.keyPair.fromSeed
was specifically introduce to accommodate such implementations. Go golang.org/x/crypto library also uses the same terminology.
var seed = window.crypto.getRandomValues(new Uint8Array(32))
console.log(seed)
var keys = nacl.sign.keyPair.fromSeed(seed)
console.log(keys.secretKey)
Output:
Uint8Array(32) [249, 226, 98, 92, 242, 5, 133, 79, 124, 20, 90, 100, 162, 149, 66, 69, 218, 126, 87, 148, 235, 194, 171, 4, 83, 203, 63, 107, 45, 168, 228, 48]
Uint8Array(64) [249, 226, 98, 92, 242, 5, 133, 79, 124, 20, 90, 100, 162, 149, 66, 69, 218, 126, 87, 148, 235, 194, 171, 4, 83, 203, 63, 107, 45, 168, 228, 48, 229, 47, 124, 229, 225, 167, 64, 216, 82, 75, 123, 136, 12, 93, 121, 65, 8, 76, 34, 251, 107, 27, 197, 81, 205, 42, 61, 13, 229, 165, 4, 34]
What's not compatible with other libs? If they use 32-byte seed as a representation of private key, it can be used with TweetNaCl-js by calling nacl.sign.keyPair.fromSeed()
. If you want to use keys generated by nacl.sign.keyPair()
in such libs, just use the first 32 bytes of the generated secretKey
.
I think I need update readme to mention this, but otherwise, there's no incompatibility.
nacl.sign.keyPair() gives out the hashed version, not the seed, am I right?
No, it gives the original random bytes.
const key1 = nacl.sign.keyPair()
const key2 = nacl.sign.keyPair.fromSeed(key1.secretKey.subarray(0, 32))
console.log(key1.secretKey);
console.log(key2.secretKey):
Output:
Uint8Array(64) [101, 5, 55, 23, 176, 97, 43, 130, 254, 211, 6, 165, 126, 114, 213, 207, 236, 250, 156, 106, 141, 110, 105, 162, 7, 102, 73, 142, 109, 102, 21, 137, 252, 6, 76, 228, 196, 168, 75, 159, 50, 130, 94, 169, 89, 74, 38, 181, 233, 211, 120, 73, 25, 243, 172, 93, 146, 74, 117, 144, 96, 244, 89, 185]
Uint8Array(64) [101, 5, 55, 23, 176, 97, 43, 130, 254, 211, 6, 165, 126, 114, 213, 207, 236, 250, 156, 106, 141, 110, 105, 162, 7, 102, 73, 142, 109, 102, 21, 137, 252, 6, 76, 228, 196, 168, 75, 159, 50, 130, 94, 169, 89, 74, 38, 181, 233, 211, 120, 73, 25, 243, 172, 93, 146, 74, 117, 144, 96, 244, 89, 185]
See implementation here: https://github.com/dchest/tweetnacl-js/blob/master/nacl.js#L704-L720
Thanks for the clarification. Is it a normal thing as per ED25519 to give out the seed and not the final private key?
Yes, this was in the original implementation, and also the seed became known as the private key in RFC.
Hi,
We'd really want to switch on Tweetnacl but our users only saved their private key as 32 bytes (without the pubkey included).
Anyway you can please provide a small function that gives the publickey from the privatekey (for signing later on with ED25519).
Thanks for this amazing work