Skip to content

Commit

Permalink
Update buildsystem and -configuration
Browse files Browse the repository at this point in the history
Switch to a tsup-based setup using Matt Pococks guide [1]
to emit ESM and CJS code, drop ts-node in favour of tsx and
fix any type issues that surfaced after updating tsconfig.

[1] https://www.totaltypescript.com/how-to-create-an-npm-package#21-create-a-packagejson-file

Signed-off-by: Stefan Knoblich <[email protected]>
  • Loading branch information
stknob committed Aug 28, 2024
1 parent 0fe14c8 commit 276fbbc
Show file tree
Hide file tree
Showing 10 changed files with 1,192 additions and 120 deletions.
51 changes: 42 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,54 @@
{
"name": "poc-aegis",
"name": "aegis-ts",
"version": "0.0.0",
"type": "module",
"private": true,
"description": "TypeScript implementation of Aegis128L and Aegis256",
"author": {
"email": "[email protected]",
"name": "Stefan Knoblich"
},
"homepage": "https://github.com/stknob/aegis-ts",
"keywords": ["aegis", "aegis128l", "aegis256", "typescript"],
"repository": {
"type": "git",
"url": "git+https://github.com/stknob/aegis-ts.git"
},
"bugs": {
"url": "https://github.com/stknob/aegis-ts/issues"
},
"dependencies": {
"@noble/ciphers": "0.6.0"
},
"devDependencies": {
"@arethetypeswrong/cli": "0.15.4",
"@types/node": "20.14.15",
"@noble/ciphers": "0.6.0",
"micro-bmark": "0.3.1",
"glob": "11.0.0",
"ts-node": "10.9.2",
"tsx": "4.19.0",
"tsup": "8.2.4",
"typescript": "5.5.4"
},
"files": [
"dist"
],
"exports": {
"./package.json": "./package.json",
"./aegis128l.js": {
"import": "./dist/aegis128l.js",
"default": "./dist/aegis128l.cjs"
},
"./aegis256.js": {
"import": "./dist/aegis256.js",
"default": "./dist/aegis256.cjs"
}
},
"scripts": {
"build": "npx tsc",
"run": "node --loader ts-node/esm src/index.mts",
"test": "node --loader ts-node/esm test/index.mts",
"prof": "node --prof --loader ts-node/esm src/index.mts",
"bench": "node --loader ts-node/esm benchmark/index.mts"
"build": "tsup",
"lint": "tsc",
"test": "tsx test/index.mts",
"prof": "tsx --prof src/index.mts",
"bench": "tsx benchmark/index.mts",
"ci": "yarn run build && yarn run lint && yarn run test && yarn run check-exports",
"check-exports": "attw --pack ."
}
}
16 changes: 8 additions & 8 deletions src/_aegis.mts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { clean, copyBytes, equalBytes, isAligned32, u32 } from "@noble/ciphers/utils";
import { AESRoundResult } from "./_aes.mjs";
import { type AESRoundResult } from "./_aes.mjs";

// C0: 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0d, 0x15, 0x22, 0x37, 0x59, 0x90, 0xe9, 0x79, 0x62
export const C0 = Uint32Array.of(0x02010100, 0x0d080503, 0x59372215, 0x6279e990);
Expand Down Expand Up @@ -37,7 +37,7 @@ export function xor256(a: Uint32Array, b: Uint32Array, out: Uint32Array): Uint32
}

