Example #1
0
    public async Task RegisterBannedCoinAsync()
    {
        using CancellationTokenSource timeoutCts = new(TimeSpan.FromMinutes(2));

        var bannedOutPoint = BitcoinFactory.CreateOutPoint();

        var httpClient = _apiApplicationFactory.WithWebHostBuilder(builder =>
                                                                   builder.ConfigureServices(services =>
        {
            var inmate = new Inmate(bannedOutPoint, Punishment.LongBanned, DateTimeOffset.UtcNow, uint256.One);
            services.AddScoped <Prison>(_ => new Prison(new[] { inmate }));
        })).CreateClient();

        var apiClient = await _apiApplicationFactory.CreateArenaClientAsync(httpClient);

        var rounds = (await apiClient.GetStatusAsync(RoundStateRequest.Empty, timeoutCts.Token)).RoundStates;
        var round  = rounds.First(x => x.CoinjoinState is ConstructionState);

        // If an output is not in the utxo dataset then it is not unspent, this
        // means that the output is spent or simply doesn't even exist.
        using var signingKey = new Key();
        var ownershipProof = WabiSabiFactory.CreateOwnershipProof(signingKey, round.Id);

        var ex = await Assert.ThrowsAsync <WabiSabiProtocolException>(async() =>
                                                                      await apiClient.RegisterInputAsync(round.Id, bannedOutPoint, ownershipProof, timeoutCts.Token));

        Assert.Equal(WabiSabiProtocolErrorCode.InputLongBanned, ex.ErrorCode);
        var inputBannedData = Assert.IsType <InputBannedExceptionData>(ex.ExceptionData);

        Assert.True(inputBannedData.BannedUntil > DateTimeOffset.UtcNow);
    }
Example #2
0
    public async Task InputUnconfirmedAsync()
    {
        using Key key = new();
        WabiSabiConfig cfg            = new();
        var            round          = WabiSabiFactory.CreateRound(cfg);
        var            ownershipProof = WabiSabiFactory.CreateOwnershipProof(key, round.Id);

        var mockRpc = new Mock <IRPCClient>();

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

        using Arena arena = await ArenaBuilder.From(cfg).With(mockRpc).CreateAndStartAsync(round);

        var arenaClient = WabiSabiFactory.CreateArenaClient(arena);

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

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

        await arena.StopAsync(CancellationToken.None);
    }
        public async Task NoPrisonSerializationAsync()
        {
            // Don't serialize when there's no change.
            var workDir = Common.GetWorkDir();
            await IoHelpers.TryDeleteDirectoryAsync(workDir);

            // Create prison.
            CoordinatorParameters coordinatorParameters = new(workDir);

            using var w = new Warden(coordinatorParameters.UtxoWardenPeriod, coordinatorParameters.PrisonFilePath, coordinatorParameters.RuntimeCoordinatorConfig);
            await w.StartAsync(CancellationToken.None);

            var i1 = new Inmate(BitcoinFactory.CreateOutPoint(), Punishment.Noted, DateTimeOffset.FromUnixTimeSeconds(DateTimeOffset.UtcNow.ToUnixTimeSeconds()), uint256.Zero);
            var i2 = new Inmate(BitcoinFactory.CreateOutPoint(), Punishment.Banned, DateTimeOffset.FromUnixTimeSeconds(DateTimeOffset.UtcNow.ToUnixTimeSeconds()), uint256.Zero);

            w.Prison.Punish(i1);
            w.Prison.Punish(i2);

            // Wait until serializes.
            await w.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(7));

            // Make sure it does not serialize again as there was no change.
            File.Delete(w.PrisonFilePath);
            await w.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(7));

            Assert.False(File.Exists(w.PrisonFilePath));
            await w.StopAsync(CancellationToken.None);
        }
