1

I have generated mnemonic using bip39.generateMnemonic();. I need to convert this mnemonic to AsymmetricKeyPair with secp256k1.

AsymmetricKeyPair<PublicKey, PrivateKey> secp256k1KeyPair() {
    var keyParams = ECKeyGeneratorParameters(ECCurve_secp256k1());
    var mnemonic = getMnemonic();
    var seed = bip39.mnemonicToSeed(mnemonic);

   
    print('mnemonicToSeed=========$seed');
    var random = FortunaRandom();
    random.seed(KeyParameter(seed));

    var generator = ECKeyGenerator();
    generator.init(ParametersWithRandom(keyParams,random));

    return generator.generateKeyPair();
  }


getMnemonic() {
    var mnemonic = bip39.generateMnemonic();
    print('mnemonic=========$mnemonic');
    return mnemonic;
  }

Unhandled Exception: Invalid argument(s): Fortuna PRNG can only be used with 256 bits keys.

Finally I'm able to generate the AsymmetricKeyPair as follows.but I'm not sure whether I did it in a correct way.Please correct me if I'm wrong.

/// generates dynamic mnemonic
  String getMnemonic() {
    var mnemonic = bip39.generateMnemonic();
    print('mnemonic=========$mnemonic');
    return mnemonic;
  }

  AsymmetricKeyPair<PublicKey, PrivateKey> secp256k1KeyPair() {

    var keyParams = ECKeyGeneratorParameters(ECCurve_secp256k1());
    var mnemonic = getMnemonic();
    var seed = bip39.mnemonicToSeed(mnemonic);
    final digest = sha256.convert(seed); // 32 bytes

    print('mnemonicToSeed=========$seed');
    var random = FortunaRandom();
    random.seed(KeyParameter(_seed(digest.bytes)));
    var generator = ECKeyGenerator();
    generator.init(ParametersWithRandom(keyParams,random));

    return generator.generateKeyPair();
  }

  Uint8List _seed(List<int> digest) {
    var seed = List<int>.generate(digest.length, (_) => digest[_]);
    return Uint8List.fromList(seed);
  }

As FortunaRandom is not robust ,Could you please suggest anything else.

