Lint and update

This commit is contained in:
2025-09-02 20:20:04 +01:00
parent 37d9f83c92
commit b12c05f3b6
20 changed files with 6815 additions and 6327 deletions

View File

@@ -1,5 +1,3 @@
{
"recommendations": [
"orta.vscode-jest"
]
"recommendations": ["orta.vscode-jest"]
}

32
.vscode/launch.json vendored
View File

@@ -1,20 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/lib/index.js",
"outFiles": [
"${workspaceFolder}/**/*.js"
]
}
]
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Launch Program",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/lib/index.js",
"outFiles": ["${workspaceFolder}/**/*.js"]
}
]
}

30
.vscode/tasks.json vendored
View File

@@ -1,18 +1,16 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "typescript",
"tsconfig": "tsconfig.json",
"option": "watch",
"problemMatcher": [
"$tsc-watch"
],
"group": {
"kind": "build",
"isDefault": true
},
"label": "tsc: watch - tsconfig.json"
}
]
"version": "2.0.0",
"tasks": [
{
"type": "typescript",
"tsconfig": "tsconfig.json",
"option": "watch",
"problemMatcher": ["$tsc-watch"],
"group": {
"kind": "build",
"isDefault": true
},
"label": "tsc: watch - tsconfig.json"
}
]
}

View File

@@ -1,4 +1,5 @@
# Romulus-JS
[![Build Status](https://drone.jacknet.io/api/badges/TerribleCodeClub/romulus-js/status.svg)](https://drone.jacknet.io/TerribleCodeClub/romulus-js) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
An implementation of the Romulus-M cryptography specification.
@@ -8,15 +9,18 @@ An implementation of the Romulus-M cryptography specification.
To build the Romulus-JS library, first clone this repository.
Run the following commands from the root of the repository:
```bash
$ npm install
$ npm run build
```
The build output will be saved to the `dist` directory.
## Development instructions
Requirements:
- The latest LTS builds of Node and npm.
Follow the instructions below to lint, test and build Romulus-JS.
@@ -34,6 +38,7 @@ $ npm run lint
$ npm install
$ npm run test
```
#### Build
```bash
@@ -48,9 +53,3 @@ This repository contains the necessary configuration files to debug, test and bu
Run the build task (`Ctrl+Shift+B` or `⇧⌘B`) to automatically compile the Typescript source files in the background.
Unit tests use the [Jest](https://jestjs.io/) library. Support for Visual Studio Code is offered through the [Jest marketplace package](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest) maintained by Orta.
## Contribution guidelines
[![JavaScript Style Guide](https://cdn.rawgit.com/standard/standard/master/badge.svg)](https://github.com/standard/standard)
This library uses [ts-standard](https://github.com/standard/ts-standard), based on [JavaScript Standard Style](https://standardjs.com/rules.html). Please ensure all contributions are ts-standard compliant before submitting a pull request.

1237
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@
"devDependencies": {
"@types/jest": "^27.4.0",
"jest": "^27.4.7",
"prettier": "^3.6.2",
"ts-jest": "^27.1.3",
"ts-standard": "^11.0.0",
"typescript": "^4.5.5"
@@ -30,7 +31,7 @@
}
},
"dependencies": {
"uuid": "^8.3.2",
"@types/uuid": "^8.3.4"
"@types/uuid": "^8.3.4",
"uuid": "^8.3.2"
}
}

View File

@@ -1,59 +1,73 @@
// SKINNY-128/384+ block cipher constants.
export const MEMBER_MASK = 32
export const NB_ROUNDS = 40
export const TWEAK_LENGTH = 48
export const PT = [9, 15, 8, 13, 10, 14, 12, 11, 0, 1, 2, 3, 4, 5, 6, 7]
export const MEMBER_MASK = 32;
export const NB_ROUNDS = 40;
export const TWEAK_LENGTH = 48;
export const PT = [9, 15, 8, 13, 10, 14, 12, 11, 0, 1, 2, 3, 4, 5, 6, 7];
export const LFSR_8_TK2 = [
0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56,
58, 60, 62, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109,
111, 113, 115, 117, 119, 121, 123, 125, 127, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152,
154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 193, 195, 197,
199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, 241,
243, 245, 247, 249, 251, 253, 255, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39,
41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92,
94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 129, 131, 133, 135, 137,
139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181,
183, 185, 187, 189, 191, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224,
226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254
]
0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40,
42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 65, 67, 69, 71, 73, 75, 77, 79,
81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113,
115, 117, 119, 121, 123, 125, 127, 128, 130, 132, 134, 136, 138, 140, 142,
144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172,
174, 176, 178, 180, 182, 184, 186, 188, 190, 193, 195, 197, 199, 201, 203,
205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233,
235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255, 1, 3, 5, 7, 9, 11, 13,
15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51,
53, 55, 57, 59, 61, 63, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88,
90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120,
122, 124, 126, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151,
153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181,
183, 185, 187, 189, 191, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210,
212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238, 240,
242, 244, 246, 248, 250, 252, 254,
];
export const LFSR_8_TK3 = [
0, 128, 1, 129, 2, 130, 3, 131, 4, 132, 5, 133, 6, 134, 7, 135, 8, 136, 9, 137, 10, 138, 11, 139, 12, 140,
13, 141, 14, 142, 15, 143, 16, 144, 17, 145, 18, 146, 19, 147, 20, 148, 21, 149, 22, 150, 23, 151, 24, 152,
25, 153, 26, 154, 27, 155, 28, 156, 29, 157, 30, 158, 31, 159, 160, 32, 161, 33, 162, 34, 163, 35, 164, 36,
165, 37, 166, 38, 167, 39, 168, 40, 169, 41, 170, 42, 171, 43, 172, 44, 173, 45, 174, 46, 175, 47, 176, 48,
177, 49, 178, 50, 179, 51, 180, 52, 181, 53, 182, 54, 183, 55, 184, 56, 185, 57, 186, 58, 187, 59, 188, 60,
189, 61, 190, 62, 191, 63, 64, 192, 65, 193, 66, 194, 67, 195, 68, 196, 69, 197, 70, 198, 71, 199, 72, 200,
73, 201, 74, 202, 75, 203, 76, 204, 77, 205, 78, 206, 79, 207, 80, 208, 81, 209, 82, 210, 83, 211, 84, 212,
85, 213, 86, 214, 87, 215, 88, 216, 89, 217, 90, 218, 91, 219, 92, 220, 93, 221, 94, 222, 95, 223, 224, 96,
225, 97, 226, 98, 227, 99, 228, 100, 229, 101, 230, 102, 231, 103, 232, 104, 233, 105, 234, 106, 235, 107,
236, 108, 237, 109, 238, 110, 239, 111, 240, 112, 241, 113, 242, 114, 243, 115, 244, 116, 245, 117, 246,
118, 247, 119, 248, 120, 249, 121, 250, 122, 251, 123, 252, 124, 253, 125, 254, 126, 255, 127
]
0, 128, 1, 129, 2, 130, 3, 131, 4, 132, 5, 133, 6, 134, 7, 135, 8, 136, 9,
137, 10, 138, 11, 139, 12, 140, 13, 141, 14, 142, 15, 143, 16, 144, 17, 145,
18, 146, 19, 147, 20, 148, 21, 149, 22, 150, 23, 151, 24, 152, 25, 153, 26,
154, 27, 155, 28, 156, 29, 157, 30, 158, 31, 159, 160, 32, 161, 33, 162, 34,
163, 35, 164, 36, 165, 37, 166, 38, 167, 39, 168, 40, 169, 41, 170, 42, 171,
43, 172, 44, 173, 45, 174, 46, 175, 47, 176, 48, 177, 49, 178, 50, 179, 51,
180, 52, 181, 53, 182, 54, 183, 55, 184, 56, 185, 57, 186, 58, 187, 59, 188,
60, 189, 61, 190, 62, 191, 63, 64, 192, 65, 193, 66, 194, 67, 195, 68, 196,
69, 197, 70, 198, 71, 199, 72, 200, 73, 201, 74, 202, 75, 203, 76, 204, 77,
205, 78, 206, 79, 207, 80, 208, 81, 209, 82, 210, 83, 211, 84, 212, 85, 213,
86, 214, 87, 215, 88, 216, 89, 217, 90, 218, 91, 219, 92, 220, 93, 221, 94,
222, 95, 223, 224, 96, 225, 97, 226, 98, 227, 99, 228, 100, 229, 101, 230,
102, 231, 103, 232, 104, 233, 105, 234, 106, 235, 107, 236, 108, 237, 109,
238, 110, 239, 111, 240, 112, 241, 113, 242, 114, 243, 115, 244, 116, 245,
117, 246, 118, 247, 119, 248, 120, 249, 121, 250, 122, 251, 123, 252, 124,
253, 125, 254, 126, 255, 127,
];
export const S8 = [
0x65, 0x4c, 0x6a, 0x42, 0x4b, 0x63, 0x43, 0x6b, 0x55, 0x75, 0x5a, 0x7a, 0x53, 0x73, 0x5b, 0x7b,
0x35, 0x8c, 0x3a, 0x81, 0x89, 0x33, 0x80, 0x3b, 0x95, 0x25, 0x98, 0x2a, 0x90, 0x23, 0x99, 0x2b,
0xe5, 0xcc, 0xe8, 0xc1, 0xc9, 0xe0, 0xc0, 0xe9, 0xd5, 0xf5, 0xd8, 0xf8, 0xd0, 0xf0, 0xd9, 0xf9,
0xa5, 0x1c, 0xa8, 0x12, 0x1b, 0xa0, 0x13, 0xa9, 0x05, 0xb5, 0x0a, 0xb8, 0x03, 0xb0, 0x0b, 0xb9,
0x32, 0x88, 0x3c, 0x85, 0x8d, 0x34, 0x84, 0x3d, 0x91, 0x22, 0x9c, 0x2c, 0x94, 0x24, 0x9d, 0x2d,
0x62, 0x4a, 0x6c, 0x45, 0x4d, 0x64, 0x44, 0x6d, 0x52, 0x72, 0x5c, 0x7c, 0x54, 0x74, 0x5d, 0x7d,
0xa1, 0x1a, 0xac, 0x15, 0x1d, 0xa4, 0x14, 0xad, 0x02, 0xb1, 0x0c, 0xbc, 0x04, 0xb4, 0x0d, 0xbd,
0xe1, 0xc8, 0xec, 0xc5, 0xcd, 0xe4, 0xc4, 0xed, 0xd1, 0xf1, 0xdc, 0xfc, 0xd4, 0xf4, 0xdd, 0xfd,
0x36, 0x8e, 0x38, 0x82, 0x8b, 0x30, 0x83, 0x39, 0x96, 0x26, 0x9a, 0x28, 0x93, 0x20, 0x9b, 0x29,
0x66, 0x4e, 0x68, 0x41, 0x49, 0x60, 0x40, 0x69, 0x56, 0x76, 0x58, 0x78, 0x50, 0x70, 0x59, 0x79,
0xa6, 0x1e, 0xaa, 0x11, 0x19, 0xa3, 0x10, 0xab, 0x06, 0xb6, 0x08, 0xba, 0x00, 0xb3, 0x09, 0xbb,
0xe6, 0xce, 0xea, 0xc2, 0xcb, 0xe3, 0xc3, 0xeb, 0xd6, 0xf6, 0xda, 0xfa, 0xd3, 0xf3, 0xdb, 0xfb,
0x31, 0x8a, 0x3e, 0x86, 0x8f, 0x37, 0x87, 0x3f, 0x92, 0x21, 0x9e, 0x2e, 0x97, 0x27, 0x9f, 0x2f,
0x61, 0x48, 0x6e, 0x46, 0x4f, 0x67, 0x47, 0x6f, 0x51, 0x71, 0x5e, 0x7e, 0x57, 0x77, 0x5f, 0x7f,
0xa2, 0x18, 0xae, 0x16, 0x1f, 0xa7, 0x17, 0xaf, 0x01, 0xb2, 0x0e, 0xbe, 0x07, 0xb7, 0x0f, 0xbf,
0xe2, 0xca, 0xee, 0xc6, 0xcf, 0xe7, 0xc7, 0xef, 0xd2, 0xf2, 0xde, 0xfe, 0xd7, 0xf7, 0xdf, 0xff
]
0x65, 0x4c, 0x6a, 0x42, 0x4b, 0x63, 0x43, 0x6b, 0x55, 0x75, 0x5a, 0x7a, 0x53,
0x73, 0x5b, 0x7b, 0x35, 0x8c, 0x3a, 0x81, 0x89, 0x33, 0x80, 0x3b, 0x95, 0x25,
0x98, 0x2a, 0x90, 0x23, 0x99, 0x2b, 0xe5, 0xcc, 0xe8, 0xc1, 0xc9, 0xe0, 0xc0,
0xe9, 0xd5, 0xf5, 0xd8, 0xf8, 0xd0, 0xf0, 0xd9, 0xf9, 0xa5, 0x1c, 0xa8, 0x12,
0x1b, 0xa0, 0x13, 0xa9, 0x05, 0xb5, 0x0a, 0xb8, 0x03, 0xb0, 0x0b, 0xb9, 0x32,
0x88, 0x3c, 0x85, 0x8d, 0x34, 0x84, 0x3d, 0x91, 0x22, 0x9c, 0x2c, 0x94, 0x24,
0x9d, 0x2d, 0x62, 0x4a, 0x6c, 0x45, 0x4d, 0x64, 0x44, 0x6d, 0x52, 0x72, 0x5c,
0x7c, 0x54, 0x74, 0x5d, 0x7d, 0xa1, 0x1a, 0xac, 0x15, 0x1d, 0xa4, 0x14, 0xad,
0x02, 0xb1, 0x0c, 0xbc, 0x04, 0xb4, 0x0d, 0xbd, 0xe1, 0xc8, 0xec, 0xc5, 0xcd,
0xe4, 0xc4, 0xed, 0xd1, 0xf1, 0xdc, 0xfc, 0xd4, 0xf4, 0xdd, 0xfd, 0x36, 0x8e,
0x38, 0x82, 0x8b, 0x30, 0x83, 0x39, 0x96, 0x26, 0x9a, 0x28, 0x93, 0x20, 0x9b,
0x29, 0x66, 0x4e, 0x68, 0x41, 0x49, 0x60, 0x40, 0x69, 0x56, 0x76, 0x58, 0x78,
0x50, 0x70, 0x59, 0x79, 0xa6, 0x1e, 0xaa, 0x11, 0x19, 0xa3, 0x10, 0xab, 0x06,
0xb6, 0x08, 0xba, 0x00, 0xb3, 0x09, 0xbb, 0xe6, 0xce, 0xea, 0xc2, 0xcb, 0xe3,
0xc3, 0xeb, 0xd6, 0xf6, 0xda, 0xfa, 0xd3, 0xf3, 0xdb, 0xfb, 0x31, 0x8a, 0x3e,
0x86, 0x8f, 0x37, 0x87, 0x3f, 0x92, 0x21, 0x9e, 0x2e, 0x97, 0x27, 0x9f, 0x2f,
0x61, 0x48, 0x6e, 0x46, 0x4f, 0x67, 0x47, 0x6f, 0x51, 0x71, 0x5e, 0x7e, 0x57,
0x77, 0x5f, 0x7f, 0xa2, 0x18, 0xae, 0x16, 0x1f, 0xa7, 0x17, 0xaf, 0x01, 0xb2,
0x0e, 0xbe, 0x07, 0xb7, 0x0f, 0xbf, 0xe2, 0xca, 0xee, 0xc6, 0xcf, 0xe7, 0xc7,
0xef, 0xd2, 0xf2, 0xde, 0xfe, 0xd7, 0xf7, 0xdf, 0xff,
];
export const C = [
0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3E, 0x3D, 0x3B, 0x37, 0x2F,
0x1E, 0x3C, 0x39, 0x33, 0x27, 0x0E, 0x1D, 0x3A, 0x35, 0x2B,
0x16, 0x2C, 0x18, 0x30, 0x21, 0x02, 0x05, 0x0B, 0x17, 0x2E,
0x1C, 0x38, 0x31, 0x23, 0x06, 0x0D, 0x1B, 0x36, 0x2D, 0x1A
]
0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3e, 0x3d, 0x3b, 0x37, 0x2f, 0x1e, 0x3c, 0x39,
0x33, 0x27, 0x0e, 0x1d, 0x3a, 0x35, 0x2b, 0x16, 0x2c, 0x18, 0x30, 0x21, 0x02,
0x05, 0x0b, 0x17, 0x2e, 0x1c, 0x38, 0x31, 0x23, 0x06, 0x0d, 0x1b, 0x36, 0x2d,
0x1a,
];
// Romulus-M cryptography specification constants.
export const T_LENGTH = 16
export const COUNTER_LENGTH = 7
export const T_LENGTH = 16;
export const COUNTER_LENGTH = 7;