Example #4
0
    public async Task InputImmatureAsync()
    {
        using Key key = new();
        WabiSabiConfig cfg            = new();
        var            round          = WabiSabiFactory.CreateRound(cfg);
        var            ownershipProof = WabiSabiFactory.CreateOwnershipProof(key, round.Id);

        var rpc    = WabiSabiFactory.CreatePreconfiguredRpcClient();
        var rpcCfg = rpc.SetupSequence(rpc => rpc.GetTxOutAsync(It.IsAny <uint256>(), It.IsAny <int>(), It.IsAny <bool>(), It.IsAny <CancellationToken>()));

        foreach (var i in Enumerable.Range(1, 100))
        {
            rpcCfg = rpcCfg.ReturnsAsync(new NBitcoin.RPC.GetTxOutResponse {
                Confirmations = i, IsCoinBase = true
            });
        }
        using Arena arena = await ArenaBuilder.From(cfg).With(rpc).CreateAndStartAsync(round);

        var arenaClient = WabiSabiFactory.CreateArenaClient(arena);

        var req = WabiSabiFactory.CreateInputRegistrationRequest(round: round);

        foreach (var i in Enumerable.Range(1, 100))
        {
            var ex = await Assert.ThrowsAsync <WabiSabiProtocolException>(
                async() => await arenaClient.RegisterInputAsync(round.Id, BitcoinFactory.CreateOutPoint(), ownershipProof, CancellationToken.None));

            Assert.Equal(WabiSabiProtocolErrorCode.InputImmature, ex.ErrorCode);
        }

        await arena.StopAsync(CancellationToken.None);
    }
        public async Task RegisterCoinIdempotencyAsync()
        {
            using var signingKey = new Key();
            var coinToRegister = new Coin(
                BitcoinFactory.CreateOutPoint(),
                new TxOut(Money.Coins(1), signingKey.PubKey.WitHash.ScriptPubKey));

            var httpClient = _apiApplicationFactory.WithWebHostBuilder(builder =>
            {
                builder.ConfigureServices(services =>
                {
                    var rpc             = BitcoinFactory.GetMockMinimalRpc();
                    rpc.OnGetTxOutAsync = (_, _, _) => new()
                    {
                        Confirmations    = 101,
                        IsCoinBase       = false,
                        ScriptPubKeyType = "witness_v0_keyhash",
                        TxOut            = coinToRegister.TxOut
                    };
                    services.AddScoped <IRPCClient>(s => rpc);
                });
            }).CreateClient();

            var apiClient = await _apiApplicationFactory.CreateArenaClientAsync(new StuttererHttpClient(httpClient));

            var rounds = await apiClient.GetStatusAsync(CancellationToken.None);

            var round = rounds.First(x => x.CoinjoinState is ConstructionState);

            var response = await apiClient.RegisterInputAsync(round.Id, coinToRegister.Outpoint, signingKey, CancellationToken.None);

            Assert.NotEqual(uint256.Zero, response.Value);
        }
Example #6
0
    public async Task InputCanBeNotedAsync()
    {
        using Key key = new();
        var outpoint = BitcoinFactory.CreateOutPoint();

        WabiSabiConfig cfg   = new();
        var            round = WabiSabiFactory.CreateRound(cfg);

        Prison prison = new();

        using Arena arena = await ArenaBuilder.From(cfg, prison).CreateAndStartAsync(round);

        prison.Punish(outpoint, Punishment.Noted, uint256.One);

        var ownershipProof = WabiSabiFactory.CreateOwnershipProof(key);

        var arenaClient = WabiSabiFactory.CreateArenaClient(arena);

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

        Assert.NotEqual(WabiSabiProtocolErrorCode.InputBanned, ex.ErrorCode);

        await arena.StopAsync(CancellationToken.None);
    }
        public async Task ReleasesInmatesAsync()
        {
            var workDir = Common.GetWorkDir();
            await IoHelpers.TryDeleteDirectoryAsync(workDir);

            // Create prison.
            CoordinatorParameters coordinatorParameters = new(workDir);

            coordinatorParameters.RuntimeCoordinatorConfig.ReleaseUtxoFromPrisonAfter = TimeSpan.FromMilliseconds(1);

            using var w = new Warden(coordinatorParameters.UtxoWardenPeriod, coordinatorParameters.PrisonFilePath, coordinatorParameters.RuntimeCoordinatorConfig);
            await w.StartAsync(CancellationToken.None);

            var i1 = new Inmate(BitcoinFactory.CreateOutPoint(), Punishment.Noted, DateTimeOffset.FromUnixTimeSeconds(DateTimeOffset.UtcNow.ToUnixTimeSeconds()), uint256.Zero);
            var i2 = new Inmate(BitcoinFactory.CreateOutPoint(), Punishment.Banned, DateTimeOffset.FromUnixTimeSeconds(DateTimeOffset.UtcNow.ToUnixTimeSeconds()), uint256.Zero);
            var p  = w.Prison;

            p.Punish(i1);
            p.Punish(i2);
            Assert.NotEmpty(p.GetInmates());

            // Wait until releases from prison.
            await w.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(7));

            Assert.Empty(p.GetInmates());
            await w.StopAsync(CancellationToken.None);
        }
