Lint and update
This commit is contained in:
4
.vscode/extensions.json
vendored
4
.vscode/extensions.json
vendored
@@ -1,5 +1,3 @@
|
|||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": ["orta.vscode-jest"]
|
||||||
"orta.vscode-jest"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
@@ -8,13 +8,9 @@
|
|||||||
"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",
|
"program": "${workspaceFolder}/lib/index.js",
|
||||||
"outFiles": [
|
"outFiles": ["${workspaceFolder}/**/*.js"]
|
||||||
"${workspaceFolder}/**/*.js"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
4
.vscode/tasks.json
vendored
4
.vscode/tasks.json
vendored
@@ -5,9 +5,7 @@
|
|||||||
"type": "typescript",
|
"type": "typescript",
|
||||||
"tsconfig": "tsconfig.json",
|
"tsconfig": "tsconfig.json",
|
||||||
"option": "watch",
|
"option": "watch",
|
||||||
"problemMatcher": [
|
"problemMatcher": ["$tsc-watch"],
|
||||||
"$tsc-watch"
|
|
||||||
],
|
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -1,4 +1,5 @@
|
|||||||
# Romulus-JS
|
# Romulus-JS
|
||||||
|
|
||||||
[](https://drone.jacknet.io/TerribleCodeClub/romulus-js) [](https://standardjs.com)
|
[](https://drone.jacknet.io/TerribleCodeClub/romulus-js) [](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
|
|
||||||
|
|
||||||
[](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
1237
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
118
src/constants.ts
118
src/constants.ts
@@ -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;
|
||||||
|
|||||||
@@ -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),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { encrypt } from './encrypt'
|
export { encrypt } from "./encrypt";
|
||||||
export { decrypt } from './decrypt'
|
export { decrypt } from "./decrypt";
|
||||||
|
|||||||
343
src/romulus-m.ts
343
src/romulus-m.ts
@@ -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.
|
||||||
@@ -9,28 +9,28 @@ import { tweakeyEncode, skinnyEncrypt } from './skinny-128-384-plus'
|
|||||||
*/
|
*/
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,23 +42,23 @@ function parse (message: number[], blockLength: number): number[][] {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,9 +67,9 @@ function pad (message: number[], padLength: number): number[] {
|
|||||||
* @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);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,15 +80,17 @@ function g (state: number[]): number[] {
|
|||||||
*/
|
*/
|
||||||
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -99,14 +101,16 @@ function rho (state: number[], mBlock: number[]): [number[], number[]] {
|
|||||||
*/
|
*/
|
||||||
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,22 +119,22 @@ function inverseRoh (state: number[], cBlock: number[]): [number[], number[]] {
|
|||||||
* @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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,9 +142,9 @@ function increaseCounter (counter: number[]): number[] {
|
|||||||
* @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,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -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
@@ -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)(
|
||||||
|
"Perform encryption using reference test %#.",
|
||||||
|
(key, nonce, plaintext, associatedData, ciphertext) => {
|
||||||
// When
|
// When
|
||||||
const result = cryptoAeadEncrypt(parseHexString(plaintext), parseHexString(associatedData), parseHexString(nonce), parseHexString(key))
|
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)(
|
||||||
|
"Perform decryption using reference test %#.",
|
||||||
|
(key, nonce, plaintext, associatedData, ciphertext) => {
|
||||||
// When
|
// When
|
||||||
const result = cryptoAeadDecrypt(parseHexString(ciphertext), parseHexString(associatedData), parseHexString(nonce), parseHexString(key))
|
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);
|
||||||
})
|
},
|
||||||
|
);
|
||||||
|
|||||||
@@ -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);
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user