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

Feature/soft hard derivation #11

Merged
merged 18 commits into from
Feb 4, 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: 4 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.

Copyright 2021 Usetech Professional, Unique Network
Copyright 2022 BloGa Tech AG, Ajuna Network
Copyright 2024 SubstrateGaming, Ajuna Network

Preamble

The GNU General Public License is a free, copyleft license for
Expand Down
24 changes: 23 additions & 1 deletion Substrate.NET.Wallet.Test/Data/json_account1.json
Original file line number Diff line number Diff line change
@@ -1 +1,23 @@
{"encoded":"1JUD/F9SaLJsjcAVaZ9lyEiJCjSXffaUCligaJJT4BkAgAAAAQAAAAgAAADCqd5XqeTdl+O4g86Yv+g5TEfIcL7jeolh67IHDR8zmuIyT7gzOSSjQMsamuxdHgucxly2RN2xp1n0ABkODcIe2KKS/nvPCme4CzPp56fJsy2Aul5FyJraK3061+QoGrjWMzFeiibwxrQKDfPHioBjE8myNoDPSk2TkYb9bK+Vmy7AaDt7rFzqEDO2d8dcR/kw20LAQMqMu00UFvdB","encoding":{"content":["pkcs8","sr25519"],"type":["scrypt","xsalsa20-poly1305"],"version":3},"address":"bULpU8nuVEEMrNCYcFUrSk34srsPoszf7c52VHxxCnY6jBFTC","meta":{"genesisHash":"0x35a06bfec2edf0ff4be89a6428ccd9ff5bd0167d618c5a0d4341f9600a458d14","isHardware":false,"name":"SUBSTRATE","tags":[],"whenCreated":1695838313716}}
{
"encoded": "1JUD/F9SaLJsjcAVaZ9lyEiJCjSXffaUCligaJJT4BkAgAAAAQAAAAgAAADCqd5XqeTdl+O4g86Yv+g5TEfIcL7jeolh67IHDR8zmuIyT7gzOSSjQMsamuxdHgucxly2RN2xp1n0ABkODcIe2KKS/nvPCme4CzPp56fJsy2Aul5FyJraK3061+QoGrjWMzFeiibwxrQKDfPHioBjE8myNoDPSk2TkYb9bK+Vmy7AaDt7rFzqEDO2d8dcR/kw20LAQMqMu00UFvdB",
"encoding": {
"content": [
"pkcs8",
"sr25519"
],
"type": [
"scrypt",
"xsalsa20-poly1305"
],
"version": 3
},
"address": "bULpU8nuVEEMrNCYcFUrSk34srsPoszf7c52VHxxCnY6jBFTC",
"meta": {
"genesisHash": "0x35a06bfec2edf0ff4be89a6428ccd9ff5bd0167d618c5a0d4341f9600a458d14",
"isHardware": false,
"name": "SUBSTRATE",
"tags": [
],
"whenCreated": 1695838313716
}
}
107 changes: 83 additions & 24 deletions Substrate.NET.Wallet.Test/KeyringTest.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
using NUnit.Framework;
using System;
using System.IO;
using Substrate.NET.Wallet.Derivation;
using Substrate.NET.Wallet.Keyring;
using Substrate.NetApi;
using static Substrate.NetApi.Mnemonic;
using System;
using System.IO;
using System.Linq;
using Substrate.NetApi.Extensions;