Example #8
0
    public async Task InputRegistrationFullAsync()
    {
        WabiSabiConfig cfg   = new() { MaxInputCountByRound = 3 };
        var            round = WabiSabiFactory.CreateRound(cfg);

        using Key key = new();
        var ownershipProof = WabiSabiFactory.CreateOwnershipProof(key, round.Id);

        using Arena arena = await ArenaBuilder.From(cfg).CreateAndStartAsync(round);

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

        round.Alices.Add(WabiSabiFactory.CreateAlice(round));
        round.Alices.Add(WabiSabiFactory.CreateAlice(round));
        round.Alices.Add(WabiSabiFactory.CreateAlice(round));

        var arenaClient = WabiSabiFactory.CreateArenaClient(arena);
        var ex          = await Assert.ThrowsAsync <WrongPhaseException>(
            async() => await arenaClient.RegisterInputAsync(round.Id, BitcoinFactory.CreateOutPoint(), ownershipProof, CancellationToken.None));

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

        await arena.StopAsync(CancellationToken.None);
    }
Example #9
0
        public void EmptyPrison()
        {
            var p = new Prison();

            Assert.Empty(p.GetInmates());
            Assert.Equal(0, p.CountInmates().noted);
            Assert.Equal(0, p.CountInmates().banned);
            Assert.False(p.TryGet(BitcoinFactory.CreateOutPoint(), out _));
        }
Example #10
0
        public async Task RoundNotFoundAsync()
        {
            using Key key     = new();
            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(new(), WabiSabiFactory.CreatePreconfiguredRpcClient());

            var arenaClient = WabiSabiFactory.CreateArenaClient(arena);
            var ex          = await Assert.ThrowsAsync <WabiSabiProtocolException>(
                async() => await arenaClient.RegisterInputAsync(uint256.Zero, BitcoinFactory.CreateOutPoint(), key, CancellationToken.None));

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

            await arena.StopAsync(CancellationToken.None);
        }
Example #11
0
    public async Task RoundNotFoundAsync()
    {
        using Key key     = new();
        using Arena arena = await ArenaBuilder.Default.CreateAndStartAsync();

        var ownershipProof = WabiSabiFactory.CreateOwnershipProof(key);

        var arenaClient = WabiSabiFactory.CreateArenaClient(arena);
        var ex          = await Assert.ThrowsAsync <WabiSabiProtocolException>(
            async() => await arenaClient.RegisterInputAsync(uint256.Zero, BitcoinFactory.CreateOutPoint(), ownershipProof, CancellationToken.None));

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

        await arena.StopAsync(CancellationToken.None);
    }
Example #12
0
    public void CanReleaseAfterLongBan()
    {
        var p    = new Prison();
        var id1  = BitcoinFactory.CreateUint256();
        var utxo = BitcoinFactory.CreateOutPoint();
        var past = DateTimeOffset.UtcNow - TimeSpan.FromDays(40);

        p.Punish(new Inmate(utxo, Punishment.Banned, past, id1, IsLongBan: true));

        Assert.Single(p.GetInmates());

        p.ReleaseEligibleInmates(normalBanPeriod: TimeSpan.FromSeconds(1), longBanPeriod: TimeSpan.FromDays(31));

        Assert.Empty(p.GetInmates());
    }
        public async Task InputNotWhitelistedAsync()
        {
            MockArena      arena = new();
            WabiSabiConfig cfg   = new();
            var            round = WabiSabiFactory.CreateRound(cfg);

            round.Alices.Add(new Alice(BitcoinFactory.CreateOutPoint()));
            Round blameRound = new(round);

            arena.OnTryGetRound = _ => blameRound;

            await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
            var req = WabiSabiFactory.CreateInputsRegistrationRequest(blameRound);
            var ex  = await Assert.ThrowsAsync <WabiSabiProtocolException>(async() => await handler.RegisterInputAsync(req));

            Assert.Equal(WabiSabiProtocolErrorCode.InputNotWhitelisted, ex.ErrorCode);
        }
