-
Notifications
You must be signed in to change notification settings - Fork 33
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
How does the actual ed25519 derivation work #61
Comments
This is what I have, which doesn't seem to match for my mnemonics - // standard BTC libs
const bp39 = require('bip39');
// also use by Stellar, Tezos (or adapted from)
const { derivePath, getPublicKey } = require('ed25519-hd-key');
// inputs
const accountIndex = 0;
const addressIndex = 0;
const mnemonic = 'abandon ...';
// using 1b2 here, the one I'm looking at is on Kusama
const path = `m/${0x2c}'/${0x01b2}'/${accountIndex}'/0'/${addressIndex}'`;
// mnemonic -> bip39 seed
const seed = bip39.mnemonicToSeedSync(mnemonic);
// ed25519 + bip39
const { key } = derivePath(path, seed);
// log
console.log('private', key.toString('hex'))
console.log('public', getPublicKey(key, false).toString('hex')); Also attempted the approach of bip39+bip32->ed (which I believe is incorrect and certainly not bip32+ed25519 nor anywhere close to passing public testcases for that type). Just adding it for the sake of completeness for attempts - const bp39 = require('bip39');
const bip32 = require('bip32');
const EdDSA = require('elliptic').eddsa;
const seed = bip39.mnemonicToSeedSync(mnemonic);
const root = bip32.fromSeed(seed);
const child = root.derivePath(path);
// using tweetnacl here has the same outputs at the end
const ec = new EdDSA('ed25519');
const eckeypair = ec.keyFromSecret(child.privateKey);
console.log("private:", new Buffer.from(eckeypair.getSecret()).toString('hex'));
console.log("public:", new Buffer.from(eckeypair.getPublic()).toString('hex')); |
Also tried the (most-probably also wrong approach) of const bp39 = require('bip39');
const bip32 = require('bip32');
const EdDSA = require('elliptic').eddsa;
const keccak256 = require('keccak256');
//sc_reduce32 algorithm function with any hex string
function sc_reduce32 (key) {
const l = 7237005577332262213973186563042994240857116359379907606001950938285454250989n
// reverse (little endian), run mod l, pad from the start with '0', reverse (again) and get the bytes
return Buffer.from(
(BigInt('0x' + key.reverse().toString('hex')) % l)
.toString(16)
.padStart(64, '0'),
'hex'
).reverse();
}
const seed = bip39.mnemonicToSeedSync(mnemonic);
const root = bip32.fromSeed(seed);
const child = root.derivePath(path);
const privateHash = keccak256(child.privateKey);
const privateSpendKey = sc_reduce32(privateHash);
const ec = new EdDSA('ed25519');
const eckeypair = ec.keyFromSecret(privateSpendKey);
console.log("private:", new Buffer.from(eckeypair.getSecret()).toString('hex'));
console.log("public:", new Buffer.from(eckeypair.getPublic()).toString('hex')); Obviously I could have gotten something small wrong in all 3 attempts here. But alas, none the wiser as the the approach followed. |
@jacogr Did you have a look at #43? Might be helpful. Also paritytech/substrate#7824 seems to be relevant here. Edit: In particular
and https://github.com/alepop/ed25519-hd-key is apparently using SLIP-0010. |
Thank you, missed that issue in #43 . Will adapt and report back :) |
@gorgos Thanks a million for the pointer. Ok, have the extraction working, will cleanup and push an example as to "how-to". (tested it and it indeed yields the correct results) EDIT: Sample here - https://github.com/jacogr/sample-ledger-ed25519 (not 100% clean, but working) |
We are working on a library to do this in a safe and consistent way. Nevertheless, we VERY STRONGLY advise against using this approach and entering your mnemonic in web or deskop apps. As described here:
|
I do agree with keeping the mnemonic safe and on the Ledger only. At the same time, it is good that there is a way to extract the seeds so it is not a complete black box at all. The above POC is also runnable via the command-line so you don't need to add it in the web,, so if I ever I do lose the Ledger (again) there is a command-line util available to recover the raw secret. Looking forward to a ZondaX-lib. Closing this, since the question has been resolved as to "how". |
thank you, kind man! I've been trying to solve this problem for more than a day! Thanks to your tool, I solved it! |
So I have an issue logged from a user around supporting Ledger seeds as an import. Generally this is not such a great idea since you really want to keep the secret safe and locked up.
Having said that, addressing it will also be self-serving for me - i.e. I just had a little episode in which I had to replace my Ledger and while waiting for a replacement (6+ weeks) didn't have access to that specific stash at all. So at least in my recent experience there is an argument to be made that keys should be derivable in a couple of places. (Not available/exposed, just the tools should be there)
So this brings me to how it is done on the Ledger. Did a bit of research and it seems the Tezos (and actual Trezor HW as well) implementation uses the bip32-ed25519. Tested that via others and libraries and it works quite well, but doesn't seem to be compatible with what is outputted on the Ledger.
For earlier posts elsewhere I picked up that (and it may be misleading) that the Ledger does the normal derivation, then hashes the secrete and feeds it into the ed25519 curve when not using the bip25519 approach.
So effectively, from my understanding, the steps are -
m/44'/354'/0'/0'/0'
(assuming account 0 & index 0)The text was updated successfully, but these errors were encountered: