Skip to content

Commit 17e325a

Browse files
committed
feat: more output formats
1 parent 305d4f3 commit 17e325a

13 files changed

Lines changed: 355 additions & 139 deletions

.editorconfig

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
# Remove the line below if you want to inherit .editorconfig settings from higher directories
22
root = true
33

4+
[*]
5+
6+
# New line preferences
7+
end_of_line = lf
8+
49
# C# files
510
[*.cs]
611

@@ -12,8 +17,7 @@ indent_style = space
1217
tab_width = 4
1318

1419
# New line preferences
15-
end_of_line = crlf
16-
insert_final_newline = false
20+
insert_final_newline = true
1721

1822
#### .NET Code Actions ####
1923

.gitattributes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
###############################################################################
22
# Set default behavior to automatically normalize line endings.
33
###############################################################################
4-
* text=auto
4+
* text=auto eol=lf
55

66
###############################################################################
77
# Set default behavior for command prompt diff.

README.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,27 +34,41 @@ dotnet publish -c Release -r linux-x64 --self-contained true -o artifacts/publis
3434
### Derive for a supported chain
3535

3636
```bash
37-
keyutils derive-chain \
37+
keyutils derive solana \
3838
--mnemonic-file mnemonic.txt \
39-
--type Evm \
39+
--format base58 \
4040
--output key.txt
4141
```
4242

43-
Supported chain types: `Evm`, `Solana`, `Cosmos`
43+
Supported chain commands: `solana`, `evm`, `cosmos`
4444

4545
`--account-index` is optional and defaults to `0`.
4646

47+
`derive solana` defaults to `--format base58`.
48+
49+
`derive evm` and `derive cosmos` only support `--format hex`.
50+
4751
### Derive with custom path
4852

4953
```bash
50-
keyutils derive-path \
54+
keyutils derive-path ed25519 \
5155
--mnemonic-file mnemonic.txt \
52-
--path "m/44'/60'/0'/0/0" \
53-
--curve Secp256k1 \
56+
--path "m/44'/501'/0'/0'/0'" \
57+
--format hex \
5458
--output key.txt
5559
```
5660

57-
Supported curves: `Secp256k1`, `Ed25519`
61+
Supported path subcommands: `ed25519`, `secp256k1`
62+
63+
`derive-path ed25519` supports `hex`, `base58`, and `json`.
64+
65+
`derive-path secp256k1` only supports `hex`.
66+
67+
Format meanings:
68+
69+
- `hex`: raw 32-byte private key as lowercase hex
70+
- `base58`: Ed25519 64-byte keypair encoded for Solana/Rust `Keypair::from_base58_string(...)`
71+
- `json`: Ed25519 64-byte keypair as a JSON byte array
5872

5973
## How It Works
6074

src/Cli/CliRootCommand.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
using KeyUtils.Cli.Derive;
1+
using KeyUtils.Cli.Derive;
22
using System.CommandLine;
33

44
namespace KeyUtils.Cli;
55

66
public class CliRootCommand : RootCommand
77
{
8-
public CliRootCommand(DeriveChainCommand deriveChainCommand, DerivePathCommand derivePathCommand) : base()
8+
public CliRootCommand(DeriveCommand deriveCommand, DerivePathCommand derivePathCommand) : base()
99
{
10-
Add(deriveChainCommand);
10+
Add(deriveCommand);
1111
Add(derivePathCommand);
1212
}
1313
}

src/Cli/Derive/ChainType.cs

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/Cli/Derive/CurveType.cs

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/Cli/Derive/DeriveChainCommand.cs

Lines changed: 0 additions & 50 deletions
This file was deleted.

