Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Light refacto + add documentation + add unit tests #13

Merged
merged 8 commits into from
Feb 22, 2024
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
[![Nuget](https://img.shields.io/nuget/v/Substrate.NET.Wallet)](https://www.nuget.org/packages/Substrate.NET.Wallet/)
[![GitHub issues](https://img.shields.io/github/issues/SubstrateGaming/Substrate.NET.Wallet.svg)](https://github.com/SubstrateGaming/Substrate.NET.Wallet/issues)
[![license](https://img.shields.io/github/license/SubstrateGaming/Substrate.NET.Wallet)](https://github.com/SubstrateGaming/Substrate.NET.Wallet/blob/origin/LICENSE)
[![contributors](https://img.shields.io/github/contributors/SubstrateGaming/Substrate.NET.Wallet)](https://github.com/SubstrateGaming/Substrate.NET.Wallet/graphs/contributors)
[![contributors](https://img.shields.io/github/contributors/SubstrateGaming/Substrate.NET.Wallet)](https://github.com/SubstrateGaming/Substrate.NET.Wallet/graphs/contributors)
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=SubstrateGaming_Substrate.NET.Wallet&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=SubstrateGaming_Substrate.NET.Wallet)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=SubstrateGaming_Substrate.NET.Wallet&metric=coverage)](https://sonarcloud.io/summary/new_code?id=SubstrateGaming_Substrate.NET.Wallet)

## How to use ?

Expand Down
29 changes: 29 additions & 0 deletions Substrate.NET.Wallet.Test/ArrayExtensionTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using NUnit.Framework;
using Substrate.NET.Wallet.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Substrate.NET.Wallet.Test
{
internal class ArrayExtensionTest
{
[Test]
public void SubArray_WithNullInput_ShouldFail()
{
byte[] input = null;
Assert.Throws<ArgumentNullException>(() => input.SubArray(0, 1));
}

[Test]
public void SubArray_WithIncoherentInputs_ShouldFail() {
byte[] input = { 0, 0, 0, 0 };
Assert.Throws<ArgumentOutOfRangeException>(() => input.SubArray(10));
Assert.Throws<ArgumentOutOfRangeException>(() => input.SubArray(-1));
Assert.Throws<ArgumentOutOfRangeException>(() => input.SubArray(3, 2));
Assert.Throws<ArgumentOutOfRangeException>(() => input.SubArray(3, 10));
}
}
}
20 changes: 20 additions & 0 deletions Substrate.NET.Wallet.Test/Keyrings/DerivationTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using NUnit.Framework;
using Substrate.NET.Wallet.Keyring;
using Substrate.NetApi;
using Substrate.NetApi.Model.Types;
using System;

namespace Substrate.NET.Wallet.Test.Keyrings
{
Expand Down Expand Up @@ -63,5 +65,23 @@ public void SubKeysScenario_ShouldSuceed(string uri, string expectedAddress, str
Assert.That(res.Account.Bytes, Is.EquivalentTo(Utils.HexToByteArray(expectedPublicKey)));
Assert.That(res.Address, Is.EqualTo(expectedAddress));
}

[Test]
public void HardDerive_Ed25519_ShouldSuceed() {
var keyring = new Keyring.Keyring();
var wallet = keyring.AddFromMnemonic(Mnemonic.GenerateMnemonic(Mnemonic.MnemonicSize.Words12), new Meta() { Name = "My account name" }, KeyType.Ed25519);

var derivedWallet = wallet.Derive("//HardDerive");

Assert.That(wallet.Address, Is.Not.EqualTo(derivedWallet.Address));
}

[Test]
public void SoftDerive_Ed25519_ShouldFail() {
var keyring = new Keyring.Keyring();
var wallet = keyring.AddFromMnemonic(Mnemonic.GenerateMnemonic(Mnemonic.MnemonicSize.Words12), new Meta() { Name = "My account name" }, KeyType.Ed25519);

Assert.Throws<InvalidOperationException>(() => wallet.Derive("/SoftIsNotAllowed"));
}
}
}
10 changes: 10 additions & 0 deletions Substrate.NET.Wallet.Test/Keyrings/UriTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ public void KeyExtractUri_DeriveMultiple_4()
Assert.That(res.Path[1].ChainCode, Is.EqualTo(HelloWorldDotBytes));
}

[Test]
public void KeyExtractPath_EdgeCase()
{
var res = Uri.KeyExtractPath("//0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");

Assert.That(res.Path.Count, Is.EqualTo(1));
Assert.That(res.Path[0].IsHard, Is.True);
}

[Test]
public void KeyExtractUri_DeriveMultiple_Alice()
{
Expand Down Expand Up @@ -158,6 +167,7 @@ public void KeyExtractPath_FromHard()
Assert.That(res.Parts, Is.EqualTo(new string[1] { "//1" }));
Assert.That(res.Path.Count, Is.EqualTo(1));
Assert.That(res.Path[0].IsHard, Is.EqualTo(true));
Assert.That(res.Path[0].IsSoft, Is.EqualTo(false));
Assert.That(res.Path[0].ChainCode, Is.EqualTo(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }));
}

Expand Down
11 changes: 5 additions & 6 deletions Substrate.NET.Wallet.Test/MainTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,12 @@ public class PairDefTest

public Wallet GetWallet()
{
var keyType = KeyType.Sr25519;
return Pair.CreatePair(
new KeyringAddress(KeyType.Sr25519),
new PairInfo(
Utils.HexToByteArray(PublickKey),
Utils.HexToByteArray(SecretKey)
)
);
new KeyringAddress(keyType),
Account.Build(keyType,
Utils.HexToByteArray(SecretKey),
Utils.HexToByteArray(PublickKey)));
}
}
}
Expand Down
1 change: 0 additions & 1 deletion Substrate.NET.Wallet.Test/Substrate.NET.Wallet.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Substrate.NET.Schnorrkel" Version="1.2.2" />
</ItemGroup>

<ItemGroup>
Expand Down
36 changes: 36 additions & 0 deletions Substrate.NET.Wallet.Test/WalletTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace Substrate.NET.Wallet.Test
{
public class WalletTest
{
private Wallet randomWallet;
[SetUp]
public void Setup()
{
Expand All @@ -25,6 +26,11 @@ public void Setup()
SystemInteraction.ReadPersistent = f => File.ReadAllText(dir(f));
SystemInteraction.PersistentExists = f => File.Exists(dir(f));
SystemInteraction.Persist = (f, c) => File.WriteAllText(dir(f), c);

randomWallet = Keyring.Keyring.CreateFromUri(
string.Join(" ", Mnemonic.GenerateMnemonic(MnemonicSize.Words12)),
new Meta(),
KeyType.Sr25519, 42);
}

[Test]
Expand Down Expand Up @@ -83,5 +89,35 @@ public void CreateWallet_SaveIt_ThenTryLoad_ShouldSuceed(string walletName, stri
Assert.That(loadedWallet.Account.Bytes, Is.EqualTo(wallet.Account.Bytes));
Assert.That(loadedWallet.Account.PrivateKey, Is.EqualTo(wallet.Account.PrivateKey));
}

[Test]
public void WalletStored_WhenNoCachingSet_ShoudFail()
{
SystemInteraction.DataExists = f => false;
SystemInteraction.PersistentExists = f => false;

Assert.That(Wallet.TryLoad("test", out Wallet wallet), Is.False);
}

[Test]
public void WalletSign_WhenLock_ShoudFail()
{
randomWallet.Lock();
Assert.Throws<InvalidOperationException>(() => randomWallet.Sign("testMessage"));
}

[Test]
public void WalletVerify_WhenLock_ShoudFail()
{
randomWallet.Lock();
Assert.Throws<InvalidOperationException>(() => randomWallet.Verify(new byte[0], "testMessage"));
}

[Test]
public void WalletDerive_WhenLock_ShoudFail()
{
randomWallet.Lock();
Assert.Throws<InvalidOperationException>(() => randomWallet.Derive("testDerive"));
}
}
}
62 changes: 61 additions & 1 deletion Substrate.NET.Wallet/Derivation/DeriveJunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,42 @@

namespace Substrate.NET.Wallet.Derivation
{
/// <summary>
/// Analyse a derivation phrase and categorize it as soft and hard derivation
/// </summary>
public class DeriveJunction
{
/// <summary>
/// Junction id lenght
/// </summary>
public const int JUNCTION_ID_LEN = 32;

/// <summary>
/// Regex number pattern
/// </summary>
public const string NUMBER_PATTERN = "^\\d+$";

/// <summary>
/// Return true if it is a hard derivation (starts with //)
/// </summary>
public bool IsHard { get; internal set; }

/// <summary>
/// Return true if it is a soft derivation (starts with //)
/// </summary>
public bool IsSoft => !IsHard;

/// <summary>
/// Represents the chain code associated with the junction
/// </summary>
public byte[] ChainCode { get; internal set; }

public static byte[] CompactAddLength(byte[] input)
/// <summary>
/// Add lenght as first byte
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
internal static byte[] CompactAddLength(byte[] input)
{
var u256 = new U256(input.Length);
var lenghtCompact = new CompactInteger(u256);
Expand All @@ -29,17 +55,31 @@ public static byte[] CompactAddLength(byte[] input)
return compacted.ToArray();
}

/// <summary>
/// Harden the given string
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public DeriveJunction Hard(string value)
{
return Soft(value).Harden();
}

/// <summary>
///
/// </summary>
/// <returns></returns>
public DeriveJunction Harden()
{
IsHard = true;
return this;
}

/// <summary>
/// Soft the given byte array
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public DeriveJunction Soft(byte[] value)
{
if (value.Length > JUNCTION_ID_LEN)
Expand All @@ -52,11 +92,21 @@ public DeriveJunction Soft(byte[] value)
return this;
}

/// <summary>
/// Soft the given number
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public DeriveJunction Soft(BigInteger value)
{
return Soft(value.ToByteArray());
}

/// <summary>
/// Soft the given string
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public DeriveJunction Soft(string value)
{
if (value.IsHex())
Expand All @@ -65,12 +115,22 @@ public DeriveJunction Soft(string value)
return Soft(CompactAddLength(value.ToBytes()));
}

/// <summary>
/// Return a soft representation
/// </summary>
/// <returns></returns>
public DeriveJunction Soften()
{
IsHard = false;
return this;
}

/// <summary>
/// Create a <see cref="DeriveJunction"/> instance from a derivation string
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public static DeriveJunction From(string p)
{
var result = new DeriveJunction();
Expand Down
58 changes: 52 additions & 6 deletions Substrate.NET.Wallet/Derivation/Keys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,86 @@

namespace Substrate.NET.Wallet.Derivation
{
/// <summary>
/// Constants definition
/// </summary>
public static class Keys
{
// The length of a Ristretto Schnorr `MiniSecretKey`, in bytes.
/// <summary>
/// The length of a Ristretto Schnorr `MiniSecretKey`, in bytes.
/// </summary>
public const int MINI_SECRET_KEY_LENGTH = 32;

// The length of a Ristretto Schnorr `PublicKey`, in bytes.
/// <summary>
/// The length of a Ristretto Schnorr `PublicKey`, in bytes.
/// </summary>
public const int PUBLIC_KEY_LENGTH = 32;

// The length of the "key" portion of a Ristretto Schnorr secret key, in bytes.
/// <summary>
/// The length of the "key" portion of a Ristretto Schnorr secret key, in bytes.
/// </summary>
public const int SECRET_KEY_KEY_LENGTH = 32;

// The length of the "nonce" portion of a Ristretto Schnorr secret key, in bytes.
/// <summary>
/// The length of the "nonce" portion of a Ristretto Schnorr secret key, in bytes.
/// </summary>
public const int SECRET_KEY_NONCE_LENGTH = 32;

// The length of a Ristretto Schnorr key, `SecretKey`, in bytes.
/// <summary>
/// The length of a Ristretto Schnorr key, `SecretKey`, in bytes.
/// </summary>
public const int SECRET_KEY_LENGTH = SECRET_KEY_KEY_LENGTH + SECRET_KEY_NONCE_LENGTH;

// The length of an Ristretto Schnorr `Keypair`, in bytes.
/// <summary>
/// The length of an Ristretto Schnorr `Keypair`, in bytes.
/// </summary>
public const int KEYPAIR_LENGTH = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH;

/// <summary>
/// Length in bytes of our chain codes.
/// </summary>
public const int CHAIN_CODE_LENGTH = 32;
}

/// <summary>
///
/// </summary>
public class KeyExtractResult
{
/// <summary>
/// Derivation phrase
/// </summary>
public string DerivePath { get; set; }

/// <summary>
/// Password use in the derivation
/// </summary>
public string Password { get; set; }

/// <summary>
/// List of derivations
/// </summary>
public IList<DeriveJunction> Path { get; set; }

/// <summary>
/// Mnemonic phrase
/// </summary>
public string Phrase { get; set; }
}

/// <summary>
/// Result of derivation path
/// </summary>
public class KeyExtractPathResult
{
/// <summary>
/// Derivation string splitted
/// </summary>
public IList<string> Parts { get; set; }

/// <summary>
/// List of derivations
/// </summary>
public IList<DeriveJunction> Path { get; set; }
}
}
Loading
Loading