Integrate Segregated Witness
MODIFY
HiddenWallet.Daemon/Wrappers/WalletWrapper.cs
HiddenWallet.Daemon/Wrappers/WalletWrapper.cs
@@ -184,8 +184,8 @@ namespace HiddenWallet.Daemon.Wrappers
_incomingBob = ab.Unconfirmed;
// receive
- var ua = _walletJob.GetUnusedScriptPubKeys(AliceAccount, HdPathType.Receive).ToArray();
- var ub = _walletJob.GetUnusedScriptPubKeys(BobAccount, HdPathType.Receive).ToArray();
+ var ua = _walletJob.GetUnusedScriptPubKeys(AddressType.Pay2WitnessPublicKeyHash, AliceAccount, HdPathType.Receive).ToArray();
+ var ub = _walletJob.GetUnusedScriptPubKeys(AddressType.Pay2WitnessPublicKeyHash, BobAccount, HdPathType.Receive).ToArray();
_receiveResponseAlice.Addresses = new string[7];
_receiveResponseBob.Addresses = new string[7];
var network = _walletJob.Safe.Network;

MODIFY
HiddenWallet.FullSpvWallet/WalletJob.cs
HiddenWallet.FullSpvWallet/WalletJob.cs
@@ -356,12 +356,19 @@ namespace HiddenWallet.FullSpv
}
private void UpdateSafeTrackingByPath(HdPathType hdPathType, SafeAccount account = null)
+ {
+ var addressTypes = new HashSet<AddressType>
+ {
+ AddressType.Pay2PublicKeyHash,
+ AddressType.Pay2WitnessPublicKeyHash
+ };
+ foreach (AddressType addressType in addressTypes)
{
int i = 0;
var cleanCount = 0;
while (true)
{
- Script scriptPubkey = account == null ? Safe.GetAddress(i, hdPathType).ScriptPubKey : Safe.GetAddress(i, hdPathType, account).ScriptPubKey;
+ Script scriptPubkey = account == null ? Safe.GetAddress(addressType, i, hdPathType).ScriptPubKey : Safe.GetAddress(addressType, i, hdPathType, account).ScriptPubKey;
Tracker.TrackedScriptPubKeys.Add(scriptPubkey);
@@ -374,6 +381,7 @@ namespace HiddenWallet.FullSpv
i++;
}
+ }
}
#endregion
@@ -477,18 +485,27 @@ namespace HiddenWallet.FullSpv
{
var maxTracked = Tracker.TrackedScriptPubKeys.Count;
var allPossiblyTrackedAddresses = new HashSet<BitcoinAddress>();
- foreach (var address in Safe.GetFirstNAddresses(maxTracked, HdPathType.Receive, account))
+
+ var addressTypes = new HashSet<AddressType>
+ {
+ AddressType.Pay2PublicKeyHash,
+ AddressType.Pay2WitnessPublicKeyHash
+ };
+ foreach (AddressType addressType in addressTypes)
+ {
+ foreach (var address in Safe.GetFirstNAddresses(addressType, maxTracked, HdPathType.Receive, account))
{
allPossiblyTrackedAddresses.Add(address);
}
- foreach (var address in Safe.GetFirstNAddresses(maxTracked, HdPathType.Change, account))
+ foreach (var address in Safe.GetFirstNAddresses(addressType, maxTracked, HdPathType.Change, account))
{
allPossiblyTrackedAddresses.Add(address);
}
- foreach (var address in Safe.GetFirstNAddresses(maxTracked, HdPathType.NonHardened, account))
+ foreach (var address in Safe.GetFirstNAddresses(addressType, maxTracked, HdPathType.NonHardened, account))
{
allPossiblyTrackedAddresses.Add(address);
}
+ }
var actuallyTrackedScriptPubKeys = new HashSet<Script>();
foreach (var address in allPossiblyTrackedAddresses)
@@ -848,7 +865,7 @@ namespace HiddenWallet.FullSpv
// 1. Get the script pubkey of the change.
Debug.WriteLine("Select change address...");
- Script changeScriptPubKey = GetUnusedScriptPubKeys(account, HdPathType.Change).FirstOrDefault();
+ Script changeScriptPubKey = GetUnusedScriptPubKeys(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Change).FirstOrDefault();
// 2. Find all coins I can spend from the account
// 3. How much money we can spend?
@@ -892,7 +909,7 @@ namespace HiddenWallet.FullSpv
}
else
{
- const int expectedMinTxSize = 1 * 148 + 2 * 34 + 10 - 1;
+ const int expectedMinTxSize = 1 * 146 + 1 * 33 + 10;
try
{
inNum = SelectCoinsToSpend(unspentCoins, amount + feePerBytes * expectedMinTxSize).Count;
@@ -903,10 +920,14 @@ namespace HiddenWallet.FullSpv
}
}
- const int outNum = 2; // 1 address to send + 1 for change
- var estimatedTxSize = inNum * 148 + outNum * 34 + 10 + inNum; // http://bitcoin.stackexchange.com/questions/1195/how-to-calculate-transaction-size-before-sending
- Debug.WriteLine($"Estimated tx size: {estimatedTxSize} bytes");
- Money fee = feePerBytes * estimatedTxSize;
+ // https://bitcoincore.org/en/segwit_wallet_dev/#transaction-fee-estimation
+ // https://bitcoin.stackexchange.com/a/46379/26859
+ int outNum = spendAll ? 1 : 2; // 1 address to send + 1 for change
+ var origTxSize = inNum * 146 + outNum * 33 + 10;
+ var newTxSize = inNum * 41 + outNum * 33 + 10; // BEWARE: This assumes segwit only inputs!
+ var vSize = (int)Math.Ceiling(((3 * newTxSize) + origTxSize) / 4m);
+ Debug.WriteLine($"Estimated tx size: {vSize} bytes");
+ Money fee = feePerBytes * vSize;
Debug.WriteLine($"Fee: {fee.ToDecimal(MoneyUnit.BTC):0.#############################}btc");
successfulResult.Fee = fee;
@@ -1015,7 +1036,7 @@ namespace HiddenWallet.FullSpv
FailingReason = "Not enough funds"
};
- public IEnumerable<Script> GetUnusedScriptPubKeys(SafeAccount account = null, HdPathType hdPathType = HdPathType.Receive)
+ public IEnumerable<Script> GetUnusedScriptPubKeys(AddressType type, SafeAccount account = null, HdPathType hdPathType = HdPathType.Receive)
{
AssertAccount(account);
@@ -1023,7 +1044,7 @@ namespace HiddenWallet.FullSpv
int i = 0;
while (true)
{
- Script scriptPubkey = account == null ? Safe.GetAddress(i, hdPathType).ScriptPubKey : Safe.GetAddress(i, hdPathType, account).ScriptPubKey;
+ Script scriptPubkey = account == null ? Safe.GetAddress(type, i, hdPathType).ScriptPubKey : Safe.GetAddress(type, i, hdPathType, account).ScriptPubKey;
if (Tracker.IsClean(scriptPubkey))
{
scriptPubKeys.Add(scriptPubkey);

MODIFY
HiddenWallet.Tests/Helpers.cs
HiddenWallet.Tests/Helpers.cs
@@ -86,7 +86,19 @@ namespace HiddenWallet.Tests
return operationsPerAllAddresses;
}
- var addresses = safe.GetFirstNAddresses(minUnusedKeys, hdPathType.GetValueOrDefault());
+ var addresses = new List<BitcoinAddress>();
+ var addressTypes = new HashSet<AddressType>
+ {
+ AddressType.Pay2PublicKeyHash,
+ AddressType.Pay2WitnessPublicKeyHash
+ };
+ foreach (AddressType addressType in addressTypes)
+ {
+ foreach(var address in safe.GetFirstNAddresses(addressType, minUnusedKeys, hdPathType.GetValueOrDefault()))
+ {
+ addresses.Add(address);
+ }
+ }
//var addresses = FakeData.FakeSafe.GetFirstNAddresses(minUnusedKeys);
var operationsPerAddresses = new Dictionary<BitcoinAddress, List<BalanceOperation>>();
@@ -105,7 +117,15 @@ namespace HiddenWallet.Tests
addresses = new List<BitcoinAddress>();
for (int i = startIndex; i < startIndex + minUnusedKeys; i++)
{
- addresses.Add(safe.GetAddress(i, hdPathType.GetValueOrDefault()));
+ addressTypes = new HashSet<AddressType>
+ {
+ AddressType.Pay2PublicKeyHash,
+ AddressType.Pay2WitnessPublicKeyHash
+ };
+ foreach (AddressType addressType in addressTypes)
+ {
+ addresses.Add(safe.GetAddress(addressType, i, hdPathType.GetValueOrDefault()));
+ }
//addresses.Add(FakeData.FakeSafe.GetAddress(i));
}
foreach (var elem in await QueryOperationsPerAddressesAsync(client, addresses).ConfigureAwait(false))

MODIFY
HiddenWallet.Tests/HiddenWallet.Tests.csproj
HiddenWallet.Tests/HiddenWallet.Tests.csproj
<ProjectReference Include="..\HiddenWallet\HiddenWallet.csproj" />
</ItemGroup>
+ <ItemGroup>
+ <None Update="xunit.runner.json">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </None>
+ </ItemGroup>
+
</Project>

MODIFY
HiddenWallet.Tests/QBitNinjaJutsus.cs
HiddenWallet.Tests/QBitNinjaJutsus.cs
@@ -7,6 +7,7 @@ using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using HiddenWallet.KeyManagement;
+using HiddenWallet.Models;
namespace HiddenWallet.Tests
{
@@ -94,7 +95,19 @@ namespace HiddenWallet.Tests
return operationsPerAllAddresses;
}
- var addresses = safe.GetFirstNAddresses(minUnusedKeys, hdPathType.GetValueOrDefault());
+ var addresses = new List<BitcoinAddress>();
+ var addressTypes = new HashSet<AddressType>
+ {
+ AddressType.Pay2PublicKeyHash,
+ AddressType.Pay2WitnessPublicKeyHash
+ };
+ foreach (AddressType addressType in addressTypes)
+ {
+ foreach (var address in safe.GetFirstNAddresses(addressType, minUnusedKeys, hdPathType.GetValueOrDefault()))
+ {
+ addresses.Add(address);
+ }
+ }
//var addresses = FakeData.FakeSafe.GetFirstNAddresses(minUnusedKeys);
var operationsPerAddresses = new Dictionary<BitcoinAddress, List<BalanceOperation>>();
@@ -113,7 +126,15 @@ namespace HiddenWallet.Tests
addresses = new List<BitcoinAddress>();
for (int i = startIndex; i < startIndex + minUnusedKeys; i++)
{
- addresses.Add(safe.GetAddress(i, hdPathType.GetValueOrDefault()));
+ addressTypes = new HashSet<AddressType>
+ {
+ AddressType.Pay2PublicKeyHash,
+ AddressType.Pay2WitnessPublicKeyHash
+ };
+ foreach (AddressType addressType in addressTypes)
+ {
+ addresses.Add(safe.GetAddress(addressType, i, hdPathType.GetValueOrDefault()));
+ }
//addresses.Add(FakeData.FakeSafe.GetAddress(i));
}
foreach (var elem in await QueryOperationsPerAddressesAsync(client, addresses).ConfigureAwait(false))

MODIFY
HiddenWallet.Tests/SafeTests.cs
HiddenWallet.Tests/SafeTests.cs
@@ -3,6 +3,7 @@ using System.Globalization;
using HiddenWallet.KeyManagement;
using NBitcoin;
using Xunit;
+using HiddenWallet.Models;
namespace HiddenWallet.Tests
{
@@ -29,12 +30,15 @@ namespace HiddenWallet.Tests
var bob = new SafeAccount(4);
try
{
- Assert.Equal(safe.GetAddress(3, account: alice), recoverdSafe.GetAddress(3, account: alice));
- Assert.Equal(safe.GetAddress(4, HdPathType.NonHardened, account: alice), recoverdSafe.GetAddress(4, HdPathType.NonHardened, account: alice));
- Assert.NotEqual(safe.GetAddress(4, HdPathType.Change, account: alice), recoverdSafe.GetAddress(4, HdPathType.NonHardened, account: alice));
- Assert.NotEqual(safe.GetAddress(3, HdPathType.NonHardened, account: alice), recoverdSafe.GetAddress(4, HdPathType.NonHardened, account: alice));
- Assert.NotEqual(safe.GetAddress(4, HdPathType.NonHardened, account: alice), recoverdSafe.GetAddress(4, HdPathType.NonHardened, account: bob));
- Assert.NotEqual(safe.GetAddress(4, account: alice), safe.GetAddress(4));
+ foreach (AddressType addressType in Enum.GetValues(typeof(AddressType)))
+ {
+ Assert.Equal(safe.GetAddress(addressType, 3, account: alice), recoverdSafe.GetAddress(addressType, 3, account: alice));
+ Assert.Equal(safe.GetAddress(addressType, 4, HdPathType.NonHardened, account: alice), recoverdSafe.GetAddress(addressType, 4, HdPathType.NonHardened, account: alice));
+ Assert.NotEqual(safe.GetAddress(addressType, 4, HdPathType.Change, account: alice), recoverdSafe.GetAddress(addressType, 4, HdPathType.NonHardened, account: alice));
+ Assert.NotEqual(safe.GetAddress(addressType, 3, HdPathType.NonHardened, account: alice), recoverdSafe.GetAddress(addressType, 4, HdPathType.NonHardened, account: alice));
+ Assert.NotEqual(safe.GetAddress(addressType, 4, HdPathType.NonHardened, account: alice), recoverdSafe.GetAddress(addressType, 4, HdPathType.NonHardened, account: bob));
+ Assert.NotEqual(safe.GetAddress(addressType, 4, account: alice), safe.GetAddress(addressType, 4));
+ }
Assert.Equal(DateTimeOffset.UtcNow.Date, safe.CreationTime.Date);
Assert.True(Safe.EarliestPossibleCreationTime < safe.CreationTime);
@@ -73,7 +77,10 @@ namespace HiddenWallet.Tests
Assert.Equal(safe.ExtKey.ScriptPubKey, loadedSafe.ExtKey.ScriptPubKey);
Assert.Equal(loadedSafe.BitcoinExtKey, recoverdSafe.BitcoinExtKey);
Assert.Equal(loadedSafe.GetBitcoinExtPubKey(index: null, hdPathType: HdPathType.NonHardened, account: new SafeAccount(1)), recoverdSafe.GetBitcoinExtPubKey(index: null, hdPathType: HdPathType.NonHardened, account: new SafeAccount(1)));
- Assert.Equal(loadedSafe.GetAddress(index), recoverdSafe.GetAddress(index));
+ foreach (AddressType addressType in Enum.GetValues(typeof(AddressType)))
+ {
+ Assert.Equal(loadedSafe.GetAddress(addressType, index), recoverdSafe.GetAddress(addressType, index));
+ }
Assert.Equal(loadedSafe.GetBitcoinExtKey(index), recoverdSafe.GetBitcoinExtKey(index));
}
finally

MODIFY
HiddenWallet.Tests/SendTests.cs
HiddenWallet.Tests/SendTests.cs
@@ -61,7 +61,7 @@ namespace HiddenWallet.Tests
Debug.WriteLine(record.Confirmed);
Debug.WriteLine(record.Amount);
- var receive = walletJob.GetUnusedScriptPubKeys(account, HdPathType.Receive).FirstOrDefault();
+ var receive = walletJob.GetUnusedScriptPubKeys(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive).FirstOrDefault();
IDictionary<Coin, bool> unspentCoins;
var bal = walletJob.GetBalance(out unspentCoins, account);
@@ -163,7 +163,7 @@ namespace HiddenWallet.Tests
Task.Delay(1000).Wait();
}
- var receive = walletJob.GetUnusedScriptPubKeys(account, HdPathType.Receive).FirstOrDefault();
+ var receive = walletJob.GetUnusedScriptPubKeys(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive).FirstOrDefault();
IDictionary<Coin, bool> unspentCoins;
var bal = walletJob.GetBalance(out unspentCoins, account);
@@ -325,7 +325,7 @@ namespace HiddenWallet.Tests
Task.Delay(1000).Wait();
}
- var receive = walletJob.GetUnusedScriptPubKeys(account, HdPathType.Receive).FirstOrDefault();
+ var receive = walletJob.GetUnusedScriptPubKeys(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive).FirstOrDefault();
IDictionary<Coin, bool> unspentCoins;
var bal = walletJob.GetBalance(out unspentCoins, account);
@@ -433,7 +433,7 @@ namespace HiddenWallet.Tests
Debug.WriteLine($"{record.TransactionId} {record.Amount} {record.Confirmed}");
}
- var receive = walletJob.GetUnusedScriptPubKeys(account, HdPathType.Receive).FirstOrDefault();
+ var receive = walletJob.GetUnusedScriptPubKeys(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive).FirstOrDefault();
IDictionary<Coin, bool> unspentCoins;
var bal = walletJob.GetBalance(out unspentCoins, account);

MODIFY
HiddenWallet/Extensions/NBitcoinExtensions.cs
HiddenWallet/Extensions/NBitcoinExtensions.cs
@@ -7,7 +7,7 @@ namespace NBitcoin
{
public static class NBitcoinExtensions
{
- public static ChainedBlock GetBlock(this ConcurrentChain concurrentChain, Height height)
- => concurrentChain.GetBlock(height.Value);
+ public static ChainedBlock GetBlock(this ConcurrentChain me, Height height)
+ => me.GetBlock(height.Value);
}
}

MODIFY
HiddenWallet/HiddenWallet.csproj
HiddenWallet/HiddenWallet.csproj
<ItemGroup>
<Folder Include="Extensions\" />
- <Folder Include="Helpers\" />
<Folder Include="KeyManagement\" />
<Folder Include="WebClients\" />
</ItemGroup>

Integrate Segregated Witness