dev
  • 73
  • 3
  • 11
  • I suspect that your design is inspired by [this post](https://stackoverflow.com/a/72047475/9014097). There, an RSA key is generated that has more parameters than an EC key. Instead of determining all parameters explicitly, an existing library function is used (`pki.rsa.generateKeyPair()`), which requires a PRNG. This is not needed in the EC case, just follow the BIP39/BIP32 approach. – Topaco Aug 02 '23 at 22:40
  • After deriving the seed with BIP39 apply [BIP32](https://learnmeabitcoin.com/technical/extended-keys#master-extended-keys) to get a master key, from which even children can be created if required. And if you really need only one key, `var privateKey = bip32.BIP32.fromSeed(seed).privateKey` (s. [bip32](https://pub.dev/packages/bip32)) is enough. Importing the private key and deriving the public key is easily done with PointyCastle. – Topaco Aug 02 '23 at 22:45
  • `bip32.BIP32.fromSeed(seed).privateKey` by using this , How am I able to generate key using secp256k1 ? – dev Aug 03 '23 at 08:35
  • See my answer please. – Topaco Aug 03 '23 at 09:10

2 Answers2

2

If I'm looking at the code then mnemonicToSeed for BIPS39 will generate a 512 bit output (the output size of PBKDF2, more information here). You can just use the leftmost bits/bytes of that and take 256 bits. Ye old Fortuna uses a block cipher in counter mode and likely the initial seed size must be identical to the (or a) key size.

Note that I've got no idea what you are trying to do. If I'd try to generate another key pair deterministically, I'd not use your code as it is implementation dependent. If anything changes in generateKeyPair, e.g. if a different method is used to extract random bytes then it would break, while the API would remain the same. The chance of that is not that high for Elliptic Curve with secp256k1 as key pair generation is largely deterministic as well but suddenly generating a the wrong private key can certainly have terrible consequences.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • I'm a beginner in Block chain concepts . I'm trying to create a wallet here with secp256k1. I'm not sure whether I'm doing it in the correct way. but I could able to get a private and public keys `var seed = bip39.mnemonicToSeed(mnemonic); final digest = sha256.convert(seed); // 32 bytes print('mnemonicToSeed=========$seed'); var random = FortunaRandom(); random.seed(KeyParameter(_seed(digest.bytes)));` with the help of above code . Please correct me if i'm wrong – dev Aug 03 '23 at 05:37
  • 1
    @jibin - You can, but the PRNG part (i.e. `FortunaRandom`) is not robust and unnecessarily complicated. This is not needed for EC and passwords of sufficient entropy (like mnemonics). – Topaco Aug 03 '23 at 05:54
  • @Topaco . Could you please tell me how can I generate CipherParameters for ECKeyGenerator from the mnemonicSeed with a code sample ? `var seed = bip39.mnemonicToSeed(mnemonic); final digest = sha256.convert(seed); var rnd = SecureRandom()..seed(KeyParameter(_seed(digest.bytes))); var generator = ECKeyGenerator(); generator.init(ParametersWithRandom(keyParams,rnd));` how about this ? **RegistryFactoryException: No algorithm registered of type SecureRandom with name** how do I add my ECCurve_secp256k1 algorithm here. – dev Aug 03 '23 at 08:07
  • `Uint8List _seed(List digest) { var seed = List.generate(digest.length, (_) => digest[_]); return Uint8List.fromList(seed); }` this method is I'm using for generating seed – dev Aug 03 '23 at 08:22
  • @jibin - Please stop posting code in comments. Edit your question, add an edit section and post the code there. Or, since your concern from the comment seems to me to be more of a new question, post a new question (why you insist on using `ECKeyGenerator` and thus a PRNG is beyond me; I posted you an alternative in the comments to your question; instead of BIP32, you can also take the SHA256 value, or the first 32 bytes if you feel more comfortable with that). – Topaco Aug 03 '23 at 08:34
2

To avoid using a PRNG (e.g. FortunaRandom), you could apply the following approach:

After derivation of a raw 32 bytes private key (via getMnemonic() and mnemonicToSeed()) this can be imported directly with ECPrivateKey().
The raw public key results by a multiplication with the generator point and subsequent import with ECPublicKey():

import 'package:bip39/bip39.dart' as bip39;
import 'package:bip32/bip32.dart' as bip32; // if BIP32 is applied
import 'package:pointycastle/export.dart';
import 'package:nanodart/nanodart.dart';
import 'package:basic_utils/basic_utils.dart'; // if PEM conversion required

...

// get seed
// var mnemonic = getMnemonic();
var mnemonic = "viable rhythm total tissue muscle forget culture strong lobster drama swim wasp"; // apply a static mnemonic for testing
var seed = bip39.mnemonicToSeed(mnemonic);

// derive private key
var privateKeyBytes = bip32.BIP32.fromSeed(seed).privateKey!; // apply BIP32 and use the "extended private key" 
// var privateKeyBytes = seed.sublist(0, 32);                 // alternatively, do not apply BIP32 and generate a key of the required size from the seed (e.g. the SHA256 hash or the first 32 bytes of the seed)

// create ECPrivateKey/ECPublicKey instances (this is what you asked for in the comment)
var privateKey = NanoHelpers.byteToBigInt(privateKeyBytes);
var domain = ECDomainParameters('secp256k1');
var ecPrivateKey = ECPrivateKey(privateKey, domain);
var g = ecPrivateKey.parameters?.G;
var ecPublicKey = ECPublicKey(g! * privateKey, domain);

// output the raw keys...
print('mnemonicToSeed=========: $seed');
print('privateKey=========: $privateKeyBytes');
var uncompressed = ecPublicKey.Q?.getEncoded(false);
print('publicKey=========: $uncompressed');

// ...or export as PEM
var sec1Pem = CryptoUtils.encodeEcPrivateKeyToPem(ecPrivateKey);
var spkiPem = CryptoUtils.encodeEcPublicKeyToPem(ecPublicKey);
print('private as sec1=========: $sec1Pem');
print('public as x.509/SPKI=========: $spkiPem');
Topaco
  • 40,594
  • 4
  • 35
  • 62
  • Is this the "normal" way that a private key should be derived according to BIP39 and BIP32? Not a critique, it is definitely better than using Fortuna I suppose. – Maarten Bodewes Aug 03 '23 at 10:38
  • 1
    @MaartenBodewes-onstrike - For HD wallets a key hierarchy is generated and not a single key. The *normal* way for HD wallets would be BIP39 (generation of a seed from a menemonic) and BIP32 (generation of a master key (= extended private key + chain code) from the seed for deriving child keys). For a single key (which is rather a misguided approach of the OP) there is in my opinion no *normal* way. My suggestion to use the *extended private key* is only kinda compromise. – Topaco Aug 03 '23 at 14:54