kernel: make the crypto/content-addressing stack actually WASM-safe (32-bit ints)
The kernel's sha2/cbor/cid/ed25519 modules were labelled 'WASM-safe' but assumed 63-bit native int. On the web targets — js_of_ocaml (32-bit int) and wasm_of_ocaml (31-bit int) — they truncated, producing wrong digests/CIDs and a Char.chr crash at kernel INIT (ed25519 precomputes sqrtm1 + base_point at module load, driving the base-2^26 bignum). This is why a freshly-built browser kernel crashed on boot while the stale committed artifact (older toolchain) still ran. Fixes (all verified bit-identical to the 63-bit native build, conformance 271/271): - sx_sha2: SHA-256 round words via Int32 (were native int + land 0xFFFFFFFF, which is a no-op on 31-bit and overflows the constants); both SHA-256/512 length-encoding via Int64 shifts (native "lsr 32" is shift-mod-32 on js, which leaked the length byte into a higher word). NIST vectors pass native/js/wasm. - sx_cbor: write_head width selection + byte emission via Int64 (the 0x100000000 literal truncated to 0 on js, sending small ints to the 8-byte branch; and "v lsr (8*i)" with i>=4 was shift-mod-32). - sx_cid: base32_lower keeps acc bounded to the unconsumed low bits (it grew 8 bits/byte and overflowed). cid_from_sx now matches native<->js exactly. - sx_ed25519: bignum mul accumulates in Int64 (26x26=52-bit products overflow); div_small running remainder in Int64 (rem<<26 ~= 2^34). This was the boot gate — the browser kernel now boots (SxKernel live, crypto-sha256 correct on js). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -15,25 +15,29 @@ exception Cbor_error of string
|
||||
|
||||
let write_head buf major v =
|
||||
let m = major lsl 5 in
|
||||
(* Width selection + big-endian byte emission via Int64, so the web targets
|
||||
compute identically to native: on js_of_ocaml [int] is 32-bit, so the
|
||||
literal 0x100000000 (2^32) truncates to 0 (sending small values to the
|
||||
8-byte branch) and [v lsr (8*i)] with i>=4 is shift-mod-32. Int64 has the
|
||||
full 64-bit width and well-defined shifts on every target. *)
|
||||
let v64 = Int64.of_int v in
|
||||
let put_be nbytes =
|
||||
for i = nbytes - 1 downto 0 do
|
||||
Buffer.add_char buf
|
||||
(Char.chr (Int64.to_int
|
||||
(Int64.logand (Int64.shift_right_logical v64 (8 * i)) 0xFFL)))
|
||||
done
|
||||
in
|
||||
if v < 24 then
|
||||
Buffer.add_char buf (Char.chr (m lor v))
|
||||
else if v < 0x100 then begin
|
||||
Buffer.add_char buf (Char.chr (m lor 24));
|
||||
Buffer.add_char buf (Char.chr v)
|
||||
Buffer.add_char buf (Char.chr (m lor 24)); put_be 1
|
||||
end else if v < 0x10000 then begin
|
||||
Buffer.add_char buf (Char.chr (m lor 25));
|
||||
Buffer.add_char buf (Char.chr ((v lsr 8) land 0xFF));
|
||||
Buffer.add_char buf (Char.chr (v land 0xFF))
|
||||
end else if v < 0x100000000 then begin
|
||||
Buffer.add_char buf (Char.chr (m lor 26));
|
||||
for i = 3 downto 0 do
|
||||
Buffer.add_char buf (Char.chr ((v lsr (8 * i)) land 0xFF))
|
||||
done
|
||||
Buffer.add_char buf (Char.chr (m lor 25)); put_be 2
|
||||
end else if Int64.compare v64 0x100000000L < 0 then begin
|
||||
Buffer.add_char buf (Char.chr (m lor 26)); put_be 4
|
||||
end else begin
|
||||
Buffer.add_char buf (Char.chr (m lor 27));
|
||||
for i = 7 downto 0 do
|
||||
Buffer.add_char buf (Char.chr ((v lsr (8 * i)) land 0xFF))
|
||||
done
|
||||
Buffer.add_char buf (Char.chr (m lor 27)); put_be 8
|
||||
end
|
||||
|
||||
(* dag-cbor map key order: shorter key first, then bytewise. *)
|
||||
|
||||
@@ -32,7 +32,11 @@ let base32_lower (s : string) : string =
|
||||
while !bits >= 5 do
|
||||
bits := !bits - 5;
|
||||
Buffer.add_char buf b32_alpha.[(!acc lsr !bits) land 0x1f]
|
||||
done) s;
|
||||
done;
|
||||
(* Keep only the unconsumed low [bits] bits, so [acc] stays tiny (< 2^13).
|
||||
Without this it grows by 8 bits per byte and overflows native [int] on
|
||||
the 32-bit web targets, corrupting the emitted symbols. *)
|
||||
acc := !acc land ((1 lsl !bits) - 1)) s;
|
||||
if !bits > 0 then
|
||||
Buffer.add_char buf b32_alpha.[(!acc lsl (5 - !bits)) land 0x1f];
|
||||
Buffer.contents buf
|
||||
|
||||
@@ -68,15 +68,22 @@ let sub (a : bn) (b : bn) : bn =
|
||||
norm r
|
||||
|
||||
let mul (a : bn) (b : bn) : bn =
|
||||
(* Accumulate in Int64: a limb product is 26+26 = 52 bits, which overflows the
|
||||
web targets' int (32-bit js_of_ocaml / 31-bit wasm_of_ocaml). Int64 is a
|
||||
real 64-bit type on every target, so the carries are exact. *)
|
||||
let la = Array.length a and lb = Array.length b in
|
||||
let r = Array.make (la + lb) 0 in
|
||||
let maskL = Int64.of_int mask in
|
||||
for i = 0 to la - 1 do
|
||||
let carry = ref 0 in
|
||||
let carry = ref 0L in
|
||||
let ai = Int64.of_int a.(i) in
|
||||
for j = 0 to lb - 1 do
|
||||
let s = r.(i + j) + a.(i) * b.(j) + !carry in
|
||||
r.(i + j) <- s land mask; carry := s lsr bits
|
||||
let s = Int64.add (Int64.add (Int64.of_int r.(i + j))
|
||||
(Int64.mul ai (Int64.of_int b.(j)))) !carry in
|
||||
r.(i + j) <- Int64.to_int (Int64.logand s maskL);
|
||||
carry := Int64.shift_right_logical s bits
|
||||
done;
|
||||
r.(i + lb) <- r.(i + lb) + !carry
|
||||
r.(i + lb) <- r.(i + lb) + Int64.to_int !carry
|
||||
done;
|
||||
norm r
|
||||
|
||||
@@ -109,12 +116,16 @@ let bn_mod (a : bn) (m : bn) : bn =
|
||||
end
|
||||
|
||||
let div_small (a : bn) (d : int) : bn =
|
||||
(* [rem lsl bits] reaches ~2^34 (rem < d <= 256, bits = 26), past the web
|
||||
targets' int width — accumulate the running remainder in Int64. *)
|
||||
let la = Array.length a in
|
||||
let q = Array.make la 0 in
|
||||
let rem = ref 0 in
|
||||
let rem = ref 0L in
|
||||
let dL = Int64.of_int d in
|
||||
for i = la - 1 downto 0 do
|
||||
let cur = (!rem lsl bits) lor a.(i) in
|
||||
q.(i) <- cur / d; rem := cur mod d
|
||||
let cur = Int64.logor (Int64.shift_left !rem bits) (Int64.of_int a.(i)) in
|
||||
q.(i) <- Int64.to_int (Int64.div cur dL);
|
||||
rem := Int64.rem cur dL
|
||||
done;
|
||||
norm q
|
||||
|
||||
|
||||
@@ -3,37 +3,40 @@
|
||||
No C stubs, no external deps. Used by the fed-sx host primitives
|
||||
[crypto-sha256] / [crypto-sha512]. Reference: FIPS 180-4. *)
|
||||
|
||||
(* ---- SHA-256 (FIPS 180-4 §6.2). 32-bit words held in native int,
|
||||
masked to 32 bits after every arithmetic op. ---- *)
|
||||
|
||||
let mask32 = 0xFFFFFFFF
|
||||
(* ---- SHA-256 (FIPS 180-4 §6.2). 32-bit words via Int32, NOT native int.
|
||||
On the web targets the kernel is compiled by js_of_ocaml (32-bit int) and
|
||||
wasm_of_ocaml (31-bit int), where native [int] silently truncates the 32-bit
|
||||
round words — producing WRONG digests (and, downstream, bad CIDs and a
|
||||
Char.chr crash at kernel init). Int32 has well-defined wrap-around mod 2^32 on
|
||||
every target, so this matches the 63-bit native build exactly. ---- *)
|
||||
|
||||
let k256 = [|
|
||||
0x428a2f98; 0x71374491; 0xb5c0fbcf; 0xe9b5dba5;
|
||||
0x3956c25b; 0x59f111f1; 0x923f82a4; 0xab1c5ed5;
|
||||
0xd807aa98; 0x12835b01; 0x243185be; 0x550c7dc3;
|
||||
0x72be5d74; 0x80deb1fe; 0x9bdc06a7; 0xc19bf174;
|
||||
0xe49b69c1; 0xefbe4786; 0x0fc19dc6; 0x240ca1cc;
|
||||
0x2de92c6f; 0x4a7484aa; 0x5cb0a9dc; 0x76f988da;
|
||||
0x983e5152; 0xa831c66d; 0xb00327c8; 0xbf597fc7;
|
||||
0xc6e00bf3; 0xd5a79147; 0x06ca6351; 0x14292967;
|
||||
0x27b70a85; 0x2e1b2138; 0x4d2c6dfc; 0x53380d13;
|
||||
0x650a7354; 0x766a0abb; 0x81c2c92e; 0x92722c85;
|
||||
0xa2bfe8a1; 0xa81a664b; 0xc24b8b70; 0xc76c51a3;
|
||||
0xd192e819; 0xd6990624; 0xf40e3585; 0x106aa070;
|
||||
0x19a4c116; 0x1e376c08; 0x2748774c; 0x34b0bcb5;
|
||||
0x391c0cb3; 0x4ed8aa4a; 0x5b9cca4f; 0x682e6ff3;
|
||||
0x748f82ee; 0x78a5636f; 0x84c87814; 0x8cc70208;
|
||||
0x90befffa; 0xa4506ceb; 0xbef9a3f7; 0xc67178f2 |]
|
||||
0x428a2f98l; 0x71374491l; 0xb5c0fbcfl; 0xe9b5dba5l;
|
||||
0x3956c25bl; 0x59f111f1l; 0x923f82a4l; 0xab1c5ed5l;
|
||||
0xd807aa98l; 0x12835b01l; 0x243185bel; 0x550c7dc3l;
|
||||
0x72be5d74l; 0x80deb1fel; 0x9bdc06a7l; 0xc19bf174l;
|
||||
0xe49b69c1l; 0xefbe4786l; 0x0fc19dc6l; 0x240ca1ccl;
|
||||
0x2de92c6fl; 0x4a7484aal; 0x5cb0a9dcl; 0x76f988dal;
|
||||
0x983e5152l; 0xa831c66dl; 0xb00327c8l; 0xbf597fc7l;
|
||||
0xc6e00bf3l; 0xd5a79147l; 0x06ca6351l; 0x14292967l;
|
||||
0x27b70a85l; 0x2e1b2138l; 0x4d2c6dfcl; 0x53380d13l;
|
||||
0x650a7354l; 0x766a0abbl; 0x81c2c92el; 0x92722c85l;
|
||||
0xa2bfe8a1l; 0xa81a664bl; 0xc24b8b70l; 0xc76c51a3l;
|
||||
0xd192e819l; 0xd6990624l; 0xf40e3585l; 0x106aa070l;
|
||||
0x19a4c116l; 0x1e376c08l; 0x2748774cl; 0x34b0bcb5l;
|
||||
0x391c0cb3l; 0x4ed8aa4al; 0x5b9cca4fl; 0x682e6ff3l;
|
||||
0x748f82eel; 0x78a5636fl; 0x84c87814l; 0x8cc70208l;
|
||||
0x90befffal; 0xa4506cebl; 0xbef9a3f7l; 0xc67178f2l |]
|
||||
|
||||
let rotr32 x n = ((x lsr n) lor (x lsl (32 - n))) land mask32
|
||||
let rotr32 (x : int32) (n : int) : int32 =
|
||||
Int32.logor (Int32.shift_right_logical x n) (Int32.shift_left x (32 - n))
|
||||
|
||||
let sha256_hex (msg : string) : string =
|
||||
let h = [| 0x6a09e667; 0xbb67ae85; 0x3c6ef372; 0xa54ff53a;
|
||||
0x510e527f; 0x9b05688c; 0x1f83d9ab; 0x5be0cd19 |] in
|
||||
let h = [| 0x6a09e667l; 0xbb67ae85l; 0x3c6ef372l; 0xa54ff53al;
|
||||
0x510e527fl; 0x9b05688cl; 0x1f83d9abl; 0x5be0cd19l |] in
|
||||
let len = String.length msg in
|
||||
(* Padded length: multiple of 64 bytes. *)
|
||||
let bitlen = len * 8 in
|
||||
let bitlen = Int64.mul (Int64.of_int len) 8L in
|
||||
let padlen =
|
||||
let r = (len + 1) mod 64 in
|
||||
if r <= 56 then 56 - r else 120 - r
|
||||
@@ -42,60 +45,79 @@ let sha256_hex (msg : string) : string =
|
||||
let buf = Bytes.make total '\000' in
|
||||
Bytes.blit_string msg 0 buf 0 len;
|
||||
Bytes.set buf len '\x80';
|
||||
(* 64-bit big-endian bit length (we cap at OCaml int range). *)
|
||||
(* 64-bit big-endian bit length. Int64 shifts so the high bytes (shift >= 32)
|
||||
are correct on the 32-bit web targets — native int `lsr 32` is shift-mod-32
|
||||
on js_of_ocaml and would leak the low length byte into a higher word. *)
|
||||
for i = 0 to 7 do
|
||||
Bytes.set buf (total - 1 - i)
|
||||
(Char.chr ((bitlen lsr (8 * i)) land 0xFF))
|
||||
(Char.chr (Int64.to_int
|
||||
(Int64.logand (Int64.shift_right_logical bitlen (8 * i)) 0xFFL)))
|
||||
done;
|
||||
let w = Array.make 64 0 in
|
||||
let byte i = Int32.of_int (Char.code (Bytes.get buf i)) in
|
||||
let w = Array.make 64 0l in
|
||||
let nblocks = total / 64 in
|
||||
for b = 0 to nblocks - 1 do
|
||||
let base = b * 64 in
|
||||
for t = 0 to 15 do
|
||||
let o = base + t * 4 in
|
||||
w.(t) <-
|
||||
(Char.code (Bytes.get buf o) lsl 24)
|
||||
lor (Char.code (Bytes.get buf (o + 1)) lsl 16)
|
||||
lor (Char.code (Bytes.get buf (o + 2)) lsl 8)
|
||||
lor (Char.code (Bytes.get buf (o + 3)))
|
||||
Int32.logor
|
||||
(Int32.logor
|
||||
(Int32.shift_left (byte o) 24)
|
||||
(Int32.shift_left (byte (o + 1)) 16))
|
||||
(Int32.logor
|
||||
(Int32.shift_left (byte (o + 2)) 8)
|
||||
(byte (o + 3)))
|
||||
done;
|
||||
for t = 16 to 63 do
|
||||
let s0 =
|
||||
(rotr32 w.(t - 15) 7) lxor (rotr32 w.(t - 15) 18)
|
||||
lxor (w.(t - 15) lsr 3) in
|
||||
Int32.logxor
|
||||
(Int32.logxor (rotr32 w.(t - 15) 7) (rotr32 w.(t - 15) 18))
|
||||
(Int32.shift_right_logical w.(t - 15) 3) in
|
||||
let s1 =
|
||||
(rotr32 w.(t - 2) 17) lxor (rotr32 w.(t - 2) 19)
|
||||
lxor (w.(t - 2) lsr 10) in
|
||||
w.(t) <- (w.(t - 16) + s0 + w.(t - 7) + s1) land mask32
|
||||
Int32.logxor
|
||||
(Int32.logxor (rotr32 w.(t - 2) 17) (rotr32 w.(t - 2) 19))
|
||||
(Int32.shift_right_logical w.(t - 2) 10) in
|
||||
w.(t) <-
|
||||
Int32.add (Int32.add w.(t - 16) s0) (Int32.add w.(t - 7) s1)
|
||||
done;
|
||||
let a = ref h.(0) and bb = ref h.(1) and c = ref h.(2)
|
||||
and d = ref h.(3) and e = ref h.(4) and f = ref h.(5)
|
||||
and g = ref h.(6) and hh = ref h.(7) in
|
||||
for t = 0 to 63 do
|
||||
let s1 =
|
||||
(rotr32 !e 6) lxor (rotr32 !e 11) lxor (rotr32 !e 25) in
|
||||
let ch = (!e land !f) lxor ((lnot !e land mask32) land !g) in
|
||||
let t1 = (!hh + s1 + ch + k256.(t) + w.(t)) land mask32 in
|
||||
Int32.logxor
|
||||
(Int32.logxor (rotr32 !e 6) (rotr32 !e 11)) (rotr32 !e 25) in
|
||||
let ch =
|
||||
Int32.logxor (Int32.logand !e !f)
|
||||
(Int32.logand (Int32.lognot !e) !g) in
|
||||
let t1 =
|
||||
Int32.add
|
||||
(Int32.add (Int32.add !hh s1) (Int32.add ch k256.(t))) w.(t) in
|
||||
let s0 =
|
||||
(rotr32 !a 2) lxor (rotr32 !a 13) lxor (rotr32 !a 22) in
|
||||
let maj = (!a land !bb) lxor (!a land !c) lxor (!bb land !c) in
|
||||
let t2 = (s0 + maj) land mask32 in
|
||||
Int32.logxor
|
||||
(Int32.logxor (rotr32 !a 2) (rotr32 !a 13)) (rotr32 !a 22) in
|
||||
let maj =
|
||||
Int32.logxor
|
||||
(Int32.logxor (Int32.logand !a !bb) (Int32.logand !a !c))
|
||||
(Int32.logand !bb !c) in
|
||||
let t2 = Int32.add s0 maj in
|
||||
hh := !g; g := !f; f := !e;
|
||||
e := (!d + t1) land mask32;
|
||||
e := Int32.add !d t1;
|
||||
d := !c; c := !bb; bb := !a;
|
||||
a := (t1 + t2) land mask32
|
||||
a := Int32.add t1 t2
|
||||
done;
|
||||
h.(0) <- (h.(0) + !a) land mask32;
|
||||
h.(1) <- (h.(1) + !bb) land mask32;
|
||||
h.(2) <- (h.(2) + !c) land mask32;
|
||||
h.(3) <- (h.(3) + !d) land mask32;
|
||||
h.(4) <- (h.(4) + !e) land mask32;
|
||||
h.(5) <- (h.(5) + !f) land mask32;
|
||||
h.(6) <- (h.(6) + !g) land mask32;
|
||||
h.(7) <- (h.(7) + !hh) land mask32
|
||||
h.(0) <- Int32.add h.(0) !a;
|
||||
h.(1) <- Int32.add h.(1) !bb;
|
||||
h.(2) <- Int32.add h.(2) !c;
|
||||
h.(3) <- Int32.add h.(3) !d;
|
||||
h.(4) <- Int32.add h.(4) !e;
|
||||
h.(5) <- Int32.add h.(5) !f;
|
||||
h.(6) <- Int32.add h.(6) !g;
|
||||
h.(7) <- Int32.add h.(7) !hh
|
||||
done;
|
||||
let out = Buffer.create 64 in
|
||||
Array.iter (fun x -> Buffer.add_string out (Printf.sprintf "%08x" x)) h;
|
||||
Array.iter (fun x -> Buffer.add_string out (Printf.sprintf "%08lx" x)) h;
|
||||
Buffer.contents out
|
||||
|
||||
(* ---- SHA-512 (FIPS 180-4 §6.4). 64-bit words via Int64.
|
||||
@@ -146,7 +168,7 @@ let sha512_hex (msg : string) : string =
|
||||
0x510e527fade682d1L; 0x9b05688c2b3e6c1fL;
|
||||
0x1f83d9abfb41bd6bL; 0x5be0cd19137e2179L |] in
|
||||
let len = String.length msg in
|
||||
let bitlen = len * 8 in
|
||||
let bitlen = Int64.mul (Int64.of_int len) 8L in
|
||||
(* Pad to a multiple of 128 bytes; 16-byte big-endian length. *)
|
||||
let padlen =
|
||||
let r = (len + 1) mod 128 in
|
||||
@@ -156,9 +178,12 @@ let sha512_hex (msg : string) : string =
|
||||
let buf = Bytes.make total '\000' in
|
||||
Bytes.blit_string msg 0 buf 0 len;
|
||||
Bytes.set buf len '\x80';
|
||||
(* Low 64 bits of the bit length (high 64 stay 0). Int64 shifts so the bytes
|
||||
at shift >= 32 are correct on the 32-bit web targets (js shift-mod-32). *)
|
||||
for i = 0 to 7 do
|
||||
Bytes.set buf (total - 1 - i)
|
||||
(Char.chr ((bitlen lsr (8 * i)) land 0xFF))
|
||||
(Char.chr (Int64.to_int
|
||||
(Int64.logand (Int64.shift_right_logical bitlen (8 * i)) 0xFFL)))
|
||||
done;
|
||||
let w = Array.make 80 0L in
|
||||
let nblocks = total / 128 in
|
||||
|
||||
Reference in New Issue
Block a user