@@ -8,12 +8,20 @@ use crate::vm::{
88const REGULAR_PC_UPDATE : u64 = 4 ;
99
1010pub enum SyscallNumbers {
11+ // Placeholder discriminant. The actual syscall value is KECCAK_SYSCALL_NUMBER.
12+ KeccakPermute = 0 ,
1113 Print = 1 ,
1214 Panic = 2 ,
1315 Commit = 64 ,
1416 Halt = 93 ,
1517}
1618
19+ /// Syscall number for KeccakPermute (u64::MAX - 1 = 0xFFFF_FFFF_FFFF_FFFE).
20+ ///
21+ /// Cannot be an enum discriminant because it exceeds isize::MAX.
22+ pub const KECCAK_SYSCALL_NUMBER : u64 = u64:: MAX - 1 ;
23+ const KECCAK_STATE_BYTES : u64 = 25 * 8 ;
24+
1725impl TryFrom < u64 > for SyscallNumbers {
1826 type Error = ( ) ;
1927 fn try_from ( value : u64 ) -> Result < Self , Self :: Error > {
@@ -22,6 +30,7 @@ impl TryFrom<u64> for SyscallNumbers {
2230 2 => Ok ( SyscallNumbers :: Panic ) ,
2331 64 => Ok ( SyscallNumbers :: Commit ) ,
2432 93 => Ok ( SyscallNumbers :: Halt ) ,
33+ v if v == KECCAK_SYSCALL_NUMBER => Ok ( SyscallNumbers :: KeccakPermute ) ,
2534 _ => Err ( ( ) ) ,
2635 }
2736 }
@@ -324,6 +333,32 @@ impl Instruction {
324333 src2_val = buf_addr;
325334 dst_val = count;
326335 }
336+ SyscallNumbers :: KeccakPermute => {
337+ // keccak-f[1600] permutation on 200 bytes (25 × u64) at address in x10
338+ let state_addr = registers. read ( 10 ) ?;
339+ if !state_addr. is_multiple_of ( 8 ) {
340+ return Err ( ExecutionError :: UnalignedKeccakStateAddress ( state_addr) ) ;
341+ }
342+ state_addr
343+ . checked_add ( KECCAK_STATE_BYTES - 1 )
344+ . ok_or ( ExecutionError :: KeccakStateAddressOverflow ( state_addr) ) ?;
345+
346+ let mut state = [ 0u64 ; 25 ] ;
347+ for ( i, lane) in state. iter_mut ( ) . enumerate ( ) {
348+ let lane_addr = state_addr
349+ . checked_add ( ( i as u64 ) * 8 )
350+ . ok_or ( ExecutionError :: KeccakStateAddressOverflow ( state_addr) ) ?;
351+ * lane = memory. load_doubleword ( lane_addr) ?;
352+ }
353+ keccak_f1600 ( & mut state) ;
354+ for ( i, & lane) in state. iter ( ) . enumerate ( ) {
355+ let lane_addr = state_addr
356+ . checked_add ( ( i as u64 ) * 8 )
357+ . ok_or ( ExecutionError :: KeccakStateAddressOverflow ( state_addr) ) ?;
358+ memory. store_doubleword ( lane_addr, lane) ?;
359+ }
360+ src2_val = state_addr;
361+ }
327362 SyscallNumbers :: Halt => {
328363 // halt
329364 return Ok ( Log {
@@ -496,4 +531,177 @@ pub enum ExecutionError {
496531 InvalidWSuffixOperation ( ArithOp ) ,
497532 #[ error( "Invalid commit fd: expected 1 (stdout), got {0}" ) ]
498533 InvalidCommitFd ( u64 ) ,
534+ #[ error( "Unaligned Keccak state address: {0:#018x}" ) ]
535+ UnalignedKeccakStateAddress ( u64 ) ,
536+ #[ error( "Keccak state address range overflows: {0:#018x}" ) ]
537+ KeccakStateAddressOverflow ( u64 ) ,
538+ }
539+
540+ // =============================================================================
541+ // Keccak-f[1600] permutation
542+ // =============================================================================
543+
544+ /// Round constants for Keccak-f[1600] (24 rounds).
545+ pub const KECCAK_RC : [ u64 ; 24 ] = [
546+ 0x0000000000000001 ,
547+ 0x0000000000008082 ,
548+ 0x800000000000808A ,
549+ 0x8000000080008000 ,
550+ 0x000000000000808B ,
551+ 0x0000000080000001 ,
552+ 0x8000000080008081 ,
553+ 0x8000000000008009 ,
554+ 0x000000000000008A ,
555+ 0x0000000000000088 ,
556+ 0x0000000080008009 ,
557+ 0x000000008000000A ,
558+ 0x000000008000808B ,
559+ 0x800000000000008B ,
560+ 0x8000000000008089 ,
561+ 0x8000000000008003 ,
562+ 0x8000000000008002 ,
563+ 0x8000000000000080 ,
564+ 0x000000000000800A ,
565+ 0x800000008000000A ,
566+ 0x8000000080008081 ,
567+ 0x8000000000008080 ,
568+ 0x0000000080000001 ,
569+ 0x8000000080008008 ,
570+ ] ;
571+
572+ /// Rotation offsets R[x][y] for the rho step of Keccak-f[1600].
573+ pub const KECCAK_RHO : [ [ u32 ; 5 ] ; 5 ] = [
574+ [ 0 , 36 , 3 , 41 , 18 ] ,
575+ [ 1 , 44 , 10 , 45 , 2 ] ,
576+ [ 62 , 6 , 43 , 15 , 61 ] ,
577+ [ 28 , 55 , 25 , 21 , 56 ] ,
578+ [ 27 , 20 , 39 , 8 , 14 ] ,
579+ ] ;
580+
581+ /// Apply the Keccak-f[1600] permutation (24 rounds) to a 25-word state.
582+ ///
583+ /// The state is indexed as `state[x + 5*y]` where `x, y ∈ {0..4}`.
584+ pub fn keccak_f1600 ( state : & mut [ u64 ; 25 ] ) {
585+ for & rc in & KECCAK_RC {
586+ // θ (theta)
587+ let mut c = [ 0u64 ; 5 ] ;
588+ for x in 0 ..5 {
589+ c[ x] = state[ x] ^ state[ x + 5 ] ^ state[ x + 10 ] ^ state[ x + 15 ] ^ state[ x + 20 ] ;
590+ }
591+ let mut d = [ 0u64 ; 5 ] ;
592+ for x in 0 ..5 {
593+ d[ x] = c[ ( x + 4 ) % 5 ] ^ c[ ( x + 1 ) % 5 ] . rotate_left ( 1 ) ;
594+ }
595+ for x in 0 ..5 {
596+ for y in 0 ..5 {
597+ state[ x + 5 * y] ^= d[ x] ;
598+ }
599+ }
600+
601+ // ρ (rho) and π (pi)
602+ let mut b = [ 0u64 ; 25 ] ;
603+ for x in 0 ..5 {
604+ for y in 0 ..5 {
605+ b[ y + 5 * ( ( 2 * x + 3 * y) % 5 ) ] = state[ x + 5 * y] . rotate_left ( KECCAK_RHO [ x] [ y] ) ;
606+ }
607+ }
608+
609+ // χ (chi)
610+ for x in 0 ..5 {
611+ for y in 0 ..5 {
612+ state[ x + 5 * y] =
613+ b[ x + 5 * y] ^ ( !b[ ( x + 1 ) % 5 + 5 * y] & b[ ( x + 2 ) % 5 + 5 * y] ) ;
614+ }
615+ }
616+
617+ // ι (iota)
618+ state[ 0 ] ^= rc;
619+ }
620+ }
621+
622+ #[ cfg( test) ]
623+ mod tests {
624+ use super :: * ;
625+
626+ #[ test]
627+ fn test_keccak_f1600_zero_input ( ) {
628+ let mut state = [ 0u64 ; 25 ] ;
629+ keccak_f1600 ( & mut state) ;
630+
631+ let expected: [ u64 ; 25 ] = [
632+ 0xF1258F7940E1DDE7 ,
633+ 0x84D5CCF933C0478A ,
634+ 0xD598261EA65AA9EE ,
635+ 0xBD1547306F80494D ,
636+ 0x8B284E056253D057 ,
637+ 0xFF97A42D7F8E6FD4 ,
638+ 0x90FEE5A0A44647C4 ,
639+ 0x8C5BDA0CD6192E76 ,
640+ 0xAD30A6F71B19059C ,
641+ 0x30935AB7D08FFC64 ,
642+ 0xEB5AA93F2317D635 ,
643+ 0xA9A6E6260D712103 ,
644+ 0x81A57C16DBCF555F ,
645+ 0x43B831CD0347C826 ,
646+ 0x01F22F1A11A5569F ,
647+ 0x05E5635A21D9AE61 ,
648+ 0x64BEFEF28CC970F2 ,
649+ 0x613670957BC46611 ,
650+ 0xB87C5A554FD00ECB ,
651+ 0x8C3EE88A1CCF32C8 ,
652+ 0x940C7922AE3A2614 ,
653+ 0x1841F924A2C509E4 ,
654+ 0x16F53526E70465C2 ,
655+ 0x75F644E97F30A13B ,
656+ 0xEAF1FF7B5CECA249 ,
657+ ] ;
658+
659+ assert_eq ! ( state, expected, "keccak-f[1600] on zero input mismatch" ) ;
660+ }
661+
662+ #[ test]
663+ fn test_keccak_f1600_nonzero_input ( ) {
664+ let mut state = [ 0u64 ; 25 ] ;
665+ state[ 0 ] = 1 ;
666+ let original = state;
667+ keccak_f1600 ( & mut state) ;
668+ assert_ne ! ( state, original) ;
669+ assert ! ( state. iter( ) . any( |& x| x != 0 ) ) ;
670+ }
671+
672+ #[ test]
673+ fn test_keccak_syscall_rejects_unaligned_state_addr ( ) {
674+ let mut pc = 0 ;
675+ let mut registers = Registers :: default ( ) ;
676+ let mut memory = Memory :: default ( ) ;
677+
678+ registers. write ( 17 , KECCAK_SYSCALL_NUMBER ) . unwrap ( ) ;
679+ registers. write ( 10 , 0x1001 ) . unwrap ( ) ;
680+
681+ let err = Instruction :: EcallEbreak
682+ . run ( & mut pc, & mut registers, & mut memory)
683+ . unwrap_err ( ) ;
684+ assert ! ( matches!(
685+ err,
686+ ExecutionError :: UnalignedKeccakStateAddress ( 0x1001 )
687+ ) ) ;
688+ }
689+
690+ #[ test]
691+ fn test_keccak_syscall_rejects_overflowing_state_range ( ) {
692+ let mut pc = 0 ;
693+ let mut registers = Registers :: default ( ) ;
694+ let mut memory = Memory :: default ( ) ;
695+
696+ registers. write ( 17 , KECCAK_SYSCALL_NUMBER ) . unwrap ( ) ;
697+ registers. write ( 10 , u64:: MAX - 191 ) . unwrap ( ) ;
698+
699+ let err = Instruction :: EcallEbreak
700+ . run ( & mut pc, & mut registers, & mut memory)
701+ . unwrap_err ( ) ;
702+ assert ! ( matches!(
703+ err,
704+ ExecutionError :: KeccakStateAddressOverflow ( addr) if addr == u64 :: MAX - 191
705+ ) ) ;
706+ }
499707}
0 commit comments