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

ed25519 public key regeneration #7

Open
vchong opened this issue May 28, 2024 · 14 comments
Open

ed25519 public key regeneration #7

vchong opened this issue May 28, 2024 · 14 comments

Comments

@vchong
Copy link

vchong commented May 28, 2024

Is it possible to shed light into the process of going from a bip39 mnemonic and the m/44'/607'/0'/0/0/0 bip44 derivation path to the 32B ed25519 public key returned in

ledger-app-ton/src/crypto.c

Lines 107 to 113 in 5ca0490

// Convert to NaCL format
for (int i = 0; i < PUBKEY_LEN; i++) {
raw_public_key[i] = public_key.W[64 - i];
}
if ((public_key.W[32] & 1) != 0) {
raw_public_key[31] |= 0x80;
}
? We're in dire need to do this without a ledger. Thank you very much in advance!

@kslamph
Copy link

kslamph commented Jun 24, 2024

this is needed, as mytonwallet dosnt support ledger on dapps now. we need to do this without a ledger.

@vchong
Copy link
Author

vchong commented Jun 24, 2024

@kslamph Will https://tonhelloworld.com/01-wallet/ work for you? e.g.

import { mnemonicToWalletKey } from "@ton/crypto";
import { WalletContractV4 } from "@ton/ton";

async function main() {
  // open wallet v4 (notice the correct wallet version here)
  const mnemonic = "unfold sugar water ..."; // your 24 secret words (replace ... with the rest of the words)
  const key = await mnemonicToWalletKey(mnemonic.split(" "));
  const wallet = WalletContractV4.create({ publicKey: key.publicKey, workchain: 0 });

  // print wallet address
  console.log(wallet.address.toString({ testOnly: true }));

  // print wallet workchain
  console.log("workchain:", wallet.address.workChain);
}

main();

Our problem is that, if you program a ledger with the same mnemonic as the one used above (e.g. unfold sugar water ...), the wallet address generated when you connect this ledger to the tonkeeper wallet extension isn't the same as the one generated by the code above. Specifically, the publicKey isn't the same (and obviously the privateKey as well).

We would like the wallet address on both occasions to be the same, hence my original question above, how is the publicKey for the ledger-app-ton derived. If we can derive the same key value without a ledger, we can just plug it into the code above and generate the same wallet address as the one generated with the ledger plus tonkeeper wallet extension.

@kslamph
Copy link

kslamph commented Jun 25, 2024

@kslamph Will https://tonhelloworld.com/01-wallet/ work for you? e.g.


import { mnemonicToWalletKey } from "@ton/crypto";

import { WalletContractV4 } from "@ton/ton";



async function main() {

  // open wallet v4 (notice the correct wallet version here)

  const mnemonic = "unfold sugar water ..."; // your 24 secret words (replace ... with the rest of the words)

  const key = await mnemonicToWalletKey(mnemonic.split(" "));

  const wallet = WalletContractV4.create({ publicKey: key.publicKey, workchain: 0 });



  // print wallet address

  console.log(wallet.address.toString({ testOnly: true }));



  // print wallet workchain

  console.log("workchain:", wallet.address.workChain);

}



main();

Our problem is that, if you program a ledger with the same mnemonic as the one used above (e.g. unfold sugar water ...), the wallet address generated when you connect this ledger to the tonkeeper wallet extension isn't the same as the one generated by the code above. Specifically, the publicKey isn't the same (and obviously the privateKey as well).

We would like the wallet address on both occasions to be the same, hence my original question above, how is the publicKey for the ledger-app-ton derived. If we can derive the same key value without a ledger, we can just plug it into the code above and generate the same wallet address as the one generated with the ledger plus tonkeeper wallet extension.

thanks, but it dose not work for my case, i have the same requirments as you, need to generate the key same mytonwallet, besides, i have a passpharse added on the 24 words.

@loupiote
Copy link

