public async Task InputUnconfirmedAsync()
        {
            using Key key = new();
            WabiSabiConfig cfg   = new();
            var            round = WabiSabiFactory.CreateRound(cfg);

            var mockRpc = new Mock <IRPCClient>();

            mockRpc.Setup(rpc => rpc.GetTxOutAsync(It.IsAny <uint256>(), It.IsAny <int>(), It.IsAny <bool>()))
            .ReturnsAsync(new NBitcoin.RPC.GetTxOutResponse {
                Confirmations = 0
            });

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, mockRpc, round);

            var arenaClient = WabiSabiFactory.CreateArenaClient(arena);

            var ex = await Assert.ThrowsAsync <WabiSabiProtocolException>(
                async() => await arenaClient.RegisterInputAsync(round.Id, BitcoinFactory.CreateOutPoint(), key, CancellationToken.None));

            Assert.Equal(WabiSabiProtocolErrorCode.InputUnconfirmed, ex.ErrorCode);

            await arena.StopAsync(CancellationToken.None);
        }
        public async Task IncorrectRequestedAmountCredentialsAsync()
        {
            WabiSabiConfig cfg   = new();
            var            round = WabiSabiFactory.CreateRound(cfg);

            round.SetPhase(Phase.ConnectionConfirmation);
            var alice = WabiSabiFactory.CreateAlice();

            round.Alices.Add(alice);
            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, round);

            var incorrectAmountCredentials = WabiSabiFactory.CreateRealCredentialRequests(round, Money.Coins(3), null).amountRequest;
            var req = WabiSabiFactory.CreateConnectionConfirmationRequest(round) with {
                RealAmountCredentialRequests = incorrectAmountCredentials
            };

            await using ArenaRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
            var ex = await Assert.ThrowsAsync <WabiSabiProtocolException>(async() => await handler.ConfirmConnectionAsync(req, CancellationToken.None));

            Assert.Equal(WabiSabiProtocolErrorCode.IncorrectRequestedAmountCredentials, ex.ErrorCode);
            Assert.False(alice.ConfirmedConnection);

            await arena.StopAsync(CancellationToken.None);
        }
예제 #3
0
        public async Task RemoveInputAsyncTest()
        {
            var config = new WabiSabiConfig();
            var round  = WabiSabiFactory.CreateRound(config);

            round.SetPhase(Phase.ConnectionConfirmation);
            var fundingTx = BitcoinFactory.CreateSmartTransaction(ownOutputCount: 1);
            var coin      = fundingTx.WalletOutputs.First().Coin;
            var alice     = new Alice(new Dictionary <Coin, byte[]> {
                { coin, Array.Empty <byte>() }
            });

            round.Alices.Add(alice);
            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(config, round);

            await using var coordinator = new ArenaRequestHandler(config, new Prison(), arena, arena.Rpc);
            var apiClient = new ArenaClient(null !, null !, coordinator);

            round.SetPhase(Phase.InputRegistration);

            await apiClient.RemoveInputAsync(round.Id, alice.Id);

            Assert.Empty(round.Alices);
        }
        public async Task InputRegistrationTimeoutCanBeModifiedRuntimeAsync()
        {
            WabiSabiConfig cfg = new() { StandardInputRegistrationTimeout = TimeSpan.FromHours(1) };

            using Key key = new();
            var coin = WabiSabiFactory.CreateCoin(key);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, WabiSabiFactory.CreatePreconfiguredRpcClient(coin));

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            var round = arena.Rounds.First();

            cfg.StandardInputRegistrationTimeout = TimeSpan.Zero;

            var arenaClient = WabiSabiFactory.CreateArenaClient(arena);
            var ex          = await Assert.ThrowsAsync <WabiSabiProtocolException>(
                async() => await arenaClient.RegisterInputAsync(round.Id, coin.Outpoint, key, CancellationToken.None));

            Assert.Equal(WabiSabiProtocolErrorCode.WrongPhase, ex.ErrorCode);
            Assert.Equal(Phase.InputRegistration, round.Phase);

            await arena.StopAsync(CancellationToken.None);
        }
        public async Task AlicesSpentAsync()
        {
            WabiSabiConfig cfg = new()
            {
                MaxInputCountByRound           = 2,
                MinInputCountByRoundMultiplier = 0.5
            };
            var mockRpc = new MockRpcClient();

            mockRpc.OnSendRawTransactionAsync = _ => throw new RPCException(RPCErrorCode.RPC_TRANSACTION_REJECTED, "", null);
            mockRpc.OnGetTxOutAsync ??= (_, _, _) => null;

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, mockRpc);

            // Create the round.
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            var round = Assert.Single(arena.Rounds).Value;

            // Register Alices.
            using Key key1 = new();
            using Key key2 = new();
            var irreq1 = WabiSabiFactory.CreateInputsRegistrationRequest(key1, round);
            var irres1 = await arena.RegisterInputAsync(
                irreq1.RoundId,
                irreq1.InputRoundSignaturePairs.ToDictionary(x => new Coin(x.Input, new TxOut(Money.Coins(1), key1.PubKey.GetSegwitAddress(Network.Main))), x => x.RoundSignature),
                irreq1.ZeroAmountCredentialRequests,
                irreq1.ZeroWeightCredentialRequests);

            var irreq2 = WabiSabiFactory.CreateInputsRegistrationRequest(key2, round);
            var irres2 = await arena.RegisterInputAsync(
                irreq2.RoundId,
                irreq2.InputRoundSignaturePairs.ToDictionary(x => new Coin(x.Input, new TxOut(Money.Coins(1), key2.PubKey.GetSegwitAddress(Network.Main))), x => x.RoundSignature),
                irreq2.ZeroAmountCredentialRequests,
                irreq2.ZeroWeightCredentialRequests);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.ConnectionConfirmation, round.Phase);
            var alice1 = round.Alices.Single(x => x.Id == irres1.AliceId);
            var alice2 = round.Alices.Single(x => x.Id == irres2.AliceId);

            // Confirm connections.
            var ccresps = new List <(ConnectionConfirmationResponse resp, WabiSabiClient amountClient, WabiSabiClient weightClient, Guid aliceId)>();
            var ccreq1  = WabiSabiFactory.CreateConnectionConfirmationRequest(round, irres1);
            var ccresp1 = await arena.ConfirmConnectionAsync(ccreq1.request);

            ccresps.Add((ccresp1, ccreq1.amountClient, ccreq1.weightClient, irres2.AliceId));
            ccreq1.amountClient.HandleResponse(ccresp1.RealAmountCredentials !, ccreq1.amountValidation);
            ccreq1.weightClient.HandleResponse(ccresp1.RealWeightCredentials !, ccreq1.weightValidation);

            var ccreq2  = WabiSabiFactory.CreateConnectionConfirmationRequest(round, irres2);
            var ccresp2 = await arena.ConfirmConnectionAsync(ccreq2.request);

            ccresps.Add((ccresp2, ccreq2.amountClient, ccreq2.weightClient, irres1.AliceId));
            ccreq2.amountClient.HandleResponse(ccresp2.RealAmountCredentials !, ccreq2.amountValidation);
            ccreq2.weightClient.HandleResponse(ccresp2.RealWeightCredentials !, ccreq2.weightValidation);
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.OutputRegistration, round.Phase);

            // Register outputs.
            foreach (var orreq in WabiSabiFactory.CreateOutputRegistrationRequests(round, ccresps))
            {
                var orresp = await arena.RegisterOutputAsync(orreq);
            }
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.TransactionSigning, round.Phase);

            var signedCoinJoin = round.Coinjoin.Clone();
            var coin1          = alice1.Coins.First();
            var coin2          = alice2.Coins.First();
            var idx1           = signedCoinJoin.Inputs.IndexOf(signedCoinJoin.Inputs.Single(x => x.PrevOut == coin1.Outpoint));
            var idx2           = signedCoinJoin.Inputs.IndexOf(signedCoinJoin.Inputs.Single(x => x.PrevOut == coin2.Outpoint));

            signedCoinJoin.Sign(key1.GetBitcoinSecret(Network.Main), coin1);
            var txsigreq1 = new TransactionSignaturesRequest(round.Id, new[] { new InputWitnessPair((uint)idx1, signedCoinJoin.Inputs[idx1].WitScript) });

            signedCoinJoin.Sign(key2.GetBitcoinSecret(Network.Main), coin2);
            var txsigreq2 = new TransactionSignaturesRequest(round.Id, new[] { new InputWitnessPair((uint)idx2, signedCoinJoin.Inputs[idx2].WitScript) });

            await arena.SignTransactionAsync(txsigreq1);

            await arena.SignTransactionAsync(txsigreq2);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.DoesNotContain(round.Id, arena.Rounds.Keys);

            // There should be no inmate, because we aren't punishing spenders with banning
            // as there's no reason to ban already spent UTXOs,
            // the cost of spending the UTXO is the punishment instead.
            Assert.Empty(arena.Prison.GetInmates());

            await arena.StopAsync(CancellationToken.None);
        }