namespace Substrate.NET.Wallet.Test
{
internal class KeyringTest
internal class KeyringTest : MainTests
{
protected string readJsonFromFile(string jsonFile)
{
return File.ReadAllText($"{AppContext.BaseDirectory}/Data/{jsonFile}");
}

private Meta defaultMeta = new Meta()
{
genesisHash = "0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3",
isHardware = false,
name = "SubstrateAccount2",
tags = null
};

[Test]
[TestCase("json_alice.json", "alicealice")]
[TestCase("json_account1.json", "SUBSTRATE")]
Expand All @@ -49,14 +42,14 @@ public void ValidJson_WithValidPassword_ShouldUnlock(string json, string passwor

[Test]
[TestCase("json_account1.json")]
public void ValidJson_WithInvalidPassword_ShouldThrowException(string json)
public void ValidJson_WithInvalidPassword_ShouldReturnFalse(string json)
{
var input = readJsonFromFile(json);

var keyring = new Keyring.Keyring();
var res = keyring.AddFromJson(input);

Assert.Throws<InvalidOperationException>(() => res.Unlock("SS2"));
Assert.That(res.Unlock("SS2"), Is.EqualTo(false));
}

[Test]
Expand All @@ -67,6 +60,7 @@ public void ImportFromValidJson_ThenDuplicateAccount_ShouldHaveSamePrivateKey(st

var keyring = new Keyring.Keyring();
var wallet = keyring.AddFromJson(input);
wallet.PasswordPolicy = passwordLightPolicy;

var walletEncryptionSamePassword = wallet.ToWalletFile("walletName", password);

Expand Down Expand Up @@ -96,14 +90,14 @@ public void GenerateNewAccount_WithPassword_AndExportToJson()
var keyring = new Keyring.Keyring();
var kp = keyring.AddFromMnemonic(Mnemonic.GenerateMnemonic(Mnemonic.MnemonicSize.Words12), new Meta()
{
genesisHash = "0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3",
isHardware = false,
name = "SubstrateAccount",
tags = null
GenesisHash = "0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3",
IsHardware = false,
Name = "SubstrateAccount",
Tags = null
}, NetApi.Model.Types.KeyType.Sr25519);

var walletResult = kp.ToWalletFile("walletName", "testPassword");
Assert.That(walletResult.meta.name, Is.EqualTo("walletName"));
var walletResult = kp.ToWalletFile("walletName", "testPassword1");
Assert.That(walletResult.Meta.Name, Is.EqualTo("walletName"));
var jsonResult = walletResult.ToJson();

Assert.That(jsonResult, Is.Not.Null);
Expand Down Expand Up @@ -149,24 +143,42 @@ public void EncodeAddress()
Assert.That(keyring.EncodeAddress(publicKey), Is.EqualTo("1NthTCKurNHLW52mMa6iA8Gz7UFYW5UnM3yTSpVdGu4Th7h"));
}

[Test]
[TestCase(NetApi.Model.Types.KeyType.Ed25519)]
[TestCase(NetApi.Model.Types.KeyType.Sr25519)]
public void GenerateNewAccount_SignAndVerify(NetApi.Model.Types.KeyType keyType)
{
var keyring = new Keyring.Keyring();

var mnemonic = Mnemonic.GenerateMnemonic(Mnemonic.MnemonicSize.Words12);
var wallet = keyring.AddFromMnemonic(mnemonic, new Meta() { Name = "My account name" }, keyType);

var message = "Hello Polkadot !".ToBytes();
var sign = wallet.Sign(message);

Assert.That(wallet.Verify(sign, message));
}

[Test]
public void WikiExample_Test()
{
// Create a new Keyring, by default ss58 format is 42 (Substrate standard address)
var keyring = new Substrate.NET.Wallet.Keyring.Keyring();
var keyring = new Keyring.Keyring();

// You can specify ss58 address if needed (check SS58 regitry here : https://github.com/paritytech/ss58-registry/blob/main/ss58-registry.json)
keyring.Ss58Format = 0; // Polkadot

// Generate a new mnemonic for a new account
var newMnemonic = Mnemonic.GenerateMnemonic(MnemonicSize.Words12);
var newMnemonic = Mnemonic.GenerateMnemonic(Mnemonic.MnemonicSize.Words12);
Assert.That(newMnemonic.Count(), Is.EqualTo(12));

// Use an existing mnemonic
var existingMnemonicAccount = "entire material egg meadow latin bargain dutch coral blood melt acoustic thought";

// Import an account from mnemonic automatically unlock all feature
var firstWallet = keyring.AddFromMnemonic(existingMnemonicAccount, new Meta() { name = "My account name"}, NetApi.Model.Types.KeyType.Ed25519);
var firstWallet = keyring.AddFromMnemonic(existingMnemonicAccount, new Meta() { Name = "My account name"}, NetApi.Model.Types.KeyType.Sr25519);
firstWallet.PasswordPolicy = passwordLightPolicy;

// firstPair.IsLocked => false
Assert.That(firstWallet.IsLocked, Is.False);
Assert.That(firstWallet.IsStored, Is.False);
Expand All @@ -175,6 +187,7 @@ public void WikiExample_Test()
var json = firstWallet.ToJson("myWalletName", "myPassword");
// Import an account from a json file
var secondWallet = keyring.AddFromJson(json);
secondWallet.PasswordPolicy = passwordLightPolicy;

Assert.That(secondWallet.IsLocked, Is.True);
// You need to unlock the account with the associated password
Expand All @@ -187,5 +200,51 @@ public void WikiExample_Test()
var isVerify = firstWallet.Verify(signature, message);
Assert.That(isVerify, Is.True);
}

[Test]
public void KeyPairFromSeed()
{
var seed = Utils.HexToByteArray("fac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e");
var expected = Utils.HexToByteArray("46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a");

var keyPair = Keyring.Keyring.KeyPairFromSeed(NetApi.Model.Types.KeyType.Sr25519, seed);
Assert.That(keyPair.PublicKey.Length, Is.EqualTo(Keys.PUBLIC_KEY_LENGTH));
Assert.That(keyPair.SecretKey.Length, Is.EqualTo(Keys.SECRET_KEY_LENGTH));

Assert.That(keyPair.PublicKey, Is.EqualTo(expected));
}

[Test]
[TestCase("bottom drive obey lake curtain smoke basket hold race lonely fit walk")]
public void MnemonicPhraseValid_ShouldSuceed(string mnemonic)
{
Assert.That(Keyring.Keyring.IsMnemonicPhraseValid(mnemonic), Is.True);

var randomMnemonic_12 = Mnemonic.GenerateMnemonic(Mnemonic.MnemonicSize.Words12);
Assert.That(Keyring.Keyring.IsMnemonicPhraseValid(randomMnemonic_12), Is.True);

var randomMnemonic_15 = Mnemonic.GenerateMnemonic(Mnemonic.MnemonicSize.Words15);
Assert.That(Keyring.Keyring.IsMnemonicPhraseValid(randomMnemonic_15), Is.True);

var randomMnemonic_18 = Mnemonic.GenerateMnemonic(Mnemonic.MnemonicSize.Words18);
Assert.That(Keyring.Keyring.IsMnemonicPhraseValid(randomMnemonic_18), Is.True);

var randomMnemonic_21 = Mnemonic.GenerateMnemonic(Mnemonic.MnemonicSize.Words21);
Assert.That(Keyring.Keyring.IsMnemonicPhraseValid(randomMnemonic_21), Is.True);

var randomMnemonic_24 = Mnemonic.GenerateMnemonic(Mnemonic.MnemonicSize.Words24);
Assert.That(Keyring.Keyring.IsMnemonicPhraseValid(randomMnemonic_24), Is.True);
}

[Test]
[TestCase("bobo drive obey lake curtain smoke basket hold race lonely fit walk")]
[TestCase("b d o l c s b h r l f w")]
[TestCase("string mnemonic obey lake curtain smoke basket hold race lonely fit walk")]
[TestCase("Hey i'm not good")]
[TestCase("")]
public void MnemonicPhraseInvalid_ShouldFail(string mnemonic)
{
Assert.That(Keyring.Keyring.IsMnemonicPhraseValid(mnemonic), Is.False);
}
}
}
}
67 changes: 67 additions & 0 deletions Substrate.NET.Wallet.Test/Keyrings/DerivationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using NUnit.Framework;
using Substrate.NetApi;
using Substrate.NetApi.Model.Types;

