public void BasicTests() { // Make sure byte array comparison works within hashset and that the underlying API won't pull the floor out. var byteArray = new byte[] { 1, 2, 3 }; var sameByteArray = new byte[] { 1, 2, 3 }; var differentByteArray = new byte[] { 4, 5, 6 }; var twoSet = new HashSet <byte[]>(new ByteArrayEqualityComparer()) { byteArray, sameByteArray, differentByteArray }; Assert.True(ByteHelpers.CompareFastUnsafe(byteArray, sameByteArray)); Assert.False(ByteHelpers.CompareFastUnsafe(byteArray, differentByteArray)); Assert.Equal(2, twoSet.Count); // It's probabilistically ensured that it never produces the same scalar, so unit test should pass always. var pseudoSet = new HashSet <byte[]>(); var secureSet = new HashSet <byte[]>(); var count = 100; using var insecureRandom = new InsecureRandom(); using var secureRandom = new SecureRandom(); for (int i = 0; i < count; i++) { pseudoSet.Add(insecureRandom.GetBytes(10)); secureSet.Add(secureRandom.GetBytes(10)); } Assert.Equal(count, pseudoSet.Count); Assert.Equal(count, secureSet.Count); }
public async Task SignTransactionAsync() { WabiSabiConfig config = new(); Round round = WabiSabiFactory.CreateRound(config); using Key key1 = new(); Alice alice1 = WabiSabiFactory.CreateAlice(key: key1); round.Alices.Add(alice1); using Key key2 = new(); Alice alice2 = WabiSabiFactory.CreateAlice(key: key2); round.Alices.Add(alice2); var coinjoin = round.Coinjoin; 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 rnd = new InsecureRandom(); var amountClient = new WabiSabiClient(round.AmountCredentialIssuerParameters, 2, rnd, 4300000000000ul); var weightClient = new WabiSabiClient(round.WeightCredentialIssuerParameters, 2, rnd, 2000ul); var apiClient = new ArenaClient(amountClient, weightClient, coordinator); round.SetPhase(Phase.TransactionSigning); // No inputs in the CoinJoin. await Assert.ThrowsAsync <ArgumentException>(async() => await apiClient.SignTransactionAsync(round.Id, alice1.Coins.ToArray(), new BitcoinSecret(key1, Network.Main), coinjoin)); coinjoin.Inputs.Add(alice1.Coins.First().Outpoint); // Trying to sign coins those are not in the CoinJoin. await Assert.ThrowsAsync <InvalidOperationException>(async() => await apiClient.SignTransactionAsync(round.Id, alice2.Coins.ToArray(), new BitcoinSecret(key2, Network.Main), coinjoin)); coinjoin.Inputs.Add(alice2.Coins.First().Outpoint); // Trying to sign coins with the wrong secret. await Assert.ThrowsAsync <InvalidOperationException>(async() => await apiClient.SignTransactionAsync(round.Id, alice1.Coins.ToArray(), new BitcoinSecret(key2, Network.Main), coinjoin)); Assert.False(round.Coinjoin.HasWitness); await apiClient.SignTransactionAsync(round.Id, alice1.Coins.ToArray(), new BitcoinSecret(key1, Network.Main), coinjoin); Assert.True(round.Coinjoin.Inputs.Where(i => alice1.Coins.Select(c => c.Outpoint).Contains(i.PrevOut)).All(i => i.HasWitScript())); await apiClient.SignTransactionAsync(round.Id, alice2.Coins.ToArray(), new BitcoinSecret(key2, Network.Main), coinjoin); Assert.True(round.Coinjoin.Inputs.Where(i => alice2.Coins.Select(c => c.Outpoint).Contains(i.PrevOut)).All(i => i.HasWitScript())); }
public async Task <ArenaClient> CreateArenaClientAsync(WabiSabiHttpApiClient wabiSabiHttpApiClient) { var rounds = (await wabiSabiHttpApiClient.GetStatusAsync(RoundStateRequest.Empty, CancellationToken.None)).RoundStates; var round = rounds.First(x => x.CoinjoinState is ConstructionState); var insecureRandom = new InsecureRandom(); var arenaClient = new ArenaClient( round.CreateAmountCredentialClient(insecureRandom), round.CreateVsizeCredentialClient(insecureRandom), wabiSabiHttpApiClient); return(arenaClient); }
public async Task CreateNewAsync() { 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(1m)); 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 wabiSabiApi = new WabiSabiController(coordinator); var insecureRandom = new InsecureRandom(); var roundState = RoundState.FromRound(round); var arenaClient = new ArenaClient( roundState.CreateAmountCredentialClient(insecureRandom), roundState.CreateVsizeCredentialClient(insecureRandom), wabiSabiApi); Assert.Equal(Phase.InputRegistration, arena.Rounds.First().Phase); var bitcoinSecret = km.GetSecrets("", coin1.ScriptPubKey).Single().PrivateKey.GetBitcoinSecret(Network.Main); var aliceClient = new AliceClient(round.Id, arenaClient, coin1.Coin, round.FeeRate, bitcoinSecret); await aliceClient.RegisterInputAsync(CancellationToken.None); using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), wabiSabiApi); Task confirmationTask = aliceClient.ConfirmConnectionAsync( TimeSpan.FromSeconds(1), new long[] { coin1.EffectiveValue(round.FeeRate) }, new long[] { roundState.MaxVsizeAllocationPerAlice - coin1.ScriptPubKey.EstimateInputVsize() }, roundStateUpdater, CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); await confirmationTask; Assert.Equal(Phase.ConnectionConfirmation, arena.Rounds.First().Phase); }
public static InputsRegistrationRequest CreateInputsRegistrationRequest(IEnumerable <InputRoundSignaturePair>?pairs, Round?round) { var roundId = round?.Id ?? Guid.NewGuid(); var inputRoundSignaturePairs = pairs ?? CreateInputRoundSignaturePairs(1, round?.Hash); var rnd = new InsecureRandom(); var client = new WabiSabiClient(new CredentialIssuerSecretKey(rnd).ComputeCredentialIssuerParameters(), 2, rnd, 4300000000000); // Input Reg var(zeroAmountCredentialRequest, _) = client.CreateRequestForZeroAmount(); var(zeroWeightCredentialRequest, _) = client.CreateRequestForZeroAmount(); return(new( roundId, inputRoundSignaturePairs, zeroAmountCredentialRequest, zeroWeightCredentialRequest)); }
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()); }
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); }
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 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); }
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); }
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 ArenaBuilder.From(config).With(mockRpc).CreateAndStartAsync(round); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); 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 insecureRandom = InsecureRandom.Instance; 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); using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), wabiSabiApi); await roundStateUpdater.StartAsync(CancellationToken.None); var keyChain = new KeyChain(km, new Kitchen("")); var task = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), aliceArenaClient, coin1, keyChain, 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); }
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); }