hashgraph / hedera-sdk-js

Hedera™ Hashgraph SDK for JavaScript/TypeScript
https://docs.hedera.com/guides/docs/sdks
Apache License 2.0
255 stars 132 forks source link

Recovering Ed2551 Accounts - Encoding Issue #2416

Open bmwolf opened 1 month ago

bmwolf commented 1 month ago

Description

Some Ed2551 encoded private keys seem like they can't be recovered using the .toStandardEd2551PrivateKey() method.

I am able to recover using .toEd25519PrivateKey() but that function is now deprecated and can be removed at any time. I am assuming .toStandardEd2551PrivateKey was supposed to fully replace .toEd25519PrivateKey() and that is why I think there may be an issue.

Steps to reproduce

Im not sure how to give steps to reproduce without giving account information

Additional context

None

Hedera network

mainnet, testnet

Version

v2.48.1

Operating system

macOS

Petyo-Lukanov commented 1 month ago

Hey @bmwolf, thank you for raising this one! As far as I see from this issue, it's also reproducible on Testnet. Would it be possible for you to send us Testnet Private Keys, which are not recovering properly so we can investigate further? I will attempt to reproduce this one in the meantime 👍

agadzhalov commented 1 month ago

Hi @bmwolf and thank you for raising this. Can you please provide a step by step example or may a snippet how to reproduce this so we can start investigating?

bmwolf commented 1 month ago

@Petyo-Lukanov @agadzhalov Hey, sorry for the delayed response. I am trying to find a testnet account that has the issue but unfortunately most of them have been wiped from the quarterly purge. I am going through the account info that my coworkers have used for testing. As soon as I find one Ill post it here. I apologize I can't be of more help.

bmwolf commented 1 month ago

@Petyo-Lukanov @agadzhalov Sorry for the delay, it looks like if an account was previously created from a 12 word mnemonic phrase with toEd25519PrivateKey() that is what is causing an issue recovering. Here is the testnet account info.

Seed Phrase: flower camera awkward coil father lava hair course switch adult symptom evidence

Private Key: 302e020100300506032b6570042204208dd10cfec6fddfef379f2b6e34838d2e7aabe2ebc32e7dcadbba47d86da75e02

ivaylonikolov7 commented 3 weeks ago

Hello @bmwolf I looked at your issue, reproduced it in a local environment using this snippet:

import {
  AccountId,
  PrivateKey,
  Mnemonic,
} from "@hashgraph/sdk";

import * as dotenv from "dotenv";

dotenv.config();

async function htsContractFunction() {
  const OPERATOR_ID = AccountId.fromString(process.env.OPERATOR_ID);
  const OPERATOR_KEY = PrivateKey.fromStringECDSA(process.env.OPERATOR_KEY);
  const words = [
    "flower",
    "camera",
    "awkward",
    "coil",
    "father",
    "lava",
    "hair",
    "course",
    "switch",
    "adult",
    "symptom",
    "evidence",
  ];
  const words2 = [
    "finish",
    "science",
    "attend",
    "acquire",
    "until",
    "derive",
    "elephant",
    "harsh",
    "tool",
    "bracket",
    "move",
    "shrimp",
  ];

  const mnemonic = await Mnemonic.fromWords(words);
  const privKey = await mnemonic.toStandardEd25519PrivateKey();
  console.log("Private key from mnemonic", privKey.toStringDer());

  const privKeyDirect = PrivateKey.fromStringED25519(
    "302e020100300506032b6570042204208dd10cfec6fddfef379f2b6e34838d2e7aabe2ebc32e7dcadbba47d86da75e02"
  );
  console.log("Direct private key", privKeyDirect);
  console.log("Direct public key", privKeyDirect.publicKey.toStringDer());

  const privKeyNon = await mnemonic.toEd25519PrivateKey();
  console.log("private key from mnemonic deprecated", privKeyNon.toStringDer());
}

void htsContractFunction();

I got the same result as you said -> didn't get the expected private key - 302e020100300506032b6570042204208dd10cfec6fddfef379f2b6e34838d2e7aabe2ebc32e7dcadbba47d86da75e02 but actually got 302e020100300506032b65700422042013b263718a331e955d7c2cda5c7c3c611ca4a5c99971ac2606600e102fae5c8f.

I asked some colleagues that worked on the depreciation of .toEd25519PrivateKey. So what they told me is this behaviour is expected. The problem is that the former implementation was wrong and it created a lot of issues. You can take a look at the issues we addressed by checking out this PR- https://github.com/hashgraph/hedera-sdk-js/pull/1550 The right way of getting the ED25519 private key from a mnemonic now is using toStandardEd25519PrivateKey function.

If you really want to use this PrivateKey 302e020100300506032b6570042204208dd10cfec6fddfef379f2b6e34838d2e7aabe2ebc32e7dcadbba47d86da75e02 you can always use PrivateKey.fromStringED25519. If you can tell me more about your problem I'd be glad to help. Any additional info that can help me with your issue would be appreciated! We can also book a call in a Google meeting if you think that would be faster way of resolving your issue.

ivaylonikolov7 commented 3 weeks ago

So I did a little more research because I wanted to make sure that toStandardEd25519PrivateKey behaves as expected. I wrote this code snippet:

import {
  Client,
  AccountId,
  PrivateKey,
  Logger,
  LogLevel,
  Mnemonic,
} from "@hashgraph/sdk";

import * as dotenv from "dotenv";

dotenv.config();

async function htsContractFunction() {
  const OPERATOR_ID = AccountId.fromString(process.env.OPERATOR_ID);
  const OPERATOR_KEY = PrivateKey.fromStringECDSA(process.env.OPERATOR_KEY);
  const words = [
    "flower",
    "camera",
    "awkward",
    "coil",
    "father",
    "lava",
    "hair",
    "course",
    "switch",
    "adult",
    "symptom",
    "evidence",
  ];

  const mnemonic = await Mnemonic.fromWords(words);
  const ed25519PrivKey = await mnemonic.toStandardEd25519PrivateKey();
  const privKeyCapitalCase = ed25519PrivKey.toStringRaw().toUpperCase();
  console.log("Capital case of private key", privKeyCapitalCase);
  console.log(
    "Public key",
    ed25519PrivKey.publicKey.toStringRaw().toUpperCase()
  );
  //const depricatedPrivatekey = await mnemonic.toEd25519PrivateKey();
  //const privKey = await mnemonic.toStandardEd25519PrivateKey();

  /*console.log("Private key from mnemonic", privKey.toStringDer());
  console.log("Private key from mnemonic raw", privKey.toStringRaw());
  console.log(privKey._key);
  console.log("public key from mnemonic", privKey.publicKey.toStringRaw());
  */

  /*
  const privKeyDirect = PrivateKey.fromStringED25519(
    "302e020100300506032b6570042204208dd10cfec6fddfef379f2b6e34838d2e7aabe2ebc32e7dcadbba47d86da75e02"
  );
  console.log("Direct private key", privKeyDirect);
  console.log("Direct public key", privKeyDirect.publicKey.toStringDer());

  const privKeyNon = await mnemonic.toEd25519PrivateKey();
  console.log("private key from mnemonic deprecated", privKeyNon.toStringDer());
  */
}

void htsContractFunction();

It gives you a private key and a public key using the mnemonics. If you copy and paste the code snippet into this website:

https://cyphr.me/ed25519_tool/ed.html - You can confirm that the correct public key is derived from the private key.

image

And this is the output of the code snippet I gave you.

image

As you can see they are the same.