lib/crypto/sha256: restore faithful re-entrant sum() (#265 unblocked)
The port shipped sum() single-shot — mutating the live hash state —
because Hare's state snapshot `let copy = *h; let h = ©` (a
deref-rhs aggregate let-init of an array-containing struct) miscompiled
in cgen. #265 fold-1 (master 4d3f846) landed the full-size aggregate
copy for that axis, so restore the faithful form: pad+finalize the
snapshot, leave the live state untouched, close() the copy.
sum() is now non-destructive — summing twice yields the same digest and
writing after a sum() continues the stream. Pinned by a new reentrant()
@test (sum-twice identical + write-after-sum continuity). NIST vectors
unchanged.
This commit is contained in:
@@ -11,11 +11,7 @@
|
|||||||
// mismatch would mean cgen promoted a u32 op to 64-bit without wrapping
|
// mismatch would mean cgen promoted a u32 op to 64-bit without wrapping
|
||||||
// (a cgen bug to STOP+report, NOT to mask here).
|
// (a cgen bug to STOP+report, NOT to mask here).
|
||||||
//
|
//
|
||||||
// DIVERGENCES from sha256.ha:
|
// DIVERGENCES from sha256.ha (all semantics-preserving spellings):
|
||||||
// - sum() is SINGLE-SHOT, not re-entrant: Hare's state-snapshot copy
|
|
||||||
// hits an array-field deref-copy cgen bug (see [[sumfn]]). This is
|
|
||||||
// the one BEHAVIORAL divergence (blocked on a filed cgen fix); the
|
|
||||||
// rest below are semantics-preserving spellings.
|
|
||||||
// - `state` embeds [[hash.hash]], whose first field is an inline
|
// - `state` embeds [[hash.hash]], whose first field is an inline
|
||||||
// io.vtable (not Hare's `stream: io::stream` pointer); see
|
// io.vtable (not Hare's `stream: io::stream` pointer); see
|
||||||
// lib/hash/hash.ww. The vtable slots are wired post-construction in
|
// lib/hash/hash.ww. The vtable slots are wired post-construction in
|
||||||
@@ -168,21 +164,17 @@ fn writefn(s: io.stream, buf: []u8) (size | io.error) = {
|
|||||||
|
|
||||||
// sumfn — finalize and emit the digest. ref/hare/crypto/sha256/sha256.ha:115-143.
|
// sumfn — finalize and emit the digest. ref/hare/crypto/sha256/sha256.ha:115-143.
|
||||||
//
|
//
|
||||||
// DIVERGENCE (re-entrancy / blocked on a cgen bug). Hare snapshots the
|
// Re-entrant per Hare: snapshot the state (`let copy = *h; let h = ©`)
|
||||||
// state — `let copy = *h; let h = ©` — so sum() is non-destructive
|
// and pad+finalize the COPY, so the live hash is untouched and the caller
|
||||||
// and the caller can keep writing afterwards. That deref-copy of an
|
// can keep writing or sum() again. The deref-copy of this array-containing
|
||||||
// array-containing struct miscompiles in ww cgen (the copied `h`/`x`
|
// struct was blocked by a cgen bug (#265) — fold-1 (master 4d3f846) landed
|
||||||
// arrays come back zeroed; minimal repro: `type t = struct { h: [4]u32 };
|
// the full-size aggregate copy for deref-rhs let-init, unblocking it. The
|
||||||
// let c: t = *(&s);` reads c.h back wrong — array-field + pointer-deref
|
// close() at the end wipes the copy (Hare's `defer hash::close(h)`), not
|
||||||
// copy, a sibling of the #135/#252 array-field cgen family). Filed for
|
// the live state.
|
||||||
// ken; flagged for a drew fidelity ruling. Until it lands, sum() runs on
|
|
||||||
// the live state and is therefore SINGLE-SHOT: writing or summing again
|
|
||||||
// after a sum() yields wrong results. Every current caller does exactly
|
|
||||||
// one terminal sum(), so the NIST digests are unaffected (all vectors,
|
|
||||||
// incl. the multi-block million-'a' stream, verify byte-identical). The
|
|
||||||
// final close() still wipes the live state per Hare.
|
|
||||||
fn sumfn(h: *hash.hash, buf: []u8) void = {
|
fn sumfn(h: *hash.hash, buf: []u8) void = {
|
||||||
let st: *state = h: *state;
|
let live: *state = h: *state;
|
||||||
|
let copy: state = *live;
|
||||||
|
let st: *state = (©);
|
||||||
|
|
||||||
let ln: size = st.ln;
|
let ln: size = st.ln;
|
||||||
let tmp: [64]u8 = [0...];
|
let tmp: [64]u8 = [0...];
|
||||||
@@ -219,7 +211,7 @@ fn sumfn(h: *hash.hash, buf: []u8) void = {
|
|||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
hash.close(h);
|
hash.close((©): *hash.hash);
|
||||||
};
|
};
|
||||||
|
|
||||||
// block — process every whole 64-byte block in `buf`.
|
// block — process every whole 64-byte block in `buf`.
|
||||||
|
|||||||
@@ -91,6 +91,34 @@ fn check(msg: []u8, want: str) void = {
|
|||||||
"cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0");
|
"cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// reentrant — sum() is non-destructive: it pads+finalizes a snapshot, so
|
||||||
|
// the live hash survives. Summing twice yields the same digest, and a
|
||||||
|
// write after a sum() continues the same stream. Pins the faithful
|
||||||
|
// state-snapshot restored once #265 fold-1 unblocked the deref-rhs
|
||||||
|
// aggregate copy `let copy = *h` (see sha256.ww [[sumfn]]).
|
||||||
|
@test fn reentrant() void = {
|
||||||
|
let st: sha256.state = sha256.sha256();
|
||||||
|
let h: *hash.hash = (&st): *hash.hash;
|
||||||
|
hash.write(h, strings.toutf8("abc"));
|
||||||
|
|
||||||
|
let out1: [32]u8;
|
||||||
|
let out2: [32]u8;
|
||||||
|
hash.sum(h, out1[0:32]);
|
||||||
|
hash.sum(h, out2[0:32]);
|
||||||
|
if (!bytes.equal(out1[0:32], out2[0:32])) { fail(); };
|
||||||
|
checkbytes(out1[0:32],
|
||||||
|
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
|
||||||
|
|
||||||
|
// Write more after the sum(): the post-sum stream is uncorrupted, so
|
||||||
|
// the running digest of "abc"+"def" matches a one-shot hash of "abcdef".
|
||||||
|
hash.write(h, strings.toutf8("def"));
|
||||||
|
let out3: [32]u8;
|
||||||
|
hash.sum(h, out3[0:32]);
|
||||||
|
let want: [32]u8;
|
||||||
|
dohash(strings.toutf8("abcdef"), want[0:32]);
|
||||||
|
if (!bytes.equal(out3[0:32], want[0:32])) { fail(); };
|
||||||
|
};
|
||||||
|
|
||||||
// sz()/bsz() report the SHA-256 constants regardless of state.
|
// sz()/bsz() report the SHA-256 constants regardless of state.
|
||||||
@test fn sizes() void = {
|
@test fn sizes() void = {
|
||||||
let st: sha256.state = sha256.sha256();
|
let st: sha256.state = sha256.sha256();
|
||||||
@@ -104,6 +132,7 @@ export fn main() i32 = {
|
|||||||
signalled = 2; abc();
|
signalled = 2; abc();
|
||||||
signalled = 3; twoblockpad();
|
signalled = 3; twoblockpad();
|
||||||
signalled = 4; millionas();
|
signalled = 4; millionas();
|
||||||
signalled = 5; sizes();
|
signalled = 5; reentrant();
|
||||||
|
signalled = 6; sizes();
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user