Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<ItemGroup>
<!-- AspNetCore. -->
<PackageVersion Include="Markdown.Avalonia" Version="11.0.3-a1" />
<PackageVersion Include="Microsoft.AspNetCore.ResponseCompression" Version="2.3.0" />
<PackageVersion Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.19" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
Expand Down
8 changes: 8 additions & 0 deletions GingerCommon/Crypto/Random/DeterministicRandom.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ public DeterministicRandom(byte[] seed)
_s3 = BinaryPrimitives.ReadUInt64LittleEndian(new ReadOnlySpan<byte>(seed, 24, 8));
}

public DeterministicRandom(ReadOnlySpan<byte> seed)
{
_s0 = BinaryPrimitives.ReadUInt64LittleEndian(seed);
_s1 = BinaryPrimitives.ReadUInt64LittleEndian(seed[8..]);
_s2 = BinaryPrimitives.ReadUInt64LittleEndian(seed[16..]);
_s3 = BinaryPrimitives.ReadUInt64LittleEndian(seed[24..]);
}

public override void GetBytes(Span<byte> buffer)
{
int idx = 0, sizeFull = buffer.Length, size8 = sizeFull & ~7;
Expand Down
15 changes: 15 additions & 0 deletions GingerCommon/Crypto/Random/GingerRandom.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,19 @@ public virtual long GetInt64(long fromInclusive, long toExclusive)

return fromInclusive + (long)high;
}

public string GetString(int len, string chars)
{
if (string.IsNullOrEmpty(chars))
{
throw new ArgumentException($"{nameof(chars)} is empty");
}

Span<char> rndChars = len <= 256 ? stackalloc char[len] : new char[len];
for (int idx = 0; idx < len; idx++)
{
rndChars[idx] = chars[GetInt(0, chars.Length)];
}
return new string(rndChars);
}
}
13 changes: 13 additions & 0 deletions GingerCommon/Crypto/Random/RandomExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

namespace GingerCommon.Crypto.Random;

Expand All @@ -17,4 +20,14 @@ public static IList<T> Shuffle<T>(this IList<T> list, GingerRandom random)
}
return list;
}

public static void GenerateSeed(Span<byte> seed, ReadOnlySpan<byte> seedArray)
{
SHA256.HashData(seedArray, seed);
}

public static void GenerateSeed(Span<byte> seed, string seedString)
{
SHA256.HashData(Encoding.UTF8.GetBytes(seedString), seed);
}
}
33 changes: 33 additions & 0 deletions GingerCommon/Static/JsonUtils.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,41 @@
using GingerCommon.Logging;
using System;
using System.Text.Json;

namespace GingerCommon.Static;

public static class JsonUtils
{
public static readonly JsonSerializerOptions OptionCaseInsensitive = new() { PropertyNameCaseInsensitive = true };

public static string Serialize<TRequest>(TRequest obj)
{
return Serialize(obj, OptionCaseInsensitive);
}

public static string Serialize<TRequest>(TRequest obj, JsonSerializerOptions options)
{
try
{
return JsonSerializer.Serialize(obj, options);
}
catch
{
Logger.LogDebug($"Failed to serialize {typeof(TRequest)} from obj '{obj}'");
throw;
}
}

public static TResponse Deserialize<TResponse>(string jsonString)
{
try
{
return JsonSerializer.Deserialize<TResponse>(jsonString, OptionCaseInsensitive) ?? throw new InvalidOperationException("Deserialization error");
}
catch
{
Logger.LogDebug($"Failed to deserialize {typeof(TResponse)} from json '{jsonString}'");
throw;
}
}
}
2 changes: 1 addition & 1 deletion WalletWasabi.Backend/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public virtual void AddExtraServices(IServiceCollection services, string dataDir
}

[SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "This method gets called by the runtime. Use this method to configure the HTTP request pipeline")]
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, Global global)
public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env, Global global)
{
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
Expand Down
7 changes: 4 additions & 3 deletions WalletWasabi.Backend/WalletWasabi.Backend.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
<PropertyGroup>
<DisableImplicitNamespaceImports>true</DisableImplicitNamespaceImports>
<NoWarn>1701;1702;1705;1591;1573;CA1031;CA1822</NoWarn>
<Product>WalletWasabiApi</Product>
<Product>GingerWalletApi</Product>
<Copyright>MIT</Copyright>
<PackageTags>walletwasabi, wasabiwallet, wasabi, wallet, bitcoin, nbitcoin, tor, zerolink, wabisabi, coinjoin, fungibility, privacy, anonymity</PackageTags>
<PackageTags>gingerwallet, ginger, wallet, bitcoin, nbitcoin, tor, zerolink, wabisabi, coinjoin, fungibility, privacy, anonymity</PackageTags>
<RepositoryType>Git</RepositoryType>
<RepositoryUrl>https://github.com/zkSNACKs/WalletWasabi/</RepositoryUrl>
<RepositoryUrl>https://github.com/GingerPrivacy/GingerBackend/</RepositoryUrl>
<PathMap>$(MSBuildProjectDirectory)\=WalletWasabi.Backend</PathMap>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<StaticWebAssetsEnabled>false</StaticWebAssetsEnabled>
Expand All @@ -34,6 +34,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" />
<PackageReference Include="Swashbuckle.AspNetCore" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" />
<PackageReference Include="System.Text.Json" />
Expand Down
6 changes: 5 additions & 1 deletion WalletWasabi.Daemon/Global.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
using WalletWasabi.Models;
using WalletWasabi.Daemon.BuySell;
using WalletWasabi.Daemon.FeeRateProviders;
using WalletWasabi.SecretHunt;

namespace WalletWasabi.Daemon;

Expand Down Expand Up @@ -91,7 +92,7 @@ public Global(string dataDir, string configFilePath, Config config, UiConfig uiC
UpdateManager = new(DataDir, Config.DownloadNewVersion, HttpClientFactory.NewHttpClient(Mode.DefaultCircuit, maximumRedirects: 10), updateChecker);
TorStatusChecker = new TorStatusChecker(TimeSpan.FromHours(6), HttpClientFactory.NewHttpClient(Mode.DefaultCircuit));

RoundStateUpdaterCircuit = new PersonCircuit();
RoundStateUpdaterCircuit = new();

Cache = new MemoryCache(new MemoryCacheOptions
{
Expand Down Expand Up @@ -189,6 +190,7 @@ public Global(string dataDir, string configFilePath, Config config, UiConfig uiC
public Uri? OnionServiceUri { get; private set; }

private PersonCircuit RoundStateUpdaterCircuit { get; }

private AllTransactionStore AllTransactionStore { get; }
private IndexStore IndexStore { get; }

Expand Down Expand Up @@ -405,6 +407,8 @@ private void RegisterCoinJoinComponents()
Tor.Http.IHttpClient roundStateUpdaterHttpClient = CoordinatorHttpClientFactory.NewHttpClient(Mode.SingleCircuitPerLifetime, RoundStateUpdaterCircuit);
HostedServices.Register<RoundStateUpdater>(() => new RoundStateUpdater(TimeSpan.FromSeconds(0.1), allowedCoordinationIdentifiers, new WabiSabiHttpApiClient(roundStateUpdaterHttpClient)), "Round info updater");

HostedServices.Register<SecretHuntUpdater>(() => new SecretHuntUpdater(WalletManager, TimeSpan.FromSeconds(0.1), CoordinatorHttpClientFactory, HostedServices.Get<RoundStateUpdater>()), "Secret Hunt updater");

var coinJoinConfiguration = new CoinJoinConfiguration(Config.CoordinatorIdentifier, Config.MaxCoordinationFeeRate, Config.MaxCoinjoinMiningFeeRate, Config.AbsoluteMinInputCount, AllowSoloCoinjoining: false);
HostedServices.Register<CoinJoinManager>(() => new CoinJoinManager(WalletManager, HostedServices.Get<RoundStateUpdater>(), CoordinatorHttpClientFactory, HostedServices.Get<WasabiSynchronizer>(), coinJoinConfiguration, CoinPrison), "CoinJoin Manager");
}
Expand Down
5 changes: 5 additions & 0 deletions WalletWasabi.Fluent/Extensions/DateTimeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ public static string ToUserFacingString(this DateTime value, bool withTime = tru
return value.ToString(withTime ? "HH:mm MMMM d, yyyy" : "MMMM d, yyyy", Resources.Culture);
}

public static string ToUserFacingStringFixLength(this DateTime value, bool withTime = true)
{
return value.ToString(withTime ? "HH:mm MMM dd, yyyy" : "MMM dd, yyyy", Resources.Culture);
}

public static string ToUserFacingFriendlyString(this DateTime value)
{
if (value.Date == DateTime.Today)
Expand Down
2 changes: 2 additions & 0 deletions WalletWasabi.Fluent/Extensions/DateTimeOffsetExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ public static class DateTimeOffsetExtensions
{
public static string ToUserFacingString(this DateTimeOffset value, bool withTime = true) => value.DateTime.ToUserFacingString(withTime);

public static string ToUserFacingStringFixLength(this DateTimeOffset value, bool withTime = true) => value.DateTime.ToUserFacingStringFixLength(withTime);

public static string ToUserFacingFriendlyString(this DateTimeOffset value) => value.DateTime.ToUserFacingFriendlyString();

public static string ToOnlyTimeString(this DateTimeOffset value) => value.ToString("HH:mm", CultureInfo.InvariantCulture);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ public WalletViewModel(WalletModel walletModel, Wallet wallet)

SignMessageCommand = ReactiveCommand.Create(() => UiContext.Navigate().To().SignMessage(WalletModel));

SecretHuntCommand = ReactiveCommand.Create(() => UiContext.Navigate().To().SecretHunt(WalletModel));

CoinjoinPlayerViewModel = new CoinjoinPlayerViewModel(WalletModel, Settings);

Tiles = GetTiles().ToList();
Expand Down Expand Up @@ -179,6 +181,8 @@ public WalletViewModel(WalletModel walletModel, Wallet wallet)

public ICommand SignMessageCommand { get; private set; }

public ICommand SecretHuntCommand { get; private set; }

public ICommand WalletSettingsCommand { get; private set; }

public ICommand WalletStatsCommand { get; private set; }
Expand Down Expand Up @@ -278,7 +282,8 @@ private ISearchItem CreateSendItem()
{
SendCommand.ExecuteIfCan();
return Task.CompletedTask;
}, Resources.Wallet, Resources.AboutViewModelKeywords.ToKeywords()) { Icon = "wallet_action_send", IsDefault = true, Priority = 1 };
}, Resources.Wallet, Resources.AboutViewModelKeywords.ToKeywords())
{ Icon = "wallet_action_send", IsDefault = true, Priority = 1 };
}

private IEnumerable<ActivatableViewModel> GetTiles()
Expand Down
13 changes: 9 additions & 4 deletions WalletWasabi.Fluent/HomeScreen/Wallets/Views/WalletView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@
</MultiBinding>
</Separator.IsVisible>
</Separator>
<MenuItem Header="{x:Static lang:Resources.SecretHuntViewModelTitle}"
Command="{Binding SecretHuntCommand}">
<MenuItem.IsVisible>
<Binding Path="WalletModel.IsWatchOnlyWallet" Converter="{x:Static BoolConverters.Not}" />
</MenuItem.IsVisible>
<MenuItem.Icon>
<PathIcon Data="{StaticResource info_regular}" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{x:Static lang:Resources.SignMessage}"
Command="{Binding SignMessageCommand}">
<MenuItem.IsVisible>
Expand Down Expand Up @@ -94,7 +103,6 @@
Width="11" Height="11"
Stroke="{DynamicResource RegionBrush}" StrokeThickness="2"
Fill="{DynamicResource WarningMessageForeground}" />

</Panel>

<!-- Buy -->
Expand All @@ -121,7 +129,6 @@
Width="11" Height="11"
Stroke="{DynamicResource RegionBrush}" StrokeThickness="2"
Fill="{DynamicResource WarningMessageForeground}" />

</Panel>
</StackPanel>

Expand Down Expand Up @@ -183,8 +190,6 @@
<TextBlock Text="{x:Static lang:Resources.WalletReceive}" />
</Button>
</StackPanel>


</StackPanel>
</ContentArea.TopContent>
<DockPanel Name="Panel" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
Expand Down
3 changes: 3 additions & 0 deletions WalletWasabi.Fluent/Models/Wallets/WalletModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using WalletWasabi.Fluent.HomeScreen.BuySell.Models;
using WalletWasabi.Fluent.HomeScreen.Labels.Models;
using WalletWasabi.Fluent.Models.Transactions;
using WalletWasabi.SecretHunt;
using WalletWasabi.Wallets;

namespace WalletWasabi.Fluent.Models.Wallets;
Expand Down Expand Up @@ -146,6 +147,8 @@ public PrivacySuggestionsModel GetPrivacySuggestionsModel(SendFlowModel sendFlow
return new PrivacySuggestionsModel(sendFlow);
}

public List<SecretHuntEventResultModel> GetSecretHuntResults() => Wallet.KeyManager.GetSecretHuntEventResultModelList();

public string SignMessage(string messageToSign, HdPubKey hdPubKey)
{
return Wallet.KeyChain.SignMessage(messageToSign, hdPubKey);
Expand Down
6 changes: 5 additions & 1 deletion WalletWasabi.Fluent/Models/Wallets/WalletSettingsModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public partial class WalletSettingsModel : ReactiveObject
[AutoNotify] private double _valueLossRateNormal;
[AutoNotify] private double _targetCoinCountPerBucket;
[AutoNotify] private bool _useOldCoinSelectorAsFallback;
[AutoNotify] private bool _enableSecretHunt;

public WalletSettingsModel(KeyManager keyManager, bool isNewWallet = false, bool isCoinJoinPaused = false)
{
Expand All @@ -45,6 +46,7 @@ public WalletSettingsModel(KeyManager keyManager, bool isNewWallet = false, bool
_isDirty = isNewWallet;
IsCoinJoinPaused = isCoinJoinPaused;

_enableSecretHunt = _keyManager.EnableSecretHunt;
_autoCoinjoin = _keyManager.AutoCoinJoin;
_isCoinjoinProfileSelected = _keyManager.IsCoinjoinProfileSelected;
_preferPsbtWorkflow = _keyManager.PreferPsbtWorkflow;
Expand Down Expand Up @@ -73,6 +75,7 @@ public WalletSettingsModel(KeyManager keyManager, bool isNewWallet = false, bool
WalletType = WalletHelpers.GetType(_keyManager);

this.WhenAnyValue(
x => x.EnableSecretHunt,
x => x.AutoCoinjoin,
x => x.IsCoinjoinProfileSelected,
x => x.PreferPsbtWorkflow,
Expand All @@ -81,7 +84,7 @@ public WalletSettingsModel(KeyManager keyManager, bool isNewWallet = false, bool
x => x.RedCoinIsolation,
x => x.FeeRateMedianTimeFrameHours,
x => x.IsRecovering,
(_, _, _, _, _, _, _, _) => Unit.Default)
(_, _, _, _, _, _, _, _, _) => Unit.Default)
.Skip(1)
.Do(_ => SetValues())
.Subscribe();
Expand Down Expand Up @@ -132,6 +135,7 @@ public WalletId Save()

private void SetValues()
{
_keyManager.EnableSecretHunt = EnableSecretHunt;
_keyManager.AutoCoinJoin = AutoCoinjoin;
_keyManager.IsCoinjoinProfileSelected = IsCoinjoinProfileSelected;
_keyManager.PreferPsbtWorkflow = PreferPsbtWorkflow;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Avalonia.Controls;
using System.Collections.ObjectModel;
using WalletWasabi.Fluent.Extensions;
using WalletWasabi.SecretHunt;

namespace WalletWasabi.Fluent.SecretHunt.ViewModels;

// Using multiple ViewModels in a tree is a pain in the neck in Avalonia, so we don't use it
public class SecretHuntItemViewModel
{
public SecretHuntItemViewModel(SecretHuntEventResultModel model, int idx)
{
if (idx >= -1)
{
ExtraSecret = idx == -1;
Description = idx == -1 ? (model.ExtraSecret ?? "") : model.Secrets[idx];
Icon = ExtraSecret ? "double_shield_regular" : "private_key_regular";
ToolTip = ExtraSecret ? Lang.Resources.SecretHuntExtraSecret : Lang.Resources.SecretHuntSecret;
return;
}

IsEventItem = true;
Id = model.Id;
StartDate = model.StartDate;
EndDate = model.EndDate;
Description = model.Description;

if (model.ExtraSecret is not null)
{
Secrets.Add(new(model, -1));
}
for (int secretIdx = 0, len = model.Secrets.Count; secretIdx < len; secretIdx++)
{
Secrets.Add(new(model, secretIdx));
}

UpdateStatus();
}

public void UpdateStatus()
{
if (IsEventItem)
{
bool extraSecret = Secrets.Count > 0 && Secrets[0].ExtraSecret;
Icon = extraSecret ? "checkmark_circle_filled" : "book_question_mark_regular";
ToolTip = extraSecret ? Lang.Resources.SecretHuntEventSolved : Lang.Resources.SecretHuntEventUnsolved;
}
}

private static GridLength GridEmpty = new(0);
private static GridLength GridDate = new(100);

public GridLength GridDateLength => IsEventItem ? GridDate : GridEmpty;

public string StartDateString => StartDate.ToUserFacingStringFixLength(false);
public string StartDateToolTipString => StartDate.ToUserFacingString();
public string EndDateString => EndDate.ToUserFacingStringFixLength(false);
public string EndDateToolTipString => EndDate.ToUserFacingString();

public bool ExtraSecret { get; set; } = false;
public bool IsEventItem { get; set; } = false;

public string Icon { get; set; } = "";
public string ToolTip { get; set; } = "";

// Event data
public string Id { get; set; } = "";

public DateTimeOffset StartDate { get; set; } = DateTimeOffset.UnixEpoch;
public DateTimeOffset EndDate { get; set; } = DateTimeOffset.UnixEpoch;
public string Description { get; set; } = "";

public ObservableCollection<SecretHuntItemViewModel> Secrets { get; set; } = [];
}
Loading
Loading