Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
B
Breeze
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
3
Issues
3
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
DeStream-public
Breeze
Commits
0952f0cc
Commit
0952f0cc
authored
May 09, 2017
by
Pieterjan Vanhoof
Committed by
GitHub
May 09, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #40 from bokobza/master
Wallet recovery work
parents
faaec180
2279ba4a
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
228 additions
and
125 deletions
+228
-125
ApiSpecification.md
Breeze.Documentation/ApiSpecification.md
+3
-3
Wallet.postman_collection.json
...Api.Tests/Postman requests/Wallet.postman_collection.json
+4
-24
WalletController.cs
Breeze/src/Breeze.Wallet/Controllers/WalletController.cs
+4
-4
IWalletManager.cs
Breeze/src/Breeze.Wallet/IWalletManager.cs
+32
-7
CreateAccountModel.cs
Breeze/src/Breeze.Wallet/Models/CreateAccountModel.cs
+0
-36
RequestModels.cs
Breeze/src/Breeze.Wallet/Models/RequestModels.cs
+21
-0
Wallet.cs
Breeze/src/Breeze.Wallet/Wallet.cs
+46
-1
WalletManager.cs
Breeze/src/Breeze.Wallet/WalletManager.cs
+118
-50
No files found.
Breeze.Documentation/ApiSpecification.md
View file @
0952f0cc
...
@@ -167,12 +167,12 @@ Cannot check if the password is good or not. If the password is wrong it'll reco
...
@@ -167,12 +167,12 @@ Cannot check if the password is good or not. If the password is wrong it'll reco
## DELETE /wallet - Deletes the wallet
## DELETE /wallet - Deletes the wallet
Works as expected.
Works as expected.
## POST /wallet/account - Adds an account to the wallet
## POST /wallet/account - Gets an unused account from the wallet
This endpoint will get the first account containing no transaction or will create a new account.
### Parameters
### Parameters
```
```
{
{
"walletName": "myFirstWallet",
"walletName": "myFirstWallet",
"accountName": "account one",
"password": "123456",
"password": "123456",
"coinType": 105
"coinType": 105
}
}
...
...
Breeze/src/Breeze.Api.Tests/Postman requests/Wallet.postman_collection.json
View file @
0952f0cc
...
@@ -206,29 +206,9 @@
...
@@ -206,29 +206,9 @@
"description"
:
"Gets all the wallets files stored in the default folder"
"description"
:
"Gets all the wallets files stored in the default folder"
},
},
"response"
:
[]
"response"
:
[]
},
},
{
"name"
:
"New account for non-existant wallet fails"
,
"request"
:
{
"url"
:
"http://localhost:5000/api/v1/wallet/account"
,
"method"
:
"POST"
,
"header"
:
[
{
"key"
:
"Content-Type"
,
"value"
:
"application/json"
,
"description"
:
""
}
],
"body"
:
{
"mode"
:
"raw"
,
"raw"
:
"{
\n\t\"
walletName
\"
:
\"
myFirstWallet
\"
,
\n\t\"
accountName
\"
:
\"
account one
\"\n
}"
},
"description"
:
""
},
"response"
:
[]
},
{
{
"name"
:
"
Create new account for
wallet"
,
"name"
:
"
Get unused account in
wallet"
,
"request"
:
{
"request"
:
{
"url"
:
"http://localhost:5000/api/v1/wallet/account"
,
"url"
:
"http://localhost:5000/api/v1/wallet/account"
,
"method"
:
"POST"
,
"method"
:
"POST"
,
...
@@ -241,7 +221,7 @@
...
@@ -241,7 +221,7 @@
],
],
"body"
:
{
"body"
:
{
"mode"
:
"raw"
,
"mode"
:
"raw"
,
"raw"
:
"{
\n\t\"
walletName
\"
:
\"
myFirstWallet
\"
,
\n\t\"
accountName
\"
:
\"
account one
\"
,
\n\t\"
password
\"
:
\"
123456
\"
,
\n\t\"
coinType
\"
: 105
\n
}"
"raw"
:
"{
\n\t\"
walletName
\"
:
\"
myFirstWallet
\"
,
\n\t\"
password
\"
:
\"
123456
\"
,
\n\t\"
coinType
\"
: 0
\n
}"
},
},
"description"
:
""
"description"
:
""
},
},
...
@@ -250,7 +230,7 @@
...
@@ -250,7 +230,7 @@
{
{
"name"
:
"Get unused address in wallet"
,
"name"
:
"Get unused address in wallet"
,
"request"
:
{
"request"
:
{
"url"
:
"http://localhost:5000/api/v1/wallet/address?walletName=wallet1&accountName=account
one
&coinType=0"
,
"url"
:
"http://localhost:5000/api/v1/wallet/address?walletName=wallet1&accountName=account
0
&coinType=0"
,
"method"
:
"GET"
,
"method"
:
"GET"
,
"header"
:
[
"header"
:
[
{
{
...
...
Breeze/src/Breeze.Wallet/Controllers/WalletController.cs
View file @
0952f0cc
...
@@ -121,7 +121,7 @@ namespace Breeze.Wallet.Controllers
...
@@ -121,7 +121,7 @@ namespace Breeze.Wallet.Controllers
DirectoryInfo
walletFolder
=
GetWalletFolder
(
request
.
FolderPath
);
DirectoryInfo
walletFolder
=
GetWalletFolder
(
request
.
FolderPath
);
Wallet
wallet
=
this
.
walletManager
.
RecoverWallet
(
request
.
Password
,
walletFolder
.
FullName
,
request
.
Name
,
request
.
Network
,
request
.
Mnemonic
);
Wallet
wallet
=
this
.
walletManager
.
RecoverWallet
(
request
.
Password
,
walletFolder
.
FullName
,
request
.
Name
,
request
.
Network
,
request
.
Mnemonic
);
// TODO give the tracker the date at which this wallet was originally created so that it can start syncing blocks for it
// TODO give the tracker the date at which this wallet was originally created so that it can start syncing blocks for it
return
this
.
Json
(
new
WalletModel
return
this
.
Json
(
new
WalletModel
...
@@ -355,7 +355,7 @@ namespace Breeze.Wallet.Controllers
...
@@ -355,7 +355,7 @@ namespace Breeze.Wallet.Controllers
/// <returns>An account name.</returns>
/// <returns>An account name.</returns>
[
Route
(
"account"
)]
[
Route
(
"account"
)]
[
HttpPost
]
[
HttpPost
]
public
IActionResult
CreateNewAccount
([
FromBody
]
Create
AccountModel
request
)
public
IActionResult
CreateNewAccount
([
FromBody
]
GetUnused
AccountModel
request
)
{
{
// checks the request is valid
// checks the request is valid
if
(!
this
.
ModelState
.
IsValid
)
if
(!
this
.
ModelState
.
IsValid
)
...
@@ -366,8 +366,8 @@ namespace Breeze.Wallet.Controllers
...
@@ -366,8 +366,8 @@ namespace Breeze.Wallet.Controllers
try
try
{
{
var
result
=
this
.
walletManager
.
CreateNewAccount
(
request
.
WalletName
,
request
.
CoinType
,
request
.
AccountNam
e
,
request
.
Password
);
var
result
=
this
.
walletManager
.
GetUnusedAccount
(
request
.
WalletName
,
request
.
CoinTyp
e
,
request
.
Password
);
return
this
.
Json
(
result
);
return
this
.
Json
(
result
.
Name
);
}
}
catch
(
Exception
e
)
catch
(
Exception
e
)
{
{
...
...
Breeze/src/Breeze.Wallet/IWalletManager.cs
View file @
0952f0cc
...
@@ -44,24 +44,49 @@ namespace Breeze.Wallet
...
@@ -44,24 +44,49 @@ namespace Breeze.Wallet
Wallet
RecoverWallet
(
string
password
,
string
folderPath
,
string
name
,
string
network
,
string
mnemonic
,
string
passphrase
=
null
,
DateTimeOffset
?
creationTime
=
null
);
Wallet
RecoverWallet
(
string
password
,
string
folderPath
,
string
name
,
string
network
,
string
mnemonic
,
string
passphrase
=
null
,
DateTimeOffset
?
creationTime
=
null
);
/// <summary>
/// <summary>
/// Delete
d
a wallet.
/// Delete
s
a wallet.
/// </summary>
/// </summary>
/// <param name="walletFilePath">The location of the wallet file.</param>
/// <param name="walletFilePath">The location of the wallet file.</param>
void
DeleteWallet
(
string
walletFilePath
);
void
DeleteWallet
(
string
walletFilePath
);
/// <summary>
/// Gets an account that contains no transactions.
/// </summary>
/// <param name="walletName">The name of the wallet from which to get an account.</param>
/// <param name="coinType">The type of coin for which to get an account.</param>
/// <param name="password">The password used to decrypt the private key.</param>
/// <remarks>
/// According to BIP44, an account at index (i) can only be created when the account
/// at index (i - 1) contains transactions.
/// </remarks>
/// <returns>An unused account.</returns>
HdAccount
GetUnusedAccount
(
string
walletName
,
CoinType
coinType
,
string
password
);
/// <summary>
/// Gets an account that contains no transactions.
/// </summary>
/// <param name="wallet">The wallet from which to get an account.</param>
/// <param name="coinType">The type of coin for which to get an account.</param>
/// <param name="password">The password used to decrypt the private key.</param>
/// <remarks>
/// According to BIP44, an account at index (i) can only be created when the account
/// at index (i - 1) contains transactions.
/// </remarks>
/// <returns>An unused account.</returns>
HdAccount
GetUnusedAccount
(
Wallet
wallet
,
CoinType
coinType
,
string
password
);
/// <summary>
/// <summary>
/// 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="wallet">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="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="password">The password used to decrypt the private key.</param>
/// <param name="password">The password used to decrypt the private key.</param>
/// <remarks>
/// <remarks>
/// According to BIP44, an account at index (i) can only be created when the account
/// According to BIP44, an account at index (i) can only be created when the account
/// at index (i - 1) contains transactions.
/// at index (i - 1) contains transactions.
/// </remarks>
/// </remarks>
/// <returns>The n
ame of the n
ew account.</returns>
/// <returns>The new account.</returns>
string
CreateNewAccount
(
string
walletName
,
CoinType
coinType
,
string
accountNam
e
,
string
password
);
HdAccount
CreateNewAccount
(
Wallet
wallet
,
CoinType
coinTyp
e
,
string
password
);
/// <summary>
/// <summary>
/// Gets an address that contains no transaction.
/// Gets an address that contains no transaction.
...
@@ -101,6 +126,6 @@ namespace Breeze.Wallet
...
@@ -101,6 +126,6 @@ namespace Breeze.Wallet
/// <param name="transaction">The transaction.</param>
/// <param name="transaction">The transaction.</param>
/// <param name="blockHeight">The height of the block this transaction came from. Null if it was not a transaction included in a block.</param>
/// <param name="blockHeight">The height of the block this transaction came from. Null if it was not a transaction included in a block.</param>
/// <param name="blockTime">The block time.</param>
/// <param name="blockTime">The block time.</param>
void
ProcessTransaction
(
CoinType
coinType
,
NBitcoin
.
Transaction
transaction
,
int
?
blockHeight
=
null
,
uint
?
blockTime
=
null
);
void
ProcessTransaction
(
CoinType
coinType
,
NBitcoin
.
Transaction
transaction
,
int
?
blockHeight
=
null
,
uint
?
blockTime
=
null
);
}
}
}
}
Breeze/src/Breeze.Wallet/Models/CreateAccountModel.cs
deleted
100644 → 0
View file @
faaec180
using
System
;
using
System.Collections.Generic
;
using
System.ComponentModel.DataAnnotations
;
using
System.Text
;
namespace
Breeze.Wallet.Models
{
public
class
CreateAccountModel
{
/// <summary>
/// The name of the wallet in which to create the account.
/// </summary>
[
Required
]
public
string
WalletName
{
get
;
set
;
}
/// <summary>
/// The type of coin this account contains.
/// </summary>
[
Required
]
public
CoinType
CoinType
{
get
;
set
;
}
/// <summary>
/// The name of the account.
/// </summary>
[
Required
]
public
string
AccountName
{
get
;
set
;
}
/// <summary>
/// The password for this wallet.
/// </summary>
[
Required
]
public
string
Password
{
get
;
set
;
}
}
}
Breeze/src/Breeze.Wallet/Models/RequestModels.cs
View file @
0952f0cc
...
@@ -114,4 +114,25 @@ namespace Breeze.Wallet.Models
...
@@ -114,4 +114,25 @@ namespace Breeze.Wallet.Models
[
Required
]
[
Required
]
public
string
AccountName
{
get
;
set
;
}
public
string
AccountName
{
get
;
set
;
}
}
}
public
class
GetUnusedAccountModel
{
/// <summary>
/// The name of the wallet in which to create the account.
/// </summary>
[
Required
]
public
string
WalletName
{
get
;
set
;
}
/// <summary>
/// The type of coin this account contains.
/// </summary>
[
Required
]
public
CoinType
CoinType
{
get
;
set
;
}
/// <summary>
/// The password for this wallet.
/// </summary>
[
Required
]
public
string
Password
{
get
;
set
;
}
}
}
}
Breeze/src/Breeze.Wallet/Wallet.cs
View file @
0952f0cc
using
System
;
using
System
;
using
System.Collections.Generic
;
using
System.Collections.Generic
;
using
System.Linq
;
using
Breeze.Wallet.JsonConverters
;
using
Breeze.Wallet.JsonConverters
;
using
NBitcoin
;
using
NBitcoin
;
using
NBitcoin.JsonConverters
;
using
NBitcoin.JsonConverters
;
...
@@ -80,6 +81,23 @@ namespace Breeze.Wallet
...
@@ -80,6 +81,23 @@ namespace Breeze.Wallet
/// </summary>
/// </summary>
[
JsonProperty
(
PropertyName
=
"accounts"
)]
[
JsonProperty
(
PropertyName
=
"accounts"
)]
public
IEnumerable
<
HdAccount
>
Accounts
{
get
;
set
;
}
public
IEnumerable
<
HdAccount
>
Accounts
{
get
;
set
;
}
/// <summary>
/// Gets the first account that contains no transaction.
/// </summary>
/// <returns>An unused account</returns>
public
HdAccount
GetFirstUnusedAccount
()
{
var
unusedAccounts
=
this
.
Accounts
.
Where
(
acc
=>
!
acc
.
ExternalAddresses
.
Any
()
&&
!
acc
.
InternalAddresses
.
Any
()).
ToList
();
if
(!
unusedAccounts
.
Any
())
{
return
null
;
}
// gets the unused account with the lowest index
var
index
=
unusedAccounts
.
Min
(
a
=>
a
.
Index
);
return
unusedAccounts
.
Single
(
a
=>
a
.
Index
==
index
);
}
}
}
/// <summary>
/// <summary>
...
@@ -125,7 +143,6 @@ namespace Breeze.Wallet
...
@@ -125,7 +143,6 @@ namespace Breeze.Wallet
[
JsonProperty
(
PropertyName
=
"name"
)]
[
JsonProperty
(
PropertyName
=
"name"
)]
public
string
Name
{
get
;
set
;
}
public
string
Name
{
get
;
set
;
}
/// <summary>
/// <summary>
/// A path to the account as defined in BIP44.
/// A path to the account as defined in BIP44.
/// </summary>
/// </summary>
...
@@ -156,6 +173,34 @@ namespace Breeze.Wallet
...
@@ -156,6 +173,34 @@ namespace Breeze.Wallet
/// </summary>
/// </summary>
[
JsonProperty
(
PropertyName
=
"internalAddresses"
)]
[
JsonProperty
(
PropertyName
=
"internalAddresses"
)]
public
IEnumerable
<
HdAddress
>
InternalAddresses
{
get
;
set
;
}
public
IEnumerable
<
HdAddress
>
InternalAddresses
{
get
;
set
;
}
/// <summary>
/// Gets the type of coin this account is for.
/// </summary>
/// <returns>A <see cref="CoinType"/>.</returns>
public
CoinType
GetCoinType
()
{
string
[]
pathElements
=
this
.
HdPath
.
Split
(
'/'
);
int
coinType
=
int
.
Parse
(
pathElements
[
2
].
Replace
(
"'"
,
string
.
Empty
));
return
(
CoinType
)
coinType
;
}
/// <summary>
/// Gets the first receiving address that contains no transaction.
/// </summary>
/// <returns>An unused address</returns>
public
HdAddress
GetFirstUnusedExternalAddress
()
{
var
unusedAddresses
=
this
.
ExternalAddresses
.
Where
(
acc
=>
!
acc
.
Transactions
.
Any
()).
ToList
();
if
(!
unusedAddresses
.
Any
())
{
return
null
;
}
// gets the unused address with the lowest index
var
index
=
unusedAddresses
.
Min
(
a
=>
a
.
Index
);
return
unusedAddresses
.
Single
(
a
=>
a
.
Index
==
index
);
}
}
}
/// <summary>
/// <summary>
...
...
Breeze/src/Breeze.Wallet/WalletManager.cs
View file @
0952f0cc
...
@@ -18,10 +18,14 @@ namespace Breeze.Wallet
...
@@ -18,10 +18,14 @@ namespace Breeze.Wallet
{
{
public
List
<
Wallet
>
Wallets
{
get
;
}
public
List
<
Wallet
>
Wallets
{
get
;
}
public
HashSet
<
Script
>
PubKeys
{
get
;
}
public
HashSet
<
Script
>
PubKeys
{
get
;
set
;
}
public
HashSet
<
TransactionDetails
>
TrackedTransactions
{
get
;
}
public
HashSet
<
TransactionDetails
>
TrackedTransactions
{
get
;
}
private
const
int
UnusedAddressesBuffer
=
20
;
private
const
int
WalletRecoveryAccountsCreationCount
=
3
;
public
WalletManager
()
public
WalletManager
()
{
{
this
.
Wallets
=
new
List
<
Wallet
>();
this
.
Wallets
=
new
List
<
Wallet
>();
...
@@ -53,7 +57,6 @@ namespace Breeze.Wallet
...
@@ -53,7 +57,6 @@ 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
);
Wallet
wallet
=
this
.
GenerateWalletFile
(
password
,
folderPath
,
name
,
WalletHelpers
.
GetNetwork
(
network
),
extendedKey
);
this
.
Load
(
wallet
);
this
.
Load
(
wallet
);
...
@@ -84,15 +87,28 @@ namespace Breeze.Wallet
...
@@ -84,15 +87,28 @@ namespace Breeze.Wallet
// generate the root seed used to generate keys
// generate the root seed used to generate keys
ExtKey
extendedKey
=
(
new
Mnemonic
(
mnemonic
)).
DeriveExtKey
(
passphrase
);
ExtKey
extendedKey
=
(
new
Mnemonic
(
mnemonic
)).
DeriveExtKey
(
passphrase
);
Network
coinNetwork
=
WalletHelpers
.
GetNetwork
(
network
);
// create a wallet file
// create a wallet file
Wallet
wallet
=
this
.
GenerateWalletFile
(
password
,
folderPath
,
name
,
WalletHelpers
.
GetNetwork
(
network
)
,
extendedKey
,
creationTime
);
Wallet
wallet
=
this
.
GenerateWalletFile
(
password
,
folderPath
,
name
,
coinNetwork
,
extendedKey
,
creationTime
);
// generate multiple accounts and addresses from the get-go
for
(
int
i
=
0
;
i
<
WalletRecoveryAccountsCreationCount
;
i
++)
{
HdAccount
account
=
CreateNewAccount
(
wallet
,
CoinType
.
Bitcoin
,
password
);
this
.
CreateAddressesInAccount
(
account
,
coinNetwork
,
UnusedAddressesBuffer
);
this
.
CreateAddressesInAccount
(
account
,
coinNetwork
,
UnusedAddressesBuffer
,
true
);
}
// save the changes to the file and add addresses to be tracked
this
.
SaveToFile
(
wallet
);
this
.
PubKeys
=
this
.
LoadKeys
(
CoinType
.
Bitcoin
);
this
.
Load
(
wallet
);
this
.
Load
(
wallet
);
return
wallet
;
return
wallet
;
}
}
/// <inheritdoc />
/// <inheritdoc />
public
string
CreateNewAccount
(
string
walletName
,
CoinType
coinType
,
string
accountNam
e
,
string
password
)
public
HdAccount
GetUnusedAccount
(
string
walletName
,
CoinType
coinTyp
e
,
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
)
...
@@ -100,53 +116,69 @@ namespace Breeze.Wallet
...
@@ -100,53 +116,69 @@ 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."
);
}
}
// get the accounts for this type of coin
return
this
.
GetUnusedAccount
(
wallet
,
coinType
,
password
);
var
accounts
=
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
).
Accounts
.
ToList
();
}
int
newAccountIndex
=
0
;
// validate account creation
/// <inheritdoc />
if
(
accounts
.
Any
())
public
HdAccount
GetUnusedAccount
(
Wallet
wallet
,
CoinType
coinType
,
string
password
)
{
// get the accounts root for this type of coin
var
accountsRoot
=
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
);
// check if an unused account exists
if
(
accountsRoot
.
Accounts
.
Any
())
{
{
// check account with same name doesn't already exists
// gets an unused account
if
(
accounts
.
Any
(
a
=>
a
.
Name
==
accountName
))
var
firstUnusedAccount
=
accountsRoot
.
GetFirstUnusedAccount
();
if
(
firstUnusedAccount
!=
null
)
{
{
throw
new
Exception
(
$"Account with name '
{
accountName
}
' already exists in '
{
walletName
}
'."
)
;
return
firstUnusedAccount
;
}
}
}
// check account at index i - 1 contains transactions.
// all accounts contain transactions, create a new one
int
lastAccountIndex
=
accounts
.
Max
(
a
=>
a
.
Index
);
var
newAccount
=
this
.
CreateNewAccount
(
wallet
,
coinType
,
password
);
HdAccount
previousAccount
=
accounts
.
Single
(
a
=>
a
.
Index
==
lastAccountIndex
);
if
(!
previousAccount
.
ExternalAddresses
.
Any
(
addresses
=>
addresses
.
Transactions
.
Any
())
&&
!
previousAccount
.
InternalAddresses
.
Any
(
addresses
=>
addresses
.
Transactions
.
Any
()))
// save the changes to the file
{
this
.
SaveToFile
(
wallet
);
throw
new
Exception
(
$"Cannot create new account '
{
accountName
}
' in '
{
walletName
}
' if the previous account '
{
previousAccount
.
Name
}
' has not been used."
)
;
return
newAccount
;
}
}
newAccountIndex
=
lastAccountIndex
+
1
;
/// <inheritdoc />
public
HdAccount
CreateNewAccount
(
Wallet
wallet
,
CoinType
coinType
,
string
password
)
{
// get the accounts for this type of coin
var
accounts
=
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
).
Accounts
.
ToList
();
int
newAccountIndex
=
0
;
if
(
accounts
.
Any
())
{
newAccountIndex
=
accounts
.
Max
(
a
=>
a
.
Index
)
+
1
;
}
}
// 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
);
var
accountHdPath
=
$"m/44'/
{(
int
)
coinType
}
'/
{
newAccountIndex
}
'"
;
var
accountHdPath
=
$"m/44'/
{(
int
)
coinType
}
'/
{
newAccountIndex
}
'"
;
KeyPath
keyPath
=
new
KeyPath
(
accountHdPath
);
KeyPath
keyPath
=
new
KeyPath
(
accountHdPath
);
ExtKey
accountExtKey
=
seedExtKey
.
Derive
(
keyPath
);
ExtKey
accountExtKey
=
seedExtKey
.
Derive
(
keyPath
);
ExtPubKey
accountExtPubKey
=
accountExtKey
.
Neuter
();
ExtPubKey
accountExtPubKey
=
accountExtKey
.
Neuter
();
accounts
.
Add
(
new
HdAccount
var
newAccount
=
new
HdAccount
{
{
Index
=
newAccountIndex
,
Index
=
newAccountIndex
,
ExtendedPubKey
=
accountExtPubKey
.
ToString
(
wallet
.
Network
),
ExtendedPubKey
=
accountExtPubKey
.
ToString
(
wallet
.
Network
),
ExternalAddresses
=
new
List
<
HdAddress
>(),
ExternalAddresses
=
new
List
<
HdAddress
>(),
InternalAddresses
=
new
List
<
HdAddress
>(),
InternalAddresses
=
new
List
<
HdAddress
>(),
Name
=
accountName
,
Name
=
$"account
{
newAccountIndex
}
"
,
HdPath
=
accountHdPath
,
HdPath
=
accountHdPath
,
CreationTime
=
DateTimeOffset
.
Now
CreationTime
=
DateTimeOffset
.
Now
}
)
;
};
accounts
.
Add
(
newAccount
);
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
).
Accounts
=
accounts
;
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
).
Accounts
=
accounts
;
this
.
SaveToFile
(
wallet
);
return
accountName
;
return
newAccount
;
}
}
/// <inheritdoc />
/// <inheritdoc />
...
@@ -165,42 +197,78 @@ namespace Breeze.Wallet
...
@@ -165,42 +197,78 @@ namespace Breeze.Wallet
throw
new
Exception
(
$"No account with name
{
accountName
}
could be found."
);
throw
new
Exception
(
$"No account with name
{
accountName
}
could be found."
);
}
}
int
newAddressIndex
=
0
;
// validate address creation
// validate address creation
if
(
account
.
ExternalAddresses
.
Any
())
if
(
account
.
ExternalAddresses
.
Any
())
{
{
// check last created address contains transactions.
// check last created address contains transactions.
int
lastAddressIndex
=
account
.
ExternalAddresses
.
Max
(
a
=>
a
.
Index
);
var
firstUnusedExternalAddress
=
account
.
GetFirstUnusedExternalAddress
();
var
lastAddress
=
account
.
ExternalAddresses
.
SingleOrDefault
(
a
=>
a
.
Index
==
lastAddressIndex
);
if
(
firstUnusedExternalAddress
!=
null
)
if
(
lastAddress
!=
null
&&
!
lastAddress
.
Transactions
.
Any
())
{
{
return
last
Address
.
Address
;
return
firstUnusedExternal
Address
.
Address
;
}
}
newAddressIndex
=
lastAddressIndex
+
1
;
}
}
// generate new receiving address
// creates an address
BitcoinPubKeyAddress
address
=
this
.
GenerateAddress
(
account
.
ExtendedPubKey
,
newAddressIndex
,
false
,
wallet
.
Network
);
this
.
CreateAddressesInAccount
(
account
,
wallet
.
Network
,
1
);
// add address details
account
.
ExternalAddresses
=
account
.
ExternalAddresses
.
Concat
(
new
[]
{
new
HdAddress
{
Index
=
newAddressIndex
,
HdPath
=
CreateBip44Path
(
coinType
,
account
.
Index
,
newAddressIndex
,
false
),
ScriptPubKey
=
address
.
ScriptPubKey
,
Address
=
address
.
ToString
(),
Transactions
=
new
List
<
TransactionData
>(),
CreationTime
=
DateTimeOffset
.
Now
}});
// persists the address to the wallet file
// persists the address to the wallet file
this
.
SaveToFile
(
wallet
);
this
.
SaveToFile
(
wallet
);
// adds the address to the list of tracked addresses
// adds the address to the list of tracked addresses
this
.
PubKeys
.
Add
(
address
.
ScriptPubKey
);
this
.
PubKeys
=
this
.
LoadKeys
(
coinType
);
return
address
.
ToString
();
return
account
.
GetFirstUnusedExternalAddress
().
Address
;
}
/// <summary>
/// Creates a number of addresses in the provided account.
/// </summary>
/// <param name="account">The account.</param>
/// <param name="network">The network.</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>A list of addresses in Base58.</returns>
private
List
<
string
>
CreateAddressesInAccount
(
HdAccount
account
,
Network
network
,
int
addressesQuantity
,
bool
isChange
=
false
)
{
List
<
string
>
addressesCreated
=
new
List
<
string
>();
var
addresses
=
isChange
?
account
.
InternalAddresses
:
account
.
ExternalAddresses
;
// gets the index of the last address with transactions
int
firstNewAddressIndex
=
0
;
if
(
addresses
.
Any
())
{
firstNewAddressIndex
=
addresses
.
Max
(
add
=>
add
.
Index
)
+
1
;
}
for
(
int
i
=
firstNewAddressIndex
;
i
<
firstNewAddressIndex
+
addressesQuantity
;
i
++)
{
// generate new receiving address
BitcoinPubKeyAddress
address
=
this
.
GenerateAddress
(
account
.
ExtendedPubKey
,
i
,
false
,
network
);
// add address details
addresses
=
addresses
.
Concat
(
new
[]
{
new
HdAddress
{
Index
=
i
,
HdPath
=
CreateBip44Path
(
account
.
GetCoinType
(),
account
.
Index
,
i
,
isChange
),
ScriptPubKey
=
address
.
ScriptPubKey
,
Address
=
address
.
ToString
(),
Transactions
=
new
List
<
TransactionData
>(),
CreationTime
=
DateTimeOffset
.
Now
}});
addressesCreated
.
Add
(
address
.
ToString
());
}
if
(
isChange
)
{
account
.
InternalAddresses
=
addresses
;
}
else
{
account
.
ExternalAddresses
=
addresses
;
}
return
addressesCreated
;
}
}
public
WalletGeneralInfoModel
GetGeneralInfo
(
string
name
)
public
WalletGeneralInfoModel
GetGeneralInfo
(
string
name
)
...
@@ -485,7 +553,7 @@ namespace Breeze.Wallet
...
@@ -485,7 +553,7 @@ namespace Breeze.Wallet
SelectMany
(
a
=>
a
.
ExternalAddresses
).
SelectMany
(
a
=>
a
.
ExternalAddresses
).
Select
(
s
=>
s
.
ScriptPubKey
));
Select
(
s
=>
s
.
ScriptPubKey
));
// uncomment the following for testing on a random address
// uncomment the following for testing on a random address
//
Select(t => (new BitcoinPubKeyAddress(t.Address, Network.Main)).ScriptPubKey));
//
Select(t => (new BitcoinPubKeyAddress(t.Address, Network.Main)).ScriptPubKey));
}
}
/// <summary>
/// <summary>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment