@@ -114,12 +114,14 @@ const BeamCmd = struct {
114114/// Test-only CLI: sign a fixed message for (epoch, slot) and dump signature hex.
115115const 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