namespace Substrate.NET.Wallet.Test.Keyrings
{
public class DerivationTests : MainTests
{
[Test]
public void AllowDerivationOnAlice()
{
var alice = pairDefs["Alice"].GetWallet();

var stash = alice.Derive("//stash");
var soft = alice.Derive("//funding/0");

Assert.That(stash.Account.Bytes, Is.EquivalentTo(Utils.HexToByteArray(pairDefs["Alice stash"].PublickKey)));
Assert.That(soft.Address, Is.EqualTo("5ECQNn7UueWHPFda5qUi4fTmTtyCnPvGnuoyVVSj5CboJh9J"));
}

[Test]
public void AllowDerivationOnBob()
{
var bob = pairDefs["Bob"].GetWallet();

var stash = bob.Derive("//stash");

Assert.That(stash.Account.Bytes, Is.EquivalentTo(Utils.HexToByteArray(pairDefs["Bob stash"].PublickKey)));
}

[Test]
[TestCase(42, "//9007199254740991", "5CDsyNZyqxLpHnTvknr68anUcYoBFjZbFKiEJJf4prB75Uog")]
[TestCase(42, "//Alice", "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY")]
[TestCase(42, "//900719925474099999", "5GHj2D7RG2m2DXYwGSDpXwuuxn53G987i7p2EQVDqP4NYu4q")]
[TestCase(252, "//Alice", "xw8P6urbSAronL3zZFB7dg8p7LLSgKCUFDUgjohnf1iP434ic")]
public void CreateWithParams(short ss58, string derivation, string address)
{
var keyring = new Keyring.Keyring();
keyring.Ss58Format = ss58;

var res = keyring.AddFromUri(derivation, defaultMeta, KeyType.Sr25519);

Assert.That(res.Address, Is.EqualTo(address));
}

/// <summary>
/// Based on SubKey (https://docs.substrate.io/reference/command-line-tools/subkey/)
/// </summary>
[Test]
[TestCase("//Alice", "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", "0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a")]
[TestCase("//Alice//stash", "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY", "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f", "0x3c881bc4d45926680c64a7f9315eeda3dd287f8d598f3653d7c107799c5422b3")]
[TestCase("//Bob", "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", "0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", "0x398f0c28f98885e046333d4a41c19cee4c37368a9832c6502f6cfd182e2aef89")]
[TestCase("//Bob//stash", "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc", "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", "0x1a7d114100653850c65edecda8a9b2b4dd65d900edef8e70b1a6ecdcda967056")]
[TestCase("//Charlie", "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y", "0x90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22", "0xbc1ede780f784bb6991a585e4f6e61522c14e1cae6ad0895fb57b9a205a8f938")]
[TestCase("//Dave", "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy", "0x306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20", "0x868020ae0687dda7d57565093a69090211449845a7e11453612800b663307246")]
[TestCase("//Ferdie", "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL", "0x1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c", "0x42438b7883391c05512a938e36c2df0131e088b3756d6aa7a755fbff19d2f842")]
[TestCase("//Eve", "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw", "0xe659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e", "0x786ad0e2df456fe43dd1f91ebca22e235bc162e0bb8d53c633e8c85b2af68b7a")]
public void SubKeysScenario_ShouldSuceed(string uri, string expectedAddress, string expectedPublicKey, string expectedSeed)
{
var keyring = new Keyring.Keyring();
var res = keyring.AddFromUri(uri, defaultMeta, KeyType.Sr25519);

Assert.That(res.Account.Bytes, Is.EquivalentTo(Utils.HexToByteArray(expectedPublicKey)));
Assert.That(res.Address, Is.EqualTo(expectedAddress));
}
}
}
20 changes: 7 additions & 13 deletions Substrate.NET.Wallet.Test/Keyrings/KeypairEd25519Tests.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
using Chaos.NaCl;
using NUnit.Framework;
using Substrate.NET.Wallet.Extensions;
using Substrate.NET.Wallet.Keyring;
using NUnit.Framework;
using Substrate.NetApi;
using Substrate.NetApi.Extensions;
using Substrate.NetApi.Model.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Substrate.NET.Wallet.Test.Keyrings
{
Expand All @@ -25,7 +18,7 @@ internal class KeypairEd25519Tests
Utils.HexToByteArray("0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")
);

