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": [ "recommendations": ["orta.vscode-jest"]
"orta.vscode-jest"
]
} }

32
.vscode/launch.json vendored
View File

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

30
.vscode/tasks.json vendored
View File

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

View File

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

View File

@@ -1,8 +1,8 @@
import { cryptoAeadDecrypt } from './romulus-m' import { cryptoAeadDecrypt } from "./romulus-m";
interface DecryptResult { interface DecryptResult {
success: boolean success: boolean;
plaintext: Uint8Array plaintext: Uint8Array;
} }
/** /**
@@ -13,17 +13,26 @@ interface DecryptResult {
* @param key The encryption key. * @param key The encryption key.
* @returns A decrypted DecryptResult object. * @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. // Split nonce from ciphertext.
const nonce = Array.from(buffer.slice(0, 16)) const nonce = Array.from(buffer.slice(0, 16));
const ciphertext = Array.from(buffer.slice(16)) const ciphertext = Array.from(buffer.slice(16));
// Decrypt ciphertext using the associated data, nonce and encryption key. // 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 the ciphertext and decryption status.
return { return {
success: result.success, 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 { cryptoAeadEncrypt } from "./romulus-m";
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from "uuid";
/** /**
* Encrypt a message using the Romulus-M encryption algorithm. * Encrypt a message using the Romulus-M encryption algorithm.
@@ -9,14 +9,25 @@ import { v4 as uuidv4 } from 'uuid'
* @param key The encryption key. * @param key The encryption key.
* @returns The nonce-prepended ciphertext. * @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. // Generate a nonce.
const nonce = new Uint8Array(16) const nonce = new Uint8Array(16);
uuidv4({}, nonce) uuidv4({}, nonce);
// Encrypt the data using the associated data, newly generated nonce and encryption key. // 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 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 { encrypt } from "./encrypt";
export { decrypt } from './decrypt' export { decrypt } from "./decrypt";

View File

@@ -1,5 +1,5 @@
import { COUNTER_LENGTH } from './constants' import { COUNTER_LENGTH } from "./constants";
import { tweakeyEncode, skinnyEncrypt } from './skinny-128-384-plus' import { tweakeyEncode, skinnyEncrypt } from "./skinny-128-384-plus";
/** /**
* Parse message into blocks. * Parse message into blocks.
@@ -7,30 +7,30 @@ import { tweakeyEncode, skinnyEncrypt } from './skinny-128-384-plus'
* @param blockLength The block length. * @param blockLength The block length.
* @returns An array of blocks. * @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. // Keep track of position in message currently parsed into blocks.
let cursor = 0 let cursor = 0;
// Slice message into blocks. // Slice message into blocks.
let ret: number[][] = [] let ret: number[][] = [];
while (message.length - cursor >= blockLength) { while (message.length - cursor >= blockLength) {
ret.push(message.slice(cursor, cursor + blockLength)) ret.push(message.slice(cursor, cursor + blockLength));
cursor = cursor + blockLength cursor = cursor + blockLength;
} }
// Append any remaining blocks regardless of block length. These will be padded later. // Append any remaining blocks regardless of block length. These will be padded later.
if (message.length - cursor > 0) { if (message.length - cursor > 0) {
ret.push(message.slice(cursor)) ret.push(message.slice(cursor));
} }
// If no message, return a single block. // If no message, return a single block.
if (message.length === 0) { if (message.length === 0) {
ret = [[]] ret = [[]];
} }
// Insert empty array at position 0. // Insert empty array at position 0.
ret.splice(0, 0, []) ret.splice(0, 0, []);
return ret return ret;
} }
/** /**
@@ -39,26 +39,26 @@ function parse (message: number[], blockLength: number): number[][] {
* @param padLength The length to pad the message to. * @param padLength The length to pad the message to.
* @returns A padded block. * @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 there is no message, return a fully padded block.
if (message.length === 0) { if (message.length === 0) {
return Array(16) return Array(16);
} }
// Return a copy of the message if no padding is required. // Return a copy of the message if no padding is required.
if (message.length === padLength) { if (message.length === padLength) {
return Array.from(message) return Array.from(message);
} }
// Pad a copy of the message to padLength. // Pad a copy of the message to padLength.
const ret = Array.from(message) const ret = Array.from(message);
const requiredPadding = padLength - message.length - 1 const requiredPadding = padLength - message.length - 1;
ret.push(...Array(requiredPadding)) ret.push(...Array(requiredPadding));
// Set the final byte of the padded blocked to the length of the original message. // 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. * @param state The state from which the key stream will be generated.
* @returns The key stream. * @returns The key stream.
*/ */
function g (state: number[]): number[] { function g(state: number[]): number[] {
return state.map(x => { return state.map((x) => {
return (x >> 1) ^ (x & 0x80) ^ ((x & 0x01) << 7) return (x >> 1) ^ (x & 0x80) ^ ((x & 0x01) << 7);
}) });
} }
/** /**
@@ -78,17 +78,19 @@ function g (state: number[]): number[] {
* @param mBlock An M block. * @param mBlock An M block.
* @returns [S', C] where S' = M ⊕ S and C = M ⊕ G(S) * @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) // G(S)
const gOfS = g(state) const gOfS = g(state);
// C = M ⊕ G(S) // 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 // 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. * @param cBlock A C block.
* @returns [S', M] where M = C ⊕ G(S) and S' = C ⊕ M. * @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) // G(S)
const gOfS = g(state) const gOfS = g(state);
// M = C ⊕ G(S) // 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 // S' = S ⊕ M
const nextState = Array.from(Array(16).keys()).map(i => state[i] ^ mBlock[i]) const nextState = Array.from(Array(16).keys()).map(
return [nextState, mBlock] (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. * @param counter The old counter.
* @returns An incremented counter. * @returns An incremented counter.
*/ */
function increaseCounter (counter: number[]): number[] { function increaseCounter(counter: number[]): number[] {
const fb0 = counter[6] >> 7 const fb0 = counter[6] >> 7;
counter[6] = (counter[6] << 1) | (counter[5] >> 7) counter[6] = (counter[6] << 1) | (counter[5] >> 7);
counter[5] = (counter[5] << 1) | (counter[4] >> 7) counter[5] = (counter[5] << 1) | (counter[4] >> 7);
counter[4] = (counter[4] << 1) | (counter[3] >> 7) counter[4] = (counter[4] << 1) | (counter[3] >> 7);
counter[3] = (counter[3] << 1) | (counter[2] >> 7) counter[3] = (counter[3] << 1) | (counter[2] >> 7);
counter[2] = (counter[2] << 1) | (counter[1] >> 7) counter[2] = (counter[2] << 1) | (counter[1] >> 7);
counter[1] = (counter[1] << 1) | (counter[0] >> 7) counter[1] = (counter[1] << 1) | (counter[0] >> 7);
if (fb0 === 1) { if (fb0 === 1) {
counter[0] = (counter[0] << 1) ^ 0x95 counter[0] = (counter[0] << 1) ^ 0x95;
} else { } else {
counter[0] = (counter[0] << 1) counter[0] = counter[0] << 1;
} }
return counter return counter;
} }
/** /**
* Returns a reset counter. * Returns a reset counter.
* @returns A reset counter. * @returns A reset counter.
*/ */
function resetCounter (): number[] { function resetCounter(): number[] {
const counter = Array(COUNTER_LENGTH) const counter = Array(COUNTER_LENGTH);
counter[0] = 1 counter[0] = 1;
return counter return counter;
} }
/** /**
@@ -149,26 +153,32 @@ function resetCounter (): number[] {
* @param parsedMessageLength The length of the parsed message. * @param parsedMessageLength The length of the parsed message.
* @param parsedAssociatedDataLength The length of the parsed associated data. * @param parsedAssociatedDataLength The length of the parsed associated data.
*/ */
function calculateDomainSeparation (combinedData: number[][], parsedMessageLength: number, parsedAssociatedDataLength: number): number { function calculateDomainSeparation(
let domainSeparation = 16 combinedData: number[][],
parsedMessageLength: number,
parsedAssociatedDataLength: number,
): number {
let domainSeparation = 16;
if (combinedData[parsedAssociatedDataLength].length < 16) { if (combinedData[parsedAssociatedDataLength].length < 16) {
domainSeparation = domainSeparation ^ 2 domainSeparation = domainSeparation ^ 2;
} }
if (combinedData[parsedAssociatedDataLength + parsedMessageLength].length < 16) { if (
domainSeparation = domainSeparation ^ 1 combinedData[parsedAssociatedDataLength + parsedMessageLength].length < 16
) {
domainSeparation = domainSeparation ^ 1;
} }
if (parsedAssociatedDataLength % 2 === 0) { if (parsedAssociatedDataLength % 2 === 0) {
domainSeparation = domainSeparation ^ 8 domainSeparation = domainSeparation ^ 8;
} }
if (parsedMessageLength % 2 === 0) { 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. * @param key A 128 bit encryption key.
* @returns The encrypted ciphertext. * @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. // Buffer for ciphertext.
const ciphertext = [] const ciphertext = [];
// Reset state and counter. // Reset state and counter.
let state = Array(16) let state = Array(16);
let counter = resetCounter() let counter = resetCounter();
// Carve message and associated data into blocks. // Carve message and associated data into blocks.
const messageBlocks = parse(message, 16) const messageBlocks = parse(message, 16);
const messageBlockCount = messageBlocks.length - 1 const messageBlockCount = messageBlocks.length - 1;
const associatedDataBlocks = parse(associatedData, 16) const associatedDataBlocks = parse(associatedData, 16);
const associatedDataBlockCount = associatedDataBlocks.length - 1 const associatedDataBlockCount = associatedDataBlocks.length - 1;
// Concatenate the message and associated data blocks, excluding each array's first element. // 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. // Insert empty array at position 0.
combinedDataBlocks.splice(0, 0, []) combinedDataBlocks.splice(0, 0, []);
// Calculate domain separation for final encryption stage. // Calculate domain separation for final encryption stage.
const domainSeparation = calculateDomainSeparation(combinedDataBlocks, messageBlockCount, associatedDataBlockCount) const domainSeparation = calculateDomainSeparation(
combinedDataBlocks,
messageBlockCount,
associatedDataBlockCount,
);
// Pad combined data. // Pad combined data.
combinedDataBlocks[associatedDataBlockCount] = pad(combinedDataBlocks[associatedDataBlockCount], 16) combinedDataBlocks[associatedDataBlockCount] = pad(
combinedDataBlocks[associatedDataBlockCount + messageBlockCount] = pad(combinedDataBlocks[associatedDataBlockCount + messageBlockCount], 16) combinedDataBlocks[associatedDataBlockCount],
16,
);
combinedDataBlocks[associatedDataBlockCount + messageBlockCount] = pad(
combinedDataBlocks[associatedDataBlockCount + messageBlockCount],
16,
);
// Process the associated data. // Process the associated data.
let x = 8 let x = 8;
for (let i = 1; i < Math.floor((associatedDataBlockCount + messageBlockCount) / 2) + 1; i++) { for (
[state] = rho(state, combinedDataBlocks[2 * i - 1]) let i = 1;
counter = increaseCounter(counter) 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) { if (i === Math.floor(associatedDataBlockCount / 2) + 1) {
x = x ^ 4 x = x ^ 4;
} }
state = skinnyEncrypt(state, tweakeyEncode(counter, x, combinedDataBlocks[2 * i], key)) state = skinnyEncrypt(
counter = increaseCounter(counter) state,
tweakeyEncode(counter, x, combinedDataBlocks[2 * i], key),
);
counter = increaseCounter(counter);
} }
if (associatedDataBlockCount % 2 === messageBlockCount % 2) { if (associatedDataBlockCount % 2 === messageBlockCount % 2) {
[state] = rho(state, Array(16)) [state] = rho(state, Array(16));
} else { } else {
[state] = rho(state, combinedDataBlocks[associatedDataBlockCount + messageBlockCount]) [state] = rho(
counter = increaseCounter(counter) state,
combinedDataBlocks[associatedDataBlockCount + messageBlockCount],
);
counter = increaseCounter(counter);
} }
// Generate authentication tag. // 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) { if (message.length === 0) {
return authenticationTag return authenticationTag;
} }
state = Array.from(authenticationTag) state = Array.from(authenticationTag);
counter = resetCounter() counter = resetCounter();
// Encrypt the message. // Encrypt the message.
const originalFinalMessageBlockLength = messageBlocks[messageBlockCount].length const originalFinalMessageBlockLength =
messageBlocks[messageBlockCount] = pad(messageBlocks[messageBlockCount], 16) messageBlocks[messageBlockCount].length;
messageBlocks[messageBlockCount] = pad(messageBlocks[messageBlockCount], 16);
for (let i = 1; i < messageBlockCount + 1; i++) { 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 let cBlock;
[state, cBlock] = rho(state, messageBlocks[i]) [state, cBlock] = rho(state, messageBlocks[i]);
counter = increaseCounter(counter) counter = increaseCounter(counter);
if (i < messageBlockCount) { if (i < messageBlockCount) {
ciphertext.push(...cBlock) ciphertext.push(...cBlock);
} else { } 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. // 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. * Return interface for decrypting a message.
*/ */
export interface DecryptResult { export interface DecryptResult {
success: boolean success: boolean;
plaintext: number[] plaintext: number[];
} }
/** /**
@@ -278,106 +319,140 @@ export interface DecryptResult {
* @param key The key. * @param key The key.
* @returns The decrypted plaintext. * @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. // Buffer for decrypted message.
const cleartext = [] const cleartext = [];
// The authentication tag is represented by the final 16 bytes of the ciphertext. // The authentication tag is represented by the final 16 bytes of the ciphertext.
const authenticationTag = ciphertext.slice(-16) const authenticationTag = ciphertext.slice(-16);
ciphertext.length -= 16 ciphertext.length -= 16;
// Reset state and counter. // Reset state and counter.
let state = Array(16) let state = Array(16);
let counter = resetCounter() let counter = resetCounter();
if (ciphertext.length !== 0) { if (ciphertext.length !== 0) {
// Combine the ciphertext. // Combine the ciphertext.
state = Array.from(authenticationTag) state = Array.from(authenticationTag);
const ciphertextBlocks = parse(ciphertext, 16) const ciphertextBlocks = parse(ciphertext, 16);
const ciphertextBlockCount = ciphertextBlocks.length - 1 const ciphertextBlockCount = ciphertextBlocks.length - 1;
const finalCiphertextBlockLength = ciphertextBlocks[ciphertextBlockCount].length const finalCiphertextBlockLength =
ciphertextBlocks[ciphertextBlockCount] = pad(ciphertextBlocks[ciphertextBlockCount], 16) ciphertextBlocks[ciphertextBlockCount].length;
ciphertextBlocks[ciphertextBlockCount] = pad(
ciphertextBlocks[ciphertextBlockCount],
16,
);
for (let i = 1; i < ciphertextBlockCount + 1; i++) { 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 let mBlock;
[state, mBlock] = inverseRoh(state, ciphertextBlocks[i]) [state, mBlock] = inverseRoh(state, ciphertextBlocks[i]);
counter = increaseCounter(counter) counter = increaseCounter(counter);
if (i < ciphertextBlockCount) { if (i < ciphertextBlockCount) {
cleartext.push(...mBlock) cleartext.push(...mBlock);
} else { } else {
cleartext.push(...mBlock.slice(0, finalCiphertextBlockLength)) cleartext.push(...mBlock.slice(0, finalCiphertextBlockLength));
} }
} }
} else { } else {
state = [] state = [];
} }
// Reset state and counter. // Reset state and counter.
state = Array(16) state = Array(16);
counter = resetCounter() counter = resetCounter();
// Carve the message and associated data into blocks. // Carve the message and associated data into blocks.
const messageBlocks = parse(cleartext, 16) const messageBlocks = parse(cleartext, 16);
const messageBlockLength = messageBlocks.length - 1 const messageBlockLength = messageBlocks.length - 1;
const associatedDataBlocks = parse(associatedData, 16) const associatedDataBlocks = parse(associatedData, 16);
const associatedDataBlockCount = associatedDataBlocks.length - 1 const associatedDataBlockCount = associatedDataBlocks.length - 1;
// Concatenate the message and associated data blocks, excluding each array's first element. // 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. // Insert empty array at position 0.
combinedData.splice(0, 0, []) combinedData.splice(0, 0, []);
// Calculate domain separation for final decryption stage. // Calculate domain separation for final decryption stage.
const domainSeparation = calculateDomainSeparation(combinedData, messageBlockLength, associatedDataBlockCount) const domainSeparation = calculateDomainSeparation(
combinedData,
messageBlockLength,
associatedDataBlockCount,
);
// Pad combined data. // Pad combined data.
combinedData[associatedDataBlockCount] = pad(combinedData[associatedDataBlockCount], 16) combinedData[associatedDataBlockCount] = pad(
combinedData[associatedDataBlockCount + messageBlockLength] = pad(combinedData[associatedDataBlockCount + messageBlockLength], 16) combinedData[associatedDataBlockCount],
16,
);
combinedData[associatedDataBlockCount + messageBlockLength] = pad(
combinedData[associatedDataBlockCount + messageBlockLength],
16,
);
// Verifiy associated data. // Verifiy associated data.
let x = 8 let x = 8;
for (let i = 1; i < Math.floor((associatedDataBlockCount + messageBlockLength) / 2) + 1; i++) { for (
[state] = rho(state, combinedData[2 * i - 1]) let i = 1;
counter = increaseCounter(counter) 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) { if (i === Math.floor(associatedDataBlockCount / 2) + 1) {
x = x ^ 4 x = x ^ 4;
} }
state = skinnyEncrypt(state, tweakeyEncode(counter, x, combinedData[2 * i], key)) state = skinnyEncrypt(
counter = increaseCounter(counter) state,
tweakeyEncode(counter, x, combinedData[2 * i], key),
);
counter = increaseCounter(counter);
} }
if (associatedDataBlockCount % 2 === messageBlockLength % 2) { if (associatedDataBlockCount % 2 === messageBlockLength % 2) {
[state] = rho(state, Array(16)) [state] = rho(state, Array(16));
} else { } else {
[state] = rho(state, combinedData[associatedDataBlockCount + messageBlockLength]) [state] = rho(
counter = increaseCounter(counter) state,
combinedData[associatedDataBlockCount + messageBlockLength],
);
counter = increaseCounter(counter);
} }
// Calculate authentication tag. // 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. // Validate authentication tag.
let compare = 0 let compare = 0;
for (let i = 0; i < 16; i++) { for (let i = 0; i < 16; i++) {
compare |= (authenticationTag[i] ^ computedTag[i]) compare |= authenticationTag[i] ^ computedTag[i];
} }
if (compare !== 0) { if (compare !== 0) {
// Authentication failed. // Authentication failed.
return { return {
success: false, success: false,
plaintext: [] plaintext: [],
} };
} else { } else {
// Decrypted successfully. // Decrypted successfully.
return { return {
success: true, 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. * 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. * @param key The encryption key.
* @returns The tweakey. * @returns The tweakey.
*/ */
export function tweakeyEncode (counter: number[], domainSeparation: number, nonce: number[], key: number[]): number[] { export function tweakeyEncode(
return counter.concat([domainSeparation ^ MEMBER_MASK], Array(8), nonce, key) 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. * @param tweakey The tweakey to use for encryption.
* @returns The ciphertext. * @returns The ciphertext.
*/ */
export function skinnyEncrypt (plaintext: number[], tweakey: number[]): number[] { export function skinnyEncrypt(
const tk = Array(NB_ROUNDS + 1).fill(Array(TWEAK_LENGTH).fill(0)) 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++) { 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++) { 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++) { for (let j = 0; j < 8; j++) {
tk[i + 1][j + 16] = LFSR_8_TK2[tk[i + 1][j + 16]] 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 + 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 i = 0; i < NB_ROUNDS; i++) {
for (let j = 0; j < 16; j++) { for (let j = 0; j < 16; j++) {
s[j] = S8[s[j]] s[j] = S8[s[j]];
} }
s[0] ^= (C[i] & 0xf) s[0] ^= C[i] & 0xf;
s[4] ^= (C[i] >> 4) & 0xf s[4] ^= (C[i] >> 4) & 0xf;
s[8] ^= 0x2 s[8] ^= 0x2;
for (let j = 0; j < 8; j++) { 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++) { for (let j = 0; j < 4; j++) {
const tmp = Array.from(s) const tmp = Array.from(s);
s[j] = tmp[j] ^ tmp[8 + j] ^ tmp[12 + j] s[j] = tmp[j] ^ tmp[8 + j] ^ tmp[12 + j];
s[4 + j] = tmp[j] s[4 + j] = tmp[j];
s[8 + j] = tmp[4 + j] ^ tmp[8 + j] s[8 + j] = tmp[4 + j] ^ tmp[8 + j];
s[12 + j] = tmp[0 + 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 // Given
const ciphertext = Uint8Array.from([ const ciphertext = Uint8Array.from([
// Nonce // Nonce
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
// Ciphertext // Ciphertext
225, 53, 3, 212, 22, 112, 246, 194, 61, 171, 230, 187, 157, 102, 32, 76, 62, 65, 225, 53, 3, 212, 22, 112, 246, 194, 61, 171, 230, 187, 157, 102, 32, 76, 62,
25, 202, 255, 201, 206, 49, 60, 58, 82, 216, 72, 116, 106, 129, 162, 142, 69, 40, 65, 25, 202, 255, 201, 206, 49, 60, 58, 82, 216, 72, 116, 106, 129, 162,
167, 88, 94, 195, 174, 217, 242, 149, 224, 125, 196, 237, 172, 165, 116, 119, 128 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]) 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 // When
const result = decrypt(ciphertext, associatedData, key) const result = decrypt(ciphertext, associatedData, key);
// Then // Then
const expectedResult = new TextEncoder().encode('Hello, World! This is a test message.') const expectedResult = new TextEncoder().encode(
expect(result.success).toBe(true) "Hello, World! This is a test message.",
expect(result.plaintext).toMatchObject(expectedResult) );
}) 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 // Given
const ciphertext = Uint8Array.from([ const ciphertext = Uint8Array.from([
// Nonce // Nonce
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
// Ciphertext // Ciphertext
225, 53, 3, 212, 22, 112, 246, 194, 61, 171, 230, 187, 157, 102, 32, 76, 62, 65, 225, 53, 3, 212, 22, 112, 246, 194, 61, 171, 230, 187, 157, 102, 32, 76, 62,
25, 202, 255, 201, 206, 49, 60, 58, 82, 216, 72, 116, 106, 129, 162, 142, 69, 40, 65, 25, 202, 255, 201, 206, 49, 60, 58, 82, 216, 72, 116, 106, 129, 162,
167, 88, 94, 195, 174, 217, 242, 149, 224, 125, 196, 237, 172, 165, 116, 119, 128 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]) 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 // When
const result = decrypt(ciphertext, associatedData, key) const result = decrypt(ciphertext, associatedData, key);
// Then // Then
expect(result.success).toBe(false) expect(result.success).toBe(false);
expect(result.plaintext).toMatchObject(new Uint8Array()) expect(result.plaintext).toMatchObject(new Uint8Array());
}) });

View File

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

View File

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