Sign all blinded outputs
MODIFY
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
using NBitcoin.BouncyCastle.Math;
+using NBitcoin.Crypto;
using NBitcoin.Protocol;
using NBitcoin.RPC;
using Newtonsoft.Json.Linq;
@@ -196,6 +197,8 @@ namespace WalletWasabi.Backend.Controllers
await batch.SendBatchAsync();
await waiting;
+ byte[] blindedOutputScriptHashesByte = ByteHelpers.Combine(blindedOutputs.Select(x=>x.ToBytes()));
+ uint256 blindedOutputScriptsHash = new uint256(Hashes.SHA256(blindedOutputScriptHashesByte));
var inputs = new HashSet<Coin>();
@@ -248,7 +251,7 @@ namespace WalletWasabi.Backend.Controllers
var address = (BitcoinWitPubKeyAddress)txout.ScriptPubKey.GetDestinationAddress(Network);
// Check if proofs are valid.
- if (!address.VerifyMessage(blindedOutputs.First(), inputProof.Proof))
+ if (!address.VerifyMessage(blindedOutputScriptsHash, inputProof.Proof))
{
return BadRequest("Provided proof is invalid.");
}

MODIFY
WalletWasabi.Tests/RegTests.cs
WalletWasabi.Tests/RegTests.cs
@@ -1797,7 +1797,9 @@ namespace WalletWasabi.Tests
uint256 msg = new uint256(Hashes.SHA256(network.Consensus.ConsensusFactory.CreateTransaction().ToBytes()));
uint256 blindedData = requester.BlindMessage(msg, round.MixingLevels.GetBaseLevel().SchnorrKey.SchnorrPubKey);
inputsRequest.BlindedOutputScripts = new[] { blindedData };
- proof = key.SignCompact(inputsRequest.BlindedOutputScripts.First());
+ uint256 blindedOutputScriptsHash = new uint256(Hashes.SHA256(blindedData.ToBytes()));
+
+ proof = key.SignCompact(blindedOutputScriptsHash);
inputsRequest.Inputs.First().Proof = proof;
httpRequestException = await Assert.ThrowsAsync<HttpRequestException>(async () => await AliceClient.CreateNewAsync(network, inputsRequest, baseUri));
Assert.StartsWith($"{HttpStatusCode.BadRequest.ToReasonString()}\nNot enough inputs are provided. Fee to pay:", httpRequestException.Message);
@@ -1822,7 +1824,8 @@ namespace WalletWasabi.Tests
msg = network.Consensus.ConsensusFactory.CreateTransaction().GetHash();
blindedData = requester.BlindMessage(msg, round.MixingLevels.GetBaseLevel().SchnorrKey.SchnorrPubKey);
inputsRequest.BlindedOutputScripts = new [] { blindedData };
- proof = key.SignCompact(inputsRequest.BlindedOutputScripts.First());
+ blindedOutputScriptsHash = new uint256(Hashes.SHA256(blindedData.ToBytes()));
+ proof = key.SignCompact(blindedOutputScriptsHash);
inputsRequest.Inputs.First().Proof = proof;
coordinator.AbortAllRoundsInInputRegistration(nameof(RegTests), "");
using (var aliceClient = await AliceClient.CreateNewAsync(network, inputsRequest, baseUri))
@@ -1862,7 +1865,8 @@ namespace WalletWasabi.Tests
requester = new Requester();
blindedData = requester.BlindScript(round.MixingLevels.GetBaseLevel().SchnorrKey.SchnorrPubKey.SignerPubKey, round.MixingLevels.GetBaseLevel().SchnorrKey.SchnorrPubKey.RpubKey, key.ScriptPubKey);
inputsRequest.BlindedOutputScripts = new[] { blindedData };
- proof = key.SignCompact(new uint256(inputsRequest.BlindedOutputScripts.First()));
+ blindedOutputScriptsHash = new uint256(Hashes.SHA256(blindedData.ToBytes()));
+ proof = key.SignCompact(blindedOutputScriptsHash);
inputsRequest.Inputs.First().Proof = proof;
using (var aliceClient = await AliceClient.CreateNewAsync(network, inputsRequest, baseUri))
{
@@ -1876,7 +1880,8 @@ namespace WalletWasabi.Tests
}
inputsRequest.BlindedOutputScripts = new[] { uint256.One };
- proof = key.SignCompact(new uint256(inputsRequest.BlindedOutputScripts.First()));
+ blindedOutputScriptsHash = new uint256(Hashes.SHA256(uint256.One.ToBytes()));
+ proof = key.SignCompact(blindedOutputScriptsHash);
inputsRequest.Inputs.First().Proof = proof;
inputsRequest.Inputs = new List<InputProofModel> { inputsRequest.Inputs.First(), inputsRequest.Inputs.First() };
httpRequestException = await Assert.ThrowsAsync<HttpRequestException>(async () => await AliceClient.CreateNewAsync(network, inputsRequest, baseUri));
@@ -1891,7 +1896,7 @@ namespace WalletWasabi.Tests
await rpc.GenerateAsync(1);
tx = await rpc.GetRawTransactionAsync(hash);
coin = tx.Outputs.GetCoins(witnessAddress.ScriptPubKey).Single();
- proof = key.SignCompact(new uint256(inputsRequest.BlindedOutputScripts.First()));
+ proof = key.SignCompact(blindedOutputScriptsHash);
inputProofs.Add(new InputProofModel { Input = coin.Outpoint.ToTxoRef(), Proof = proof });
}
await rpc.GenerateAsync(1);
@@ -1969,8 +1974,9 @@ namespace WalletWasabi.Tests
requester = new Requester();
Script script = new Key().PubKey.GetSegwitAddress(network).ScriptPubKey;
blindedData = requester.BlindScript(round.MixingLevels.GetBaseLevel().Signer.Key.PubKey, round.MixingLevels.GetBaseLevel().Signer.R.PubKey, script);
+ blindedOutputScriptsHash = new uint256(Hashes.SHA256(blindedData.ToBytes()));
- using (var aliceClient = await AliceClient.CreateNewAsync(network, new Key().PubKey.GetAddress(network), new[] { blindedData }, new InputProofModel[] { new InputProofModel { Input = coin.Outpoint.ToTxoRef(), Proof = key.SignCompact(blindedData) } }, baseUri))
+ using (var aliceClient = await AliceClient.CreateNewAsync(network, new Key().PubKey.GetAddress(network), new[] { blindedData }, new InputProofModel[] { new InputProofModel { Input = coin.Outpoint.ToTxoRef(), Proof = key.SignCompact(blindedOutputScriptsHash) } }, baseUri))
{
Assert.NotEmpty(aliceClient.BlindedOutputSignatures);
Assert.NotEqual(Guid.Empty, aliceClient.UniqueId);
@@ -2010,7 +2016,7 @@ namespace WalletWasabi.Tests
Assert.Equal($"{HttpStatusCode.Gone.ToReasonString()}\nRound is not running.", httpRequestException.Message);
}
- using (var aliceClient = await AliceClient.CreateNewAsync(network, new Key().PubKey.GetAddress(network), new[] { blindedData }, new InputProofModel[] { new InputProofModel { Input = coin.Outpoint.ToTxoRef(), Proof = key.SignCompact(blindedData) } }, baseUri))
+ using (var aliceClient = await AliceClient.CreateNewAsync(network, new Key().PubKey.GetAddress(network), new[] { blindedData }, new InputProofModel[] { new InputProofModel { Input = coin.Outpoint.ToTxoRef(), Proof = key.SignCompact(blindedOutputScriptsHash) } }, baseUri))
{
Assert.NotEmpty(aliceClient.BlindedOutputSignatures);
Assert.NotEqual(Guid.Empty, aliceClient.UniqueId);
@@ -2065,13 +2071,15 @@ namespace WalletWasabi.Tests
var requester2 = new Requester();
uint256 blinded1 = requester1.BlindScript(round.MixingLevels.GetBaseLevel().Signer.Key.PubKey, round.MixingLevels.GetBaseLevel().Signer.R.PubKey, outputAddress1.ScriptPubKey);
+ uint256 blindedOutputScriptsHash1 = new uint256(Hashes.SHA256(blinded1.ToBytes()));
uint256 blinded2 = requester2.BlindScript(round.MixingLevels.GetBaseLevel().Signer.Key.PubKey, round.MixingLevels.GetBaseLevel().Signer.R.PubKey, outputAddress2.ScriptPubKey);
+ uint256 blindedOutputScriptsHash2 = new uint256(Hashes.SHA256(blinded2.ToBytes()));
var input1 = new OutPoint(hash1, index1);
var input2 = new OutPoint(hash2, index2);
- using (var aliceClient1 = await AliceClient.CreateNewAsync(network, new Key().PubKey.GetAddress(network), new[] { blinded1 }, new InputProofModel[] { new InputProofModel { Input = input1.ToTxoRef(), Proof = key1.SignCompact(blinded1) } }, baseUri))
- using (var aliceClient2 = await AliceClient.CreateNewAsync(network, new Key().PubKey.GetAddress(network), new[] { blinded2 }, new InputProofModel[] { new InputProofModel { Input = input2.ToTxoRef(), Proof = key2.SignCompact(blinded2) } }, baseUri))
+ using (var aliceClient1 = await AliceClient.CreateNewAsync(network, new Key().PubKey.GetAddress(network), new[] { blinded1 }, new InputProofModel[] { new InputProofModel { Input = input1.ToTxoRef(), Proof = key1.SignCompact(blindedOutputScriptsHash1) } }, baseUri))
+ using (var aliceClient2 = await AliceClient.CreateNewAsync(network, new Key().PubKey.GetAddress(network), new[] { blinded2 }, new InputProofModel[] { new InputProofModel { Input = input2.ToTxoRef(), Proof = key2.SignCompact(blindedOutputScriptsHash2) } }, baseUri))
{
Assert.Equal(aliceClient2.RoundId, aliceClient1.RoundId);
Assert.NotEqual(aliceClient2.UniqueId, aliceClient1.UniqueId);
@@ -2203,6 +2211,7 @@ namespace WalletWasabi.Tests
CcjRound round = coordinator.GetCurrentInputRegisterableRound();
var requester = new Requester();
uint256 blinded = requester.BlindScript(round.MixingLevels.GetBaseLevel().Signer.Key.PubKey, round.MixingLevels.GetBaseLevel().Signer.R.PubKey, activeOutputAddress.ScriptPubKey);
+ uint256 blindedOutputScriptsHash = new uint256(Hashes.SHA256(blinded.ToBytes()));
uint256 txHash = await rpc.SendToAddressAsync(inputAddress, Money.Coins(2));
await rpc.GenerateAsync(1);
@@ -2210,7 +2219,7 @@ namespace WalletWasabi.Tests
Coin coin = transaction.Outputs.GetCoins(inputAddress.ScriptPubKey).Single();
OutPoint input = coin.Outpoint;
- InputProofModel inputProof = new InputProofModel { Input = input.ToTxoRef(), Proof = inputKey.SignCompact(blinded) };
+ InputProofModel inputProof = new InputProofModel { Input = input.ToTxoRef(), Proof = inputKey.SignCompact(blindedOutputScriptsHash) };
InputProofModel[] inputsProofs = new InputProofModel[] { inputProof };
registerRequests.Add((changeOutputAddress, blinded, inputsProofs));
await AliceClient.CreateNewAsync(network, changeOutputAddress, new[] { blinded }, inputsProofs, baseUri);
@@ -2279,6 +2288,7 @@ namespace WalletWasabi.Tests
CcjRound round = coordinator.GetCurrentInputRegisterableRound();
Requester requester = new Requester();
uint256 blinded = requester.BlindScript(round.MixingLevels.GetBaseLevel().Signer.Key.PubKey, round.MixingLevels.GetBaseLevel().Signer.R.PubKey, activeOutputAddress.ScriptPubKey);
+ uint256 blindedOutputScriptsHash = new uint256(Hashes.SHA256(blinded.ToBytes()));
var inputProofModels = new List<InputProofModel>();
int numberOfInputs = new Random().Next(1, 7);
@@ -2301,7 +2311,7 @@ namespace WalletWasabi.Tests
var coin = transaction.Outputs.GetCoins(inputAddress.ScriptPubKey).Single();
OutPoint input = coin.Outpoint;
- var inputProof = new InputProofModel { Input = input.ToTxoRef(), Proof = key.SignCompact(blinded) };
+ var inputProof = new InputProofModel { Input = input.ToTxoRef(), Proof = key.SignCompact(blindedOutputScriptsHash) };
inputProofModels.Add(inputProof);
GetTxOutResponse getTxOutResponse = await rpc.GetTxOutAsync(input.Hash, (int)input.N, includeMempool: true);
@@ -2484,6 +2494,7 @@ namespace WalletWasabi.Tests
CcjRound round = coordinator.GetCurrentInputRegisterableRound();
ECDSABlinding.Requester requester = new ECDSABlinding.Requester();
uint256 blinded = requester.BlindScript(round.MixingLevels.GetBaseLevel().Signer.Key.PubKey, round.MixingLevels.GetBaseLevel().Signer.R.PubKey, activeOutputAddress.ScriptPubKey);
+ uint256 blindedOutputScriptsHash = new uint256(Hashes.SHA256(blinded.ToBytes()));
var inputProofModels = new List<InputProofModel>();
int numberOfInputs = new Random().Next(1, 7);
@@ -2506,7 +2517,7 @@ namespace WalletWasabi.Tests
var coin = transaction.Outputs.GetCoins(inputAddress.ScriptPubKey).Single();
OutPoint input = coin.Outpoint;
- var inputProof = new InputProofModel { Input = input.ToTxoRef(), Proof = key.SignCompact(blinded) };
+ var inputProof = new InputProofModel { Input = input.ToTxoRef(), Proof = key.SignCompact(blindedOutputScriptsHash) };
inputProofModels.Add(inputProof);
GetTxOutResponse getTxOutResponse = await rpc.GetTxOutAsync(input.Hash, (int)input.N, includeMempool: true);

MODIFY
WalletWasabi/Services/CcjClient.cs
WalletWasabi/Services/CcjClient.cs
@@ -567,6 +567,9 @@ namespace WalletWasabi.Services
registeredAddresses.Add(address);
}
+ byte[] blindedOutputScriptHashesByte = ByteHelpers.Combine(blindedOutputScriptHashes.Select(x=>x.ToBytes()));
+ uint256 blindedOutputScriptsHash = new uint256(Hashes.SHA256(blindedOutputScriptHashesByte));
+
var inputProofs = new List<InputProofModel>();
foreach ((uint256 txid, uint index) coinReference in registrableCoins)
{
@@ -576,7 +579,7 @@ namespace WalletWasabi.Services
var inputProof = new InputProofModel
{
Input = coin.GetTxoRef(),
- Proof = coin.Secret.PrivateKey.SignCompact(blindedOutputScriptHashes.First())
+ Proof = coin.Secret.PrivateKey.SignCompact(blindedOutputScriptsHash)
};
inputProofs.Add(inputProof);
}

Sign all blinded outputs