Example #14
0
        public void PrisonOperations()
        {
            var p = new Prison();

            var id1 = BitcoinFactory.CreateUint256();

            var utxo = BitcoinFactory.CreateOutPoint();

            p.Punish(utxo, Punishment.Noted, id1);
            Assert.Single(p.GetInmates());
            Assert.Equal(1, p.CountInmates().noted);
            Assert.Equal(0, p.CountInmates().banned);
            Assert.True(p.TryGet(utxo, out _));

            // Updates to banned.
            p.Punish(utxo, Punishment.Banned, id1);
            Assert.Single(p.GetInmates());
            Assert.Equal(0, p.CountInmates().noted);
            Assert.Equal(1, p.CountInmates().banned);
            Assert.True(p.TryGet(utxo, out _));

            // Removes.
            Assert.True(p.TryRelease(utxo, out _));
            Assert.Empty(p.GetInmates());

            // Noting twice flips to banned.
            p.Punish(utxo, Punishment.Noted, id1);
            p.Punish(utxo, Punishment.Noted, id1);
            Assert.Single(p.GetInmates());
            Assert.Equal(0, p.CountInmates().noted);
            Assert.Equal(1, p.CountInmates().banned);
            Assert.True(p.TryGet(utxo, out _));
            Assert.True(p.TryRelease(utxo, out _));

            // Updates round.
            var id2 = BitcoinFactory.CreateUint256();

            p.Punish(utxo, Punishment.Banned, id1);
            p.Punish(utxo, Punishment.Banned, id2);
            Assert.Single(p.GetInmates());
            Assert.True(p.TryGet(utxo, out var inmate));
            Assert.Equal(id2, inmate !.LastDisruptedRoundId);
            Assert.True(p.TryRelease(utxo, out _));
        }
Example #15
0
    public void SignTransactionTest()
    {
        var keyManager          = KeyManager.CreateNew(out _, "", Network.Main);
        var destinationProvider = new InternalDestinationProvider(keyManager);
        var keyChain            = new KeyChain(keyManager, new Kitchen(""));

        var coinDestination = destinationProvider.GetNextDestinations(1).First();
        var coin            = new Coin(BitcoinFactory.CreateOutPoint(), new TxOut(Money.Coins(1.0m), coinDestination));
        var ownershipProof  = keyChain.GetOwnershipProof(coinDestination, new CoinJoinInputCommitmentData("test", uint256.One));

        var transaction = Transaction.Create(Network.Main);         // the transaction doesn't contain the input that we request to be signed.

        Assert.Throws <ArgumentException>(() => keyChain.Sign(transaction, coin, ownershipProof));

        transaction.Inputs.Add(coin.Outpoint);
        var signedTx = keyChain.Sign(transaction, coin, ownershipProof);

        Assert.True(signedTx.HasWitness);
    }
