Skip to content

Commit

Permalink
IKeyStore & Web3KeyStore
Browse files Browse the repository at this point in the history
  • Loading branch information
dahlia committed Mar 24, 2020
1 parent 3760cca commit 8e0692a
Show file tree
Hide file tree
Showing 7 changed files with 472 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGES.md
Expand Up @@ -75,6 +75,8 @@ To be released.
- Added `BlockVerificationState` class, a subclass of `PreloadState`.
[[#798]]
- Added `AppProtocolVersion` struct. [[#266], [#815]]
- Added `IKeyStore` interface. [[#831]]
- Added `Web3KeyStore` class. [[#831]]
- Added `BlockDigest` struct. [[#785]]
- Added `BlockHeader` struct. [[#785]]
- Added `IStore.GetBlockDigest(HashDigest<SHA256>)` method. [[#785]]
Expand All @@ -86,6 +88,8 @@ To be released.
[[#266], [#815]]
- Added `Peer.IsCompatibleWith()` method. [[#266], [#815]]
- Added `TxViolatingBlockPolicyException` class. [[#827]]
- Added `KeyStoreException` class. [[#831]]
- Added `NoKeyException` class. [[#831]]

### Behavioral changes

Expand Down Expand Up @@ -126,6 +130,7 @@ To be released.
[#815]: https://github.com/planetarium/libplanet/pull/815
[#820]: https://github.com/planetarium/libplanet/pull/820
[#825]: https://github.com/planetarium/libplanet/pull/825
[#831]: https://github.com/planetarium/libplanet/pull/831


Version 0.8.0
Expand Down
93 changes: 93 additions & 0 deletions Libplanet.Tests/KeyStore/KeyStoreTest.cs
@@ -0,0 +1,93 @@
using System;
using System.Linq;
using Libplanet.Crypto;
using Libplanet.KeyStore;
using Xunit;

namespace Libplanet.Tests.KeyStore
{
public abstract class KeyStoreTest<T>
where T : IKeyStore
{
public abstract T KeyStore { get; }

[Fact]
public void List()
{
Assert.Empty(KeyStore.List());
Assert.Empty(KeyStore.ListIds());

var key = new PrivateKey();
ProtectedPrivateKey ppk = ProtectedPrivateKey.Protect(key, "pass");
Guid id = KeyStore.Add(ppk);

Assert.Single(KeyStore.List());
Tuple<Guid, ProtectedPrivateKey> pair = KeyStore.List().First();
Assert.Equal(id, pair.Item1);
Assert.Equal(ppk.Address, pair.Item2.Address);
Assert.Equal(new[] { id }, KeyStore.ListIds());

var key2 = new PrivateKey();
ProtectedPrivateKey ppk2 = ProtectedPrivateKey.Protect(key2, "pass");
Guid id2 = KeyStore.Add(ppk2);

Assert.Equal(
new[] { (id, ppk.Address), (id2, ppk2.Address) }.ToHashSet(),
KeyStore.List().Select(tuple => (tuple.Item1, tuple.Item2.Address)).ToHashSet()
);
Assert.Equal(
new[] { id, id2 }.ToHashSet(),
KeyStore.ListIds().ToHashSet()
);
}

[Fact]
public void Get()
{
var key = new PrivateKey();
ProtectedPrivateKey ppk = ProtectedPrivateKey.Protect(key, "pass");
Guid id = KeyStore.Add(ppk);

Assert.Equal(ppk.Address, KeyStore.Get(id).Address);
Assert.Equal(key, KeyStore.Get(id).Unprotect("pass"));
Assert.Throws<NoKeyException>(() => KeyStore.Get(Guid.NewGuid()));
}

[Fact]
public void Add()
{
var key = new PrivateKey();
ProtectedPrivateKey ppk = ProtectedPrivateKey.Protect(key, "pass");
Guid id = KeyStore.Add(ppk);
var key2 = new PrivateKey();
ProtectedPrivateKey ppk2 = ProtectedPrivateKey.Protect(key2, "pass");
Guid id2 = KeyStore.Add(ppk2);

// Key ids should be unique.
Assert.NotEqual(id, id2);

Assert.Equal(ppk.Address, KeyStore.Get(id).Address);
Assert.Equal(ppk2.Address, KeyStore.Get(id2).Address);

(Guid, Address Address) ToSimplePair(Tuple<Guid, ProtectedPrivateKey> pair) =>
(pair.Item1, pair.Item2.Address);

Assert.Contains((id, ppk.Address), KeyStore.List().Select(ToSimplePair));
Assert.Contains((id2, ppk2.Address), KeyStore.List().Select(ToSimplePair));
}

[Fact]
public void Remove()
{
var key = new PrivateKey();
Guid id = KeyStore.Add(ProtectedPrivateKey.Protect(key, "pass"));

Assert.Throws<NoKeyException>(() => KeyStore.Remove(Guid.NewGuid()));
Assert.Equal(new[] { id }, KeyStore.ListIds());

KeyStore.Remove(id);
Assert.Throws<NoKeyException>(() => KeyStore.Get(id));
Assert.Empty(KeyStore.ListIds());
}
}
}
80 changes: 80 additions & 0 deletions Libplanet.Tests/KeyStore/Web3KeyStoreTest.cs
@@ -0,0 +1,80 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using Libplanet.Crypto;
using Libplanet.KeyStore;
using Xunit;

namespace Libplanet.Tests.KeyStore
{
public class Web3KeyStoreTest : KeyStoreTest<Web3KeyStore>, IDisposable
{
public Web3KeyStoreTest()
{
var tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
KeyStore = new Web3KeyStore(tempDir);
}

public override Web3KeyStore KeyStore { get; }

[Fact]
public void Constructor()
{
// Constructor creates a directory if needed.
Assert.True(Directory.Exists(KeyStore.Path));
}

[Fact]
public void DefaultKeyStore()
{
string path = Web3KeyStore.DefaultKeyStore.Path;
if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD) ||
RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Assert.Equal(
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
".config",
"planetarium",
"keystore"
),
path
);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Assert.Equal(
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"planetarium",
"keystore"
),
path
);
}
}

[Fact]
public void Load()
{
string idStr = "c8b0c0b1-82a0-41cd-9528-a22a0f208dee";
Guid id = Guid.Parse(idStr);
var path = Path.Combine(KeyStore.Path, $"UTC--2020-03-23T00-00-00Z--{idStr}");
var key = new PrivateKey();
ProtectedPrivateKey ppk = ProtectedPrivateKey.Protect(key, "pass");
using (var f = new FileStream(path, FileMode.Create))
{
ppk.WriteJson(f, id);
}

Assert.Equal(new[] { id }, KeyStore.ListIds());
Assert.Equal(ppk.Address, KeyStore.Get(id).Address);
}

public void Dispose()
{
Directory.Delete(KeyStore.Path, true);
}
}
}
54 changes: 54 additions & 0 deletions Libplanet/KeyStore/IKeyStore.cs
@@ -0,0 +1,54 @@
#nullable enable
using System;
using System.Collections.Generic;

namespace Libplanet.KeyStore
{
/// <summary>
/// The interface to store <see cref="ProtectedPrivateKey"/>s. An appropriate implementation
/// should be used according to a running platform.
/// </summary>
public interface IKeyStore
{
/// <summary>
/// Lists all keys IDs in the key store.
/// </summary>
/// <returns>All keys IDs in the key store. The order is undefined and not deterministic.
/// </returns>
public IEnumerable<Guid> ListIds();

/// <summary>
/// Lists all keys in the key store.
/// </summary>
/// <returns>All keys in the key store. The order is undefined and not deterministic.
/// </returns>
public IEnumerable<Tuple<Guid, ProtectedPrivateKey>> List();

/// <summary>
/// Looks for a key having the requested <paramref name="id"/> in the key store.
/// </summary>
/// <param name="id">The key ID to look for.</param>
/// <returns>The found key.</returns>
/// <exception cref="NoKeyException">Thrown when there are no key of the given
/// <paramref name="id"/>.</exception>
public ProtectedPrivateKey Get(Guid id);

/// <summary>
/// Adds a new <paramref name="key"/> into the key store.
/// </summary>
/// <param name="key">A key to add.</param>
/// <returns>The id of the added <paramref name="key"/>.</returns>
/// <exception cref="ArgumentNullException">Thrown when <c>null</c> is passed to
/// <paramref name="key"/>.</exception>
public Guid Add(ProtectedPrivateKey key);

/// <summary>
/// Removes a key having the given <pramref name="id"/> from the key store.
/// </summary>
/// <param name="id">The key ID to remove.</param>
/// <exception cref="NoKeyException">Thrown when there is no key having
/// the given <paramref name="id"/>.</exception>
public void Remove(Guid id);
}
}
#nullable restore
21 changes: 21 additions & 0 deletions Libplanet/KeyStore/KeyStoreException.cs
@@ -0,0 +1,21 @@
using System;

namespace Libplanet.KeyStore
{
/// <summary>
/// Serves as the base class for exceptions thrown by <see cref="IKeyStore"/>
/// implementations.
/// </summary>
public abstract class KeyStoreException : Exception
{
/// <summary>
/// Initializes a new instance with the given <paramref name="message"/>.
/// </summary>
/// <param name="message">A descriptive error message for programmers.
/// Goes to <see cref="System.Exception.Message"/>.</param>
public KeyStoreException(string message)
: base(message)
{
}
}
}
31 changes: 31 additions & 0 deletions Libplanet/KeyStore/NoKeyException.cs
@@ -0,0 +1,31 @@
using System;

namespace Libplanet.KeyStore
{
/// <summary>
/// The exception that is thrown when there is no <see cref="ProtectedPrivateKey"/>
/// with a given key ID in an <see cref="IKeyStore"/>.
/// </summary>
public class NoKeyException : KeyStoreException
{
/// <summary>
/// Instantiates a new exception object with proper metadata.
/// </summary>
/// <param name="keyId">The key ID tried to look for.
/// It is automatically included to the <see cref="System.Exception.Message"/>
/// string.
/// </param>
/// <param name="message">A descriptive error message for programmers.
/// Goes to <see cref="System.Exception.Message"/>.</param>
public NoKeyException(Guid keyId, string message)
: base($"{message}: {keyId}")
{
KeyId = keyId;
}

/// <summary>
/// The key ID tried to look for.
/// </summary>
public Guid KeyId { get; }
}
}

0 comments on commit 8e0692a

Please sign in to comment.