public static InputsRegistrationResponse RegisterInput( WabiSabiConfig config, Guid roundId, IDictionary <Coin, byte[]> coinRoundSignaturePairs, ZeroCredentialsRequest zeroAmountCredentialRequests, ZeroCredentialsRequest zeroVsizeCredentialRequests, IDictionary <Guid, Round> rounds, Network network) { if (!rounds.TryGetValue(roundId, out var round)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.RoundNotFound); } var coins = coinRoundSignaturePairs.Select(x => x.Key); if (round.MaxInputCountByAlice < coins.Count()) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooManyInputs); } if (round.IsBlameRound && coins.Select(x => x.Outpoint).Any(x => !round.BlameWhitelist.Contains(x))) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.InputNotWhitelisted); } foreach (var coinRoundSignaturePair in coinRoundSignaturePairs) { var coin = coinRoundSignaturePair.Key; var signature = coinRoundSignaturePair.Value; var coinJoinInputCommitmentData = new CoinJoinInputCommitmentData("CoinJoinCoordinatorIdentifier", round.Hash); if (!OwnershipProof.VerifyCoinJoinInputProof(signature, coin.TxOut.ScriptPubKey, coinJoinInputCommitmentData)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongRoundSignature); } } var alice = new Alice(coinRoundSignaturePairs); if (alice.TotalInputAmount < round.MinRegistrableAmount) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.NotEnoughFunds); } if (alice.TotalInputAmount > round.MaxRegistrableAmount) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchFunds); } if (alice.TotalInputVsize > round.PerAliceVsizeAllocation) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchVsize); } if (round.IsInputRegistrationEnded(config.MaxInputCountByRound, config.GetInputRegistrationTimeout(round))) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase); } if (round.RemainingInputVsizeAllocation < round.PerAliceVsizeAllocation) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchTotalWeight); } var commitAmountCredentialResponse = round.AmountCredentialIssuer.PrepareResponse(zeroAmountCredentialRequests); var commitVsizeCredentialResponse = round.VsizeCredentialIssuer.PrepareResponse(zeroVsizeCredentialRequests); RemoveDuplicateAlices(rounds, alice); alice.SetDeadlineRelativeTo(round.ConnectionConfirmationTimeout); round.Alices.Add(alice); return(new(alice.Id, commitAmountCredentialResponse.Commit(), commitVsizeCredentialResponse.Commit())); }
public async Task <InputRegistrationResponse> RegisterInputAsync(InputRegistrationRequest request, CancellationToken cancellationToken) { var coin = await OutpointToCoinAsync(request, cancellationToken).ConfigureAwait(false); using (await AsyncLock.LockAsync(cancellationToken).ConfigureAwait(false)) { var round = GetRound(request.RoundId); var registeredCoins = Rounds.Where(x => !(x.Phase == Phase.Ended && !x.WasTransactionBroadcast)) .SelectMany(r => r.Alices.Select(a => a.Coin)); if (registeredCoins.Any(x => x.Outpoint == coin.Outpoint)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.AliceAlreadyRegistered); } if (round.IsInputRegistrationEnded(Config.MaxInputCountByRound)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase); } if (round is BlameRound blameRound && !blameRound.BlameWhitelist.Contains(coin.Outpoint)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.InputNotWhitelisted); } // Compute but don't commit updated CoinJoin to round state, it will // be re-calculated on input confirmation. This is computed it here // for validation purposes. _ = round.Assert <ConstructionState>().AddInput(coin); var coinJoinInputCommitmentData = new CoinJoinInputCommitmentData("CoinJoinCoordinatorIdentifier", round.Id); if (!OwnershipProof.VerifyCoinJoinInputProof(request.OwnershipProof, coin.TxOut.ScriptPubKey, coinJoinInputCommitmentData)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongOwnershipProof); } // Generate a new GUID with the secure random source, to be sure // that it is not guessable (Guid.NewGuid() documentation does // not say anything about GUID version or randomness source, // only that the probability of duplicates is very low). var id = new Guid(Random.GetBytes(16)); var alice = new Alice(coin, request.OwnershipProof, round, id); if (alice.TotalInputAmount < round.MinAmountCredentialValue) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.NotEnoughFunds); } if (alice.TotalInputAmount > round.MaxAmountCredentialValue) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchFunds); } if (alice.TotalInputVsize > round.MaxVsizeAllocationPerAlice) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchVsize); } var amountCredentialTask = round.AmountCredentialIssuer.HandleRequestAsync(request.ZeroAmountCredentialRequests, cancellationToken); var vsizeCredentialTask = round.VsizeCredentialIssuer.HandleRequestAsync(request.ZeroVsizeCredentialRequests, cancellationToken); if (round.RemainingInputVsizeAllocation < round.MaxVsizeAllocationPerAlice) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.VsizeQuotaExceeded); } var commitAmountCredentialResponse = await amountCredentialTask.ConfigureAwait(false); var commitVsizeCredentialResponse = await vsizeCredentialTask.ConfigureAwait(false); alice.SetDeadlineRelativeTo(round.ConnectionConfirmationTimeFrame.Duration); round.Alices.Add(alice); return(new(alice.Id, commitAmountCredentialResponse, commitVsizeCredentialResponse)); } }
public static InputsRegistrationResponse RegisterInput( WabiSabiConfig config, Guid roundId, IDictionary <Coin, byte[]> coinRoundSignaturePairs, ZeroCredentialsRequest zeroAmountCredentialRequests, ZeroCredentialsRequest zeroWeightCredentialRequests, IDictionary <Guid, Round> rounds, Network network) { if (!rounds.TryGetValue(roundId, out var round)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.RoundNotFound); } var alice = new Alice(coinRoundSignaturePairs); var coins = alice.Coins; if (round.MaxInputCountByAlice < coins.Count()) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooManyInputs); } if (round.IsBlameRound && coins.Select(x => x.Outpoint).Any(x => !round.BlameWhitelist.Contains(x))) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.InputNotWhitelisted); } var inputValueSum = Money.Zero; var inputWeightSum = 0; foreach (var coinRoundSignaturePair in alice.CoinRoundSignaturePairs) { var coin = coinRoundSignaturePair.Key; var signature = coinRoundSignaturePair.Value; var coinJoinInputCommitmentData = new CoinJoinInputCommitmentData("CoinJoinCoordinatorIdentifier", round.Hash); if (!OwnershipProof.VerifyCoinJoinInputProof(signature, coin.TxOut.ScriptPubKey, coinJoinInputCommitmentData)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongRoundSignature); } inputValueSum += coin.TxOut.Value; // Convert conservative P2WPKH size in virtual bytes to weight units. inputWeightSum += coin.TxOut.ScriptPubKey.EstimateInputVsize() * 4; } if (inputValueSum < round.MinRegistrableAmount) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.NotEnoughFunds); } if (inputValueSum > round.MaxRegistrableAmount) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchFunds); } if (inputWeightSum > round.RegistrableWeightCredentials) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchWeight); } if (round.IsInputRegistrationEnded(config.MaxInputCountByRound, config.GetInputRegistrationTimeout(round))) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase); } var amountCredentialResponse = round.AmountCredentialIssuer.HandleRequest(zeroAmountCredentialRequests); var weightCredentialResponse = round.WeightCredentialIssuer.HandleRequest(zeroWeightCredentialRequests); RemoveDuplicateAlices(rounds, alice); alice.SetDeadlineRelativeTo(round.ConnectionConfirmationTimeout); round.Alices.Add(alice); return(new(alice.Id, amountCredentialResponse, weightCredentialResponse)); }
public static InputRegistrationResponse RegisterInput( WabiSabiConfig config, uint256 roundId, Coin coin, OwnershipProof ownershipProof, ZeroCredentialsRequest zeroAmountCredentialRequests, ZeroCredentialsRequest zeroVsizeCredentialRequests, HashSet <Round> rounds) { if (rounds.FirstOrDefault(x => x.Id == roundId) is not Round round) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.RoundNotFound); } if (round.IsInputRegistrationEnded(config.MaxInputCountByRound, config.GetInputRegistrationTimeout(round))) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase); } if (round.IsBlameRound && !round.BlameWhitelist.Contains(coin.Outpoint)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.InputNotWhitelisted); } // Compute but don't commit updated CoinJoin to round state, it will // be re-calculated on input confirmation. This is computed it here // for validation purposes. round.Assert <ConstructionState>().AddInput(coin); var coinJoinInputCommitmentData = new CoinJoinInputCommitmentData("CoinJoinCoordinatorIdentifier", round.Id); if (!OwnershipProof.VerifyCoinJoinInputProof(ownershipProof, coin.TxOut.ScriptPubKey, coinJoinInputCommitmentData)) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongOwnershipProof); } var alice = new Alice(coin, ownershipProof); if (alice.TotalInputAmount < round.MinRegistrableAmount) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.NotEnoughFunds); } if (alice.TotalInputAmount > round.MaxRegistrableAmount) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchFunds); } if (alice.TotalInputVsize > round.MaxVsizeAllocationPerAlice) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchVsize); } if (round.RemainingInputVsizeAllocation < round.MaxVsizeAllocationPerAlice) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.VsizeQuotaExceeded); } var commitAmountCredentialResponse = round.AmountCredentialIssuer.PrepareResponse(zeroAmountCredentialRequests); var commitVsizeCredentialResponse = round.VsizeCredentialIssuer.PrepareResponse(zeroVsizeCredentialRequests); alice.SetDeadlineRelativeTo(round.ConnectionConfirmationTimeout); round.Alices.Add(alice); return(new(alice.Id, commitAmountCredentialResponse.Commit(), commitVsizeCredentialResponse.Commit())); }