Supply Chain Integrity, Transparency, and Trust (IETF SCITT)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Replace runtime 64-bit check with build-time dune constraint

The failwith at module init was an uncatchable crash on 32-bit targets.
Use (enabled_if %{arch_sixtyfour}) in dune so the library is rejected
at compile time. Document why 64-bit is required: native int overflow
at ~2B entries is reachable for transparency logs.

+85 -52
+1
lib/dune
··· 1 1 (library 2 2 (name scitt) 3 3 (public_name scitt) 4 + (enabled_if %{arch_sixtyfour}) 4 5 (libraries 5 6 cose 6 7 cbort
+82 -51
lib/vds.ml
··· 1 - (* Verifiable Data Structures for SCITT. *) 1 + (* Verifiable Data Structures for SCITT. 2 2 3 - let () = 4 - if Sys.int_size < 63 then 5 - failwith 6 - "scitt requires 64-bit OCaml (Sys.int_size >= 63) for safe tree indexing" 3 + Requires 64-bit OCaml: inclusion proof indices (leaf_index, tree_size) use 4 + native [int] for zero-allocation arithmetic on the hot path. A 32-bit int 5 + overflows at ~2 billion entries which is well within reach for a transparency 6 + log. The build constraint in lib/dune rejects 32-bit targets at compile 7 + time so this cannot fail silently at runtime. *) 7 8 8 9 let max_statement_size = 16 * 1024 * 1024 (* 16 MiB *) 9 10 let max_proof_path_length = 64 ··· 40 41 41 42 let algorithm_id (T { impl = (module I); state }) = I.algorithm_id state 42 43 let proof_format (T { impl = (module I); state }) = I.proof_format state 43 - let append (T { impl = (module I); state }) ~key ~value = I.append state ~key ~value 44 + 45 + let append (T { impl = (module I); state }) ~key ~value = 46 + I.append state ~key ~value 44 47 45 48 let batch_append (T { impl = (module I); state }) entries = 46 49 let rec go acc = function ··· 230 233 let algorithm_id t = Hash.id t.hash 231 234 let proof_format _ = Hash.Rfc9162 232 235 let size t = with_lock t (fun () -> Growable.length t.hashes) 233 - let root t = with_lock t (fun () -> Compact.root t.compact ~empty_hash:t.empty_hash) 236 + 237 + let root t = 238 + with_lock t (fun () -> Compact.root t.compact ~empty_hash:t.empty_hash) 239 + 234 240 let lookup t ~key = with_lock t (fun () -> Hashtbl.find_opt t.leaves key) 235 241 236 242 let append t ~key ~value = 237 243 with_lock t (fun () -> 238 - if Hashtbl.mem t.leaves key then Error ("duplicate key: " ^ key) 239 - else 240 - let leaf_h = Hash.leaf_hash_with t.hash value in 241 - let idx = Growable.length t.hashes in 242 - Growable.push t.hashes leaf_h; 243 - Compact.append t.compact leaf_h; 244 - Hashtbl.replace t.leaves key value; 245 - t.leaves_order <- key :: t.leaves_order; 246 - let n = Growable.length t.hashes in 247 - let path = Node_cache.inclusion_path t.ncache 0 n idx in 248 - let root = Compact.root t.compact ~empty_hash:t.empty_hash in 249 - Ok { leaf_index = idx; tree_size = n; root; path; leaf_hash = leaf_h }) 244 + if Hashtbl.mem t.leaves key then Error ("duplicate key: " ^ key) 245 + else 246 + let leaf_h = Hash.leaf_hash_with t.hash value in 247 + let idx = Growable.length t.hashes in 248 + Growable.push t.hashes leaf_h; 249 + Compact.append t.compact leaf_h; 250 + Hashtbl.replace t.leaves key value; 251 + t.leaves_order <- key :: t.leaves_order; 252 + let n = Growable.length t.hashes in 253 + let path = Node_cache.inclusion_path t.ncache 0 n idx in 254 + let root = Compact.root t.compact ~empty_hash:t.empty_hash in 255 + Ok 256 + { 257 + leaf_index = idx; 258 + tree_size = n; 259 + root; 260 + path; 261 + leaf_hash = leaf_h; 262 + }) 250 263 251 - let export t = with_lock t (fun () -> 252 - let entries = 253 - List.rev t.leaves_order 254 - |> List.filter_map (fun k -> 255 - match Hashtbl.find_opt t.leaves k with 256 - | Some v -> Some (k, v) 257 - | None -> None) 258 - in 259 - let r = Compact.root t.compact ~empty_hash:t.empty_hash in 260 - export_cbor ~hash:t.hash ~root:r ~entries) 264 + let export t = 265 + with_lock t (fun () -> 266 + let entries = 267 + List.rev t.leaves_order 268 + |> List.filter_map (fun k -> 269 + match Hashtbl.find_opt t.leaves k with 270 + | Some v -> Some (k, v) 271 + | None -> None) 272 + in 273 + let r = Compact.root t.compact ~empty_hash:t.empty_hash in 274 + export_cbor ~hash:t.hash ~root:r ~entries) 261 275 end 262 276 263 277 let v ?(hash = Hash.sha256) () = ··· 300 314 let algorithm_id t = Hash.id t.hash 301 315 let proof_format _ = Hash.Rfc9162 302 316 let size t = with_lock t (fun () -> Growable.length t.hashes) 303 - let root t = with_lock t (fun () -> Compact.root t.compact ~empty_hash:t.empty_hash) 317 + 318 + let root t = 319 + with_lock t (fun () -> Compact.root t.compact ~empty_hash:t.empty_hash) 320 + 304 321 let lookup t ~key = with_lock t (fun () -> Sqlite.Table.find t.entries key) 305 322 306 323 let append t ~key ~value = 307 324 with_lock t (fun () -> 308 - if Sqlite.Table.mem t.entries key then Error ("duplicate key: " ^ key) 309 - else 310 - let leaf_h = Hash.leaf_hash_with t.hash value in 311 - Sqlite.with_transaction t.db (fun () -> 312 - let _ = 313 - Sqlite.insert t.db ~table:"scitt_hashes" [ Sqlite.Vblob leaf_h ] 314 - in 315 - Sqlite.Table.put t.entries key value; 316 - Growable.push t.hashes leaf_h; 317 - Compact.append t.compact leaf_h); 318 - let n = Growable.length t.hashes in 319 - let idx = n - 1 in 320 - let path = Node_cache.inclusion_path t.ncache 0 n idx in 321 - let root = Compact.root t.compact ~empty_hash:t.empty_hash in 322 - Ok { leaf_index = idx; tree_size = n; root; path; leaf_hash = leaf_h }) 325 + if Sqlite.Table.mem t.entries key then Error ("duplicate key: " ^ key) 326 + else 327 + let leaf_h = Hash.leaf_hash_with t.hash value in 328 + Sqlite.with_transaction t.db (fun () -> 329 + let _ = 330 + Sqlite.insert t.db ~table:"scitt_hashes" 331 + [ Sqlite.Vblob leaf_h ] 332 + in 333 + Sqlite.Table.put t.entries key value; 334 + Growable.push t.hashes leaf_h; 335 + Compact.append t.compact leaf_h); 336 + let n = Growable.length t.hashes in 337 + let idx = n - 1 in 338 + let path = Node_cache.inclusion_path t.ncache 0 n idx in 339 + let root = Compact.root t.compact ~empty_hash:t.empty_hash in 340 + Ok 341 + { 342 + leaf_index = idx; 343 + tree_size = n; 344 + root; 345 + path; 346 + leaf_hash = leaf_h; 347 + }) 323 348 324 - let export t = with_lock t (fun () -> 325 - let all = ref [] in 326 - Sqlite.Table.iter t.entries ~f:(fun k v -> all := (k, v) :: !all); 327 - let r = Compact.root t.compact ~empty_hash:t.empty_hash in 328 - export_cbor ~hash:t.hash ~root:r ~entries:(List.rev !all)) 349 + let export t = 350 + with_lock t (fun () -> 351 + let all = ref [] in 352 + Sqlite.Table.iter t.entries ~f:(fun k v -> all := (k, v) :: !all); 353 + let r = Compact.root t.compact ~empty_hash:t.empty_hash in 354 + export_cbor ~hash:t.hash ~root:r ~entries:(List.rev !all)) 329 355 end 330 356 331 357 let v ?(hash = Hash.sha256) (db : Sqlite.t) = ··· 394 420 Error "import: duplicate keys in CBOR map" 395 421 else Ok pairs 396 422 397 - let import data ~create = 423 + let parse_import_header data = 398 424 let ( let* ) = Result.bind in 399 425 let* cbor = 400 426 Cbort.decode_string Cbort.any data ··· 432 458 | None -> Error "import: missing entries field" 433 459 | Some items -> parse_cbor_entries items 434 460 in 461 + Ok (hash, expected_root, entries) 462 + 463 + let import data ~create = 464 + let ( let* ) = Result.bind in 465 + let* hash, expected_root, entries = parse_import_header data in 435 466 let vds = create ~hash () in 436 467 let rec go = function 437 468 | [] -> Ok ()
+2 -1
test/test_vds.ml
··· 124 124 for d = 0 to n_domains - 1 do 125 125 for i = 0 to n_per_domain - 1 do 126 126 let key = Fmt.str "d%d-k%d" d i in 127 - Alcotest.(check bool) (Fmt.str "found %s" key) true 127 + Alcotest.(check bool) 128 + (Fmt.str "found %s" key) true 128 129 (Option.is_some (Scitt.vds_lookup vds ~key)) 129 130 done 130 131 done