anthropic-manager: seal the refresh token to the node key, do no crypto to open
The manager module drops its bespoke ECIES at-rest envelope and the node-private-key mount.
A module is never given a node's private key, so it cannot open an envelope -- the refresh
token is now delivered to it as cleartext by the host, unsealed from an ordinary sealed box.
- sealedbox.ts: a dependency-free NaCl crypto_box_seal (node:crypto for X25519, transcribed
XSalsa20-Poly1305 and BLAKE2b-24), byte-compatible with Go's box.SealAnonymous. It SEALS
only -- opening is the host's job. Proven by a cross-language test in mesh-control.
- adopt: reads the node's PUBLIC key from the delivered bound facts and seals the operator's
refresh token to it, handing out only the box.
- refresh: reads the refresh token as cleartext the host mounted, calls the vendor, re-seals
a rotated token to the node's public key, submits only { access token, box }.
- module.json: a model-access holder now -- binds the facts, binds the refresh token as a
sealed secret; no keys dir, no MESH_NODE_SEALING_* mount.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
@@ -0,0 +1,480 @@
|
||||
// A NaCl `crypto_box_seal`, in TypeScript, byte-compatible with Go's `box.SealAnonymous`.
|
||||
//
|
||||
// **Why this file exists, and why it is exactly this.** novox/hq ADR 0050's refreshable-grant
|
||||
// carve-out delivers the refresh token to the manager module the way the mesh delivers every other
|
||||
// credential: sealed to the node's key, and unsealed by the *host* — never by the module. The host
|
||||
// unseals with Go's `golang.org/x/crypto/nacl/box.OpenAnonymous` (mesh-host
|
||||
// internal/identity/sealing.go), and mesh-control seals with `box.SealAnonymous`
|
||||
// (mesh-control internal/secrets/seal.go). Both are NaCl `crypto_box_seal`:
|
||||
//
|
||||
// sealed = ephemeralPub(32) ‖ crypto_box(msg, nonce, recipientPub, ephemeralSecret)
|
||||
// nonce = blake2b( ephemeralPub ‖ recipientPub , 24 bytes, unkeyed )
|
||||
//
|
||||
// When the vendor rotates the refresh token, the manager module must store the new one back the
|
||||
// same way — sealed to the manager node's own sealing key — so mesh-control keeps it without ever
|
||||
// reading it and the host can later unseal it to deliver the cleartext again. That reseal happens
|
||||
// here, on the manager node, in TypeScript. It therefore has to produce the *identical* byte format
|
||||
// Go's `Open` accepts, or the host would refuse the delivery.
|
||||
//
|
||||
// **Dependency-free on purpose.** The module runtime image carries only mesh-tools' node_modules
|
||||
// (novox/hq ADR 0052), so a module cannot pull `tweetnacl` at runtime. node:crypto gives X25519 but
|
||||
// not XSalsa20-Poly1305 or a 24-byte BLAKE2b, so the `crypto_box` and the nonce hash are transcribed
|
||||
// here from the public-domain TweetNaCl (Chestnykh/Mandiri, 2014) and blakejs (dcposch, RFC 7693).
|
||||
// X25519 (ephemeral key generation and the Diffie-Hellman) is left to node:crypto, which is
|
||||
// standards-conformant and interoperates with Go's curve25519 regardless of who generated a key.
|
||||
//
|
||||
// **How it is kept honest.** A cross-language test seals a fixture here and opens it in Go
|
||||
// (mesh-control internal/secrets/sealedbox_xcheck_test.go), and this module's own test round-trips
|
||||
// it against a second decrypt. A transcription slip surfaces there as a seal Go cannot open, not as
|
||||
// a refresh token silently mangled in production.
|
||||
//
|
||||
// This module SEALS only. It never opens — opening is the host's job, with the node private key the
|
||||
// module is deliberately never given.
|
||||
|
||||
import {
|
||||
createPublicKey,
|
||||
diffieHellman,
|
||||
generateKeyPairSync,
|
||||
type KeyObject,
|
||||
} from "node:crypto";
|
||||
|
||||
const RAW_KEY_LEN = 32;
|
||||
// "expand 32-byte k", the Salsa20 constant.
|
||||
const SIGMA = new Uint8Array([
|
||||
101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98, 121, 116, 101, 32, 107,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Seal a value to a node's public sealing key, producing what Go's `box.OpenAnonymous` opens.
|
||||
*
|
||||
* @param value the plaintext (e.g. a rotated refresh token)
|
||||
* @param recipientPublicB64 the node's raw 32-byte X25519 public key, standard base64
|
||||
* @returns standard-base64( ephemeralPub ‖ box )
|
||||
*/
|
||||
export function seal(value: Uint8Array, recipientPublicB64: string): string {
|
||||
const recipientPub = Buffer.from(recipientPublicB64, "base64");
|
||||
if (recipientPub.length !== RAW_KEY_LEN) {
|
||||
throw new Error(`a sealing public key is 32 bytes, not ${recipientPub.length}`);
|
||||
}
|
||||
const recipientKey = importRawX25519Public(recipientPub);
|
||||
|
||||
// Ephemeral-static ECDH: a throwaway X25519 key pair whose public half rides in front, and the
|
||||
// raw Diffie-Hellman point shared with the recipient. node:crypto does both.
|
||||
const eph = generateKeyPairSync("x25519");
|
||||
const ephemeralPub = rawX25519Public(eph.publicKey);
|
||||
const dh = new Uint8Array(diffieHellman({ privateKey: eph.privateKey, publicKey: recipientKey }));
|
||||
|
||||
// The crypto_box shared key is HSalsa20 of the DH point (crypto_box_beforenm).
|
||||
const boxKey = new Uint8Array(32);
|
||||
cryptoCoreHsalsa20(boxKey, new Uint8Array(16), dh, SIGMA);
|
||||
|
||||
// nonce = blake2b(ephemeralPub ‖ recipientPub, 24), unkeyed — exactly Go's sealNonce.
|
||||
const nonce = blake2b24(concat(ephemeralPub, recipientPub));
|
||||
|
||||
const boxed = cryptoBox(value, nonce, boxKey);
|
||||
|
||||
return Buffer.from(concat(ephemeralPub, boxed)).toString("base64");
|
||||
}
|
||||
|
||||
// --- X25519 via node:crypto ------------------------------------------------------------------
|
||||
|
||||
function importRawX25519Public(raw: Uint8Array): KeyObject {
|
||||
return createPublicKey({
|
||||
key: { kty: "OKP", crv: "X25519", x: Buffer.from(raw).toString("base64url") },
|
||||
format: "jwk",
|
||||
});
|
||||
}
|
||||
|
||||
function rawX25519Public(key: KeyObject): Uint8Array {
|
||||
const jwk = key.export({ format: "jwk" }) as { x?: string };
|
||||
if (!jwk.x) throw new Error("a public key had no point");
|
||||
return new Uint8Array(Buffer.from(jwk.x, "base64url"));
|
||||
}
|
||||
|
||||
// --- crypto_box / crypto_secretbox (XSalsa20-Poly1305) ---------------------------------------
|
||||
//
|
||||
// Transcribed from TweetNaCl (public domain). crypto_box after the DH/HSalsa20 above is exactly
|
||||
// crypto_secretbox: XSalsa20 keystream XOR, then a Poly1305 tag over the ciphertext.
|
||||
|
||||
/** crypto_box_afternm: secretbox(msg, nonce, key), returning tag(16) ‖ ciphertext. */
|
||||
function cryptoBox(msg: Uint8Array, nonce: Uint8Array, key: Uint8Array): Uint8Array {
|
||||
// secretbox operates on a 32-byte-zero-prefixed message; its output's first 16 bytes are zero,
|
||||
// and the useful box is everything from byte 16 (the Poly1305 tag, then the ciphertext).
|
||||
const m = new Uint8Array(32 + msg.length);
|
||||
m.set(msg, 32);
|
||||
const c = new Uint8Array(m.length);
|
||||
cryptoSecretbox(c, m, m.length, nonce, key);
|
||||
return c.subarray(16);
|
||||
}
|
||||
|
||||
function cryptoSecretbox(
|
||||
c: Uint8Array,
|
||||
m: Uint8Array,
|
||||
d: number,
|
||||
n: Uint8Array,
|
||||
k: Uint8Array,
|
||||
): void {
|
||||
if (d < 32) throw new Error("secretbox message underflow");
|
||||
cryptoStreamXor(c, 0, m, 0, d, n, k);
|
||||
cryptoOnetimeauth(c, 16, c, 32, d - 32, c);
|
||||
for (let i = 0; i < 16; i++) c[i] = 0;
|
||||
}
|
||||
|
||||
function L32(x: number, c: number): number {
|
||||
return (x << c) | (x >>> (32 - c));
|
||||
}
|
||||
|
||||
function ld32(x: Uint8Array, i: number): number {
|
||||
let u = x[i + 3] & 0xff;
|
||||
u = (u << 8) | (x[i + 2] & 0xff);
|
||||
u = (u << 8) | (x[i + 1] & 0xff);
|
||||
return (u << 8) | (x[i + 0] & 0xff);
|
||||
}
|
||||
|
||||
function st32(x: Uint8Array, j: number, u: number): void {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
x[j + i] = u & 255;
|
||||
u >>>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
function core(out: Uint8Array, inp: Uint8Array, k: Uint8Array, c: Uint8Array, h: boolean): void {
|
||||
const w = new Uint32Array(16);
|
||||
const x = new Uint32Array(16);
|
||||
const y = new Uint32Array(16);
|
||||
const t = new Uint32Array(4);
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
x[5 * i] = ld32(c, 4 * i);
|
||||
x[1 + i] = ld32(k, 4 * i);
|
||||
x[6 + i] = ld32(inp, 4 * i);
|
||||
x[11 + i] = ld32(k, 16 + 4 * i);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 16; i++) y[i] = x[i];
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
for (let j = 0; j < 4; j++) {
|
||||
for (let m = 0; m < 4; m++) t[m] = x[(5 * j + 4 * m) % 16];
|
||||
t[1] ^= L32((t[0] + t[3]) | 0, 7);
|
||||
t[2] ^= L32((t[1] + t[0]) | 0, 9);
|
||||
t[3] ^= L32((t[2] + t[1]) | 0, 13);
|
||||
t[0] ^= L32((t[3] + t[2]) | 0, 18);
|
||||
for (let m = 0; m < 4; m++) w[4 * j + ((j + m) % 4)] = t[m];
|
||||
}
|
||||
for (let m = 0; m < 16; m++) x[m] = w[m];
|
||||
}
|
||||
|
||||
if (h) {
|
||||
for (let i = 0; i < 16; i++) x[i] = (x[i] + y[i]) | 0;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
x[5 * i] = (x[5 * i] - ld32(c, 4 * i)) | 0;
|
||||
x[6 + i] = (x[6 + i] - ld32(inp, 4 * i)) | 0;
|
||||
}
|
||||
for (let i = 0; i < 4; i++) {
|
||||
st32(out, 4 * i, x[5 * i]);
|
||||
st32(out, 16 + 4 * i, x[6 + i]);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < 16; i++) st32(out, 4 * i, (x[i] + y[i]) | 0);
|
||||
}
|
||||
}
|
||||
|
||||
function cryptoCoreHsalsa20(out: Uint8Array, inp: Uint8Array, k: Uint8Array, c: Uint8Array): void {
|
||||
core(out, inp, k, c, true);
|
||||
}
|
||||
|
||||
function cryptoStreamSalsa20Xor(
|
||||
c: Uint8Array,
|
||||
cpos: number,
|
||||
m: Uint8Array,
|
||||
mpos: number,
|
||||
b: number,
|
||||
n: Uint8Array,
|
||||
k: Uint8Array,
|
||||
): void {
|
||||
const z = new Uint8Array(16);
|
||||
const x = new Uint8Array(64);
|
||||
if (!b) return;
|
||||
for (let i = 0; i < 8; i++) z[i] = n[i];
|
||||
while (b >= 64) {
|
||||
coreSalsa20(x, z, k, SIGMA);
|
||||
for (let i = 0; i < 64; i++) c[cpos + i] = m[mpos + i] ^ x[i];
|
||||
let u = 1;
|
||||
for (let i = 8; i < 16; i++) {
|
||||
u = (u + (z[i] & 0xff)) | 0;
|
||||
z[i] = u & 0xff;
|
||||
u >>>= 8;
|
||||
}
|
||||
b -= 64;
|
||||
cpos += 64;
|
||||
mpos += 64;
|
||||
}
|
||||
if (b > 0) {
|
||||
coreSalsa20(x, z, k, SIGMA);
|
||||
for (let i = 0; i < b; i++) c[cpos + i] = m[mpos + i] ^ x[i];
|
||||
}
|
||||
}
|
||||
|
||||
function coreSalsa20(out: Uint8Array, inp: Uint8Array, k: Uint8Array, c: Uint8Array): void {
|
||||
core(out, inp, k, c, false);
|
||||
}
|
||||
|
||||
function cryptoStreamXor(
|
||||
c: Uint8Array,
|
||||
cpos: number,
|
||||
m: Uint8Array,
|
||||
mpos: number,
|
||||
d: number,
|
||||
n: Uint8Array,
|
||||
k: Uint8Array,
|
||||
): void {
|
||||
const s = new Uint8Array(32);
|
||||
cryptoCoreHsalsa20(s, n, k, SIGMA);
|
||||
cryptoStreamSalsa20Xor(c, cpos, m, mpos, d, n.subarray(16), s);
|
||||
}
|
||||
|
||||
const MINUSP = new Uint32Array([5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252]);
|
||||
|
||||
function add1305(h: Uint32Array, c: Uint32Array): void {
|
||||
let u = 0;
|
||||
for (let j = 0; j < 17; j++) {
|
||||
u = (u + ((h[j] + c[j]) | 0)) | 0;
|
||||
h[j] = u & 255;
|
||||
u >>>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
function cryptoOnetimeauth(
|
||||
out: Uint8Array,
|
||||
outpos: number,
|
||||
m: Uint8Array,
|
||||
mpos: number,
|
||||
n: number,
|
||||
k: Uint8Array,
|
||||
): void {
|
||||
const x = new Uint32Array(17);
|
||||
const r = new Uint32Array(17);
|
||||
const h = new Uint32Array(17);
|
||||
const c = new Uint32Array(17);
|
||||
const g = new Uint32Array(17);
|
||||
for (let j = 0; j < 16; j++) r[j] = k[j];
|
||||
r[3] &= 15;
|
||||
r[4] &= 252;
|
||||
r[7] &= 15;
|
||||
r[8] &= 252;
|
||||
r[11] &= 15;
|
||||
r[12] &= 252;
|
||||
r[15] &= 15;
|
||||
|
||||
let j: number;
|
||||
while (n > 0) {
|
||||
for (j = 0; j < 17; j++) c[j] = 0;
|
||||
for (j = 0; j < 16 && j < n; ++j) c[j] = m[mpos + j];
|
||||
c[j] = 1;
|
||||
mpos += j;
|
||||
n -= j;
|
||||
add1305(h, c);
|
||||
for (let i = 0; i < 17; i++) {
|
||||
x[i] = 0;
|
||||
for (j = 0; j < 17; j++) {
|
||||
x[i] =
|
||||
(x[i] + (h[j] * (j <= i ? r[i - j] : (320 * r[i + 17 - j]) | 0)) | 0) | 0;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < 17; i++) h[i] = x[i];
|
||||
let u = 0;
|
||||
for (j = 0; j < 16; j++) {
|
||||
u = (u + h[j]) | 0;
|
||||
h[j] = u & 255;
|
||||
u >>>= 8;
|
||||
}
|
||||
u = (u + h[16]) | 0;
|
||||
h[16] = u & 3;
|
||||
u = (5 * (u >>> 2)) | 0;
|
||||
for (j = 0; j < 16; j++) {
|
||||
u = (u + h[j]) | 0;
|
||||
h[j] = u & 255;
|
||||
u >>>= 8;
|
||||
}
|
||||
u = (u + h[16]) | 0;
|
||||
h[16] = u;
|
||||
}
|
||||
|
||||
for (j = 0; j < 17; j++) g[j] = h[j];
|
||||
add1305(h, MINUSP);
|
||||
const s = -(h[16] >>> 7) | 0;
|
||||
for (j = 0; j < 17; j++) h[j] ^= s & (g[j] ^ h[j]);
|
||||
|
||||
for (j = 0; j < 16; j++) c[j] = k[j + 16];
|
||||
c[16] = 0;
|
||||
add1305(h, c);
|
||||
for (j = 0; j < 16; j++) out[outpos + j] = h[j];
|
||||
}
|
||||
|
||||
// --- BLAKE2b (24-byte, unkeyed) — the sealed-box nonce hash ----------------------------------
|
||||
//
|
||||
// Transcribed from blakejs (RFC 7693 reference). Only the fixed path this needs: no key, no salt,
|
||||
// no personalisation, a single ≤128-byte input.
|
||||
|
||||
const BLAKE2B_IV32 = new Uint32Array([
|
||||
0xf3bcc908, 0x6a09e667, 0x84caa73b, 0xbb67ae85, 0xfe94f82b, 0x3c6ef372, 0x5f1d36f1, 0xa54ff53a,
|
||||
0xade682d1, 0x510e527f, 0x2b3e6c1f, 0x9b05688c, 0xfb41bd6b, 0x1f83d9ab, 0x137e2179, 0x5be0cd19,
|
||||
]);
|
||||
|
||||
const SIGMA82 = new Uint8Array(
|
||||
[
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2,
|
||||
11, 7, 5, 3, 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4, 7, 9, 3, 1, 13, 12, 11, 14,
|
||||
2, 6, 5, 10, 4, 0, 15, 8, 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13, 2, 12, 6, 10, 0,
|
||||
11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9, 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11, 13,
|
||||
11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4,
|
||||
10, 5, 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
|
||||
11, 12, 13, 14, 15, 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3,
|
||||
].map((n) => n * 2),
|
||||
);
|
||||
|
||||
interface Blake2bCtx {
|
||||
b: Uint8Array;
|
||||
h: Uint32Array;
|
||||
t: number;
|
||||
c: number;
|
||||
outlen: number;
|
||||
}
|
||||
|
||||
function b2bGet32(arr: Uint8Array, i: number): number {
|
||||
return (arr[i] ^ (arr[i + 1] << 8) ^ (arr[i + 2] << 16) ^ (arr[i + 3] << 24)) >>> 0;
|
||||
}
|
||||
|
||||
function add64aa(v: Uint32Array, a: number, b: number): void {
|
||||
const o0 = v[a] + v[b];
|
||||
let o1 = v[a + 1] + v[b + 1];
|
||||
if (o0 >= 0x100000000) o1++;
|
||||
v[a] = o0;
|
||||
v[a + 1] = o1;
|
||||
}
|
||||
|
||||
function add64ac(v: Uint32Array, a: number, b0: number, b1: number): void {
|
||||
let o0 = v[a] + b0;
|
||||
if (b0 < 0) o0 += 0x100000000;
|
||||
let o1 = v[a + 1] + b1;
|
||||
if (o0 >= 0x100000000) o1++;
|
||||
v[a] = o0;
|
||||
v[a + 1] = o1;
|
||||
}
|
||||
|
||||
function b2bG(
|
||||
v: Uint32Array,
|
||||
m: Uint32Array,
|
||||
a: number,
|
||||
b: number,
|
||||
c: number,
|
||||
d: number,
|
||||
ix: number,
|
||||
iy: number,
|
||||
): void {
|
||||
const x0 = m[ix];
|
||||
const x1 = m[ix + 1];
|
||||
const y0 = m[iy];
|
||||
const y1 = m[iy + 1];
|
||||
|
||||
add64aa(v, a, b);
|
||||
add64ac(v, a, x0, x1);
|
||||
|
||||
let xor0 = v[d] ^ v[a];
|
||||
let xor1 = v[d + 1] ^ v[a + 1];
|
||||
v[d] = xor1;
|
||||
v[d + 1] = xor0;
|
||||
|
||||
add64aa(v, c, d);
|
||||
|
||||
xor0 = v[b] ^ v[c];
|
||||
xor1 = v[b + 1] ^ v[c + 1];
|
||||
v[b] = (xor0 >>> 24) ^ (xor1 << 8);
|
||||
v[b + 1] = (xor1 >>> 24) ^ (xor0 << 8);
|
||||
|
||||
add64aa(v, a, b);
|
||||
add64ac(v, a, y0, y1);
|
||||
|
||||
xor0 = v[d] ^ v[a];
|
||||
xor1 = v[d + 1] ^ v[a + 1];
|
||||
v[d] = (xor0 >>> 16) ^ (xor1 << 16);
|
||||
v[d + 1] = (xor1 >>> 16) ^ (xor0 << 16);
|
||||
|
||||
add64aa(v, c, d);
|
||||
|
||||
xor0 = v[b] ^ v[c];
|
||||
xor1 = v[b + 1] ^ v[c + 1];
|
||||
v[b] = (xor1 >>> 31) ^ (xor0 << 1);
|
||||
v[b + 1] = (xor0 >>> 31) ^ (xor1 << 1);
|
||||
}
|
||||
|
||||
function blake2bCompress(ctx: Blake2bCtx, last: boolean): void {
|
||||
const v = new Uint32Array(32);
|
||||
const m = new Uint32Array(32);
|
||||
for (let i = 0; i < 16; i++) {
|
||||
v[i] = ctx.h[i];
|
||||
v[i + 16] = BLAKE2B_IV32[i];
|
||||
}
|
||||
v[24] = v[24] ^ ctx.t;
|
||||
v[25] = v[25] ^ (ctx.t / 0x100000000);
|
||||
if (last) {
|
||||
v[28] = ~v[28];
|
||||
v[29] = ~v[29];
|
||||
}
|
||||
for (let i = 0; i < 32; i++) m[i] = b2bGet32(ctx.b, 4 * i);
|
||||
for (let i = 0; i < 12; i++) {
|
||||
b2bG(v, m, 0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1]);
|
||||
b2bG(v, m, 2, 10, 18, 26, SIGMA82[i * 16 + 2], SIGMA82[i * 16 + 3]);
|
||||
b2bG(v, m, 4, 12, 20, 28, SIGMA82[i * 16 + 4], SIGMA82[i * 16 + 5]);
|
||||
b2bG(v, m, 6, 14, 22, 30, SIGMA82[i * 16 + 6], SIGMA82[i * 16 + 7]);
|
||||
b2bG(v, m, 0, 10, 20, 30, SIGMA82[i * 16 + 8], SIGMA82[i * 16 + 9]);
|
||||
b2bG(v, m, 2, 12, 22, 24, SIGMA82[i * 16 + 10], SIGMA82[i * 16 + 11]);
|
||||
b2bG(v, m, 4, 14, 16, 26, SIGMA82[i * 16 + 12], SIGMA82[i * 16 + 13]);
|
||||
b2bG(v, m, 6, 8, 18, 28, SIGMA82[i * 16 + 14], SIGMA82[i * 16 + 15]);
|
||||
}
|
||||
for (let i = 0; i < 16; i++) ctx.h[i] = ctx.h[i] ^ v[i] ^ v[i + 16];
|
||||
}
|
||||
|
||||
function blake2b24(input: Uint8Array): Uint8Array {
|
||||
const outlen = 24;
|
||||
const ctx: Blake2bCtx = {
|
||||
b: new Uint8Array(128),
|
||||
h: new Uint32Array(16),
|
||||
t: 0,
|
||||
c: 0,
|
||||
outlen,
|
||||
};
|
||||
// Parameter block: outlen, keylen=0, fanout=1, depth=1; the rest zero.
|
||||
const param = new Uint8Array(64);
|
||||
param[0] = outlen;
|
||||
param[2] = 1;
|
||||
param[3] = 1;
|
||||
for (let i = 0; i < 16; i++) ctx.h[i] = BLAKE2B_IV32[i] ^ b2bGet32(param, i * 4);
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
if (ctx.c === 128) {
|
||||
ctx.t += ctx.c;
|
||||
blake2bCompress(ctx, false);
|
||||
ctx.c = 0;
|
||||
}
|
||||
ctx.b[ctx.c++] = input[i];
|
||||
}
|
||||
|
||||
ctx.t += ctx.c;
|
||||
while (ctx.c < 128) ctx.b[ctx.c++] = 0;
|
||||
blake2bCompress(ctx, true);
|
||||
|
||||
const out = new Uint8Array(outlen);
|
||||
for (let i = 0; i < outlen; i++) out[i] = ctx.h[i >> 2] >> (8 * (i & 3));
|
||||
return out;
|
||||
}
|
||||
|
||||
// --- small helpers ---------------------------------------------------------------------------
|
||||
|
||||
function concat(a: Uint8Array, b: Uint8Array): Uint8Array {
|
||||
const out = new Uint8Array(a.length + b.length);
|
||||
out.set(a, 0);
|
||||
out.set(b, a.length);
|
||||
return out;
|
||||
}
|
||||
Reference in New Issue
Block a user