export type AegisCipher = {
encrypt(plaintext: Uint8Array, ad?: Uint8Array): Uint8Array
encrypt(plaintext: Uint8Array, ad?: Uint8Array): Uint8Array,
decrypt(ciphertext: Uint8Array, ad?: Uint8Array): Uint8Array,
encrypt_detached(plaintext: Uint8Array, ad?: Uint8Array): [Uint8Array, Uint8Array],
decrypt_detached(ciphertext: Uint8Array, tag: Uint8Array, ad?: Uint8Array): Uint8Array,
Expand All @@ -51,8 +51,8 @@ export interface AegisState {
blockSize: number;

init(key: Uint8Array, nonce: Uint8Array): this;
absorb(ai: Uint32Array);
absorbPartial(ai: Uint8Array);
absorb(ai: Uint32Array): void;
absorbPartial(ai: Uint8Array): void;
encBlock(xi: Uint32Array, out: Uint32Array): Uint32Array;
encPartial(xi: Uint8Array, out: Uint8Array): Uint8Array;
decBlock(ci: Uint32Array, out: Uint32Array): Uint32Array;
Expand All @@ -62,7 +62,7 @@ export interface AegisState {

export class AegisInvalidTagError extends Error {
constructor() { super("Aegis decryption failed") }
get name() { return this.constructor.name; }
override get name() { return this.constructor.name; }
}

export const aegis_encrypt_detached = (state: AegisState, pt: Uint8Array, ad?: Uint8Array, tag_len: number = 32): [Uint8Array, Uint8Array] => {
Expand All @@ -77,7 +77,7 @@ export const aegis_encrypt_detached = (state: AegisState, pt: Uint8Array, ad?: U
const blockSizeU32 = blockSizeU8 >> 2;

const ad_len = ad?.length || 0;
if (ad_len) {
if (ad && ad_len) {
if (!isAligned32(ad)) toClean.push((ad = copyBytes(ad)));
const ad32 = u32(ad);

Expand Down Expand Up @@ -120,8 +120,8 @@ export const aegis_decrypt_detached = (state: AegisState, ct: Uint8Array, tag: U
const msg = new Uint8Array(ct.length);
const dst32 = u32(msg);

const ad_len = ad?.length;
if (ad_len) {
const ad_len = ad?.length || 0;
if (ad && ad_len) {
if (!isAligned32(ad)) toClean.push((ad = copyBytes(ad)));
const ad32 = u32(ad);

Expand Down
15 changes: 13 additions & 2 deletions src/aegis128l.mts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isAligned32, concatBytes, copyBytes, wrapCipher, u8, u32, clean } from "@noble/ciphers/utils";
import { aegis_decrypt_detached, aegis_encrypt_detached, AegisCipher, AegisCipherOptions, AegisState, C0, C1, set128, xor128, xor256 } from "./_aegis.mjs";
import { aegis_decrypt_detached, aegis_encrypt_detached, type AegisCipher, type AegisCipherOptions, type AegisState, C0, C1, set128, xor128, xor256 } from "./_aegis.mjs";
import { u64BitLengths } from "./_utils.mjs";
import { AESRound } from "./_aes.mjs";

Expand All @@ -8,6 +8,17 @@ export type Aegis128LBlocks = [
Uint32Array, Uint32Array, Uint32Array, Uint32Array,
];

const DUMMY_BLOCKS: Aegis128LBlocks = [
Uint32Array.of(0, 0, 0, 0),
Uint32Array.of(0, 0, 0, 0),
Uint32Array.of(0, 0, 0, 0),
Uint32Array.of(0, 0, 0, 0),
Uint32Array.of(0, 0, 0, 0),
Uint32Array.of(0, 0, 0, 0),
Uint32Array.of(0, 0, 0, 0),
Uint32Array.of(0, 0, 0, 0),
];


/**
* Aegis128L state update function with single 256bit input block, extracted for testability
Expand Down Expand Up @@ -48,7 +59,7 @@ export function aegis128l_update2(blocks: Aegis128LBlocks, m0: Uint32Array, m1:


class Aegis128LState implements AegisState {
#blocks: Aegis128LBlocks;
#blocks: Aegis128LBlocks = DUMMY_BLOCKS;
#tmpBlock32 = new Uint32Array(8); // Scratch buffer for generic operations e.g. xor128 and ZeroPad()
#tmpBlock8 = u8(this.#tmpBlock32); // Uint8Array view into generic scratch buffer for ZeroPad()
#sBlock32 = new Uint32Array(8); // Scratch buffer for aegis128l_updateX
Expand Down
13 changes: 11 additions & 2 deletions src/aegis256.mts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isAligned32, concatBytes, copyBytes, wrapCipher, clean, u32, u8 } from "@noble/ciphers/utils";
import { aegis_decrypt_detached, aegis_encrypt_detached, AegisCipher, AegisCipherOptions, AegisState, C0, C1, set128, xor128 } from "./_aegis.mjs";
import { aegis_decrypt_detached, aegis_encrypt_detached, type AegisCipher, type AegisCipherOptions, type AegisState, C0, C1, set128, xor128 } from "./_aegis.mjs";
import { u64BitLengths } from "./_utils.mjs";
import { AESRound } from "./_aes.mjs";

Expand All @@ -8,6 +8,15 @@ export type Aegis256Blocks = [
Uint32Array, Uint32Array
];

const DUMMY_BLOCKS: Aegis256Blocks = [
Uint32Array.of(0, 0, 0, 0),
Uint32Array.of(0, 0, 0, 0),
Uint32Array.of(0, 0, 0, 0),
Uint32Array.of(0, 0, 0, 0),
Uint32Array.of(0, 0, 0, 0),
Uint32Array.of(0, 0, 0, 0),
];

/**
* Aegis256 state update function, extracted for testability
* @param blocks
Expand All @@ -32,7 +41,7 @@ export function aegis256_update(blocks: Aegis256Blocks, msg: Uint32Array, tmp: U
}

class Aegis256State implements AegisState {
#blocks: Aegis256Blocks;
#blocks: Aegis256Blocks = DUMMY_BLOCKS;
#tmpBlock32 = new Uint32Array(4); // Scratch buffer for generic operations e.g. xor128 and ZeroPad()
#tmpBlock8 = u8(this.#tmpBlock32); // Uint8Array view into generic scratch buffer for ZeroPad()
#sBlock32 = new Uint32Array(4); // Scratch buffer for aegis256_update
Expand Down
4 changes: 2 additions & 2 deletions test/aegis128l.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import assert from "node:assert";
import suite from "node:test";

import { aegis128l, aegis128l_update2, Aegis128LBlocks } from "../src/aegis128l.mjs";
import { runAegisTestVectors } from "./common.mjs";
import { AegisTestVectorDesc, runAegisTestVectors } from "./common.mjs";

suite("aegis128l", async (s) => {
await s.test("aegis128l update", () => {
Expand Down Expand Up @@ -38,7 +38,7 @@ suite("aegis128l", async (s) => {
});

await s.test("aegis128l test vectors", () => {
const AEGIS128L_TEST_VECTORS = [{
const AEGIS128L_TEST_VECTORS: AegisTestVectorDesc[] = [{
// Aegis128L testvector #1
key: hexToBytes("10010000000000000000000000000000"),
nonce: hexToBytes("10000200000000000000000000000000"),
Expand Down
4 changes: 2 additions & 2 deletions test/aegis256.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import assert from "node:assert";
import suite from "node:test";

import { aegis256, aegis256_update, Aegis256Blocks } from "../src/aegis256.mjs";
import { runAegisTestVectors } from "./common.mjs";
import { AegisTestVectorDesc, runAegisTestVectors } from "./common.mjs";

suite("aegis256", async (s) => {
await s.test("aegis256 update", () => {
Expand Down Expand Up @@ -34,7 +34,7 @@ suite("aegis256", async (s) => {
});

await s.test("aegis256 test vectors", () => {
const AEGIS256_TEST_VECTORS = [{
const AEGIS256_TEST_VECTORS: AegisTestVectorDesc[] = [{
// Aegis256 testvector #1
key: hexToBytes("1001000000000000000000000000000000000000000000000000000000000000"),
nonce: hexToBytes("1000020000000000000000000000000000000000000000000000000000000000"),
Expand Down
30 changes: 22 additions & 8 deletions test/common.mts
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import assert from "node:assert";
import { AegisInvalidTagError } from "../src/_aegis.mjs";
import { AegisInvalidTagError, type AegisCipher, type AegisCipherOptions } from "../src/_aegis.mjs";

export function runAegisTestVectors(name, cipher, vectors) {
export interface AegisTestVectorDesc {
key: Uint8Array,
msg: Uint8Array|null,
ad: Uint8Array,
ct: Uint8Array,
nonce: Uint8Array,
tag128: Uint8Array,
tag256: Uint8Array,
decryptOnly?: boolean,
valid: boolean,
}

const EMPTY_BUF = Uint8Array.from([]);

export function runAegisTestVectors(name: string, cipher: (key: Uint8Array, nonce: Uint8Array, options?: AegisCipherOptions) => AegisCipher, vectors: AegisTestVectorDesc[]) {
for (const [idx, desc] of vectors.entries()) {
if (!desc.decryptOnly) {
const [ct, tag256] = cipher(desc.key, desc.nonce, { tagLength: 32 }).encrypt_detached(desc.msg, desc.ad);
const [__, tag128] = cipher(desc.key, desc.nonce, { tagLength: 16 }).encrypt_detached(desc.msg, desc.ad);
const [ct, tag256] = cipher(desc.key, desc.nonce, { tagLength: 32 }).encrypt_detached(desc.msg || EMPTY_BUF, desc.ad);
const [__, tag128] = cipher(desc.key, desc.nonce, { tagLength: 16 }).encrypt_detached(desc.msg || EMPTY_BUF, desc.ad);

assert.deepStrictEqual(ct, desc.ct, `${name} testvector #${idx + 1} failed ciphertext validation`);
assert.deepStrictEqual(tag128, desc.tag128, `${name} testvector #${idx + 1} failed 128bit tag`);
Expand All @@ -14,25 +28,25 @@ export function runAegisTestVectors(name, cipher, vectors) {

// Decryption w/ 128bit tag
if (desc.valid) {
let pt = null;
let pt: Uint8Array|null = null;
assert.doesNotThrow(() => { pt = cipher(desc.key, desc.nonce).decrypt_detached(desc.ct, desc.tag128, desc.ad); }, AegisInvalidTagError,
`${name} testvector #${idx + 1} failed decryption w/ 128bit tag`);
assert.deepStrictEqual(pt, desc.msg, `${name} testvector #${idx + 1} failed decryption w/ 128bit tag`);
} else {
let pt = null;
let pt: Uint8Array|null = null;
assert.throws(() => { pt = cipher(desc.key, desc.nonce).decrypt_detached(desc.ct, desc.tag128, desc.ad); }, AegisInvalidTagError,
`${name} testvector #${idx + 1} succeeded decryption w/ 128bit tag`);
assert.deepStrictEqual(pt, desc.msg);
}

// Decryption w/ 256bit tag
if (desc.valid) {
let pt = null;
let pt: Uint8Array|null = null;
assert.doesNotThrow(() => { pt = cipher(desc.key, desc.nonce).decrypt_detached(desc.ct, desc.tag256, desc.ad); }, AegisInvalidTagError,
`${name} testvector #${idx + 1} failed decryption w/ 256bit tag`);
assert.deepStrictEqual(pt, desc.msg, `${name} testvector #${idx + 1} failed decryption w/ 256bit tag`);
} else {
let pt = null;
let pt: Uint8Array|null = null;
assert.throws(() => { pt = cipher(desc.key, desc.nonce).decrypt_detached(desc.ct, desc.tag256, desc.ad); }, AegisInvalidTagError,
`${name} testvector #${idx + 1} succeeded decryption w/ 256bit tag`);
assert.deepStrictEqual(pt, desc.msg);
Expand Down
17 changes: 15 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
{
"compilerOptions": {
"esModuleInterop": true,
"skipLibCheck": true,
"allowJs": true,
"target": "ES2022",
"strict": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"noImplicitOverride": true,
"moduleResolution": "NodeNext",
"sourceMap": true,
"declaration": true,
"declarationMap": true,
"lib": ["es2022"],
"module": "NodeNext",
"outDir": "dist/"
"rootDirs": ["src", "test"],
"outDir": "dist",
"noEmit": true
},
"include": [
"src/"
"src"
],
"exclude": [
"node_modules"
Expand Down
9 changes: 9 additions & 0 deletions tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from "tsup";

export default defineConfig({
entry: ["src/aegis128l.mts", "src/aegis256.mts"],
format: ["cjs", "esm"],
dts: true,
outDir: "dist",
clean: true,
});
Loading

0 comments on commit 276fbbc

Please sign in to comment.