code wiki / (root) / nx_save_slot.nx

nx_save_slot.nx source

↩ module page · 145 lines · 5133 B

1// nx_save_slot.nx -- signed content-addressed game save format. 2// 3// Composes nx_chromatin (snapshot mechanism) + nx_hash_facade (content 4// hash) + nx_sign_facade (signature). A save_slot holds player + 5// world chromatin pointers; verify roundtrip ensures the saved state 6// hasn't been tampered with between sessions. 7// 8// Composes: 9// nx_chromatin -- snapshots are the substrate's canonical state-capture 10// nx_hash_facade -- content hash for the combined player+world bytes 11// nx_sign_facade -- signature over (hash + ts + save_id) 12// nx_vacuole -- saves can be stored in a vacuole 13 14import "nx_syscalls.nx" 15import "nx_tier.nx" 16import "nx_chromatin.nx" 17import "nx_hash_facade.nx" 18import "nx_sign_facade.nx" 19 20const NX_SS_OK: nx_int = 0 21const NX_SS_ERR_BAD_INPUT: nx_int = 1 22const NX_SS_ERR_BAD_SIG: nx_int = 2 23const NX_SS_ERR_BAD_HASH: nx_int = 3 24const NX_SS_VALID: nx_int = 4 25const NX_SS_INVALID: nx_int = 5 26 27// ===== Struct: NxSaveSlot ========================================== 28 29struct NxSaveSlot { 30 save_id: nx_int, 31 player_chromatin: *NxChromatin, 32 world_chromatin: *NxChromatin, 33 captured_us: nx_size, 34 combined_hash: nx_size, 35 signature: *u8, 36 signature_len: nx_size, 37} 38 39// ===== _hash_two_chromatins ======================================== 40// 41// Combine the two chromatin content_hash values into one substrate- 42// level "save hash" by XOR-chaining through nx_hash_facade. 43 44func _hash_two_chromatins(player: *NxChromatin, world: *NxChromatin) -> nx_size { 45 let p_h: nx_size = player.content_hash 46 let w_h: nx_size = world.content_hash 47 // Combined: rotate p_h and XOR with w_h (cheap mixing) 48 let rotated: nx_size = ((p_h << 17) | (p_h >> 47)) & 0xFFFFFFFFFFFFFFFF 49 return rotated ^ w_h 50} 51 52// ===== nx_save_slot_create ========================================== 53// 54// Snapshot a save: capture combined hash + sign it. signature buffer 55// is caller-allocated. sk is the save-signing key. 56 57func nx_save_slot_create(save_id: nx_int, 58 player: *NxChromatin, 59 world: *NxChromatin, 60 now_us: nx_size, 61 sk: *u8, sk_len: nx_size, 62 sig_buf: *u8, sig_cap: nx_size, 63 trust_q10: nx_int) -> *NxSaveSlot { 64 if (player as i64) == 0 { return (0 as i64) as *NxSaveSlot } 65 if (world as i64) == 0 { return (0 as i64) as *NxSaveSlot } 66 67 let s: *NxSaveSlot = (sys_mmap(56)) as *NxSaveSlot 68 s.save_id = save_id 69 s.player_chromatin = player 70 s.world_chromatin = world 71 s.captured_us = now_us 72 s.combined_hash = _hash_two_chromatins(player, world) 73 74 // Build the message to sign: (save_id, captured_us, combined_hash) 75 // Pack as 24 bytes 76 let msg: *u8 = (sys_mmap(24)) as *u8 77 var i: nx_size = 0 78 while i < 8 { 79 msg[i] = ((s.save_id >> (i * 8)) & 255) as u8 80 i = i + 1 81 } 82 while i < 16 { 83 msg[i] = ((s.captured_us >> ((i - 8) * 8)) & 255) as u8 84 i = i + 1 85 } 86 while i < 24 { 87 msg[i] = ((s.combined_hash >> ((i - 16) * 8)) & 255) as u8 88 i = i + 1 89 } 90 let req: *NxSignRequest = nx_sign_request_new(NX_SA_ED25519, 91 msg, 24, sk, sk_len, sig_buf, sig_cap, trust_q10) 92 let sig_len: nx_int = nx_sign_facade_sign(req) 93 if sig_len < 0 { return (0 as i64) as *NxSaveSlot } 94 s.signature = sig_buf 95 s.signature_len = sig_len as nx_size 96 return s 97} 98 99// ===== nx_save_slot_verify ========================================== 100// 101// Re-derive the signed message + check signature. Also confirms that 102// the player+world chromatins still hash to the saved combined_hash 103// (catches in-memory tampering of chromatin state since save). 104// Returns NX_SS_VALID or NX_SS_INVALID. 105 106func nx_save_slot_verify(s: *NxSaveSlot, 107 sk: *u8, sk_len: nx_size, 108 trust_q10: nx_int) -> nx_int { 109 if (s as i64) == 0 { return NX_SS_ERR_BAD_INPUT } 110 if (s.signature as i64) == 0 { return NX_SS_ERR_BAD_SIG } 111 let h: nx_size = _hash_two_chromatins(s.player_chromatin, s.world_chromatin) 112 if h != s.combined_hash { return NX_SS_INVALID } 113 114 // Rebuild original signed message 115 let msg: *u8 = (sys_mmap(24)) as *u8 116 var i: nx_size = 0 117 while i < 8 { 118 msg[i] = ((s.save_id >> (i * 8)) & 255) as u8 119 i = i + 1 120 } 121 while i < 16 { 122 msg[i] = ((s.captured_us >> ((i - 8) * 8)) & 255) as u8 123 i = i + 1 124 } 125 while i < 24 { 126 msg[i] = ((s.combined_hash >> ((i - 16) * 8)) & 255) as u8 127 i = i + 1 128 } 129 let v: nx_int = nx_sign_facade_verify(NX_SA_ED25519, 130 msg, 24, s.signature, s.signature_len, sk, sk_len, trust_q10) 131 if v != NX_SF_VERIFIED { return NX_SS_INVALID } 132 return NX_SS_VALID 133} 134 135func nx_save_slot_id(s: *NxSaveSlot) -> nx_int { 136 return s.save_id 137} 138 139func nx_save_slot_captured_us(s: *NxSaveSlot) -> nx_size { 140 return s.captured_us 141} 142 143func nx_save_slot_combined_hash(s: *NxSaveSlot) -> nx_size { 144 return s.combined_hash 145}