|
|
// Tests only: the writer with its random values fixed by name (plan of phase
|
|
|
// 3, decision 4), to reproduce the deterministic sections of the fixtures of
|
|
|
// the Go reference and to reach the checks of the writer. A value that is
|
|
|
// not given is drawn from crypto.getRandomValues, as encrypt does. The draws
|
|
|
// hand the writer copies, and the writer wipes them.
|
|
|
|
|
|
import { cryptoWords, type RandomWords } from '../random.ts';
|
|
|
import { type Draws, type EncryptOptions, type Encrypted, type EncryptSource, writeCapsule } from '../writer.ts';
|
|
|
import { newX25519Identity } from '../x25519.ts';
|
|
|
|
|
|
/** The random values to fix; any other is drawn at random. */
|
|
|
export interface FixedDraws {
|
|
|
readonly capsuleId?: Uint8Array;
|
|
|
readonly payloadIdentity?: Uint8Array;
|
|
|
readonly accessIdentity?: Uint8Array;
|
|
|
readonly credentialId?: Uint8Array;
|
|
|
/** The scalars of the dummies, in the order they are drawn. */
|
|
|
readonly dummies?: readonly Uint8Array[];
|
|
|
/** The words of the permutation of the slots; see wordsFor. */
|
|
|
readonly words?: readonly number[];
|
|
|
/**
|
|
|
* Where to record every secret handed to the writer: I_PAYLOAD, I_ACCESS
|
|
|
* and the scalars of the dummies, which the writer must leave zeroed.
|
|
|
*/
|
|
|
readonly handed?: Uint8Array[];
|
|
|
}
|
|
|
|
|
|
/** Draws that return copies of the fixed values, and random ones otherwise. */
|
|
|
export function fixedDraws(f: FixedDraws): Draws {
|
|
|
const bytes = (n: number) => (): Uint8Array => crypto.getRandomValues(new Uint8Array(n));
|
|
|
const fixed = (v: Uint8Array | undefined, otherwise: () => Uint8Array) => (): Uint8Array => (v === undefined ? otherwise() : v.slice());
|
|
|
const dummies = [...(f.dummies ?? [])];
|
|
|
let words: RandomWords = cryptoWords();
|
|
|
if (f.words !== undefined) {
|
|
|
const list = [...f.words];
|
|
|
words = () => {
|
|
|
const w = list.shift();
|
|
|
if (w === undefined) throw new Error('testing: the fixed words are exhausted');
|
|
|
return w;
|
|
|
};
|
|
|
}
|
|
|
const secret = (draw: () => Uint8Array) => (): Uint8Array => {
|
|
|
const b = draw();
|
|
|
f.handed?.push(b);
|
|
|
return b;
|
|
|
};
|
|
|
return {
|
|
|
capsuleId: fixed(f.capsuleId, bytes(16)),
|
|
|
payloadIdentity: secret(fixed(f.payloadIdentity, newX25519Identity)),
|
|
|
accessIdentity: secret(fixed(f.accessIdentity, newX25519Identity)),
|
|
|
dummy: secret(() => dummies.shift()?.slice() ?? newX25519Identity()),
|
|
|
words,
|
|
|
credentialId: fixed(f.credentialId, bytes(16)),
|
|
|
};
|
|
|
}
|
|
|
|
|
|
/** encrypt with some of its random values fixed. */
|
|
|
export function encryptWith(src: EncryptSource, opts: EncryptOptions, f: FixedDraws): Promise<Encrypted> {
|
|
|
return writeCapsule(src, opts, fixedDraws(f));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* The words that make the permutation of the writer leave credential i, the
|
|
|
* item at position i before it, in slot `slots[i]`, the dummies filling the
|
|
|
* slots left in their order. The permutation is Fisher–Yates from the last
|
|
|
* position down: at position p it swaps with an index j ≤ p, drawn as the
|
|
|
* word j, which randomIndex accepts as it is.
|
|
|
*/
|
|
|
export function wordsFor(slots: readonly number[], n = 16): number[] {
|
|
|
const target = new Array<number>(n).fill(-1);
|
|
|
for (const [i, s] of slots.entries()) target[s] = i;
|
|
|
let next = slots.length;
|
|
|
for (let p = 0; p < n; p++) if (target[p] === -1) target[p] = next++;
|
|
|
const items = Array.from({ length: n }, (_, i) => i);
|
|
|
const words: number[] = [];
|
|
|
for (let p = n - 1; p > 0; p--) {
|
|
|
const j = items.indexOf(target[p]!);
|
|
|
words.push(j);
|
|
|
[items[p], items[j]] = [items[j]!, items[p]!];
|
|
|
}
|
|
|
return words;
|
|
|
}
|