Wallet.fromJson constructor
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);
}