Skip to content

Commit 7746d19

Browse files
authored
cli: add testsig command and fix parse-error exit (#621)
- 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.
1 parent fd63f2c commit 7746d19

File tree

1 file changed

+63
-2
lines changed

1 file changed

+63
-2
lines changed

pkgs/cli/src/main.zig

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub const std_options: std.Options = .{
1414
};
1515

1616
const types = @import("@zeam/types");
17+
const xmss = @import("@zeam/xmss");
1718
const node_lib = @import("@zeam/node");
1819
const Clock = node_lib.Clock;
1920
const state_proving_manager = @import("@zeam/state-proving-manager");
@@ -110,6 +111,21 @@ const BeamCmd = struct {
110111
}
111112
};
112113

114+
/// Test-only CLI: sign a fixed message for (epoch, slot) and dump signature hex.
115+
const TestsigCmd = struct {
116+
help: bool = false,
117+
@"private-key": []const u8,
118+
epoch: u64 = 0,
119+
slot: u64 = 0,
120+
121+
pub const __messages__ = .{
122+
.@"private-key" = "Seed phrase for key generation (testing only)",
123+
.epoch = "Epoch number for signing",
124+
.slot = "Slot number (encoded in signed message)",
125+
.help = "Show help for testsig",
126+
};
127+
};
128+
113129
const ZeamArgs = struct {
114130
genesis: u64 = 1234,
115131
log_filename: []const u8 = "consensus", // Default logger filename
@@ -164,13 +180,15 @@ const ZeamArgs = struct {
164180
},
165181
},
166182
node: NodeCommand,
183+
testsig: TestsigCmd,
167184

168185
pub const __messages__ = .{
169186
.clock = "Run the clock service for slot timing",
170187
.beam = "Run a full Beam node",
171188
.prove = "Generate and verify ZK proofs for state transitions on a mock chain",
172189
.prometheus = "Prometheus configuration management",
173190
.node = "Run a lean node",
191+
.testsig = "Dump a signature for (private-key, epoch, slot); testing only",
174192
};
175193
},
176194

@@ -203,6 +221,7 @@ const ZeamArgs = struct {
203221
.genconfig => |genconfig| try writer.print("prometheus.genconfig(api_port={d}, filename=\"{s}\")", .{ genconfig.@"api-port", genconfig.filename }),
204222
},
205223
.node => |cmd| try writer.print("node(node-id=\"{s}\", custom_genesis=\"{s}\", validator_config=\"{s}\", data-dir=\"{s}\", api_port={d})", .{ cmd.@"node-id", cmd.custom_genesis, cmd.validator_config, cmd.@"data-dir", cmd.@"api-port" }),
224+
.testsig => |cmd| try writer.print("testsig(epoch={d}, slot={d})", .{ cmd.epoch, cmd.slot }),
206225
}
207226
try writer.writeAll(")");
208227
}
@@ -213,6 +232,9 @@ const ErrorHandler = error_handler.ErrorHandler;
213232

214233
pub fn main() void {
215234
mainInner() catch |err| {
235+
if (err == error.MissingSubCommand) {
236+
std.process.exit(1);
237+
}
216238
ErrorHandler.handleApplicationError(err);
217239
std.process.exit(1);
218240
};
@@ -232,10 +254,12 @@ fn mainInner() !void {
232254
const app_description = "Zeam - Zig implementation of Beam Chain, a ZK-based Ethereum Consensus Protocol";
233255
const app_version = build_options.version;
234256

235-
const opts = simargs.parse(allocator, ZeamArgs, app_description, app_version) catch |err| {
257+
var parse_arena = std.heap.ArenaAllocator.init(allocator);
258+
defer parse_arena.deinit();
259+
260+
const opts = simargs.parse(parse_arena.allocator(), ZeamArgs, app_description, app_version) catch |err| {
236261
std.debug.print("Failed to parse command-line arguments: {s}\n", .{@errorName(err)});
237262
std.debug.print("Run 'zeam --help' for usage information.\n", .{});
238-
ErrorHandler.logErrorWithOperation(err, "parse command-line arguments");
239263
return err;
240264
};
241265
defer opts.deinit();
@@ -745,6 +769,43 @@ fn mainInner() !void {
745769
return err;
746770
};
747771
},
772+
.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");
781+
return err;
782+
};
783+
defer keypair.deinit();
784+
785+
var message: [32]u8 = [_]u8{0} ** 32;
786+
std.mem.writeInt(u64, message[0..8], cmd.slot, .little);
787+
788+
const epoch_u32: u32 = @intCast(cmd.epoch);
789+
var signature = keypair.sign(&message, epoch_u32) catch |err| {
790+
ErrorHandler.logErrorWithOperation(err, "sign message");
791+
return err;
792+
};
793+
defer signature.deinit();
794+
795+
var sig_buf: [types.SIGSIZE]u8 = undefined;
796+
const bytes_written = signature.toBytes(&sig_buf) catch |err| {
797+
ErrorHandler.logErrorWithOperation(err, "serialize signature");
798+
return err;
799+
};
800+
801+
const sig_slice = sig_buf[0..bytes_written];
802+
const hex_str = std.fmt.allocPrint(allocator, "0x{x}", .{sig_slice}) catch |err| {
803+
ErrorHandler.logErrorWithOperation(err, "format signature hex");
804+
return err;
805+
};
806+
defer allocator.free(hex_str);
807+
std.debug.print("signature: {s}\n", .{hex_str});
808+
},
748809
}
749810
}
750811

0 commit comments

Comments
 (0)