예제 #6
0
        public async Task RegisterInputAsyncTest()
        {
            var config = new WabiSabiConfig();
            var round  = WabiSabiFactory.CreateRound(config);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(config, round);

            using var key = new Key();
            var outpoint = BitcoinFactory.CreateOutPoint();

            var mockRpc = new Mock <IRPCClient>();

            mockRpc.Setup(rpc => rpc.GetTxOutAsync(outpoint.Hash, (int)outpoint.N, true))
            .ReturnsAsync(new NBitcoin.RPC.GetTxOutResponse
            {
                IsCoinBase    = false,
                Confirmations = 200,
                TxOut         = new TxOut(Money.Coins(1m), key.PubKey.WitHash.GetAddress(Network.Main)),
            });
            await using var coordinator = new ArenaRequestHandler(config, new Prison(), arena, mockRpc.Object);

            var rnd = new InsecureRandom();
            var protocolCredentialNumber  = 2;
            var protocolMaxWeightPerAlice = 1_000L;
            var amountClient = new WabiSabiClient(round.AmountCredentialIssuerParameters, protocolCredentialNumber, rnd, 4_300_000_000_000ul);
            var weightClient = new WabiSabiClient(round.WeightCredentialIssuerParameters, protocolCredentialNumber, rnd, (ulong)protocolMaxWeightPerAlice);

            var apiClient = new ArenaClient(amountClient, weightClient, coordinator);

            var aliceId = await apiClient.RegisterInputAsync(Money.Coins(1m), outpoint, key, round.Id, round.Hash);

            Assert.NotEqual(Guid.Empty, aliceId);
            Assert.Empty(apiClient.AmountCredentialClient.Credentials.Valuable);

            var reissuanceAmounts = new[]
            {
                Money.Coins(.75m) - round.FeeRate.GetFee(Constants.P2wpkhInputVirtualSize),
                Money.Coins(.25m)
            };

            var inputWeight           = 4 * Constants.P2wpkhInputVirtualSize;
            var inputRemainingWeights = new[] { protocolMaxWeightPerAlice - inputWeight };

            // Phase: Input Registration
            await apiClient.ConfirmConnectionAsync(
                round.Id,
                aliceId,
                inputRemainingWeights,
                apiClient.AmountCredentialClient.Credentials.ZeroValue.Take(protocolCredentialNumber),
                reissuanceAmounts);

            Assert.Empty(apiClient.AmountCredentialClient.Credentials.Valuable);

            // Phase: Connection Confirmation
            round.SetPhase(Phase.ConnectionConfirmation);
            await apiClient.ConfirmConnectionAsync(
                round.Id,
                aliceId,
                inputRemainingWeights,
                apiClient.AmountCredentialClient.Credentials.ZeroValue.Take(protocolCredentialNumber),
                reissuanceAmounts);

            Assert.Single(apiClient.AmountCredentialClient.Credentials.Valuable, x => x.Amount.ToMoney() == reissuanceAmounts.First());
            Assert.Single(apiClient.AmountCredentialClient.Credentials.Valuable, x => x.Amount.ToMoney() == reissuanceAmounts.Last());
        }
예제 #7
0
        public async Task SomeBobsRegisteredTimeoutAsync()
        {
            WabiSabiConfig cfg = new()
            {
                MaxInputCountByRound           = 2,
                MinInputCountByRoundMultiplier = 0.5,
                OutputRegistrationTimeout      = TimeSpan.Zero
            };

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg);

            // Create the round.
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            var round = Assert.Single(arena.Rounds).Value;

            // Register Alices.
            using Key key1 = new();
            var irreq1 = WabiSabiFactory.CreateInputsRegistrationRequest(key1, round);
            var irres1 = await arena.RegisterInputAsync(
                irreq1.RoundId,
                irreq1.InputRoundSignaturePairs.ToDictionary(x => new Coin(x.Input, new TxOut(Money.Coins(1), key1.PubKey.GetSegwitAddress(Network.Main))), x => x.RoundSignature),
                irreq1.ZeroAmountCredentialRequests,
                irreq1.ZeroVsizeCredentialRequests);

            using Key key2 = new();
            var irreq2 = WabiSabiFactory.CreateInputsRegistrationRequest(key2, round);
            var irres2 = await arena.RegisterInputAsync(
                irreq2.RoundId,
                irreq2.InputRoundSignaturePairs.ToDictionary(x => new Coin(x.Input, new TxOut(Money.Coins(1), key2.PubKey.GetSegwitAddress(Network.Main))), x => x.RoundSignature),
                irreq2.ZeroAmountCredentialRequests,
                irreq2.ZeroVsizeCredentialRequests);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.ConnectionConfirmation, round.Phase);

            // Confirm connections.
            var ccresps = new List <(ConnectionConfirmationResponse resp, WabiSabiClient amountClient, WabiSabiClient vsizeClient, Guid aliceId)>();

            var ccreq1  = WabiSabiFactory.CreateConnectionConfirmationRequest(round, irres1);
            var ccresp1 = await arena.ConfirmConnectionAsync(ccreq1.request);

            ccresps.Add((ccresp1, ccreq1.amountClient, ccreq1.vsizeClient, irres2.AliceId));
            ccreq1.amountClient.HandleResponse(ccresp1.RealAmountCredentials !, ccreq1.amountValidation);
            ccreq1.vsizeClient.HandleResponse(ccresp1.RealVsizeCredentials !, ccreq1.vsizeValidation);

            var ccreq2  = WabiSabiFactory.CreateConnectionConfirmationRequest(round, irres2);
            var ccresp2 = await arena.ConfirmConnectionAsync(ccreq2.request);

            ccresps.Add((ccresp2, ccreq2.amountClient, ccreq2.vsizeClient, irres1.AliceId));
            ccreq2.amountClient.HandleResponse(ccresp2.RealAmountCredentials !, ccreq2.amountValidation);
            ccreq2.vsizeClient.HandleResponse(ccresp2.RealVsizeCredentials !, ccreq2.vsizeValidation);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.OutputRegistration, round.Phase);

            // Register outputs.
            var orresp = await arena.RegisterOutputAsync(WabiSabiFactory.CreateOutputRegistrationRequests(round, ccresps).First());

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.TransactionSigning, round.Phase);
            Assert.Equal(2, round.Coinjoin.Inputs.Count);
            Assert.Equal(2, round.Coinjoin.Outputs.Count);
            Assert.Contains(cfg.BlameScript, round.Coinjoin.Outputs.Select(x => x.ScriptPubKey));

            await arena.StopAsync(CancellationToken.None);
        }