loupiote commented Oct 22, 2024

Note that Ledger Live uses the derivation path m/44'/607'/0'/0'/0'/0' (not m/44'/607'/0'/0/0/0). No idea why Ledger Live uses this weird derivation path!

And yes, as @vchong said, it appears to be different from the derivation path used by Tonkeeper.

@vchong
Copy link
Author

vchong commented Oct 22, 2024

@loupiote i think that was the missing piece

@kslamph do you still need this? can you try below and see if it works?

import * as bip39 from 'bip39';
import * as ed25519 from 'ed25519-hd-key';
import { WalletContractV4, WalletContractV3R2 } from '@ton/ton';

function derivePublicKey(mnemonic, hdPath) {
    const seed = bip39.mnemonicToSeedSync(mnemonic);

    const { key: rawPrivateKey } = ed25519.derivePath(hdPath, seed.toString('hex'));
    console.log('rawPrivateKey:', rawPrivateKey.toString('hex'));

    const rawPublicKey = ed25519.getPublicKey(rawPrivateKey);
    console.log('rawPublicKey:', rawPublicKey.toString('hex'));

    // Get public key and slice off the '00' prefix before converting to hex
    const rawPublicKey2 = ed25519.getPublicKey(rawPrivateKey).slice(1);
    console.log('rawPublicKey2:', rawPublicKey2.toString('hex'));

    return {
        privateKey: rawPrivateKey,
        publicKey: rawPublicKey2
    }
}

// Example usage
const mnemonic = '<insert mnemonic here>';
const hdPath = "m/44'/607'/0'/0'/0'/0'"; // account index 0
// const hdPath = "m/44'/607'/0'/0'/1'/0'"; // account index 1
const keyPair = derivePublicKey(mnemonic, hdPath);
console.log('Private Key:', keyPair.privateKey.toString('hex'));
console.log('Public Key:', keyPair.publicKey.toString('hex'));

const contract = WalletContractV4.create({
    workchain: 0,
    publicKey: keyPair.publicKey
});

const friendlyAddress = contract.address.toString({urlSafe: true, bounceable: true, testOnly: false}); // set this to `true` if testnet
const friendlyAddressNonBounceable = contract.address.toString({urlSafe: true, bounceable: false, testOnly: false});

// these addresses should match those from a ledger
console.log(`
    v4R2 Wallet:
    Bounceable address: ${friendlyAddress}
    Non-bounceable address: ${friendlyAddressNonBounceable}
        `);

@mikey-cool
Copy link

@loupiote我认为这是缺失的部分

@kslamph你还需要这个吗?你可以尝试下面看看它是否有效吗?

import * as bip39 from 'bip39';
import * as ed25519 from 'ed25519-hd-key';
import { WalletContractV4, WalletContractV3R2 } from '@ton/ton';

function derivePublicKey(mnemonic, hdPath) {
    const seed = bip39.mnemonicToSeedSync(mnemonic);

    const { key: rawPrivateKey } = ed25519.derivePath(hdPath, seed.toString('hex'));
    console.log('rawPrivateKey:', rawPrivateKey.toString('hex'));

    const rawPublicKey = ed25519.getPublicKey(rawPrivateKey);
    console.log('rawPublicKey:', rawPublicKey.toString('hex'));

    // Get public key and slice off the '00' prefix before converting to hex
    const rawPublicKey2 = ed25519.getPublicKey(rawPrivateKey).slice(1);
    console.log('rawPublicKey2:', rawPublicKey2.toString('hex'));

    return {
        privateKey: rawPrivateKey,
        publicKey: rawPublicKey2
    }
}

// Example usage
const mnemonic = '<insert mnemonic here>';
const hdPath = "m/44'/607'/0'/0'/0'/0'"; // account index 0
// const hdPath = "m/44'/607'/0'/0'/1'/0'"; // account index 1
const keyPair = derivePublicKey(mnemonic, hdPath);
console.log('Private Key:', keyPair.privateKey.toString('hex'));
console.log('Public Key:', keyPair.publicKey.toString('hex'));

const contract = WalletContractV4.create({
    workchain: 0,
    publicKey: keyPair.publicKey
});

const friendlyAddress = contract.address.toString({urlSafe: true, bounceable: true, testOnly: false}); // set this to `true` if testnet
const friendlyAddressNonBounceable = contract.address.toString({urlSafe: true, bounceable: false, testOnly: false});

// these addresses should match those from a ledger
console.log(`
    v4R2 Wallet:
    Bounceable address: ${friendlyAddress}
    Non-bounceable address: ${friendlyAddressNonBounceable}
        `);

This still does not match the address generated by ledger live

@mikey-cool
Copy link

@loupiote我认为这是缺失的部分

@kslamph你还需要这个吗?你可以尝试下面看看它是否有效吗?

import * as bip39 from 'bip39';
import * as ed25519 from 'ed25519-hd-key';
import { WalletContractV4, WalletContractV3R2 } from '@ton/ton';

function derivePublicKey(mnemonic, hdPath) {
    const seed = bip39.mnemonicToSeedSync(mnemonic);

    const { key: rawPrivateKey } = ed25519.derivePath(hdPath, seed.toString('hex'));
    console.log('rawPrivateKey:', rawPrivateKey.toString('hex'));

    const rawPublicKey = ed25519.getPublicKey(rawPrivateKey);
    console.log('rawPublicKey:', rawPublicKey.toString('hex'));

    // Get public key and slice off the '00' prefix before converting to hex
    const rawPublicKey2 = ed25519.getPublicKey(rawPrivateKey).slice(1);
    console.log('rawPublicKey2:', rawPublicKey2.toString('hex'));

    return {
        privateKey: rawPrivateKey,
        publicKey: rawPublicKey2
    }
}

// Example usage
const mnemonic = '<insert mnemonic here>';
const hdPath = "m/44'/607'/0'/0'/0'/0'"; // account index 0
// const hdPath = "m/44'/607'/0'/0'/1'/0'"; // account index 1
const keyPair = derivePublicKey(mnemonic, hdPath);
console.log('Private Key:', keyPair.privateKey.toString('hex'));
console.log('Public Key:', keyPair.publicKey.toString('hex'));

const contract = WalletContractV4.create({
    workchain: 0,
    publicKey: keyPair.publicKey
});

const friendlyAddress = contract.address.toString({urlSafe: true, bounceable: true, testOnly: false}); // set this to `true` if testnet
const friendlyAddressNonBounceable = contract.address.toString({urlSafe: true, bounceable: false, testOnly: false});

// these addresses should match those from a ledger
console.log(`
    v4R2 Wallet:
    Bounceable address: ${friendlyAddress}
    Non-bounceable address: ${friendlyAddressNonBounceable}
        `);

Mnemonic word:perfect valley spy embark stadium hint ocean pen conduct prevent skull plug empower obscure night inside violin weasel brief barrel crucial choice brisk husband
ledger live address:UQDNElOhL-KgNzU9I-j6HZLbvNsCaE5Jqs23lf-xYUkhITim
TonKeeper address:UQBLP6SOA1FhMb6UfNnGmVUgcl7KVpLoMTzdoZ4BV1HzKrWe
this code generate address: UQCR97S0JjEutMBft4FMIa_HVDdqREi1P1o_CEjOAqr3GEKV

@vchong
Copy link
Author

vchong commented Oct 23, 2024

Did you do this?

const mnemonic = '<insert your "perfect valley .." mnemonic here>';

I was able to get UQDNElOhL-KgNzU9I-j6HZLbvNsCaE5Jqs23lf-xYUkhITim with the code, which matches the ledger live address.

rawPrivateKey: ...
rawPublicKey: ...
rawPublicKey2: 042727f75e5505bca999ea862c6fb4d7dfbd34ef4567fdc9e7e3b24d62b882d8
Private Key: ...
Public Key: 042727f75e5505bca999ea862c6fb4d7dfbd34ef4567fdc9e7e3b24d62b882d8

    v4R2 Wallet:
    Bounceable address: EQDNElOhL-KgNzU9I-j6HZLbvNsCaE5Jqs23lf-xYUkhIWVj
    Non-bounceable address: UQDNElOhL-KgNzU9I-j6HZLbvNsCaE5Jqs23lf-xYUkhITim

Importing the mnemonic directly into Tonkeeper will not give the same address as it does its derivation different than the ledger.

@mikey-cool
Copy link

你做过这个吗?

const mnemonic = '<insert your "perfect valley .." mnemonic here>';

我能够获得UQDNElOhL-KgNzU9I-j6HZLbvNsCaE5Jqs23lf-xYUkhITim与分类帐实时地址相匹配的代码。

rawPrivateKey: ...
rawPublicKey: ...
rawPublicKey2: 042727f75e5505bca999ea862c6fb4d7dfbd34ef4567fdc9e7e3b24d62b882d8
Private Key: ...
Public Key: 042727f75e5505bca999ea862c6fb4d7dfbd34ef4567fdc9e7e3b24d62b882d8

    v4R2 Wallet:
    Bounceable address: EQDNElOhL-KgNzU9I-j6HZLbvNsCaE5Jqs23lf-xYUkhIWVj
    Non-bounceable address: UQDNElOhL-KgNzU9I-j6HZLbvNsCaE5Jqs23lf-xYUkhITim

将助记词直接导入 Tonkeeper 不会给出相同的地址,因为它的派生方式与分类账不同。

Can you give me the package used for your package.json? I wonder if we have different bags

@vchong
Copy link
Author

vchong commented Oct 23, 2024

It's not a "clean" copy, but here you go.

{
  "dependencies": {
    "@ton/crypto": "^3.3.0",
    "@ton/ton": "^13.11.2",
    "bip32": "^4.0.0",
    "bip39": "^3.1.0",
    "ed25519": "^0.0.5",
    "ed25519-hd-key": "^1.3.0",
    "elliptic": "^6.5.5",
    "ethereumjs-util": "^7.1.5",
    "hdkey": "^2.1.0",
    "tiny-secp256k1": "^2.2.3",
    "tweetnacl": "^1.0.3"
  }
}

Only bip39, ed25519-hd-key and @ton/ton should be required.
It's not good though if the output depends on the library versions.

@mikey-cool
Copy link

这不是一个“干净”的副本,但是你可以看一下。

{
  "dependencies": {
    "@ton/crypto": "^3.3.0",
    "@ton/ton": "^13.11.2",
    "bip32": "^4.0.0",
    "bip39": "^3.1.0",
    "ed25519": "^0.0.5",
    "ed25519-hd-key": "^1.3.0",
    "elliptic": "^6.5.5",
    "ethereumjs-util": "^7.1.5",
    "hdkey": "^2.1.0",
    "tiny-secp256k1": "^2.2.3",
    "tweetnacl": "^1.0.3"
  }
}

只有bip39ed25519-hd-key@ton/ton应该是必需的。 但如果输出依赖于库版本,那就不好了。

It really is the version of the problem, my "@ton/ton": "^15.0.0", replaced by your version will be the same, thank you

@vchong
Copy link
Author

vchong commented Oct 23, 2024

I used 15.0.0 and was able to get the same address as well. 🤔

@mikey-cool
Copy link

我使用了 15.0.0 并且也能够获得相同的地址。🤔

It was a ledger bug, I had an account with ton, but after I reset it I imported the mnemonic from before and now he gets a different account.

@loupiote
Copy link

loupiote commented Oct 23, 2024 via email

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

6 participants
@vchong @kslamph @loupiote @mikey-cool and others