Skip to content

Commit

Permalink
Generalize KeyFile to EncryptedFile (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
KingGorrin committed Mar 6, 2024
1 parent a846e3c commit a97c2c1
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 59 deletions.
24 changes: 15 additions & 9 deletions src/Zenon.Tests/Wallet/KeyFileTest.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using FluentAssertions;
using System.Collections.Generic;
using Xunit;
using Zenon.Utils;
using Zenon.Wallet.Json;

namespace Zenon.Wallet
Expand All @@ -10,9 +12,12 @@ public KeyFileFixture()
{
Mnemonic = "route become dream access impulse price inform obtain engage ski believe awful absent pig thing vibrant possible exotic flee pepper marble rural fire fancy";
Password = "Secret";
KeyFileJson = new JKeyFile()
KeyFileJson = new JEncryptedFile()
{
baseAddress = "z1qqjnwjjpnue8xmmpanz6csze6tcmtzzdtfsww7",
metadata = new Dictionary<string, dynamic>()
{
{ "baseAddress", "z1qqjnwjjpnue8xmmpanz6csze6tcmtzzdtfsww7" }
},
crypto = new JCryptoData()
{
argon2Params = new JArgon2Params()
Expand All @@ -31,14 +36,14 @@ public KeyFileFixture()

public string Mnemonic { get; }
public string Password { get; }
public JKeyFile KeyFileJson { get; }
public JEncryptedFile KeyFileJson { get; }
}

public class KeyFileTest : IClassFixture<KeyFileFixture>
{
public string Mnemonic { get; }
public string Password { get; }
public JKeyFile KeyFileJson { get; }
public JEncryptedFile KeyFileJson { get; }

public KeyFileTest(KeyFileFixture fixture)
{
Expand All @@ -52,11 +57,12 @@ public void When_Encrypt_ExpectToEqual()
{
// Setup
var originalKeyStore = KeyStore.FromMnemonic(Mnemonic);
var originalKeyStoreData = BytesUtils.FromHexString(originalKeyStore.Entropy);

// Execute
var keyFile = KeyFile.Encrypt(originalKeyStore, Password);
keyFile = new KeyFile(keyFile.ToJson());
var decryptedKeyStore = keyFile.Decrypt(Password);
var keyFile = EncryptedFile.Encrypt(originalKeyStoreData, Password);
keyFile = new EncryptedFile(keyFile.ToJson());
var decryptedKeyStore = KeyStore.FromEntropy(BytesUtils.ToHexString(keyFile.Decrypt(Password)));

// Validate
originalKeyStore.Entropy.Should().BeEquivalentTo(decryptedKeyStore.Entropy);
Expand All @@ -69,10 +75,10 @@ public void When_Decrypt_ExpectToEqual()
{
// Setup
var originalKeyStore = KeyStore.FromMnemonic(Mnemonic);
var keyFile = new KeyFile(KeyFileJson);
var keyFile = new EncryptedFile(KeyFileJson);

// Execute
KeyStore decryptedKeyStore = keyFile.Decrypt(Password);
var decryptedKeyStore = KeyStore.FromEntropy(BytesUtils.ToHexString(keyFile.Decrypt(Password)));

// Validate
originalKeyStore.Entropy.Should().BeEquivalentTo(decryptedKeyStore.Entropy);
Expand Down
7 changes: 7 additions & 0 deletions src/Zenon/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,12 @@ public static class Constants
// Bridge
public const int BridgeMinGuardians = 5;
public const int BridgeMaximumFee = 10000;

// Wallet Metadata
public const string BaseAddressKey = "baseAddress";
public const string WalletTypeKey = "walletType";

// Wallet KeyStore
public const string KeyStoreWalletType = "keystore";
}
}
40 changes: 20 additions & 20 deletions src/Zenon/Wallet/KeyFile.cs → src/Zenon/Wallet/EncryptedFile.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
using Konscious.Security.Cryptography;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using Zenon.Model.Primitives;
using Zenon.Utils;
using Zenon.Wallet.Json;

namespace Zenon.Wallet
{
public class KeyFile
public class EncryptedFile
{
public static KeyFile Encrypt(KeyStore store, string password)
public static EncryptedFile Encrypt(byte[] data, string password, IDictionary<string, dynamic> metadata = null)
{
if (!AesGcm.IsSupported)
throw new NotSupportedException("AES-GCM Encryption is not supported on this platform.");
Expand All @@ -22,14 +22,14 @@ public static KeyFile Encrypt(KeyStore store, string password)

var timestamp = (int)Math.Round(ts.TotalMilliseconds / 1000);

var stored = new KeyFile(store.GetKeyPair().Address,
var stored = new EncryptedFile(metadata,
new CryptoData(new Argon2Params(new byte[0]), new byte[0], "aes-256-gcm", "argon2.IDKey", new byte[0]),
timestamp, 1);

return stored.EncryptEntropy(store, password);
return stored.EncryptData(data, password);
}

public KeyStore Decrypt(string password)
public byte[] Decrypt(string password)
{
try
{
Expand All @@ -45,16 +45,16 @@ public KeyStore Decrypt(string password)

using (var aes = new AesGcm(key))
{
var entropy = new byte[this.Crypto.CipherData.Length - 16];
var data = new byte[this.Crypto.CipherData.Length - 16];

aes.Decrypt(
this.Crypto.Nonce,
this.Crypto.CipherData.Sublist(0, this.Crypto.CipherData.Length - 16),
this.Crypto.CipherData.Sublist(this.Crypto.CipherData.Length - 16, this.Crypto.CipherData.Length),
entropy,
data,
Encoding.UTF8.GetBytes("zenon"));

return KeyStore.FromEntropy(BytesUtils.ToHexString(entropy));
return data;
}
}
catch (CryptographicException)
Expand Down Expand Up @@ -137,28 +137,28 @@ public JArgon2Params ToJson()
}
}

public KeyFile(JKeyFile json)
public EncryptedFile(JEncryptedFile json)
{
BaseAddress = Address.Parse(json.baseAddress);
Metadata = json.metadata != null ? new Dictionary<string, dynamic>(json.metadata) : new Dictionary<string, dynamic>();
Crypto = json.crypto != null ? new CryptoData(json.crypto) : null;
Timestamp = json.timestamp;
Version = json.version;
}

private KeyFile(Address baseAddress, CryptoData crypto, int timestamp, int version)
private EncryptedFile(IDictionary<string, dynamic> metadata, CryptoData crypto, int timestamp, int version)
{
this.BaseAddress = baseAddress;
this.Metadata = metadata != null ? new Dictionary<string, dynamic>(metadata) : new Dictionary<string, dynamic>();
this.Crypto = crypto;
this.Timestamp = timestamp;
this.Version = version;
}

public Address BaseAddress { get; }
public Dictionary<string, dynamic> Metadata { get; }
private CryptoData Crypto { get; }
public int Timestamp { get; }
public int Version { get; }

private KeyFile EncryptEntropy(KeyStore store, string password)
private EncryptedFile EncryptData(byte[] data, string password)
{
using (var generator = RandomNumberGenerator.Create())
{
Expand Down Expand Up @@ -198,7 +198,7 @@ private KeyFile EncryptEntropy(KeyStore store, string password)
var ciphertext = new byte[key.Length];
var tag = new byte[AesGcm.TagByteSizes.MaxSize];

aes.Encrypt(nonce, BytesUtils.FromHexString(store.Entropy), ciphertext, tag, Encoding.UTF8.GetBytes("zenon"));
aes.Encrypt(nonce, data, ciphertext, tag, Encoding.UTF8.GetBytes("zenon"));

this.Crypto.CipherData = ArrayUtils.Concat(ciphertext, tag);
this.Crypto.Nonce = nonce;
Expand All @@ -209,11 +209,11 @@ private KeyFile EncryptEntropy(KeyStore store, string password)
}
}

public JKeyFile ToJson()
public JEncryptedFile ToJson()
{
return new JKeyFile()
return new JEncryptedFile()
{
baseAddress = this.BaseAddress.ToString(),
metadata = new Dictionary<string, dynamic>(this.Metadata),
crypto = this.Crypto?.ToJson(),
timestamp = this.Timestamp,
version = this.Version
Expand All @@ -222,7 +222,7 @@ public JKeyFile ToJson()

public override string ToString()
{
return JsonConvert.SerializeObject(this.ToJson(), Formatting.Indented);
return JsonConvert.SerializeObject(JEncryptedFile.ToJObject(this.ToJson()), Formatting.Indented);
}
}
}
11 changes: 0 additions & 11 deletions src/Zenon/Wallet/InvalidKeyStorePath.cs

This file was deleted.

39 changes: 39 additions & 0 deletions src/Zenon/Wallet/Json/JEncryptedFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Newtonsoft.Json.Linq;
using System.Collections.Generic;

namespace Zenon.Wallet.Json
{
public class JEncryptedFile
{
public static JEncryptedFile FromJObject(JObject json)
{
var data = json.DeepClone() as JObject;
var file = new JEncryptedFile();
file.crypto = data["crypto"] != null ? data["crypto"].ToObject<JCryptoData>() : null;
data.Remove("crypto");
file.timestamp = data.Value<int>("timestamp");
data.Remove("timestamp");
file.version = data.Value<int>("version");
data.Remove("version");
file.metadata = data.Count != 0 ? data.ToObject<Dictionary<string, dynamic>>() : null;
return file;
}

public static JObject ToJObject(JEncryptedFile file)
{
var json = file.metadata != null ? JObject.FromObject(file.metadata) : new JObject();
if (file.crypto != null)
{
json["crypto"] = JObject.FromObject(file.crypto);
}
json["timestamp"] = file.timestamp;
json["version"] = file.version;
return json;
}

public Dictionary<string, dynamic> metadata { get; set; }
public JCryptoData crypto { get; set; }
public int timestamp { get; set; }
public int version { get; set; }
}
}
10 changes: 0 additions & 10 deletions src/Zenon/Wallet/Json/JKeyFile.cs

This file was deleted.

2 changes: 1 addition & 1 deletion src/Zenon/Wallet/KeyStoreDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public KeyStoreDefinition(string keyStorePath)
{
if (!File.Exists(keyStorePath))
{
throw new InvalidKeyStorePathException(
throw new WalletException(
$"Given keyStore does not exist ({keyStorePath})");
}

Expand Down
27 changes: 19 additions & 8 deletions src/Zenon/Wallet/KeyStoreManager.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Zenon.Utils;
using Zenon.Wallet.Json;

namespace Zenon.Wallet
Expand All @@ -24,25 +25,35 @@ public KeyStoreManager(string walletPath)

public KeyStoreDefinition SaveKeyStore(KeyStore store, string password, string name)
{
name = name ?? store.GetKeyPair(0).Address.ToString();

var encrypted = KeyFile.Encrypt(store, password);
var filePath = Path.Join(WalletPath, name);
var baseAddress = store.GetKeyPair(0).Address;
var encrypted = EncryptedFile.Encrypt(BytesUtils.FromHexString(store.Entropy), password, new Dictionary<string, dynamic>() {
{ Constants.BaseAddressKey, baseAddress.ToString() },
{ Constants.WalletTypeKey, Constants.KeyStoreWalletType }
});
var filePath = Path.Join(WalletPath, name ?? baseAddress.ToString());
Directory.CreateDirectory(WalletPath);
File.WriteAllText(filePath, JsonConvert.SerializeObject(encrypted.ToJson()));
File.WriteAllText(filePath, encrypted.ToString());
return new KeyStoreDefinition(filePath);
}

public KeyStore ReadKeyStore(string password, string keyStorePath)
{
if (!File.Exists(keyStorePath))
{
throw new InvalidKeyStorePathException(
throw new WalletException(
$"Given keyStore does not exist ({keyStorePath})");
}

var content = File.ReadAllText(keyStorePath);
return new KeyFile(JsonConvert.DeserializeObject<JKeyFile>(content)).Decrypt(password);
var file = new EncryptedFile(JEncryptedFile.FromJObject(JObject.Parse(content)));
if (file.Metadata != null &&
file.Metadata![Constants.WalletTypeKey] != null &&
file.Metadata![Constants.WalletTypeKey] != Constants.KeyStoreWalletType)
{
throw new WalletException($"Wallet type ({file.Metadata[Constants.WalletTypeKey]}) is not supported");
}
var data = file.Decrypt(password);
return KeyStore.FromEntropy(BytesUtils.ToHexString(data));
}

public KeyStoreDefinition FindKeyStore(string name)
Expand Down
11 changes: 11 additions & 0 deletions src/Zenon/Wallet/WalletException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace Zenon.Wallet
{
public class WalletException : Exception
{
public WalletException(string message)
: base(message)
{ }
}
}

0 comments on commit a97c2c1

Please sign in to comment.