diff --git a/include/TrustWalletCore/TWStoredKey.h b/include/TrustWalletCore/TWStoredKey.h index c5b6484d3cb..cbf427a9c56 100644 --- a/include/TrustWalletCore/TWStoredKey.h +++ b/include/TrustWalletCore/TWStoredKey.h @@ -12,6 +12,7 @@ #include "TWHDWallet.h" #include "TWPrivateKey.h" #include "TWString.h" +#include "TWStoredKeyEncryptionLevel.h" TW_EXTERN_C_BEGIN @@ -35,7 +36,11 @@ struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemo TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nullable TWStoredKeyImportJSON(TWData* _Nonnull json); -/// Creates a new key. Returned object needs to be deleted. +/// Creates a new key, with given encrpytion strength level. Returned object needs to be deleted. +TW_EXPORT_STATIC_METHOD +struct TWStoredKey* _Nonnull TWStoredKeyCreateLevel(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel); + +/// DEPRECATED, use TWStoredKeyCreateLevel. Creates a new key. Returned object needs to be deleted. TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password); @@ -105,4 +110,8 @@ TWData* _Nullable TWStoredKeyExportJSON(struct TWStoredKey* _Nonnull key); TW_EXPORT_METHOD bool TWStoredKeyFixAddresses(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); +/// Retrieve stored key encoding parameters, as JSON string. +TW_EXPORT_PROPERTY +TWString* _Nullable TWStoredKeyEncryptionParameters(struct TWStoredKey* _Nonnull key); + TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWStoredKeyEncryptionLevel.h b/include/TrustWalletCore/TWStoredKeyEncryptionLevel.h new file mode 100644 index 00000000000..587d88b60c7 --- /dev/null +++ b/include/TrustWalletCore/TWStoredKeyEncryptionLevel.h @@ -0,0 +1,24 @@ +// Copyright © 2017-2021 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" + +TW_EXTERN_C_BEGIN + +/// Preset encryption parameter with different security strength, for key store +TW_EXPORT_ENUM(uint32_t) +enum TWStoredKeyEncryptionLevel { + /// Default, which is one of the below values, determined by the implementation. + TWStoredKeyEncryptionLevelDefault = 0, + /// Minimal sufficient level of encryption strength (scrypt 4096) + TWStoredKeyEncryptionLevelWeak = 1, + /// Standard level of encryption strength (scrypt 262k) + TWStoredKeyEncryptionLevelStandard = 2, +}; + +TW_EXTERN_C_END diff --git a/src/Keystore/EncryptionParameters.cpp b/src/Keystore/EncryptionParameters.cpp index ddb461d982e..406474e638a 100644 --- a/src/Keystore/EncryptionParameters.cpp +++ b/src/Keystore/EncryptionParameters.cpp @@ -28,8 +28,52 @@ static Data computeMAC(Iter begin, Iter end, const Data& key) { return Hash::keccak256(data); } -EncryptionParameters::EncryptionParameters(const Data& password, const Data& data) : mac() { - auto scryptParams = boost::get(kdfParams); +// ----------------- +// Encoding/Decoding +// ----------------- + +namespace CodingKeys { +static const auto encrypted = "ciphertext"; +static const auto cipher = "cipher"; +static const auto cipherParams = "cipherparams"; +static const auto kdf = "kdf"; +static const auto kdfParams = "kdfparams"; +static const auto mac = "mac"; +} // namespace CodingKeys + +EncryptionParameters::EncryptionParameters(const nlohmann::json& json) { + cipher = json[CodingKeys::cipher].get(); + cipherParams = AESParameters(json[CodingKeys::cipherParams]); + + auto kdf = json[CodingKeys::kdf].get(); + if (kdf == "scrypt") { + kdfParams = ScryptParameters(json[CodingKeys::kdfParams]); + } else if (kdf == "pbkdf2") { + kdfParams = PBKDF2Parameters(json[CodingKeys::kdfParams]); + } +} + +nlohmann::json EncryptionParameters::json() const { + nlohmann::json j; + j[CodingKeys::cipher] = cipher; + j[CodingKeys::cipherParams] = cipherParams.json(); + + if (kdfParams.which() == 0) { + auto scryptParams = boost::get(kdfParams); + j[CodingKeys::kdf] = "scrypt"; + j[CodingKeys::kdfParams] = scryptParams.json(); + } else if (kdfParams.which() == 1) { + auto pbkdf2Params = boost::get(kdfParams); + j[CodingKeys::kdf] = "pbkdf2"; + j[CodingKeys::kdfParams] = pbkdf2Params.json(); + } + + return j; +} + +EncryptedPayload::EncryptedPayload(const Data& password, const Data& data, const EncryptionParameters& params) : + params(std::move(params)), mac() { + auto scryptParams = boost::get(params.kdfParams); auto derivedKey = Data(scryptParams.desiredKeyLength); scrypt(reinterpret_cast(password.data()), password.size(), scryptParams.salt.data(), scryptParams.salt.size(), scryptParams.n, scryptParams.r, scryptParams.p, derivedKey.data(), @@ -39,7 +83,7 @@ EncryptionParameters::EncryptionParameters(const Data& password, const Data& dat auto result = aes_encrypt_key128(derivedKey.data(), &ctx); assert(result == EXIT_SUCCESS); if (result == EXIT_SUCCESS) { - Data iv = cipherParams.iv; + Data iv = params.cipherParams.iv; encrypted = Data(data.size()); aes_ctr_encrypt(data.data(), encrypted.data(), static_cast(data.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); @@ -47,23 +91,24 @@ EncryptionParameters::EncryptionParameters(const Data& password, const Data& dat } } -EncryptionParameters::~EncryptionParameters() { +EncryptedPayload::~EncryptedPayload() { std::fill(encrypted.begin(), encrypted.end(), 0); + std::fill(mac.begin(), mac.end(), 0); } -Data EncryptionParameters::decrypt(const Data& password) const { +Data EncryptedPayload::decrypt(const Data& password) const { auto derivedKey = Data(); auto mac = Data(); - if (kdfParams.which() == 0) { - auto scryptParams = boost::get(kdfParams); + if (params.kdfParams.which() == 0) { + auto scryptParams = boost::get(params.kdfParams); derivedKey.resize(scryptParams.defaultDesiredKeyLength); scrypt(password.data(), password.size(), scryptParams.salt.data(), scryptParams.salt.size(), scryptParams.n, scryptParams.r, scryptParams.p, derivedKey.data(), scryptParams.defaultDesiredKeyLength); mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); - } else if (kdfParams.which() == 1) { - auto pbkdf2Params = boost::get(kdfParams); + } else if (params.kdfParams.which() == 1) { + auto pbkdf2Params = boost::get(params.kdfParams); derivedKey.resize(pbkdf2Params.defaultDesiredKeyLength); pbkdf2_hmac_sha256(password.data(), static_cast(password.size()), pbkdf2Params.salt.data(), static_cast(pbkdf2Params.salt.size()), pbkdf2Params.iterations, derivedKey.data(), @@ -78,15 +123,15 @@ Data EncryptionParameters::decrypt(const Data& password) const { } Data decrypted(encrypted.size()); - Data iv = cipherParams.iv; - if (cipher == "aes-128-ctr") { + Data iv = params.cipherParams.iv; + if (params.cipher == "aes-128-ctr") { aes_encrypt_ctx ctx; auto __attribute__((unused)) result = aes_encrypt_key(derivedKey.data(), 16, &ctx); assert(result != EXIT_FAILURE); aes_ctr_decrypt(encrypted.data(), decrypted.data(), static_cast(encrypted.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); - } else if (cipher == "aes-128-cbc") { + } else if (params.cipher == "aes-128-cbc") { aes_decrypt_ctx ctx; auto __attribute__((unused)) result = aes_decrypt_key(derivedKey.data(), 16, &ctx); assert(result != EXIT_FAILURE); @@ -101,50 +146,15 @@ Data EncryptionParameters::decrypt(const Data& password) const { return decrypted; } -// ----------------- -// Encoding/Decoding -// ----------------- - -namespace CodingKeys { -static const auto encrypted = "ciphertext"; -static const auto cipher = "cipher"; -static const auto cipherParams = "cipherparams"; -static const auto kdf = "kdf"; -static const auto kdfParams = "kdfparams"; -static const auto mac = "mac"; -} // namespace CodingKeys - -EncryptionParameters::EncryptionParameters(const nlohmann::json& json) { +EncryptedPayload::EncryptedPayload(const nlohmann::json& json) { + params = EncryptionParameters(json); encrypted = parse_hex(json[CodingKeys::encrypted].get()); - cipher = json[CodingKeys::cipher].get(); - cipherParams = AESParameters(json[CodingKeys::cipherParams]); mac = parse_hex(json[CodingKeys::mac].get()); - - auto kdf = json[CodingKeys::kdf].get(); - if (kdf == "scrypt") { - kdfParams = ScryptParameters(json[CodingKeys::kdfParams]); - } else if (kdf == "pbkdf2") { - kdfParams = PBKDF2Parameters(json[CodingKeys::kdfParams]); - } } -nlohmann::json EncryptionParameters::json() const { - nlohmann::json j; +nlohmann::json EncryptedPayload::json() const { + nlohmann::json j = params.json(); j[CodingKeys::encrypted] = hex(encrypted); - j[CodingKeys::cipher] = cipher; - j[CodingKeys::cipherParams] = cipherParams.json(); j[CodingKeys::mac] = hex(mac); - - if (kdfParams.which() == 0) { - auto scryptParams = boost::get(kdfParams); - j[CodingKeys::kdf] = "scrypt"; - j[CodingKeys::kdfParams] = scryptParams.json(); - } else if (kdfParams.which() == 1) { - auto pbkdf2Params = boost::get(kdfParams); - j[CodingKeys::kdf] = "pbkdf2"; - j[CodingKeys::kdfParams] = pbkdf2Params.json(); - - } - return j; } diff --git a/src/Keystore/EncryptionParameters.h b/src/Keystore/EncryptionParameters.h index e1e68933977..1d5a70f819c 100644 --- a/src/Keystore/EncryptionParameters.h +++ b/src/Keystore/EncryptionParameters.h @@ -10,6 +10,7 @@ #include "PBKDF2Parameters.h" #include "ScryptParameters.h" #include "../Data.h" +#include #include #include @@ -17,6 +18,49 @@ namespace TW::Keystore { +/// Set of parameters used when encoding +struct EncryptionParameters { + static EncryptionParameters getPreset(enum TWStoredKeyEncryptionLevel preset) { + switch (preset) { + case TWStoredKeyEncryptionLevelWeak: + case TWStoredKeyEncryptionLevelDefault: + default: + return EncryptionParameters(AESParameters(), ScryptParameters::Light); + case TWStoredKeyEncryptionLevelStandard: + return EncryptionParameters(AESParameters(), ScryptParameters::Standard); + } + } + + /// Cipher algorithm. + std::string cipher = "aes-128-ctr"; + + /// Cipher parameters. + AESParameters cipherParams = AESParameters(); + + /// Key derivation function parameters. + boost::variant kdfParams = ScryptParameters(); + + EncryptionParameters() = default; + + /// Initializes with standard values. + EncryptionParameters(AESParameters cipherParams, boost::variant kdfParams) + : cipherParams(std::move(cipherParams)) + , kdfParams(std::move(kdfParams)) {} + + /// Initializes with a JSON object. + EncryptionParameters(const nlohmann::json& json); + + /// Saves `this` as a JSON object. + nlohmann::json json() const; + + EncryptionParameters(const EncryptionParameters& other) = default; + EncryptionParameters(EncryptionParameters&& other) = default; + EncryptionParameters& operator=(const EncryptionParameters& other) = default; + EncryptionParameters& operator=(EncryptionParameters&& other) = default; + + virtual ~EncryptionParameters() = default; +}; + /// Errors thrown when decrypting a key. enum class DecryptionError { unsupportedKDF, @@ -27,37 +71,31 @@ enum class DecryptionError { invalidPassword, }; -struct EncryptionParameters { +/// An encrypted payload data +struct EncryptedPayload { +public: + EncryptionParameters params; + /// Encrypted data. Data encrypted; - /// Cipher algorithm. - std::string cipher = "aes-128-ctr"; - - /// Cipher parameters. - AESParameters cipherParams = AESParameters(); - - /// Key derivation function parameters. - boost::variant kdfParams = ScryptParameters(); - /// Message authentication code. Data mac; - EncryptionParameters() = default; + EncryptedPayload() = default; - /// Initializes `EncryptionParameters` with standard values. - EncryptionParameters(const Data& encrypted, AESParameters cipherParams, boost::variant kdfParams, const Data& mac) - : encrypted(std::move(encrypted)) - , cipherParams(std::move(cipherParams)) - , kdfParams(std::move(kdfParams)) + /// Initializes with standard values. + EncryptedPayload(const EncryptionParameters& params, const Data& encrypted, const Data& mac) + : params(std::move(params)) + , encrypted(std::move(encrypted)) , mac(std::move(mac)) {} - /// Initializes `EncryptionParameters` by encrypting data with a password + /// Initializes by encrypting data with a password /// using standard values. - EncryptionParameters(const Data& password, const Data& data); + EncryptedPayload(const Data& password, const Data& data, const EncryptionParameters& params); - /// Initializes `EncryptionParameters` with a JSON object. - EncryptionParameters(const nlohmann::json& json); + /// Initializes with a JSON object. + EncryptedPayload(const nlohmann::json& json); /// Decrypts the payload with the given password. Data decrypt(const Data& password) const; @@ -65,12 +103,12 @@ struct EncryptionParameters { /// Saves `this` as a JSON object. nlohmann::json json() const; - EncryptionParameters(const EncryptionParameters& other) = default; - EncryptionParameters(EncryptionParameters&& other) = default; - EncryptionParameters& operator=(const EncryptionParameters& other) = default; - EncryptionParameters& operator=(EncryptionParameters&& other) = default; + EncryptedPayload(const EncryptedPayload& other) = default; + EncryptedPayload(EncryptedPayload&& other) = default; + EncryptedPayload& operator=(const EncryptedPayload& other) = default; + EncryptedPayload& operator=(EncryptedPayload&& other) = default; - virtual ~EncryptionParameters(); + virtual ~EncryptedPayload(); }; } // namespace TW::Keystore diff --git a/src/Keystore/ScryptParameters.cpp b/src/Keystore/ScryptParameters.cpp index 16ccd489c4c..8c89afc8f47 100644 --- a/src/Keystore/ScryptParameters.cpp +++ b/src/Keystore/ScryptParameters.cpp @@ -12,6 +12,10 @@ using namespace TW; using namespace TW::Keystore; +ScryptParameters ScryptParameters::Light = ScryptParameters(Data(), lightN, defaultR, lightP, defaultDesiredKeyLength); + +ScryptParameters ScryptParameters::Standard = ScryptParameters(Data(), standardN, defaultR, standardP, defaultDesiredKeyLength); + ScryptParameters::ScryptParameters() : salt(32) { random_buffer(salt.data(), salt.size()); } diff --git a/src/Keystore/ScryptParameters.h b/src/Keystore/ScryptParameters.h index 10e7c019bd6..f98807693a3 100644 --- a/src/Keystore/ScryptParameters.h +++ b/src/Keystore/ScryptParameters.h @@ -23,6 +23,10 @@ enum class ScryptValidationError { /// Scrypt function parameters. struct ScryptParameters { + static ScryptParameters Light; + + static ScryptParameters Standard; + /// The N parameter of Scrypt encryption algorithm, using 256MB memory and /// taking approximately 1s CPU time on a modern processor. static const uint32_t standardN = 1 << 18; diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index ee7b5d9dcc5..3272305b4a6 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -27,27 +27,27 @@ using namespace TW; using namespace TW::Keystore; -StoredKey StoredKey::createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic) { +StoredKey StoredKey::createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic, TWStoredKeyEncryptionLevel encryptionLevel) { if (!Mnemonic::isValid(mnemonic)) { throw std::invalid_argument("Invalid mnemonic"); } Data mnemonicData = TW::Data(mnemonic.begin(), mnemonic.end()); - StoredKey key = StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData); + StoredKey key = StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData, encryptionLevel); return key; } -StoredKey StoredKey::createWithMnemonicRandom(const std::string& name, const Data& password) { +StoredKey StoredKey::createWithMnemonicRandom(const std::string& name, const Data& password, TWStoredKeyEncryptionLevel encryptionLevel) { const auto wallet = TW::HDWallet(128, ""); const auto& mnemonic = wallet.getMnemonic(); assert(Mnemonic::isValid(mnemonic)); Data mnemonicData = TW::Data(mnemonic.begin(), mnemonic.end()); - StoredKey key = StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData); + StoredKey key = StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData, encryptionLevel); return key; } StoredKey StoredKey::createWithMnemonicAddDefaultAddress(const std::string& name, const Data& password, const std::string& mnemonic, TWCoinType coin) { - StoredKey key = createWithMnemonic(name, password, mnemonic); + StoredKey key = createWithMnemonic(name, password, mnemonic, TWStoredKeyEncryptionLevelDefault); const auto wallet = HDWallet(mnemonic, ""); const auto derivationPath = TW::derivationPath(coin); @@ -59,7 +59,7 @@ StoredKey StoredKey::createWithMnemonicAddDefaultAddress(const std::string& name } StoredKey StoredKey::createWithPrivateKey(const std::string& name, const Data& password, const Data& privateKeyData) { - StoredKey key = StoredKey(StoredKeyType::privateKey, name, password, privateKeyData); + StoredKey key = StoredKey(StoredKeyType::privateKey, name, password, privateKeyData, TWStoredKeyEncryptionLevelDefault); return key; } @@ -78,8 +78,10 @@ StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& na return key; } -StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data) - : type(type), id(), name(std::move(name)), payload(password, data), accounts() { +StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel) + : type(type), id(), name(std::move(name)), accounts() { + const auto encryptionParams = EncryptionParameters::getPreset(encryptionLevel); + payload = EncryptedPayload(password, data, encryptionParams); boost::uuids::random_generator gen; id = boost::lexical_cast(gen()); } @@ -225,10 +227,10 @@ void StoredKey::loadJson(const nlohmann::json& json) { } if (json.count(CodingKeys::crypto) != 0) { - payload = EncryptionParameters(json[CodingKeys::crypto]); + payload = EncryptedPayload(json[CodingKeys::crypto]); } else if (json.count(UppercaseCodingKeys::crypto) != 0) { // Workaround for myEtherWallet files - payload = EncryptionParameters(json[UppercaseCodingKeys::crypto]); + payload = EncryptedPayload(json[UppercaseCodingKeys::crypto]); } else { throw DecryptionError::invalidKeyFile; } diff --git a/src/Keystore/StoredKey.h b/src/Keystore/StoredKey.h index 4a019c5b5b3..bc156658653 100644 --- a/src/Keystore/StoredKey.h +++ b/src/Keystore/StoredKey.h @@ -36,18 +36,18 @@ class StoredKey { std::string name; /// Encrypted payload. - EncryptionParameters payload; + EncryptedPayload payload; /// Active accounts. std::vector accounts; /// Create a new StoredKey, with the given name, mnemonic and password. /// @throws std::invalid_argument if mnemonic is invalid - static StoredKey createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic); + static StoredKey createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic, TWStoredKeyEncryptionLevel encryptionLevel); /// Create a new StoredKey, with the given name, mnemonic and password. /// @throws std::invalid_argument if mnemonic is invalid - static StoredKey createWithMnemonicRandom(const std::string& name, const Data& password); + static StoredKey createWithMnemonicRandom(const std::string& name, const Data& password, TWStoredKeyEncryptionLevel encryptionLevel); /// Create a new StoredKey, with the given name, mnemonic and password, and also add the default address for the given coin.. /// @throws std::invalid_argument if mnemonic is invalid @@ -119,7 +119,7 @@ class StoredKey { /// Initializes a `StoredKey` with a type, an encryption password, and unencrypted data. /// This contstructor will encrypt the provided data with default encryption /// parameters. - StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data); + StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel); }; } // namespace TW::Keystore diff --git a/src/interface/TWStoredKey.cpp b/src/interface/TWStoredKey.cpp index cb4625a40c7..b5b86031f4f 100644 --- a/src/interface/TWStoredKey.cpp +++ b/src/interface/TWStoredKey.cpp @@ -25,10 +25,14 @@ struct TWStoredKey* _Nullable TWStoredKeyLoad(TWString* _Nonnull path) { } } -struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password) { +struct TWStoredKey* _Nonnull TWStoredKeyCreateLevel(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel) { const auto& nameString = *reinterpret_cast(name); const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); - return new TWStoredKey{ StoredKey::createWithMnemonicRandom(nameString, passwordData) }; + return new TWStoredKey{ StoredKey::createWithMnemonicRandom(nameString, passwordData, encryptionLevel) }; +} + +struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password) { + return TWStoredKeyCreateLevel(name, password, TWStoredKeyEncryptionLevelDefault); } struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin) { @@ -178,3 +182,11 @@ bool TWStoredKeyFixAddresses(struct TWStoredKey* _Nonnull key, TWData* _Nonnull return false; } } + +TWString* _Nullable TWStoredKeyEncryptionParameters(struct TWStoredKey* _Nonnull key) { + if (!key->impl.id) { + return nullptr; + } + const std::string params = key->impl.payload.json().dump(); + return TWStringCreateWithUTF8Bytes(params.c_str()); +} diff --git a/swift/Tests/Keystore/KeystoreKeyTests.swift b/swift/Tests/Keystore/KeystoreKeyTests.swift index dafa208495b..b33659ceaef 100755 --- a/swift/Tests/Keystore/KeystoreKeyTests.swift +++ b/swift/Tests/Keystore/KeystoreKeyTests.swift @@ -119,4 +119,25 @@ class KeystoreKeyTests: XCTestCase { let data = keystore.decryptPrivateKey(password: password) XCTAssertEqual(data?.hexString, "4357b2f9a6150ba969bc52f01c98cce5313595fe49f2d08303759c73e5c7a46c") } + + struct KdfParams: Decodable { + let dklen: Int + let n: Int + } + + struct EncryptionParameters: Decodable { + let kdf: String + let kdfparams: KdfParams + } + + func testEncryptionParameters() { + let url = Bundle(for: type(of: self)).url(forResource: "key", withExtension: "json")! + let key = StoredKey.load(path: url.path)! + + let paramsData = key.encryptionParameters!.data(using: .utf8)! + let params = try! JSONDecoder().decode(EncryptionParameters.self, from: paramsData) + + XCTAssertEqual(params.kdf, "scrypt"); + XCTAssertEqual(params.kdfparams.n, 262144); + } } diff --git a/tests/Keystore/StoredKeyTests.cpp b/tests/Keystore/StoredKeyTests.cpp index 84266df0ccd..41b72e82e7a 100644 --- a/tests/Keystore/StoredKeyTests.cpp +++ b/tests/Keystore/StoredKeyTests.cpp @@ -31,7 +31,7 @@ const TWCoinType coinTypeEth = TWCoinTypeEthereum; const TWCoinType coinTypeBscLegacy = TWCoinTypeSmartChainLegacy; TEST(StoredKey, CreateWithMnemonic) { - auto key = StoredKey::createWithMnemonic("name", password, mnemonic); + auto key = StoredKey::createWithMnemonic("name", password, mnemonic, TWStoredKeyEncryptionLevelDefault); EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); const Data& mnemo2Data = key.payload.decrypt(password); EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(mnemonic)); @@ -46,7 +46,7 @@ TEST(StoredKey, CreateWithMnemonic) { TEST(StoredKey, CreateWithMnemonicInvalid) { try { - auto key = StoredKey::createWithMnemonic("name", password, "_THIS_IS_NOT_A_VALID_MNEMONIC_"); + auto key = StoredKey::createWithMnemonic("name", password, "_THIS_IS_NOT_A_VALID_MNEMONIC_", TWStoredKeyEncryptionLevelDefault); } catch (std::invalid_argument&) { // expedcted exception OK return; @@ -55,7 +55,7 @@ TEST(StoredKey, CreateWithMnemonicInvalid) { } TEST(StoredKey, CreateWithMnemonicRandom) { - const auto key = StoredKey::createWithMnemonicRandom("name", password); + const auto key = StoredKey::createWithMnemonicRandom("name", password, TWStoredKeyEncryptionLevelDefault); EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); // random mnemonic: check only length and validity const Data& mnemo2Data = key.payload.decrypt(password); @@ -102,7 +102,7 @@ TEST(StoredKey, CreateWithPrivateKeyAddDefaultAddressInvalid) { } TEST(StoredKey, AccountGetCreate) { - auto key = StoredKey::createWithMnemonic("name", password, mnemonic); + auto key = StoredKey::createWithMnemonic("name", password, mnemonic, TWStoredKeyEncryptionLevelDefault); EXPECT_EQ(key.accounts.size(), 0); // not exists @@ -140,7 +140,7 @@ TEST(StoredKey, AccountGetCreate) { } TEST(StoredKey, AccountGetDoesntChange) { - auto key = StoredKey::createWithMnemonic("name", password, mnemonic); + auto key = StoredKey::createWithMnemonic("name", password, mnemonic, TWStoredKeyEncryptionLevelDefault); auto wallet = key.wallet(password); EXPECT_EQ(key.accounts.size(), 0); @@ -164,7 +164,7 @@ TEST(StoredKey, AccountGetDoesntChange) { } TEST(StoredKey, AddRemoveAccount) { - auto key = StoredKey::createWithMnemonic("name", password, mnemonic); + auto key = StoredKey::createWithMnemonic("name", password, mnemonic, TWStoredKeyEncryptionLevelDefault); EXPECT_EQ(key.accounts.size(), 0); { @@ -195,7 +195,7 @@ TEST(StoredKey, AddRemoveAccount) { TEST(StoredKey, FixAddress) { { - auto key = StoredKey::createWithMnemonic("name", password, mnemonic); + auto key = StoredKey::createWithMnemonic("name", password, mnemonic, TWStoredKeyEncryptionLevelDefault); key.fixAddresses(password); } { @@ -243,10 +243,10 @@ TEST(StoredKey, LoadPBKDF2Key) { EXPECT_EQ(key.id, "3198bc9c-6672-5ab3-d995-4942343ae5b6"); const auto& payload = key.payload; - ASSERT_TRUE(payload.kdfParams.which() == 1); - EXPECT_EQ(boost::get(payload.kdfParams).desiredKeyLength, 32); - EXPECT_EQ(boost::get(payload.kdfParams).iterations, 262144); - EXPECT_EQ(hex(boost::get(payload.kdfParams).salt), "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"); + ASSERT_TRUE(payload.params.kdfParams.which() == 1); + EXPECT_EQ(boost::get(payload.params.kdfParams).desiredKeyLength, 32); + EXPECT_EQ(boost::get(payload.params.kdfParams).iterations, 262144); + EXPECT_EQ(hex(boost::get(payload.params.kdfParams).salt), "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"); EXPECT_EQ(hex(payload.decrypt(TW::data("testpassword"))), "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"); } @@ -287,17 +287,17 @@ TEST(StoredKey, ReadWallet) { const auto header = key.payload; - EXPECT_EQ(header.cipher, "aes-128-ctr"); + EXPECT_EQ(header.params.cipher, "aes-128-ctr"); EXPECT_EQ(hex(header.encrypted), "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c"); EXPECT_EQ(hex(header.mac), "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"); - EXPECT_EQ(hex(header.cipherParams.iv), "83dbcc02d8ccb40e466191a123791e0e"); - - ASSERT_TRUE(header.kdfParams.which() == 0); - EXPECT_EQ(boost::get(header.kdfParams).desiredKeyLength, 32); - EXPECT_EQ(boost::get(header.kdfParams).n, 262144); - EXPECT_EQ(boost::get(header.kdfParams).p, 8); - EXPECT_EQ(boost::get(header.kdfParams).r, 1); - EXPECT_EQ(hex(boost::get(header.kdfParams).salt), "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"); + EXPECT_EQ(hex(header.params.cipherParams.iv), "83dbcc02d8ccb40e466191a123791e0e"); + + ASSERT_TRUE(header.params.kdfParams.which() == 0); + EXPECT_EQ(boost::get(header.params.kdfParams).desiredKeyLength, 32); + EXPECT_EQ(boost::get(header.params.kdfParams).n, 262144); + EXPECT_EQ(boost::get(header.params.kdfParams).p, 8); + EXPECT_EQ(boost::get(header.params.kdfParams).r, 1); + EXPECT_EQ(hex(boost::get(header.params.kdfParams).salt), "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"); } TEST(StoredKey, ReadMyEtherWallet) { @@ -333,7 +333,7 @@ TEST(StoredKey, CreateWallet) { TEST(StoredKey, CreateAccounts) { string mnemonicPhrase = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; - auto key = StoredKey::createWithMnemonic("name", password, mnemonicPhrase); + auto key = StoredKey::createWithMnemonic("name", password, mnemonicPhrase, TWStoredKeyEncryptionLevelDefault); const auto wallet = key.wallet(password); EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->address, "0x494f60cb6Ac2c8F5E1393aD9FdBdF4Ad589507F7"); @@ -383,4 +383,40 @@ TEST(StoredKey, EtherWalletAddressNo0x) { EXPECT_EQ(key.account(TWCoinTypeEthereum, nullptr)->address, "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309"); } +TEST(StoredKey, CreateWeakEncryptionParameters) { + const auto key = StoredKey::createWithMnemonic("name", password, mnemonic, TWStoredKeyEncryptionLevelWeak); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + const Data& mnemo2Data = key.payload.decrypt(password); + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(mnemonic)); + EXPECT_EQ(key.accounts.size(), 0); + EXPECT_EQ(key.wallet(password).getMnemonic(), string(mnemonic)); + + const auto json = key.json(); + + EXPECT_EQ(json["crypto"]["kdf"], "scrypt"); + EXPECT_EQ(json["crypto"]["kdfparams"]["n"], 4096); + + // load it back + const auto key2 = StoredKey::createWithJson(json); + EXPECT_EQ(key2.wallet(password).getMnemonic(), string(mnemonic)); +} + +TEST(StoredKey, CreateStandardEncryptionParameters) { + const auto key = StoredKey::createWithMnemonic("name", password, mnemonic, TWStoredKeyEncryptionLevelStandard); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + const Data& mnemo2Data = key.payload.decrypt(password); + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(mnemonic)); + EXPECT_EQ(key.accounts.size(), 0); + EXPECT_EQ(key.wallet(password).getMnemonic(), string(mnemonic)); + + const auto json = key.json(); + + EXPECT_EQ(json["crypto"]["kdf"], "scrypt"); + EXPECT_EQ(json["crypto"]["kdfparams"]["n"], 262144); + + // load it back + const auto key2 = StoredKey::createWithJson(json); + EXPECT_EQ(key2.wallet(password).getMnemonic(), string(mnemonic)); +} + } // namespace TW::Keystore diff --git a/tests/interface/TWStoredKeyTests.cpp b/tests/interface/TWStoredKeyTests.cpp index ff498e4f6cb..f2f47e09a6c 100644 --- a/tests/interface/TWStoredKeyTests.cpp +++ b/tests/interface/TWStoredKeyTests.cpp @@ -14,6 +14,7 @@ #include "../src/HexCoding.h" #include +#include #include @@ -56,7 +57,7 @@ TEST(TWStoredKey, createWallet) { const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); - const auto key = WRAP(TWStoredKey, TWStoredKeyCreate(name.get(), password.get())); + const auto key = WRAP(TWStoredKey, TWStoredKeyCreateLevel(name.get(), password.get(), TWStoredKeyEncryptionLevelDefault)); const auto name2 = WRAPS(TWStoredKeyName(key.get())); EXPECT_EQ(string(TWStringUTF8Bytes(name2.get())), "name"); const auto mnemonic = WRAPS(TWStoredKeyDecryptMnemonic(key.get(), password.get())); @@ -221,7 +222,41 @@ TEST(TWStoredKey, getWalletPasswordInvalid) { const auto invalidString = WRAPS(TWStringCreateWithUTF8Bytes("_THIS_IS_INVALID_PASSWORD_")); const auto passwordInvalid = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(invalidString.get())), TWStringSize(invalidString.get()))); - auto key = WRAP(TWStoredKey, TWStoredKeyCreate (name.get(), password.get())); + auto key = WRAP(TWStoredKey, TWStoredKeyCreate(name.get(), password.get())); ASSERT_NE(WRAP(TWHDWallet, TWStoredKeyWallet(key.get(), password.get())).get(), nullptr); ASSERT_EQ(WRAP(TWHDWallet, TWStoredKeyWallet(key.get(), passwordInvalid.get())).get(), nullptr); } + +TEST(TWStoredKey, encryptionParameters) { + const auto key = createDefaultStoredKey(); + const auto params = WRAPS(TWStoredKeyEncryptionParameters(key.get())); + + nlohmann::json jsonParams = nlohmann::json::parse(string(TWStringUTF8Bytes(params.get()))); + + // compare some specific parameters + EXPECT_EQ(jsonParams["kdfparams"]["n"], 4096); + EXPECT_EQ(std::string(jsonParams["cipherparams"]["iv"]).length(), 32); + + // compare all keys, except dynamic ones (like cipherparams/iv) + jsonParams["cipherparams"] = {}; + jsonParams["ciphertext"] = ""; + jsonParams["kdfparams"]["salt"] = ""; + jsonParams["mac"] = ""; + const auto params2 = jsonParams.dump(); + assertJSONEqual(params2, R"( + { + "cipher": "aes-128-ctr", + "cipherparams": null, + "ciphertext": "", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 4096, + "p": 6, + "r": 8, + "salt": "" + }, + "mac": "" + } + )"); +}