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}