예제 #8
0
        public async Task FullCoinjoinAsyncTestAsync()
        {
            var config = new WabiSabiConfig {
                MaxInputCountByRound = 1
            };
            var round = WabiSabiFactory.CreateRound(config);

            round.MaxVsizeAllocationPerAlice = 255;
            using var key = new Key();
            var outpoint = BitcoinFactory.CreateOutPoint();
            var mockRpc  = new Mock <IRPCClient>();

            mockRpc.Setup(rpc => rpc.GetTxOutAsync(outpoint.Hash, (int)outpoint.N, true))
            .ReturnsAsync(new NBitcoin.RPC.GetTxOutResponse
            {
                IsCoinBase    = false,
                Confirmations = 200,
                TxOut         = new TxOut(Money.Coins(1m), key.PubKey.WitHash.GetAddress(Network.Main)),
            });
            mockRpc.Setup(rpc => rpc.EstimateSmartFeeAsync(It.IsAny <int>(), It.IsAny <EstimateSmartFeeMode>()))
            .ReturnsAsync(new EstimateSmartFeeResponse
            {
                Blocks  = 1000,
                FeeRate = new FeeRate(10m)
            });
            mockRpc.Setup(rpc => rpc.GetMempoolInfoAsync(It.IsAny <CancellationToken>()))
            .ReturnsAsync(new MemPoolInfo
            {
                MinRelayTxFee = 1
            });
            mockRpc.Setup(rpc => rpc.PrepareBatch()).Returns(mockRpc.Object);
            mockRpc.Setup(rpc => rpc.SendBatchAsync()).Returns(Task.CompletedTask);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(config, mockRpc, round);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));

            await using var coordinator = new ArenaRequestHandler(config, new Prison(), arena, mockRpc.Object);
            var wabiSabiApi      = new WabiSabiController(coordinator);
            var insecureRandom   = new InsecureRandom();
            var roundState       = RoundState.FromRound(round);
            var aliceArenaClient = new ArenaClient(
                roundState.CreateAmountCredentialClient(insecureRandom),
                roundState.CreateVsizeCredentialClient(insecureRandom),
                wabiSabiApi);

            var inputRegistrationResponse = await aliceArenaClient.RegisterInputAsync(round.Id, outpoint, key, CancellationToken.None);

            var aliceId = inputRegistrationResponse.Value;

            var inputVsize       = Constants.P2wpkhInputVirtualSize;
            var amountsToRequest = new[]
            {
                Money.Coins(.75m) - round.FeeRate.GetFee(inputVsize),
                Money.Coins(.25m),
            }.Select(x => x.Satoshi).ToArray();

            using var destinationKey1 = new Key();
            using var destinationKey2 = new Key();
            var p2wpkhScriptSize = (long)destinationKey1.PubKey.WitHash.ScriptPubKey.EstimateOutputVsize();

            var vsizesToRequest = new[] { roundState.MaxVsizeAllocationPerAlice - (inputVsize + 2 * p2wpkhScriptSize), 2 * p2wpkhScriptSize };

            // Phase: Input Registration
            Assert.Equal(Phase.InputRegistration, round.Phase);

            var connectionConfirmationResponse1 = await aliceArenaClient.ConfirmConnectionAsync(
                round.Id,
                aliceId,
                amountsToRequest,
                vsizesToRequest,
                inputRegistrationResponse.IssuedAmountCredentials,
                inputRegistrationResponse.IssuedVsizeCredentials,
                CancellationToken.None);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));

            Assert.Equal(Phase.ConnectionConfirmation, round.Phase);

            // Phase: Connection Confirmation
            var connectionConfirmationResponse2 = await aliceArenaClient.ConfirmConnectionAsync(
                round.Id,
                aliceId,
                amountsToRequest,
                vsizesToRequest,
                connectionConfirmationResponse1.IssuedAmountCredentials,
                connectionConfirmationResponse1.IssuedVsizeCredentials,
                CancellationToken.None);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(1));

            // Phase: Output Registration
            Assert.Equal(Phase.OutputRegistration, round.Phase);

            var bobArenaClient = new ArenaClient(
                roundState.CreateAmountCredentialClient(insecureRandom),
                roundState.CreateVsizeCredentialClient(insecureRandom),
                wabiSabiApi);

            var reissuanceResponse = await bobArenaClient.ReissueCredentialAsync(
                round.Id,
                amountsToRequest,
                Enumerable.Repeat(p2wpkhScriptSize, 2),
                connectionConfirmationResponse2.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber),
                connectionConfirmationResponse2.IssuedVsizeCredentials.Skip(1).Take(ProtocolConstants.CredentialNumber),                 // first amount is the leftover value
                CancellationToken.None);

            Credential amountCred1     = reissuanceResponse.IssuedAmountCredentials.ElementAt(0);
            Credential amountCred2     = reissuanceResponse.IssuedAmountCredentials.ElementAt(1);
            Credential zeroAmountCred1 = reissuanceResponse.IssuedAmountCredentials.ElementAt(2);
            Credential zeroAmountCred2 = reissuanceResponse.IssuedAmountCredentials.ElementAt(3);

            Credential vsizeCred1     = reissuanceResponse.IssuedVsizeCredentials.ElementAt(0);
            Credential vsizeCred2     = reissuanceResponse.IssuedVsizeCredentials.ElementAt(1);
            Credential zeroVsizeCred1 = reissuanceResponse.IssuedVsizeCredentials.ElementAt(2);
            Credential zeroVsizeCred2 = reissuanceResponse.IssuedVsizeCredentials.ElementAt(3);

            await bobArenaClient.RegisterOutputAsync(
                round.Id,
                destinationKey1.PubKey.WitHash.ScriptPubKey,
                new[] { amountCred1, zeroAmountCred1 },
                new[] { vsizeCred1, zeroVsizeCred1 },
                CancellationToken.None);

            await bobArenaClient.RegisterOutputAsync(
                round.Id,
                destinationKey2.PubKey.WitHash.ScriptPubKey,
                new[] { amountCred2, zeroAmountCred2 },
                new[] { vsizeCred2, zeroVsizeCred2 },
                CancellationToken.None);

            await aliceArenaClient.ReadyToSignAsync(round.Id, aliceId, CancellationToken.None);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));

            Assert.Equal(Phase.TransactionSigning, round.Phase);

            var tx = round.Assert <SigningState>().CreateTransaction();

            Assert.Single(tx.Inputs);
            Assert.Equal(2, tx.Outputs.Count);
        }
