Commit bf06398b authored by Jeremy Bokobza's avatar Jeremy Bokobza

Regrouped the accounts for different coins under the same wallet

parent da338538
...@@ -20,7 +20,7 @@ namespace Breeze.Api.Tests ...@@ -20,7 +20,7 @@ namespace Breeze.Api.Tests
{ {
Mnemonic mnemonic = new Mnemonic(Wordlist.English, WordCount.Twelve); Mnemonic mnemonic = new Mnemonic(Wordlist.English, WordCount.Twelve);
var mockWalletCreate = new Mock<IWalletManager>(); var mockWalletCreate = new Mock<IWalletManager>();
mockWalletCreate.Setup(wallet => wallet.CreateWallet(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), null, CoinType.Bitcoin)).Returns(mnemonic); mockWalletCreate.Setup(wallet => wallet.CreateWallet(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), null)).Returns(mnemonic);
var controller = new WalletController(mockWalletCreate.Object); var controller = new WalletController(mockWalletCreate.Object);
...@@ -50,7 +50,7 @@ namespace Breeze.Api.Tests ...@@ -50,7 +50,7 @@ namespace Breeze.Api.Tests
}; };
var mockWalletWrapper = new Mock<IWalletManager>(); var mockWalletWrapper = new Mock<IWalletManager>();
mockWalletWrapper.Setup(w => w.RecoverWallet(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), null, CoinType.Bitcoin, null)).Returns(wallet); mockWalletWrapper.Setup(w => w.RecoverWallet(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), null, null)).Returns(wallet);
var controller = new WalletController(mockWalletWrapper.Object); var controller = new WalletController(mockWalletWrapper.Object);
......
...@@ -241,7 +241,7 @@ ...@@ -241,7 +241,7 @@
], ],
"body": { "body": {
"mode": "raw", "mode": "raw",
"raw": "{\n\t\"walletName\": \"myFirstWallet\",\n\t\"accountName\": \"account one\",\n\t\"password\": \"123456\"\n}" "raw": "{\n\t\"walletName\": \"myFirstWallet\",\n\t\"accountName\": \"account one\",\n\t\"password\": \"123456\",\n\t\"coinType\": 105\n}"
}, },
"description": "" "description": ""
}, },
...@@ -261,7 +261,7 @@ ...@@ -261,7 +261,7 @@
], ],
"body": { "body": {
"mode": "raw", "mode": "raw",
"raw": "{\n\t\"walletName\": \"myFirstWallet\",\n\t\"accountName\": \"account one\"\n}" "raw": "{\n\t\"walletName\": \"myFirstWallet\",\n\t\"accountName\": \"account one\",\n\t\"coinType\": 0\n}"
}, },
"description": "" "description": ""
}, },
......
...@@ -328,7 +328,7 @@ namespace Breeze.Wallet.Controllers ...@@ -328,7 +328,7 @@ namespace Breeze.Wallet.Controllers
try try
{ {
var result = this.walletManager.CreateNewAccount(request.WalletName, request.AccountName, request.Password); var result = this.walletManager.CreateNewAccount(request.WalletName, request.CoinType, request.AccountName, request.Password);
return this.Json(result); return this.Json(result);
} }
catch (Exception e) catch (Exception e)
...@@ -354,7 +354,7 @@ namespace Breeze.Wallet.Controllers ...@@ -354,7 +354,7 @@ namespace Breeze.Wallet.Controllers
try try
{ {
var result = this.walletManager.CreateNewAddress(request.WalletName, request.AccountName); var result = this.walletManager.CreateNewAddress(request.WalletName, request.CoinType, request.AccountName);
return this.Json(result); return this.Json(result);
} }
catch (Exception e) catch (Exception e)
......
...@@ -17,9 +17,8 @@ namespace Breeze.Wallet ...@@ -17,9 +17,8 @@ namespace Breeze.Wallet
/// <param name="name">The name of the wallet.</param> /// <param name="name">The name of the wallet.</param>
/// <param name="network">The network this wallet is for.</param> /// <param name="network">The network this wallet is for.</param>
/// <param name="passphrase">The passphrase used in the seed.</param> /// <param name="passphrase">The passphrase used in the seed.</param>
/// <param name="coinType">The type of coin this wallet will contain.</param>
/// <returns>A mnemonic defining the wallet's seed used to generate addresses.</returns> /// <returns>A mnemonic defining the wallet's seed used to generate addresses.</returns>
Mnemonic CreateWallet(string password, string folderPath, string name, string network, string passphrase = null, CoinType coinType = CoinType.Bitcoin); Mnemonic CreateWallet(string password, string folderPath, string name, string network, string passphrase = null);
/// <summary> /// <summary>
/// Loads a wallet from a file. /// Loads a wallet from a file.
...@@ -39,10 +38,9 @@ namespace Breeze.Wallet ...@@ -39,10 +38,9 @@ namespace Breeze.Wallet
/// <param name="network">The network in which to creae this wallet</param> /// <param name="network">The network in which to creae this wallet</param>
/// <param name="mnemonic">The user's mnemonic for the wallet.</param> /// <param name="mnemonic">The user's mnemonic for the wallet.</param>
/// <param name="passphrase">The passphrase used in the seed.</param> /// <param name="passphrase">The passphrase used in the seed.</param>
/// <param name="coinType">The type of coin this wallet will contain.</param>
/// <param name="creationTime">The time this wallet was created.</param> /// <param name="creationTime">The time this wallet was created.</param>
/// <returns>The recovered wallet.</returns> /// <returns>The recovered wallet.</returns>
Wallet RecoverWallet(string password, string folderPath, string name, string network, string mnemonic, string passphrase = null, CoinType coinType = CoinType.Bitcoin, DateTimeOffset? creationTime = null); Wallet RecoverWallet(string password, string folderPath, string name, string network, string mnemonic, string passphrase = null, DateTimeOffset? creationTime = null);
/// <summary> /// <summary>
/// Deleted a wallet. /// Deleted a wallet.
...@@ -54,6 +52,7 @@ namespace Breeze.Wallet ...@@ -54,6 +52,7 @@ namespace Breeze.Wallet
/// Creates a new account. /// Creates a new account.
/// </summary> /// </summary>
/// <param name="walletName">The name of the wallet in which this account will be created.</param> /// <param name="walletName">The name of the wallet in which this account will be created.</param>
/// <param name="coinType">the type of coin for which to create an account.</param>
/// <param name="accountName">The name by which this account will be identified.</param> /// <param name="accountName">The name by which this account will be identified.</param>
/// <param name="password">The password used to decrypt the private key.</param> /// <param name="password">The password used to decrypt the private key.</param>
/// <remarks> /// <remarks>
...@@ -61,15 +60,16 @@ namespace Breeze.Wallet ...@@ -61,15 +60,16 @@ namespace Breeze.Wallet
/// at index (i - 1) contains transactions. /// at index (i - 1) contains transactions.
/// </remarks> /// </remarks>
/// <returns>The name of the new account.</returns> /// <returns>The name of the new account.</returns>
string CreateNewAccount(string walletName, string accountName, string password); string CreateNewAccount(string walletName, CoinType coinType, string accountName, string password);
/// <summary> /// <summary>
/// Creates the new address. /// Creates the new address.
/// </summary> /// </summary>
/// <param name="walletName">The name of the wallet in which this address will be created.</param> /// <param name="walletName">The name of the wallet in which this address will be created.</param>
/// <param name="coinType">the type of coin for which to create an account.</param>
/// <param name="accountName">The name of the account in which this address will be created.</param> /// <param name="accountName">The name of the account in which this address will be created.</param>
/// <returns>The new address, in Base58 format.</returns> /// <returns>The new address, in Base58 format.</returns>
string CreateNewAddress(string walletName, string accountName); string CreateNewAddress(string walletName, CoinType coinType, string accountName);
WalletGeneralInfoModel GetGeneralInfo(string walletName); WalletGeneralInfoModel GetGeneralInfo(string walletName);
......
...@@ -13,6 +13,12 @@ namespace Breeze.Wallet.Models ...@@ -13,6 +13,12 @@ namespace Breeze.Wallet.Models
[Required] [Required]
public string WalletName { get; set; } public string WalletName { get; set; }
/// <summary>
/// The type of coin this account contains.
/// </summary>
[Required]
public CoinType CoinType { get; set; }
/// <summary> /// <summary>
/// The name of the account. /// The name of the account.
/// </summary> /// </summary>
......
...@@ -13,6 +13,12 @@ namespace Breeze.Wallet.Models ...@@ -13,6 +13,12 @@ namespace Breeze.Wallet.Models
[Required] [Required]
public string WalletName { get; set; } public string WalletName { get; set; }
/// <summary>
/// The type of coin this account contains.
/// </summary>
[Required]
public CoinType CoinType { get; set; }
/// <summary> /// <summary>
/// The name of the account in which to create the address. /// The name of the account in which to create the address.
/// </summary> /// </summary>
......
...@@ -51,6 +51,18 @@ namespace Breeze.Wallet ...@@ -51,6 +51,18 @@ namespace Breeze.Wallet
[JsonProperty(PropertyName = "walletFilePath")] [JsonProperty(PropertyName = "walletFilePath")]
public string WalletFilePath { get; set; } public string WalletFilePath { get; set; }
/// <summary>
/// The root of the accounts tree.
/// </summary>
[JsonProperty(PropertyName = "accountsRoot")]
public IEnumerable<AccountRoot> AccountsRoot { get; set; }
}
/// <summary>
/// The root for the accounts for any type of coins.
/// </summary>
public class AccountRoot
{
/// <summary> /// <summary>
/// The type of coin, Bitcoin or Stratis. /// The type of coin, Bitcoin or Stratis.
/// </summary> /// </summary>
......
...@@ -23,7 +23,7 @@ namespace Breeze.Wallet ...@@ -23,7 +23,7 @@ namespace Breeze.Wallet
} }
/// <inheritdoc /> /// <inheritdoc />
public Mnemonic CreateWallet(string password, string folderPath, string name, string network, string passphrase = null, CoinType coinType = CoinType.Bitcoin) public Mnemonic CreateWallet(string password, string folderPath, string name, string network, string passphrase = null)
{ {
// for now the passphrase is set to be the password by default. // for now the passphrase is set to be the password by default.
if (passphrase == null) if (passphrase == null)
...@@ -37,7 +37,7 @@ namespace Breeze.Wallet ...@@ -37,7 +37,7 @@ namespace Breeze.Wallet
ExtKey extendedKey = mnemonic.DeriveExtKey(passphrase); ExtKey extendedKey = mnemonic.DeriveExtKey(passphrase);
// create a wallet file // create a wallet file
Wallet wallet = this.GenerateWalletFile(password, folderPath, name, WalletHelpers.GetNetwork(network), extendedKey, coinType); Wallet wallet = this.GenerateWalletFile(password, folderPath, name, WalletHelpers.GetNetwork(network), extendedKey);
this.Load(wallet); this.Load(wallet);
return mnemonic; return mnemonic;
...@@ -59,7 +59,7 @@ namespace Breeze.Wallet ...@@ -59,7 +59,7 @@ namespace Breeze.Wallet
} }
/// <inheritdoc /> /// <inheritdoc />
public Wallet RecoverWallet(string password, string folderPath, string name, string network, string mnemonic, string passphrase = null, CoinType coinType = CoinType.Bitcoin, DateTimeOffset? creationTime = null) public Wallet RecoverWallet(string password, string folderPath, string name, string network, string mnemonic, string passphrase = null, DateTimeOffset? creationTime = null)
{ {
// for now the passphrase is set to be the password by default. // for now the passphrase is set to be the password by default.
if (passphrase == null) if (passphrase == null)
...@@ -71,35 +71,37 @@ namespace Breeze.Wallet ...@@ -71,35 +71,37 @@ namespace Breeze.Wallet
ExtKey extendedKey = (new Mnemonic(mnemonic)).DeriveExtKey(passphrase); ExtKey extendedKey = (new Mnemonic(mnemonic)).DeriveExtKey(passphrase);
// create a wallet file // create a wallet file
Wallet wallet = this.GenerateWalletFile(password, folderPath, name, WalletHelpers.GetNetwork(network), extendedKey, coinType, creationTime); Wallet wallet = this.GenerateWalletFile(password, folderPath, name, WalletHelpers.GetNetwork(network), extendedKey, creationTime);
this.Load(wallet); this.Load(wallet);
return wallet; return wallet;
} }
/// <inheritdoc /> /// <inheritdoc />
public string CreateNewAccount(string walletName, string accountName, string password) public string CreateNewAccount(string walletName, CoinType coinType, string accountName, string password)
{ {
Wallet wallet = this.Wallets.SingleOrDefault(w => w.Name == walletName); Wallet wallet = this.Wallets.SingleOrDefault(w => w.Name == walletName);
if (wallet == null) if (wallet == null)
{ {
throw new Exception($"No wallet with name {walletName} could be found."); throw new Exception($"No wallet with name {walletName} could be found.");
} }
// get the accounts for this type of coin
var accounts = wallet.AccountsRoot.Single(a => a.CoinType == coinType).Accounts.ToList();
int newAccountIndex = 0; int newAccountIndex = 0;
// validate account creation // validate account creation
if (wallet.Accounts.Any()) if (accounts.Any())
{ {
// check account with same name doesn't already exists // check account with same name doesn't already exists
if (wallet.Accounts.Any(a => a.Name == accountName)) if (accounts.Any(a => a.Name == accountName))
{ {
throw new Exception($"Account with name '{accountName}' already exists in '{walletName}'."); throw new Exception($"Account with name '{accountName}' already exists in '{walletName}'.");
} }
// check account at index i - 1 contains transactions. // check account at index i - 1 contains transactions.
int lastAccountIndex = wallet.Accounts.Max(a => a.Index); int lastAccountIndex = accounts.Max(a => a.Index);
HdAccount previousAccount = wallet.Accounts.Single(a => a.Index == lastAccountIndex); HdAccount previousAccount = accounts.Single(a => a.Index == lastAccountIndex);
if (!previousAccount.ExternalAddresses.Any(addresses => addresses.Transactions.Any()) && !previousAccount.InternalAddresses.Any(addresses => addresses.Transactions.Any())) if (!previousAccount.ExternalAddresses.Any(addresses => addresses.Transactions.Any()) && !previousAccount.InternalAddresses.Any(addresses => addresses.Transactions.Any()))
{ {
throw new Exception($"Cannot create new account '{accountName}' in '{walletName}' if the previous account '{previousAccount.Name}' has not been used."); throw new Exception($"Cannot create new account '{accountName}' in '{walletName}' if the previous account '{previousAccount.Name}' has not been used.");
...@@ -111,11 +113,11 @@ namespace Breeze.Wallet ...@@ -111,11 +113,11 @@ namespace Breeze.Wallet
// get the extended pub key used to generate addresses for this account // get the extended pub key used to generate addresses for this account
var privateKey = Key.Parse(wallet.EncryptedSeed, password, wallet.Network); var privateKey = Key.Parse(wallet.EncryptedSeed, password, wallet.Network);
var seedExtKey = new ExtKey(privateKey, wallet.ChainCode); var seedExtKey = new ExtKey(privateKey, wallet.ChainCode);
KeyPath keyPath = new KeyPath($"m/44'/{(int)wallet.CoinType}'/{newAccountIndex}'"); KeyPath keyPath = new KeyPath($"m/44'/{(int)coinType}'/{newAccountIndex}'");
var accountExtKey = seedExtKey.Derive(keyPath); ExtKey accountExtKey = seedExtKey.Derive(keyPath);
ExtPubKey accountExtPubKey = accountExtKey.Neuter(); ExtPubKey accountExtPubKey = accountExtKey.Neuter();
wallet.Accounts = wallet.Accounts.Concat(new[] {new HdAccount accounts.Add(new HdAccount
{ {
Index = newAccountIndex, Index = newAccountIndex,
ExtendedPubKey = accountExtPubKey.ToString(wallet.Network), ExtendedPubKey = accountExtPubKey.ToString(wallet.Network),
...@@ -123,15 +125,16 @@ namespace Breeze.Wallet ...@@ -123,15 +125,16 @@ namespace Breeze.Wallet
InternalAddresses = new List<HdAddress>(), InternalAddresses = new List<HdAddress>(),
Name = accountName, Name = accountName,
CreationTime = DateTimeOffset.Now CreationTime = DateTimeOffset.Now
}}); });
wallet.AccountsRoot.Single(a => a.CoinType == coinType).Accounts = accounts;
this.SaveToFile(wallet); this.SaveToFile(wallet);
return accountName; return accountName;
} }
/// <inheritdoc /> /// <inheritdoc />
public string CreateNewAddress(string walletName, string accountName) public string CreateNewAddress(string walletName, CoinType coinType, string accountName)
{ {
Wallet wallet = this.Wallets.SingleOrDefault(w => w.Name == walletName); Wallet wallet = this.Wallets.SingleOrDefault(w => w.Name == walletName);
if (wallet == null) if (wallet == null)
...@@ -139,7 +142,8 @@ namespace Breeze.Wallet ...@@ -139,7 +142,8 @@ namespace Breeze.Wallet
throw new Exception($"No wallet with name {walletName} could be found."); throw new Exception($"No wallet with name {walletName} could be found.");
} }
HdAccount account = wallet.Accounts.SingleOrDefault(a => a.Name == accountName); // get the account
HdAccount account = wallet.AccountsRoot.Single(a => a.CoinType == coinType).Accounts.SingleOrDefault(a => a.Name == accountName);
if (account == null) if (account == null)
{ {
throw new Exception($"No account with name {accountName} could be found."); throw new Exception($"No account with name {accountName} could be found.");
...@@ -160,7 +164,7 @@ namespace Breeze.Wallet ...@@ -160,7 +164,7 @@ namespace Breeze.Wallet
newAddressIndex = lastAddressIndex + 1; newAddressIndex = lastAddressIndex + 1;
} }
// generate new receiving address // generate new receiving address
BitcoinPubKeyAddress address = this.GenerateAddress(account.ExtendedPubKey, newAddressIndex, false, wallet.Network); BitcoinPubKeyAddress address = this.GenerateAddress(account.ExtendedPubKey, newAddressIndex, false, wallet.Network);
...@@ -168,7 +172,7 @@ namespace Breeze.Wallet ...@@ -168,7 +172,7 @@ namespace Breeze.Wallet
account.ExternalAddresses = account.ExternalAddresses.Concat(new[] {new HdAddress account.ExternalAddresses = account.ExternalAddresses.Concat(new[] {new HdAddress
{ {
Index = newAddressIndex, Index = newAddressIndex,
HdPath = CreateBip44Path(wallet.CoinType, account.Index, newAddressIndex, false), HdPath = CreateBip44Path(coinType, account.Index, newAddressIndex, false),
ScriptPubKey = address.ScriptPubKey, ScriptPubKey = address.ScriptPubKey,
Address = address.ToString(), Address = address.ToString(),
Transactions = new List<TransactionData>(), Transactions = new List<TransactionData>(),
...@@ -230,7 +234,7 @@ namespace Breeze.Wallet ...@@ -230,7 +234,7 @@ namespace Breeze.Wallet
/// <param name="creationTime">The time this wallet was created.</param> /// <param name="creationTime">The time this wallet was created.</param>
/// <returns></returns> /// <returns></returns>
/// <exception cref="System.NotSupportedException"></exception> /// <exception cref="System.NotSupportedException"></exception>
private Wallet GenerateWalletFile(string password, string folderPath, string name, Network network, ExtKey extendedKey, CoinType coinType = CoinType.Bitcoin, DateTimeOffset? creationTime = null) private Wallet GenerateWalletFile(string password, string folderPath, string name, Network network, ExtKey extendedKey, DateTimeOffset? creationTime = null)
{ {
string walletFilePath = Path.Combine(folderPath, $"{name}.json"); string walletFilePath = Path.Combine(folderPath, $"{name}.json");
...@@ -241,11 +245,12 @@ namespace Breeze.Wallet ...@@ -241,11 +245,12 @@ namespace Breeze.Wallet
{ {
Name = name, Name = name,
EncryptedSeed = extendedKey.PrivateKey.GetEncryptedBitcoinSecret(password, network).ToWif(), EncryptedSeed = extendedKey.PrivateKey.GetEncryptedBitcoinSecret(password, network).ToWif(),
ChainCode = extendedKey.ChainCode, ChainCode = extendedKey.ChainCode,
CreationTime = creationTime ?? DateTimeOffset.Now, CreationTime = creationTime ?? DateTimeOffset.Now,
Network = network, Network = network,
Accounts = new List<HdAccount>(), AccountsRoot = new List<AccountRoot> {
CoinType = coinType, new AccountRoot { Accounts = new List<HdAccount>(), CoinType = CoinType.Bitcoin },
new AccountRoot { Accounts = new List<HdAccount>(), CoinType = CoinType.Stratis} },
WalletFilePath = walletFilePath WalletFilePath = walletFilePath
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment