Skip to content

Commit f459b0c

Browse files
authored
cli: testsig subcommand and parse-error handling (#622)
* cli: add testsig command and fix parse-error exit - Add testsig subcommand: accepts --private-key (seed), --epoch, --slot; signs a 32-byte message (slot in first 8 bytes) and dumps SSZ signature hex. - Use arena allocator for simargs.parse so failed parse does not leak. - On MissingSubCommand, exit without duplicate error handler output. * testsig: print public_key with signature for verification * testsig: add --key-path to load keypair from *_pk.ssz and *_sk.ssz
1 parent 7746d19 commit f459b0c

File tree

1 file changed

+64
-11
lines changed

1 file changed

+64
-11
lines changed

pkgs/cli/src/main.zig

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,14 @@ const BeamCmd = struct {
114114
/// Test-only CLI: sign a fixed message for (epoch, slot) and dump signature hex.
115115
const TestsigCmd = struct {
116116
help: bool = false,
117-
@"private-key": []const u8,
117+
@"private-key": ?[]const u8 = null,
118+
@"key-path": ?[]const u8 = null,
118119
epoch: u64 = 0,
119120
slot: u64 = 0,
120121

121122
pub const __messages__ = .{
122-
.@"private-key" = "Seed phrase for key generation (testing only)",
123+
.@"private-key" = "Seed phrase for key generation (testing only); use with --key-path for SSZ key files",
124+
.@"key-path" = "Path to validator_X_pk.ssz; loads keypair from pk.ssz and same-dir validator_X_sk.ssz",
123125
.epoch = "Epoch number for signing",
124126
.slot = "Slot number (encoded in signed message)",
125127
.help = "Show help for testsig",
@@ -770,17 +772,68 @@ fn mainInner() !void {
770772
};
771773
},
772774
.testsig => |cmd| {
773-
const num_active_epochs = @max(cmd.epoch + 1, 1);
774-
var keypair = xmss.KeyPair.generate(
775-
allocator,
776-
cmd.@"private-key",
777-
0,
778-
num_active_epochs,
779-
) catch |err| {
780-
ErrorHandler.logErrorWithOperation(err, "generate key from seed");
775+
var keypair: xmss.KeyPair = undefined;
776+
777+
if (cmd.@"key-path") |key_path| {
778+
if (!std.mem.endsWith(u8, key_path, "_pk.ssz")) {
779+
std.debug.print("key-path must point to a file named *_pk.ssz (e.g. validator_0_pk.ssz)\n", .{});
780+
return error.InvalidKeyPath;
781+
}
782+
const sk_path = std.fmt.allocPrint(allocator, "{s}_sk.ssz", .{key_path[0 .. key_path.len - "_pk.ssz".len]}) catch |err| {
783+
ErrorHandler.logErrorWithOperation(err, "build private key path");
784+
return err;
785+
};
786+
defer allocator.free(sk_path);
787+
788+
const pk_file = std.fs.cwd().openFile(key_path, .{}) catch |err| {
789+
ErrorHandler.logErrorWithDetails(err, "open public key file", .{ .path = key_path });
790+
return err;
791+
};
792+
defer pk_file.close();
793+
const pk_bytes = pk_file.readToEndAlloc(allocator, 256) catch |err| {
794+
ErrorHandler.logErrorWithOperation(err, "read public key file");
795+
return err;
796+
};
797+
defer allocator.free(pk_bytes);
798+
799+
const sk_file = std.fs.cwd().openFile(sk_path, .{}) catch |err| {
800+
ErrorHandler.logErrorWithDetails(err, "open private key file", .{ .path = sk_path });
801+
return err;
802+
};
803+
defer sk_file.close();
804+
const sk_bytes = sk_file.readToEndAlloc(allocator, 16 * 1024 * 1024) catch |err| {
805+
ErrorHandler.logErrorWithOperation(err, "read private key file");
806+
return err;
807+
};
808+
defer allocator.free(sk_bytes);
809+
810+
keypair = xmss.KeyPair.fromSsz(allocator, sk_bytes, pk_bytes) catch |err| {
811+
ErrorHandler.logErrorWithOperation(err, "load keypair from SSZ");
812+
return err;
813+
};
814+
} else if (cmd.@"private-key") |seed| {
815+
const num_active_epochs = @max(cmd.epoch + 1, 1);
816+
keypair = xmss.KeyPair.generate(allocator, seed, 0, num_active_epochs) catch |err| {
817+
ErrorHandler.logErrorWithOperation(err, "generate key from seed");
818+
return err;
819+
};
820+
} else {
821+
std.debug.print("testsig requires either --private-key (seed) or --key-path (path to *_pk.ssz)\n", .{});
822+
return error.MissingTestsigKey;
823+
}
824+
defer keypair.deinit();
825+
826+
var pk_buf: [64]u8 = undefined;
827+
const pk_len = keypair.pubkeyToBytes(&pk_buf) catch |err| {
828+
ErrorHandler.logErrorWithOperation(err, "serialize public key");
781829
return err;
782830
};
783-
defer keypair.deinit();
831+
const pk_hex = std.fmt.allocPrint(allocator, "0x{x}", .{pk_buf[0..pk_len]}) catch |err| {
832+
ErrorHandler.logErrorWithOperation(err, "format public key hex");
833+
return err;
834+
};
835+
defer allocator.free(pk_hex);
836+
std.debug.print("public_key: {s}\n", .{pk_hex});
784837

785838
var message: [32]u8 = [_]u8{0} ** 32;
786839
std.mem.writeInt(u64, message[0..8], cmd.slot, .little);

0 commit comments

Comments
 (0)