Make sure a smartly crafted scriptpubkey won't be able to DoS the coordinator
MODIFY
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
@@ -85,7 +85,7 @@ namespace WalletWasabi.Backend.Controllers
if (!ModelState.IsValid
|| request == null
|| string.IsNullOrWhiteSpace(request.BlindedOutputScriptHex)
- || string.IsNullOrWhiteSpace(request.ChangeOutputScript)
+ || string.IsNullOrWhiteSpace(request.ChangeOutputAddress)
|| request.Inputs == null
|| request.Inputs.Count() == 0
|| request.Inputs.Any(x => x.Input == null
@@ -112,7 +112,15 @@ namespace WalletWasabi.Backend.Controllers
return BadRequest("Blinded output has already been registered.");
}
- var changeOutput = new Script(request.ChangeOutputScript);
+ BitcoinAddress changeOutputAddress;
+ try
+ {
+ changeOutputAddress = BitcoinAddress.Create(request.ChangeOutputAddress, Network);
+ }
+ catch (FormatException ex)
+ {
+ return BadRequest($"Invalid ChangeOutputAddress. Details: {ex.Message}");
+ }
var inputs = new HashSet<(OutPoint OutPoint, TxOut Output)>();
@@ -220,7 +228,7 @@ namespace WalletWasabi.Backend.Controllers
}
// Make sure Alice checks work.
- var alice = new Alice(inputs, networkFeeToPay, new Script(request.ChangeOutputScript), request.BlindedOutputScriptHex);
+ var alice = new Alice(inputs, networkFeeToPay, changeOutputAddress, request.BlindedOutputScriptHex);
foreach (Guid aliceToRemove in alicesToRemove)
{
@@ -444,12 +452,12 @@ namespace WalletWasabi.Backend.Controllers
[ProducesResponseType(404)]
[ProducesResponseType(409)]
[ProducesResponseType(410)]
- public async Task<IActionResult> PostOutputAsync([FromQuery]string roundHash, [FromBody]OutputRequest outputRequest)
+ public async Task<IActionResult> PostOutputAsync([FromQuery]string roundHash, [FromBody]OutputRequest request)
{
if (string.IsNullOrWhiteSpace(roundHash)
- || outputRequest == null
- || string.IsNullOrWhiteSpace(outputRequest.OutputScript)
- || string.IsNullOrWhiteSpace(outputRequest.SignatureHex)
+ || request == null
+ || string.IsNullOrWhiteSpace(request.OutputAddress)
+ || string.IsNullOrWhiteSpace(request.SignatureHex)
|| !ModelState.IsValid)
{
return BadRequest();
@@ -472,21 +480,29 @@ namespace WalletWasabi.Backend.Controllers
return Conflict($"Output registration can only be done from OutputRegistration phase. Current phase: {phase}.");
}
- var outputScript = new Script(outputRequest.OutputScript);
+ BitcoinAddress outputAddress;
+ try
+ {
+ outputAddress = BitcoinAddress.Create(request.OutputAddress, Network);
+ }
+ catch (FormatException ex)
+ {
+ return BadRequest($"Invalid OutputAddress. Details: {ex.Message}");
+ }
- if (RsaKey.PubKey.Verify(ByteHelpers.FromHex(outputRequest.SignatureHex), outputScript.ToBytes()))
+ if (RsaKey.PubKey.Verify(ByteHelpers.FromHex(request.SignatureHex), outputAddress.ScriptPubKey.ToBytes()))
{
using (await OutputLock.LockAsync())
{
Bob bob = null;
try
{
- bob = new Bob(outputScript);
+ bob = new Bob(outputAddress);
round.AddBob(bob);
}
catch (Exception ex)
{
- return BadRequest($"Invalid outputScript is provided. Details: {ex.Message}");
+ return BadRequest($"Invalid outputAddress is provided. Details: {ex.Message}");
}
if (round.CountBobs() == round.AnonymitySet)

MODIFY
WalletWasabi.Tests/RegTests.cs
WalletWasabi.Tests/RegTests.cs
@@ -1611,20 +1611,15 @@ namespace WalletWasabi.Tests
var inputsRequest = new InputsRequest
{
BlindedOutputScriptHex = null,
- ChangeOutputScript = null,
+ ChangeOutputAddress = null,
Inputs = null,
};
HttpRequestException httpRequestException = await Assert.ThrowsAsync<HttpRequestException>(async () => await AliceClient.CreateNewAsync(inputsRequest, baseUri));
Assert.Equal($"{HttpStatusCode.BadRequest.ToReasonString()}\nInvalid request.", httpRequestException.Message);
- Script blindedOutputScript = new Script();
- byte[] changeOutputScript = new byte[] { };
- httpRequestException = await Assert.ThrowsAsync<HttpRequestException>(async () => await AliceClient.CreateNewAsync(blindedOutputScript, changeOutputScript, new InputProofModel[] { new InputProofModel { Input = new OutPoint(), Proof = "" } }, baseUri));
- Assert.Equal($"{HttpStatusCode.BadRequest.ToReasonString()}\nInvalid request.", httpRequestException.Message);
-
inputsRequest.BlindedOutputScriptHex = "c";
- inputsRequest.ChangeOutputScript = "a";
+ inputsRequest.ChangeOutputAddress = new Key().PubKey.GetAddress(network).ToString();
inputsRequest.Inputs = new List<InputProofModel> { new InputProofModel { Input = new OutPoint(uint256.One, 0), Proof = "b" } };
httpRequestException = await Assert.ThrowsAsync<HttpRequestException>(async () => await AliceClient.CreateNewAsync(inputsRequest, baseUri));
Assert.Equal($"{HttpStatusCode.BadRequest.ToReasonString()}\nProvided input is not unspent.", httpRequestException.Message);
@@ -1826,7 +1821,7 @@ namespace WalletWasabi.Tests
scriptBytes = new Key().PubKey.GetSegwitAddress(network).ScriptPubKey.ToBytes();
(BlindingFactor, BlindedData) = blindingKey.PubKey.Blind(scriptBytes);
- using (var aliceClient = await AliceClient.CreateNewAsync(new Key().ScriptPubKey, BlindedData, new InputProofModel[] { new InputProofModel { Input = new OutPoint(hash, index), Proof = key.SignMessage(ByteHelpers.ToHex(BlindedData)) } }, baseUri))
+ using (var aliceClient = await AliceClient.CreateNewAsync(new Key().PubKey.GetAddress(network), BlindedData, new InputProofModel[] { new InputProofModel { Input = new OutPoint(hash, index), Proof = key.SignMessage(ByteHelpers.ToHex(BlindedData)) } }, baseUri))
{
Assert.NotNull(aliceClient.BlindedOutputSignature);
Assert.NotEqual(Guid.Empty, aliceClient.UniqueId);
@@ -1870,7 +1865,7 @@ namespace WalletWasabi.Tests
Assert.Equal($"{HttpStatusCode.Gone.ToReasonString()}\nRound is not running.", httpRequestException.Message);
}
- using (var aliceClient = await AliceClient.CreateNewAsync(new Key().ScriptPubKey, BlindedData, new InputProofModel[] { new InputProofModel { Input = new OutPoint(hash, index), Proof = key.SignMessage(ByteHelpers.ToHex(BlindedData)) } }, baseUri))
+ using (var aliceClient = await AliceClient.CreateNewAsync(new Key().PubKey.GetAddress(network), BlindedData, new InputProofModel[] { new InputProofModel { Input = new OutPoint(hash, index), Proof = key.SignMessage(ByteHelpers.ToHex(BlindedData)) } }, baseUri))
{
Assert.NotNull(aliceClient.BlindedOutputSignature);
Assert.NotEqual(Guid.Empty, aliceClient.UniqueId);
@@ -1930,8 +1925,8 @@ namespace WalletWasabi.Tests
var input1 = new OutPoint(hash1, index1);
var input2 = new OutPoint(hash2, index2);
- using (var aliceClient1 = await AliceClient.CreateNewAsync(new Key().ScriptPubKey, blinded1.BlindedData, new InputProofModel[] { new InputProofModel { Input = input1, Proof = key1.SignMessage(blindedOutputScriptHex1) } }, baseUri))
- using (var aliceClient2 = await AliceClient.CreateNewAsync(new Key().ScriptPubKey, blinded2.BlindedData, new InputProofModel[] { new InputProofModel { Input = input2, Proof = key2.SignMessage(blindedOutputScriptHex2) } }, baseUri))
+ using (var aliceClient1 = await AliceClient.CreateNewAsync(new Key().PubKey.GetAddress(network), blinded1.BlindedData, new InputProofModel[] { new InputProofModel { Input = input1, Proof = key1.SignMessage(blindedOutputScriptHex1) } }, baseUri))
+ using (var aliceClient2 = await AliceClient.CreateNewAsync(new Key().PubKey.GetAddress(network), blinded2.BlindedData, new InputProofModel[] { new InputProofModel { Input = input2, Proof = key2.SignMessage(blindedOutputScriptHex2) } }, baseUri))
{
Assert.Equal(aliceClient2.RoundId, aliceClient1.RoundId);
Assert.NotEqual(aliceClient2.UniqueId, aliceClient1.UniqueId);
@@ -1956,8 +1951,8 @@ namespace WalletWasabi.Tests
using (var bobClient1 = new BobClient(baseUri))
using (var bobClient2 = new BobClient(baseUri))
{
- await bobClient1.PostOutputAsync(roundHash, outputAddress1.ScriptPubKey, unblindedSignature1);
- await bobClient2.PostOutputAsync(roundHash, outputAddress2.ScriptPubKey, unblindedSignature2);
+ await bobClient1.PostOutputAsync(roundHash, outputAddress1, unblindedSignature1);
+ await bobClient2.PostOutputAsync(roundHash, outputAddress2, unblindedSignature2);
}
roundState = await satoshiClient.GetRoundStateAsync(aliceClient1.RoundId);
@@ -2046,13 +2041,13 @@ namespace WalletWasabi.Tests
Uri baseUri = new Uri(RegTestFixture.BackendEndPoint);
var fundingTxCount = 0;
- var inputRegistrationUsers = new List<((BigInteger blindingFactor, byte[] blindedData) blinded, Script activeOutputScript, Script changeOutputScript, IEnumerable<InputProofModel> inputProofModels, List<(Key key, BitcoinWitPubKeyAddress address, uint256 txHash, Transaction tx, OutPoint input)> userInputData)>();
+ var inputRegistrationUsers = new List<((BigInteger blindingFactor, byte[] blindedData) blinded, BitcoinAddress activeOutputAddress, BitcoinAddress changeOutputAddress, IEnumerable<InputProofModel> inputProofModels, List<(Key key, BitcoinWitPubKeyAddress address, uint256 txHash, Transaction tx, OutPoint input)> userInputData)>();
for (int i = 0; i < roundConfig.AnonymitySet; i++)
{
var userInputData = new List<(Key key, BitcoinWitPubKeyAddress inputAddress, uint256 txHash, Transaction tx, OutPoint input)>();
- var activeOutputScript = new Key().ScriptPubKey;
- var changeOutputScript = new Key().ScriptPubKey;
- var blinded = blindingKey.PubKey.Blind(activeOutputScript.ToBytes());
+ var activeOutputAddress = new Key().PubKey.GetAddress(network);
+ var changeOutputAddress = new Key().PubKey.GetAddress(network);
+ var blinded = blindingKey.PubKey.Blind(activeOutputAddress.ScriptPubKey.ToBytes());
var inputProofModels = new List<InputProofModel>();
int numberOfInputs = new Random().Next(1, 7);
@@ -2085,7 +2080,7 @@ namespace WalletWasabi.Tests
userInputData.Add((key, inputAddress, txHash, transaction, input));
}
- inputRegistrationUsers.Add((blinded, activeOutputScript, changeOutputScript, inputProofModels, userInputData));
+ inputRegistrationUsers.Add((blinded, activeOutputAddress, changeOutputAddress, inputProofModels, userInputData));
}
var mempool = await rpc.GetRawMempoolAsync();
@@ -2103,11 +2098,11 @@ namespace WalletWasabi.Tests
foreach (var user in inputRegistrationUsers)
{
- aliceClients.Add(AliceClient.CreateNewAsync(user.changeOutputScript, user.blinded.blindedData, user.inputProofModels, baseUri));
+ aliceClients.Add(AliceClient.CreateNewAsync(user.changeOutputAddress, user.blinded.blindedData, user.inputProofModels, baseUri));
}
long roundId = 0;
- var users = new List<((BigInteger blindingFactor, byte[] blindedData) blinded, Script activeOutputScript, Script changeOutputScript, IEnumerable<InputProofModel> inputProofModels, List<(Key key, BitcoinWitPubKeyAddress address, uint256 txHash, Transaction tx, OutPoint input)> userInputData, AliceClient aliceClient, byte[] unblindedSignature)>();
+ var users = new List<((BigInteger blindingFactor, byte[] blindedData) blinded, BitcoinAddress activeOutputAddress, BitcoinAddress changeOutputAddress, IEnumerable<InputProofModel> inputProofModels, List<(Key key, BitcoinWitPubKeyAddress address, uint256 txHash, Transaction tx, OutPoint input)> userInputData, AliceClient aliceClient, byte[] unblindedSignature)>();
for (int i = 0; i < inputRegistrationUsers.Count; i++)
{
var user = inputRegistrationUsers[i];
@@ -2123,7 +2118,7 @@ namespace WalletWasabi.Tests
Assert.Equal(roundId, aliceClient.RoundId);
}
// Because it's valuetuple.
- users.Add((user.blinded, user.activeOutputScript, user.changeOutputScript, user.inputProofModels, user.userInputData, aliceClient, blindingKey.PubKey.UnblindSignature(aliceClient.BlindedOutputSignature, user.blinded.blindingFactor)));
+ users.Add((user.blinded, user.activeOutputAddress, user.changeOutputAddress, user.inputProofModels, user.userInputData, aliceClient, blindingKey.PubKey.UnblindSignature(aliceClient.BlindedOutputSignature, user.blinded.blindingFactor)));
}
Logger.TurnOn();
@@ -2154,7 +2149,7 @@ namespace WalletWasabi.Tests
foreach (var user in users)
{
var bobClient = new BobClient(baseUri);
- outputRequests.Add((bobClient, bobClient.PostOutputAsync(roundHash, user.activeOutputScript, user.unblindedSignature)));
+ outputRequests.Add((bobClient, bobClient.PostOutputAsync(roundHash, user.activeOutputAddress, user.unblindedSignature)));
}
foreach (var request in outputRequests)
@@ -2393,14 +2388,14 @@ namespace WalletWasabi.Tests
chaumianClient1.Start();
chaumianClient2.Start();
- var customChange1 = new Key().ScriptPubKey;
- var customActive1 = new Key().ScriptPubKey;
- var customChange2 = new Key().ScriptPubKey;
- var customActive2 = new Key().ScriptPubKey;
- chaumianClient1.AddCustomChangeScript(customChange1);
- chaumianClient2.AddCustomChangeScript(customChange2);
- chaumianClient1.AddCustomActiveScript(customActive1);
- chaumianClient2.AddCustomActiveScript(customActive2);
+ var customChange1 = new Key().PubKey.GetAddress(network);
+ var customActive1 = new Key().PubKey.GetAddress(network);
+ var customChange2 = new Key().PubKey.GetAddress(network);
+ var customActive2 = new Key().PubKey.GetAddress(network);
+ chaumianClient1.AddCustomChangeAddress(customChange1);
+ chaumianClient2.AddCustomChangeAddress(customChange2);
+ chaumianClient1.AddCustomActiveAddress(customActive1);
+ chaumianClient2.AddCustomActiveAddress(customActive2);
Assert.True(2 == (await chaumianClient1.QueueCoinsToMixAsync(password, smartCoin1, smartCoin2)).Count());
Assert.True(1 == (await chaumianClient2.QueueCoinsToMixAsync(password, smartCoin3)).Count());
@@ -2421,10 +2416,10 @@ namespace WalletWasabi.Tests
smartCoin3.SpenderTransactionId = cjid;
var cj = await rpc.GetRawTransactionAsync(cjid);
- Assert.Contains(customActive1, cj.Outputs.Select(x => x.ScriptPubKey));
- Assert.DoesNotContain(customChange1, cj.Outputs.Select(x => x.ScriptPubKey)); // The smallest of CJ won't generate change.
- Assert.Contains(customActive2, cj.Outputs.Select(x => x.ScriptPubKey));
- Assert.Contains(customChange2, cj.Outputs.Select(x => x.ScriptPubKey));
+ Assert.Contains(customActive1.ScriptPubKey, cj.Outputs.Select(x => x.ScriptPubKey));
+ Assert.DoesNotContain(customChange1.ScriptPubKey, cj.Outputs.Select(x => x.ScriptPubKey)); // The smallest of CJ won't generate change.
+ Assert.Contains(customActive2.ScriptPubKey, cj.Outputs.Select(x => x.ScriptPubKey));
+ Assert.Contains(customChange2.ScriptPubKey, cj.Outputs.Select(x => x.ScriptPubKey));
}
finally
{

MODIFY
WalletWasabi/Backend/Models/Requests/InputsRequest.cs
WalletWasabi/Backend/Models/Requests/InputsRequest.cs
@@ -18,7 +18,7 @@ namespace WalletWasabi.Backend.Models.Requests
public string BlindedOutputScriptHex { get; set; }
[Required]
- public string ChangeOutputScript { get; set; }
+ public string ChangeOutputAddress { get; set; }
public StringContent ToHttpStringContent()
{

MODIFY
WalletWasabi/Backend/Models/Requests/OutputRequest.cs
WalletWasabi/Backend/Models/Requests/OutputRequest.cs
@@ -10,7 +10,7 @@ namespace WalletWasabi.Backend.Models.Requests
public class OutputRequest
{
[Required]
- public string OutputScript { get; set; }
+ public string OutputAddress { get; set; }
[Required]
public string SignatureHex { get; set; }

MODIFY
WalletWasabi/Helpers/NBitcoinHelpers.cs
WalletWasabi/Helpers/NBitcoinHelpers.cs
@@ -20,5 +20,23 @@ namespace WalletWasabi.Helpers
return HashHelpers.GenerateSha256Hash(sb.ToString());
}
+ public static BitcoinAddress ParseBitcoinAddress(string address)
+ {
+ try
+ {
+ return BitcoinAddress.Create(address, Network.RegTest);
+ }
+ catch
+ {
+ try
+ {
+ return BitcoinAddress.Create(address, Network.TestNet);
+ }
+ catch
+ {
+ return BitcoinAddress.Create(address, Network.Main);
+ }
+ }
+ }
}
}

MODIFY
WalletWasabi/Models/ChaumianCoinJoin/Alice.cs
WalletWasabi/Models/ChaumianCoinJoin/Alice.cs
@@ -21,7 +21,7 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
public IEnumerable<(OutPoint OutPoint, TxOut Output)> Inputs { get; }
- public Script ChangeOutputScript { get; }
+ public BitcoinAddress ChangeOutputAddress { get; }
public string BlindedOutputScriptHex { get; }
@@ -29,20 +29,20 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
public AliceState State { get; set; }
- public Alice(IEnumerable<(OutPoint OutPoint, TxOut Output)> inputs, Money networkFeeToPay, Script changeOutputScript, string blindedOutputHex)
+ public Alice(IEnumerable<(OutPoint OutPoint, TxOut Output)> inputs, Money networkFeeToPay, BitcoinAddress changeOutputAddress, string blindedOutputScriptHex)
{
Inputs = Guard.NotNullOrEmpty(nameof(inputs), inputs);
NetworkFeeToPay = Guard.NotNull(nameof(networkFeeToPay), networkFeeToPay);
- BlindedOutputScriptHex = Guard.NotNullOrEmptyOrWhitespace(nameof(blindedOutputHex), blindedOutputHex);
+ BlindedOutputScriptHex = Guard.NotNullOrEmptyOrWhitespace(nameof(blindedOutputScriptHex), blindedOutputScriptHex);
- Guard.NotNull(nameof(changeOutputScript), changeOutputScript);
+ Guard.NotNull(nameof(changeOutputAddress), changeOutputAddress);
// 33 bytes maximum: https://bitcoin.stackexchange.com/a/46379/26859
- int byteCount = changeOutputScript.ToBytes().Length;
+ int byteCount = changeOutputAddress.ScriptPubKey.ToBytes().Length;
if (byteCount > 33)
{
- throw new ArgumentOutOfRangeException(nameof(changeOutputScript), byteCount, $"Can be maximum 33 bytes.");
+ throw new ArgumentOutOfRangeException(nameof(changeOutputAddress), byteCount, $"Can be maximum 33 bytes.");
}
- ChangeOutputScript = changeOutputScript;
+ ChangeOutputAddress = changeOutputAddress;
LastSeen = DateTimeOffset.UtcNow;
UniqueId = Guid.NewGuid();

MODIFY
WalletWasabi/Models/ChaumianCoinJoin/Bob.cs
WalletWasabi/Models/ChaumianCoinJoin/Bob.cs
@@ -8,18 +8,18 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
{
public class Bob
{
- public Script ActiveOutputScript { get; }
+ public BitcoinAddress ActiveOutputAddress { get; }
- public Bob(Script activeOutputScript)
+ public Bob(BitcoinAddress activeOutputAddress)
{
- Guard.NotNull(nameof(activeOutputScript), activeOutputScript);
+ Guard.NotNull(nameof(activeOutputAddress), activeOutputAddress);
// 33 bytes maximum: https://bitcoin.stackexchange.com/a/46379/26859
- int byteCount = activeOutputScript.ToBytes().Length;
+ int byteCount = activeOutputAddress.ScriptPubKey.ToBytes().Length;
if (byteCount > 33)
{
- throw new ArgumentOutOfRangeException(nameof(activeOutputScript), byteCount, $"Can be maximum 33 bytes.");
+ throw new ArgumentOutOfRangeException(nameof(activeOutputAddress), byteCount, $"Can be maximum 33 bytes.");
}
- ActiveOutputScript = activeOutputScript;
+ ActiveOutputAddress = activeOutputAddress;
}
}
}

MODIFY
WalletWasabi/Models/ChaumianCoinJoin/CcjClientRound.cs
WalletWasabi/Models/ChaumianCoinJoin/CcjClientRound.cs
@@ -19,8 +19,8 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
public AliceClient AliceClient { get; set; }
- public Script ChangeOutputScript { get; set; }
- public Script ActiveOutputScript { get; set; }
+ public BitcoinAddress ChangeOutputAddress { get; set; }
+ public BitcoinAddress ActiveOutputAddress { get; set; }
public byte[] UnblindedSignature { get; set; }
@@ -39,8 +39,8 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
public void ClearRegistration()
{
CoinsRegistered.Clear();
- ChangeOutputScript = null;
- ActiveOutputScript = null;
+ ChangeOutputAddress = null;
+ ActiveOutputAddress = null;
UnblindedSignature = null;
RoundHash = null;
AliceClient?.Dispose();

MODIFY
WalletWasabi/Models/ChaumianCoinJoin/CcjRound.cs
WalletWasabi/Models/ChaumianCoinJoin/CcjRound.cs
@@ -248,7 +248,7 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
// 2. Add Bob outputs.
foreach (Bob bob in Bobs)
{
- transaction.AddOutput(newDenomination, bob.ActiveOutputScript);
+ transaction.AddOutput(newDenomination, bob.ActiveOutputAddress.ScriptPubKey);
}
BitcoinWitPubKeyAddress coordinatorAddress = Constants.GetCoordinatorAddress(RpcClient.Network);
@@ -281,7 +281,7 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
}
else
{
- transaction.AddOutput(changeAmount, alice.ChangeOutputScript);
+ transaction.AddOutput(changeAmount, alice.ChangeOutputAddress.ScriptPubKey);
}
}
else
@@ -708,7 +708,7 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
{
throw new InvalidOperationException("Adding Bob is only allowed in OutputRegistration phase.");
}
- if (Bobs.Any(x => x.ActiveOutputScript == bob.ActiveOutputScript))
+ if (Bobs.Any(x => x.ActiveOutputAddress == bob.ActiveOutputAddress))
{
return; // Bob is already added.
}

MODIFY
WalletWasabi/Services/CcjClient.cs
WalletWasabi/Services/CcjClient.cs
@@ -25,10 +25,10 @@ namespace WalletWasabi.Services
BlindingRsaPubKey CoordinatorPubKey { get; }
public KeyManager KeyManager { get; }
- public List<Script> CustomChangeScripts { get; }
- private object CustomChangeScriptsLock { get; }
- public List<Script> CustomActiveScripts { get; }
- private object CustomActiveScriptsLock { get; }
+ public List<BitcoinAddress> CustomChangeAddresses { get; }
+ private object CustomChangeAddressesLock { get; }
+ public List<BitcoinAddress> CustomActiveAddresses { get; }
+ private object CustomActiveAddressesLock { get; }
public SatoshiClient SatoshiClient { get; }
public Uri CcjHostUri { get; }
@@ -66,10 +66,10 @@ namespace WalletWasabi.Services
State = new CcjClientState();
MixLock = new AsyncLock();
- CustomChangeScripts = new List<Script>();
- CustomActiveScripts = new List<Script>();
- CustomChangeScriptsLock = new object();
- CustomActiveScriptsLock = new object();
+ CustomChangeAddresses = new List<BitcoinAddress>();
+ CustomActiveAddresses = new List<BitcoinAddress>();
+ CustomChangeAddressesLock = new object();
+ CustomActiveAddressesLock = new object();
}
public void Start()
@@ -183,27 +183,27 @@ namespace WalletWasabi.Services
if (registrableCoins.Count() > 0)
{
- Script changeKey = null;
- Script activeKey = null;
- lock (CustomChangeScriptsLock)
+ BitcoinAddress changeAddress = null;
+ BitcoinAddress activeAddress = null;
+ lock (CustomChangeAddressesLock)
{
- if(CustomChangeScripts.Count > 0)
+ if(CustomChangeAddresses.Count > 0)
{
- changeKey = CustomChangeScripts.First();
- CustomChangeScripts.RemoveFirst();
+ changeAddress = CustomChangeAddresses.First();
+ CustomChangeAddresses.RemoveFirst();
}
}
- lock (CustomActiveScriptsLock)
+ lock (CustomActiveAddressesLock)
{
- if (CustomActiveScripts.Count > 0)
+ if (CustomActiveAddresses.Count > 0)
{
- activeKey = CustomActiveScripts.First();
- CustomActiveScripts.RemoveFirst();
+ activeAddress = CustomActiveAddresses.First();
+ CustomActiveAddresses.RemoveFirst();
}
}
- changeKey = changeKey ?? KeyManager.GenerateNewKey("CoinJoin Change Output", KeyState.Locked, isInternal: true).GetP2wpkhScript();
- activeKey = activeKey ?? KeyManager.GenerateNewKey("CoinJoin Active Output", KeyState.Locked, isInternal: true).GetP2wpkhScript();
- var blind = CoordinatorPubKey.Blind(activeKey.ToBytes());
+ changeAddress = changeAddress ?? KeyManager.GenerateNewKey("CoinJoin Change Output", KeyState.Locked, isInternal: true).GetP2wpkhAddress(Network);
+ activeAddress = activeAddress ?? KeyManager.GenerateNewKey("CoinJoin Active Output", KeyState.Locked, isInternal: true).GetP2wpkhAddress(Network);
+ var blind = CoordinatorPubKey.Blind(activeAddress.ScriptPubKey.ToBytes());
var inputProofs = new List<InputProofModel>();
foreach ((uint256 txid, int index) coinReference in registrableCoins)
@@ -217,11 +217,11 @@ namespace WalletWasabi.Services
};
inputProofs.Add(inputProof);
}
- AliceClient aliceClient = await AliceClient.CreateNewAsync(changeKey, blind.BlindedData, inputProofs, CcjHostUri, TorSocks5EndPoint);
+ AliceClient aliceClient = await AliceClient.CreateNewAsync(changeAddress, blind.BlindedData, inputProofs, CcjHostUri, TorSocks5EndPoint);
byte[] unblindedSignature = CoordinatorPubKey.UnblindSignature(aliceClient.BlindedOutputSignature, blind.BlindingFactor);
- if (!CoordinatorPubKey.Verify(unblindedSignature, activeKey.ToBytes()))
+ if (!CoordinatorPubKey.Verify(unblindedSignature, activeAddress.ScriptPubKey.ToBytes()))
{
throw new NotSupportedException("Coordinator did not sign the blinded output properly.");
}
@@ -242,8 +242,8 @@ namespace WalletWasabi.Services
roundRegistered.CoinsRegistered.Add(coin);
State.RemoveCoinFromWaitingList(coin);
}
- roundRegistered.ActiveOutputScript = activeKey;
- roundRegistered.ChangeOutputScript = changeKey;
+ roundRegistered.ActiveOutputAddress = activeAddress;
+ roundRegistered.ChangeOutputAddress = changeAddress;
roundRegistered.UnblindedSignature = unblindedSignature;
roundRegistered.AliceClient = aliceClient;
}
@@ -308,7 +308,7 @@ namespace WalletWasabi.Services
using (var bobClient = new BobClient(CcjHostUri, TorSocks5EndPoint))
{
- await bobClient.PostOutputAsync(ongoingRound.RoundHash, ongoingRound.ActiveOutputScript, ongoingRound.UnblindedSignature);
+ await bobClient.PostOutputAsync(ongoingRound.RoundHash, ongoingRound.ActiveOutputAddress, ongoingRound.UnblindedSignature);
ongoingRound.PostedOutput = true;
Logger.LogInfo<AliceClient>($"Round ({ongoingRound.State.RoundId})Bob Posted output.");
}
@@ -324,7 +324,7 @@ namespace WalletWasabi.Services
throw new NotSupportedException("Coordinator provided invalid roundHash.");
}
Money amountBack = unsignedCoinJoin.Outputs
- .Where(x => x.ScriptPubKey == ongoingRound.ActiveOutputScript || x.ScriptPubKey == ongoingRound.ChangeOutputScript)
+ .Where(x => x.ScriptPubKey == ongoingRound.ActiveOutputAddress.ScriptPubKey || x.ScriptPubKey == ongoingRound.ChangeOutputAddress.ScriptPubKey)
.Sum(y => y.Value);
Money minAmountBack = ongoingRound.CoinsRegistered.Sum(x => x.Amount); // Start with input sum.
minAmountBack -= ongoingRound.State.FeePerOutputs * 2 + ongoingRound.State.FeePerInputs * ongoingRound.CoinsRegistered.Count; // Minus miner fee.
@@ -333,7 +333,7 @@ namespace WalletWasabi.Services
minAmountBack -= expectedCoordinatorFee; // Minus expected coordinator fee.
// If there's no change output then coordinator protection may happened:
- if (unsignedCoinJoin.Outputs.All(x => x.ScriptPubKey != ongoingRound.ChangeOutputScript))
+ if (unsignedCoinJoin.Outputs.All(x => x.ScriptPubKey != ongoingRound.ChangeOutputAddress.ScriptPubKey))
{
Money minimumOutputAmount = new Money(0.0001m, MoneyUnit.BTC); // If the change would be less than about $1 then add it to the coordinator.
Money onePercentOfDenomination = new Money(actualDenomination.ToDecimal(MoneyUnit.BTC) * 0.01m, MoneyUnit.BTC); // If the change is less than about 1% of the newDenomination then add it to the coordinator fee.
@@ -399,86 +399,86 @@ namespace WalletWasabi.Services
Interlocked.Exchange(ref _frequentStatusProcessingIfNotMixing, 0);
}
- #region ScriptLists
+ #region CustomAddressesLists
- public IEnumerable<Script> GetCustomChangeScripts()
+ public IEnumerable<BitcoinAddress> GetCustomChangeAddresses()
{
- lock(CustomChangeScriptsLock)
+ lock(CustomChangeAddressesLock)
{
- return CustomChangeScripts;
+ return CustomChangeAddresses;
}
}
- public IEnumerable<Script> GetCustomActiveScripts()
+ public IEnumerable<BitcoinAddress> GetCustomActiveAddresses()
{
- lock (CustomActiveScriptsLock)
+ lock (CustomActiveAddressesLock)
{
- return CustomActiveScripts;
+ return CustomActiveAddresses;
}
}
/// <summary>
- /// Best effort. For example if a round is disrupted my malicious actors, the script won't be registered again, therefore it's not guaranteed money will arrive.
+ /// Best effort. For example if a round is disrupted my malicious actors, the address won't be registered again, therefore it's not guaranteed money will arrive.
/// </summary>
- public void AddCustomActiveScript(Script script, bool beginning = false)
+ public void AddCustomActiveAddress(BitcoinAddress address, bool beginning = false)
{
- lock (CustomActiveScriptsLock)
+ lock (CustomActiveAddressesLock)
{
- if(CustomActiveScripts.Contains(script))
+ if(CustomActiveAddresses.Contains(address))
{
- CustomActiveScripts.Remove(script);
+ CustomActiveAddresses.Remove(address);
}
if (beginning)
{
- CustomActiveScripts.Insert(0, script);
+ CustomActiveAddresses.Insert(0, address);
}
else
{
- CustomActiveScripts.Add(script);
+ CustomActiveAddresses.Add(address);
}
}
}
/// <summary>
- /// Best effort. For example if a round is disrupted my malicious actors, the script won't be registered again, therefore it's not guaranteed money will arrive.
+ /// Best effort. For example if a round is disrupted my malicious actors, the address won't be registered again, therefore it's not guaranteed money will arrive.
/// </summary>
- public void AddCustomChangeScript(Script script, bool beginning = false)
+ public void AddCustomChangeAddress(BitcoinAddress address, bool beginning = false)
{
- lock (CustomChangeScriptsLock)
+ lock (CustomChangeAddressesLock)
{
- if (CustomChangeScripts.Contains(script))
+ if (CustomChangeAddresses.Contains(address))
{
- CustomChangeScripts.Remove(script);
+ CustomChangeAddresses.Remove(address);
}
if (beginning)
{
- CustomChangeScripts.Insert(0, script);
+ CustomChangeAddresses.Insert(0, address);
}
else
{
- CustomChangeScripts.Add(script);
+ CustomChangeAddresses.Add(address);
}
}
}
- public void RemoveCustomChangeScript(Script script)
+ public void RemoveCustomChangeAddress(BitcoinAddress address)
{
- lock (CustomChangeScriptsLock)
+ lock (CustomChangeAddressesLock)
{
- if (CustomChangeScripts.Contains(script))
+ if (CustomChangeAddresses.Contains(address))
{
- CustomChangeScripts.Remove(script);
+ CustomChangeAddresses.Remove(address);
}
}
}
- public void RemoveCustomActiveScript(Script script)
+ public void RemoveCustomActiveAddress(BitcoinAddress address)
{
- lock (CustomActiveScriptsLock)
+ lock (CustomActiveAddressesLock)
{
- if (CustomActiveScripts.Contains(script))
+ if (CustomActiveAddresses.Contains(address))
{
- CustomActiveScripts.Remove(script);
+ CustomActiveAddresses.Remove(address);
}
}
}