Substrate.NET.Wallet.Keyring.Keyring keyring { get; set; }
private Substrate.NET.Wallet.Keyring.Keyring keyring { get; set; }

[SetUp]
public void Setup()
Expand All @@ -47,11 +40,12 @@ public void AddPairTwo()
}

[Test]
public void CreateEd25519_WithMnemonic()
[TestCase("seed sock milk update focus rotate barely fade car face mechanic mercy", "5DkQP32jP4DVJLWWBRBoZF2tpWjqFrcrTBo6H5NcSk7MxKCC")]
public void CreateEd25519_WithMnemonic(string mnemonic, string publicKey)
{
var kp = keyring.AddFromUri(
"seed sock milk update focus rotate barely fade car face mechanic mercy", null, NetApi.Model.Types.KeyType.Ed25519);
Assert.That(kp.Address, Is.EqualTo("5DkQP32jP4DVJLWWBRBoZF2tpWjqFrcrTBo6H5NcSk7MxKCC"));
mnemonic, null, NetApi.Model.Types.KeyType.Ed25519);
Assert.That(kp.Address, Is.EqualTo(publicKey));
}

[Test]
Expand Down Expand Up @@ -92,4 +86,4 @@ public void GetByPublicKey()
Assert.That(keyring.GetWallet(SecondAccount.publicKey), Is.Null);
}
}
}
}
Loading
Loading