Based on this old answer, independently of the programming language (I'm using node.js with BigInt(), though), to generate the private spend key you should:
- Start with a seed (the same as the private spend key(?)):
const hexSeed = '198584347013dd91832be3d82529437db7cc8e1850e559cdd3872b29ca819601';
const decSeed = BigInt('0x'+hexSeed); //convert the hex seed to a dec BigInt: 11543724318435336731650421271699387718989312826353117660615550988399570687489
- Execute the Keccak-256 hash of the buffer of the hex seed:
const hexSeedKeccak = keccak256( Buffer.from( hexSeed, 'hex' ) ).toString('hex'); //889da12a88d36bce0966ab1a79125779dd1f2fc6f1145de131fd52a5b468796d, ok
const decSeedKeccak = BigInt('0x'+hexSeedKeccak); //convert the hex seed keccak to a dec BigInt: 61793054724383473727619989110790419561637989469961590701362921716625506335085
- Execute the sc_reduce32 function on the buffer (based on this and this sc_reduce32 is just
decSeedKeccak % l
wherel = 2**252 + 27742317777372353535851937790883648493
or simplyl = 7237005577332262213973186563042994240857116359379907606001950938285454250989
):
const l = 7237005577332262213973186563042994240857116359379907606001950938285454250989n;
const decPrivateViewKey = decSeedKeccak % l; //3897010105725376015834496606446465634781058594922329853347314210341872327173
const hexPrivateViewKey = decPrivateViewKey.toString(16) //convert the dec BigInt to hex: 89da12a88d36bce0966ab1a79125779362760d1d957772e716a39d2ccb9da05
(Making it in little endian bytes results in 05DAB9CCD2396A712E7757D9D1602736795712791AAB6609CE6BD3882AA19D08
)
But if you check the private view key calculated by skaht in the first link it doesn't match, while in the example of the 4th link it works fine using the method of using BigInt() with the % (mod) sign. When using the Cryptonote address tester sc_reduce32
function they both match to the example of the original answers. Getting deeper the function looks like:
function sc_reduce32(hex) {
var input = hextobin(hex);
if (input.length !== 32) {
throw "Invalid input length";
}
var mem = Module._malloc(32);
Module.HEAPU8.set(input, mem);
Module.ccall('sc_reduce32', 'void', ['number'], [mem]);
var output = Module.HEAPU8.subarray(mem, mem + 32);
Module._free(mem);
return bintohex(output);
}
I understood (partly) how it works, but without understanding why it didn't simply use a modulo function:
- Get the binary version of the
hexSeedKeccak
- Check if the binary has 32 digits, if not throw an error
- Call
var mem = Module._malloc(32)
(may be this):var ptr = Runtime.dynamicAlloc(bytes+8)
(still can't get what it does)- Return the AND bitwise operation of
(5276816+8) & 4294967288
- Set the input in a Unit8Array with an offset of the memory of
mem
(probably related to this) - Run
Module.ccall
(still can't get what it does) - Gets the subarray with the 72 bits
- Frees the memory (?)
- Converts the binary back to hex
I would like to point out that at the 276th line of the oldMiniNero.py the function uses CURVE_P
instead of l
, where CURVE_P = (2**255 - 19)
so
const CURVE_P = (2n**255n - 19n);
const decPrivateViewKey = decSeedKeccak % CURVE_P; //3897010105725376015834496606446465635002997137141308681634129712668941515136
const hexPrivateViewKey = decPrivateViewKey.toString(16) //convert the dec BigInt to hex: 89da12a88d36bce0966ab1a79125779dd1f2fc6f1145de131fd52a5b4687980
Which is still wrong. That is because the lines 280-281 in the comment says that
s is already in the form:
s[0]+256*s1+...+256^31*s[31] = s I can't really understand what it's meant to do, but I guess it's simply dividing into blocks and joining them literally into a binary string (I guess) of 32 digits (like step 1 of the jssc_reduce32
function)
So now I have a few question:
- Why doesn't the
sc_reduce32
simply run amod l
and instead does some stuff with binary and memory? Is it related with theed25519
library? - Why is the hexPrivateViewKey 89da... instead of faa5... (wrong) when calculating the mod, but it works just fine with the example of the 4th link?
- Does it metter if I use the seed or the private spend key when generating, or could those two differ in some cases? Based this it should, is it true?
I would like to know if it's possible to make a function for this without having to import an external library (apart from the possible require('elliptic')
and require('keccak256')
ones). I'm new to monero, so, if you can, I would like a step-by-step explanation of the process of the sc_reduce32 function (possibly the concept for any programming language, even if js-specific is fine).
EDIT
After knacc's answer I think I can post here the final result so people can understand how to do it. (I wrote it node.js-specific, but the logic is fine for any other programming-language)
- Firstly import the libraries:
//load the secure random integers generator algorithm
const secureRandom = require('secure-random');
//load the keccak256 algorithm
const keccak256 = require('keccak256');
- After that create the constants and functions
//sc_reduce32 algorithm function with any hex string
function sc_reduce32 (hex) {
if (hex.length != 32) //if length isn't 32 throw error
throw 'Input invalid length';
return Buffer.from((BigInt('0x' + Buffer.from(hex, 'hex').reverse().toString('hex')) % l ).toString(16).padStart(64, '0'), 'hex').reverse(); //reverse (little endian), run mod l, pad from the start with '0', reverse (again) and get the hex.
}
//validate hex with simple regexp
function valid_hex (hex) {
return new RegExp("[0-9a-fA-F]{" + hex.length + "}").test(hex);
//create constant l as big int
const l = 7237005577332262213973186563042994240857116359379907606001950938285454250989n
};
- Get the seed:
- From first argument (if valid)
- Randomly (if the first argument is not valid)
//check if 1st argument is valid for seed (even length, non-empty and valid hex)
const isArgumentValidSeed = process.argv[2]&&process.argv[2].length%2<1&&valid_hex(process.argv[2]);
//get the seed
const seed = isArgumentValidSeed?Buffer.from(process.argv[2], 'hex'):secureRandom.randomBuffer(32);
- Validate the seed
//if seed length is not 32 fast-hash it
const validSeed = seed.length != 32 ? keccak256(seed) : seed;
- Reduce the seed to the private spend key
//reduce validSeed to a valid 32-byte private spend key
const privateSpendKey = sc_reduce32(validSeed);
- If input seed length was 32 use validSeed, otherwise use the private spend key
//reduce the keccak-256 hash of validSeed (if the input seed length wasn't 32) or the private spend key (if the input seed length was 32)
const privateViewKey = sc_reduce32(keccak256(seed.length != 32 ? validSeed : privateSpendKey, 'hex'));
After that you can convert both keys to strings (they're stored as buffers)