MODIFY
WalletWasabi/WebClients/ChaumianCoinJoin/AliceClient.cs
WalletWasabi/WebClients/ChaumianCoinJoin/AliceClient.cs
@@ -67,12 +67,12 @@ namespace WalletWasabi.WebClients.ChaumianCoinJoin
}
}
- public static async Task<AliceClient> CreateNewAsync(Script changeOutput, byte[] blindedData, IEnumerable<InputProofModel> inputs, Uri baseUri, IPEndPoint torSocks5EndPoint = null)
+ public static async Task<AliceClient> CreateNewAsync(BitcoinAddress changeOutput, byte[] blindedData, IEnumerable<InputProofModel> inputs, Uri baseUri, IPEndPoint torSocks5EndPoint = null)
{
var request = new InputsRequest
{
BlindedOutputScriptHex = ByteHelpers.ToHex(blindedData),
- ChangeOutputScript = changeOutput.ToString(),
+ ChangeOutputAddress = changeOutput.ToString(),
Inputs = inputs
};
return await CreateNewAsync(request, baseUri, torSocks5EndPoint);

MODIFY
WalletWasabi/WebClients/ChaumianCoinJoin/BobClient.cs
WalletWasabi/WebClients/ChaumianCoinJoin/BobClient.cs
@@ -23,9 +23,9 @@ namespace WalletWasabi.WebClients.ChaumianCoinJoin
TorClient = new TorHttpClient(baseUri, torSocks5EndPoint, isolateStream: true);
}
- public async Task PostOutputAsync(string roundHash, Script activeOutput, byte[] unblindedSignature)
+ public async Task PostOutputAsync(string roundHash, BitcoinAddress activeOutputAddress, byte[] unblindedSignature)
{
- var request = new OutputRequest() { OutputScript = activeOutput.ToString(), SignatureHex = ByteHelpers.ToHex(unblindedSignature) };
+ var request = new OutputRequest() { OutputAddress = activeOutputAddress.ToString(), SignatureHex = ByteHelpers.ToHex(unblindedSignature) };
using (var response = await TorClient.SendAsync(HttpMethod.Post, $"/api/v1/btc/chaumiancoinjoin/output?roundHash={roundHash}", request.ToHttpStringContent()))
{
if (response.StatusCode != HttpStatusCode.NoContent)

Make sure a smartly crafted scriptpubkey won't be able to DoS the coordinator