src/Cli/Derive/DeriveCommand.cs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using Keysmith.Net.BIP;
2+
using Keysmith.Net.EC;
3+
using Keysmith.Net.ED;
4+
using System.CommandLine;
5+
6+
namespace KeyUtils.Cli.Derive;
7+
8+
public sealed class DeriveCommand : Command
9+
{
10+
public DeriveCommand() : base("derive", "derive a key for a supported chain")
11+
{
12+
Add(new DeriveChainLeafCommand(
13+
"solana",
14+
"derive a Solana key",
15+
BIP44.Solana,
16+
ED25519.Instance,
17+
[OutputFormat.Hex, OutputFormat.Base58, OutputFormat.Json],
18+
OutputFormat.Base58));
19+
20+
Add(new DeriveChainLeafCommand(
21+
"evm",
22+
"derive an EVM key",
23+
accountIndex => BIP44.Ethereum((uint) accountIndex),
24+
Secp256k1.Instance,
25+
[OutputFormat.Hex],
26+
OutputFormat.Hex));
27+
28+
Add(new DeriveChainLeafCommand(
29+
"cosmos",
30+
"derive a Cosmos key",
31+
BIP44.Cosmos,
32+
Secp256k1.Instance,
33+
[OutputFormat.Hex],
34+
OutputFormat.Hex));
35+
}
36+
37+
private sealed class DeriveChainLeafCommand : Command
38+
{
39+
private readonly Func<int, string> _pathFactory;
40+
private readonly ECCurve _curve;
41+
private readonly OutputFormat[] _supportedFormats;
42+
43+
private readonly Option<FileInfo> _mnemonicFileOption = new("--mnemonic-file", "-i")
44+
{
45+
Description = "Path to the file containing the mnemonic",
46+
Required = true
47+
};
48+
49+
private readonly Option<int> _accountIndexOption = new("--account-index")
50+
{
51+
Description = "The account index to derive",
52+
DefaultValueFactory = _ => 0
53+
};
54+
55+
private readonly Option<string> _formatOption;
56+
57+
private readonly Option<FileInfo?> _outputOption = new("--output", "-o")
58+
{
59+
Description = "Path to the file where the derived key will be saved"
60+
};
61+
62+
public DeriveChainLeafCommand(
63+
string name,
64+
string description,
65+
Func<int, string> pathFactory,
66+
ECCurve curve,
67+
OutputFormat[] supportedFormats,
68+
OutputFormat defaultFormat) : base(name, description)
69+
{
70+
_pathFactory = pathFactory;
71+
_curve = curve;
72+
_supportedFormats = supportedFormats;
73+
_formatOption = new Option<string>("--format", "-f")
74+
{
75+
Description = $"Output format ({OutputFormatExtensions.Describe(supportedFormats)}). Defaults to {defaultFormat.ToCliValue()}",
76+
DefaultValueFactory = _ => defaultFormat.ToCliValue()
77+
};
78+
_formatOption.AcceptOnlyFromAmong([.. supportedFormats.Select(format => format.ToCliValue())]);
79+
80+
Add(_mnemonicFileOption);
81+
Add(_accountIndexOption);
82+
Add(_formatOption);
83+
Add(_outputOption);
84+
85+
SetAction(Handle);
86+
}
87+
88+
private Task<int> Handle(ParseResult parseResult)
89+
{
90+
var mnemonicFile = parseResult.GetValue(_mnemonicFileOption)!;
91+
int accountIndex = parseResult.GetValue(_accountIndexOption);
92+
string formatText = parseResult.GetValue(_formatOption)!;
93+
var outputFile = parseResult.GetValue(_outputOption);
94+
95+
if(!OutputFormatExtensions.TryParse(formatText, out var format) || !_supportedFormats.Contains(format))
96+
{
97+
Console.WriteLine($"Error: Unsupported format '{formatText}'. Supported formats: {OutputFormatExtensions.Describe(_supportedFormats)}");
98+
return Task.FromResult(1);
99+
}
100+
101+
return DeriveKeySupport.DeriveAsync(mnemonicFile, _pathFactory(accountIndex), _curve, format, outputFile);
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)