Implement watchonly keymanager
MODIFY
WalletWasabi.Gui/Tabs/WalletManager/LoadWalletViewModel.cs
WalletWasabi.Gui/Tabs/WalletManager/LoadWalletViewModel.cs
@@ -227,7 +227,7 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
}
else
{
- Wallets.Add($"{hwi.Type}, fingerprint: {hwi.Fingerprint}");
+ Wallets.Add($"{hwi.Type}-{hwi.Fingerprint}");
}
}
@@ -295,6 +295,7 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
return null;
}
+ var walletName = SelectedWallet;
if (isHardwareWallet)
{
var lastEnumerationClone = LastHardwareWalletEnumeration.ToList();
@@ -309,12 +310,34 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
}
else
{
- var fingerprint = trimmedSelectedWallet.Substring(SelectedWallet.LastIndexOf("fingerprint:") + "fingerprint:".Length).Trim();
+ var fingerprint = trimmedSelectedWallet.Substring(SelectedWallet.LastIndexOf("-") + 1).Trim();
selectedHwi = lastEnumerationClone.First(x => x.Fingerprint == fingerprint);
}
ExtPubKey extPubKey = await HwiProcessManager.GetXpubAsync(selectedHwi);
- return null;
+ var walletFiles = new DirectoryInfo(Global.WalletsDir);
+ var walletBackupFiles = new DirectoryInfo(Global.WalletsDir);
+ walletName = null;
+ foreach (FileInfo walletFile in walletFiles.EnumerateFiles()
+ .Concat(walletBackupFiles.EnumerateFiles())
+ .OrderByDescending(x => x.LastAccessTimeUtc))
+ {
+ var km = KeyManager.FromFile(walletFile.FullName);
+ if (km.ExtPubKey == extPubKey) // We already had it.
+ {
+ walletName = walletFile.Name;
+ break;
+ }
+ }
+
+ if (walletName == null)
+ {
+ Logger.LogInfo<LoadWalletViewModel>("Hardware wallet wasn't used previously on this computer. Creating new wallet file.");
+
+ walletName = $"{selectedHwi.Type}-{selectedHwi.Fingerprint}";
+ var path = Global.GetWalletFullPath(walletName);
+ KeyManager.CreateNew(extPubKey, path);
+ }
}
else
{
@@ -322,10 +345,9 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
return null;
}
}
- else
- {
- var walletFullPath = Global.GetWalletFullPath(SelectedWallet);
- var walletBackupFullPath = Global.GetWalletBackupFullPath(SelectedWallet);
+
+ var walletFullPath = Global.GetWalletFullPath(walletName);
+ var walletBackupFullPath = Global.GetWalletBackupFullPath(walletName);
if (!File.Exists(walletFullPath) && !File.Exists(walletBackupFullPath))
{
// The selected wallet is not available any more (someone deleted it?).
@@ -358,7 +380,6 @@ namespace WalletWasabi.Gui.Tabs.WalletManager
return keyManager;
}
- }
catch (Exception ex)
{
// Initialization failed.

MODIFY
WalletWasabi/Hwi/HwiProcessManager.cs
WalletWasabi/Hwi/HwiProcessManager.cs
@@ -112,6 +112,12 @@ namespace WalletWasabi.Hwi
var jToken = JToken.Parse(response);
return jToken;
}
+ if (command.Contains("getxpub", StringComparison.OrdinalIgnoreCase))
+ {
+ string response = "{\"xpub\": \"xpub6DP9afdc7qsz7s7mwAvciAR2dV6vPC3gyiQbqKDzDcPAq3UQChKPimHc3uCYfTTkpoXdwRTFnVTBdFpM9ysbf6KV34uMqkD3zXr6FzkJtcB\"}";
+ var jToken = JToken.Parse(response);
+ return jToken;
+ }
else
{
string response = await process.StandardOutput.ReadToEndAsync();

MODIFY
WalletWasabi/JsonConverters/BitcoinEncryptedSecretNoECJsonConverter.cs
WalletWasabi/JsonConverters/BitcoinEncryptedSecretNoECJsonConverter.cs
@@ -15,6 +15,8 @@ namespace WalletWasabi.JsonConverters
/// <inheritdoc />
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
+ if (reader.Value is null) return null;
+
return new BitcoinEncryptedSecretNoEC((string)reader.Value);
}

MODIFY
WalletWasabi/JsonConverters/ByteArrayJsonConverter.cs
WalletWasabi/JsonConverters/ByteArrayJsonConverter.cs
@@ -18,6 +18,8 @@ namespace WalletWasabi.JsonConverters
/// <inheritdoc />
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
+ if (reader.Value is null) return null;
+
return Convert.FromBase64String((string)reader.Value);
}

MODIFY
WalletWasabi/KeyManagement/KeyManager.cs
WalletWasabi/KeyManagement/KeyManager.cs
@@ -56,6 +56,8 @@ namespace WalletWasabi.KeyManagement
public string FilePath { get; private set; }
private object ToFileLock { get; }
+ public bool IsWatchOnly { get; }
+
[JsonConstructor]
public KeyManager(BitcoinEncryptedSecretNoEC encryptedSecret, byte[] chainCode, ExtPubKey extPubKey, bool? passwordVerified, BlockchainState blockchainState, string filePath = null)
{
@@ -67,8 +69,9 @@ namespace WalletWasabi.KeyManagement
ScriptHdPubkeyMapLock = new object();
BlockchainStateLock = new object();
- EncryptedSecret = Guard.NotNull(nameof(encryptedSecret), encryptedSecret);
- ChainCode = Guard.NotNull(nameof(chainCode), chainCode);
+ EncryptedSecret = encryptedSecret;
+ IsWatchOnly = EncryptedSecret is null;
+ ChainCode = chainCode;
ExtPubKey = Guard.NotNull(nameof(extPubKey), extPubKey);
PasswordVerified = passwordVerified;
@@ -97,6 +100,7 @@ namespace WalletWasabi.KeyManagement
}
EncryptedSecret = Guard.NotNull(nameof(encryptedSecret), encryptedSecret);
+ IsWatchOnly = false;
ChainCode = Guard.NotNull(nameof(chainCode), chainCode);
var extKey = new ExtKey(encryptedSecret.GetKey(password), chainCode);
@@ -121,6 +125,11 @@ namespace WalletWasabi.KeyManagement
return new KeyManager(encryptedSecret, extKey.ChainCode, extKey.Derive(AccountKeyPath).Neuter(), false, new BlockchainState(), filePath);
}
+ public static KeyManager CreateNew(ExtPubKey extPubKey, string filePath = null)
+ {
+ return new KeyManager(null, null, extPubKey, null, new BlockchainState(), filePath);
+ }
+
public static KeyManager Recover(Mnemonic mnemonic, string password, string filePath = null)
{
Guard.NotNull(nameof(mnemonic), mnemonic);

Implement watchonly keymanager