Wallet.fromJson constructor

Wallet.fromJson(
  1. String encoded,
  2. String password
)

Reads and unlocks the wallet denoted in the json string given with the specified password. encoded must be the String contents of a valid v3 Core wallet file.

Implementation

factory Wallet.fromJson(String encoded, String password) {
  /*
    In order to read the wallet and obtain the secret key stored in it, we
    need to do the following:
    1: Key Derivation: Based on the key derivator specified (either pbdkdf2 or
       scryt), we need to use the password to obtain the aes key used to
       decrypt the private key.
    2: Using the obtained aes key and the iv parameter, decrypt the private
       key stored in the wallet.
  */

  final decoded = json.decode(encoded);
  if (decoded is! Map<String, dynamic>) {
    throw const FormatException('Wallet must be a JSON object');
  }
  final data = decoded;

  // Ensure version is 3, only version that we support at the moment
  final version = data['version'];
  if (version != 3) {
    throw ArgumentError.value(
      version,
      'version',
      'Library only supports '
          'version 3 of wallet files at the moment. However, the following value'
          ' has been given:',
    );
  }

  final cryptoValue = data['crypto'] ?? data['Crypto'];
  if (cryptoValue is! Map<String, dynamic>) {
    throw const FormatException('Wallet crypto data is missing or invalid');
  }
  final crypto = cryptoValue;

  final kdf = _requiredString(crypto, 'kdf');
  _KeyDerivator derivator;

  switch (kdf) {
    case 'pbkdf2':
      final derParams = _requiredMap(crypto, 'kdfparams');

      if (derParams['prf'] != 'hmac-sha256') {
        throw ArgumentError(
          'Invalid prf supplied with the pdf: was ${derParams["prf"]}, expected hmac-sha256',
        );
      }

      final iterations = _requiredInt(derParams, 'c');
      final dklen = _requiredInt(derParams, 'dklen');
      _validatePbkdf2Parameters(dklen, iterations);
      derivator = _PBDKDF2KeyDerivator(
        iterations,
        _decodeHex(_requiredString(derParams, 'salt'), 'salt'),
        dklen,
      );

      break;
    case 'scrypt':
      final derParams = _requiredMap(crypto, 'kdfparams');
      final dklen = _requiredInt(derParams, 'dklen');
      final n = _requiredInt(derParams, 'n');
      final r = _requiredInt(derParams, 'r');
      final p = _requiredInt(derParams, 'p');
      _validateScryptParameters(dklen, n, r, p);
      derivator = _ScryptKeyDerivator(
        dklen,
        n,
        r,
        p,
        _decodeHex(_requiredString(derParams, 'salt'), 'salt'),
      );
      break;
    default:
      throw ArgumentError(
        'Wallet file uses $kdf as key derivation function, which is not supported.',
      );
  }

  // Now that we have the derivator, let's obtain the aes key:
  final encodedPassword = Uint8List.fromList(utf8.encode(password));
  final derivedKey = derivator.deriveKey(encodedPassword);
  final aesKey = Uint8List.fromList(derivedKey.sublist(0, 16));

  final encryptedPrivateKey = _decodeHex(
    _requiredString(crypto, 'ciphertext'),
    'ciphertext',
  );

  // Validate the derived key without leaking where the MAC differs.
  final derivedMac = _decodeHex(
    _generateMac(derivedKey, encryptedPrivateKey),
    'derivedMac',
  );
  final expectedMac = _decodeHex(_requiredString(crypto, 'mac'), 'mac');
  if (!_constantTimeEquals(derivedMac, expectedMac)) {
    throw ArgumentError(
      'Could not unlock wallet file. You either supplied the wrong password or the file is corrupted',
    );
  }

  // We only support this mode at the moment
  if (crypto['cipher'] != 'aes-128-ctr') {
    throw ArgumentError(
      'Wallet file uses ${crypto["cipher"]} as cipher, but only aes-128-ctr is supported.',
    );
  }
  final cipherParams = _requiredMap(crypto, 'cipherparams');
  final iv = _decodeHex(_requiredString(cipherParams, 'iv'), 'iv');
  if (iv.length != 16) {
    throw const FormatException('Wallet IV must contain exactly 16 bytes');
  }

  // Decrypt the private key

  final aes = _initCipher(false, aesKey, iv);

  final privateKey = aes.process(Uint8List.fromList(encryptedPrivateKey));
  final credentials = XCBPrivateKey(privateKey);

  final id = parseUuid(data['id'] as String);

  return Wallet._(credentials, derivator, encodedPassword, iv, id);
}