mirror of
https://github.com/waku-org/js-noise.git
synced 2025-02-21 15:38:13 +00:00
initial commit
This commit is contained in:
commit
b1390e0855
26
.cspell.json
Normal file
26
.cspell.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"version": "0.1",
|
||||
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/master/cspell.schema.json",
|
||||
"language": "en",
|
||||
"words": ["Waku", "keypair", "nwaku"],
|
||||
"flagWords": [],
|
||||
"ignorePaths": [
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
"yarn.lock",
|
||||
"tsconfig.json",
|
||||
"node_modules/**",
|
||||
"build",
|
||||
"gen",
|
||||
"proto",
|
||||
"*.spec.ts",
|
||||
"src/resources.ts"
|
||||
],
|
||||
"patterns": [
|
||||
{
|
||||
"name": "import",
|
||||
"pattern": "/import .*/"
|
||||
}
|
||||
],
|
||||
"ignoreRegExpList": ["import"]
|
||||
}
|
15
.editorconfig
Normal file
15
.editorconfig
Normal file
@ -0,0 +1,15 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
max_line_length = 80
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = 0
|
||||
trim_trailing_whitespace = false
|
70
.eslintrc.json
Normal file
70
.eslintrc.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.dev.json"
|
||||
},
|
||||
"env": {
|
||||
"es6": true
|
||||
},
|
||||
"ignorePatterns": ["node_modules", "build", "coverage", "proto"],
|
||||
"plugins": ["import", "eslint-comments", "functional"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:eslint-comments/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:import/typescript",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"globals": {
|
||||
"BigInt": true,
|
||||
"console": true,
|
||||
"WebAssembly": true
|
||||
},
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-function-return-type": [
|
||||
"error",
|
||||
{
|
||||
"allowExpressions": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"eslint-comments/disable-enable-pair": [
|
||||
"error",
|
||||
{
|
||||
"allowWholeFile": true
|
||||
}
|
||||
],
|
||||
"eslint-comments/no-unused-disable": "error",
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
"newlines-between": "always",
|
||||
"alphabetize": {
|
||||
"order": "asc"
|
||||
}
|
||||
}
|
||||
],
|
||||
"no-constant-condition": [
|
||||
"error",
|
||||
{
|
||||
"checkLoops": false
|
||||
}
|
||||
],
|
||||
"sort-imports": [
|
||||
"error",
|
||||
{
|
||||
"ignoreDeclarationSort": true,
|
||||
"ignoreCase": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.spec.ts", "**/test_utils/*.ts"],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-non-null-assertion": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
15
.github/workflows/add-action-project.yml
vendored
Normal file
15
.github/workflows/add-action-project.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
name: Add new issues to Waku project board
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
add-to-project:
|
||||
name: Add issue to project
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/add-to-project@v0.3.0
|
||||
with:
|
||||
project-url: https://github.com/orgs/waku-org/projects/2
|
||||
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
|
46
.github/workflows/ci.yml
vendored
Normal file
46
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
- "staging"
|
||||
- "trying"
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
NODE_JS: "16"
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_JS }}
|
||||
- uses: bahmutov/npm-install@v1
|
||||
- run: npm run test:lint
|
||||
- run: npm run test:prettier
|
||||
- run: npm run test:spelling
|
||||
- run: npm run test:tsc
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_JS }}
|
||||
- uses: bahmutov/npm-install@v1
|
||||
- run: npm run build
|
||||
|
||||
browser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_JS }}
|
||||
- uses: bahmutov/npm-install@v1
|
||||
- run: npm run test:browser
|
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
.idea/*
|
||||
.angular
|
||||
build
|
||||
dist
|
||||
node_modules
|
||||
coverage
|
||||
*.log
|
||||
/tsconfig.tsbuildinfo
|
||||
/tsconfig.dev.tsbuildinfo
|
||||
/bundle/
|
11
.mocharc.json
Normal file
11
.mocharc.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"extension": ["ts"],
|
||||
"spec": "src/**/*.spec.ts",
|
||||
"require": ["ts-node/register", "isomorphic-fetch", "jsdom-global/register"],
|
||||
"loader": "ts-node/esm",
|
||||
"node-option": [
|
||||
"experimental-specifier-resolution=node",
|
||||
"loader=ts-node/esm"
|
||||
],
|
||||
"exit": true
|
||||
}
|
7
.size-limit.cjs
Normal file
7
.size-limit.cjs
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = [
|
||||
{
|
||||
name: "RLN core",
|
||||
path: "bundle/index.js",
|
||||
import: "{ RLN }",
|
||||
},
|
||||
];
|
202
LICENSE-APACHE-v2
Normal file
202
LICENSE-APACHE-v2
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2018 Status Research & Development GmbH
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
21
LICENSE-MIT
Normal file
21
LICENSE-MIT
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 Status Research & Development GmbH
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
9
bors.toml
Normal file
9
bors.toml
Normal file
@ -0,0 +1,9 @@
|
||||
status = [
|
||||
"check",
|
||||
"build",
|
||||
"proto",
|
||||
"browser",
|
||||
"node",
|
||||
]
|
||||
block_labels = ["work-in-progress"]
|
||||
delete_merged_branches = true
|
7
buf.gen.yaml
Normal file
7
buf.gen.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
version: v1beta1
|
||||
|
||||
plugins:
|
||||
- name: ts_proto
|
||||
out: ./src/proto
|
||||
strategy: all
|
||||
opt: grpc_js,esModuleInterop=true,forceLong=long
|
67
karma.conf.cjs
Normal file
67
karma.conf.cjs
Normal file
@ -0,0 +1,67 @@
|
||||
process.env.CHROME_BIN = require("puppeteer").executablePath();
|
||||
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
const ResolveTypeScriptPlugin = require("resolve-typescript-plugin");
|
||||
|
||||
const output = {
|
||||
path:
|
||||
path.join(os.tmpdir(), "_karma_webpack_") +
|
||||
Math.floor(Math.random() * 1000000),
|
||||
};
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
frameworks: ["webpack", "mocha"],
|
||||
preprocessors: {
|
||||
"**/*.ts": ["webpack"],
|
||||
},
|
||||
|
||||
files: [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.ts",
|
||||
{
|
||||
pattern: `${output.path}/**/*`,
|
||||
watched: false,
|
||||
included: false,
|
||||
served: true,
|
||||
},
|
||||
],
|
||||
envPreprocessor: ["CI"],
|
||||
reporters: ["progress"],
|
||||
browsers: ["ChromeHeadless"],
|
||||
singleRun: true,
|
||||
client: {
|
||||
mocha: {
|
||||
timeout: 6000, // Default is 2s
|
||||
},
|
||||
},
|
||||
webpack: {
|
||||
mode: "production",
|
||||
resolve: {
|
||||
// Add `.ts` and `.tsx` as a resolvable extension.
|
||||
extensions: [".ts", ".tsx", ".js"],
|
||||
plugins: [new ResolveTypeScriptPlugin()],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.wasm$/,
|
||||
type: "asset/resource",
|
||||
},
|
||||
{
|
||||
test: /\.(js|tsx?)$/,
|
||||
loader: "ts-loader",
|
||||
exclude: /node_modules|\.d\.ts$/,
|
||||
options: { configFile: "tsconfig.karma.json" },
|
||||
},
|
||||
{
|
||||
test: /\.d\.ts$/,
|
||||
loader: "ignore-loader",
|
||||
},
|
||||
],
|
||||
},
|
||||
output,
|
||||
},
|
||||
});
|
||||
};
|
15662
package-lock.json
generated
Normal file
15662
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
119
package.json
Normal file
119
package.json
Normal file
@ -0,0 +1,119 @@
|
||||
{
|
||||
"name": "@waku/noise",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"types": "./dist/index.d.ts",
|
||||
"module": "./dist/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"prepare": "husky install",
|
||||
"build": "run-s build:**",
|
||||
"build:tsc": "tsc",
|
||||
"build:bundle": "rollup --config rollup.config.js",
|
||||
"size": "npm run build && size-limit",
|
||||
"fix": "run-s fix:*",
|
||||
"fix:prettier": "prettier \"src/**/*.ts\" \"./*.json\" \"*.*js\" \".github/**/*.yml\" --write",
|
||||
"fix:lint": "eslint src --ext .ts --ext .cjs --fix",
|
||||
"test": "run-s test:*",
|
||||
"test:lint": "eslint src --ext .ts",
|
||||
"test:prettier": "prettier \"src/**/*.ts\" \"./*.json\" \"*.*js\" \".github/**/*.yml\" --list-different",
|
||||
"test:spelling": "cspell \"{*.md,.github/*.md,src/**/*.ts}\"",
|
||||
"test:tsc": "tsc -p tsconfig.dev.json",
|
||||
"test:browser": "karma start karma.conf.cjs",
|
||||
"watch:build": "tsc -p tsconfig.json -w",
|
||||
"watch:test": "mocha --watch",
|
||||
"prepublish": "npm run build",
|
||||
"reset-hard": "git clean -dfx && git reset --hard && npm i && npm run build"
|
||||
},
|
||||
"browser": {
|
||||
"crypto": false
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"author": "",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^22.0.2",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"@size-limit/preset-big-lib": "^8.0.0",
|
||||
"@types/app-root-path": "^1.2.4",
|
||||
"@types/chai": "^4.2.15",
|
||||
"@types/debug": "^4.1.7",
|
||||
"@types/mocha": "^9.1.0",
|
||||
"@types/node": "^17.0.6",
|
||||
"@types/tail": "^2.0.0",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.8.1",
|
||||
"@typescript-eslint/parser": "^5.8.1",
|
||||
"app-root-path": "^3.0.0",
|
||||
"chai": "^4.3.4",
|
||||
"cspell": "^5.14.0",
|
||||
"eslint": "^8.6.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||
"eslint-plugin-functional": "^4.0.2",
|
||||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"fast-check": "^2.25.0",
|
||||
"gh-pages": "^3.2.3",
|
||||
"husky": "^7.0.4",
|
||||
"ignore-loader": "^0.1.2",
|
||||
"jsdom": "^19.0.0",
|
||||
"jsdom-global": "^3.0.2",
|
||||
"karma": "^6.3.12",
|
||||
"karma-chrome-launcher": "^3.1.0",
|
||||
"karma-mocha": "^2.0.1",
|
||||
"karma-webpack": "^5.0.0",
|
||||
"lint-staged": "^13.0.3",
|
||||
"mocha": "^9.1.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"p-timeout": "^4.1.0",
|
||||
"prettier": "^2.1.1",
|
||||
"process": "^0.11.10",
|
||||
"puppeteer": "^13.0.1",
|
||||
"resolve-typescript-plugin": "^1.2.0",
|
||||
"rollup": "^2.75.0",
|
||||
"size-limit": "^8.0.0",
|
||||
"tail": "^2.2.0",
|
||||
"ts-loader": "^9.3.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typedoc": "^0.23.10",
|
||||
"typescript": "^4.5.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"bundle",
|
||||
"src/**.ts",
|
||||
"src/**.js",
|
||||
"!**/*.spec.*",
|
||||
"!**/*.json",
|
||||
"CHANGELOG.md",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"lint-staged": {
|
||||
"*.ts": [
|
||||
"eslint --fix"
|
||||
],
|
||||
"*.{ts,md,json,conf*.*js}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@stablelib/chacha20poly1305": "^1.0.1",
|
||||
"@stablelib/hkdf": "^1.0.1",
|
||||
"@stablelib/sha256": "^1.0.1",
|
||||
"@stablelib/x25519": "^1.0.1",
|
||||
"pkcs7-padding": "^0.1.1",
|
||||
"uint8arraylist": "^2.3.2",
|
||||
"uint8arrays": "^4.0.2"
|
||||
}
|
||||
}
|
21
rollup.config.js
Normal file
21
rollup.config.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import json from "@rollup/plugin-json";
|
||||
export default {
|
||||
input: {
|
||||
index: "dist/index.js",
|
||||
},
|
||||
output: {
|
||||
dir: "bundle",
|
||||
format: "esm",
|
||||
},
|
||||
plugins: [
|
||||
commonjs(),
|
||||
json(),
|
||||
nodeResolve({
|
||||
browser: true,
|
||||
preferBuiltins: false,
|
||||
extensions: [".js", ".ts"],
|
||||
}),
|
||||
],
|
||||
};
|
5
src/@types/basic.ts
Normal file
5
src/@types/basic.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export type bytes = Uint8Array;
|
||||
export type bytes32 = Uint8Array;
|
||||
export type bytes16 = Uint8Array;
|
||||
|
||||
export type uint64 = number;
|
49
src/@types/handshake.ts
Normal file
49
src/@types/handshake.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import type { Nonce } from "../nonce.js";
|
||||
|
||||
import type { bytes, bytes32, uint64 } from "./basic.js";
|
||||
import type { KeyPair } from "./keypair.js";
|
||||
|
||||
export type Hkdf = [bytes, bytes, bytes];
|
||||
|
||||
export interface MessageBuffer {
|
||||
ne: bytes32;
|
||||
ns: bytes;
|
||||
ciphertext: bytes;
|
||||
}
|
||||
|
||||
export interface CipherState {
|
||||
k: bytes32;
|
||||
// For performance reasons, the nonce is represented as a Nonce object
|
||||
// The nonce is treated as a uint64, even though the underlying `number` only has 52 safely-available bits.
|
||||
n: Nonce;
|
||||
}
|
||||
|
||||
export interface SymmetricState {
|
||||
cs: CipherState;
|
||||
ck: bytes32; // chaining key
|
||||
h: bytes32; // handshake hash
|
||||
}
|
||||
|
||||
export interface HandshakeState {
|
||||
ss: SymmetricState;
|
||||
s: KeyPair;
|
||||
e?: KeyPair;
|
||||
rs: bytes32;
|
||||
re: bytes32;
|
||||
psk: bytes32;
|
||||
}
|
||||
|
||||
export interface NoiseSession {
|
||||
hs: HandshakeState;
|
||||
h?: bytes32;
|
||||
cs1?: CipherState;
|
||||
cs2?: CipherState;
|
||||
mc: uint64;
|
||||
i: boolean;
|
||||
}
|
||||
|
||||
export interface INoisePayload {
|
||||
identityKey: bytes;
|
||||
identitySig: bytes;
|
||||
data: bytes;
|
||||
}
|
6
src/@types/keypair.ts
Normal file
6
src/@types/keypair.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import type { bytes32 } from "./basic.js";
|
||||
|
||||
export interface KeyPair {
|
||||
publicKey: bytes32;
|
||||
privateKey: bytes32;
|
||||
}
|
6
src/@types/pkcs7-padding/index.d.ts
vendored
Normal file
6
src/@types/pkcs7-padding/index.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
declare module "pkcs7-padding" {
|
||||
export function pad(data: Uint8Array, size?: number): Uint8Array;
|
||||
export function pad(data: string, size?: number): string;
|
||||
export function unpad(data: Uint8Array): Uint8Array;
|
||||
export function unpad(data: string): string;
|
||||
}
|
96
src/crypto.ts
Normal file
96
src/crypto.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import { ChaCha20Poly1305 } from "@stablelib/chacha20poly1305";
|
||||
import { HKDF } from "@stablelib/hkdf";
|
||||
import { hash, SHA256 } from "@stablelib/sha256";
|
||||
import * as x25519 from "@stablelib/x25519";
|
||||
|
||||
import type { bytes, bytes32 } from "./@types/basic.js";
|
||||
import type { Hkdf } from "./@types/handshake.js";
|
||||
import type { KeyPair } from "./@types/keypair.js";
|
||||
|
||||
export const Curve25519KeySize = x25519.PUBLIC_KEY_LENGTH;
|
||||
|
||||
export function hashSHA256(data: Uint8Array): Uint8Array {
|
||||
return hash(data);
|
||||
}
|
||||
|
||||
export function intoCurve25519Key(s: Uint8Array): bytes32 {
|
||||
if (s.length != x25519.PUBLIC_KEY_LENGTH) {
|
||||
throw "invalid public key length";
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
export function getHKDF(ck: bytes32, ikm: Uint8Array): Hkdf {
|
||||
const hkdf = new HKDF(SHA256, ikm, ck);
|
||||
const okmU8Array = hkdf.expand(96);
|
||||
const okm = okmU8Array;
|
||||
|
||||
const k1 = okm.subarray(0, 32);
|
||||
const k2 = okm.subarray(32, 64);
|
||||
const k3 = okm.subarray(64, 96);
|
||||
|
||||
return [k1, k2, k3];
|
||||
}
|
||||
|
||||
export function generateX25519KeyPair(): KeyPair {
|
||||
const keypair = x25519.generateKeyPair();
|
||||
|
||||
return {
|
||||
publicKey: keypair.publicKey,
|
||||
privateKey: keypair.secretKey,
|
||||
};
|
||||
}
|
||||
|
||||
export function generateX25519KeyPairFromSeed(seed: Uint8Array): KeyPair {
|
||||
const keypair = x25519.generateKeyPairFromSeed(seed);
|
||||
|
||||
return {
|
||||
publicKey: keypair.publicKey,
|
||||
privateKey: keypair.secretKey,
|
||||
};
|
||||
}
|
||||
|
||||
export function generateX25519SharedKey(
|
||||
privateKey: Uint8Array,
|
||||
publicKey: Uint8Array
|
||||
): Uint8Array {
|
||||
return x25519.sharedKey(privateKey, publicKey);
|
||||
}
|
||||
|
||||
export function chaCha20Poly1305Encrypt(
|
||||
plaintext: Uint8Array,
|
||||
nonce: Uint8Array,
|
||||
ad: Uint8Array,
|
||||
k: bytes32
|
||||
): bytes {
|
||||
const ctx = new ChaCha20Poly1305(k);
|
||||
|
||||
return ctx.seal(nonce, plaintext, ad);
|
||||
}
|
||||
|
||||
export function chaCha20Poly1305Decrypt(
|
||||
ciphertext: Uint8Array,
|
||||
nonce: Uint8Array,
|
||||
ad: Uint8Array,
|
||||
k: bytes32
|
||||
): bytes | null {
|
||||
const ctx = new ChaCha20Poly1305(k);
|
||||
|
||||
return ctx.open(nonce, ciphertext, ad);
|
||||
}
|
||||
|
||||
export function dh(privateKey: bytes32, publicKey: bytes32): bytes32 {
|
||||
try {
|
||||
const derivedU8 = generateX25519SharedKey(privateKey, publicKey);
|
||||
|
||||
if (derivedU8.length === 32) {
|
||||
return derivedU8;
|
||||
}
|
||||
|
||||
return derivedU8.subarray(0, 32);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return new Uint8Array(32);
|
||||
}
|
||||
}
|
501
src/handshake.ts
Normal file
501
src/handshake.ts
Normal file
@ -0,0 +1,501 @@
|
||||
import * as pkcs7 from "pkcs7-padding";
|
||||
|
||||
import { bytes32 } from "./@types/basic.js";
|
||||
import type { KeyPair } from "./@types/keypair.js";
|
||||
import {
|
||||
Curve25519KeySize,
|
||||
dh,
|
||||
generateX25519KeyPair,
|
||||
intoCurve25519Key,
|
||||
} from "./crypto.js";
|
||||
import { SymmetricState } from "./noise.js";
|
||||
import {
|
||||
EmptyPreMessage,
|
||||
HandshakePattern,
|
||||
MessageDirection,
|
||||
NoiseTokens,
|
||||
PreMessagePattern,
|
||||
} from "./patterns";
|
||||
import { NoisePublicKey } from "./publickey.js";
|
||||
|
||||
// The padding blocksize of a transport message
|
||||
const NoisePaddingBlockSize = 248;
|
||||
|
||||
// The Handshake State as in https://noiseprotocol.org/noise.html#the-handshakestate-object
|
||||
// Contains
|
||||
// - the local and remote ephemeral/static keys e,s,re,rs (if any)
|
||||
// - the initiator flag (true if the user creating the state is the handshake initiator, false otherwise)
|
||||
// - the handshakePattern (containing the handshake protocol name, and (pre)message patterns)
|
||||
// This object is futher extended from specifications by storing:
|
||||
// - a message pattern index msgPatternIdx indicating the next handshake message pattern to process
|
||||
// - the user's preshared psk, if any
|
||||
export class HandshakeState {
|
||||
s?: KeyPair;
|
||||
e?: KeyPair;
|
||||
rs?: bytes32;
|
||||
re?: bytes32;
|
||||
ss: SymmetricState;
|
||||
initiator: boolean;
|
||||
handshakePattern: HandshakePattern;
|
||||
msgPatternIdx: number;
|
||||
psk: Uint8Array;
|
||||
|
||||
constructor(hsPattern: HandshakePattern, psk: Uint8Array) {
|
||||
// By default the Handshake State initiator flag is set to false
|
||||
// Will be set to true when the user associated to the handshake state starts an handshake
|
||||
this.initiator = false;
|
||||
|
||||
this.handshakePattern = hsPattern;
|
||||
this.psk = psk;
|
||||
|
||||
this.ss = new SymmetricState(hsPattern);
|
||||
|
||||
this.msgPatternIdx = 0;
|
||||
}
|
||||
|
||||
// Handshake Processing
|
||||
|
||||
// Based on the message handshake direction and if the user is or not the initiator, returns a boolean tuple telling if the user
|
||||
// has to read or write the next handshake message
|
||||
getReadingWritingState(direction: MessageDirection): {
|
||||
reading: boolean;
|
||||
writing: boolean;
|
||||
} {
|
||||
let reading = false;
|
||||
let writing = false;
|
||||
|
||||
if (this.initiator && direction == MessageDirection.r) {
|
||||
// I'm Alice and direction is ->
|
||||
reading = false;
|
||||
writing = true;
|
||||
} else if (this.initiator && direction == MessageDirection.l) {
|
||||
// I'm Alice and direction is <-
|
||||
reading = true;
|
||||
writing = false;
|
||||
} else if (!this.initiator && direction == MessageDirection.r) {
|
||||
// I'm Bob and direction is ->
|
||||
reading = true;
|
||||
writing = false;
|
||||
} else if (!this.initiator && direction == MessageDirection.l) {
|
||||
// I'm Bob and direction is <-
|
||||
reading = false;
|
||||
writing = true;
|
||||
}
|
||||
return { reading, writing };
|
||||
}
|
||||
|
||||
// Checks if a pre-message is valid according to Noise specifications
|
||||
// http://www.noiseprotocol.org/noise.html#handshake-patterns
|
||||
isValid(msg: Array<PreMessagePattern>): boolean {
|
||||
let isValid = true;
|
||||
|
||||
// Non-empty pre-messages can only have patterns "e", "s", "e,s" in each direction
|
||||
const allowedPatterns = [
|
||||
new PreMessagePattern(MessageDirection.r, [NoiseTokens.s]),
|
||||
new PreMessagePattern(MessageDirection.r, [NoiseTokens.e]),
|
||||
new PreMessagePattern(MessageDirection.r, [NoiseTokens.e, NoiseTokens.s]),
|
||||
new PreMessagePattern(MessageDirection.l, [NoiseTokens.s]),
|
||||
new PreMessagePattern(MessageDirection.l, [NoiseTokens.e]),
|
||||
new PreMessagePattern(MessageDirection.l, [NoiseTokens.e, NoiseTokens.s]),
|
||||
];
|
||||
|
||||
// We check if pre message patterns are allowed
|
||||
for (const pattern of msg) {
|
||||
if (!allowedPatterns.find((x) => x.equals(pattern))) {
|
||||
isValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// Handshake messages processing procedures
|
||||
|
||||
// Processes pre-message patterns
|
||||
processPreMessagePatternTokens(
|
||||
inPreMessagePKs: Array<NoisePublicKey> = []
|
||||
): void {
|
||||
// I make a copy of the input pre-message public keys, so that I can easily delete processed ones without using iterators/counters
|
||||
const preMessagePKs = inPreMessagePKs;
|
||||
|
||||
// Here we store currently processed pre message public key
|
||||
let currPK: NoisePublicKey;
|
||||
|
||||
// We retrieve the pre-message patterns to process, if any
|
||||
// If none, there's nothing to do
|
||||
if (this.handshakePattern.preMessagePatterns == EmptyPreMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If not empty, we check that pre-message is valid according to Noise specifications
|
||||
if (!this.isValid(this.handshakePattern.preMessagePatterns)) {
|
||||
throw "invalid pre-message in handshake";
|
||||
}
|
||||
|
||||
// We iterate over each pattern contained in the pre-message
|
||||
for (const messagePattern of this.handshakePattern.preMessagePatterns) {
|
||||
const direction = messagePattern.direction;
|
||||
const tokens = messagePattern.tokens;
|
||||
|
||||
// We get if the user is reading or writing the current pre-message pattern
|
||||
const { reading, writing } = this.getReadingWritingState(direction);
|
||||
|
||||
// We process each message pattern token
|
||||
for (const token of tokens) {
|
||||
// We process the pattern token
|
||||
switch (token) {
|
||||
case NoiseTokens.e:
|
||||
// We expect an ephemeral key, so we attempt to read it (next PK to process will always be at index 0 of preMessagePKs)
|
||||
if (preMessagePKs.length > 0) {
|
||||
currPK = preMessagePKs[0];
|
||||
} else {
|
||||
throw "noise pre-message read e, expected a public key";
|
||||
}
|
||||
|
||||
// If user is reading the "e" token
|
||||
if (reading) {
|
||||
console.trace("noise pre-message read e");
|
||||
|
||||
// We check if current key is encrypted or not. We assume pre-message public keys are all unencrypted on users' end
|
||||
if (currPK.flag == 0) {
|
||||
// Sets re and calls MixHash(re.public_key).
|
||||
this.re = intoCurve25519Key(currPK.pk);
|
||||
this.ss.mixHash(this.re);
|
||||
} else {
|
||||
throw "noise read e, incorrect encryption flag for pre-message public key";
|
||||
}
|
||||
// If user is writing the "e" token
|
||||
} else if (writing) {
|
||||
console.trace("noise pre-message write e");
|
||||
|
||||
// When writing, the user is sending a public key,
|
||||
// We check that the public part corresponds to the set local key and we call MixHash(e.public_key).
|
||||
if (this.e && this.e.publicKey == intoCurve25519Key(currPK.pk)) {
|
||||
this.ss.mixHash(this.e.publicKey);
|
||||
} else {
|
||||
throw "noise pre-message e key doesn't correspond to locally set e key pair";
|
||||
}
|
||||
}
|
||||
|
||||
// Noise specification: section 9.2
|
||||
// In non-PSK handshakes, the "e" token in a pre-message pattern or message pattern always results
|
||||
// in a call to MixHash(e.public_key).
|
||||
// In a PSK handshake, all of these calls are followed by MixKey(e.public_key).
|
||||
if (this.handshakePattern.name.indexOf("psk") > -1) {
|
||||
this.ss.mixKey(currPK.pk);
|
||||
}
|
||||
|
||||
// We delete processed public key
|
||||
preMessagePKs.shift();
|
||||
break;
|
||||
case NoiseTokens.s:
|
||||
// We expect a static key, so we attempt to read it (next PK to process will always be at index of preMessagePKs)
|
||||
if (preMessagePKs.length > 0) {
|
||||
currPK = preMessagePKs[0];
|
||||
} else {
|
||||
throw "noise pre-message read s, expected a public key";
|
||||
}
|
||||
|
||||
// If user is reading the "s" token
|
||||
if (reading) {
|
||||
console.trace("noise pre-message read s");
|
||||
|
||||
// We check if current key is encrypted or not. We assume pre-message public keys are all unencrypted on users' end
|
||||
if (currPK.flag == 0) {
|
||||
// Sets re and calls MixHash(re.public_key).
|
||||
this.rs = intoCurve25519Key(currPK.pk);
|
||||
this.ss.mixHash(this.rs);
|
||||
} else {
|
||||
throw "noise read s, incorrect encryption flag for pre-message public key";
|
||||
}
|
||||
|
||||
// If user is writing the "s" token
|
||||
} else if (writing) {
|
||||
console.trace("noise pre-message write s");
|
||||
|
||||
// If writing, it means that the user is sending a public key,
|
||||
// We check that the public part corresponds to the set local key and we call MixHash(s.public_key).
|
||||
if (this.s && this.s.publicKey == intoCurve25519Key(currPK.pk)) {
|
||||
this.ss.mixHash(this.s.publicKey);
|
||||
} else {
|
||||
throw "noise pre-message s key doesn't correspond to locally set s key pair";
|
||||
}
|
||||
}
|
||||
|
||||
// Noise specification: section 9.2
|
||||
// In non-PSK handshakes, the "e" token in a pre-message pattern or message pattern always results
|
||||
// in a call to MixHash(e.public_key).
|
||||
// In a PSK handshake, all of these calls are followed by MixKey(e.public_key).
|
||||
if (this.handshakePattern.name.indexOf("psk") > -1) {
|
||||
this.ss.mixKey(currPK.pk);
|
||||
}
|
||||
|
||||
// We delete processed public key
|
||||
preMessagePKs.shift();
|
||||
break;
|
||||
default:
|
||||
throw "invalid Token for pre-message pattern";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This procedure encrypts/decrypts the implicit payload attached at the end of every message pattern
|
||||
// An optional extraAd to pass extra additional data in encryption/decryption can be set (useful to authenticate messageNametag)
|
||||
processMessagePatternPayload(
|
||||
transportMessage: Uint8Array,
|
||||
extraAd: Uint8Array = new Uint8Array()
|
||||
): Uint8Array {
|
||||
let payload: Uint8Array;
|
||||
|
||||
// We retrieve current message pattern (direction + tokens) to process
|
||||
const direction =
|
||||
this.handshakePattern.messagePatterns[this.msgPatternIdx].direction;
|
||||
|
||||
// We get if the user is reading or writing the input handshake message
|
||||
const { reading, writing } = this.getReadingWritingState(direction);
|
||||
|
||||
// We decrypt the transportMessage, if any
|
||||
if (reading) {
|
||||
payload = this.ss.decryptAndHash(transportMessage, extraAd);
|
||||
payload = pkcs7.pad(payload, NoisePaddingBlockSize);
|
||||
} else if (writing) {
|
||||
payload = pkcs7.unpad(transportMessage);
|
||||
payload = this.ss.encryptAndHash(payload, extraAd);
|
||||
} else {
|
||||
throw "undefined state";
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
// We process an input handshake message according to current handshake state and we return the next handshake step's handshake message
|
||||
processMessagePatternTokens(
|
||||
inputHandshakeMessage: Array<NoisePublicKey> = []
|
||||
): Array<NoisePublicKey> {
|
||||
// We retrieve current message pattern (direction + tokens) to process
|
||||
const messagePattern =
|
||||
this.handshakePattern.messagePatterns[this.msgPatternIdx];
|
||||
const direction = messagePattern.direction;
|
||||
const tokens = messagePattern.tokens;
|
||||
|
||||
// We get if the user is reading or writing the input handshake message
|
||||
const { reading, writing } = this.getReadingWritingState(direction);
|
||||
|
||||
// I make a copy of the handshake message so that I can easily delete processed PKs without using iterators/counters
|
||||
// (Possibly) non-empty if reading
|
||||
const inHandshakeMessage = inputHandshakeMessage;
|
||||
|
||||
// The party's output public keys
|
||||
// (Possibly) non-empty if writing
|
||||
const outHandshakeMessage: Array<NoisePublicKey> = [];
|
||||
|
||||
// In currPK we store the currently processed public key from the handshake message
|
||||
let currPK: NoisePublicKey;
|
||||
|
||||
// We process each message pattern token
|
||||
for (const token of tokens) {
|
||||
switch (token) {
|
||||
case NoiseTokens.e:
|
||||
// If user is reading the "s" token
|
||||
if (reading) {
|
||||
console.trace("noise read e");
|
||||
|
||||
// We expect an ephemeral key, so we attempt to read it (next PK to process will always be at index 0 of preMessagePKs)
|
||||
if (inHandshakeMessage.length > 0) {
|
||||
currPK = inHandshakeMessage[0];
|
||||
} else {
|
||||
throw "noise read e, expected a public key";
|
||||
}
|
||||
|
||||
// We check if current key is encrypted or not
|
||||
// Note: by specification, ephemeral keys should always be unencrypted. But we support encrypted ones.
|
||||
if (currPK.flag == 0) {
|
||||
// Unencrypted Public Key
|
||||
// Sets re and calls MixHash(re.public_key).
|
||||
this.re = intoCurve25519Key(currPK.pk);
|
||||
this.ss.mixHash(this.re);
|
||||
|
||||
// The following is out of specification: we call decryptAndHash for encrypted ephemeral keys, similarly as happens for (encrypted) static keys
|
||||
} else if (currPK.flag == 1) {
|
||||
// Encrypted public key
|
||||
// Decrypts re, sets re and calls MixHash(re.public_key).
|
||||
this.re = intoCurve25519Key(this.ss.decryptAndHash(currPK.pk));
|
||||
} else {
|
||||
throw "noise read e, incorrect encryption flag for public key";
|
||||
}
|
||||
|
||||
// Noise specification: section 9.2
|
||||
// In non-PSK handshakes, the "e" token in a pre-message pattern or message pattern always results
|
||||
// in a call to MixHash(e.public_key).
|
||||
// In a PSK handshake, all of these calls are followed by MixKey(e.public_key).
|
||||
if (this.handshakePattern.name.indexOf("psk") > -1) {
|
||||
this.ss.mixKey(this.re);
|
||||
}
|
||||
|
||||
// We delete processed public key
|
||||
inHandshakeMessage.shift();
|
||||
|
||||
// If user is writing the "e" token
|
||||
} else if (writing) {
|
||||
console.trace("noise write e");
|
||||
|
||||
// We generate a new ephemeral keypair
|
||||
this.e = generateX25519KeyPair();
|
||||
|
||||
// We update the state
|
||||
this.ss.mixHash(this.e.publicKey);
|
||||
|
||||
// Noise specification: section 9.2
|
||||
// In non-PSK handshakes, the "e" token in a pre-message pattern or message pattern always results
|
||||
// in a call to MixHash(e.public_key).
|
||||
// In a PSK handshake, all of these calls are followed by MixKey(e.public_key).
|
||||
if (this.handshakePattern.name.indexOf("psk") > -1) {
|
||||
this.ss.mixKey(this.e.publicKey);
|
||||
}
|
||||
|
||||
// We add the ephemeral public key to the Waku payload
|
||||
outHandshakeMessage.push(NoisePublicKey.to(this.e.publicKey));
|
||||
}
|
||||
break;
|
||||
|
||||
case NoiseTokens.s:
|
||||
// If user is reading the "s" token
|
||||
if (reading) {
|
||||
console.trace("noise read s");
|
||||
|
||||
// We expect a static key, so we attempt to read it (next PK to process will always be at index 0 of preMessagePKs)
|
||||
if (inHandshakeMessage.length > 0) {
|
||||
currPK = inHandshakeMessage[0];
|
||||
} else {
|
||||
throw "noise read s, expected a public key";
|
||||
}
|
||||
|
||||
// We check if current key is encrypted or not
|
||||
if (currPK.flag == 0) {
|
||||
// Unencrypted Public Key
|
||||
// Sets re and calls MixHash(re.public_key).
|
||||
this.rs = intoCurve25519Key(currPK.pk);
|
||||
this.ss.mixHash(this.rs);
|
||||
} else if (currPK.flag == 1) {
|
||||
// Encrypted public key
|
||||
// Decrypts rs, sets rs and calls MixHash(rs.public_key).
|
||||
this.rs = intoCurve25519Key(this.ss.decryptAndHash(currPK.pk));
|
||||
} else {
|
||||
throw "noise read s, incorrect encryption flag for public key";
|
||||
}
|
||||
|
||||
// We delete processed public key
|
||||
inHandshakeMessage.shift();
|
||||
|
||||
// If user is writing the "s" token
|
||||
} else if (writing) {
|
||||
console.trace("noise write s");
|
||||
|
||||
// If the local static key is not set (the handshake state was not properly initialized), we raise an error
|
||||
if (!this.s) {
|
||||
throw "static key not set";
|
||||
}
|
||||
|
||||
// We encrypt the public part of the static key in case a key is set in the Cipher State
|
||||
// That is, encS may either be an encrypted or unencrypted static key.
|
||||
const encS = this.ss.encryptAndHash(this.s.publicKey);
|
||||
|
||||
// We add the (encrypted) static public key to the Waku payload
|
||||
// Note that encS = (Enc(s) || tag) if encryption key is set, otherwise encS = s.
|
||||
// We distinguish these two cases by checking length of encryption and we set the proper encryption flag
|
||||
if (encS.length > Curve25519KeySize) {
|
||||
outHandshakeMessage.push(new NoisePublicKey(1, encS));
|
||||
} else {
|
||||
outHandshakeMessage.push(new NoisePublicKey(0, encS));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case NoiseTokens.psk:
|
||||
// If user is reading the "psk" token
|
||||
|
||||
console.trace("noise psk");
|
||||
|
||||
// Calls MixKeyAndHash(psk)
|
||||
this.ss.mixKeyAndHash(this.psk);
|
||||
break;
|
||||
|
||||
case NoiseTokens.ee:
|
||||
// If user is reading the "ee" token
|
||||
|
||||
console.trace("noise dh ee");
|
||||
|
||||
// If local and/or remote ephemeral keys are not set, we raise an error
|
||||
if (!this.e || !this.re) {
|
||||
throw "local or remote ephemeral key not set";
|
||||
}
|
||||
|
||||
// Calls MixKey(DH(e, re)).
|
||||
this.ss.mixKey(dh(this.e.privateKey, this.re));
|
||||
break;
|
||||
|
||||
case NoiseTokens.es:
|
||||
// If user is reading the "es" token
|
||||
|
||||
console.trace("noise dh es");
|
||||
|
||||
// We check if keys are correctly set.
|
||||
// If both present, we call MixKey(DH(e, rs)) if initiator, MixKey(DH(s, re)) if responder.
|
||||
if (this.initiator) {
|
||||
if (!this.e || !this.rs) {
|
||||
throw "local or remote ephemeral/static key not set";
|
||||
}
|
||||
|
||||
this.ss.mixKey(dh(this.e.privateKey, this.rs));
|
||||
} else {
|
||||
if (!this.re || !this.s) {
|
||||
throw "local or remote ephemeral/static key not set";
|
||||
}
|
||||
|
||||
this.ss.mixKey(dh(this.s.privateKey, this.re));
|
||||
}
|
||||
break;
|
||||
|
||||
case NoiseTokens.se:
|
||||
// If user is reading the "se" token
|
||||
|
||||
console.trace("noise dh se");
|
||||
|
||||
// We check if keys are correctly set.
|
||||
// If both present, call MixKey(DH(s, re)) if initiator, MixKey(DH(e, rs)) if responder.
|
||||
if (this.initiator) {
|
||||
if (!this.s || !this.re) {
|
||||
throw "local or remote ephemeral/static key not set";
|
||||
}
|
||||
|
||||
this.ss.mixKey(dh(this.s.privateKey, this.re));
|
||||
} else {
|
||||
if (!this.rs || !this.e) {
|
||||
throw "local or remote ephemeral/static key not set";
|
||||
}
|
||||
|
||||
this.ss.mixKey(dh(this.e.privateKey, this.rs));
|
||||
}
|
||||
break;
|
||||
|
||||
case NoiseTokens.ss:
|
||||
// If user is reading the "ss" token
|
||||
|
||||
console.trace("noise dh ss");
|
||||
|
||||
// If local and/or remote static keys are not set, we raise an error
|
||||
if (!this.s || !this.rs) {
|
||||
throw "local or remote static key not set";
|
||||
}
|
||||
|
||||
// Calls MixKey(DH(s, rs)).
|
||||
this.ss.mixKey(dh(this.s.privateKey, this.rs));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return outHandshakeMessage;
|
||||
}
|
||||
}
|
0
src/index.ts
Normal file
0
src/index.ts
Normal file
270
src/noise.ts
Normal file
270
src/noise.ts
Normal file
@ -0,0 +1,270 @@
|
||||
import { fromString as uint8ArrayFromString } from "uint8arrays";
|
||||
import { concat as uint8ArrayConcat } from "uint8arrays/concat";
|
||||
import { equals as uint8ArrayEquals } from "uint8arrays/equals";
|
||||
|
||||
import type { bytes32 } from "./@types/basic.js";
|
||||
import {
|
||||
chaCha20Poly1305Decrypt,
|
||||
chaCha20Poly1305Encrypt,
|
||||
getHKDF,
|
||||
hashSHA256,
|
||||
} from "./crypto.js";
|
||||
import { Nonce } from "./nonce.js";
|
||||
import { HandshakePattern } from "./patterns.js";
|
||||
|
||||
// Waku Noise Protocols for Waku Payload Encryption
|
||||
// Noise module implementing the Noise State Objects and ChaChaPoly encryption/decryption primitives
|
||||
// See spec for more details:
|
||||
// https://github.com/vacp2p/rfc/tree/master/content/docs/rfcs/35
|
||||
//
|
||||
// Implementation partially inspired by noise-libp2p and js-libp2p-noise
|
||||
// https://github.com/status-im/nim-libp2p/blob/master/libp2p/protocols/secure/noise.nim
|
||||
// https://github.com/ChainSafe/js-libp2p-noise
|
||||
|
||||
/*
|
||||
# Noise state machine primitives
|
||||
|
||||
# Overview :
|
||||
# - Alice and Bob process (i.e. read and write, based on their role) each token appearing in a handshake pattern, consisting of pre-message and message patterns;
|
||||
# - Both users initialize and update according to processed tokens a Handshake State, a Symmetric State and a Cipher State;
|
||||
# - A preshared key psk is processed by calling MixKeyAndHash(psk);
|
||||
# - When an ephemeral public key e is read or written, the handshake hash value h is updated by calling mixHash(e); If the handshake expects a psk, MixKey(e) is further called
|
||||
# - When an encrypted static public key s or a payload message m is read, it is decrypted with decryptAndHash;
|
||||
# - When a static public key s or a payload message is writted, it is encrypted with encryptAndHash;
|
||||
# - When any Diffie-Hellman token ee, es, se, ss is read or written, the chaining key ck is updated by calling MixKey on the computed secret;
|
||||
# - If all tokens are processed, users compute two new Cipher States by calling Split;
|
||||
# - The two Cipher States obtained from Split are used to encrypt/decrypt outbound/inbound messages.
|
||||
|
||||
#################################
|
||||
# Cipher State Primitives
|
||||
#################################
|
||||
*/
|
||||
|
||||
// The Cipher State as in https://noiseprotocol.org/noise.html#the-cipherstate-object
|
||||
// Contains an encryption key k and a nonce n (used in Noise as a counter)
|
||||
export class CipherState {
|
||||
k: bytes32;
|
||||
// For performance reasons, the nonce is represented as a Nonce object
|
||||
// The nonce is treated as a uint64, even though the underlying `number` only has 52 safely-available bits.
|
||||
n: Nonce;
|
||||
|
||||
constructor(k: bytes32 = CipherState.createEmptyKey()) {
|
||||
this.k = k;
|
||||
this.n = new Nonce();
|
||||
}
|
||||
|
||||
// Checks if a Cipher State has an encryption key set
|
||||
protected hasKey(): boolean {
|
||||
return !this.isEmptyKey(this.k);
|
||||
}
|
||||
|
||||
static createEmptyKey(): bytes32 {
|
||||
return new Uint8Array(32);
|
||||
}
|
||||
|
||||
protected isEmptyKey(k: bytes32): boolean {
|
||||
const emptyKey = CipherState.createEmptyKey();
|
||||
return uint8ArrayEquals(emptyKey, k);
|
||||
}
|
||||
|
||||
// Encrypts a plaintext using key material in a Noise Cipher State
|
||||
// The CipherState is updated increasing the nonce (used as a counter in Noise) by one
|
||||
encryptWithAd(ad: Uint8Array, plaintext: Uint8Array): Uint8Array {
|
||||
this.n.assertValue();
|
||||
|
||||
let ciphertext = new Uint8Array();
|
||||
|
||||
if (this.hasKey()) {
|
||||
// If an encryption key is set in the Cipher state, we proceed with encryption
|
||||
|
||||
ciphertext = chaCha20Poly1305Encrypt(
|
||||
plaintext,
|
||||
this.n.getBytes(),
|
||||
ad,
|
||||
this.k
|
||||
);
|
||||
this.n.increment();
|
||||
this.n.assertValue();
|
||||
|
||||
console.trace("encryptWithAd", ciphertext, this.n.getUint64() - 1);
|
||||
} else {
|
||||
// Otherwise we return the input plaintext according to specification http://www.noiseprotocol.org/noise.html#the-cipherstate-object
|
||||
ciphertext = plaintext;
|
||||
console.debug(
|
||||
"encryptWithAd called with no encryption key set. Returning plaintext."
|
||||
);
|
||||
}
|
||||
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
// Decrypts a ciphertext using key material in a Noise Cipher State
|
||||
// The CipherState is updated increasing the nonce (used as a counter in Noise) by one
|
||||
decryptWithAd(ad: Uint8Array, ciphertext: Uint8Array): Uint8Array {
|
||||
this.n.assertValue();
|
||||
|
||||
if (this.hasKey()) {
|
||||
const plaintext = chaCha20Poly1305Decrypt(
|
||||
ciphertext,
|
||||
this.n.getBytes(),
|
||||
ad,
|
||||
this.k
|
||||
);
|
||||
if (!plaintext) {
|
||||
throw "decryptWithAd failed";
|
||||
}
|
||||
return plaintext;
|
||||
} else {
|
||||
// Otherwise we return the input ciphertext according to specification
|
||||
// http://www.noiseprotocol.org/noise.html#the-cipherstate-object
|
||||
console.debug(
|
||||
"decryptWithAd called with no encryption key set. Returning ciphertext."
|
||||
);
|
||||
return ciphertext;
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the nonce of a Cipher State
|
||||
setNonce(nonce: Nonce): void {
|
||||
this.n = nonce;
|
||||
}
|
||||
|
||||
// Sets the key of a Cipher State
|
||||
setCipherStateKey(key: bytes32): void {
|
||||
this.k = key;
|
||||
}
|
||||
|
||||
// Gets the key of a Cipher State
|
||||
getKey(): bytes32 {
|
||||
return this.k;
|
||||
}
|
||||
|
||||
// Gets the nonce of a Cipher State
|
||||
getNonce(): Nonce {
|
||||
return this.n;
|
||||
}
|
||||
}
|
||||
|
||||
function hashProtocol(name: string): Uint8Array {
|
||||
// If protocol_name is less than or equal to HASHLEN bytes in length,
|
||||
// sets h equal to protocol_name with zero bytes appended to make HASHLEN bytes.
|
||||
// Otherwise sets h = HASH(protocol_name).
|
||||
const protocolName = uint8ArrayFromString(name, "utf-8");
|
||||
|
||||
if (protocolName.length <= 32) {
|
||||
const h = new Uint8Array(32);
|
||||
h.set(protocolName);
|
||||
return h;
|
||||
} else {
|
||||
return hashSHA256(protocolName);
|
||||
}
|
||||
}
|
||||
|
||||
// The Symmetric State as in https://noiseprotocol.org/noise.html#the-symmetricstate-object
|
||||
// Contains a Cipher State cs, the chaining key ck and the handshake hash value h
|
||||
export class SymmetricState {
|
||||
cs: CipherState;
|
||||
ck: bytes32; // chaining key
|
||||
h: bytes32; // handshake hash
|
||||
|
||||
constructor(hsPattern: HandshakePattern) {
|
||||
this.h = hashProtocol(hsPattern.name);
|
||||
this.ck = this.h;
|
||||
this.cs = new CipherState();
|
||||
}
|
||||
|
||||
// MixKey as per Noise specification http://www.noiseprotocol.org/noise.html#the-symmetricstate-object
|
||||
// Updates a Symmetric state chaining key and symmetric state
|
||||
mixKey(inputKeyMaterial: Uint8Array): void {
|
||||
// We derive two keys using HKDF
|
||||
const [ck, tempK] = getHKDF(this.ck, inputKeyMaterial);
|
||||
// We update ck and the Cipher state's key k using the output of HDKF
|
||||
this.cs = new CipherState(tempK);
|
||||
this.ck = ck;
|
||||
console.trace("mixKey", this.ck, this.cs.k);
|
||||
}
|
||||
|
||||
// MixHash as per Noise specification http://www.noiseprotocol.org/noise.html#the-symmetricstate-object
|
||||
// Hashes data into a Symmetric State's handshake hash value h
|
||||
mixHash(data: Uint8Array): void {
|
||||
// We hash the previous handshake hash and input data and store the result in the Symmetric State's handshake hash value
|
||||
this.h = hashSHA256(
|
||||
uint8ArrayConcat([this.h, data], this.h.length + data.length)
|
||||
);
|
||||
console.trace("mixHash", this.h);
|
||||
}
|
||||
|
||||
// mixKeyAndHash as per Noise specification http://www.noiseprotocol.org/noise.html#the-symmetricstate-object
|
||||
// Combines MixKey and MixHash
|
||||
mixKeyAndHash(inputKeyMaterial: Uint8Array): void {
|
||||
// Derives 3 keys using HKDF, the chaining key and the input key material
|
||||
const [tempk0, tempk1, tempk2] = getHKDF(this.ck, inputKeyMaterial);
|
||||
// Sets the chaining key
|
||||
this.ck = tempk0;
|
||||
// Updates the handshake hash value
|
||||
this.mixHash(tempk1);
|
||||
// Updates the Cipher state's key
|
||||
// Note for later support of 512 bits hash functions: "If HASHLEN is 64, then truncates tempKeys[2] to 32 bytes."
|
||||
this.cs = new CipherState(tempk2);
|
||||
}
|
||||
|
||||
// EncryptAndHash as per Noise specification http://www.noiseprotocol.org/noise.html#the-symmetricstate-object
|
||||
// Combines encryptWithAd and mixHash
|
||||
// Note that by setting extraAd, it is possible to pass extra additional data that will be concatenated to the ad specified by Noise (can be used to authenticate messageNametag)
|
||||
encryptAndHash(
|
||||
plaintext: Uint8Array,
|
||||
extraAd: Uint8Array = new Uint8Array()
|
||||
): Uint8Array {
|
||||
// The additional data
|
||||
const ad = uint8ArrayConcat([this.h, extraAd]);
|
||||
// Note that if an encryption key is not set yet in the Cipher state, ciphertext will be equal to plaintex
|
||||
const ciphertext = this.cs.encryptWithAd(ad, plaintext);
|
||||
// We call mixHash over the result
|
||||
this.mixHash(ciphertext);
|
||||
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
// DecryptAndHash as per Noise specification http://www.noiseprotocol.org/noise.html#the-symmetricstate-object
|
||||
// Combines decryptWithAd and mixHash
|
||||
decryptAndHash(
|
||||
ciphertext: Uint8Array,
|
||||
extraAd: Uint8Array = new Uint8Array()
|
||||
): Uint8Array {
|
||||
// The additional data
|
||||
const ad = uint8ArrayConcat([this.h, extraAd]);
|
||||
// Note that if an encryption key is not set yet in the Cipher state, plaintext will be equal to ciphertext
|
||||
const plaintext = this.cs.decryptWithAd(ad, ciphertext);
|
||||
// According to specification, the ciphertext enters mixHash (and not the plaintext)
|
||||
this.mixHash(ciphertext);
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
// Split as per Noise specification http://www.noiseprotocol.org/noise.html#the-symmetricstate-object
|
||||
// Once a handshake is complete, returns two Cipher States to encrypt/decrypt outbound/inbound messages
|
||||
split(): { cs1: CipherState; cs2: CipherState } {
|
||||
// Derives 2 keys using HKDF and the chaining key
|
||||
const [tempk1, tempk2] = getHKDF(this.ck, new Uint8Array(0));
|
||||
// Returns a tuple of two Cipher States initialized with the derived keys
|
||||
return {
|
||||
cs1: new CipherState(tempk1),
|
||||
cs2: new CipherState(tempk2),
|
||||
};
|
||||
}
|
||||
|
||||
// Gets the chaining key field of a Symmetric State
|
||||
getChainingKey(): bytes32 {
|
||||
return this.ck;
|
||||
}
|
||||
|
||||
// Gets the handshake hash field of a Symmetric State
|
||||
getHandshakeHash(): bytes32 {
|
||||
return this.h;
|
||||
}
|
||||
|
||||
// Gets the Cipher State field of a Symmetric State
|
||||
getCipherState(): CipherState {
|
||||
return this.cs;
|
||||
}
|
||||
}
|
54
src/nonce.ts
Normal file
54
src/nonce.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import type { bytes, uint64 } from "./@types/basic";
|
||||
|
||||
export const MIN_NONCE = 0;
|
||||
// For performance reasons, the nonce is represented as a JS `number`
|
||||
// Although JS `number` can safely represent integers up to 2 ** 53 - 1, we choose to only use
|
||||
// 4 bytes to store the data for performance reason.
|
||||
// This is a slight deviation from the noise spec, which describes the max nonce as 2 ** 64 - 2
|
||||
// The effect is that this implementation will need a new handshake to be performed after fewer messages are exchanged than other implementations with full uint64 nonces.
|
||||
// this MAX_NONCE is still a large number of messages, so the practical effect of this is negligible.
|
||||
export const MAX_NONCE = 0xffffffff;
|
||||
|
||||
const ERR_MAX_NONCE =
|
||||
"Cipherstate has reached maximum n, a new handshake must be performed";
|
||||
|
||||
/**
|
||||
* The nonce is an uint that's increased over time.
|
||||
* Maintaining different representations help improve performance.
|
||||
*/
|
||||
export class Nonce {
|
||||
private n: uint64;
|
||||
private readonly bytes: bytes;
|
||||
private readonly view: DataView;
|
||||
|
||||
constructor(n = MIN_NONCE) {
|
||||
this.n = n;
|
||||
this.bytes = new Uint8Array(12);
|
||||
this.view = new DataView(
|
||||
this.bytes.buffer,
|
||||
this.bytes.byteOffset,
|
||||
this.bytes.byteLength
|
||||
);
|
||||
this.view.setUint32(4, n, true);
|
||||
}
|
||||
|
||||
increment(): void {
|
||||
this.n++;
|
||||
// Even though we're treating the nonce as 8 bytes, RFC7539 specifies 12 bytes for a nonce.
|
||||
this.view.setUint32(4, this.n, true);
|
||||
}
|
||||
|
||||
getBytes(): bytes {
|
||||
return this.bytes;
|
||||
}
|
||||
|
||||
getUint64(): uint64 {
|
||||
return this.n;
|
||||
}
|
||||
|
||||
assertValue(): void {
|
||||
if (this.n > MAX_NONCE) {
|
||||
throw new Error(ERR_MAX_NONCE);
|
||||
}
|
||||
}
|
||||
}
|
156
src/patterns.ts
Normal file
156
src/patterns.ts
Normal file
@ -0,0 +1,156 @@
|
||||
// The Noise tokens appearing in Noise (pre)message patterns
|
||||
// as in http://www.noiseprotocol.org/noise.html#handshake-pattern-basics
|
||||
export enum NoiseTokens {
|
||||
e = "e",
|
||||
s = "s",
|
||||
es = "es",
|
||||
ee = "ee",
|
||||
se = "se",
|
||||
ss = "ss",
|
||||
psk = "psk",
|
||||
}
|
||||
|
||||
// The direction of a (pre)message pattern in canonical form (i.e. Alice-initiated form)
|
||||
// as in http://www.noiseprotocol.org/noise.html#alice-and-bob
|
||||
export enum MessageDirection {
|
||||
r = "->",
|
||||
l = "<-",
|
||||
}
|
||||
|
||||
// The pre message pattern consisting of a message direction and some Noise tokens, if any.
|
||||
// (if non empty, only tokens e and s are allowed: http://www.noiseprotocol.org/noise.html#handshake-pattern-basics)
|
||||
export class PreMessagePattern {
|
||||
direction: MessageDirection;
|
||||
tokens: Array<NoiseTokens>;
|
||||
|
||||
constructor(direction: MessageDirection, tokens: Array<NoiseTokens>) {
|
||||
this.direction = direction;
|
||||
this.tokens = tokens;
|
||||
}
|
||||
|
||||
equals(b: PreMessagePattern): boolean {
|
||||
return (
|
||||
this.direction == b.direction &&
|
||||
this.tokens.length === b.tokens.length &&
|
||||
this.tokens.every((val, index) => val === b.tokens[index])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// The message pattern consisting of a message direction and some Noise tokens
|
||||
// All Noise tokens are allowed
|
||||
export class MessagePattern {
|
||||
direction: MessageDirection;
|
||||
tokens: Array<NoiseTokens>;
|
||||
|
||||
constructor(direction: MessageDirection, tokens: Array<NoiseTokens>) {
|
||||
this.direction = direction;
|
||||
this.tokens = tokens;
|
||||
}
|
||||
}
|
||||
|
||||
// The handshake pattern object. It stores the handshake protocol name, the handshake pre message patterns and the handshake message patterns
|
||||
export class HandshakePattern {
|
||||
name: string;
|
||||
preMessagePatterns: Array<PreMessagePattern>;
|
||||
messagePatterns: Array<MessagePattern>;
|
||||
|
||||
constructor(
|
||||
name: string,
|
||||
preMessagePatterns: Array<PreMessagePattern>,
|
||||
messagePatterns: Array<MessagePattern>
|
||||
) {
|
||||
this.name = name;
|
||||
this.preMessagePatterns = preMessagePatterns;
|
||||
this.messagePatterns = messagePatterns;
|
||||
}
|
||||
}
|
||||
|
||||
// Constants (supported protocols)
|
||||
export const EmptyPreMessage = new Array<PreMessagePattern>();
|
||||
|
||||
// Supported Noise handshake patterns as defined in https://rfc.vac.dev/spec/35/#specification
|
||||
export const NoiseHandshakePatterns = {
|
||||
K1K1: new HandshakePattern(
|
||||
"Noise_K1K1_25519_ChaChaPoly_SHA256",
|
||||
[
|
||||
new PreMessagePattern(MessageDirection.r, [NoiseTokens.s]),
|
||||
new PreMessagePattern(MessageDirection.l, [NoiseTokens.s]),
|
||||
],
|
||||
[
|
||||
new MessagePattern(MessageDirection.r, [NoiseTokens.e]),
|
||||
new MessagePattern(MessageDirection.l, [
|
||||
NoiseTokens.e,
|
||||
NoiseTokens.ee,
|
||||
NoiseTokens.es,
|
||||
]),
|
||||
new MessagePattern(MessageDirection.r, [NoiseTokens.se]),
|
||||
]
|
||||
),
|
||||
XK1: new HandshakePattern(
|
||||
"Noise_XK1_25519_ChaChaPoly_SHA256",
|
||||
[new PreMessagePattern(MessageDirection.l, [NoiseTokens.s])],
|
||||
[
|
||||
new MessagePattern(MessageDirection.r, [NoiseTokens.e]),
|
||||
new MessagePattern(MessageDirection.l, [
|
||||
NoiseTokens.e,
|
||||
NoiseTokens.ee,
|
||||
NoiseTokens.es,
|
||||
]),
|
||||
new MessagePattern(MessageDirection.r, [NoiseTokens.s, NoiseTokens.se]),
|
||||
]
|
||||
),
|
||||
XX: new HandshakePattern(
|
||||
"Noise_XX_25519_ChaChaPoly_SHA256",
|
||||
EmptyPreMessage,
|
||||
[
|
||||
new MessagePattern(MessageDirection.r, [NoiseTokens.e]),
|
||||
new MessagePattern(MessageDirection.l, [
|
||||
NoiseTokens.e,
|
||||
NoiseTokens.ee,
|
||||
NoiseTokens.s,
|
||||
NoiseTokens.es,
|
||||
]),
|
||||
new MessagePattern(MessageDirection.r, [NoiseTokens.s, NoiseTokens.se]),
|
||||
]
|
||||
),
|
||||
XXpsk0: new HandshakePattern(
|
||||
"Noise_XXpsk0_25519_ChaChaPoly_SHA256",
|
||||
EmptyPreMessage,
|
||||
[
|
||||
new MessagePattern(MessageDirection.r, [NoiseTokens.psk, NoiseTokens.e]),
|
||||
new MessagePattern(MessageDirection.l, [
|
||||
NoiseTokens.e,
|
||||
NoiseTokens.ee,
|
||||
NoiseTokens.s,
|
||||
NoiseTokens.es,
|
||||
]),
|
||||
new MessagePattern(MessageDirection.r, [NoiseTokens.s, NoiseTokens.se]),
|
||||
]
|
||||
),
|
||||
WakuPairing: new HandshakePattern(
|
||||
"Noise_WakuPairing_25519_ChaChaPoly_SHA256",
|
||||
[new PreMessagePattern(MessageDirection.l, [NoiseTokens.e])],
|
||||
[
|
||||
new MessagePattern(MessageDirection.r, [NoiseTokens.e, NoiseTokens.ee]),
|
||||
new MessagePattern(MessageDirection.l, [NoiseTokens.s, NoiseTokens.es]),
|
||||
new MessagePattern(MessageDirection.r, [
|
||||
NoiseTokens.s,
|
||||
NoiseTokens.se,
|
||||
NoiseTokens.ss,
|
||||
]),
|
||||
]
|
||||
),
|
||||
};
|
||||
|
||||
// Supported Protocol ID for PayloadV2 objects
|
||||
// Protocol IDs are defined according to https://rfc.vac.dev/spec/35/#specification
|
||||
export const PayloadV2ProtocolIDs = {
|
||||
"": 0,
|
||||
Noise_K1K1_25519_ChaChaPoly_SHA256: 10,
|
||||
Noise_XK1_25519_ChaChaPoly_SHA256: 11,
|
||||
Noise_XX_25519_ChaChaPoly_SHA256: 12,
|
||||
Noise_XXpsk0_25519_ChaChaPoly_SHA256: 13,
|
||||
Noise_WakuPairing_25519_ChaChaPoly_SHA256: 14,
|
||||
ChaChaPoly: 30,
|
||||
};
|
28
src/publickey.ts
Normal file
28
src/publickey.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { equals as uint8ArrayEquals } from "uint8arrays/equals";
|
||||
|
||||
import { bytes32 } from "./@types/basic";
|
||||
|
||||
// A Noise public key is a public key exchanged during Noise handshakes (no private part)
|
||||
// This follows https://rfc.vac.dev/spec/35/#public-keys-serialization
|
||||
// pk contains the X coordinate of the public key, if unencrypted (this implies flag = 0)
|
||||
// or the encryption of the X coordinate concatenated with the authorization tag, if encrypted (this implies flag = 1)
|
||||
// Note: besides encryption, flag can be used to distinguish among multiple supported Elliptic Curves
|
||||
export class NoisePublicKey {
|
||||
flag: number;
|
||||
pk: Uint8Array;
|
||||
|
||||
constructor(flag: number, pk: Uint8Array) {
|
||||
this.flag = flag;
|
||||
this.pk = pk;
|
||||
}
|
||||
|
||||
// Checks equality between two Noise public keys
|
||||
equals(k2: NoisePublicKey): boolean {
|
||||
return this.flag == k2.flag && uint8ArrayEquals(this.pk, k2.pk);
|
||||
}
|
||||
|
||||
// Converts a public Elliptic Curve key to an unencrypted Noise public key
|
||||
static to(publicKey: bytes32): NoisePublicKey {
|
||||
return new NoisePublicKey(0, publicKey);
|
||||
}
|
||||
}
|
8
tsconfig.dev.json
Normal file
8
tsconfig.dev.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig",
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"noEmit": true
|
||||
},
|
||||
"exclude": []
|
||||
}
|
49
tsconfig.json
Normal file
49
tsconfig.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"target": "es2020",
|
||||
"outDir": "dist/",
|
||||
"rootDir": "src",
|
||||
"moduleResolution": "node",
|
||||
"module": "es2020",
|
||||
"declaration": true,
|
||||
"allowJs": true,
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||
"resolveJsonModule": true /* Include modules imported with .json extension. */,
|
||||
"tsBuildInfoFile": "dist/.tsbuildinfo",
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
/* Strict Type-Checking Options */
|
||||
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
|
||||
"strictNullChecks": true /* Enable strict null checks. */,
|
||||
"strictFunctionTypes": true /* Enable strict checking of function types. */,
|
||||
"strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */,
|
||||
"noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */,
|
||||
"alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */,
|
||||
/* Additional Checks */
|
||||
"noUnusedLocals": true /* Report errors on unused locals. */,
|
||||
"noUnusedParameters": true /* Report errors on unused parameters. */,
|
||||
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
|
||||
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
/* Debugging Options */
|
||||
"traceResolution": false /* Report module resolution log messages. */,
|
||||
"listEmittedFiles": false /* Print names of generated files part of the compilation. */,
|
||||
"listFiles": false /* Print names of files part of the compilation. */,
|
||||
"pretty": true /* Stylize errors and messages using color and context. */,
|
||||
// Due to broken types in indirect dependencies
|
||||
"skipLibCheck": true,
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */,
|
||||
// "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */,
|
||||
"lib": ["es2020", "dom"],
|
||||
"types": ["node", "mocha"],
|
||||
"typeRoots": ["node_modules/@types", "src/@types"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.spec.ts", "src/test_utils"],
|
||||
"compileOnSave": false,
|
||||
"ts-node": {
|
||||
"files": true
|
||||
}
|
||||
}
|
6
tsconfig.karma.json
Normal file
6
tsconfig.karma.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "./tsconfig.dev",
|
||||
"compilerOptions": {
|
||||
"noEmit": false
|
||||
}
|
||||
}
|
10
typedoc.json
Normal file
10
typedoc.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"entryPoints": ["./src/index.ts"],
|
||||
"out": "build/docs",
|
||||
"exclude": "**/*.spec.ts",
|
||||
"excludeInternal": true,
|
||||
"validation": {
|
||||
"invalidLink": true,
|
||||
"notExported": true
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user