diff --git a/src/AddressOwnershipTool/AddressCheckResult.cs b/src/AddressOwnershipTool/AddressCheckResult.cs new file mode 100644 index 0000000000..de5eed267c --- /dev/null +++ b/src/AddressOwnershipTool/AddressCheckResult.cs @@ -0,0 +1,15 @@ +namespace AddressOwnershipTool +{ + public class AddressCheckResult + { + public AddressCheckResult(bool hasBalance, bool hasActivity) + { + this.HasActivity = hasActivity; + this.HasBalance = hasBalance; + } + + public bool HasBalance { get; set; } + + public bool HasActivity { get; set; } + } +} diff --git a/src/AddressOwnershipTool/BlockExplorerClient.cs b/src/AddressOwnershipTool/BlockExplorerClient.cs index 227ea82885..c09d01b423 100644 --- a/src/AddressOwnershipTool/BlockExplorerClient.cs +++ b/src/AddressOwnershipTool/BlockExplorerClient.cs @@ -1,4 +1,6 @@ -using System; +using System.Linq; +using System.Collections.Generic; +using Stratis.Bitcoin.Controllers.Models; namespace AddressOwnershipTool { @@ -13,5 +15,13 @@ public bool HasBalance(string address) return balance > 0; } + + public bool HasActivity(string address) + { + var stratisApiClient = new NodeApiClient($"{ExplorerBaseUrl}api"); + List changes = stratisApiClient.GetVerboseAddressBalance(address); + + return changes.Any(); + } } } diff --git a/src/AddressOwnershipTool/LedgerService.cs b/src/AddressOwnershipTool/LedgerService.cs index d58992bb09..edc7930d10 100644 --- a/src/AddressOwnershipTool/LedgerService.cs +++ b/src/AddressOwnershipTool/LedgerService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -16,6 +17,8 @@ public class LedgerService private readonly BlockExplorerClient blockExplorerClient; + private const int maximumInactiveAddresses = 20; + public LedgerService(bool testnet) { this.network = testnet ? new StratisTest() : new StratisMain(); @@ -26,33 +29,44 @@ public async Task ExportAddressesAsync(int numberOfAddressesToScan, string desti { LedgerClient ledger = (await LedgerClient.GetHIDLedgersAsync()).First(); - var numberOfIterations = string.IsNullOrEmpty(keyPath) ? numberOfAddressesToScan : 1; - - // m / purpose' / coin_type' / account' / change / address_index - for (int index = 0; index < numberOfIterations; index++) + if (!string.IsNullOrEmpty(keyPath)) { - var key = new KeyPath(string.IsNullOrEmpty(keyPath) ? $"m/44'/105'/0'/0/{index}" : keyPath); + await ProcessAddressAsync(ledger, keyPath, ignoreBalance, destinationAddress); + return; + } + + bool foundInactiveAccount = false; - GetWalletPubKeyResponse walletPubKey = await ledger.GetWalletPubKeyAsync(key); + for (int accountIndex = 0; !foundInactiveAccount; accountIndex++) + { + var addressChecks = new List(); - PubKey pubKey = walletPubKey.ExtendedPublicKey.PubKey; - var address = walletPubKey.Address; + Console.WriteLine($"Checking addresses for m/44'/105'/{accountIndex}"); - if (!ignoreBalance) + for (int addressIndex = 0; addressIndex < numberOfAddressesToScan; addressIndex++) { - Console.WriteLine($"Checking balance for {address}"); - if (!this.blockExplorerClient.HasBalance(address)) - continue; + var currentKeyPath = $"m/44'/105'/{accountIndex}'/0/{addressIndex}"; + + AddressCheckResult addressCheckResult = await this.ProcessAddressAsync(ledger, currentKeyPath, ignoreBalance, destinationAddress); + addressChecks.Add(addressCheckResult); + + if (addressIndex == maximumInactiveAddresses - 1 && addressChecks.All(a => !a.HasActivity)) + { + foundInactiveAccount = true; + break; + } } - - await ledger.PrepareMessage(key, address); - var resp = await ledger.SignMessage(); - - var signature = GetSignature(address, resp, pubKey); - if (signature == null) + + if (foundInactiveAccount) continue; - this.OutputToFile(address, destinationAddress, signature); + // Now scan all change addresses if account was active + for (int addressIndex = 0; addressIndex < numberOfAddressesToScan; addressIndex++) + { + var currentKeyPath = $"m/44'/105'/{accountIndex}'/1/{addressIndex}"; + + await this.ProcessAddressAsync(ledger, currentKeyPath, ignoreBalance, destinationAddress); + } } } @@ -68,6 +82,41 @@ public void OutputToFile(string address, string destinationAddress, string signa } } + private async Task ProcessAddressAsync(LedgerClient ledger, string keyPath, bool ignoreBalance, string destinationAddress) + { + var result = new AddressCheckResult(false, false); + var key = new KeyPath(keyPath); + + GetWalletPubKeyResponse walletPubKey = await ledger.GetWalletPubKeyAsync(key); + + PubKey pubKey = walletPubKey.ExtendedPublicKey.PubKey; + var address = walletPubKey.Address; + + var hasActivity = this.blockExplorerClient.HasActivity(address); + result.HasActivity = hasActivity; + + if (!ignoreBalance) + { + Console.WriteLine($"Checking balance for {address}"); + if (!this.blockExplorerClient.HasBalance(address)) + return result; + + Console.WriteLine($"Balance Found for {keyPath} - Please confirm transaction on your ledger device."); + result.HasBalance = true; + } + + await ledger.PrepareMessage(key, address); + var resp = await ledger.SignMessage(); + + var signature = GetSignature(address, resp, pubKey); + if (signature == null) + return result; + + this.OutputToFile(address, destinationAddress, signature); + + return result; + } + private string GetSignature(string address, byte[] resp, PubKey pubKey) { // Convert the ASN.1 signature into the proper format for NBitcoin/NStratis to validate in the ownership tool diff --git a/src/AddressOwnershipTool/NodeApiClient.cs b/src/AddressOwnershipTool/NodeApiClient.cs index 30e51b9970..5c00976737 100644 --- a/src/AddressOwnershipTool/NodeApiClient.cs +++ b/src/AddressOwnershipTool/NodeApiClient.cs @@ -52,6 +52,21 @@ public decimal GetAddressBalance(string address) return addressBalance.Balance.ToDecimal(MoneyUnit.Satoshi); } + public List GetVerboseAddressBalance(string address) + { + VerboseAddressBalancesResult result = $"{this.baseUrl}" + .AppendPathSegment("Blockstore/getverboseaddressesbalances") + .SetQueryParam("addresses", address) + .GetJsonAsync().GetAwaiter().GetResult(); + + AddressIndexerData addressBalance = result.BalancesData.FirstOrDefault(); + + if (addressBalance == null) + return new List(); + + return addressBalance.BalanceChanges; + } + public WalletBuildTransactionModel BuildTransaction(string walletName, string walletPassword, string accountName, List recipients) { var result = $"{this.baseUrl}"