xclud / web3dart

Ethereum library, written in Dart.
https://pub.dev/packages/web3dart
MIT License
170 stars 94 forks source link

EthPrivateKey random creation with signature inconsistencies #104

Open yangricardo opened 1 year ago

yangricardo commented 1 year ago

Hi, I am developing a digital signature app based on ECDSA with Ethereum. After several tests i noted that the random keys creation may be inconsistent by validating the signatures generated. As a fix to my implementation i only return the EthPrivateKey after verify that some signature is valid. The main error i faced during validation belongs to this assert line

image

https://github.com/xclud/web3dart/blob/9701d8bdbb3b0ad35844d55c5889e6c41781056c/lib/src/crypto/formatting.dart#L52

xclud commented 1 year ago

Hi, Thanks or reporting.

What do you mean by "inconsistent"? Do you get a signature not matching the public/private key?

yangricardo commented 1 year ago

Hi, thanks for look this issue. Yes, i have implemented a flow of bytes signature. Sometimes, i generate a key pair and when try to verify a signauture from it using by calling ecRecover(messageHash, signature) the assert call from unsignedIntToBytes throws a error...

To avoid this i only return key pairs that i can validate effectivelly its signature, but i am not sure if the library error is on random EhtPrivateKey.createRandom or at ecRecover...

Here some snippets:

import 'dart:convert';
import 'dart:typed_data';
import 'dart:math'; 
import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';
import 'package:flutter/material.dart';
// ignore: implementation_imports
import 'package:web3dart/src/utils/typed_data.dart';

Wallet createRandomWallet(String password) {
    try {
      final Random random = Random.secure();
      final EthPrivateKey privateKey = EthPrivateKey.createRandom(random);
      final Wallet wallet = Wallet.createNew(privateKey, password, random);
      debugPrint(
          "Wallet Private Key ${bytesToHex(wallet.privateKey.privateKey, include0x: true)}");
      debugPrint("Wallet Public Address ${wallet.privateKey.address.hex}");
      final signatureTestBytes =
          sign(wallet.privateKey, privateKey.address.hex);
      debugPrint(
          "Signature Test ${bytesToHex(signatureTestBytes, include0x: true)}");
      final signatureValidation = isValidHexSignatureFromRawMessage(
          bytesToHex(signatureTestBytes, include0x: true),
          privateKey.address.hex,
          privateKey.address.hex);
      if (!signatureValidation) {
        throw Exception("Signature Validation Failed");
      }
      return wallet;
    } catch (e) {
      return createRandomWallet(password);
    }
  }

  Wallet openWalletFromJSON(String encodedJsonWallet, String password) {
    return Wallet.fromJson(encodedJsonWallet, password);
  }

  EthPrivateKey fromPrivateKeyHex(String privateKeyHex) {
    final privateKeyBytes = hexToBytes(strip0x(privateKeyHex));
    final privateKey = EthPrivateKey.fromHex(bytesToHex(privateKeyBytes));
    debugPrint(
        "Private Key ${bytesToHex(privateKey.privateKey, include0x: true)}");
    debugPrint("Public Address ${privateKey.address.hex}");
    return privateKey;
  }

  Uint8List strToBytes(String message) {
    return Uint8List.fromList(message.codeUnits);
  }

  String bytesToStr(Uint8List message) {
    return String.fromCharCodes(message);
  }

  Uint8List sign(EthPrivateKey privateKey, String message) {
    try {
      final messageBytes = strToBytes(message);
      final signature = privateKey.signPersonalMessageToUint8List(messageBytes);
      final signatureHex = bytesToHex(signature, include0x: true);
      debugPrint("signature: $signatureHex");
      return signature;
    } catch (e) {
      return sign(privateKey, message);
    }
  }

  Uint8List buildPrefixedMessage(String message) {
    final Uint8List payload = strToBytes(message);
    const messagePrefix = '\u0019Ethereum Signed Message:\n';
    final prefix = messagePrefix + payload.length.toString();
    final prefixBytes = ascii.encode(prefix);

    // will be a Uint8List, see the documentation of Uint8List.+
    final prefixedMessage = uint8ListFromList(prefixBytes + payload);
    debugPrint(
        "prefixedMessage: ${bytesToHex(prefixedMessage, include0x: true)}");
    return prefixedMessage;
  }

  Uint8List buildPrefixedMessageHash(String message) {
    final Uint8List prefixedMessage = buildPrefixedMessage(message);
    final Uint8List messageHash = keccak256(prefixedMessage);
    debugPrint("messageHash: ${bytesToHex(messageHash, include0x: true)}");
    return messageHash;
  }

  MsgSignature hexToMsgSignature(String signatureHex) {
    final signatureBytes = hexToBytes(strip0x(signatureHex));
    final r = Uint8List.sublistView(signatureBytes, 0, 32);
    final s = Uint8List.sublistView(signatureBytes, 32, 64);
    final v = signatureBytes[64];
    debugPrint(
        "r: ${bytesToHex(r, include0x: true)} s: ${bytesToHex(s, include0x: true)} v: ${v.toRadixString(16)}");
    return MsgSignature(bytesToInt(r.toList()), bytesToInt(s.toList()), v);
  }

  MsgSignature signatureBytesToMsgSignature(Uint8List signatureBytes) {
    return hexToMsgSignature(bytesToHex(signatureBytes, include0x: true));
  }

  EthereumAddress recoverSignatureAddress(
      MsgSignature signature, Uint8List messageHash) {
    final recoveredPublicKey = ecRecover(messageHash, signature);
    EthereumAddress recoveredAddress =
        EthereumAddress.fromPublicKey(recoveredPublicKey);
    debugPrint("recoveredAddress: ${recoveredAddress.hex}");
    return recoveredAddress;
  }

  bool isValidHexSignatureFromRawMessage(
      String hexSignature, String message, String hexAddress) {
    final signature = hexToMsgSignature(hexSignature);
    final messageHash = buildPrefixedMessageHash(message);
    final recoveredAddress = recoverSignatureAddress(signature, messageHash);

    final comparedToAddess = EthereumAddress.fromHex(hexAddress);
    final isValid = recoveredAddress == comparedToAddess;
    debugPrint(
        "isValid: $isValid recoveredAddress: ${recoveredAddress.hex} comparedToAddess: ${comparedToAddess.hex}");
    return isValid;
  }

void main(List<String> args) {
    createRandomWallet("test-password");
}