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()));
        }
Ejemplo n.º 2
0
        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));
            }
        }
Ejemplo n.º 3
0
        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()));
        }