@@ -2,7 +2,9 @@ use alloc::vec::Vec;
22use alloc:: string:: String ;
33use crate :: provenance:: provenance_hash;
44use crate :: println;
5- use crate :: task:: CryptoVerificationJob ;
5+
6+ /// Provenance block granularity (standard 4 KiB, matching fs-verity/dm-verity).
7+ pub const BLOCK_SIZE : usize = 4096 ;
68
79#[ derive( Debug , Clone ) ]
810pub enum FileType {
@@ -15,26 +17,72 @@ pub struct FileNode {
1517 pub name : String ,
1618 pub file_type : FileType ,
1719 pub data : Vec < u8 > ,
20+ /// Whole-file BLAKE3 (baseline path; goes stale after a partial write_block).
1821 pub provenance_hash : [ u8 ; 32 ] ,
22+ /// Per-BLOCK_SIZE BLAKE3 leaves; a range read verifies only touched blocks.
23+ pub block_hashes : Vec < [ u8 ; 32 ] > ,
24+ /// BLAKE3 Merkle root over the leaves, recomputed lazily when dirty.
25+ merkle_root : [ u8 ; 32 ] ,
26+ root_dirty : bool ,
27+ }
28+
29+ /// One BLAKE3 hash per BLOCK_SIZE chunk (last chunk may be short).
30+ fn compute_block_hashes ( data : & [ u8 ] ) -> Vec < [ u8 ; 32 ] > {
31+ data. chunks ( BLOCK_SIZE ) . map ( |c| provenance_hash ( c) ) . collect ( )
32+ }
33+
34+ /// BLAKE3 Merkle root over the leaves: pairs hashed bottom-up, a lone node is
35+ /// promoted unchanged. Empty input -> zero root.
36+ fn compute_merkle_root ( leaves : & [ [ u8 ; 32 ] ] ) -> [ u8 ; 32 ] {
37+ if leaves. is_empty ( ) {
38+ return [ 0u8 ; 32 ] ;
39+ }
40+ let mut level: Vec < [ u8 ; 32 ] > = leaves. to_vec ( ) ;
41+ while level. len ( ) > 1 {
42+ let mut next: Vec < [ u8 ; 32 ] > = Vec :: with_capacity ( ( level. len ( ) + 1 ) / 2 ) ;
43+ let mut i = 0 ;
44+ while i < level. len ( ) {
45+ if i + 1 < level. len ( ) {
46+ let mut buf = [ 0u8 ; 64 ] ;
47+ buf[ ..32 ] . copy_from_slice ( & level[ i] ) ;
48+ buf[ 32 ..] . copy_from_slice ( & level[ i + 1 ] ) ;
49+ next. push ( provenance_hash ( & buf) ) ;
50+ } else {
51+ next. push ( level[ i] ) ;
52+ }
53+ i += 2 ;
54+ }
55+ level = next;
56+ }
57+ level[ 0 ]
1958}
2059
2160impl FileNode {
2261 pub fn new_file ( name : & str , data : & [ u8 ] ) -> Self {
2362 let hash = provenance_hash ( data) ;
63+ let block_hashes = compute_block_hashes ( data) ;
64+ let merkle_root = compute_merkle_root ( & block_hashes) ;
2465 FileNode {
2566 name : String :: from ( name) ,
2667 file_type : FileType :: Regular ,
2768 data : Vec :: from ( data) ,
2869 provenance_hash : hash,
70+ block_hashes,
71+ merkle_root,
72+ root_dirty : false ,
2973 }
3074 }
3175
3276 pub fn new_file_untrusted ( name : & str , data : & [ u8 ] ) -> Self {
77+ let nblocks = ( data. len ( ) + BLOCK_SIZE - 1 ) / BLOCK_SIZE ;
3378 FileNode {
3479 name : String :: from ( name) ,
3580 file_type : FileType :: Regular ,
3681 data : Vec :: from ( data) ,
3782 provenance_hash : [ 0u8 ; 32 ] , // deliberately invalid — always fails verify()
83+ block_hashes : alloc:: vec![ [ 0u8 ; 32 ] ; nblocks] , // also fails verify_range()
84+ merkle_root : [ 0u8 ; 32 ] ,
85+ root_dirty : false ,
3886 }
3987 }
4088
@@ -44,6 +92,9 @@ impl FileNode {
4492 file_type : FileType :: Directory ,
4593 data : Vec :: new ( ) ,
4694 provenance_hash : [ 0u8 ; 32 ] ,
95+ block_hashes : Vec :: new ( ) ,
96+ merkle_root : [ 0u8 ; 32 ] ,
97+ root_dirty : false ,
4798 }
4899 }
49100
@@ -53,6 +104,54 @@ impl FileNode {
53104 & self . provenance_hash
54105 )
55106 }
107+
108+ /// Block-level: verify only the leaves overlapping [offset, offset+len).
109+ pub fn verify_range ( & self , offset : usize , len : usize ) -> bool {
110+ if len == 0 {
111+ return true ;
112+ }
113+ let end = core:: cmp:: min ( offset + len, self . data . len ( ) ) ;
114+ if offset >= end {
115+ return false ;
116+ }
117+ let start_block = offset / BLOCK_SIZE ;
118+ let end_block = ( end - 1 ) / BLOCK_SIZE ;
119+ for b in start_block..=end_block {
120+ let bstart = b * BLOCK_SIZE ;
121+ let bend = core:: cmp:: min ( bstart + BLOCK_SIZE , self . data . len ( ) ) ;
122+ let live = provenance_hash ( & self . data [ bstart..bend] ) ;
123+ match self . block_hashes . get ( b) {
124+ Some ( stored) if crate :: provenance:: constant_time_eq ( & live, stored) => { }
125+ _ => return false ,
126+ }
127+ }
128+ true
129+ }
130+
131+ /// Lazily return the Merkle root, recomputing from leaves only if dirty.
132+ pub fn merkle_root ( & mut self ) -> [ u8 ; 32 ] {
133+ if self . root_dirty {
134+ self . merkle_root = compute_merkle_root ( & self . block_hashes ) ;
135+ self . root_dirty = false ;
136+ }
137+ self . merkle_root
138+ }
139+
140+ /// Overwrite one block: updates that leaf and marks the root dirty, so a
141+ /// single-block write costs one block hash instead of a full-file rehash.
142+ /// The whole-file `provenance_hash` (baseline only) is left stale.
143+ pub fn write_block ( & mut self , idx : usize , new_data : & [ u8 ] ) -> bool {
144+ let bstart = idx * BLOCK_SIZE ;
145+ if bstart >= self . data . len ( ) || idx >= self . block_hashes . len ( ) {
146+ return false ;
147+ }
148+ let bend = core:: cmp:: min ( bstart + BLOCK_SIZE , self . data . len ( ) ) ;
149+ let n = core:: cmp:: min ( new_data. len ( ) , bend - bstart) ;
150+ self . data [ bstart..bstart + n] . copy_from_slice ( & new_data[ ..n] ) ;
151+ self . block_hashes [ idx] = provenance_hash ( & self . data [ bstart..bend] ) ;
152+ self . root_dirty = true ;
153+ true
154+ }
56155}
57156
58157pub struct VirtualFS {
@@ -85,6 +184,35 @@ impl VirtualFS {
85184 Some ( file. data . as_slice ( ) )
86185 }
87186
187+ /// Block-level verified ranged read: verifies only the blocks overlapping
188+ /// the range. Cost scales with bytes read, not total file size.
189+ pub fn read_range ( & self , name : & str , offset : usize , len : usize ) -> Option < & [ u8 ] > {
190+ let file = self . files . iter ( ) . find ( |f| f. name == name) ?;
191+ let end = core:: cmp:: min ( offset + len, file. data . len ( ) ) ;
192+ if offset >= end {
193+ return None ;
194+ }
195+ if !file. verify_range ( offset, len) {
196+ println ! ( "[AXIOM KERNEL] READ BLOCKED: \" {}\" block {} provenance violation" ,
197+ name, offset / BLOCK_SIZE ) ;
198+ return None ;
199+ }
200+ Some ( & file. data [ offset..end] )
201+ }
202+
203+ /// Overwrite one block of a file (exercises the lazy Merkle path).
204+ pub fn write_block ( & mut self , name : & str , idx : usize , data : & [ u8 ] ) -> bool {
205+ match self . files . iter_mut ( ) . find ( |f| f. name == name) {
206+ Some ( f) => f. write_block ( idx, data) ,
207+ None => false ,
208+ }
209+ }
210+
211+ /// Current Merkle root for a file (recomputed lazily if dirty).
212+ pub fn merkle_root ( & mut self , name : & str ) -> Option < [ u8 ; 32 ] > {
213+ self . files . iter_mut ( ) . find ( |f| f. name == name) . map ( |f| f. merkle_root ( ) )
214+ }
215+
88216 pub fn verify ( & self , name : & str ) -> Option < bool > {
89217 self . files . iter ( )
90218 . find ( |f| f. name == name)
@@ -106,31 +234,4 @@ impl VirtualFS {
106234 }
107235 }
108236 }
109- }
110-
111- // Atomic State Machine Definitions for Asynchronous Verification
112- pub const STATE_PENDING : u8 = 0 ;
113- pub const STATE_VERIFIED : u8 = 1 ;
114- pub const STATE_CORRUPTED : u8 = 2 ;
115-
116- pub struct CachedVfsBlock {
117- pub block_id : u64 ,
118- pub data : [ u8 ; 4096 ] ,
119- pub lineage_token : u64 ,
120- pub status : core:: sync:: atomic:: AtomicU8 ,
121- }
122-
123- pub fn read_block_async ( block : & mut CachedVfsBlock ) {
124- block. status . store ( STATE_PENDING , core:: sync:: atomic:: Ordering :: Relaxed ) ;
125-
126- let job = CryptoVerificationJob {
127- block_ptr : block as * mut CachedVfsBlock ,
128- expected_hash : [ 0u8 ; 32 ] ,
129- } ;
130-
131- if let Some ( queue) = crate :: task:: VERIFICATION_QUEUE . get ( ) {
132- let _ = queue. push ( job) ;
133- }
134-
135- crate :: task:: yield_current_thread ( ) ;
136237}
0 commit comments