예제 #9
0
        public async Task SignTransactionAsync()
        {
            WabiSabiConfig config = new();
            Round          round  = WabiSabiFactory.CreateRound(config);

            using Key key1 = new();
            Alice alice1 = WabiSabiFactory.CreateAlice(key: key1, round: round);

            round.Alices.Add(alice1);

            using Key key2 = new();
            Alice alice2 = WabiSabiFactory.CreateAlice(key: key2, round: round);

            round.Alices.Add(alice2);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(config, round);

            var mockRpc = new Mock <IRPCClient>();

            await using var coordinator = new ArenaRequestHandler(config, new Prison(), arena, mockRpc.Object);
            var wabiSabiApi = new WabiSabiController(coordinator);

            var rnd          = new InsecureRandom();
            var amountClient = new WabiSabiClient(round.AmountCredentialIssuerParameters, rnd, 4300000000000L);
            var vsizeClient  = new WabiSabiClient(round.VsizeCredentialIssuerParameters, rnd, 2000L);
            var apiClient    = new ArenaClient(amountClient, vsizeClient, wabiSabiApi);

            round.SetPhase(Phase.TransactionSigning);

            var emptyState = round.Assert <ConstructionState>();

            // We can't use ``emptyState.Finalize()` because this is not a valid transaction so we fake it
            var finalizedEmptyState = new SigningState(emptyState.Parameters, emptyState.Inputs, emptyState.Outputs);

            // No inputs in the CoinJoin.
            await Assert.ThrowsAsync <ArgumentException>(async() =>
                                                         await apiClient.SignTransactionAsync(round.Id, alice1.Coin, new BitcoinSecret(key1, Network.Main), finalizedEmptyState.CreateUnsignedTransaction(), CancellationToken.None));

            var oneInput = emptyState.AddInput(alice1.Coin).Finalize();

            round.CoinjoinState = oneInput;

            // Trying to sign coins those are not in the CoinJoin.
            await Assert.ThrowsAsync <InvalidOperationException>(async() =>
                                                                 await apiClient.SignTransactionAsync(round.Id, alice2.Coin, new BitcoinSecret(key2, Network.Main), oneInput.CreateUnsignedTransaction(), CancellationToken.None));

            var twoInputs = emptyState.AddInput(alice1.Coin).AddInput(alice2.Coin).Finalize();

            round.CoinjoinState = twoInputs;

            // Trying to sign coins with the wrong secret.
            await Assert.ThrowsAsync <InvalidOperationException>(async() =>
                                                                 await apiClient.SignTransactionAsync(round.Id, alice1.Coin, new BitcoinSecret(key2, Network.Main), twoInputs.CreateUnsignedTransaction(), CancellationToken.None));

            Assert.False(round.Assert <SigningState>().IsFullySigned);

            var unsigned = round.Assert <SigningState>().CreateUnsignedTransaction();

            await apiClient.SignTransactionAsync(round.Id, alice1.Coin, new BitcoinSecret(key1, Network.Main), unsigned, CancellationToken.None);

            Assert.True(round.Assert <SigningState>().IsInputSigned(alice1.Coin.Outpoint));
            Assert.False(round.Assert <SigningState>().IsInputSigned(alice2.Coin.Outpoint));

            Assert.False(round.Assert <SigningState>().IsFullySigned);

            await apiClient.SignTransactionAsync(round.Id, alice2.Coin, new BitcoinSecret(key2, Network.Main), unsigned, CancellationToken.None);

            Assert.True(round.Assert <SigningState>().IsInputSigned(alice2.Coin.Outpoint));

            Assert.True(round.Assert <SigningState>().IsFullySigned);
        }
        public async Task TimeoutSufficientPeersAsync()
        {
            WabiSabiConfig cfg = new()
            {
                MaxInputCountByRound           = 2,
                MinInputCountByRoundMultiplier = 1,
                TransactionSigningTimeout      = TimeSpan.Zero,
                OutputRegistrationTimeout      = TimeSpan.Zero
            };

            using Key key1 = new();
            using Key key2 = new();
            var coin1 = WabiSabiFactory.CreateCoin(key1);
            var coin2 = WabiSabiFactory.CreateCoin(key2);

            var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1, coin2);

            mockRpc.Setup(rpc => rpc.SendRawTransactionAsync(It.IsAny <Transaction>()))
            .ThrowsAsync(new RPCException(RPCErrorCode.RPC_TRANSACTION_REJECTED, "", null));

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, mockRpc);

            var(round, aliceClient1, aliceClient2) = await CreateRoundWithOutputsReadyToSignAsync(arena, key1, coin1, key2, coin2);

            // Make sure not all alices signed.
            var alice3 = WabiSabiFactory.CreateAlice(round);

            alice3.ConfirmedConnection = true;
            round.Alices.Add(alice3);
            round.CoinjoinState = round.Assert <ConstructionState>().AddInput(alice3.Coin);
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.TransactionSigning, round.Phase);

            var signedCoinJoin = round.Assert <SigningState>().CreateTransaction();
            await aliceClient1.SignTransactionAsync(signedCoinJoin, CancellationToken.None);

            await aliceClient2.SignTransactionAsync(signedCoinJoin, CancellationToken.None);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.DoesNotContain(round, arena.ActiveRounds);
            Assert.Single(arena.Rounds.Where(x => x.IsBlameRound));
            var badOutpoint = alice3.Coin.Outpoint;

            Assert.Contains(badOutpoint, arena.Prison.GetInmates().Select(x => x.Utxo));

            var blameRound = arena.Rounds.Single(x => x.IsBlameRound);

            Assert.True(blameRound.IsBlameRound);
            Assert.NotNull(blameRound.BlameOf);
            Assert.Equal(round.Id, blameRound.BlameOf?.Id);

            var whitelist = blameRound.BlameWhitelist;

            Assert.Contains(aliceClient1.Coin.Outpoint, whitelist);
            Assert.Contains(aliceClient2.Coin.Outpoint, whitelist);
            Assert.DoesNotContain(badOutpoint, whitelist);

            await arena.StopAsync(CancellationToken.None);
        }
        public async Task DiffTooSmallToBlameAsync()
        {
            WabiSabiConfig cfg = new()
            {
                MaxInputCountByRound           = 2,
                MinInputCountByRoundMultiplier = 0.5,
                OutputRegistrationTimeout      = TimeSpan.Zero
            };

            using Key key1 = new();
            using Key key2 = new();
            var coin1 = WabiSabiFactory.CreateCoin(key1);
            var coin2 = WabiSabiFactory.CreateCoin(key2);

            var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1, coin2);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, mockRpc);

            var(round, arenaClient, alices) = await CreateRoundWithTwoConfirmedConnectionsAsync(arena, key1, coin1, key2, coin2);

            var(amountCredentials1, vsizeCredentials1) = (alices[0].IssuedAmountCredentials, alices[0].IssuedVsizeCredentials);
            var(amountCredentials2, vsizeCredentials2) = (alices[1].IssuedAmountCredentials, alices[1].IssuedVsizeCredentials);

            // Register outputs.
            var bobClient = new BobClient(round.Id, arenaClient);

            using var destKey1 = new Key();
            using var destKey2 = new Key();
            await bobClient.RegisterOutputAsync(
                coin1.Amount - round.FeeRate.GetFee(coin1.ScriptPubKey.EstimateInputVsize()),
                destKey1.PubKey.WitHash.ScriptPubKey,
                amountCredentials1.Take(ProtocolConstants.CredentialNumber),
                vsizeCredentials1.Take(ProtocolConstants.CredentialNumber),
                CancellationToken.None);

            await bobClient.RegisterOutputAsync(
                coin2.Amount - round.FeeRate.GetFee(coin2.ScriptPubKey.EstimateInputVsize()),
                destKey2.PubKey.WitHash.ScriptPubKey,
                amountCredentials2.Take(ProtocolConstants.CredentialNumber),
                vsizeCredentials2.Take(ProtocolConstants.CredentialNumber),
                CancellationToken.None);

            // Add another input. The input must be able to pay for itself, but
            // the remaining amount after deducting the fees needs to be less
            // than the minimum.
            var txParams   = round.Assert <ConstructionState>().Parameters;
            var extraAlice = WabiSabiFactory.CreateAlice(txParams.FeeRate.GetFee(Constants.P2wpkhInputVirtualSize) + txParams.AllowedOutputAmounts.Min - new Money(1L), round);

            round.Alices.Add(extraAlice);
            round.CoinjoinState = round.Assert <ConstructionState>().AddInput(extraAlice.Coin);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.TransactionSigning, round.Phase);
            var tx = round.Assert <SigningState>().CreateTransaction();

            Assert.Equal(3, tx.Inputs.Count);
            Assert.Equal(2, tx.Outputs.Count);
            Assert.DoesNotContain(cfg.BlameScript, tx.Outputs.Select(x => x.ScriptPubKey));

            await arena.StopAsync(CancellationToken.None);
        }
