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); }
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); }
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); }
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); }
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; } }
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); }
public async Task SignTransactionAsync(TransactionSignaturesRequest request, CancellationToken cancellableToken) { await Arena.SignTransactionAsync(request, cancellableToken); }
public Task SignTransactionAsync(TransactionSignaturesRequest request, CancellationToken cancellableToken) { return(RequestHandler.SignTransactionAsync(request, cancellableToken)); }
public virtual Task SignTransactionAsync(TransactionSignaturesRequest request, CancellationToken cancellationToken) => SendAndReceiveAsync <TransactionSignaturesRequest>(RemoteAction.SignTransaction, request, cancellationToken);
public override Task SignTransactionAsync(TransactionSignaturesRequest request, CancellationToken cancellationToken) { return(Task.CompletedTask); }
public Task SignTransactionAsync(TransactionSignaturesRequest request, CancellationToken cancellationToken) => arena.SignTransactionAsync(request);
public override async Task SignTransactionAsync(TransactionSignaturesRequest request, CancellationToken cancellationToken) { return; }