Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:
- name: Install Dotnet coverage
shell: powershell
run: |
dotnet tool install --global dotnet-coverage
dotnet tool install --global dotnet-coverage --version 17.7.0

- name: Build and analyze
env:
Expand Down
1 change: 1 addition & 0 deletions Substrate.NET.Wallet.Test/Data/json_account2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"encoded":"yGt35xsmFfYx3purRt03Za/GybpW20Vdj9DxpEDh5PIAAAIAAQAAAAgAAAA6UwR3S011ZM8n3ny9YH0KlqdPS1WGzxwKd2gIuIHt+G+FPHYbEP71eDVOhWiWcI51dgsyj3Jf2IKS3e1bYiX9u8WkkHU6N9JAUBQUSj0EpzynwfhvLM1ADcmUmsccW5Cx4gTDVUsPCJdnEJJeBO4W7bv1H3nbIu7xu0EUsWtUkB/P1sR5m/+Ai0sPUGwWMX8rZcd+KIezhC7+GKmn","encoding":{"content":["pkcs8","sr25519"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"address":"5HMZkTx4Zmbm7agoTZzJqAY7zWTcto8KMwyXd3NxkPZ5jniV","meta":{"genesisHash":null,"name":"Substrate Wallet test (Ahojky123)","whenCreated":1780500543634}}
1 change: 1 addition & 0 deletions Substrate.NET.Wallet.Test/Data/json_long_password1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"encoded":"jAzzplHmnIaz5AYrkwPjKQAtOylvuamkph8JZbu8RDYAAAIAAQAAAAgAAAB7giGHJUBmS0teXBcmv5lG8ieDpb9prUyuCmpGC8IowZR+5XykyR5Yllt9nyuZzDCW0Hb7WJiZFk7s/OtyUl6AD3lw9AIw4czu1oaLEpHUY03Aridj+PdCZJstYzUlq7lyAfI2DvE3MZeoKDFVDyAVUZbVOVAwIjrxg8lxRI4VIICyW0xRVbNYE2wOdUarammFelR9HN/S85mkxy+x","encoding":{"content":["pkcs8","sr25519"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"address":"5CaEjsKAMXMue2b5vLyxRyp7weg2KrMb3eHFJgPNXy2xcwLf","meta":{"genesisHash":null,"name":"Substrate Wallet test (Ahojky123Ahojky123Ahojky123Ahojky123#!()-_)","whenCreated":1780501254656}}
1 change: 1 addition & 0 deletions Substrate.NET.Wallet.Test/Data/json_short_password1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"encoded":"ocUuZ7m+/MvzmSxkzP2Y+OPLkWe/bpsBcozYEqc+e7wAAAIAAQAAAAgAAACBn5jX3H1TpJyGGGRZJoc1QQ0AjJhDPgutkXkPRelwiqRdiIKMVHtOW4QBTCqAa5ExUKxEDEOLyrLljo2QkVE9PofGcTAo0cUubhdl5lL7NtquVkPmFP7X6D/nMNbsnJWvU9+TryiuTOQ8nYeyogaT0zjW/kaRYvuRgbYzKhCqwfjBDi4Fn2kVHZdVXy6wGvi3QUkuDXMdUHw8aeSQ","encoding":{"content":["pkcs8","sr25519"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"address":"5DoRqq9WP7fLW9FgbR135y76FZYCmFD5YMpSpuSvaW8RV8YH","meta":{"genesisHash":null,"name":"Substrate Wallet test (123456)","whenCreated":1780497337968}}
3 changes: 3 additions & 0 deletions Substrate.NET.Wallet.Test/KeyringTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ protected string readJsonFromFile(string jsonFile)
[TestCase("json_alice.json", "alicealice")]
[TestCase("json_account1.json", "SUBSTRATE")]
[TestCase("json_generated_1.json", "AccountTest4")]
[TestCase("json_short_password1.json", "123456")]
[TestCase("json_account2.json", "Ahojky123")]
[TestCase("json_long_password1.json", "Ahojky123Ahojky123Ahojky123Ahojky123#!()-_")]
public void ValidJson_WithValidPassword_ShouldUnlock(string json, string password)
{
var input = readJsonFromFile(json);
Expand Down
9 changes: 9 additions & 0 deletions Substrate.NET.Wallet.Test/Substrate.NET.Wallet.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@
<None Update="Data\json_generated_1.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Data\json_account2.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Data\json_long_password1.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Data\json_short_password1.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="dev_wallet1.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
Expand Down
78 changes: 70 additions & 8 deletions Substrate.NET.Wallet/Keyring/Scrypt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ namespace Substrate.NET.Wallet.Keyring
/// </summary>
public static class Scrypt
{
private const int ScryptHeaderLength = 44;
private const int ScryptSaltLength = 32;
Comment on lines +15 to +16
private const uint MaxIterationCount = 1 << 18;
private const uint MaxThreadCount = 16;
private const uint MaxBlockSize = 32;
private const long MaxMemoryBytes = 256L * 1024 * 1024;

/// <summary>
/// https://github.com/polkadot-js/common/blob/master/packages/util-crypto/src/scrypt/fromU8a.ts
/// </summary>
Expand All @@ -20,19 +27,71 @@ public static class Scrypt
/// <exception cref="InvalidOperationException"></exception>
public static ScryptResult FromBytes(byte[] data)
{
var salt = data.SubArray(0, 32);
var N = new BigInteger(data.SubArray(32 + 0, 32 + 4));
var p = new BigInteger(data.SubArray(32 + 4, 32 + 8));
var r = new BigInteger(data.SubArray(32 + 8, 32 + 12));
if (data == null || data.Length < ScryptHeaderLength)
{
throw new ArgumentException($"Data must be at least {ScryptHeaderLength} bytes long.", nameof(data));
}
Comment on lines +30 to +33

var salt = data.SubArray(0, ScryptSaltLength);
var iterationCount = ReadUInt32LittleEndian(data, 32);
var threadCount = ReadUInt32LittleEndian(data, 36);
var blockSize = ReadUInt32LittleEndian(data, 40);

ValidateScryptParams(iterationCount, threadCount, blockSize);

return new ScryptResult(new ScryptParam(iterationCount, threadCount, blockSize), salt);
}

private static uint ReadUInt32LittleEndian(byte[] data, int startIndex)
{
var bytes = data.SubArray(startIndex, startIndex + sizeof(uint));

if (!BitConverter.IsLittleEndian)
Array.Reverse(bytes);

return BitConverter.ToUInt32(bytes, 0);
}
Comment on lines +45 to +53

internal static byte[] ToUInt32LittleEndian(BigInteger value)
{
if (value < uint.MinValue || value > uint.MaxValue)
{
throw new InvalidOperationException("Scrypt parameter is out of range.");
}

var bytes = BitConverter.GetBytes((uint)value);

if (!BitConverter.IsLittleEndian)
Array.Reverse(bytes);

return bytes;
}

private static void ValidateScryptParams(uint iterationCount, uint threadCount, uint blockSize)
{
if (!IsPowerOfTwo(iterationCount))
{
throw new InvalidOperationException("Invalid Scrypt iteration count.");
}

if (threadCount == 0 || blockSize == 0)
{
throw new InvalidOperationException("Invalid Scrypt params.");
}

if (N != ScryptParam.Default.IterationCount || p != ScryptParam.Default.ThreadCount || r != ScryptParam.Default.BlockSize)
if (iterationCount > MaxIterationCount || threadCount > MaxThreadCount || blockSize > MaxBlockSize)
{
throw new InvalidOperationException("Invalid Scrypt params");
throw new InvalidOperationException("Scrypt params exceed supported limits.");
}

return new ScryptResult(new ScryptParam(N, p, r), salt);
if (128L * iterationCount * blockSize > MaxMemoryBytes)
{
throw new InvalidOperationException("Scrypt params require too much memory.");
}
}
Comment on lines +70 to 91

private static bool IsPowerOfTwo(uint value) => value >= 2 && (value & (value - 1)) == 0;

/// <summary>
///
/// </summary>
Expand Down Expand Up @@ -160,6 +219,9 @@ public ScryptParam(BigInteger iterationCount, BigInteger threadCount, BigInteger
/// To bytes
/// </summary>
/// <returns></returns>
public byte[] ToBytes() => new byte[] { 0, 128, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0 };
public byte[] ToBytes() => Scrypt.ToUInt32LittleEndian(IterationCount)
.Concat(Scrypt.ToUInt32LittleEndian(ThreadCount))
.Concat(Scrypt.ToUInt32LittleEndian(BlockSize))
.ToArray();
Comment on lines +222 to +225
}
}
Loading