-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbody.ts
More file actions
46 lines (41 loc) · 1.69 KB
/
Copy pathbody.ts
File metadata and controls
46 lines (41 loc) · 1.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* Body canonicalization and content_hash — Verifiable OKF SPEC §5.
*
* The canonical body is defined deterministically so that signer and verifier
* always agree byte-for-byte. No markdown-semantic normalization is performed
* in v0 (no link/heading/whitespace collapsing inside lines).
*/
import { createHash } from "node:crypto";
const utf8Encoder = new TextEncoder();
/** Decode bytes as UTF-8, rejecting invalid UTF-8 (SPEC §5 step 2). */
export function decodeUtf8(bytes: Uint8Array): string {
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
}
/**
* Canonicalize an (already UTF-8-decoded) body string into canonical bytes.
* Steps 3–6 of SPEC §5:
* 3. normalize CRLF / CR → LF
* 4. strip trailing spaces/tabs at the end of each line
* 5. ensure exactly one trailing LF
* 6. re-encode UTF-8
*/
export function canonicalizeBody(body: string): Uint8Array {
const lf = body.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); // step 3
const stripped = lf
.split("\n")
.map((line) => line.replace(/[ \t]+$/u, "")) // step 4
.join("\n");
const oneTrailingLf = stripped.replace(/\n+$/u, "") + "\n"; // step 5
return utf8Encoder.encode(oneTrailingLf); // step 6
}
/**
* Canonicalize body bytes directly: validates UTF-8 (§5 step 2) then applies
* steps 3–6. Throws `TypeError` on invalid UTF-8.
*/
export function canonicalizeBodyBytes(bodyBytes: Uint8Array): Uint8Array {
return canonicalizeBody(decodeUtf8(bodyBytes));
}
/** content_hash over canonical body bytes: "sha256:" + lowercase hex SHA-256 (§5). */
export function contentHash(canonicalBody: Uint8Array): string {
return "sha256:" + createHash("sha256").update(canonicalBody).digest("hex");
}