Example #16
0
        public async Task InputCantBeNotedAsync()
        {
            using Key key = new();
            var outpoint = BitcoinFactory.CreateOutPoint();

            WabiSabiConfig cfg   = new() { AllowNotedInputRegistration = false };
            var            round = WabiSabiFactory.CreateRound(cfg);

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

            arena.Prison.Punish(outpoint, Punishment.Noted, uint256.One);

            var arenaClient = WabiSabiFactory.CreateArenaClient(arena);

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

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

            await arena.StopAsync(CancellationToken.None);
        }
        public async Task PrisonSerializationAsync()
        {
            var workDir = Common.GetWorkDir();
            await IoHelpers.TryDeleteDirectoryAsync(workDir);

            // Create prison.
            CoordinatorParameters coordinatorParameters = new(workDir);

            using var w = new Warden(coordinatorParameters.UtxoWardenPeriod, coordinatorParameters.PrisonFilePath, coordinatorParameters.RuntimeCoordinatorConfig);
            await w.StartAsync(CancellationToken.None);

            var i1 = new Inmate(BitcoinFactory.CreateOutPoint(), Punishment.Noted, DateTimeOffset.FromUnixTimeSeconds(DateTimeOffset.UtcNow.ToUnixTimeSeconds()), uint256.Zero);
            var i2 = new Inmate(BitcoinFactory.CreateOutPoint(), Punishment.Banned, DateTimeOffset.FromUnixTimeSeconds(DateTimeOffset.UtcNow.ToUnixTimeSeconds()), uint256.Zero);

            w.Prison.Punish(i1);
            w.Prison.Punish(i2);

            // Wait until serializes.
            await w.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(7));

            await w.StopAsync(CancellationToken.None);

            // See if prev UTXOs are loaded.
            CoordinatorParameters coordinatorParameters2 = new(workDir);

            using var w2 = new Warden(coordinatorParameters2.UtxoWardenPeriod, coordinatorParameters2.PrisonFilePath, coordinatorParameters2.RuntimeCoordinatorConfig);
            await w2.StartAsync(CancellationToken.None);

            Assert.True(w2.Prison.TryGet(i1.Utxo, out var sameI1));
            Assert.True(w2.Prison.TryGet(i2.Utxo, out var sameI2));
            Assert.Equal(i1.LastDisruptedRoundId, sameI1 !.LastDisruptedRoundId);
            Assert.Equal(i2.LastDisruptedRoundId, sameI2 !.LastDisruptedRoundId);
            Assert.Equal(i1.Punishment, sameI1 !.Punishment);
            Assert.Equal(i2.Punishment, sameI2 !.Punishment);
            Assert.Equal(i1.Started, sameI1 !.Started);
            Assert.Equal(i2.Started, sameI2 !.Started);

            await w2.StopAsync(CancellationToken.None);
        }
Example #18
0
        public async Task InputSpentAsync()
        {
            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((NBitcoin.RPC.GetTxOutResponse?)null);

            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.InputSpent, ex.ErrorCode);

            await arena.StopAsync(CancellationToken.None);
        }
Example #19
0
        public void PrisonChangeTracking()
        {
            var p = new Prison();
            var currentChangeId = p.ChangeId;

            // Make sure we set them to the past so the release method that looks at the time evaluates to true.
            var past = DateTimeOffset.UtcNow - TimeSpan.FromMilliseconds(2);

            var id1 = BitcoinFactory.CreateUint256();

            p.Punish(new Inmate(BitcoinFactory.CreateOutPoint(), Punishment.Banned, past, id1));
            Assert.NotEqual(currentChangeId, p.ChangeId);
            currentChangeId = p.ChangeId;

            p.Punish(new Inmate(BitcoinFactory.CreateOutPoint(), Punishment.Noted, past, id1));
            Assert.NotEqual(currentChangeId, p.ChangeId);
            currentChangeId = p.ChangeId;

            var op  = BitcoinFactory.CreateOutPoint();
            var id2 = BitcoinFactory.CreateUint256();

            p.Punish(new Inmate(op, Punishment.Noted, past, id2));
            Assert.NotEqual(currentChangeId, p.ChangeId);
            currentChangeId = p.ChangeId;

            p.Punish(new Inmate(op, Punishment.Noted, past, id1));
            Assert.NotEqual(currentChangeId, p.ChangeId);
            currentChangeId = p.ChangeId;

            Assert.True(p.TryRelease(op, out _));
            Assert.NotEqual(currentChangeId, p.ChangeId);
            currentChangeId = p.ChangeId;

            p.ReleaseEligibleInmates(TimeSpan.FromMilliseconds(1));
            Assert.NotEqual(currentChangeId, p.ChangeId);
        }
        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());
        }
Example #21
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);
        }
