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
109 changes: 1 addition & 108 deletions packages/chacha20poly1305/src/chacha/_arx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,9 @@ import {
checkOpts,
clean,
copyBytes,
type PRG,
u32,
type XorStream,
} from "./utils.ts";
import { randomBytes } from "./webcrypto.ts";

// Can't use similar utils.utf8ToBytes, because it uses `TextEncoder` - not available in all envs
const _utf8ToBytes = (str: string) =>
Expand Down Expand Up @@ -116,6 +114,7 @@ const BLOCK_LEN32 = 16;
const MAX_COUNTER = 2 ** 32 - 1;

const U32_EMPTY = Uint32Array.of();

function runCipher(
core: CipherCoreFn,
sigma: Uint32Array,
Expand Down Expand Up @@ -256,109 +255,3 @@ export function createCipher(core: CipherCoreFn, opts: CipherOpts): XorStream {
return output;
};
}

/** Internal class which wraps chacha20 or chacha8 to create CSPRNG. */
export class _XorStreamPRG implements PRG {
readonly blockLen: number;
readonly keyLen: number;
readonly nonceLen: number;
private state: Uint8Array;
private buf: Uint8Array;
private key: Uint8Array;
private nonce: Uint8Array;
private pos: number;
private ctr: number;
private cipher: XorStream;
constructor(
cipher: XorStream,
blockLen: number,
keyLen: number,
nonceLen: number,
seed: Uint8Array,
) {
this.cipher = cipher;
this.blockLen = blockLen;
this.keyLen = keyLen;
this.nonceLen = nonceLen;
this.state = new Uint8Array(this.keyLen + this.nonceLen);
this.reseed(seed);
this.ctr = 0;
this.pos = this.blockLen;
this.buf = new Uint8Array(this.blockLen);
this.key = this.state.subarray(0, this.keyLen);
this.nonce = this.state.subarray(this.keyLen);
}
private reseed(seed: Uint8Array) {
abytes(seed);
if (!seed || seed.length === 0) throw new Error("entropy required");
for (let i = 0; i < seed.length; i++) {
this.state[i % this.state.length] ^= seed[i];
}
this.ctr = 0;
this.pos = this.blockLen;
}
addEntropy(seed: Uint8Array): void {
this.state.set(this.randomBytes(this.state.length));
this.reseed(seed);
}
randomBytes(len: number): Uint8Array {
anumber(len);
if (len === 0) return new Uint8Array(0);
const out = new Uint8Array(len);
let outPos = 0;
// Leftovers
if (this.pos < this.blockLen) {
const take = Math.min(len, this.blockLen - this.pos);
out.set(this.buf.subarray(this.pos, this.pos + take), 0);
this.pos += take;
outPos += take;
if (outPos === len) return out; // fast path
}
// Full blocks directly to out
const blocks = Math.floor((len - outPos) / this.blockLen);
if (blocks > 0) {
const blockBytes = blocks * this.blockLen;
const b = out.subarray(outPos, outPos + blockBytes);
this.cipher(this.key, this.nonce, b, b, this.ctr);
this.ctr += blocks;
outPos += blockBytes;
}
// Save leftovers
const left = len - outPos;
if (left > 0) {
this.buf.fill(0);
// NOTE: cipher will handle overflow
this.cipher(this.key, this.nonce, this.buf, this.buf, this.ctr++);
out.set(this.buf.subarray(0, left), outPos);
this.pos = left;
}
return out;
}
clone(): _XorStreamPRG {
return new _XorStreamPRG(
this.cipher,
this.blockLen,
this.keyLen,
this.nonceLen,
this.randomBytes(this.state.length),
);
}
clean(): void {
this.pos = 0;
this.ctr = 0;
this.buf.fill(0);
this.state.fill(0);
}
}

export type XorPRG = (seed?: Uint8Array) => _XorStreamPRG;

export const createPRG = (
cipher: XorStream,
blockLen: number,
keyLen: number,
nonceLen: number,
): XorPRG => {
return (seed: Uint8Array = randomBytes(32)): _XorStreamPRG =>
new _XorStreamPRG(cipher, blockLen, keyLen, nonceLen, seed);
};
Loading
Loading