/// <inheritdoc cref="AddNewAccount(string, string, byte[], Network, DateTimeOffset)"/> /// <summary> /// Adds an account to the current account root using extended public key and account index. /// </summary> /// <param name="accountExtPubKey">The extended public key for the account.</param> /// <param name="accountIndex">The zero-based account index.</param> public HdAccount AddNewAccount(ExtPubKey accountExtPubKey, int accountIndex, Network network, DateTimeOffset accountCreationTime) { ICollection <HdAccount> hdAccounts = this.Accounts.ToList(); if (hdAccounts.Any(a => a.Index == accountIndex)) { throw new WalletException("There is already an account in this wallet with index: " + accountIndex); } if (hdAccounts.Any(x => x.ExtendedPubKey == accountExtPubKey.ToString(network))) { throw new WalletException("There is already an account in this wallet with this xpubkey: " + accountExtPubKey.ToString(network)); } string accountHdPath = HdOperations.GetAccountHdPath((int)this.CoinType, accountIndex); var newAccount = new HdAccount { Index = accountIndex, ExtendedPubKey = accountExtPubKey.ToString(network), ExternalAddresses = new List <HdAddress>(), InternalAddresses = new List <HdAddress>(), Name = $"account {accountIndex}", HdPath = accountHdPath, CreationTime = accountCreationTime }; hdAccounts.Add(newAccount); this.Accounts = hdAccounts; return(newAccount); }
/// <summary> /// Create an account for a specific account index and account name pattern. /// </summary> /// <param name="password">The password used to decrypt the wallet's encrypted seed.</param> /// <param name="encryptedSeed">The encrypted private key for this wallet.</param> /// <param name="chainCode">The chain code for this wallet.</param> /// <param name="network">The network for which this account will be created.</param> /// <param name="accountCreationTime">Creation time of the account to be created.</param> /// <param name="newAccountIndex">The optional account index to use.</param> /// <param name="newAccountName">The optional account name to use.</param> /// <returns>A new HD account.</returns> public HdAccount CreateAccount(string password, string encryptedSeed, byte[] chainCode, Network network, DateTimeOffset accountCreationTime, int newAccountIndex, string newAccountName = null) { if (string.IsNullOrEmpty(newAccountName)) { newAccountName = string.Format(Wallet.AccountNamePattern, newAccountIndex); } // Get the extended pub key used to generate addresses for this account. string accountHdPath = HdOperations.GetAccountHdPath((int)this.CoinType, newAccountIndex); Key privateKey = HdOperations.DecryptSeed(encryptedSeed, password, network); ExtPubKey accountExtPubKey = HdOperations.GetExtendedPublicKey(privateKey, chainCode, accountHdPath); return(new HdAccount { Index = newAccountIndex, ExtendedPubKey = accountExtPubKey.ToString(network), ExternalAddresses = new List <HdAddress>(), InternalAddresses = new List <HdAddress>(), Name = newAccountName, HdPath = accountHdPath, CreationTime = accountCreationTime }); }
/// <summary> /// Adds an account to the current account root using encrypted seed and password. /// </summary> /// <remarks>The name given to the account is of the form "account (i)" by default, where (i) is an incremental index starting at 0. /// According to BIP44, an account at index (i) can only be created when the account at index (i - 1) contains transactions. /// <seealso cref="https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki"/></remarks> /// <param name="password">The password used to decrypt the wallet's encrypted seed.</param> /// <param name="encryptedSeed">The encrypted private key for this wallet.</param> /// <param name="chainCode">The chain code for this wallet.</param> /// <param name="network">The network for which this account will be created.</param> /// <param name="accountCreationTime">Creation time of the account to be created.</param> /// <returns>A new HD account.</returns> public HdAccount AddNewAccount(string password, string encryptedSeed, byte[] chainCode, Network network, DateTimeOffset accountCreationTime) { Guard.NotEmpty(password, nameof(password)); Guard.NotEmpty(encryptedSeed, nameof(encryptedSeed)); Guard.NotNull(chainCode, nameof(chainCode)); int newAccountIndex = 0; ICollection <HdAccount> hdAccounts = this.Accounts.ToList(); if (hdAccounts.Any()) { newAccountIndex = hdAccounts.Max(a => a.Index) + 1; } // Get the extended pub key used to generate addresses for this account. string accountHdPath = HdOperations.GetAccountHdPath((int)this.CoinType, newAccountIndex); Key privateKey = HdOperations.DecryptSeed(encryptedSeed, password, network); ExtPubKey accountExtPubKey = HdOperations.GetExtendedPublicKey(privateKey, chainCode, accountHdPath); var newAccount = new HdAccount { Index = newAccountIndex, ExtendedPubKey = accountExtPubKey.ToString(network), ExternalAddresses = new List <HdAddress>(), InternalAddresses = new List <HdAddress>(), Name = $"account {newAccountIndex}", HdPath = accountHdPath, CreationTime = accountCreationTime }; hdAccounts.Add(newAccount); this.Accounts = hdAccounts; return(newAccount); }
/// <summary> /// Gets the extended private key for the given address. /// </summary> /// <param name="password">The password used to encrypt/decrypt sensitive info.</param> /// <param name="address">The address to get the private key for.</param> /// <returns>The extended private key.</returns> public ISecret GetExtendedPrivateKeyForAddress(string password, HdAddress address) { Guard.NotEmpty(password, nameof(password)); Guard.NotNull(address, nameof(address)); // Check if the wallet contains the address. if (!this.ContainsAddress(address)) { throw new WalletException("Address not found on wallet."); } // get extended private key Key privateKey = HdOperations.DecryptSeed(this.EncryptedSeed, password, this.Network); return(HdOperations.GetExtendedPrivateKey(privateKey, this.ChainCode, address.HdPath, this.Network)); }
/// <summary> /// Creates a number of additional addresses in the current account. /// </summary> /// <remarks> /// The name given to the account is of the form "account (i)" by default, where (i) is an incremental index starting at 0. /// According to BIP44, an account at index (i) can only be created when the account at index (i - 1) contains at least one transaction. /// </remarks> /// <param name="network">The network these addresses will be for.</param> /// <param name="addressesQuantity">The number of addresses to create.</param> /// <param name="isChange">Whether the addresses added are change (internal) addresses or receiving (external) addresses.</param> /// <returns>The created addresses.</returns> public IEnumerable <HdAddress> CreateAddresses(Network network, int addressesQuantity, bool isChange = false) { ICollection <HdAddress> addresses = isChange ? this.InternalAddresses : this.ExternalAddresses; // Get the index of the last address. int firstNewAddressIndex = 0; if (addresses.Any()) { firstNewAddressIndex = addresses.Max(add => add.Index) + 1; } var addressesCreated = new List <HdAddress>(); for (int i = firstNewAddressIndex; i < firstNewAddressIndex + addressesQuantity; i++) { // Generate a new address. PubKey pubkey = HdOperations.GeneratePublicKey(this.ExtendedPubKey, i, isChange); BitcoinPubKeyAddress address = pubkey.GetAddress(network); // Add the new address details to the list of addresses. var newAddress = new HdAddress { Index = i, HdPath = HdOperations.CreateHdPath((int)this.GetCoinType(), this.Index, isChange, i), ScriptPubKey = address.ScriptPubKey, Pubkey = pubkey.ScriptPubKey, Address = address.ToString(), Transactions = new List <TransactionData>() }; addresses.Add(newAddress); addressesCreated.Add(newAddress); } if (isChange) { this.InternalAddresses = addresses; } else { this.ExternalAddresses = addresses; } return(addressesCreated); }
/// <inheritdoc /> public Mnemonic CreateWallet(string password, string name, string passphrase = null, string mnemonicList = null) { Guard.NotEmpty(password, nameof(password)); Guard.NotEmpty(name, nameof(name)); // for now the passphrase is set to be the password by default. if (passphrase == null) { passphrase = password; } // generate the root seed used to generate keys from a mnemonic picked at random // and a passphrase optionally provided by the user Mnemonic mnemonic = string.IsNullOrEmpty(mnemonicList) ? new Mnemonic(Wordlist.English, WordCount.Twelve) : new Mnemonic(mnemonicList); ExtKey extendedKey = HdOperations.GetHdPrivateKey(mnemonic, passphrase); // create a wallet file Wallet wallet = this.GenerateWalletFile(password, name, extendedKey); // generate multiple accounts and addresses from the get-go for (int i = 0; i < WalletCreationAccountsCount; i++) { HdAccount account = wallet.AddNewAccount(password, this.coinType); account.CreateAddresses(this.network, UnusedAddressesBuffer); account.CreateAddresses(this.network, UnusedAddressesBuffer, true); } // update the height of the we start syncing from this.UpdateLastBlockSyncedHeight(wallet, this.chain.Tip); // save the changes to the file and add addresses to be tracked this.SaveToFile(wallet); this.Load(wallet); this.LoadKeysLookup(); return(mnemonic); }
/// <inheritdoc /> public Wallet RecoverWallet(string password, string name, string mnemonic, DateTime creationTime, string passphrase = null) { Guard.NotEmpty(password, nameof(password)); Guard.NotEmpty(name, nameof(name)); Guard.NotEmpty(mnemonic, nameof(mnemonic)); // for now the passphrase is set to be the password by default. if (passphrase == null) { passphrase = password; } // generate the root seed used to generate keys ExtKey extendedKey = HdOperations.GetHdPrivateKey(mnemonic, passphrase); // create a wallet file Wallet wallet = this.GenerateWalletFile(password, name, extendedKey, creationTime); // generate multiple accounts and addresses from the get-go for (int i = 0; i < WalletRecoveryAccountsCount; i++) { HdAccount account = wallet.AddNewAccount(password, this.coinType); account.CreateAddresses(this.network, UnusedAddressesBuffer); account.CreateAddresses(this.network, UnusedAddressesBuffer, true); } int blockSyncStart = this.chain.GetHeightAtTime(creationTime); this.UpdateLastBlockSyncedHeight(wallet, this.chain.GetBlock(blockSyncStart)); // save the changes to the file and add addresses to be tracked this.SaveToFile(wallet); this.Load(wallet); this.LoadKeysLookup(); return(wallet); }
/// <summary> /// Determines whether this is a change address or a receive address. /// </summary> /// <returns> /// <c>true</c> if it is a change address; otherwise, <c>false</c>. /// </returns> public bool IsChangeAddress() { return(HdOperations.IsChangeAddress(this.HdPath)); }
/// <summary> /// Gets the type of coin this account is for. /// </summary> /// <returns>A <see cref="CoinType"/>.</returns> public CoinType GetCoinType() { return((CoinType)HdOperations.GetCoinType(this.HdPath)); }