Example #22
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);
        }
        public async Task SoloCoinJoinTest()
        {
            const int InputCount = 2;

            // At the end of the test a coinjoin transaction has to be created and broadcasted.
            var transactionCompleted = new TaskCompletionSource <Transaction>();

            // Total test timeout.
            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(120));
            cts.Token.Register(() => transactionCompleted.TrySetCanceled(), useSynchronizationContext: false);

            // Create a key manager and use it to create two fake coins.
            var keyManager = KeyManager.CreateNew(out var _, password: "");

            keyManager.AssertCleanKeysIndexed();
            var coins = keyManager.GetKeys()
                        .Take(InputCount)
                        .Select(x => new Coin(
                                    BitcoinFactory.CreateOutPoint(),
                                    new TxOut(Money.Coins(1), x.P2wpkhScript)))
                        .ToArray();

            var httpClient = _apiApplicationFactory.WithWebHostBuilder(builder =>
            {
                builder.ConfigureServices(services =>
                {
                    var rpc = BitcoinFactory.GetMockMinimalRpc();

                    // Make the coordinator to believe that those two coins are real and
                    // that they exist in the blockchain with many confirmations.
                    rpc.OnGetTxOutAsync = (txId, idx, _) => new()
                    {
                        Confirmations    = 101,
                        IsCoinBase       = false,
                        ScriptPubKeyType = "witness_v0_keyhash",
                        TxOut            = coins.Single(x => x.Outpoint.Hash == txId && x.Outpoint.N == idx).TxOut
                    };

                    // Make the coordinator believe that the transaction is being
                    // broadcasted using the RPC interface. Once we receive this tx
                    // (the `SendRawTransationAsync` was invoked) we stop waiting
                    // and finish the waiting tasks to finish the test successfully.
                    rpc.OnSendRawTransactionAsync = (tx) =>
                    {
                        transactionCompleted.SetResult(tx);
                        return(tx.GetHash());
                    };

                    // Instruct the coodinator DI container to use these two scoped
                    // services to build everything (wabisabi controller, arena, etc)
                    services.AddScoped <IRPCClient>(s => rpc);
                    services.AddScoped <WabiSabiConfig>(s => new WabiSabiConfig {
                        MaxInputCountByRound = InputCount
                    });
                });
            }).CreateClient();

            // Create the coinjoin client
            var apiClient = _apiApplicationFactory.CreateWabiSabiHttpApiClient(httpClient);

            using var roundStateUpdater = new RoundStateUpdater(TimeSpan.FromSeconds(1), apiClient);
            await roundStateUpdater.StartAsync(CancellationToken.None);

            var kitchen = new Kitchen();

            kitchen.Cook("");

            var coinJoinClient = new CoinJoinClient(apiClient, coins, kitchen, keyManager, roundStateUpdater);

            // Run the coinjoin client task.
            await coinJoinClient.StartCoinJoinAsync(cts.Token);

            var boadcastedTx = await transactionCompleted.Task.ConfigureAwait(false);             // wait for the transaction to be broadcasted.

            Assert.NotNull(boadcastedTx);

            await roundStateUpdater.StopAsync(CancellationToken.None);
        }