View File

@@ -1,8 +1,8 @@
import { cryptoAeadDecrypt } from './romulus-m'
import { cryptoAeadDecrypt } from "./romulus-m";
interface DecryptResult {
success: boolean
plaintext: Uint8Array
success: boolean;
plaintext: Uint8Array;
}
/**
@@ -13,17 +13,26 @@ interface DecryptResult {
* @param key The encryption key.
* @returns A decrypted DecryptResult object.
*/
export function decrypt (buffer: Uint8Array, associatedData: Uint8Array, key: Uint8Array): DecryptResult {
export function decrypt(
buffer: Uint8Array,
associatedData: Uint8Array,
key: Uint8Array,
): DecryptResult {
// Split nonce from ciphertext.
const nonce = Array.from(buffer.slice(0, 16))
const ciphertext = Array.from(buffer.slice(16))
const nonce = Array.from(buffer.slice(0, 16));
const ciphertext = Array.from(buffer.slice(16));
// Decrypt ciphertext using the associated data, nonce and encryption key.
const result = cryptoAeadDecrypt(ciphertext, Array.from(associatedData), nonce, Array.from(key))
const result = cryptoAeadDecrypt(
ciphertext,
Array.from(associatedData),
nonce,
Array.from(key),
);
// Return the ciphertext and decryption status.
return {
success: result.success,
plaintext: Uint8Array.from(result.plaintext)
}
plaintext: Uint8Array.from(result.plaintext),
};
}

View File

