11use std:: fs:: { self , File } ;
2- use std:: io:: { self , Read } ;
2+ use std:: io:: { self , Read , Write } ;
33use std:: path:: Path ;
44use std:: process;
55
@@ -51,6 +51,60 @@ fn main() {
5151 }
5252 }
5353 }
54+ // hemera encode <file> [-o output] — encode to verified stream
55+ Some ( "encode" ) => {
56+ let rest: Vec < & str > = args[ 1 ..] . iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
57+ match rest. as_slice ( ) {
58+ [ input] => process:: exit ( cmd_encode ( input, None ) ) ,
59+ [ input, "-o" , output] => process:: exit ( cmd_encode ( input, Some ( output) ) ) ,
60+ _ => {
61+ eprintln ! ( "hemera: encode requires <file> [-o output]" ) ;
62+ process:: exit ( 1 ) ;
63+ }
64+ }
65+ }
66+ // hemera decode <file> <hash> [-o output] — decode and verify stream
67+ Some ( "decode" ) => {
68+ let rest: Vec < & str > = args[ 1 ..] . iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
69+ match rest. as_slice ( ) {
70+ [ input, hash] => process:: exit ( cmd_decode ( input, hash, None ) ) ,
71+ [ input, hash, "-o" , output] => process:: exit ( cmd_decode ( input, hash, Some ( output) ) ) ,
72+ _ => {
73+ eprintln ! ( "hemera: decode requires <file> <hash> [-o output]" ) ;
74+ process:: exit ( 1 ) ;
75+ }
76+ }
77+ }
78+ // hemera outboard <file> [-o output] — compute outboard hash tree
79+ Some ( "outboard" ) => {
80+ let rest: Vec < & str > = args[ 1 ..] . iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
81+ match rest. as_slice ( ) {
82+ [ input] => process:: exit ( cmd_outboard ( input, None ) ) ,
83+ [ input, "-o" , output] => process:: exit ( cmd_outboard ( input, Some ( output) ) ) ,
84+ _ => {
85+ eprintln ! ( "hemera: outboard requires <file> [-o output]" ) ;
86+ process:: exit ( 1 ) ;
87+ }
88+ }
89+ }
90+ // hemera keyed-hash <key-hex> <file> — keyed hash
91+ Some ( "keyed-hash" ) => {
92+ let rest: Vec < & str > = args[ 1 ..] . iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
93+ if rest. len ( ) != 2 {
94+ eprintln ! ( "hemera: keyed-hash requires <key-hex> <file>" ) ;
95+ process:: exit ( 1 ) ;
96+ }
97+ process:: exit ( cmd_keyed_hash ( rest[ 0 ] , rest[ 1 ] ) ) ;
98+ }
99+ // hemera derive-key <context> <file> — derive key from context + material
100+ Some ( "derive-key" ) => {
101+ let rest: Vec < & str > = args[ 1 ..] . iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
102+ if rest. len ( ) != 2 {
103+ eprintln ! ( "hemera: derive-key requires <context> <file>" ) ;
104+ process:: exit ( 1 ) ;
105+ }
106+ process:: exit ( cmd_derive_key ( rest[ 0 ] , rest[ 1 ] ) ) ;
107+ }
54108 // hemera verify <file> <hash> — verify single file against hash
55109 // hemera verify <checksums> — verify batch from checksum file
56110 Some ( "verify" ) => {
@@ -269,6 +323,166 @@ fn verify_checksums(path: &str) -> i32 {
269323 }
270324}
271325
326+ fn cmd_encode ( path : & str , output : Option < & str > ) -> i32 {
327+ let data = match fs:: read ( path) {
328+ Ok ( d) => d,
329+ Err ( e) => {
330+ eprintln ! ( "hemera: {path}: {e}" ) ;
331+ return 1 ;
332+ }
333+ } ;
334+
335+ let ( root, encoded) = cyber_hemera:: stream:: encode ( & data) ;
336+ let out_path = output. unwrap_or_else ( || {
337+ // leak is fine — we exit right after
338+ Box :: leak ( format ! ( "{path}.hemera" ) . into_boxed_str ( ) )
339+ } ) ;
340+
341+ if let Err ( e) = fs:: write ( out_path, & encoded) {
342+ eprintln ! ( "hemera: {out_path}: {e}" ) ;
343+ return 1 ;
344+ }
345+
346+ println ! ( "{root} {out_path}" ) ;
347+ 0
348+ }
349+
350+ fn cmd_decode ( path : & str , hash_hex : & str , output : Option < & str > ) -> i32 {
351+ let encoded = match fs:: read ( path) {
352+ Ok ( d) => d,
353+ Err ( e) => {
354+ eprintln ! ( "hemera: {path}: {e}" ) ;
355+ return 1 ;
356+ }
357+ } ;
358+
359+ let root = match parse_hash ( hash_hex) {
360+ Some ( h) => h,
361+ None => {
362+ eprintln ! ( "hemera: invalid hash: {hash_hex}" ) ;
363+ return 1 ;
364+ }
365+ } ;
366+
367+ match cyber_hemera:: stream:: decode ( & encoded, & root) {
368+ Ok ( data) => {
369+ if let Some ( out_path) = output {
370+ if let Err ( e) = fs:: write ( out_path, & data) {
371+ eprintln ! ( "hemera: {out_path}: {e}" ) ;
372+ return 1 ;
373+ }
374+ println ! ( "{path}: OK → {out_path}" ) ;
375+ } else {
376+ let stdout = io:: stdout ( ) ;
377+ let mut handle = stdout. lock ( ) ;
378+ if let Err ( e) = handle. write_all ( & data) {
379+ eprintln ! ( "hemera: {e}" ) ;
380+ return 1 ;
381+ }
382+ }
383+ 0
384+ }
385+ Err ( e) => {
386+ eprintln ! ( "hemera: {path}: {e}" ) ;
387+ 1
388+ }
389+ }
390+ }
391+
392+ fn cmd_outboard ( path : & str , output : Option < & str > ) -> i32 {
393+ let data = match fs:: read ( path) {
394+ Ok ( d) => d,
395+ Err ( e) => {
396+ eprintln ! ( "hemera: {path}: {e}" ) ;
397+ return 1 ;
398+ }
399+ } ;
400+
401+ let ( root, ob) = cyber_hemera:: stream:: outboard ( & data) ;
402+ let out_path = output. unwrap_or_else ( || {
403+ Box :: leak ( format ! ( "{path}.obao" ) . into_boxed_str ( ) )
404+ } ) ;
405+
406+ if let Err ( e) = fs:: write ( out_path, & ob) {
407+ eprintln ! ( "hemera: {out_path}: {e}" ) ;
408+ return 1 ;
409+ }
410+
411+ println ! ( "{root} {out_path}" ) ;
412+ 0
413+ }
414+
415+ fn cmd_keyed_hash ( key_hex : & str , path : & str ) -> i32 {
416+ if key_hex. len ( ) != cyber_hemera:: OUTPUT_BYTES * 2 {
417+ eprintln ! (
418+ "hemera: key must be {} hex chars ({} bytes)" ,
419+ cyber_hemera:: OUTPUT_BYTES * 2 ,
420+ cyber_hemera:: OUTPUT_BYTES
421+ ) ;
422+ return 1 ;
423+ }
424+
425+ let key = match hex_to_bytes ( key_hex) {
426+ Some ( b) => b,
427+ None => {
428+ eprintln ! ( "hemera: invalid hex key: {key_hex}" ) ;
429+ return 1 ;
430+ }
431+ } ;
432+
433+ let mut key_arr = [ 0u8 ; cyber_hemera:: OUTPUT_BYTES ] ;
434+ key_arr. copy_from_slice ( & key) ;
435+
436+ let data = match fs:: read ( path) {
437+ Ok ( d) => d,
438+ Err ( e) => {
439+ eprintln ! ( "hemera: {path}: {e}" ) ;
440+ return 1 ;
441+ }
442+ } ;
443+
444+ let h = cyber_hemera:: keyed_hash ( & key_arr, & data) ;
445+ println ! ( "{h} {path}" ) ;
446+ 0
447+ }
448+
449+ fn cmd_derive_key ( context : & str , path : & str ) -> i32 {
450+ let data = match fs:: read ( path) {
451+ Ok ( d) => d,
452+ Err ( e) => {
453+ eprintln ! ( "hemera: {path}: {e}" ) ;
454+ return 1 ;
455+ }
456+ } ;
457+
458+ let key = cyber_hemera:: derive_key ( context, & data) ;
459+ for byte in & key {
460+ print ! ( "{byte:02x}" ) ;
461+ }
462+ println ! ( " {path}" ) ;
463+ 0
464+ }
465+
466+ fn parse_hash ( hex : & str ) -> Option < cyber_hemera:: Hash > {
467+ let bytes = hex_to_bytes ( hex) ?;
468+ if bytes. len ( ) != cyber_hemera:: OUTPUT_BYTES {
469+ return None ;
470+ }
471+ let mut arr = [ 0u8 ; cyber_hemera:: OUTPUT_BYTES ] ;
472+ arr. copy_from_slice ( & bytes) ;
473+ Some ( cyber_hemera:: Hash :: from_bytes ( arr) )
474+ }
475+
476+ fn hex_to_bytes ( hex : & str ) -> Option < Vec < u8 > > {
477+ if hex. len ( ) % 2 != 0 {
478+ return None ;
479+ }
480+ ( 0 ..hex. len ( ) )
481+ . step_by ( 2 )
482+ . map ( |i| u8:: from_str_radix ( & hex[ i..i + 2 ] , 16 ) . ok ( ) )
483+ . collect ( )
484+ }
485+
272486fn print_usage ( ) {
273487 eprintln ! (
274488 "\
@@ -285,14 +499,19 @@ fn print_usage() {
285499 t=16 R_F=8 R_P=64 d=7 rate=8 output=64B
286500 genesis: [0x63, 0x79, 0x62, 0x65, 0x72]
287501\x1b [0m
288- hemera file1.txt file2.txt Hash files
289- hemera src/ Hash directory (recursive)
290- echo hello | hemera Hash stdin
291- hemera tree file.txt Show tree structure
292- hemera prove file.txt [chunk] Leaf inclusion proof
293- hemera prove file.txt 0:4 Subtree inclusion proof
294- hemera verify file.txt <hash> Verify file against hash
295- hemera verify sums.txt Verify checksums from file
502+ hemera file1.txt file2.txt Hash files
503+ hemera src/ Hash directory (recursive)
504+ echo hello | hemera Hash stdin
505+ hemera tree file.txt Show tree structure
506+ hemera prove file.txt [chunk] Leaf inclusion proof
507+ hemera prove file.txt 0:4 Subtree inclusion proof
508+ hemera verify file.txt <hash> Verify file against hash
509+ hemera verify sums.txt Verify checksums from file
510+ hemera encode file.txt [-o out] Encode to verified stream
511+ hemera decode file.hemera <hash> Decode and verify stream
512+ hemera outboard file.txt [-o out] Compute outboard hash tree
513+ hemera keyed-hash <key-hex> file Keyed hash
514+ hemera derive-key <context> file Derive key from context
296515
297516 -h, --help Print this help"
298517 ) ;
0 commit comments