Example #24
0
        public void EqualityTest()
        {
            uint256  roundId   = BitcoinFactory.CreateUint256();
            uint256  roundHash = BitcoinFactory.CreateUint256();
            OutPoint outPoint  = BitcoinFactory.CreateOutPoint();

            using Key key = new();

            // Request #1.
            InputRegistrationRequest request1 = new(
                RoundId : roundId,
                Input : outPoint,
                OwnershipProof : CreateOwnershipProof(key, roundHash),
                ZeroAmountCredentialRequests : GetZeroCredentialsRequest(),
                ZeroVsizeCredentialRequests : GetZeroCredentialsRequest()
                );

            // Request #2.
            InputRegistrationRequest request2 = new(
                RoundId : roundId,
                Input : outPoint,
                OwnershipProof : CreateOwnershipProof(key, roundHash),
                ZeroAmountCredentialRequests : GetZeroCredentialsRequest(),
                ZeroVsizeCredentialRequests : GetZeroCredentialsRequest()
                );

            Assert.Equal(request1, request2);

            // Request #3.
            InputRegistrationRequest request3 = new(
                RoundId : BitcoinFactory.CreateUint256(),                // Intentionally changed.
                Input : outPoint,
                OwnershipProof : CreateOwnershipProof(key, roundHash),
                ZeroAmountCredentialRequests : GetZeroCredentialsRequest(),
                ZeroVsizeCredentialRequests : GetZeroCredentialsRequest()
                );

            Assert.NotEqual(request1, request3);

            // Request #4.
            InputRegistrationRequest request4 = new(
                RoundId : roundId,
                Input : BitcoinFactory.CreateOutPoint(),                // Intentionally changed.
                OwnershipProof : CreateOwnershipProof(key, roundHash),
                ZeroAmountCredentialRequests : GetZeroCredentialsRequest(),
                ZeroVsizeCredentialRequests : GetZeroCredentialsRequest()
                );

            Assert.NotEqual(request1, request4);

            // Request #5.
            InputRegistrationRequest request5 = new(
                RoundId : roundId,
                Input : outPoint,
                OwnershipProof : CreateOwnershipProof(key, roundHash : BitcoinFactory.CreateUint256()),               // Intentionally changed.
                ZeroAmountCredentialRequests : GetZeroCredentialsRequest(),
                ZeroVsizeCredentialRequests : GetZeroCredentialsRequest()
                );

            Assert.NotEqual(request1, request5);

            // Request #6.
            InputRegistrationRequest request6 = new(
                RoundId : roundId,
                Input : outPoint,
                OwnershipProof : CreateOwnershipProof(key, roundHash),
                ZeroAmountCredentialRequests : GetZeroCredentialsRequest(modifier : 2),               // Intentionally changed.
                ZeroVsizeCredentialRequests : GetZeroCredentialsRequest()
                );

            Assert.NotEqual(request1, request6);

            // Request #7.
            InputRegistrationRequest request7 = new(
                RoundId : roundId,
                Input : outPoint,
                OwnershipProof : CreateOwnershipProof(key, roundHash),
                ZeroAmountCredentialRequests : GetZeroCredentialsRequest(),
                ZeroVsizeCredentialRequests : GetZeroCredentialsRequest(modifier : 2)               // Intentionally changed.
                );

            Assert.NotEqual(request1, request7);
        }
Example #25
0
    public async Task SignTransactionAsync()
    {
        WabiSabiConfig config   = new();
        Round          round    = WabiSabiFactory.CreateRound(config);
        var            password = "******";

        var km                  = ServiceFactory.CreateKeyManager(password);
        var keyChain            = new KeyChain(km, new Kitchen(password));
        var destinationProvider = new InternalDestinationProvider(km);

        var coins = destinationProvider.GetNextDestinations(2)
                    .Select(dest => (
                                Coin: new Coin(BitcoinFactory.CreateOutPoint(), new TxOut(Money.Coins(1.0m), dest)),
                                OwnershipProof: keyChain.GetOwnershipProof(dest, new CoinJoinInputCommitmentData("test", uint256.One))))
                    .ToArray();

        Alice alice1 = WabiSabiFactory.CreateAlice(coins[0].Coin, coins[0].OwnershipProof, round: round);

        round.Alices.Add(alice1);

        Alice alice2 = WabiSabiFactory.CreateAlice(coins[1].Coin, coins[1].OwnershipProof, round: round);

        round.Alices.Add(alice2);

        using Arena arena = await ArenaBuilder.From(config).CreateAndStartAsync(round);

        var mockRpc = new Mock <IRPCClient>();

        using var memoryCache = new MemoryCache(new MemoryCacheOptions());
        var idempotencyRequestCache = new IdempotencyRequestCache(memoryCache);

        using CoinJoinFeeRateStatStore coinJoinFeeRateStatStore = new(config, arena.Rpc);
        var wabiSabiApi = new WabiSabiController(idempotencyRequestCache, arena, coinJoinFeeRateStatStore);

        InsecureRandom rnd          = InsecureRandom.Instance;
        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(round.Parameters, emptyState.Events);

        // No inputs in the coinjoin.
        await Assert.ThrowsAsync <ArgumentException>(async() =>
                                                     await apiClient.SignTransactionAsync(round.Id, alice1.Coin, coins[0].OwnershipProof, keyChain, 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, coins[1].OwnershipProof, keyChain, oneInput.CreateUnsignedTransaction(), CancellationToken.None));

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

        round.CoinjoinState = twoInputs;

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

        await apiClient.SignTransactionAsync(round.Id, alice1.Coin, coins[0].OwnershipProof, keyChain, 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, coins[1].OwnershipProof, keyChain, unsigned, CancellationToken.None);

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

        Assert.True(round.Assert <SigningState>().IsFullySigned);
    }