Exemple #1
0
        public async Task SuccessAsync()
        {
            WabiSabiConfig cfg   = new();
            var            round = WabiSabiFactory.CreateRound(cfg);

            using Key key = new();
            Alice alice = WabiSabiFactory.CreateAlice(key: key);

            round.Alices.Add(alice);
            var coinjoin = round.Coinjoin;

            coinjoin.Inputs.Add(alice.Coins.First().Outpoint);
            round.SetPhase(Phase.TransactionSigning);
            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, round);

            var signedCoinJoin = coinjoin.Clone();

            signedCoinJoin.Sign(key.GetBitcoinSecret(Network.Main), alice.Coins.First());

            var req = new TransactionSignaturesRequest(round.Id, new[] { new InputWitnessPair(0, signedCoinJoin.Inputs[0].WitScript) });

            await using ArenaRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
            await handler.SignTransactionAsync(req);

            Assert.True(round.Coinjoin.Inputs.First().HasWitScript());
            await arena.StopAsync(CancellationToken.None);
        }
Exemple #2
0
        public async Task WrongCoinjoinSignatureAsync()
        {
            WabiSabiConfig cfg   = new();
            var            round = WabiSabiFactory.CreateRound(cfg);

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

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

            round.Alices.Add(alice1);
            round.Alices.Add(alice2);
            var coinjoin = round.Coinjoin;

            coinjoin.Inputs.Add(alice1.Coins.First().Outpoint);
            coinjoin.Inputs.Add(alice2.Coins.First().Outpoint);
            round.SetPhase(Phase.TransactionSigning);
            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, round);

            // Submit the signature for the second alice to the first alice's input.
            var signedCoinJoin = coinjoin.Clone();

            signedCoinJoin.Sign(key2.GetBitcoinSecret(Network.Main), alice2.Coins.First());

            var req = new TransactionSignaturesRequest(round.Id, new[] { new InputWitnessPair(0, signedCoinJoin.Inputs[0].WitScript) });

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

            Assert.Equal(WabiSabiProtocolErrorCode.WrongCoinjoinSignature, ex.ErrorCode);
            await arena.StopAsync(CancellationToken.None);
        }
Exemple #3
0
    public async Task WrongPhaseAsync()
    {
        WabiSabiConfig cfg = new();

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

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

        var round = arena.Rounds.First();

        var req = new TransactionSignaturesRequest(round.Id, 0, WitScript.Empty);

        foreach (Phase phase in Enum.GetValues(typeof(Phase)))
        {
            if (phase != Phase.TransactionSigning)
            {
                round.SetPhase(phase);

                var ex = await Assert.ThrowsAsync <WrongPhaseException>(async() =>
                                                                        await arena.SignTransactionAsync(req, CancellationToken.None));

                Assert.Equal(WabiSabiProtocolErrorCode.WrongPhase, ex.ErrorCode);
            }
        }
        await arena.StopAsync(CancellationToken.None);
    }
Exemple #4
0
    public async Task RoundNotFoundAsync()
    {
        using Arena arena = await ArenaBuilder.Default.CreateAndStartAsync();

        var req = new TransactionSignaturesRequest(uint256.Zero, 0, WitScript.Empty);
        var ex  = await Assert.ThrowsAsync <WabiSabiProtocolException>(async() => await arena.SignTransactionAsync(req, CancellationToken.None));

        Assert.Equal(WabiSabiProtocolErrorCode.RoundNotFound, ex.ErrorCode);
        await arena.StopAsync(CancellationToken.None);
    }
Exemple #5
0
    public async Task SignTransactionAsync(TransactionSignaturesRequest request, CancellationToken cancellationToken)
    {
        using (await AsyncLock.LockAsync(cancellationToken).ConfigureAwait(false))
        {
            var round = GetRound(request.RoundId, Phase.TransactionSigning);

            var state = round.Assert <SigningState>().AddWitness((int)request.InputIndex, request.Witness);

            // at this point all of the witnesses have been verified and the state can be updated
            round.CoinjoinState = state;
        }
    }
Exemple #6
0
        public async Task SuccessAsync()
        {
            WabiSabiConfig cfg   = new();
            var            round = WabiSabiFactory.CreateRound(cfg);

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

            round.Alices.Add(alice);
            round.CoinjoinState = round.AddInput(alice.Coin).Finalize();
            round.SetPhase(Phase.TransactionSigning);
            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, round);

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

            aliceSignedCoinJoin.Sign(key.GetBitcoinSecret(Network.Main), alice.Coin);

            var req = new TransactionSignaturesRequest(round.Id, new[] { new InputWitnessPair(0, aliceSignedCoinJoin.Inputs[0].WitScript) });
            await arena.SignTransactionAsync(req, CancellationToken.None);

            Assert.True(round.Assert <SigningState>().IsFullySigned);
            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);
        }
    }
        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 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);
        }
Exemple #10
0
 public async Task SignTransactionAsync(TransactionSignaturesRequest request, CancellationToken cancellableToken)
 {
     await Arena.SignTransactionAsync(request, cancellableToken);
 }
Exemple #11
0
 public Task SignTransactionAsync(TransactionSignaturesRequest request, CancellationToken cancellableToken)
 {
     return(RequestHandler.SignTransactionAsync(request, cancellableToken));
 }
Exemple #12
0
 public virtual Task SignTransactionAsync(TransactionSignaturesRequest request, CancellationToken cancellationToken) =>
 SendAndReceiveAsync <TransactionSignaturesRequest>(RemoteAction.SignTransaction, request, cancellationToken);
Exemple #13
0
 public override Task SignTransactionAsync(TransactionSignaturesRequest request, CancellationToken cancellationToken)
 {
     return(Task.CompletedTask);
 }
 public Task SignTransactionAsync(TransactionSignaturesRequest request, CancellationToken cancellationToken)
 => arena.SignTransactionAsync(request);
Exemple #15
0
 public override async Task SignTransactionAsync(TransactionSignaturesRequest request, CancellationToken cancellationToken)
 {
     return;
 }