@@ -1,5 +1,5 @@
import { cryptoAeadEncrypt } from './romulus-m'
import { v4 as uuidv4 } from 'uuid'
import { cryptoAeadEncrypt } from "./romulus-m";
import { v4 as uuidv4 } from "uuid";
/**
* Encrypt a message using the Romulus-M encryption algorithm.
@@ -9,14 +9,25 @@ import { v4 as uuidv4 } from 'uuid'
* @param key The encryption key.
* @returns The nonce-prepended ciphertext.
*/
export function encrypt (message: Uint8Array, associatedData: Uint8Array, key: Uint8Array): Uint8Array {
export function encrypt(
message: Uint8Array,
associatedData: Uint8Array,
key: Uint8Array,
): Uint8Array {
// Generate a nonce.
const nonce = new Uint8Array(16)
uuidv4({}, nonce)
const nonce = new Uint8Array(16);
uuidv4({}, nonce);
// Encrypt the data using the associated data, newly generated nonce and encryption key.
const ciphertext = Uint8Array.from(cryptoAeadEncrypt(Array.from(message), Array.from(associatedData), Array.from(nonce), Array.from(key)))
const ciphertext = Uint8Array.from(
cryptoAeadEncrypt(
Array.from(message),
Array.from(associatedData),
Array.from(nonce),
Array.from(key),
),
);
// Return the nonce-prepended ciphertext.
return new Uint8Array([...nonce, ...ciphertext])
return new Uint8Array([...nonce, ...ciphertext]);
}

View File

@@ -1,2 +1,2 @@
export { encrypt } from './encrypt'
export { decrypt } from './decrypt'
export { encrypt } from "./encrypt";
export { decrypt } from "./decrypt";

View File

@@ -1,5 +1,5 @@
import { COUNTER_LENGTH } from './constants'
import { tweakeyEncode, skinnyEncrypt } from './skinny-128-384-plus'
import { COUNTER_LENGTH } from "./constants";
import { tweakeyEncode, skinnyEncrypt } from "./skinny-128-384-plus";
/**
* Parse message into blocks.
@@ -7,30 +7,30 @@ import { tweakeyEncode, skinnyEncrypt } from './skinny-128-384-plus'
* @param blockLength The block length.
* @returns An array of blocks.
*/
function parse (message: number[], blockLength: number): number[][] {
function parse(message: number[], blockLength: number): number[][] {
// Keep track of position in message currently parsed into blocks.
let cursor = 0
let cursor = 0;
// Slice message into blocks.
let ret: number[][] = []
let ret: number[][] = [];
while (message.length - cursor >= blockLength) {
ret.push(message.slice(cursor, cursor + blockLength))
cursor = cursor + blockLength
ret.push(message.slice(cursor, cursor + blockLength));
cursor = cursor + blockLength;
}
// Append any remaining blocks regardless of block length. These will be padded later.
if (message.length - cursor > 0) {
ret.push(message.slice(cursor))
ret.push(message.slice(cursor));
}
// If no message, return a single block.
if (message.length === 0) {
ret = [[]]
ret = [[]];
}
// Insert empty array at position 0.
ret.splice(0, 0, [])
return ret
ret.splice(0, 0, []);
return ret;
}
/**
@@ -39,26 +39,26 @@ function parse (message: number[], blockLength: number): number[][] {
* @param padLength The length to pad the message to.
* @returns A padded block.
*/
function pad (message: number[], padLength: number): number[] {
function pad(message: number[], padLength: number): number[] {
// If there is no message, return a fully padded block.
if (message.length === 0) {
return Array(16)
return Array(16);
}
// Return a copy of the message if no padding is required.
if (message.length === padLength) {
return Array.from(message)
return Array.from(message);
}
// Pad a copy of the message to padLength.
const ret = Array.from(message)
const requiredPadding = padLength - message.length - 1
ret.push(...Array(requiredPadding))
const ret = Array.from(message);
const requiredPadding = padLength - message.length - 1;
ret.push(...Array(requiredPadding));
// Set the final byte of the padded blocked to the length of the original message.
ret[padLength - 1] = message.length
ret[padLength - 1] = message.length;
return ret
return ret;
}
/**
@@ -66,10 +66,10 @@ function pad (message: number[], padLength: number): number[] {
* @param state The state from which the key stream will be generated.
* @returns The key stream.
*/
function g (state: number[]): number[] {
return state.map(x => {
return (x >> 1) ^ (x & 0x80) ^ ((x & 0x01) << 7)
})
function g(state: number[]): number[] {
return state.map((x) => {
return (x >> 1) ^ (x & 0x80) ^ ((x & 0x01) << 7);
});
}
/**
@@ -78,17 +78,19 @@ function g (state: number[]): number[] {
* @param mBlock An M block.
* @returns [S', C] where S' = M ⊕ S and C = M ⊕ G(S)
*/
function rho (state: number[], mBlock: number[]): [number[], number[]] {
function rho(state: number[], mBlock: number[]): [number[], number[]] {
// G(S)
const gOfS = g(state)
const gOfS = g(state);
// C = M ⊕ G(S)
const cBlock = Array.from(Array(16).keys()).map(i => mBlock[i] ^ gOfS[i])
const cBlock = Array.from(Array(16).keys()).map((i) => mBlock[i] ^ gOfS[i]);
// S' = M ⊕ S
const nextState = Array.from(Array(16).keys()).map(i => state[i] ^ mBlock[i])
const nextState = Array.from(Array(16).keys()).map(
(i) => state[i] ^ mBlock[i],
);
return [nextState, cBlock]
return [nextState, cBlock];
}
/**
@@ -97,16 +99,18 @@ function rho (state: number[], mBlock: number[]): [number[], number[]] {
* @param cBlock A C block.
* @returns [S', M] where M = C ⊕ G(S) and S' = C ⊕ M.
*/
function inverseRoh (state: number[], cBlock: number[]): [number[], number[]] {
function inverseRoh(state: number[], cBlock: number[]): [number[], number[]] {
// G(S)
const gOfS = g(state)
const gOfS = g(state);
// M = C ⊕ G(S)
const mBlock = Array.from(Array(16).keys()).map(i => cBlock[i] ^ gOfS[i])
const mBlock = Array.from(Array(16).keys()).map((i) => cBlock[i] ^ gOfS[i]);
// S' = S ⊕ M
const nextState = Array.from(Array(16).keys()).map(i => state[i] ^ mBlock[i])
return [nextState, mBlock]
const nextState = Array.from(Array(16).keys()).map(
(i) => state[i] ^ mBlock[i],
);
return [nextState, mBlock];
}
/**
@@ -114,33 +118,33 @@ function inverseRoh (state: number[], cBlock: number[]): [number[], number[]] {
* @param counter The old counter.
* @returns An incremented counter.
*/
function increaseCounter (counter: number[]): number[] {
const fb0 = counter[6] >> 7
function increaseCounter(counter: number[]): number[] {
const fb0 = counter[6] >> 7;
counter[6] = (counter[6] << 1) | (counter[5] >> 7)
counter[5] = (counter[5] << 1) | (counter[4] >> 7)
counter[4] = (counter[4] << 1) | (counter[3] >> 7)
counter[3] = (counter[3] << 1) | (counter[2] >> 7)
counter[2] = (counter[2] << 1) | (counter[1] >> 7)
counter[1] = (counter[1] << 1) | (counter[0] >> 7)
counter[6] = (counter[6] << 1) | (counter[5] >> 7);
counter[5] = (counter[5] << 1) | (counter[4] >> 7);
counter[4] = (counter[4] << 1) | (counter[3] >> 7);
counter[3] = (counter[3] << 1) | (counter[2] >> 7);
counter[2] = (counter[2] << 1) | (counter[1] >> 7);
counter[1] = (counter[1] << 1) | (counter[0] >> 7);
if (fb0 === 1) {
counter[0] = (counter[0] << 1) ^ 0x95
counter[0] = (counter[0] << 1) ^ 0x95;
} else {
counter[0] = (counter[0] << 1)
counter[0] = counter[0] << 1;
}
return counter
return counter;
}
/**
* Returns a reset counter.
* @returns A reset counter.
*/
function resetCounter (): number[] {
const counter = Array(COUNTER_LENGTH)
counter[0] = 1
return counter
function resetCounter(): number[] {
const counter = Array(COUNTER_LENGTH);
counter[0] = 1;
return counter;
}
/**
@@ -149,26 +153,32 @@ function resetCounter (): number[] {
* @param parsedMessageLength The length of the parsed message.
* @param parsedAssociatedDataLength The length of the parsed associated data.
*/
function calculateDomainSeparation (combinedData: number[][], parsedMessageLength: number, parsedAssociatedDataLength: number): number {
let domainSeparation = 16
function calculateDomainSeparation(
combinedData: number[][],
parsedMessageLength: number,
parsedAssociatedDataLength: number,
): number {
let domainSeparation = 16;
if (combinedData[parsedAssociatedDataLength].length < 16) {
domainSeparation = domainSeparation ^ 2
domainSeparation = domainSeparation ^ 2;
}
if (combinedData[parsedAssociatedDataLength + parsedMessageLength].length < 16) {
domainSeparation = domainSeparation ^ 1
if (
combinedData[parsedAssociatedDataLength + parsedMessageLength].length < 16
) {
domainSeparation = domainSeparation ^ 1;
}
if (parsedAssociatedDataLength % 2 === 0) {
domainSeparation = domainSeparation ^ 8
domainSeparation = domainSeparation ^ 8;
}
if (parsedMessageLength % 2 === 0) {
domainSeparation = domainSeparation ^ 4
domainSeparation = domainSeparation ^ 4;
}
return domainSeparation
return domainSeparation;
}
/**
@@ -180,93 +190,124 @@ function calculateDomainSeparation (combinedData: number[][], parsedMessageLengt
* @param key A 128 bit encryption key.
* @returns The encrypted ciphertext.
*/
export function cryptoAeadEncrypt (message: number[], associatedData: number[], nonce: number[], key: number[]): number[] {
export function cryptoAeadEncrypt(
message: number[],
associatedData: number[],
nonce: number[],
key: number[],
): number[] {
// Buffer for ciphertext.
const ciphertext = []
const ciphertext = [];
// Reset state and counter.
let state = Array(16)
let counter = resetCounter()
let state = Array(16);
let counter = resetCounter();
// Carve message and associated data into blocks.
const messageBlocks = parse(message, 16)
const messageBlockCount = messageBlocks.length - 1
const messageBlocks = parse(message, 16);
const messageBlockCount = messageBlocks.length - 1;
const associatedDataBlocks = parse(associatedData, 16)
const associatedDataBlockCount = associatedDataBlocks.length - 1
const associatedDataBlocks = parse(associatedData, 16);
const associatedDataBlockCount = associatedDataBlocks.length - 1;
// Concatenate the message and associated data blocks, excluding each array's first element.
const combinedDataBlocks = associatedDataBlocks.slice(1).concat(messageBlocks.slice(1))
const combinedDataBlocks = associatedDataBlocks
.slice(1)
.concat(messageBlocks.slice(1));
// Insert empty array at position 0.
combinedDataBlocks.splice(0, 0, [])
combinedDataBlocks.splice(0, 0, []);
// Calculate domain separation for final encryption stage.
const domainSeparation = calculateDomainSeparation(combinedDataBlocks, messageBlockCount, associatedDataBlockCount)
const domainSeparation = calculateDomainSeparation(
combinedDataBlocks,
messageBlockCount,
associatedDataBlockCount,
);
// Pad combined data.
combinedDataBlocks[associatedDataBlockCount] = pad(combinedDataBlocks[associatedDataBlockCount], 16)
combinedDataBlocks[associatedDataBlockCount + messageBlockCount] = pad(combinedDataBlocks[associatedDataBlockCount + messageBlockCount], 16)
combinedDataBlocks[associatedDataBlockCount] = pad(
combinedDataBlocks[associatedDataBlockCount],
16,
);
combinedDataBlocks[associatedDataBlockCount + messageBlockCount] = pad(
combinedDataBlocks[associatedDataBlockCount + messageBlockCount],
16,
);
// Process the associated data.
let x = 8
for (let i = 1; i < Math.floor((associatedDataBlockCount + messageBlockCount) / 2) + 1; i++) {
[state] = rho(state, combinedDataBlocks[2 * i - 1])
counter = increaseCounter(counter)
let x = 8;
for (
let i = 1;
i < Math.floor((associatedDataBlockCount + messageBlockCount) / 2) + 1;
i++
) {
[state] = rho(state, combinedDataBlocks[2 * i - 1]);
counter = increaseCounter(counter);
if (i === Math.floor(associatedDataBlockCount / 2) + 1) {
x = x ^ 4
x = x ^ 4;
}
state = skinnyEncrypt(state, tweakeyEncode(counter, x, combinedDataBlocks[2 * i], key))
counter = increaseCounter(counter)
state = skinnyEncrypt(
state,
tweakeyEncode(counter, x, combinedDataBlocks[2 * i], key),
);
counter = increaseCounter(counter);
}
if (associatedDataBlockCount % 2 === messageBlockCount % 2) {
[state] = rho(state, Array(16))
[state] = rho(state, Array(16));
} else {
[state] = rho(state, combinedDataBlocks[associatedDataBlockCount + messageBlockCount])
counter = increaseCounter(counter)
[state] = rho(
state,
combinedDataBlocks[associatedDataBlockCount + messageBlockCount],
);
counter = increaseCounter(counter);
}
// Generate authentication tag.
const [,authenticationTag] = rho(skinnyEncrypt(state, tweakeyEncode(counter, domainSeparation, nonce, key)), Array(16))
const [, authenticationTag] = rho(
skinnyEncrypt(state, tweakeyEncode(counter, domainSeparation, nonce, key)),
Array(16),
);
if (message.length === 0) {
return authenticationTag
return authenticationTag;
}
state = Array.from(authenticationTag)
counter = resetCounter()
state = Array.from(authenticationTag);
counter = resetCounter();
// Encrypt the message.
const originalFinalMessageBlockLength = messageBlocks[messageBlockCount].length
messageBlocks[messageBlockCount] = pad(messageBlocks[messageBlockCount], 16)
const originalFinalMessageBlockLength =
messageBlocks[messageBlockCount].length;
messageBlocks[messageBlockCount] = pad(messageBlocks[messageBlockCount], 16);
for (let i = 1; i < messageBlockCount + 1; i++) {
state = skinnyEncrypt(state, tweakeyEncode(counter, 4, nonce, key))
state = skinnyEncrypt(state, tweakeyEncode(counter, 4, nonce, key));
let cBlock
[state, cBlock] = rho(state, messageBlocks[i])
counter = increaseCounter(counter)
let cBlock;
[state, cBlock] = rho(state, messageBlocks[i]);
counter = increaseCounter(counter);
if (i < messageBlockCount) {
ciphertext.push(...cBlock)
ciphertext.push(...cBlock);
} else {
ciphertext.push(...cBlock.slice(0, originalFinalMessageBlockLength))
ciphertext.push(...cBlock.slice(0, originalFinalMessageBlockLength));
}
}
// Store the authentication tag in the final 16 bytes of the ciphertext.
ciphertext.push(...authenticationTag)
ciphertext.push(...authenticationTag);
return ciphertext
return ciphertext;
}
/**
* Return interface for decrypting a message.
*/
export interface DecryptResult {
success: boolean
plaintext: number[]
success: boolean;
plaintext: number[];
}
/**
@@ -278,106 +319,140 @@ export interface DecryptResult {
* @param key The key.
* @returns The decrypted plaintext.
*/
export function cryptoAeadDecrypt (ciphertext: number[], associatedData: number[], nonce: number[], key: number[]): DecryptResult {
export function cryptoAeadDecrypt(
ciphertext: number[],
associatedData: number[],
nonce: number[],
key: number[],
): DecryptResult {
// Buffer for decrypted message.
const cleartext = []
const cleartext = [];
// The authentication tag is represented by the final 16 bytes of the ciphertext.
const authenticationTag = ciphertext.slice(-16)
ciphertext.length -= 16
const authenticationTag = ciphertext.slice(-16);
ciphertext.length -= 16;
// Reset state and counter.
let state = Array(16)
let counter = resetCounter()
let state = Array(16);
let counter = resetCounter();
if (ciphertext.length !== 0) {
// Combine the ciphertext.
state = Array.from(authenticationTag)
const ciphertextBlocks = parse(ciphertext, 16)
const ciphertextBlockCount = ciphertextBlocks.length - 1
const finalCiphertextBlockLength = ciphertextBlocks[ciphertextBlockCount].length
ciphertextBlocks[ciphertextBlockCount] = pad(ciphertextBlocks[ciphertextBlockCount], 16)
state = Array.from(authenticationTag);
const ciphertextBlocks = parse(ciphertext, 16);
const ciphertextBlockCount = ciphertextBlocks.length - 1;
const finalCiphertextBlockLength =
ciphertextBlocks[ciphertextBlockCount].length;
ciphertextBlocks[ciphertextBlockCount] = pad(
ciphertextBlocks[ciphertextBlockCount],
16,
);
for (let i = 1; i < ciphertextBlockCount + 1; i++) {
state = skinnyEncrypt(state, tweakeyEncode(counter, 4, nonce, key))
state = skinnyEncrypt(state, tweakeyEncode(counter, 4, nonce, key));
let mBlock
[state, mBlock] = inverseRoh(state, ciphertextBlocks[i])
counter = increaseCounter(counter)
let mBlock;
[state, mBlock] = inverseRoh(state, ciphertextBlocks[i]);
counter = increaseCounter(counter);
if (i < ciphertextBlockCount) {
cleartext.push(...mBlock)
cleartext.push(...mBlock);
} else {
cleartext.push(...mBlock.slice(0, finalCiphertextBlockLength))
cleartext.push(...mBlock.slice(0, finalCiphertextBlockLength));
}
}
} else {
state = []
state = [];
}
// Reset state and counter.
state = Array(16)
counter = resetCounter()
state = Array(16);
counter = resetCounter();
// Carve the message and associated data into blocks.
const messageBlocks = parse(cleartext, 16)
const messageBlockLength = messageBlocks.length - 1
const messageBlocks = parse(cleartext, 16);
const messageBlockLength = messageBlocks.length - 1;
const associatedDataBlocks = parse(associatedData, 16)
const associatedDataBlockCount = associatedDataBlocks.length - 1
const associatedDataBlocks = parse(associatedData, 16);
const associatedDataBlockCount = associatedDataBlocks.length - 1;
// Concatenate the message and associated data blocks, excluding each array's first element.
const combinedData = associatedDataBlocks.slice(1).concat(messageBlocks.slice(1))
const combinedData = associatedDataBlocks
.slice(1)
.concat(messageBlocks.slice(1));
// Insert empty array at position 0.
combinedData.splice(0, 0, [])
combinedData.splice(0, 0, []);
// Calculate domain separation for final decryption stage.
const domainSeparation = calculateDomainSeparation(combinedData, messageBlockLength, associatedDataBlockCount)
const domainSeparation = calculateDomainSeparation(
combinedData,
messageBlockLength,
associatedDataBlockCount,
);
// Pad combined data.
combinedData[associatedDataBlockCount] = pad(combinedData[associatedDataBlockCount], 16)
combinedData[associatedDataBlockCount + messageBlockLength] = pad(combinedData[associatedDataBlockCount + messageBlockLength], 16)
combinedData[associatedDataBlockCount] = pad(
combinedData[associatedDataBlockCount],
16,
);
combinedData[associatedDataBlockCount + messageBlockLength] = pad(
combinedData[associatedDataBlockCount + messageBlockLength],
16,
);
// Verifiy associated data.
let x = 8
for (let i = 1; i < Math.floor((associatedDataBlockCount + messageBlockLength) / 2) + 1; i++) {
[state] = rho(state, combinedData[2 * i - 1])
counter = increaseCounter(counter)
let x = 8;
for (
let i = 1;
i < Math.floor((associatedDataBlockCount + messageBlockLength) / 2) + 1;
i++
) {
[state] = rho(state, combinedData[2 * i - 1]);
counter = increaseCounter(counter);
if (i === Math.floor(associatedDataBlockCount / 2) + 1) {
x = x ^ 4
x = x ^ 4;
}
state = skinnyEncrypt(state, tweakeyEncode(counter, x, combinedData[2 * i], key))
counter = increaseCounter(counter)
state = skinnyEncrypt(
state,
tweakeyEncode(counter, x, combinedData[2 * i], key),
);
counter = increaseCounter(counter);
}
if (associatedDataBlockCount % 2 === messageBlockLength % 2) {
[state] = rho(state, Array(16))
[state] = rho(state, Array(16));
} else {
[state] = rho(state, combinedData[associatedDataBlockCount + messageBlockLength])
counter = increaseCounter(counter)
[state] = rho(
state,
combinedData[associatedDataBlockCount + messageBlockLength],
);
counter = increaseCounter(counter);
}
// Calculate authentication tag.
const [,computedTag] = rho(skinnyEncrypt(state, tweakeyEncode(counter, domainSeparation, nonce, key)), Array(16))
const [, computedTag] = rho(
skinnyEncrypt(state, tweakeyEncode(counter, domainSeparation, nonce, key)),
Array(16),
);
// Validate authentication tag.
let compare = 0
let compare = 0;
for (let i = 0; i < 16; i++) {
compare |= (authenticationTag[i] ^ computedTag[i])
compare |= authenticationTag[i] ^ computedTag[i];
}
if (compare !== 0) {
// Authentication failed.
return {
success: false,
plaintext: []
}
plaintext: [],
};
} else {
// Decrypted successfully.
return {
success: true,
plaintext: cleartext
}
plaintext: cleartext,
};
}
}

View File

@@ -1,4 +1,13 @@
import { MEMBER_MASK, NB_ROUNDS, TWEAK_LENGTH, PT, LFSR_8_TK2, LFSR_8_TK3, S8, C } from './constants'
import {
MEMBER_MASK,
NB_ROUNDS,
TWEAK_LENGTH,
PT,
LFSR_8_TK2,
LFSR_8_TK3,
S8,
C,
} from "./constants";
/**
* Create a tweakey based on the specified domain separation, nonce, key and current counter state.
@@ -8,8 +17,13 @@ import { MEMBER_MASK, NB_ROUNDS, TWEAK_LENGTH, PT, LFSR_8_TK2, LFSR_8_TK3, S8, C
* @param key The encryption key.
* @returns The tweakey.
*/
export function tweakeyEncode (counter: number[], domainSeparation: number, nonce: number[], key: number[]): number[] {
return counter.concat([domainSeparation ^ MEMBER_MASK], Array(8), nonce, key)
export function tweakeyEncode(
counter: number[],
domainSeparation: number,
nonce: number[],
key: number[],
): number[] {
return counter.concat([domainSeparation ^ MEMBER_MASK], Array(8), nonce, key);
}
/**
@@ -18,48 +32,68 @@ export function tweakeyEncode (counter: number[], domainSeparation: number, nonc
* @param tweakey The tweakey to use for encryption.
* @returns The ciphertext.
*/
export function skinnyEncrypt (plaintext: number[], tweakey: number[]): number[] {
const tk = Array(NB_ROUNDS + 1).fill(Array(TWEAK_LENGTH).fill(0))
export function skinnyEncrypt(
plaintext: number[],
tweakey: number[],
): number[] {
const tk = Array(NB_ROUNDS + 1).fill(Array(TWEAK_LENGTH).fill(0));
tk[0] = Array.from(Array(TWEAK_LENGTH).keys()).map(i => tweakey[i])
tk[0] = Array.from(Array(TWEAK_LENGTH).keys()).map((i) => tweakey[i]);
for (let i = 0; i < NB_ROUNDS - 1; i++) {
tk[i + 1] = Array.from(tk[i])
tk[i + 1] = Array.from(tk[i]);
for (let j = 0; j < TWEAK_LENGTH; j++) {
tk[i + 1][j] = tk[i][j - j % 16 + PT[j % 16]]
tk[i + 1][j] = tk[i][j - (j % 16) + PT[j % 16]];
}
for (let j = 0; j < 8; j++) {
tk[i + 1][j + 16] = LFSR_8_TK2[tk[i + 1][j + 16]]
tk[i + 1][j + 32] = LFSR_8_TK3[tk[i + 1][j + 32]]
tk[i + 1][j + 16] = LFSR_8_TK2[tk[i + 1][j + 16]];
tk[i + 1][j + 32] = LFSR_8_TK3[tk[i + 1][j + 32]];
}
}
let s = Array.from(Array(16).keys()).map(i => plaintext[i])
let s = Array.from(Array(16).keys()).map((i) => plaintext[i]);
for (let i = 0; i < NB_ROUNDS; i++) {
for (let j = 0; j < 16; j++) {
s[j] = S8[s[j]]
s[j] = S8[s[j]];
}
s[0] ^= (C[i] & 0xf)
s[4] ^= (C[i] >> 4) & 0xf
s[8] ^= 0x2
s[0] ^= C[i] & 0xf;
s[4] ^= (C[i] >> 4) & 0xf;
s[8] ^= 0x2;
for (let j = 0; j < 8; j++) {
s[j] ^= tk[i][j] ^ tk[i][j + 16] ^ tk[i][j + 32]
s[j] ^= tk[i][j] ^ tk[i][j + 16] ^ tk[i][j + 32];
}
s = [s[0], s[1], s[2], s[3], s[7], s[4], s[5], s[6], s[10], s[11], s[8], s[9], s[13], s[14], s[15], s[12]]
s = [
s[0],
s[1],
s[2],
s[3],
s[7],
s[4],
s[5],
s[6],
s[10],
s[11],
s[8],
s[9],
s[13],
s[14],
s[15],
s[12],
];
for (let j = 0; j < 4; j++) {
const tmp = Array.from(s)
s[j] = tmp[j] ^ tmp[8 + j] ^ tmp[12 + j]
s[4 + j] = tmp[j]
s[8 + j] = tmp[4 + j] ^ tmp[8 + j]
s[12 + j] = tmp[0 + j] ^ tmp[8 + j]
const tmp = Array.from(s);
s[j] = tmp[j] ^ tmp[8 + j] ^ tmp[12 + j];
s[4 + j] = tmp[j];
s[8 + j] = tmp[4 + j] ^ tmp[8 + j];
s[12 + j] = tmp[0 + j] ^ tmp[8 + j];
}
}
return Array.from(Array(16).keys()).map(i => s[i])
return Array.from(Array(16).keys()).map((i) => s[i]);
}

View File

@@ -1,44 +1,54 @@
import { decrypt } from '../src/decrypt'
import { decrypt } from "../src/decrypt";
test('Test nonce parsing by public decrypt function.', () => {
test("Test nonce parsing by public decrypt function.", () => {
// Given
const ciphertext = Uint8Array.from([
// Nonce
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
// Ciphertext
225, 53, 3, 212, 22, 112, 246, 194, 61, 171, 230, 187, 157, 102, 32, 76, 62, 65,
25, 202, 255, 201, 206, 49, 60, 58, 82, 216, 72, 116, 106, 129, 162, 142, 69, 40,
167, 88, 94, 195, 174, 217, 242, 149, 224, 125, 196, 237, 172, 165, 116, 119, 128
])
const associatedData = new TextEncoder().encode('Some associated data.')
const key = Uint8Array.from([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f])
225, 53, 3, 212, 22, 112, 246, 194, 61, 171, 230, 187, 157, 102, 32, 76, 62,
65, 25, 202, 255, 201, 206, 49, 60, 58, 82, 216, 72, 116, 106, 129, 162,
142, 69, 40, 167, 88, 94, 195, 174, 217, 242, 149, 224, 125, 196, 237, 172,
165, 116, 119, 128,
]);
const associatedData = new TextEncoder().encode("Some associated data.");
const key = Uint8Array.from([
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f,
]);
// When
const result = decrypt(ciphertext, associatedData, key)
const result = decrypt(ciphertext, associatedData, key);
// Then
const expectedResult = new TextEncoder().encode('Hello, World! This is a test message.')
expect(result.success).toBe(true)
expect(result.plaintext).toMatchObject(expectedResult)
})
const expectedResult = new TextEncoder().encode(
"Hello, World! This is a test message.",
);
expect(result.success).toBe(true);
expect(result.plaintext).toMatchObject(expectedResult);
});
test('Test decryption with an invalid key.', () => {
test("Test decryption with an invalid key.", () => {
// Given
const ciphertext = Uint8Array.from([
// Nonce
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
// Ciphertext
225, 53, 3, 212, 22, 112, 246, 194, 61, 171, 230, 187, 157, 102, 32, 76, 62, 65,
25, 202, 255, 201, 206, 49, 60, 58, 82, 216, 72, 116, 106, 129, 162, 142, 69, 40,
167, 88, 94, 195, 174, 217, 242, 149, 224, 125, 196, 237, 172, 165, 116, 119, 128
])
const associatedData = new TextEncoder().encode('Some associated data.')
const key = Uint8Array.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
225, 53, 3, 212, 22, 112, 246, 194, 61, 171, 230, 187, 157, 102, 32, 76, 62,
65, 25, 202, 255, 201, 206, 49, 60, 58, 82, 216, 72, 116, 106, 129, 162,
142, 69, 40, 167, 88, 94, 195, 174, 217, 242, 149, 224, 125, 196, 237, 172,
165, 116, 119, 128,
]);
const associatedData = new TextEncoder().encode("Some associated data.");
const key = Uint8Array.from([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
]);
// When
const result = decrypt(ciphertext, associatedData, key)
const result = decrypt(ciphertext, associatedData, key);
// Then
expect(result.success).toBe(false)
expect(result.plaintext).toMatchObject(new Uint8Array())
})
expect(result.success).toBe(false);
expect(result.plaintext).toMatchObject(new Uint8Array());
});

View File

@@ -1,17 +1,22 @@
import { decrypt } from '../src/decrypt'
import { encrypt } from '../src/encrypt'
import { decrypt } from "../src/decrypt";
import { encrypt } from "../src/encrypt";
test('Test nonce generation by public encrypt function.', () => {
test("Test nonce generation by public encrypt function.", () => {
// Given
const message = new TextEncoder().encode('Hello, World! This is a test message.')
const associatedData = new TextEncoder().encode('Some associated data.')
const key = Uint8Array.from([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f])
const message = new TextEncoder().encode(
"Hello, World! This is a test message.",
);
const associatedData = new TextEncoder().encode("Some associated data.");
const key = Uint8Array.from([
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f,
]);
// When
const ciphertext = encrypt(message, associatedData, key)
const plaintext = decrypt(ciphertext, associatedData, key)
const ciphertext = encrypt(message, associatedData, key);
const plaintext = decrypt(ciphertext, associatedData, key);
// Then
expect(plaintext.success).toBe(true)
expect(plaintext.plaintext).toMatchObject(message)
})
expect(plaintext.success).toBe(true);
expect(plaintext.plaintext).toMatchObject(message);
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,32 +1,52 @@
import { referenceTests } from './resources/reference-tests'
import { cryptoAeadDecrypt, cryptoAeadEncrypt, DecryptResult } from '../src/romulus-m'
import { referenceTests } from "./resources/reference-tests";
import {
cryptoAeadDecrypt,
cryptoAeadEncrypt,
DecryptResult,
} from "../src/romulus-m";
function parseHexString (string: string): number[] {
const ret = []
function parseHexString(string: string): number[] {
const ret = [];
for (let i = 0; i < string.length; i += 2) {
ret.push(parseInt(string.slice(i, i + 2), 16))
ret.push(parseInt(string.slice(i, i + 2), 16));
}
return ret
return ret;
}
test.each(referenceTests)('Perform encryption using reference test %#.', (key, nonce, plaintext, associatedData, ciphertext) => {
// When
const result = cryptoAeadEncrypt(parseHexString(plaintext), parseHexString(associatedData), parseHexString(nonce), parseHexString(key))
test.each(referenceTests)(
"Perform encryption using reference test %#.",
(key, nonce, plaintext, associatedData, ciphertext) => {
// When
const result = cryptoAeadEncrypt(
parseHexString(plaintext),
parseHexString(associatedData),
parseHexString(nonce),
parseHexString(key),
);
// Then
const expectedResult = parseHexString(ciphertext)
expect(result).toMatchObject(expectedResult)
})
// Then
const expectedResult = parseHexString(ciphertext);
expect(result).toMatchObject(expectedResult);
},
);
test.each(referenceTests)('Perform decryption using reference test %#.', (key, nonce, plaintext, associatedData, ciphertext) => {
// When
const result = cryptoAeadDecrypt(parseHexString(ciphertext), parseHexString(associatedData), parseHexString(nonce), parseHexString(key))
test.each(referenceTests)(
"Perform decryption using reference test %#.",
(key, nonce, plaintext, associatedData, ciphertext) => {
// When
const result = cryptoAeadDecrypt(
parseHexString(ciphertext),
parseHexString(associatedData),
parseHexString(nonce),
parseHexString(key),
);
// Then
const expectedResult: DecryptResult = {
success: true,
plaintext: parseHexString(plaintext)
}
expect(result).toMatchObject(expectedResult)
})
// Then
const expectedResult: DecryptResult = {
success: true,
plaintext: parseHexString(plaintext),
};
expect(result).toMatchObject(expectedResult);
},
);

View File

@@ -1,84 +1,104 @@
import { cryptoAeadDecrypt, cryptoAeadEncrypt } from '../src/romulus-m'
import { cryptoAeadDecrypt, cryptoAeadEncrypt } from "../src/romulus-m";
function stringToArray (string: string): number[] {
const encoder = new TextEncoder()
return Array.from(encoder.encode(string))
function stringToArray(string: string): number[] {
const encoder = new TextEncoder();
return Array.from(encoder.encode(string));
}
test('Encrypt a message with no associated data.', () => {
test("Encrypt a message with no associated data.", () => {
// Given
const message = stringToArray('Hello, World! This is a test message.')
const associatedData = stringToArray('')
const nonce = stringToArray('\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f')
const key = stringToArray('\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f')
const message = stringToArray("Hello, World! This is a test message.");
const associatedData = stringToArray("");
const nonce = stringToArray(
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
);
const key = stringToArray(
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
);
// When
const result = cryptoAeadEncrypt(message, associatedData, nonce, key)
const result = cryptoAeadEncrypt(message, associatedData, nonce, key);
// Then
const expectedResult = [
85, 125, 23, 244, 73, 241, 140, 72, 166, 113, 114, 78, 239, 211, 84, 113, 222,
153, 207, 183, 69, 142, 174, 15, 38, 46, 112, 162, 229, 27, 136, 184, 163, 78,
132, 42, 107, 160, 74, 115, 28, 251, 209, 37, 48, 57, 184, 204, 199, 247, 93, 5, 208
]
expect(result).toMatchObject(expectedResult)
})
85, 125, 23, 244, 73, 241, 140, 72, 166, 113, 114, 78, 239, 211, 84, 113,
222, 153, 207, 183, 69, 142, 174, 15, 38, 46, 112, 162, 229, 27, 136, 184,
163, 78, 132, 42, 107, 160, 74, 115, 28, 251, 209, 37, 48, 57, 184, 204,
199, 247, 93, 5, 208,
];
expect(result).toMatchObject(expectedResult);
});
test('Encrypt a message with associated data.', () => {
test("Encrypt a message with associated data.", () => {
// Given
const message = stringToArray('Hello, World! This is a test message.')
const associatedData = stringToArray('Some associated data.')
const nonce = stringToArray('\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f')
const key = stringToArray('\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f')
const message = stringToArray("Hello, World! This is a test message.");
const associatedData = stringToArray("Some associated data.");
const nonce = stringToArray(
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
);
const key = stringToArray(
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
);
// When
const result = cryptoAeadEncrypt(message, associatedData, nonce, key)
const result = cryptoAeadEncrypt(message, associatedData, nonce, key);
// Then
const expectedResult = [
225, 53, 3, 212, 22, 112, 246, 194, 61, 171, 230, 187, 157, 102, 32, 76, 62, 65,
25, 202, 255, 201, 206, 49, 60, 58, 82, 216, 72, 116, 106, 129, 162, 142, 69, 40,
167, 88, 94, 195, 174, 217, 242, 149, 224, 125, 196, 237, 172, 165, 116, 119, 128
]
expect(result).toMatchObject(expectedResult)
})
225, 53, 3, 212, 22, 112, 246, 194, 61, 171, 230, 187, 157, 102, 32, 76, 62,
65, 25, 202, 255, 201, 206, 49, 60, 58, 82, 216, 72, 116, 106, 129, 162,
142, 69, 40, 167, 88, 94, 195, 174, 217, 242, 149, 224, 125, 196, 237, 172,
165, 116, 119, 128,
];
expect(result).toMatchObject(expectedResult);
});
test('Decrypt a message with no associated data.', () => {
test("Decrypt a message with no associated data.", () => {
// Given
const ciphertext = [
85, 125, 23, 244, 73, 241, 140, 72, 166, 113, 114, 78, 239, 211, 84, 113, 222,
153, 207, 183, 69, 142, 174, 15, 38, 46, 112, 162, 229, 27, 136, 184, 163, 78,
132, 42, 107, 160, 74, 115, 28, 251, 209, 37, 48, 57, 184, 204, 199, 247, 93, 5, 208
]
const associatedData = stringToArray('')
const nonce = stringToArray('\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f')
const key = stringToArray('\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f')
85, 125, 23, 244, 73, 241, 140, 72, 166, 113, 114, 78, 239, 211, 84, 113,
222, 153, 207, 183, 69, 142, 174, 15, 38, 46, 112, 162, 229, 27, 136, 184,
163, 78, 132, 42, 107, 160, 74, 115, 28, 251, 209, 37, 48, 57, 184, 204,
199, 247, 93, 5, 208,
];
const associatedData = stringToArray("");
const nonce = stringToArray(
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
);
const key = stringToArray(
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
);
// When
const result = cryptoAeadDecrypt(ciphertext, associatedData, nonce, key)
const result = cryptoAeadDecrypt(ciphertext, associatedData, nonce, key);
// Then
const expectedResult = stringToArray('Hello, World! This is a test message.')
expect(result.success).toBe(true)
expect(result.plaintext).toMatchObject(expectedResult)
})
const expectedResult = stringToArray("Hello, World! This is a test message.");
expect(result.success).toBe(true);
expect(result.plaintext).toMatchObject(expectedResult);
});
test('Decrypt a message with associated data.', () => {
test("Decrypt a message with associated data.", () => {
// Given
const ciphertext = [
225, 53, 3, 212, 22, 112, 246, 194, 61, 171, 230, 187, 157, 102, 32, 76, 62, 65,
25, 202, 255, 201, 206, 49, 60, 58, 82, 216, 72, 116, 106, 129, 162, 142, 69, 40,
167, 88, 94, 195, 174, 217, 242, 149, 224, 125, 196, 237, 172, 165, 116, 119, 128
]
const associatedData = stringToArray('Some associated data.')
const nonce = stringToArray('\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f')
const key = stringToArray('\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f')
225, 53, 3, 212, 22, 112, 246, 194, 61, 171, 230, 187, 157, 102, 32, 76, 62,
65, 25, 202, 255, 201, 206, 49, 60, 58, 82, 216, 72, 116, 106, 129, 162,
142, 69, 40, 167, 88, 94, 195, 174, 217, 242, 149, 224, 125, 196, 237, 172,
165, 116, 119, 128,
];
const associatedData = stringToArray("Some associated data.");
const nonce = stringToArray(
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
);
const key = stringToArray(
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
);
// When
const result = cryptoAeadDecrypt(ciphertext, associatedData, nonce, key)
const result = cryptoAeadDecrypt(ciphertext, associatedData, nonce, key);
// Then
const expectedResult = stringToArray('Hello, World! This is a test message.')
expect(result.success).toBe(true)
expect(result.plaintext).toMatchObject(expectedResult)
})
const expectedResult = stringToArray("Hello, World! This is a test message.");
expect(result.success).toBe(true);
expect(result.plaintext).toMatchObject(expectedResult);
});

View File

@@ -1,19 +1,16 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"module": "commonjs", /* Specify what module code is generated. */
"rootDir": "src", /* Specify the root folder within your source files. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
"outDir": "dist", /* Specify an output folder for all emitted files. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"strict": true, /* Enable all strict type-checking options. */
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"module": "commonjs" /* Specify what module code is generated. */,
"rootDir": "src" /* Specify the root folder within your source files. */,
"sourceMap": true /* Create source map files for emitted JavaScript files. */,
"outDir": "dist" /* Specify an output folder for all emitted files. */,
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
"strict": true /* Enable all strict type-checking options. */,
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
"declaration": true
},
"exclude": [
"tests",
"dist"
]
"exclude": ["tests", "dist"]
}