예제 #12
0
        public async Task RegisterOutputTestAsync()
        {
            var config = new WabiSabiConfig {
                MaxInputCountByRound = 1
            };
            var       round = WabiSabiFactory.CreateRound(config);
            var       km    = ServiceFactory.CreateKeyManager("");
            var       key   = BitcoinFactory.CreateHdPubKey(km);
            SmartCoin coin1 = BitcoinFactory.CreateSmartCoin(key, Money.Coins(2m));

            var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1.Coin);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(config, mockRpc, round);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));

            await using var coordinator = new ArenaRequestHandler(config, new Prison(), arena, mockRpc.Object);
            var insecureRandom   = new InsecureRandom();
            var wabiSabiApi      = new WabiSabiController(coordinator);
            var roundState       = RoundState.FromRound(round);
            var aliceArenaClient = new ArenaClient(
                roundState.CreateAmountCredentialClient(insecureRandom),
                roundState.CreateVsizeCredentialClient(insecureRandom),
                wabiSabiApi);
            var bobArenaClient = new ArenaClient(
                roundState.CreateAmountCredentialClient(insecureRandom),
                roundState.CreateVsizeCredentialClient(insecureRandom),
                wabiSabiApi);

            Assert.Equal(Phase.InputRegistration, round.Phase);

            var bitcoinSecret = km.GetSecrets("", coin1.ScriptPubKey).Single().PrivateKey.GetBitcoinSecret(Network.Main);

            using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), wabiSabiApi);
            await roundStateUpdater.StartAsync(CancellationToken.None);

            var task = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), aliceArenaClient, coin1, bitcoinSecret, roundStateUpdater, CancellationToken.None);

            do
            {
                await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));
            }while (round.Phase != Phase.ConnectionConfirmation);

            var aliceClient = await task;

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));

            Assert.Equal(Phase.OutputRegistration, round.Phase);

            using var destinationKey = new Key();
            var destination = destinationKey.PubKey.WitHash.ScriptPubKey;

            var bobClient = new BobClient(round.Id, bobArenaClient);

            await bobClient.RegisterOutputAsync(
                destination,
                aliceClient.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber),
                aliceClient.IssuedVsizeCredentials.Take(ProtocolConstants.CredentialNumber),
                CancellationToken.None);

            var bob = Assert.Single(round.Bobs);

            Assert.Equal(destination, bob.Script);

            var credentialAmountSum = aliceClient.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber).Sum(x => x.Value);

            Assert.Equal(credentialAmountSum, bob.CredentialAmount);
        }
예제 #13
0
        public async Task RegisterOutputTestAsync()
        {
            var config = new WabiSabiConfig {
                MaxInputCountByRound = 1
            };
            var round = WabiSabiFactory.CreateRound(config);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(config, round);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));

            var       km       = ServiceFactory.CreateKeyManager("");
            var       key      = BitcoinFactory.CreateHdPubKey(km);
            SmartCoin coin1    = BitcoinFactory.CreateSmartCoin(key, Money.Coins(2m));
            var       outpoint = coin1.OutPoint;

            var mockRpc = new Mock <IRPCClient>();

            mockRpc.Setup(rpc => rpc.GetTxOutAsync(outpoint.Hash, (int)outpoint.N, true))
            .ReturnsAsync(new NBitcoin.RPC.GetTxOutResponse
            {
                IsCoinBase    = false,
                Confirmations = coin1.Height,
                TxOut         = coin1.TxOut,
            });

            CredentialPool amountCredential = new();
            CredentialPool vsizeCredential  = new();

            await using var coordinator = new ArenaRequestHandler(config, new Prison(), arena, mockRpc.Object);
            var aliceArenaClient = new ArenaClient(round.AmountCredentialIssuerParameters, round.VsizeCredentialIssuerParameters, amountCredential, vsizeCredential, coordinator, new InsecureRandom());
            var bobArenaClient   = new ArenaClient(round.AmountCredentialIssuerParameters, round.VsizeCredentialIssuerParameters, amountCredential, vsizeCredential, coordinator, new InsecureRandom());

            Assert.Equal(Phase.InputRegistration, round.Phase);

            var bitcoinSecret = km.GetSecrets("", coin1.ScriptPubKey).Single().PrivateKey.GetBitcoinSecret(Network.Main);
            var aliceClient   = await AliceClient.CreateNewAsync(aliceArenaClient, new[] { coin1.Coin }, bitcoinSecret, round.Id, round.Hash, round.FeeRate);

            Task confirmationTask = aliceClient.ConfirmConnectionAsync(TimeSpan.FromSeconds(3), CancellationToken.None);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));

            await confirmationTask;

            Assert.Equal(Phase.ConnectionConfirmation, round.Phase);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));

            Assert.Equal(Phase.OutputRegistration, round.Phase);

            using var destinationKey1 = new Key();
            using var destinationKey2 = new Key();
            using var destinationKey3 = new Key();
            using var destinationKey4 = new Key();

            var bobClient = new BobClient(round.Id, bobArenaClient);
            await bobClient.RegisterOutputAsync(Money.Coins(0.25m), destinationKey1.PubKey.WitHash.ScriptPubKey);

            await bobClient.RegisterOutputAsync(Money.Coins(0.25m), destinationKey2.PubKey.WitHash.ScriptPubKey);

            await bobClient.RegisterOutputAsync(Money.Coins(0.25m), destinationKey3.PubKey.WitHash.ScriptPubKey);

            await bobClient.RegisterOutputAsync(Money.Coins(0.25m), destinationKey4.PubKey.WitHash.ScriptPubKey);

            Assert.Equal(4, round.Bobs.Count);
        }
