How NEP-6 Wallet Files Store Account Keys and Encryption Methods
NEP-6 wallet files store account keys as NEP-2 encrypted strings using scrypt key derivation and AES-256-ECB encryption, with parameters defined in the wallet's JSON structure.
NEP-6 is the standard wallet format used by the Neo blockchain to persist user accounts in human-readable JSON files. According to the neo-project/neo source code, these NEP-6 wallet files implement a specific encryption pipeline that protects private keys while allowing watch-only accounts to exist without sensitive data.
NEP-6 Wallet File Structure and Key Storage
JSON Schema and Account Array
NEP-6 wallet files are simple JSON documents that contain a top-level accounts array. Each object in this array represents a single Neo account with properties for the address, label, and encryption status. The wallet also stores global scrypt parameters that control the key derivation function used across all encrypted accounts in the file.
The Key Property and NEP-2 Strings
Each account entry contains a key property that serves as the storage mechanism for private key data:
null— Indicates a watch-only account with no private key stored- NEP-2 string — A Base58Check-encoded encrypted private key when the account is password-protected
In src/Neo/Wallets/NEP6/NEP6Account.cs, the NEP6Account class stores this string in the private field nep2key and only decrypts it on demand when GetKey() or GetKey(string password) is called. The resulting KeyPair is cached after a successful password check to avoid repeated decryption operations.
NEP-2 Encryption Algorithm and Parameters
Scrypt Key Derivation
When encrypting or decrypting a private key, the wallet uses the NEP-2 algorithm with scrypt-based key derivation. The process begins with Scrypt.Generate(passphrase, addresshash, N, r, p, 64) to derive a 64-byte key from the user's password and the account's address hash.
The wallet's ScryptParameters define the computational cost factors stored in the JSON file's scrypt section. Default values used by the Neo implementation are:
- N: 16384 (iteration count)
- r: 8 (block size)
- p: 8 (parallelization factor)
These parameters are passed to the decryption routine in src/Neo/Wallets/Wallet.cs via the GetPrivateKeyFromNEP2 method.
AES-256-ECB Encryption and XOR Operations
The NEP-2 decryption pipeline implemented in Wallet.GetPrivateKeyFromNEP2 follows this sequence:
- Split the derived key: The 64-byte scrypt output divides into
derivedhalf1(first 32 bytes) andderivedhalf2(last 32 bytes) - AES decryption: The encrypted private key (32 bytes) decrypts using AES-256-ECB with
derivedhalf2as the encryption key - XOR operation: The decrypted result is XOR-ed with
derivedhalf1to obtain the clear private key
The same routine handles encryption when creating new accounts, performing the inverse operations to generate the NEP-2 string stored in the wallet file.
Implementation in the Neo Source Code
NEP6Account.cs and Lazy Decryption
The NEP6Account class in src/Neo/Wallets/NEP6/NEP6Account.cs encapsulates the lazy decryption pattern. It maintains the encrypted nep2key string internally and only materializes the KeyPair when explicitly requested through GetKey() methods. This design ensures that private keys remain encrypted in memory until the moment they are needed for signing operations.
Wallet.cs and GetPrivateKeyFromNEP2
The core cryptographic operations reside in src/Neo/Wallets/Wallet.cs. The GetPrivateKeyFromNEP2 method implements both overloads of the NEP-2 decryption algorithm, handling the scrypt key derivation, AES-256-ECB decryption, and XOR operations required to recover the private key from the encrypted string.
NEP6Wallet.cs and Password Verification
The NEP6Wallet class in src/Neo/Wallets/NEP6/NEP6Wallet.cs manages the wallet file's JSON structure and password validation. The VerifyPasswordInternal method decrypts the first available encrypted account to validate that the provided password can successfully unlock the stored keys, using the scrypt parameters defined in the wallet's configuration.
Practical Code Examples
Load an Existing NEP-6 Wallet and Retrieve a Decrypted Private Key
using Neo.Wallets.NEP6;
using Neo;
// Open the wallet (read-only if password is null)
var wallet = NEP6Wallet.Open("mywallet.json", "myPassword", ProtocolSettings.Default);
// Find the first account that has a key
var account = wallet.GetAccounts().FirstOrDefault(a => a.HasKey);
if (account != null)
{
// Decrypt the key (NEP-2 → KeyPair)
KeyPair key = account.GetKey()!; // uses the wallet's stored password
Console.WriteLine($"Public key: {key.PublicKey}");
}
GetKey() internally calls wallet.DecryptKey(nep2key) which uses Wallet.GetPrivateKeyFromNEP2.
Add a New Password-Protected Account
using Neo.Wallets.NEP6;
using Neo;
// Create a new wallet (will generate a fresh scrypt config)
var wallet = new NEP6Wallet("newwallet.json", "strongPass", ProtocolSettings.Default);
// Generate a fresh key pair
byte[] privateKey = new byte[32];
RandomNumberGenerator.Fill(privateKey);
var keyPair = new KeyPair(privateKey);
// Create the account – the constructor encrypts the private key to NEP-2
var account = wallet.CreateAccount(privateKey);
wallet.Save(); // persists the JSON with the encrypted "key" field
Change Wallet Password (Re-Encrypts All NEP-2 Strings)
wallet.ChangePassword("oldPass", "newPass");
wallet.Save(); // writes the newly encrypted keys
ChangePassword calls NEP6Account.ChangePasswordPrepare for each account, which re-exports the private key with the new password using KeyPair.Export (internally NEP-2).
Summary
- NEP-6 wallet files are JSON documents that store account metadata and encrypted private keys in an
accountsarray. - Account keys are stored as NEP-2 encrypted strings in the
keyproperty, ornullfor watch-only accounts. - Encryption uses scrypt key derivation (default: N=16384, r=8, p=8) followed by AES-256-ECB decryption and XOR operations.
- Implementation resides in
NEP6Account.csfor lazy decryption,Wallet.csfor cryptographic operations, andNEP6Wallet.csfor file management. - Password changes trigger re-encryption of all account keys using the new passphrase while maintaining the same NEP-2 format.
Frequently Asked Questions
What is the difference between NEP-6 and NEP-2 in Neo wallets?
NEP-2 defines the encryption standard for individual private keys, specifying how a single key is encrypted using scrypt and AES-256-ECB. NEP-6 defines the wallet file format—a JSON structure that can hold multiple accounts, each potentially containing a NEP-2 encrypted key string, along with metadata like labels, contracts, and scrypt parameters.
How does the wallet verify that a password is correct without decrypting all accounts?
The NEP6Wallet.VerifyPasswordInternal method decrypts only the first available encrypted account (or any single account with a key) using the provided password. If the scrypt-derived key successfully decrypts the NEP-2 string and produces a valid private key matching the account's public address, the password is validated without needing to process every account in the wallet.
Can I import a NEP-6 wallet without knowing the password?
Yes, but only in read-only mode. If you open a NEP6Wallet with a null password, you can view account addresses, labels, and balances, but the key field remains encrypted and the GetKey() method will return null. You cannot sign transactions or access the private key without the correct password to decrypt the NEP-2 strings.
What happens when I change the password on a NEP-6 wallet?
When you call wallet.ChangePassword(oldPass, newPass), the wallet iterates through all accounts and calls NEP6Account.ChangePasswordPrepare, which decrypts each private key using the old password and re-encrypts it using the new password via KeyPair.Export. The wallet then saves the updated JSON file with all accounts containing newly encrypted NEP-2 strings derived from the new passphrase, while the underlying scrypt parameters remain unchanged.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →