Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelog/unreleased/1606.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- `crypto/sha2`: `Sha2` publishes `hashBytes` and `blockBytes`, so consumers
sizing byte buffers read them instead of converting bit lengths themselves
4 changes: 2 additions & 2 deletions fjs/crypto/hmac/module.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ const iPad = vec8(0x36n)
* that takes a message and computes the HMAC.
*/
export const hmac = hashFunc => {
const { blockLength } = hashFunc
const p = repeat(blockLength >> 3n)
const { blockLength, blockBytes } = hashFunc
const p = repeat(blockBytes)
const ip = p(iPad)
const op = p(oPad)
const c = computeSync(hashFunc)
Expand Down
4 changes: 3 additions & 1 deletion fjs/crypto/sha2/module.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* @import { Base, Sha2, State, V16, V8 } from './types.ts'
*/

import { mask } from '../../types/bigint/module.f.mjs'
import { divUp8, mask } from '../../types/bigint/module.f.mjs'
import {
vec,
length,
Expand Down Expand Up @@ -257,6 +257,8 @@ const base = ({ logBitLen, k, bs0, bs1, ss0, ss1 }) => {
const sha2 = ({ append, end, chunkLength }, hash, hashLength) => ({
hashLength,
blockLength: chunkLength,
hashBytes: divUp8(hashLength),
blockBytes: divUp8(chunkLength),
init: {
hash,
len: 0n,
Expand Down
24 changes: 24 additions & 0 deletions fjs/crypto/sha2/proof.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ import { assertEq } from '../../asserts/module.f.mjs'
import { map } from '../../types/list/module.f.mjs'
import { base32, base64, computeSync, sha224, sha256, sha384, sha512, sha512x224, sha512x256 } from './module.f.mjs'

/**
* Every SHA-2 length is a whole number of bytes, so the rounded-up byte count
* and the exact division agree — this pins both that identity and the values.
*
* @type {(sha2: Sha2) => (hashBytes: bigint, blockBytes: bigint) => void}
*/
const checkBytes = ({ hashLength, blockLength, hashBytes, blockBytes }) => (h, b) => {
assertEq(hashBytes, h)
assertEq(blockBytes, b)
assertEq(hashBytes, hashLength >> 3n)
assertEq(blockBytes, blockLength >> 3n)
}

/** @type {(sha2: Sha2) => (x: bigint) => void} */
const checkEmpty = ({ init, end, hashLength }) => x => {
const result = end(init)
Expand Down Expand Up @@ -71,6 +84,17 @@ export const proof = {
sha512x256: () => checkEmpty(sha512x256)(0xc672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967an),
sha512x224: () => checkEmpty(sha512x224)(0x6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4n),
},
// The byte counts consumers read instead of converting bit lengths
// themselves. Asserted against the bit length each is derived from, not
// against a hand-written number, so the pairing is what is pinned.
byteLengths: [
() => checkBytes(sha224)(28n, 64n),
() => checkBytes(sha256)(32n, 64n),
() => checkBytes(sha384)(48n, 128n),
() => checkBytes(sha512)(64n, 128n),
() => checkBytes(sha512x224)(28n, 128n),
() => checkBytes(sha512x256)(32n, 128n),
],
utf8: [
() => {
const e = 0x730e109bd7a8a32b1cb9d9a09aa2325d2430587ddbc0c38bad911525n
Expand Down
55 changes: 0 additions & 55 deletions fjs/crypto/sha2/todo/byte-length-fields.md

This file was deleted.

11 changes: 11 additions & 0 deletions fjs/crypto/sha2/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ export type Base = {
export type Sha2 = {
readonly hashLength: bigint
readonly blockLength: bigint
/**
* `hashLength` and `blockLength` in whole bytes, rounded up. Consumers
* that size a byte buffer read these instead of converting: `hmac` and
* `sign` each used to do it themselves, with two different spellings
* (`>> 3n` and `divUp8`), leaving a reader to work out per site whether
* the two roundings agree. They do for every SHA-2 variant, whose lengths
* are byte multiples — which is exactly why the decision belongs here
* once rather than at each call site.
*/
readonly hashBytes: bigint
readonly blockBytes: bigint
readonly init: State
readonly append: Fold<Vec, State>
readonly end: (state: State) => Vec
Expand Down
4 changes: 2 additions & 2 deletions fjs/crypto/sign/module.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*/

import { assertNotNullish } from '../../asserts/module.f.mjs'
import { bitLength, divUp8, roundUp8 } from '../../types/bigint/module.f.mjs'
import { bitLength, roundUp8 } from '../../types/bigint/module.f.mjs'
import { empty, length, msb, repeat, unpack, vec, vec8 } from '../../types/bit_vec/module.f.mjs'
import { hmac } from '../hmac/module.f.mjs'
import { computeSync } from '../sha2/module.f.mjs'
Expand Down Expand Up @@ -75,7 +75,7 @@ export const computeK =
// step and all subsequent steps, we use the same H function as the
// one used in step 'a' to process the input message; this choice
// will be discussed in more detail in Section 3.6.
const rep = repeat(divUp8(hf.hashLength))
const rep = repeat(hf.hashBytes)
const v0 = rep(x01)
// c. Set:
// K = 0x00 0x00 0x00 ... 0x00
Expand Down
Loading