예제 #14
0
        public async Task RegisterOutputTestAsync()
        {
            var config = new WabiSabiConfig {
                MaxInputCountByRound = 1
            };
            var       round = WabiSabiFactory.CreateRound(config);
            var       km    = ServiceFactory.CreateKeyManager("");
            var       key   = BitcoinFactory.CreateHdPubKey(km);
            SmartCoin coin1 = BitcoinFactory.CreateSmartCoin(key, Money.Coins(2m));

            var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1.Coin);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(config, mockRpc, round);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));

            ZeroCredentialPool zeroAmountCredential = new();
            ZeroCredentialPool zeroVsizeCredential  = new();

            await using var coordinator = new ArenaRequestHandler(config, new Prison(), arena, mockRpc.Object);
            var insecureRandom   = new InsecureRandom();
            var wabiSabiApi      = new WabiSabiController(coordinator);
            var roundState       = RoundState.FromRound(round);
            var aliceArenaClient = new ArenaClient(
                roundState.CreateAmountCredentialClient(zeroAmountCredential, insecureRandom),
                roundState.CreateVsizeCredentialClient(zeroVsizeCredential, insecureRandom),
                wabiSabiApi);
            var bobArenaClient = new ArenaClient(
                roundState.CreateAmountCredentialClient(zeroAmountCredential, insecureRandom),
                roundState.CreateVsizeCredentialClient(zeroVsizeCredential, insecureRandom),
                wabiSabiApi);

            Assert.Equal(Phase.InputRegistration, round.Phase);

            var bitcoinSecret = km.GetSecrets("", coin1.ScriptPubKey).Single().PrivateKey.GetBitcoinSecret(Network.Main);

            var aliceClient = new AliceClient(round.Id, aliceArenaClient, coin1.Coin, round.FeeRate, bitcoinSecret);
            await aliceClient.RegisterInputAsync(CancellationToken.None);

            Task confirmationTask = aliceClient.ConfirmConnectionAsync(TimeSpan.FromSeconds(1), roundState.MaxVsizeAllocationPerAlice, CancellationToken.None);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));

            await confirmationTask;

            Assert.Equal(Phase.ConnectionConfirmation, round.Phase);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));

            Assert.Equal(Phase.OutputRegistration, round.Phase);

            using var destinationKey = new Key();
            var destination = destinationKey.PubKey.WitHash.ScriptPubKey;

            var bobClient = new BobClient(round.Id, bobArenaClient);

            await bobClient.RegisterOutputAsync(Money.Coins(0.25m), destination, aliceClient.RealAmountCredentials, aliceClient.RealVsizeCredentials, CancellationToken.None);

            var bob = Assert.Single(round.Bobs);

            Assert.Equal(destination, bob.Script);
            Assert.Equal(25_000_000, bob.CredentialAmount);
        }
        public async Task DoesntSwitchImmaturelyAsync()
        {
            WabiSabiConfig cfg = new()
            {
                MaxInputCountByRound           = 2,
                MinInputCountByRoundMultiplier = 0.5
            };

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg);

            // Create the round.
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            var round = Assert.Single(arena.Rounds).Value;

            // Register Alices.
            using Key key1 = new();
            var irreq1 = WabiSabiFactory.CreateInputsRegistrationRequest(key1, round);
            var irres1 = await arena.RegisterInputAsync(
                irreq1.RoundId,
                irreq1.InputRoundSignaturePairs.ToDictionary(x => new Coin(x.Input, new TxOut(Money.Coins(1), key1.PubKey.GetSegwitAddress(Network.Main))), x => x.RoundSignature),
                irreq1.ZeroAmountCredentialRequests,
                irreq1.ZeroWeightCredentialRequests);

            using Key key2 = new();
            var irreq2 = WabiSabiFactory.CreateInputsRegistrationRequest(key2, round);
            var irres2 = await arena.RegisterInputAsync(
                irreq2.RoundId,
                irreq2.InputRoundSignaturePairs.ToDictionary(x => new Coin(x.Input, new TxOut(Money.Coins(1), key2.PubKey.GetSegwitAddress(Network.Main))), x => x.RoundSignature),
                irreq2.ZeroAmountCredentialRequests,
                irreq2.ZeroWeightCredentialRequests);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.ConnectionConfirmation, round.Phase);

            // Confirm connections.
            var ccresps      = new List <(ConnectionConfirmationResponse resp, WabiSabiClient amountClient, WabiSabiClient weightClient, Guid aliceId)>();
            var ugliSolution = false;

            foreach (var ccreq in WabiSabiFactory.CreateConnectionConfirmationRequests(round, irres1, irres2))
            {
                var ccresp = await arena.ConfirmConnectionAsync(ccreq.request);

                ccresps.Add((ccresp, ccreq.amountClient, ccreq.weightClient, ugliSolution ? irres1.AliceId : irres2.AliceId));
                ugliSolution = true;
                ccreq.amountClient.HandleResponse(ccresp.RealAmountCredentials !, ccreq.amountValidation);
                ccreq.weightClient.HandleResponse(ccresp.RealWeightCredentials !, ccreq.weightValidation);
            }
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.OutputRegistration, round.Phase);

            // Register outputs.
            var orresp = await arena.RegisterOutputAsync(WabiSabiFactory.CreateOutputRegistrationRequests(round, ccresps).First());

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.OutputRegistration, round.Phase);

            await arena.StopAsync(CancellationToken.None);
        }
    }
        public async Task EveryoneSignedAsync()
        {
            WabiSabiConfig cfg = new()
            {
                MaxInputCountByRound           = 2,
                MinInputCountByRoundMultiplier = 0.5
            };

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg);

            // Create the round.
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            var round = Assert.Single(arena.Rounds).Value;

            // Register Alices.
            using Key key1 = new();
            using Key key2 = new();
            var irreq1 = WabiSabiFactory.CreateInputsRegistrationRequest(key1, round);
            var irres1 = await arena.RegisterInputAsync(
                irreq1.RoundId,
                irreq1.InputRoundSignaturePairs.ToDictionary(x => new Coin(x.Input, new TxOut(Money.Coins(1), key1.PubKey.GetSegwitAddress(Network.Main))), x => x.RoundSignature),
                irreq1.ZeroAmountCredentialRequests,
                irreq1.ZeroWeightCredentialRequests);

            var irreq2 = WabiSabiFactory.CreateInputsRegistrationRequest(key2, round);
            var irres2 = await arena.RegisterInputAsync(
                irreq2.RoundId,
                irreq2.InputRoundSignaturePairs.ToDictionary(x => new Coin(x.Input, new TxOut(Money.Coins(1), key2.PubKey.GetSegwitAddress(Network.Main))), x => x.RoundSignature),
                irreq2.ZeroAmountCredentialRequests,
                irreq2.ZeroWeightCredentialRequests);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.ConnectionConfirmation, round.Phase);
            var alice1 = round.Alices.Single(x => x.Id == irres1.AliceId);
            var alice2 = round.Alices.Single(x => x.Id == irres2.AliceId);

            // Confirm connections.
            var ccresps = new List <(ConnectionConfirmationResponse resp, WabiSabiClient amountClient, WabiSabiClient weightClient, Guid aliceId)>();
            var ccreq1  = WabiSabiFactory.CreateConnectionConfirmationRequest(round, irres1);
            var ccresp1 = await arena.ConfirmConnectionAsync(ccreq1.request);

            ccresps.Add((ccresp1, ccreq1.amountClient, ccreq1.weightClient, irres2.AliceId));
            ccreq1.amountClient.HandleResponse(ccresp1.RealAmountCredentials !, ccreq1.amountValidation);
            ccreq1.weightClient.HandleResponse(ccresp1.RealWeightCredentials !, ccreq1.weightValidation);

            var ccreq2  = WabiSabiFactory.CreateConnectionConfirmationRequest(round, irres2);
            var ccresp2 = await arena.ConfirmConnectionAsync(ccreq2.request);

            ccresps.Add((ccresp2, ccreq2.amountClient, ccreq2.weightClient, irres1.AliceId));
            ccreq2.amountClient.HandleResponse(ccresp2.RealAmountCredentials !, ccreq2.amountValidation);
            ccreq2.weightClient.HandleResponse(ccresp2.RealWeightCredentials !, ccreq2.weightValidation);
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.OutputRegistration, round.Phase);

            // Register outputs.
            foreach (var orreq in WabiSabiFactory.CreateOutputRegistrationRequests(round, ccresps))
            {
                var orresp = await arena.RegisterOutputAsync(orreq);
            }
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.TransactionSigning, round.Phase);

            var signedCoinJoin = round.Coinjoin.Clone();
            var coin1          = alice1.Coins.First();
            var coin2          = alice2.Coins.First();
            var idx1           = signedCoinJoin.Inputs.IndexOf(signedCoinJoin.Inputs.Single(x => x.PrevOut == coin1.Outpoint));
            var idx2           = signedCoinJoin.Inputs.IndexOf(signedCoinJoin.Inputs.Single(x => x.PrevOut == coin2.Outpoint));

            signedCoinJoin.Sign(key1.GetBitcoinSecret(Network.Main), coin1);
            var txsigreq1 = new TransactionSignaturesRequest(round.Id, new[] { new InputWitnessPair((uint)idx1, signedCoinJoin.Inputs[idx1].WitScript) });

            signedCoinJoin.Sign(key2.GetBitcoinSecret(Network.Main), coin2);
            var txsigreq2 = new TransactionSignaturesRequest(round.Id, new[] { new InputWitnessPair((uint)idx2, signedCoinJoin.Inputs[idx2].WitScript) });

            await arena.SignTransactionAsync(txsigreq1);

            await arena.SignTransactionAsync(txsigreq2);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.TransactionBroadcasting, round.Phase);

            await arena.StopAsync(CancellationToken.None);
        }
        public async Task TimeoutSufficientPeersAsync()
        {
            WabiSabiConfig cfg = new()
            {
                MaxInputCountByRound           = 2,
                MinInputCountByRoundMultiplier = 1,
                TransactionSigningTimeout      = TimeSpan.Zero,
                OutputRegistrationTimeout      = TimeSpan.Zero
            };
            var mockRpc = new MockRpcClient();

            mockRpc.OnSendRawTransactionAsync = _ => throw new RPCException(RPCErrorCode.RPC_TRANSACTION_REJECTED, "", null);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, mockRpc);

            // Create the round.
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            var round = Assert.Single(arena.Rounds).Value;

            // Register Alices.
            using Key key1 = new();
            using Key key2 = new();
            var irreq1 = WabiSabiFactory.CreateInputsRegistrationRequest(key1, round);
            var irres1 = await arena.RegisterInputAsync(
                irreq1.RoundId,
                irreq1.InputRoundSignaturePairs.ToDictionary(x => new Coin(x.Input, new TxOut(Money.Coins(1), key1.PubKey.GetSegwitAddress(Network.Main))), x => x.RoundSignature),
                irreq1.ZeroAmountCredentialRequests,
                irreq1.ZeroWeightCredentialRequests);

            var irreq2 = WabiSabiFactory.CreateInputsRegistrationRequest(key2, round);
            var irres2 = await arena.RegisterInputAsync(
                irreq2.RoundId,
                irreq2.InputRoundSignaturePairs.ToDictionary(x => new Coin(x.Input, new TxOut(Money.Coins(1), key2.PubKey.GetSegwitAddress(Network.Main))), x => x.RoundSignature),
                irreq2.ZeroAmountCredentialRequests,
                irreq2.ZeroWeightCredentialRequests);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.ConnectionConfirmation, round.Phase);
            var alice1 = round.Alices.Single(x => x.Id == irres1.AliceId);
            var alice2 = round.Alices.Single(x => x.Id == irres2.AliceId);

            // Confirm connections.
            var ccresps = new List <(ConnectionConfirmationResponse resp, WabiSabiClient amountClient, WabiSabiClient weightClient, Guid aliceId)>();
            var ccreq1  = WabiSabiFactory.CreateConnectionConfirmationRequest(round, irres1);
            var ccresp1 = await arena.ConfirmConnectionAsync(ccreq1.request);

            ccresps.Add((ccresp1, ccreq1.amountClient, ccreq1.weightClient, irres2.AliceId));
            ccreq1.amountClient.HandleResponse(ccresp1.RealAmountCredentials !, ccreq1.amountValidation);
            ccreq1.weightClient.HandleResponse(ccresp1.RealWeightCredentials !, ccreq1.weightValidation);

            var ccreq2  = WabiSabiFactory.CreateConnectionConfirmationRequest(round, irres2);
            var ccresp2 = await arena.ConfirmConnectionAsync(ccreq2.request);

            ccresps.Add((ccresp2, ccreq2.amountClient, ccreq2.weightClient, irres1.AliceId));
            ccreq2.amountClient.HandleResponse(ccresp2.RealAmountCredentials !, ccreq2.amountValidation);
            ccreq2.weightClient.HandleResponse(ccresp2.RealWeightCredentials !, ccreq2.weightValidation);
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.OutputRegistration, round.Phase);

            // Register outputs.
            foreach (var orreq in WabiSabiFactory.CreateOutputRegistrationRequests(round, ccresps))
            {
                var orresp = await arena.RegisterOutputAsync(orreq);
            }

            // Make sure not all alices signed.
            var alice3 = WabiSabiFactory.CreateAlice();

            alice3.ConfirmedConnection = true;
            round.Alices.Add(alice3);
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.TransactionSigning, round.Phase);

            var signedCoinJoin = round.Coinjoin.Clone();
            var coin1          = alice1.Coins.First();
            var coin2          = alice2.Coins.First();
            var idx1           = signedCoinJoin.Inputs.IndexOf(signedCoinJoin.Inputs.Single(x => x.PrevOut == coin1.Outpoint));
            var idx2           = signedCoinJoin.Inputs.IndexOf(signedCoinJoin.Inputs.Single(x => x.PrevOut == coin2.Outpoint));

            signedCoinJoin.Sign(key1.GetBitcoinSecret(Network.Main), coin1);
            var txsigreq1 = new TransactionSignaturesRequest(round.Id, new[] { new InputWitnessPair((uint)idx1, signedCoinJoin.Inputs[idx1].WitScript) });

            signedCoinJoin.Sign(key2.GetBitcoinSecret(Network.Main), coin2);
            var txsigreq2 = new TransactionSignaturesRequest(round.Id, new[] { new InputWitnessPair((uint)idx2, signedCoinJoin.Inputs[idx2].WitScript) });

            await arena.SignTransactionAsync(txsigreq1);

            await arena.SignTransactionAsync(txsigreq2);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.DoesNotContain(round.Id, arena.Rounds.Keys);
            Assert.Single(arena.Rounds.Where(x => x.Value.IsBlameRound));
            var badOutpoint = alice3.Coins.Select(x => x.Outpoint).First();

            Assert.Contains(badOutpoint, arena.Prison.GetInmates().Select(x => x.Utxo));

            var blameRound = arena.Rounds.Single(x => x.Value.IsBlameRound).Value;

            Assert.True(blameRound.IsBlameRound);
            Assert.NotNull(blameRound.BlameOf);
            Assert.Equal(round.Id, blameRound.BlameOf?.Id);

            var whitelist = blameRound.BlameWhitelist;

            Assert.Contains(alice1.Coins.Select(x => x.Outpoint).First(), whitelist);
            Assert.Contains(alice2.Coins.Select(x => x.Outpoint).First(), whitelist);
            Assert.DoesNotContain(badOutpoint, whitelist);

            await arena.StopAsync(CancellationToken.None);
        }
    }
예제 #18
0
        public async Task FullCoinjoinAsyncTest()
        {
            var config = new WabiSabiConfig {
                MaxInputCountByRound = 1
            };
            var round = WabiSabiFactory.CreateRound(config);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(config, round);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(1));

            using var key = new Key();
            var outpoint = BitcoinFactory.CreateOutPoint();

            var mockRpc = new Mock <IRPCClient>();

            mockRpc.Setup(rpc => rpc.GetTxOutAsync(outpoint.Hash, (int)outpoint.N, true))
            .ReturnsAsync(new NBitcoin.RPC.GetTxOutResponse
            {
                IsCoinBase    = false,
                Confirmations = 200,
                TxOut         = new TxOut(Money.Coins(1m), key.PubKey.WitHash.GetAddress(Network.Main)),
            });
            await using var coordinator = new ArenaRequestHandler(config, new Prison(), arena, mockRpc.Object);

            var apiClient         = new ArenaClient(round.AmountCredentialIssuerParameters, round.WeightCredentialIssuerParameters, coordinator, new InsecureRandom());
            var amountCredentials = apiClient.AmountCredentialClient.Credentials;
            var weightCredentials = apiClient.WeightCredentialClient.Credentials;

            var aliceId = await apiClient.RegisterInputAsync(Money.Coins(1m), outpoint, key, round.Id, round.Hash);

            Assert.NotEqual(Guid.Empty, aliceId);
            Assert.Empty(amountCredentials.Valuable);

            var reissuanceAmounts = new[]
            {
                Money.Coins(.75m) - round.FeeRate.GetFee(Constants.P2wpkhInputVirtualSize),
                Money.Coins(.25m)
            };

            var inputWeight           = Constants.WitnessScaleFactor * Constants.P2wpkhInputVirtualSize;
            var inputRemainingWeights = new[] { (long)ArenaClient.ProtocolMaxWeightPerAlice - inputWeight };

            // Phase: Input Registration
            Assert.Equal(Phase.InputRegistration, round.Phase);

            await apiClient.ConfirmConnectionAsync(
                round.Id,
                aliceId,
                inputRemainingWeights,
                amountCredentials.ZeroValue.Take(ArenaClient.ProtocolCredentialNumber),
                reissuanceAmounts);

            Assert.Empty(amountCredentials.Valuable);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(1));

            Assert.Equal(Phase.ConnectionConfirmation, round.Phase);

            // Phase: Connection Confirmation
            await apiClient.ConfirmConnectionAsync(
                round.Id,
                aliceId,
                inputRemainingWeights,
                amountCredentials.ZeroValue.Take(ArenaClient.ProtocolCredentialNumber),
                reissuanceAmounts);

            Assert.Single(apiClient.AmountCredentialClient.Credentials.Valuable, x => x.Amount.ToMoney() == reissuanceAmounts.First());
            Assert.Single(apiClient.AmountCredentialClient.Credentials.Valuable, x => x.Amount.ToMoney() == reissuanceAmounts.Last());

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(1));

            Assert.Equal(Phase.OutputRegistration, round.Phase);

            // Phase: Output Registration
            using var destinationKey1 = new Key();
            using var destinationKey2 = new Key();

            await apiClient.RegisterOutputAsync(
                round.Id,
                reissuanceAmounts[0],
                destinationKey1.PubKey.WitHash.ScriptPubKey,
                apiClient.AmountCredentialClient.Credentials.Valuable,
                apiClient.WeightCredentialClient.Credentials.Valuable);

            await apiClient.RegisterOutputAsync(
                round.Id,
                reissuanceAmounts[1],
                destinationKey2.PubKey.WitHash.ScriptPubKey,
                apiClient.AmountCredentialClient.Credentials.Valuable,
                apiClient.WeightCredentialClient.Credentials.Valuable);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(Phase.TransactionSigning, round.Phase);

            Assert.Equal(1, round.Coinjoin.Inputs.Count);
            Assert.Equal(2, round.Coinjoin.Outputs.Count);
        }