Compare commits

...

123 Commits

Author SHA1 Message Date
Pavel Murygin
bd737dc2c6 bump dependencies 2021-04-09 17:03:41 +03:00
Marin Petrunić
522d28cd8b
Update README.md 2021-03-19 09:16:45 +01:00
Marin Petrunić
d482e44882
Update README.md 2021-03-19 09:13:12 +01:00
Marin Petrunić
82d093accc
Merge pull request #90 from NodeFactoryIo/release-2.0.5
Release v2.0.5
2021-02-26 10:22:23 +01:00
Marin Petrunić
98aeb5a59b
Release v2.0.5 2021-02-24 09:26:30 +01:00
Marin Petrunić
81a70d1e4f
Merge pull request #89 from hugomrdias/feat/new-aegir
fix: fix repo to use new aegir
2021-02-24 09:21:39 +01:00
Hugo Dias
34bb8d4ef7
fix: test node 14 2021-02-23 17:16:01 +00:00
Hugo Dias
a9b99e4e26
fix: fix ci to build before check 2021-02-23 17:12:41 +00:00
Hugo Dias
cc982dd240
fix: action branch 2021-02-23 17:05:20 +00:00
Hugo Dias
9b914ad94c
fix: update aegir 2021-02-23 17:01:31 +00:00
Hugo Dias
a2abb93d8b
fix: lock 2021-02-18 16:59:18 +00:00
Hugo Dias
c23ba08f58
fix: aegir types 2021-02-18 16:53:32 +00:00
Hugo Dias
5d36667b0c
chore: fix webkit ci 2021-02-12 18:03:42 +00:00
Hugo Dias
0e6ceaa72f
chore: cache .. 2021-02-12 17:58:52 +00:00
Hugo Dias
3095240532
chore: fix test node and readd cache 2021-02-12 17:53:46 +00:00
Hugo Dias
8373dec1af
chore: remove cache 2021-02-12 17:48:59 +00:00
Hugo Dias
34effc2ef5
chore: lock 2021-02-12 17:44:13 +00:00
Hugo Dias
7a0d01abcc
chore: trigger ci 2021-02-12 17:35:26 +00:00
Hugo Dias
693c833942
chore: update action 2021-02-12 16:57:07 +00:00
Hugo Dias
c36b0a686a
chore: lock 2021-02-12 16:48:01 +00:00
Hugo Dias
a00ed39a4f
Merge branch 'feat/new-aegir' of https://github.com/hugomrdias/js-libp2p-noise into feat/new-aegir
* 'feat/new-aegir' of https://github.com/hugomrdias/js-libp2p-noise:
  fix package.json typo and bump nvm to 14
2021-02-12 16:32:00 +00:00
Hugo Dias
30a1955929
fix: new aegir 2021-02-12 16:29:20 +00:00
Marin Petrunić
279c2e6f64
fix package.json typo and bump nvm to 14 2021-02-10 13:34:58 +01:00
Marin Petrunić
94e4ffe0a5
only run ci on push to master 2021-01-27 12:03:43 +01:00
Marin Petrunić
fd99dc88fe
Add better names to CI 2021-01-27 11:57:40 +01:00
Marin Petrunić
47e773509e
add test steps to ga ci and remove travis 2021-01-27 11:57:35 +01:00
Marin Petrunić
ef3ae768d0
add check job in ci 2021-01-27 11:57:28 +01:00
Hugo Dias
2038b26b76
fix: fix setup for new aegir
- tweak tsconfig.js
- add ts type check including test files
- fix ci setup for new aegir
- add new awesome aegir docs feature
  - you can publish to github pages with the flag --publish
2021-01-26 21:52:12 +00:00
Hugo Dias
89dd965bd2
chore: add docs to gitignore 2021-01-26 21:39:58 +00:00
Hugo Dias
ead151471e
fix: ts and lint errors 2021-01-26 21:39:37 +00:00
Marin Petrunić
2df5f9546e
Merge pull request #87 from NodeFactoryIo/release-2.0.4
Release 2.0.4
2021-01-26 13:02:12 +01:00
Marin Petrunić
8d3b130509
release 2.0.4 2021-01-26 12:59:26 +01:00
Marin Petrunić
941fe281cb
Merge pull request #85 from NodeFactoryIo/release-v2.0.3
Release v2.0.3
2021-01-26 09:29:42 +01:00
Marin Petrunić
d41e9aa831
Release v2.0.3 2021-01-26 09:28:16 +01:00
Marin Petrunić
24d4eabb8c
Merge pull request #84 from NodeFactoryIo/release-2.0.2
Release v2.0.2
2021-01-25 12:29:05 +01:00
Marin Petrunić
4f7bdf368f
Release v2.0.2 2021-01-25 12:28:11 +01:00
Marin Petrunić
46c6458414
Merge pull request #83 from NodeFactoryIo/dependabot/npm_and_yarn/node-fetch-2.6.1
Bump node-fetch from 2.6.0 to 2.6.1
2021-01-25 12:25:50 +01:00
dependabot[bot]
b484a5f39c
Bump node-fetch from 2.6.0 to 2.6.1
Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.6.0 to 2.6.1.
- [Release notes](https://github.com/bitinn/node-fetch/releases)
- [Changelog](https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/bitinn/node-fetch/compare/v2.6.0...v2.6.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-25 11:16:15 +00:00
Marin Petrunić
00968da390
Merge pull request #82 from NodeFactoryIo/chore/update-deps-25_01_2021
chore: update deps
2021-01-25 12:15:34 +01:00
Vasco Santos
11d7f21920 chore: update deps 2021-01-25 11:40:21 +01:00
Marin Petrunić
d525a790a8
Merge pull request #81 from NodeFactoryIo/dependabot/npm_and_yarn/socket.io-2.4.1
Bump socket.io from 2.3.0 to 2.4.1
2021-01-21 14:54:34 +01:00
dependabot[bot]
be8778e542
Bump socket.io from 2.3.0 to 2.4.1
Bumps [socket.io](https://github.com/socketio/socket.io) from 2.3.0 to 2.4.1.
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/2.4.1/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/2.3.0...2.4.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-21 13:42:43 +00:00
Marin Petrunić
e362dbcf7b
Merge pull request #79 from NodeFactoryIo/dependabot/npm_and_yarn/ini-1.3.8
Bump ini from 1.3.5 to 1.3.8
2020-12-12 07:54:35 +01:00
dependabot[bot]
1db2cf19e8
Bump ini from 1.3.5 to 1.3.8
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-12 00:06:00 +00:00
Marin Petrunić
f9d56d8c87
fix secret name 2020-09-03 14:06:31 +02:00
Marin Petrunić
c5dac93733
Merge pull request #78 from NodeFactoryIo/mpetrunic/cd
fix #3 for yarn publish
2020-09-03 13:55:02 +02:00
Marin Petrunić
2ac2261df2
fix #3 for yarn publish 2020-09-03 13:20:28 +02:00
Marin Petrunić
0f47db25f1
Merge pull request #77 from NodeFactoryIo/mpetrunic/cd
add missing quotation
2020-09-03 13:15:20 +02:00
Marin Petrunić
c90aa1b018
add missing qutation 2020-09-03 12:59:03 +02:00
Marin Petrunić
f0fefdc530
Merge pull request #76 from NodeFactoryIo/mpetrunic/cd
Fix cd script
2020-09-03 12:57:43 +02:00
Marin Petrunić
0345aea790
fix cd script 2020-09-03 12:39:31 +02:00
Marin Petrunić
7a1d0dc43a
Merge pull request #75 from NodeFactoryIo/mpetrunic/cd
Release 2.0.1
2020-09-03 12:27:02 +02:00
Marin Petrunić
489d60ca6d
Merge pull request #74 from NodeFactoryIo/mpetrunic/fix-missing-files
fix missing type declaration in dist
2020-09-03 12:04:47 +02:00
Marin Petrunić
c2784b1d37
Release 2.0.1 2020-09-03 10:40:43 +02:00
Marin Petrunić
2ab12403a6
add publish workflow 2020-09-03 10:40:24 +02:00
Marin Petrunić
59b6c96355
fix missing type declaration in dist 2020-09-03 10:16:06 +02:00
Marin Petrunić
2a8e3ef8dd
Merge pull request #73 from NodeFactoryIo/dependabot/npm_and_yarn/bl-4.0.3
Bump bl from 4.0.2 to 4.0.3
2020-09-03 10:07:33 +02:00
dependabot[bot]
7a392bd1de
Bump bl from 4.0.2 to 4.0.3
Bumps [bl](https://github.com/rvagg/bl) from 4.0.2 to 4.0.3.
- [Release notes](https://github.com/rvagg/bl/releases)
- [Commits](https://github.com/rvagg/bl/compare/v4.0.2...v4.0.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-02 16:21:18 +00:00
Marin Petrunić
14c1905307
Release v2.0.0 2020-08-20 10:09:43 +02:00
Marin Petrunić
661f48dcf5
Merge pull request #72 from NodeFactoryIo/release-2.0.0-beta
Release v2.0.0-beta
2020-08-20 09:25:00 +02:00
Marin Petrunić
7b54d65c03
Release v2.0.0-beta 2020-08-20 09:10:02 +02:00
Marin Petrunić
5ae0639f52
Merge pull request #71 from NodeFactoryIo/release-1.2.0
Release 1.2.0-rc.1
2020-08-19 15:45:57 +02:00
Marin Petrunić
b9805b3dc3
fix lint 2020-08-15 21:23:19 +02:00
Marin Petrunić
31705d5f89
Release 1.2.0-rc.1 2020-08-15 14:49:32 +02:00
Marin Petrunić
006e35a3bc
fix yarn conflict 2020-08-15 14:48:57 +02:00
Marin Petrunić
ec4eacadfa
Merge remote-tracking branch 'origin/master' into release-1.2.0
# Conflicts:
#	yarn.lock
2020-08-15 14:48:38 +02:00
Marin Petrunić
29efe156c0
Release 1.2.0-rc.1 2020-08-15 14:47:56 +02:00
Marin Petrunić
66e569cb65
fix conflicts 2020-08-15 14:44:54 +02:00
Marin Petrunić
e20e4eb293
Merge pull request #70 from achingbrain/fix/update-deps
chore: update deps to latest versions
2020-08-15 14:43:42 +02:00
Marin Petrunić
8da430cdc5
add benchmark 2020-08-15 14:37:43 +02:00
achingbrain
a6c6dc5ef0 chore: remove package-lock.json 2020-08-11 15:24:57 +01:00
achingbrain
de1cdb5bd0 chore: fix ts error 2020-08-11 15:14:11 +01:00
achingbrain
e52fe108ad chore: update yarn lock fiel 2020-08-11 12:16:52 +01:00
achingbrain
1a6490d829 chore: update deps to latest versions
I've tried to make the minimum amount of changes necessary for this,
since the underlying crypto libraries only support node Buffers or
BufferLists there doesn't seem a lot of point in doing lots of
conversions between Uint8Arrays and Buffers.

BREAKING CHANGES:

- All deps use Uint8Arrays in place of node Buffers
2020-08-11 11:41:10 +01:00
Belma Gutlic
f1b92a9f1b
Merge pull request #69 from dapplion/dapplion/readme-highlight
Add syntax highlight in README code snippet
2020-06-29 09:58:47 +02:00
dapplion
dccdc678b1 Add syntax highlight in README code snippet 2020-06-28 22:51:42 +02:00
Marin Petrunić
e2747854ec
Merge pull request #68 from achingbrain/chore/use-http-github-link-for-repo
chore: use http link for github repo
2020-06-23 18:41:03 +02:00
Marin Petrunić
e01d8e293a
Merge pull request #67 from NodeFactoryIo/mpetrunic/lint-fix
Fix lint errors and tests
2020-06-23 17:36:45 +02:00
achingbrain
08b1c7197b chore: use http link for github repo
The page for this module on npm [doesn't have a link to the repo](https://www.npmjs.com/package/libp2p-noise),
I guess because it's specified as an ssh-type URL in the package.json
file so npm can't turn it into a hyperlink.

The change here is to use a `git+https` URL which should let npm
display a link on the module page.
2020-06-23 15:33:47 +01:00
Marin Petrunić
40b547a6b1
fix travis windows 2020-06-23 12:00:01 +02:00
Marin Petrunić
e9bc0dbe44
run codecov only on linux 2020-06-23 11:11:39 +02:00
Marin Petrunić
fd1cc28f41
publish codecov only from single matrix 2020-06-23 10:56:03 +02:00
Marin Petrunić
c928cc1514
add node globals in ci 2020-06-23 10:18:05 +02:00
Marin Petrunić
9dc300c65e
fix tests and Pr comments 2020-06-23 10:12:58 +02:00
Marin Petrunić
d01d6f428a
fix error because original types are invalid 2020-06-19 13:32:32 +02:00
Marin Petrunić
153f51ae5a
fix type errors 2020-06-19 13:23:18 +02:00
Marin Petrunić
e16e25cbef
add missing karma plugin 2020-06-19 13:12:23 +02:00
Marin Petrunić
ca39bc5d99
lint fixes 2020-06-19 13:06:31 +02:00
Marin Petrunić
8327a60356
fix automatic lint 2020-06-19 12:49:40 +02:00
Marin Petrunić
9b11560183
fix some configurations 2020-06-19 12:49:10 +02:00
Marin Petrunić
c4469d55e4
Merge pull request #65 from NodeFactoryIo/mpetrunic/add-codeowners
add codeowners file
2020-06-19 11:20:10 +02:00
Marin Petrunić
273678aa49
add codeowners file 2020-06-19 11:18:49 +02:00
Marin Petrunić
7f2a37f692
Merge pull request #64 from hugomrdias/feat/aegir
feat: use aegir
2020-06-19 11:15:53 +02:00
Hugo Dias
069a2f9573
fix: feedback 2020-06-19 10:03:34 +01:00
Hugo Dias
a8274ad416
fix: fix package.json 2020-06-18 22:24:59 +01:00
Hugo Dias
f05150e640
feat: use aegir 2020-06-18 22:23:39 +01:00
Marin Petrunić
3a782aadaa
Merge pull request #63 from NodeFactoryIo/release-1.1.2
Release 1.1.2
2020-06-15 14:51:53 +02:00
Marin Petrunić
496dfd3005
Release 1.1.2 2020-06-15 14:46:59 +02:00
Marin Petrunić
a504e3abec
Merge pull request #62 from NodeFactoryIo/mpetrunic/fix-web-build
Remove regeneratorRuntime dep in wbe build
2020-06-15 14:45:43 +02:00
Marin Petrunić
088e43642a
update changelog 2020-06-15 13:48:19 +02:00
Marin Petrunić
e35f067b5e
change babel build targets 2020-06-15 13:46:57 +02:00
Marin Petrunić
0fdb309d8c
Merge pull request #60 from NodeFactoryIo/chore-release
Release 1.1.1
2020-05-08 21:26:22 +02:00
Marin Petrunić
d188709247
Release 1.1.1 2020-05-08 19:20:56 +02:00
Marin Petrunić
ef80af69c7
Merge pull request #59 from jacobheun/fix/public-key
fix: marshal the verified peer from its public key
2020-05-08 19:13:03 +02:00
Jacob Heun
e9af13a1ee
fix: marshal the verified peer from its public key
This ensures that completed outbound handshakes will always contain the public key of the remote peer
2020-05-07 16:24:15 +02:00
Belma Gutlic
c8905cc774
Merge pull request #57 from NodeFactoryIo/morrigan/v1.1.0
Release 1.1.0
2020-04-23 15:46:38 +02:00
morrigan
8cdbc3dc70 Release 1.1.0 2020-04-23 15:06:19 +02:00
Marin Petrunić
324f555e0c
Merge pull request #56 from NodeFactoryIo/revert-51-remove-bcrypto-dependency
Revert "Replace bcrypto with standalone libraries"
2020-04-23 14:51:54 +02:00
morrigan
635963062a Address PR comments 2020-04-23 12:56:09 +02:00
morrigan
6e38ba69b9 Remove tsc incremental flag 2020-04-23 12:49:57 +02:00
morrigan
5cd8a902fb Fix eslint 2020-04-23 08:10:41 +02:00
morrigan
dff7f8bae0 Fix yarn.lock 2020-04-22 19:31:24 +02:00
morrigan
3b0df24fbd Release 1.1.0-rc2 2020-04-22 19:10:20 +02:00
morrigan
60b2367dac Remove bn.js 2020-04-22 19:09:35 +02:00
morrigan
f5f4b9f344 Merge branch 'morrigan/fix-interop' into revert-51-remove-bcrypto-dependency
# Conflicts:
#	package.json
#	src/@types/basic.ts
#	src/handshakes/ik.ts
#	src/handshakes/xx.ts
#	yarn.lock
2020-04-22 19:07:03 +02:00
Belma Gutlic
7b11f5a3ab
Revert "Replace bcrypto with standalone libraries" 2020-04-22 19:03:41 +02:00
Marin Petrunić
866ae6d333 add bundle analyzer
# Conflicts:
#	package.json
#	webpack.bundle.config.js
#	yarn.lock
2020-04-22 19:01:30 +02:00
Marin Petrunić
cc96cf5e37 remove bn.js
# Conflicts:
#	src/@types/basic.ts
#	src/handshakes/ik.ts
#	src/handshakes/xx.ts
2020-04-22 18:59:38 +02:00
Marin Petrunić
2efbfcb105
update changelog 2020-04-03 13:22:29 +02:00
Marin Petrunić
fddab30049
fix tests 2020-04-03 09:39:53 +02:00
Marin Petrunić
c747634c4f
add bundle analyzer 2020-04-03 09:32:19 +02:00
Marin Petrunić
2d3c6167db
adjust imports for smaller bundlesize 2020-04-03 09:24:58 +02:00
Marin Petrunić
e7ec32ca2a
update bcrypto dependency 2020-04-03 09:07:29 +02:00
68 changed files with 17561 additions and 7338 deletions

28
.aegir.js Normal file
View File

@ -0,0 +1,28 @@
const path = require('path')
/** @type {import('aegir').Options["build"]["config"]} */
const esbuild = {
inject: [path.join(__dirname, 'test/fixtures/node-globals.js')]
}
/** @type {import('aegir').PartialOptions} */
const config = {
tsRepo: true,
docs: {
entryPoint: "src/index.ts"
},
test: {
browser :{
config: {
buildConfig: esbuild
}
}
},
build: {
bundlesizeMax: '214KB',
config: esbuild
}
}
module.exports = config

View File

@ -1,21 +0,0 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "10"
}
}
],
["@babel/preset-typescript", {
"allowNamespaces": true
}]
],
"plugins": [
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-proposal-export-default-from",
"@babel/plugin-proposal-async-generator-functions",
"@babel/plugin-proposal-class-properties"
]
}

View File

@ -1,21 +0,0 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"env": {
"mocha": true
},
"plugins": ["@typescript-eslint"],
"extends": ["plugin:@typescript-eslint/recommended"],
"rules": {
"new-parens": "error",
"no-caller": "error",
"no-bitwise": "off",
"@typescript-eslint/indent": ["error", 2],
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/interface-name-prefix": ["error", { "prefixWithI": "always" }],
"no-console": "warn"
}
}

1
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1 @@
* @morrigan @mpetrunic

174
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,174 @@
name: CI
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
check:
name: Lint and Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v1
with:
node-version: 14
- uses: actions/checkout@v2
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn --prefer-offline --frozen-lockfile
- run: yarn run build
- run: yarn run check
- uses: ipfs/aegir/actions/bundle-size@master
name: Check bundle size
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
test-node:
name: Test Nodejs
needs: check
strategy:
matrix:
node: [14]
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v2
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn --prefer-offline --frozen-lockfile
- run: npx aegir test -t node --bail --cov
- uses: codecov/codecov-action@v1
test-chrome:
name: Test Chrome
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v2
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn --prefer-offline --frozen-lockfile
- run: npx aegir test -t browser -t webworker --bail
test-firefox:
name: Test Firefox
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v2
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn --prefer-offline --frozen-lockfile
- run: npx aegir test -t browser -t webworker --bail -- --browser firefox
test-webkit:
name: Test Webkit
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn --prefer-offline --frozen-lockfile
- run: npx aegir test -t browser -t webworker --bail -- --browser webkit
test-electron-main:
name: Test Electron Main
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v2
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn --prefer-offline --frozen-lockfile
- run: npx xvfb-maybe aegir test -t electron-main --bail
test-electron-renderer:
name: Test Electron Renderer
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- uses: actions/checkout@v2
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn --prefer-offline --frozen-lockfile
- run: npx xvfb-maybe aegir test -t electron-renderer --bail

View File

@ -1,27 +0,0 @@
name: npm-publish
on:
push:
branches:
- master # Change this to your default branch
jobs:
npm-publish:
name: npm-publish
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@master
- name: Set up Node.js
uses: actions/setup-node@master
with:
node-version: 12.4.0
- name: Build library
run: yarn install --frozen-lockfile && yarn run build
- name: Publish if version has been updated
uses: pascalgn/npm-publish-action@51fdb4531e99aac1873764ef7271af448dc42ab4
with: # All of theses inputs are optional
tag_name: "v%s"
tag_message: "v%s"
commit_pattern: "^Release (\\S+)"
env: # More info about the environment variables in the README
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Leave this as is, it's automatically generated
NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} # You need to set this in your repo settings

80
.github/workflows/publish.yml vendored Normal file
View File

@ -0,0 +1,80 @@
name: Release
on:
push:
branches:
- 'master'
jobs:
tag:
name: Check and Tag
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Create tag
id: tag
uses: butlerlogic/action-autotag@1.1.1
with:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
strategy: package # Optional, since "package" is the default strategy
tag_prefix: "v"
outputs:
tag: ${{ steps.tag.outputs.tagname }}
version: ${{ steps.tag.outputs.version }}
publish:
name: Publish
runs-on: ubuntu-latest
needs: tag
if: ${{ needs.tag.outputs.tag != ''}}
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Nodejs
uses: actions/setup-node@v1
with:
node-version: '14'
always-auth: true
registry-url: 'https://registry.npmjs.org'
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
- name: Install dependencies
run: yarn install --frozen-lockfile --non-interactive
- name: Build packages
run: yarn run build
- name: Publish packages
# manual switch to latest
run: yarn publish --ignore-scripts --no-git-tag-version --no-commit-hooks --non-interactive --tag beta
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
- name: Get Changelog Entry
id: changelog_reader
uses: mindsers/changelog-reader-action@v2
with:
version: ${{ needs.tag.outputs.version }}
path: ./CHANGELOG.md
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ needs.tag.outputs.tag }}
body: ${{ steps.changelog_reader.outputs.changes }}
prerelease: true
release_name: Release ${{ needs.tag.outputs.tag }}
#in case of failure
- name: Rollback on failure
if: failure()
uses: author/action-rollback@9ec72a6af74774e00343c6de3e946b0901c23013
with:
id: ${{ steps.create_release.outputs.id }}
tag: ${{ needs.tag.outputs.tag }}
delete_orphan_tag: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

2
.gitignore vendored
View File

@ -1,9 +1,11 @@
bundle
node_modules/
.idea
.env
.nyc_output
lib
dist
docs
# Logs
logs

2
.nvmrc
View File

@ -1 +1 @@
12.4.0
14

View File

@ -1,12 +0,0 @@
language: node_js
cache: false
install:
- yarn install --frozen-lockfile --network-timeout 1000000
script:
set -e;
yarn run lint;
yarn run build;
yarn run test

View File

@ -5,6 +5,53 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.5]
#### Bugfixes
- update aegir to latest
- update dependencies
## [2.0.4]
#### Bugfixes
- downgrade aegir because js files are missing in dist directory
## [2.0.3]
#### Bugfixes
- update peer-id dependency
## [2.0.2]
#### Bugfixes
- update dependencies
## [2.0.1]
#### Bugfixes
- security update for bl dependency
- add missing type declaration files in dist
## [2.0.0]
#### Features
- switched to aegir for building and linting
- using peer id with Uint8Arrays (breaking!)
## [1.1.2]
#### Bugfixes
- fix issue where web build depends on global regeneratorRuntime
## [1.1.1] - 2020-05-08
#### Bugfixes
- fix issue [#58](https://github.com/NodeFactoryIo/js-libp2p-noise/issues/58)
## [1.1.0] - 2020-04-23
Stable version, interoperable with go.
Using reduced size with bcrypto.
## [1.1.0-rc.1] - 2020-04-22
- Added early data API
@ -19,6 +66,9 @@ Stable version, interobable with go-libp2p-noise!
- fix types to be compatible with rest of libp2p typescript projects
- update it-pb-rpc to 0.1.8 (contains proper typescript types)
### Bugfixes
- changed bcrypto imports to use pure js versions (web bundle size reduction)
## [1.0.0-rc.9] - 2019-03-11
### Bugfixes

View File

@ -1,7 +1,7 @@
# js-libp2p-noise
![npm](https://img.shields.io/npm/v/libp2p-noise)
[![Build Status](https://travis-ci.com/NodeFactoryIo/js-libp2p-noise.svg?branch=master)](https://travis-ci.com/NodeFactoryIo/js-libp2p-noise)
[![CI](https://github.com/NodeFactoryIo/js-libp2p-noise/actions/workflows/ci.yml/badge.svg?branch=master&event=push)](https://github.com/NodeFactoryIo/js-libp2p-noise/actions/workflows/ci.yml)
[![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](https://libp2p.io/)
![](https://img.shields.io/github/issues-raw/nodefactoryio/js-libp2p-noise)
@ -15,14 +15,16 @@
This repository contains TypeScript implementation of noise protocol, an encryption protocol used in libp2p.
##### Warning: Even though this package works in browser, it will bundle around 1.5Mb of code
##### Warning: Even though this package works in browser, it will bundle around 600Kb (200Kb gzipped) of code
https://bundlephobia.com/result?p=libp2p-noise@latest
## Usage
Install with `yarn add libp2p-noise` or `npm i libp2p-noise`.
Example of using default noise configuration and passing it to the libp2p config:
```
```js
import {NOISE, Noise} from "libp2p-noise"

View File

@ -1,4 +0,0 @@
require('@babel/register')({
extensions: ['.ts'],
ignore: ['node_modules'],
})

View File

@ -1,21 +0,0 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "10"
}
}
],
["@babel/preset-typescript", {
"allowNamespaces": true
}]
],
"plugins": [
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-proposal-export-default-from",
"@babel/plugin-proposal-async-generator-functions",
"@babel/plugin-proposal-class-properties"
]
}

View File

@ -1,22 +0,0 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": "last 2 versions, not ie <=11"
},
"modules": false
}
],
["@babel/preset-typescript", {
"allowNamespaces": true
}]
],
"plugins": [
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-proposal-export-default-from",
"@babel/plugin-proposal-async-generator-functions",
"@babel/plugin-proposal-class-properties"
]
}

40
benchmarks/benchmark.js Normal file
View File

@ -0,0 +1,40 @@
/* eslint-disable */
const { Noise } = require('../dist/src/index')
const benchmark = require('benchmark')
const DuplexPair = require('it-pair/duplex')
const PeerId = require('peer-id')
const bench = async function () {
console.log('Initializing handshake benchmark')
const initiator = new Noise()
const initiatorPeer = await PeerId.createFromJSON({
id: '12D3KooWH45PiqBjfnEfDfCD6TqJrpqTBJvQDwGHvjGpaWwms46D',
privKey: 'CAESYBtKXrMwawAARmLScynQUuSwi/gGSkwqDPxi15N3dqDHa4T4iWupkMe5oYGwGH3Hyfvd/QcgSTqg71oYZJadJ6prhPiJa6mQx7mhgbAYfcfJ+939ByBJOqDvWhhklp0nqg==',
pubKey: 'CAESIGuE+IlrqZDHuaGBsBh9x8n73f0HIEk6oO9aGGSWnSeq'
})
const responder = new Noise()
const responderPeer = await PeerId.createFromJSON({
id: '12D3KooWP63uzL78BRMpkQ7augMdNi1h3VBrVWZucKjyhzGVaSi1',
privKey: 'CAESYPxO3SHyfc2578hDmfkGGBY255JjiLuVavJWy+9ivlpsxSyVKf36ipyRGL6szGzHuFs5ceEuuGVrPMg/rW2Ch1bFLJUp/fqKnJEYvqzMbMe4Wzlx4S64ZWs8yD+tbYKHVg==',
pubKey: 'CAESIMUslSn9+oqckRi+rMxsx7hbOXHhLrhlazzIP61tgodW'
})
console.log('Init complete, running benchmark')
const bench = new benchmark('handshake', {
defer: true,
fn: async function (deffered) {
const [inboundConnection, outboundConnection] = DuplexPair()
await Promise.all([
initiator.secureOutbound(initiatorPeer, outboundConnection, responderPeer),
responder.secureInbound(responderPeer, inboundConnection, initiatorPeer)
])
deffered.resolve()
}
})
.on('complete', function (stats) {
console.log(String(stats.currentTarget))
})
bench.run({ async: true })
}
bench()

View File

@ -1,26 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-require-imports
const webpackConfig = require("./webpack.config");
module.exports = function(config) {
config.set({
basePath: "",
frameworks: ["mocha", "chai"],
files: ["test/**/*.test.ts"],
exclude: [],
preprocessors: {
"test/**/*.ts": ["webpack"]
},
webpack: {
mode: "production",
node: webpackConfig.node,
module: webpackConfig.module,
resolve: webpackConfig.resolve
},
reporters: ["spec"],
browsers: ["ChromeHeadless"],
singleRun: true
});
};

10015
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,16 @@
{
"name": "libp2p-noise",
"version": "1.1.0-rc1",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"module": "lib/index.js",
"version": "2.0.5",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
"files": [
"dist",
"lib",
"src"
],
"repository": "git@github.com:NodeFactoryIo/js-libp2p-noise.git",
"repository": {
"type": "git",
"url": "git+https://github.com/NodeFactoryIo/js-libp2p-noise.git"
},
"author": "NodeFactory <info@nodefactory.io>",
"license": "MIT",
"keywords": [
@ -18,71 +19,59 @@
"crypto"
],
"scripts": {
"prebuild": "rm -rf lib && rm -rf dist",
"build": "yarn run build:node && yarn run build:web && yarn run build:types",
"bundle": "webpack --config webpack.bundle.config.js",
"build:node": "babel --no-babelrc --config-file ./babel.config.json src --copy-files -x .ts -d dist --source-maps",
"build:web": "babel --no-babelrc --config-file ./babel.web.config.json src --copy-files -x .ts -d lib --source-maps",
"build:types": "tsc --declaration --outDir dist --emitDeclarationOnly",
"proto:gen": "pbjs -t static-module -o ./src/proto/payload.js ./src/proto/payload.proto && pbts -o ./src/proto/payload.d.ts ./src/proto/payload.js && yarn run lint --fix",
"check-types": "tsc --incremental --noEmit",
"lint": "eslint --ext .ts src/",
"pretest": "yarn check-types",
"test": "yarn run test:node && yarn run test:web",
"test:node": "mocha -r ./babel-register.js \"test/**/*.test.ts\"",
"test:web": "karma start"
"bench": "node benchmarks/benchmark.js",
"clean": "rm -rf dist",
"check": "aegir dep-check && aegir ts -p check",
"build": "aegir build",
"lint": "aegir lint",
"lint:fix": "aegir lint --fix",
"pretest": "yarn run check",
"test": "aegir test",
"test:node": "aegir test -t node",
"test:browser": "aegir test -t browser",
"docs": "aegir docs",
"proto:gen": "pbjs -t static-module -o ./src/proto/payload.js ./src/proto/payload.proto && pbts -o ./src/proto/payload.d.ts ./src/proto/payload.js && yarn run lint --fix"
},
"browser": {
"util": false
},
"devDependencies": {
"@babel/cli": "^7.6.4",
"@babel/core": "^7.6.4",
"@babel/plugin-proposal-async-generator-functions": "^7.7.0",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-export-default-from": "^7.8.3",
"@babel/plugin-proposal-object-rest-spread": "^7.6.2",
"@babel/preset-env": "^7.6.3",
"@babel/preset-typescript": "^7.6.0",
"@babel/register": "^7.6.2",
"@babel/runtime": "^7.6.3",
"@types/bl": "^2.1.0",
"@types/chai": "^4.2.4",
"@types/mocha": "^5.2.7",
"@typescript-eslint/eslint-plugin": "^2.6.0",
"@typescript-eslint/parser": "^2.6.0",
"babel-loader": "^8.1.0",
"aegir": "^31.0.0",
"benchmark": "^2.1.4",
"buffer": "^5.7.1",
"chai": "^4.2.0",
"eslint": "^6.6.0",
"karma": "^4.4.1",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^3.1.0",
"karma-cli": "^2.0.0",
"karma-mocha": "^1.3.0",
"karma-spec-reporter": "^0.0.32",
"karma-webpack": "^4.0.2",
"mocha": "^6.2.2",
"sinon": "^8.1.0",
"ts-loader": "^6.2.1",
"typescript": "^3.6.4",
"webpack": "^4.41.5",
"webpack-bundle-analyzer": "^3.6.1",
"webpack-cli": "^3.3.11"
"events": "^3.2.0",
"microtime": "^3.0.0",
"mocha": "^8.2.1",
"sinon": "^9.2.4"
},
"dependencies": {
"aead-js": "^0.1.0",
"buffer": "^5.4.3",
"debug": "^4.1.1",
"futoin-hkdf": "^1.3.2",
"hash.js": "^1.1.7",
"bcrypto": "^5.4.0",
"debug": "^4.3.1",
"it-buffer": "^0.1.1",
"it-length-prefixed": "^3.0.0",
"it-pair": "^1.0.0",
"it-pb-rpc": "^0.1.8",
"it-pb-rpc": "^0.1.9",
"it-pipe": "^1.1.0",
"libp2p-crypto": "^0.17.6",
"peer-id": "^0.13.5",
"protobufjs": "6.8.8",
"tweetnacl": "^1.0.1"
"libp2p-crypto": "fluencelabs/js-libp2p-crypto",
"peer-id": "^0.14.3",
"protobufjs": "^6.10.1",
"uint8arrays": "^2.1.4"
},
"resolutions": {
"bn.js": "4.4.0"
},
"eslintConfig": {
"extends": "ipfs",
"rules": {
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-function-return-type": "warn",
"@typescript-eslint/strict-boolean-expressions": "off"
},
"ignorePatterns": [
"src/proto/payload.js",
"test/fixtures/node-globals.js"
]
}
}

8
src/@types/basic.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
import { Buffer } from 'buffer'
export type bytes = Buffer
export type bytes32 = Buffer
export type bytes16 = Buffer
export type uint32 = number
export type uint64 = number

View File

@ -1,8 +0,0 @@
import {Buffer} from 'buffer';
export type bytes = Buffer;
export type bytes32 = Buffer;
export type bytes16 = Buffer;
export type uint32 = number;
export type uint64 = number;

11
src/@types/handshake-interface.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
import { bytes } from './basic'
import { NoiseSession } from './handshake'
import PeerId from 'peer-id'
export interface IHandshake {
session: NoiseSession
remotePeer: PeerId
remoteEarlyData: Buffer
encrypt: (plaintext: bytes, session: NoiseSession) => bytes
decrypt: (ciphertext: bytes, session: NoiseSession) => {plaintext: bytes, valid: boolean}
}

View File

@ -1,11 +0,0 @@
import {bytes} from "./basic";
import {NoiseSession} from "./handshake";
import PeerId from "peer-id";
export interface IHandshake {
session: NoiseSession;
remotePeer: PeerId;
remoteEarlyData: Buffer;
encrypt(plaintext: bytes, session: NoiseSession): bytes;
decrypt(ciphertext: bytes, session: NoiseSession): {plaintext: bytes; valid: boolean};
}

45
src/@types/handshake.d.ts vendored Normal file
View File

@ -0,0 +1,45 @@
import { bytes, bytes32, uint32, uint64 } from './basic'
import { KeyPair } from './libp2p'
export type Hkdf = [bytes, bytes, bytes]
export interface MessageBuffer {
ne: bytes32
ns: bytes
ciphertext: bytes
}
export interface CipherState {
k: bytes32
n: uint32
}
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
}

View File

@ -1,45 +0,0 @@
import {bytes, bytes32, uint32, uint64} from "./basic";
import {KeyPair} from "./libp2p";
export type Hkdf = [bytes, bytes, bytes];
export type MessageBuffer = {
ne: bytes32;
ns: bytes;
ciphertext: bytes;
}
export type CipherState = {
k: bytes32;
n: uint32;
}
export type SymmetricState = {
cs: CipherState;
ck: bytes32; // chaining key
h: bytes32; // handshake hash
}
export type HandshakeState = {
ss: SymmetricState;
s: KeyPair;
e?: KeyPair;
rs: bytes32;
re: bytes32;
psk: bytes32;
}
export type NoiseSession = {
hs: HandshakeState;
h?: bytes32;
cs1?: CipherState;
cs2?: CipherState;
mc: uint64;
i: boolean;
}
export interface INoisePayload {
identityKey: bytes;
identitySig: bytes;
data: bytes;
}

View File

@ -1,39 +1,38 @@
declare module "it-length-prefixed" {
/* eslint-disable @typescript-eslint/interface-name-prefix */
import BufferList from "bl";
import {Buffer} from "buffer"
declare module 'it-length-prefixed' {
import BufferList from 'bl'
import { Buffer } from 'buffer'
interface LengthDecoderFunction {
(data: Buffer | BufferList): number;
bytes: number;
(data: Buffer | BufferList): number
bytes: number
}
interface LengthEncoderFunction {
(value: Buffer, target: number, offset: number): number|Buffer;
bytes: number;
(value: number, target: Buffer, offset: number): number|Buffer
bytes: number
}
interface Encoder {
(options?: Partial<{lengthEncoder: LengthEncoderFunction}>): AsyncGenerator<BufferList, Buffer>;
single: (chunk: Buffer, options?: Partial<{lengthEncoder: LengthEncoderFunction}>) => BufferList;
MIN_POOL_SIZE: number;
DEFAULT_POOL_SIZE: number;
(options?: Partial<{lengthEncoder: LengthEncoderFunction}>): AsyncGenerator<BufferList, Buffer>
single: (chunk: Buffer, options?: Partial<{lengthEncoder: LengthEncoderFunction}>) => BufferList
MIN_POOL_SIZE: number
DEFAULT_POOL_SIZE: number
}
interface DecoderOptions {
lengthDecoder: LengthDecoderFunction;
maxLengthLength: number;
maxDataLength: number;
lengthDecoder: LengthDecoderFunction
maxLengthLength: number
maxDataLength: number
}
interface Decoder {
(options?: Partial<DecoderOptions>): AsyncGenerator<BufferList, BufferList>;
fromReader: (reader: any, options?: Partial<DecoderOptions>) => BufferList;
MAX_LENGTH_LENGTH: number;
MAX_DATA_LENGTH: number;
(options?: Partial<DecoderOptions>): AsyncGenerator<BufferList, BufferList>
fromReader: (reader: any, options?: Partial<DecoderOptions>) => BufferList
MAX_LENGTH_LENGTH: number
MAX_DATA_LENGTH: number
}
export const encode: Encoder;
export const decode: Decoder;
export const encode: Encoder
export const decode: Decoder
}

View File

@ -1,8 +1,8 @@
declare module 'it-pair' {
export type Duplex = [Stream, Stream];
export type Duplex = [Stream, Stream]
type Stream = {
sink(source: Iterable<any>): void;
source: Record<string, any>;
interface Stream {
sink: (source: Iterable<any>) => void
source: Record<string, any>
}
}

19
src/@types/libp2p.d.ts vendored Normal file
View File

@ -0,0 +1,19 @@
import { bytes, bytes32 } from './basic'
import PeerId from 'peer-id'
export interface KeyPair {
publicKey: bytes32
privateKey: bytes32
}
export interface INoiseConnection {
remoteEarlyData?: () => bytes
secureOutbound: (localPeer: PeerId, insecure: any, remotePeer: PeerId) => Promise<SecureOutbound>
secureInbound: (localPeer: PeerId, insecure: any, remotePeer: PeerId) => Promise<SecureOutbound>
}
export interface SecureOutbound {
conn: any
remoteEarlyData: Buffer
remotePeer: PeerId
}

View File

@ -1,20 +0,0 @@
import { bytes, bytes32 } from "./basic";
import PeerId from "peer-id";
export type KeyPair = {
publicKey: bytes32;
privateKey: bytes32;
}
export interface INoiseConnection {
remoteEarlyData?(): bytes;
secureOutbound(localPeer: PeerId, insecure: any, remotePeer: PeerId): Promise<SecureOutbound>;
secureInbound(localPeer: PeerId, insecure: any, remotePeer: PeerId): Promise<SecureOutbound>;
}
export type SecureOutbound = {
conn: any;
remoteEarlyData: Buffer;
remotePeer: PeerId;
}

View File

@ -1,6 +1,4 @@
export const NOISE_MSG_MAX_LENGTH_BYTES = 65535;
export const NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG = NOISE_MSG_MAX_LENGTH_BYTES - 16;
export const DUMP_SESSION_KEYS = process.env.DUMP_SESSION_KEYS;
export const NOISE_MSG_MAX_LENGTH_BYTES = 65535
export const NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG = NOISE_MSG_MAX_LENGTH_BYTES - 16
export const DUMP_SESSION_KEYS = process.env.DUMP_SESSION_KEYS

View File

@ -1,49 +1,48 @@
import { Buffer } from "buffer";
import {IHandshake} from "./@types/handshake-interface";
import {NOISE_MSG_MAX_LENGTH_BYTES, NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG} from "./constants";
import { Buffer } from 'buffer'
import { IHandshake } from './@types/handshake-interface'
import { NOISE_MSG_MAX_LENGTH_BYTES, NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG } from './constants'
interface IReturnEncryptionWrapper {
(source: Iterable<Uint8Array>): AsyncIterableIterator<Uint8Array>;
(source: Iterable<Uint8Array>): AsyncIterableIterator<Uint8Array>
}
// Returns generator that encrypts payload from the user
export function encryptStream(handshake: IHandshake): IReturnEncryptionWrapper {
export function encryptStream (handshake: IHandshake): IReturnEncryptionWrapper {
return async function * (source) {
for await (const chunk of source) {
const chunkBuffer = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.length);
const chunkBuffer = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.length)
for (let i = 0; i < chunkBuffer.length; i += NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG) {
let end = i + NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG;
let end = i + NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG
if (end > chunkBuffer.length) {
end = chunkBuffer.length;
end = chunkBuffer.length
}
const data = handshake.encrypt(chunkBuffer.slice(i, end), handshake.session);
yield data;
const data = handshake.encrypt(chunkBuffer.slice(i, end), handshake.session)
yield data
}
}
}
}
// Decrypt received payload to the user
export function decryptStream(handshake: IHandshake): IReturnEncryptionWrapper {
export function decryptStream (handshake: IHandshake): IReturnEncryptionWrapper {
return async function * (source) {
for await (const chunk of source) {
const chunkBuffer = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.length);
const chunkBuffer = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.length)
for (let i = 0; i < chunkBuffer.length; i += NOISE_MSG_MAX_LENGTH_BYTES) {
let end = i + NOISE_MSG_MAX_LENGTH_BYTES;
let end = i + NOISE_MSG_MAX_LENGTH_BYTES
if (end > chunkBuffer.length) {
end = chunkBuffer.length;
end = chunkBuffer.length
}
const chunk = chunkBuffer.slice(i, end);
const {plaintext: decrypted, valid} = await handshake.decrypt(chunk, handshake.session);
if(!valid) {
throw new Error("Failed to validate decrypted chunk");
const chunk = chunkBuffer.slice(i, end)
const { plaintext: decrypted, valid } = await handshake.decrypt(chunk, handshake.session)
if (!valid) {
throw new Error('Failed to validate decrypted chunk')
}
yield decrypted;
yield decrypted
}
}
}

View File

@ -1,66 +1,67 @@
import {Buffer} from "buffer";
import {bytes} from "./@types/basic";
import {MessageBuffer} from "./@types/handshake";
import { Buffer } from 'buffer'
import { bytes } from './@types/basic'
import { MessageBuffer } from './@types/handshake'
import BufferList from 'bl'
export const uint16BEEncode = (value, target, offset) => {
target = target || Buffer.allocUnsafe(2);
target.writeUInt16BE(value, offset);
return target;
};
uint16BEEncode.bytes = 2;
export const uint16BEEncode = (value: number, target: Buffer, offset: number): Buffer => {
target = target || Buffer.allocUnsafe(2)
target.writeUInt16BE(value, offset)
return target
}
uint16BEEncode.bytes = 2
export const uint16BEDecode = data => {
if (data.length < 2) throw RangeError('Could not decode int16BE');
return data.readUInt16BE(0);
};
uint16BEDecode.bytes = 2;
export const uint16BEDecode = (data: Buffer | BufferList): number => {
if (data.length < 2) throw RangeError('Could not decode int16BE')
return data.readUInt16BE(0)
}
uint16BEDecode.bytes = 2
// Note: IK and XX encoder usage is opposite (XX uses in stages encode0 where IK uses encode1)
export function encode0(message: MessageBuffer): bytes {
return Buffer.concat([message.ne, message.ciphertext]);
export function encode0 (message: MessageBuffer): bytes {
return Buffer.concat([message.ne, message.ciphertext])
}
export function encode1(message: MessageBuffer): bytes {
return Buffer.concat([message.ne, message.ns, message.ciphertext]);
export function encode1 (message: MessageBuffer): bytes {
return Buffer.concat([message.ne, message.ns, message.ciphertext])
}
export function encode2(message: MessageBuffer): bytes {
return Buffer.concat([message.ns, message.ciphertext]);
export function encode2 (message: MessageBuffer): bytes {
return Buffer.concat([message.ns, message.ciphertext])
}
export function decode0(input: bytes): MessageBuffer {
export function decode0 (input: bytes): MessageBuffer {
if (input.length < 32) {
throw new Error("Cannot decode stage 0 MessageBuffer: length less than 32 bytes.");
throw new Error('Cannot decode stage 0 MessageBuffer: length less than 32 bytes.')
}
return {
ne: input.slice(0, 32),
ciphertext: input.slice(32, input.length),
ns: Buffer.alloc(0),
ns: Buffer.alloc(0)
}
}
export function decode1(input: bytes): MessageBuffer {
export function decode1 (input: bytes): MessageBuffer {
if (input.length < 80) {
throw new Error("Cannot decode stage 1 MessageBuffer: length less than 80 bytes.");
throw new Error('Cannot decode stage 1 MessageBuffer: length less than 80 bytes.')
}
return {
ne: input.slice(0, 32),
ns: input.slice(32, 80),
ciphertext: input.slice(80, input.length),
ciphertext: input.slice(80, input.length)
}
}
export function decode2(input: bytes): MessageBuffer {
export function decode2 (input: bytes): MessageBuffer {
if (input.length < 48) {
throw new Error("Cannot decode stage 2 MessageBuffer: length less than 48 bytes.");
throw new Error('Cannot decode stage 2 MessageBuffer: length less than 48 bytes.')
}
return {
ne: Buffer.alloc(0),
ns: input.slice(0, 48),
ciphertext: input.slice(48, input.length),
ciphertext: input.slice(48, input.length)
}
}

View File

@ -1,10 +1,12 @@
import BufferList from 'bl'
export class FailedIKError extends Error {
public initialMsg;
public initialMsg: string|BufferList|Buffer
constructor(initialMsg, message?: string) {
super(message);
constructor (initialMsg: string|BufferList|Buffer, message?: string) {
super(message)
this.initialMsg = initialMsg;
this.name = "FailedIKhandshake";
this.initialMsg = initialMsg
this.name = 'FailedIKhandshake'
}
};
}

View File

@ -1,36 +1,36 @@
import {WrappedConnection} from "./noise";
import {IK} from "./handshakes/ik";
import {NoiseSession} from "./@types/handshake";
import {bytes, bytes32} from "./@types/basic";
import {KeyPair} from "./@types/libp2p";
import {IHandshake} from "./@types/handshake-interface";
import {Buffer} from "buffer";
import {decode0, decode1, encode0, encode1} from "./encoder";
import {decodePayload, getPeerIdFromPayload, verifySignedPayload} from "./utils";
import {FailedIKError} from "./errors";
import { WrappedConnection } from './noise'
import { IK } from './handshakes/ik'
import { NoiseSession } from './@types/handshake'
import { bytes, bytes32 } from './@types/basic'
import { KeyPair } from './@types/libp2p'
import { IHandshake } from './@types/handshake-interface'
import { Buffer } from 'buffer'
import { decode0, decode1, encode0, encode1 } from './encoder'
import { decodePayload, getPeerIdFromPayload, verifySignedPayload } from './utils'
import { FailedIKError } from './errors'
import {
logger,
logger,
logLocalStaticKeys,
logRemoteStaticKey,
logLocalEphemeralKeys,
logRemoteEphemeralKey,
logRemoteStaticKey,
logLocalEphemeralKeys,
logRemoteEphemeralKey,
logCipherState
} from "./logger";
import PeerId from "peer-id";
} from './logger'
import PeerId from 'peer-id'
export class IKHandshake implements IHandshake {
public isInitiator: boolean;
public session: NoiseSession;
public remotePeer!: PeerId;
public remoteEarlyData: Buffer;
public isInitiator: boolean
public session: NoiseSession
public remotePeer!: PeerId
public remoteEarlyData: Buffer
private payload: bytes;
private prologue: bytes32;
private staticKeypair: KeyPair;
private connection: WrappedConnection;
private ik: IK;
private readonly payload: bytes
private readonly prologue: bytes32
private readonly staticKeypair: KeyPair
private readonly connection: WrappedConnection
private readonly ik: IK
constructor(
constructor (
isInitiator: boolean,
payload: bytes,
prologue: bytes32,
@ -38,118 +38,120 @@ export class IKHandshake implements IHandshake {
connection: WrappedConnection,
remoteStaticKey: bytes,
remotePeer?: PeerId,
handshake?: IK,
handshake?: IK
) {
this.isInitiator = isInitiator;
this.payload = Buffer.from(payload);
this.prologue = prologue;
this.staticKeypair = staticKeypair;
this.connection = connection;
if(remotePeer) {
this.remotePeer = remotePeer;
this.isInitiator = isInitiator
this.payload = Buffer.from(payload)
this.prologue = prologue
this.staticKeypair = staticKeypair
this.connection = connection
if (remotePeer) {
this.remotePeer = remotePeer
}
this.ik = handshake || new IK();
this.session = this.ik.initSession(this.isInitiator, this.prologue, this.staticKeypair, remoteStaticKey);
this.ik = handshake ?? new IK()
this.session = this.ik.initSession(this.isInitiator, this.prologue, this.staticKeypair, remoteStaticKey)
this.remoteEarlyData = Buffer.alloc(0)
}
public async stage0(): Promise<void> {
public async stage0 (): Promise<void> {
logLocalStaticKeys(this.session.hs.s)
logRemoteStaticKey(this.session.hs.rs)
if (this.isInitiator) {
logger("IK Stage 0 - Initiator sending message...");
const messageBuffer = this.ik.sendMessage(this.session, this.payload);
this.connection.writeLP(encode1(messageBuffer));
logger("IK Stage 0 - Initiator sent message.");
logger('IK Stage 0 - Initiator sending message...')
const messageBuffer = this.ik.sendMessage(this.session, this.payload)
this.connection.writeLP(encode1(messageBuffer))
logger('IK Stage 0 - Initiator sent message.')
logLocalEphemeralKeys(this.session.hs.e)
} else {
logger("IK Stage 0 - Responder receiving message...");
const receivedMsg = await this.connection.readLP();
logger('IK Stage 0 - Responder receiving message...')
const receivedMsg = await this.connection.readLP()
try {
const receivedMessageBuffer = decode1(receivedMsg.slice());
const {plaintext, valid} = this.ik.recvMessage(this.session, receivedMessageBuffer);
if(!valid) {
throw new Error("ik handshake stage 0 decryption validation fail");
const receivedMessageBuffer = decode1(receivedMsg.slice())
const { plaintext, valid } = this.ik.recvMessage(this.session, receivedMessageBuffer)
if (!valid) {
throw new Error('ik handshake stage 0 decryption validation fail')
}
logger("IK Stage 0 - Responder got message, going to verify payload.");
const decodedPayload = await decodePayload(plaintext);
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload);
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer);
this.setRemoteEarlyData(decodedPayload.data);
logger("IK Stage 0 - Responder successfully verified payload!");
logger('IK Stage 0 - Responder got message, going to verify payload.')
const decodedPayload = await decodePayload(plaintext)
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload)
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer)
this.setRemoteEarlyData(decodedPayload.data)
logger('IK Stage 0 - Responder successfully verified payload!')
logRemoteEphemeralKey(this.session.hs.re)
} catch (e) {
logger("Responder breaking up with IK handshake in stage 0.");
const err = e as Error
logger('Responder breaking up with IK handshake in stage 0.')
throw new FailedIKError(receivedMsg, `Error occurred while verifying initiator's signed payload: ${e.message}`);
throw new FailedIKError(receivedMsg, `Error occurred while verifying initiator's signed payload: ${err.message}`)
}
}
}
public async stage1(): Promise<void> {
public async stage1 (): Promise<void> {
if (this.isInitiator) {
logger("IK Stage 1 - Initiator receiving message...");
const receivedMsg = (await this.connection.readLP()).slice();
const receivedMessageBuffer = decode0(Buffer.from(receivedMsg));
const {plaintext, valid} = this.ik.recvMessage(this.session, receivedMessageBuffer);
logger("IK Stage 1 - Initiator got message, going to verify payload.");
logger('IK Stage 1 - Initiator receiving message...')
const receivedMsg = (await this.connection.readLP()).slice()
const receivedMessageBuffer = decode0(Buffer.from(receivedMsg))
const { plaintext, valid } = this.ik.recvMessage(this.session, receivedMessageBuffer)
logger('IK Stage 1 - Initiator got message, going to verify payload.')
try {
if(!valid) {
throw new Error("ik stage 1 decryption validation fail");
if (!valid) {
throw new Error('ik stage 1 decryption validation fail')
}
const decodedPayload = await decodePayload(plaintext);
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload);
await verifySignedPayload(receivedMessageBuffer.ns.slice(0, 32), decodedPayload, this.remotePeer);
this.setRemoteEarlyData(decodedPayload.data);
logger("IK Stage 1 - Initiator successfully verified payload!");
const decodedPayload = await decodePayload(plaintext)
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload)
await verifySignedPayload(receivedMessageBuffer.ns.slice(0, 32), decodedPayload, this.remotePeer)
this.setRemoteEarlyData(decodedPayload.data)
logger('IK Stage 1 - Initiator successfully verified payload!')
logRemoteEphemeralKey(this.session.hs.re)
} catch (e) {
logger("Initiator breaking up with IK handshake in stage 1.");
throw new FailedIKError(receivedMsg, `Error occurred while verifying responder's signed payload: ${e.message}`);
const err = e as Error
logger('Initiator breaking up with IK handshake in stage 1.')
throw new FailedIKError(receivedMsg, `Error occurred while verifying responder's signed payload: ${err.message}`)
}
} else {
logger("IK Stage 1 - Responder sending message...");
const messageBuffer = this.ik.sendMessage(this.session, this.payload);
this.connection.writeLP(encode0(messageBuffer));
logger("IK Stage 1 - Responder sent message...");
logger('IK Stage 1 - Responder sending message...')
const messageBuffer = this.ik.sendMessage(this.session, this.payload)
this.connection.writeLP(encode0(messageBuffer))
logger('IK Stage 1 - Responder sent message...')
logLocalEphemeralKeys(this.session.hs.e)
}
logCipherState(this.session)
}
public decrypt(ciphertext: bytes, session: NoiseSession): {plaintext: bytes; valid: boolean} {
const cs = this.getCS(session, false);
return this.ik.decryptWithAd(cs, Buffer.alloc(0), ciphertext);
public decrypt (ciphertext: bytes, session: NoiseSession): {plaintext: bytes, valid: boolean} {
const cs = this.getCS(session, false)
return this.ik.decryptWithAd(cs, Buffer.alloc(0), ciphertext)
}
public encrypt(plaintext: Buffer, session: NoiseSession): Buffer {
const cs = this.getCS(session);
return this.ik.encryptWithAd(cs, Buffer.alloc(0), plaintext);
public encrypt (plaintext: Buffer, session: NoiseSession): Buffer {
const cs = this.getCS(session)
return this.ik.encryptWithAd(cs, Buffer.alloc(0), plaintext)
}
public getLocalEphemeralKeys(): KeyPair {
public getLocalEphemeralKeys (): KeyPair {
if (!this.session.hs.e) {
throw new Error("Ephemeral keys do not exist.");
throw new Error('Ephemeral keys do not exist.')
}
return this.session.hs.e;
return this.session.hs.e
}
private getCS(session: NoiseSession, encryption = true) {
private getCS (session: NoiseSession, encryption = true) {
if (!session.cs1 || !session.cs2) {
throw new Error("Handshake not completed properly, cipher state does not exist.");
throw new Error('Handshake not completed properly, cipher state does not exist.')
}
if (this.isInitiator) {
return encryption ? session.cs1 : session.cs2;
return encryption ? session.cs1 : session.cs2
} else {
return encryption ? session.cs2 : session.cs1;
return encryption ? session.cs2 : session.cs1
}
}
private setRemoteEarlyData(data: Uint8Array|null|undefined): void {
if(data){
this.remoteEarlyData = Buffer.from(data.buffer, data.byteOffset, data.length);
private setRemoteEarlyData (data: Uint8Array|null|undefined): void {
if (data) {
this.remoteEarlyData = Buffer.from(data.buffer, data.byteOffset, data.length)
}
}
}

View File

@ -1,19 +1,19 @@
import {Buffer} from "buffer";
import {XXHandshake} from "./handshake-xx";
import {XX} from "./handshakes/xx";
import {KeyPair} from "./@types/libp2p";
import {bytes, bytes32} from "./@types/basic";
import {decodePayload, getPeerIdFromPayload, verifySignedPayload} from "./utils";
import {logger, logLocalEphemeralKeys, logRemoteEphemeralKey, logRemoteStaticKey} from "./logger";
import {WrappedConnection} from "./noise";
import {decode0, decode1} from "./encoder";
import PeerId from "peer-id";
import { Buffer } from 'buffer'
import { XXHandshake } from './handshake-xx'
import { XX } from './handshakes/xx'
import { KeyPair } from './@types/libp2p'
import { bytes, bytes32 } from './@types/basic'
import { decodePayload, getPeerIdFromPayload, verifySignedPayload } from './utils'
import { logger, logLocalEphemeralKeys, logRemoteEphemeralKey, logRemoteStaticKey } from './logger'
import { WrappedConnection } from './noise'
import { decode0, decode1 } from './encoder'
import PeerId from 'peer-id'
export class XXFallbackHandshake extends XXHandshake {
private ephemeralKeys?: KeyPair;
private initialMsg: bytes;
private readonly ephemeralKeys?: KeyPair
private readonly initialMsg: bytes
constructor(
constructor (
isInitiator: boolean,
payload: bytes,
prologue: bytes32,
@ -22,63 +22,65 @@ export class XXFallbackHandshake extends XXHandshake {
initialMsg: bytes,
remotePeer?: PeerId,
ephemeralKeys?: KeyPair,
handshake?: XX,
handshake?: XX
) {
super(isInitiator, payload, prologue, staticKeypair, connection, remotePeer, handshake);
super(isInitiator, payload, prologue, staticKeypair, connection, remotePeer, handshake)
if (ephemeralKeys) {
this.ephemeralKeys = ephemeralKeys;
this.ephemeralKeys = ephemeralKeys
}
this.initialMsg = initialMsg;
this.initialMsg = initialMsg
}
// stage 0
public async propose(): Promise<void> {
// eslint-disable-next-line require-await
public async propose (): Promise<void> {
if (this.isInitiator) {
this.xx.sendMessage(this.session, Buffer.alloc(0), this.ephemeralKeys);
logger("XX Fallback Stage 0 - Initialized state as the first message was sent by initiator.");
this.xx.sendMessage(this.session, Buffer.alloc(0), this.ephemeralKeys)
logger('XX Fallback Stage 0 - Initialized state as the first message was sent by initiator.')
logLocalEphemeralKeys(this.session.hs.e)
} else {
logger("XX Fallback Stage 0 - Responder decoding initial msg from IK.");
const receivedMessageBuffer = decode0(this.initialMsg);
const {valid} = this.xx.recvMessage(this.session, {
logger('XX Fallback Stage 0 - Responder decoding initial msg from IK.')
const receivedMessageBuffer = decode0(this.initialMsg)
const { valid } = this.xx.recvMessage(this.session, {
ne: receivedMessageBuffer.ne,
ns: Buffer.alloc(0),
ciphertext: Buffer.alloc(0),
});
if(!valid) {
throw new Error("xx fallback stage 0 decryption validation fail");
ciphertext: Buffer.alloc(0)
})
if (!valid) {
throw new Error('xx fallback stage 0 decryption validation fail')
}
logger("XX Fallback Stage 0 - Responder used received message from IK.");
logger('XX Fallback Stage 0 - Responder used received message from IK.')
logRemoteEphemeralKey(this.session.hs.re)
}
}
// stage 1
public async exchange(): Promise<void> {
public async exchange (): Promise<void> {
if (this.isInitiator) {
const receivedMessageBuffer = decode1(this.initialMsg);
const {plaintext, valid} = this.xx.recvMessage(this.session, receivedMessageBuffer);
if(!valid) {
throw new Error("xx fallback stage 1 decryption validation fail");
const receivedMessageBuffer = decode1(this.initialMsg)
const { plaintext, valid } = this.xx.recvMessage(this.session, receivedMessageBuffer)
if (!valid) {
throw new Error('xx fallback stage 1 decryption validation fail')
}
logger('XX Fallback Stage 1 - Initiator used received message from IK.');
logger('XX Fallback Stage 1 - Initiator used received message from IK.')
logRemoteEphemeralKey(this.session.hs.re)
logRemoteStaticKey(this.session.hs.rs)
logger("Initiator going to check remote's signature...");
logger("Initiator going to check remote's signature...")
try {
const decodedPayload = await decodePayload(plaintext);
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload);
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer);
const decodedPayload = await decodePayload(plaintext)
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload)
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer)
this.setRemoteEarlyData(decodedPayload.data)
} catch (e) {
throw new Error(`Error occurred while verifying signed payload from responder: ${e.message}`);
const err = e as Error
throw new Error(`Error occurred while verifying signed payload from responder: ${err.message}`)
}
logger("All good with the signature!");
logger('All good with the signature!')
} else {
logger("XX Fallback Stage 1 - Responder start");
await super.exchange();
logger("XX Fallback Stage 1 - Responder end");
logger('XX Fallback Stage 1 - Responder start')
await super.exchange()
logger('XX Fallback Stage 1 - Responder end')
}
}
}

View File

@ -1,173 +1,175 @@
import { Buffer } from "buffer";
import { Buffer } from 'buffer'
import { XX } from "./handshakes/xx";
import { KeyPair } from "./@types/libp2p";
import { bytes, bytes32 } from "./@types/basic";
import { NoiseSession } from "./@types/handshake";
import {IHandshake} from "./@types/handshake-interface";
import { XX } from './handshakes/xx'
import { KeyPair } from './@types/libp2p'
import { bytes, bytes32 } from './@types/basic'
import { NoiseSession } from './@types/handshake'
import { IHandshake } from './@types/handshake-interface'
import {
decodePayload,
getPeerIdFromPayload,
verifySignedPayload,
} from "./utils";
verifySignedPayload
} from './utils'
import {
logger,
logLocalStaticKeys,
logLocalEphemeralKeys,
logRemoteEphemeralKey,
logRemoteStaticKey,
logCipherState,
} from "./logger";
import {decode0, decode1, decode2, encode0, encode1, encode2} from "./encoder";
import { WrappedConnection } from "./noise";
import PeerId from "peer-id";
logger,
logLocalStaticKeys,
logLocalEphemeralKeys,
logRemoteEphemeralKey,
logRemoteStaticKey,
logCipherState
} from './logger'
import { decode0, decode1, decode2, encode0, encode1, encode2 } from './encoder'
import { WrappedConnection } from './noise'
import PeerId from 'peer-id'
export class XXHandshake implements IHandshake {
public isInitiator: boolean;
public session: NoiseSession;
public remotePeer!: PeerId;
public remoteEarlyData: Buffer;
public isInitiator: boolean
public session: NoiseSession
public remotePeer!: PeerId
public remoteEarlyData: Buffer
protected payload: bytes;
protected connection: WrappedConnection;
protected xx: XX;
protected staticKeypair: KeyPair;
protected payload: bytes
protected connection: WrappedConnection
protected xx: XX
protected staticKeypair: KeyPair
private prologue: bytes32;
private readonly prologue: bytes32
constructor(
constructor (
isInitiator: boolean,
payload: bytes,
prologue: bytes32,
staticKeypair: KeyPair,
connection: WrappedConnection,
remotePeer?: PeerId,
handshake?: XX,
handshake?: XX
) {
this.isInitiator = isInitiator;
this.payload = payload;
this.prologue = prologue;
this.staticKeypair = staticKeypair;
this.connection = connection;
if(remotePeer) {
this.remotePeer = remotePeer;
this.isInitiator = isInitiator
this.payload = payload
this.prologue = prologue
this.staticKeypair = staticKeypair
this.connection = connection
if (remotePeer) {
this.remotePeer = remotePeer
}
this.xx = handshake || new XX();
this.session = this.xx.initSession(this.isInitiator, this.prologue, this.staticKeypair);
this.xx = handshake ?? new XX()
this.session = this.xx.initSession(this.isInitiator, this.prologue, this.staticKeypair)
this.remoteEarlyData = Buffer.alloc(0)
}
// stage 0
public async propose(): Promise<void> {
public async propose (): Promise<void> {
logLocalStaticKeys(this.session.hs.s)
if (this.isInitiator) {
logger("Stage 0 - Initiator starting to send first message.");
const messageBuffer = this.xx.sendMessage(this.session, Buffer.alloc(0));
this.connection.writeLP(encode0(messageBuffer));
logger("Stage 0 - Initiator finished sending first message.");
logger('Stage 0 - Initiator starting to send first message.')
const messageBuffer = this.xx.sendMessage(this.session, Buffer.alloc(0))
this.connection.writeLP(encode0(messageBuffer))
logger('Stage 0 - Initiator finished sending first message.')
logLocalEphemeralKeys(this.session.hs.e)
} else {
logger("Stage 0 - Responder waiting to receive first message...");
const receivedMessageBuffer = decode0((await this.connection.readLP()).slice());
const {valid} = this.xx.recvMessage(this.session, receivedMessageBuffer);
if(!valid) {
throw new Error("xx handshake stage 0 validation fail");
logger('Stage 0 - Responder waiting to receive first message...')
const receivedMessageBuffer = decode0((await this.connection.readLP()).slice())
const { valid } = this.xx.recvMessage(this.session, receivedMessageBuffer)
if (!valid) {
throw new Error('xx handshake stage 0 validation fail')
}
logger("Stage 0 - Responder received first message.");
logger('Stage 0 - Responder received first message.')
logRemoteEphemeralKey(this.session.hs.re)
}
}
// stage 1
public async exchange(): Promise<void> {
public async exchange (): Promise<void> {
if (this.isInitiator) {
logger('Stage 1 - Initiator waiting to receive first message from responder...');
const receivedMessageBuffer = decode1((await this.connection.readLP()).slice());
const {plaintext, valid} = this.xx.recvMessage(this.session, receivedMessageBuffer);
if(!valid) {
throw new Error("xx handshake stage 1 validation fail");
logger('Stage 1 - Initiator waiting to receive first message from responder...')
const receivedMessageBuffer = decode1((await this.connection.readLP()).slice())
const { plaintext, valid } = this.xx.recvMessage(this.session, receivedMessageBuffer)
if (!valid) {
throw new Error('xx handshake stage 1 validation fail')
}
logger('Stage 1 - Initiator received the message.');
logger('Stage 1 - Initiator received the message.')
logRemoteEphemeralKey(this.session.hs.re)
logRemoteStaticKey(this.session.hs.rs)
logger("Initiator going to check remote's signature...");
logger("Initiator going to check remote's signature...")
try {
const decodedPayload = await decodePayload(plaintext);
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload);
this.remotePeer = await verifySignedPayload(receivedMessageBuffer.ns, decodedPayload, this.remotePeer);
const decodedPayload = await decodePayload(plaintext)
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload)
this.remotePeer = await verifySignedPayload(receivedMessageBuffer.ns, decodedPayload, this.remotePeer)
this.setRemoteEarlyData(decodedPayload.data)
} catch (e) {
throw new Error(`Error occurred while verifying signed payload: ${e.message}`);
const err = e as Error
throw new Error(`Error occurred while verifying signed payload: ${err.message}`)
}
logger("All good with the signature!");
logger('All good with the signature!')
} else {
logger('Stage 1 - Responder sending out first message with signed payload and static key.');
const messageBuffer = this.xx.sendMessage(this.session, this.payload);
this.connection.writeLP(encode1(messageBuffer));
logger('Stage 1 - Responder sending out first message with signed payload and static key.')
const messageBuffer = this.xx.sendMessage(this.session, this.payload)
this.connection.writeLP(encode1(messageBuffer))
logger('Stage 1 - Responder sent the second handshake message with signed payload.')
logLocalEphemeralKeys(this.session.hs.e)
}
}
// stage 2
public async finish(): Promise<void> {
public async finish (): Promise<void> {
if (this.isInitiator) {
logger('Stage 2 - Initiator sending third handshake message.');
const messageBuffer = this.xx.sendMessage(this.session, this.payload);
this.connection.writeLP(encode2(messageBuffer));
logger('Stage 2 - Initiator sent message with signed payload.');
logger('Stage 2 - Initiator sending third handshake message.')
const messageBuffer = this.xx.sendMessage(this.session, this.payload)
this.connection.writeLP(encode2(messageBuffer))
logger('Stage 2 - Initiator sent message with signed payload.')
} else {
logger('Stage 2 - Responder waiting for third handshake message...');
const receivedMessageBuffer = decode2((await this.connection.readLP()).slice());
const {plaintext, valid} = this.xx.recvMessage(this.session, receivedMessageBuffer);
if(!valid) {
throw new Error("xx handshake stage 2 validation fail");
logger('Stage 2 - Responder waiting for third handshake message...')
const receivedMessageBuffer = decode2((await this.connection.readLP()).slice())
const { plaintext, valid } = this.xx.recvMessage(this.session, receivedMessageBuffer)
if (!valid) {
throw new Error('xx handshake stage 2 validation fail')
}
logger('Stage 2 - Responder received the message, finished handshake.');
logger('Stage 2 - Responder received the message, finished handshake.')
try {
const decodedPayload = await decodePayload(plaintext);
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload);
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer);
const decodedPayload = await decodePayload(plaintext)
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload)
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer)
this.setRemoteEarlyData(decodedPayload.data)
} catch (e) {
throw new Error(`Error occurred while verifying signed payload: ${e.message}`);
const err = e as Error
throw new Error(`Error occurred while verifying signed payload: ${err.message}`)
}
}
logCipherState(this.session)
}
public encrypt(plaintext: bytes, session: NoiseSession): bytes {
const cs = this.getCS(session);
public encrypt (plaintext: bytes, session: NoiseSession): bytes {
const cs = this.getCS(session)
return this.xx.encryptWithAd(cs, Buffer.alloc(0), plaintext);
return this.xx.encryptWithAd(cs, Buffer.alloc(0), plaintext)
}
public decrypt(ciphertext: bytes, session: NoiseSession): {plaintext: bytes; valid: boolean} {
const cs = this.getCS(session, false);
return this.xx.decryptWithAd(cs, Buffer.alloc(0), ciphertext);
public decrypt (ciphertext: bytes, session: NoiseSession): {plaintext: bytes, valid: boolean} {
const cs = this.getCS(session, false)
return this.xx.decryptWithAd(cs, Buffer.alloc(0), ciphertext)
}
public getRemoteStaticKey(): bytes {
return this.session.hs.rs;
public getRemoteStaticKey (): bytes {
return this.session.hs.rs
}
private getCS(session: NoiseSession, encryption = true) {
private getCS (session: NoiseSession, encryption = true) {
if (!session.cs1 || !session.cs2) {
throw new Error("Handshake not completed properly, cipher state does not exist.");
throw new Error('Handshake not completed properly, cipher state does not exist.')
}
if (this.isInitiator) {
return encryption ? session.cs1 : session.cs2;
return encryption ? session.cs1 : session.cs2
} else {
return encryption ? session.cs2 : session.cs1;
return encryption ? session.cs2 : session.cs1
}
}
protected setRemoteEarlyData(data: Uint8Array|null|undefined): void {
if(data){
this.remoteEarlyData = Buffer.from(data.buffer, data.byteOffset, data.length);
protected setRemoteEarlyData (data: Uint8Array|null|undefined): void {
if (data) {
this.remoteEarlyData = Buffer.from(data.buffer, data.byteOffset, data.length)
}
}
}

View File

@ -1,179 +1,179 @@
import {Buffer} from "buffer";
import hash from 'hash.js';
import {box} from 'tweetnacl';
import {AEAD} from 'aead-js';
import { Buffer } from 'buffer'
import AEAD from 'bcrypto/lib/js/aead'
import x25519 from 'bcrypto/lib/js/x25519'
import SHA256 from 'bcrypto/lib/js/sha256'
import {bytes, bytes32, uint32} from "../@types/basic";
import {CipherState, MessageBuffer, SymmetricState} from "../@types/handshake";
import {getHkdf} from "../utils";
import {logger} from "../logger";
import { bytes, bytes32, uint32 } from '../@types/basic'
import { CipherState, MessageBuffer, SymmetricState } from '../@types/handshake'
import { getHkdf } from '../utils'
import { logger } from '../logger'
export const MIN_NONCE = 0;
export const MIN_NONCE = 0
export abstract class AbstractHandshake {
public encryptWithAd(cs: CipherState, ad: bytes, plaintext: bytes): bytes {
const e = this.encrypt(cs.k, cs.n, ad, plaintext);
this.setNonce(cs, this.incrementNonce(cs.n));
public encryptWithAd (cs: CipherState, ad: bytes, plaintext: bytes): bytes {
const e = this.encrypt(cs.k, cs.n, ad, plaintext)
this.setNonce(cs, this.incrementNonce(cs.n))
return e;
return e
}
public decryptWithAd(cs: CipherState, ad: bytes, ciphertext: bytes): {plaintext: bytes; valid: boolean} {
const {plaintext, valid} = this.decrypt(cs.k, cs.n, ad, ciphertext);
this.setNonce(cs, this.incrementNonce(cs.n));
public decryptWithAd (cs: CipherState, ad: bytes, ciphertext: bytes): {plaintext: bytes, valid: boolean} {
const { plaintext, valid } = this.decrypt(cs.k, cs.n, ad, ciphertext)
this.setNonce(cs, this.incrementNonce(cs.n))
return {plaintext, valid};
return { plaintext, valid }
}
// Cipher state related
protected hasKey(cs: CipherState): boolean {
return !this.isEmptyKey(cs.k);
protected hasKey (cs: CipherState): boolean {
return !this.isEmptyKey(cs.k)
}
protected setNonce(cs: CipherState, nonce: uint32): void {
cs.n = nonce;
protected setNonce (cs: CipherState, nonce: uint32): void {
cs.n = nonce
}
protected createEmptyKey(): bytes32 {
return Buffer.alloc(32);
protected createEmptyKey (): bytes32 {
return Buffer.alloc(32)
}
protected isEmptyKey(k: bytes32): boolean {
const emptyKey = this.createEmptyKey();
return emptyKey.equals(k);
protected isEmptyKey (k: bytes32): boolean {
const emptyKey = this.createEmptyKey()
return emptyKey.equals(k)
}
protected incrementNonce(n: uint32): uint32 {
return n + 1;
protected incrementNonce (n: uint32): uint32 {
return n + 1
}
protected nonceToBytes(n: uint32): bytes {
const nonce = Buffer.alloc(12);
nonce.writeUInt32LE(n, 4);
protected nonceToBytes (n: uint32): bytes {
const nonce = Buffer.alloc(12)
nonce.writeUInt32LE(n, 4)
return nonce;
return nonce
}
protected encrypt(k: bytes32, n: uint32, ad: bytes, plaintext: bytes): bytes {
const nonce = this.nonceToBytes(n);
const ctx = new AEAD();
plaintext = Buffer.from(plaintext);
ctx.init(k, nonce);
ctx.aad(ad);
ctx.encrypt(plaintext);
protected encrypt (k: bytes32, n: uint32, ad: bytes, plaintext: bytes): bytes {
const nonce = this.nonceToBytes(n)
const ctx = new AEAD()
plaintext = Buffer.from(plaintext)
ctx.init(k, nonce)
ctx.aad(ad)
ctx.encrypt(plaintext)
// Encryption is done on the sent reference
return Buffer.concat([plaintext, ctx.final()]);
return Buffer.concat([plaintext, ctx.final()])
}
protected encryptAndHash(ss: SymmetricState, plaintext: bytes): bytes {
let ciphertext;
protected encryptAndHash (ss: SymmetricState, plaintext: bytes): bytes {
let ciphertext
if (this.hasKey(ss.cs)) {
ciphertext = this.encryptWithAd(ss.cs, ss.h, plaintext);
ciphertext = this.encryptWithAd(ss.cs, ss.h, plaintext)
} else {
ciphertext = plaintext;
ciphertext = plaintext
}
this.mixHash(ss, ciphertext);
return ciphertext;
this.mixHash(ss, ciphertext)
return ciphertext
}
protected decrypt(k: bytes32, n: uint32, ad: bytes, ciphertext: bytes): {plaintext: bytes; valid: boolean} {
const nonce = this.nonceToBytes(n);
const ctx = new AEAD();
ciphertext = Buffer.from(ciphertext);
const tag = ciphertext.slice(ciphertext.length - 16);
ciphertext = ciphertext.slice(0, ciphertext.length - 16);
ctx.init(k, nonce);
ctx.aad(ad);
ctx.decrypt(ciphertext);
protected decrypt (k: bytes32, n: uint32, ad: bytes, ciphertext: bytes): {plaintext: bytes, valid: boolean} {
const nonce = this.nonceToBytes(n)
const ctx = new AEAD()
ciphertext = Buffer.from(ciphertext)
const tag = ciphertext.slice(ciphertext.length - 16)
ciphertext = ciphertext.slice(0, ciphertext.length - 16)
ctx.init(k, nonce)
ctx.aad(ad)
ctx.decrypt(ciphertext)
// Decryption is done on the sent reference
return {plaintext: ciphertext, valid: ctx.verify(tag)};
return { plaintext: ciphertext, valid: ctx.verify(tag) }
}
protected decryptAndHash(ss: SymmetricState, ciphertext: bytes): {plaintext: bytes; valid: boolean} {
let plaintext: bytes, valid = true;
protected decryptAndHash (ss: SymmetricState, ciphertext: bytes): {plaintext: bytes, valid: boolean} {
let plaintext: bytes; let valid = true
if (this.hasKey(ss.cs)) {
({plaintext, valid} = this.decryptWithAd(ss.cs, ss.h, ciphertext));
({ plaintext, valid } = this.decryptWithAd(ss.cs, ss.h, ciphertext))
} else {
plaintext = ciphertext;
plaintext = ciphertext
}
this.mixHash(ss, ciphertext);
return {plaintext, valid};
this.mixHash(ss, ciphertext)
return { plaintext, valid }
}
protected dh(privateKey: bytes32, publicKey: bytes32): bytes32 {
protected dh (privateKey: bytes32, publicKey: bytes32): bytes32 {
try {
const sharedKey = box.before(publicKey, privateKey)
return Buffer.from(sharedKey.buffer, sharedKey.byteOffset, sharedKey.length);
const derived = x25519.derive(publicKey, privateKey)
const result = Buffer.alloc(32)
derived.copy(result)
return result
} catch (e) {
logger(e.message);
return Buffer.alloc(32);
logger(e.message)
return Buffer.alloc(32)
}
}
protected mixHash(ss: SymmetricState, data: bytes): void {
ss.h = this.getHash(ss.h, data);
protected mixHash (ss: SymmetricState, data: bytes): void {
ss.h = this.getHash(ss.h, data)
}
protected getHash(a: bytes, b: bytes): bytes32 {
const hashValue = hash.sha256().update(Buffer.from([...a, ...b])).digest();
return Buffer.from(hashValue);
protected getHash (a: bytes, b: bytes): bytes32 {
return SHA256.digest(Buffer.from([...a, ...b]))
}
protected mixKey(ss: SymmetricState, ikm: bytes32): void {
const [ ck, tempK ] = getHkdf(ss.ck, ikm);
ss.cs = this.initializeKey(tempK) as CipherState;
ss.ck = ck;
protected mixKey (ss: SymmetricState, ikm: bytes32): void {
const [ck, tempK] = getHkdf(ss.ck, ikm)
ss.cs = this.initializeKey(tempK)
ss.ck = ck
}
protected initializeKey(k: bytes32): CipherState {
const n = MIN_NONCE;
return { k, n };
protected initializeKey (k: bytes32): CipherState {
const n = MIN_NONCE
return { k, n }
}
// Symmetric state related
protected initializeSymmetric(protocolName: string): SymmetricState {
const protocolNameBytes: bytes = Buffer.from(protocolName, 'utf-8');
const h = this.hashProtocolName(protocolNameBytes);
protected initializeSymmetric (protocolName: string): SymmetricState {
const protocolNameBytes: bytes = Buffer.from(protocolName, 'utf-8')
const h = this.hashProtocolName(protocolNameBytes)
const ck = h;
const key = this.createEmptyKey();
const cs: CipherState = this.initializeKey(key);
const ck = h
const key = this.createEmptyKey()
const cs: CipherState = this.initializeKey(key)
return { cs, ck, h };
return { cs, ck, h }
}
protected hashProtocolName(protocolName: bytes): bytes32 {
protected hashProtocolName (protocolName: bytes): bytes32 {
if (protocolName.length <= 32) {
const h = Buffer.alloc(32);
protocolName.copy(h);
return h;
const h = Buffer.alloc(32)
protocolName.copy(h)
return h
} else {
return this.getHash(protocolName, Buffer.alloc(0));
return this.getHash(protocolName, Buffer.alloc(0))
}
}
protected split(ss: SymmetricState) {
const [ tempk1, tempk2 ] = getHkdf(ss.ck, Buffer.alloc(0));
const cs1 = this.initializeKey(tempk1);
const cs2 = this.initializeKey(tempk2);
protected split (ss: SymmetricState): {cs1: CipherState, cs2: CipherState} {
const [tempk1, tempk2] = getHkdf(ss.ck, Buffer.alloc(0))
const cs1 = this.initializeKey(tempk1)
const cs2 = this.initializeKey(tempk2)
return { cs1, cs2 };
return { cs1, cs2 }
}
protected writeMessageRegular(cs: CipherState, payload: bytes): MessageBuffer {
const ciphertext = this.encryptWithAd(cs, Buffer.alloc(0), payload);
const ne = this.createEmptyKey();
const ns = Buffer.alloc(0);
protected writeMessageRegular (cs: CipherState, payload: bytes): MessageBuffer {
const ciphertext = this.encryptWithAd(cs, Buffer.alloc(0), payload)
const ne = this.createEmptyKey()
const ns = Buffer.alloc(0)
return { ne, ns, ciphertext };
return { ne, ns, ciphertext }
}
protected readMessageRegular(cs: CipherState, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
return this.decryptWithAd(cs, Buffer.alloc(0), message.ciphertext);
protected readMessageRegular (cs: CipherState, message: MessageBuffer): {plaintext: bytes, valid: boolean} {
return this.decryptWithAd(cs, Buffer.alloc(0), message.ciphertext)
}
}

View File

@ -1,157 +1,156 @@
import {Buffer} from "buffer";
import {CipherState, HandshakeState, MessageBuffer, NoiseSession} from "../@types/handshake";
import {bytes, bytes32} from "../@types/basic";
import {generateKeypair, isValidPublicKey} from "../utils";
import {AbstractHandshake} from "./abstract-handshake";
import {KeyPair} from "../@types/libp2p";
import { Buffer } from 'buffer'
import { CipherState, HandshakeState, MessageBuffer, NoiseSession } from '../@types/handshake'
import { bytes, bytes32 } from '../@types/basic'
import { generateKeypair, isValidPublicKey } from '../utils'
import { AbstractHandshake } from './abstract-handshake'
import { KeyPair } from '../@types/libp2p'
export class IK extends AbstractHandshake {
public initSession(initiator: boolean, prologue: bytes32, s: KeyPair, rs: bytes32): NoiseSession {
const psk = this.createEmptyKey();
public initSession (initiator: boolean, prologue: bytes32, s: KeyPair, rs: bytes32): NoiseSession {
const psk = this.createEmptyKey()
let hs;
let hs
if (initiator) {
hs = this.initializeInitiator(prologue, s, rs, psk);
hs = this.initializeInitiator(prologue, s, rs, psk)
} else {
hs = this.initializeResponder(prologue, s, rs, psk);
hs = this.initializeResponder(prologue, s, rs, psk)
}
return {
hs,
i: initiator,
mc: 0,
};
mc: 0
}
}
public sendMessage(session: NoiseSession, message: bytes): MessageBuffer {
let messageBuffer: MessageBuffer;
public sendMessage (session: NoiseSession, message: bytes): MessageBuffer {
let messageBuffer: MessageBuffer
if (session.mc === 0) {
messageBuffer = this.writeMessageA(session.hs, message);
messageBuffer = this.writeMessageA(session.hs, message)
} else if (session.mc === 1) {
const { messageBuffer: mb, h, cs1, cs2 } = this.writeMessageB(session.hs, message);
messageBuffer = mb;
session.h = h;
session.cs1 = cs1;
session.cs2 = cs2;
const { messageBuffer: mb, h, cs1, cs2 } = this.writeMessageB(session.hs, message)
messageBuffer = mb
session.h = h
session.cs1 = cs1
session.cs2 = cs2
} else if (session.mc > 1) {
if (session.i) {
if (!session.cs1) {
throw new Error("CS1 (cipher state) is not defined")
throw new Error('CS1 (cipher state) is not defined')
}
messageBuffer = this.writeMessageRegular(session.cs1, message);
messageBuffer = this.writeMessageRegular(session.cs1, message)
} else {
if (!session.cs2) {
throw new Error("CS2 (cipher state) is not defined")
throw new Error('CS2 (cipher state) is not defined')
}
messageBuffer = this.writeMessageRegular(session.cs2, message);
messageBuffer = this.writeMessageRegular(session.cs2, message)
}
} else {
throw new Error("Session invalid.")
throw new Error('Session invalid.')
}
session.mc++;
return messageBuffer;
session.mc++
return messageBuffer
}
public recvMessage(session: NoiseSession, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
let plaintext = Buffer.alloc(0), valid = false;
public recvMessage (session: NoiseSession, message: MessageBuffer): {plaintext: bytes, valid: boolean} {
let plaintext = Buffer.alloc(0); let valid = false
if (session.mc === 0) {
({plaintext, valid} = this.readMessageA(session.hs, message));
({ plaintext, valid } = this.readMessageA(session.hs, message))
}
if (session.mc === 1) {
const { plaintext: pt, valid: v, h, cs1, cs2 } = this.readMessageB(session.hs, message);
plaintext = pt;
valid = v;
session.h = h;
session.cs1 = cs1;
session.cs2 = cs2;
const { plaintext: pt, valid: v, h, cs1, cs2 } = this.readMessageB(session.hs, message)
plaintext = pt
valid = v
session.h = h
session.cs1 = cs1
session.cs2 = cs2
}
session.mc++;
return {plaintext, valid};
session.mc++
return { plaintext, valid }
}
private writeMessageA(hs: HandshakeState, payload: bytes): MessageBuffer {
hs.e = generateKeypair();
const ne = hs.e.publicKey;
this.mixHash(hs.ss, ne);
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs));
const spk = Buffer.from(hs.s.publicKey);
const ns = this.encryptAndHash(hs.ss, spk);
private writeMessageA (hs: HandshakeState, payload: bytes): MessageBuffer {
hs.e = generateKeypair()
const ne = hs.e.publicKey
this.mixHash(hs.ss, ne)
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs))
const spk = Buffer.from(hs.s.publicKey)
const ns = this.encryptAndHash(hs.ss, spk)
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.rs));
const ciphertext = this.encryptAndHash(hs.ss, payload);
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.rs))
const ciphertext = this.encryptAndHash(hs.ss, payload)
return { ne, ns, ciphertext };
return { ne, ns, ciphertext }
}
private writeMessageB(hs: HandshakeState, payload: bytes) {
hs.e = generateKeypair();
const ne = hs.e.publicKey;
this.mixHash(hs.ss, ne);
private writeMessageB (hs: HandshakeState, payload: bytes) {
hs.e = generateKeypair()
const ne = hs.e.publicKey
this.mixHash(hs.ss, ne)
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re));
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs));
const ciphertext = this.encryptAndHash(hs.ss, payload);
const ns = this.createEmptyKey();
const messageBuffer: MessageBuffer = {ne, ns, ciphertext};
const { cs1, cs2 } = this.split(hs.ss);
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re))
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs))
const ciphertext = this.encryptAndHash(hs.ss, payload)
const ns = this.createEmptyKey()
const messageBuffer: MessageBuffer = { ne, ns, ciphertext }
const { cs1, cs2 } = this.split(hs.ss)
return { messageBuffer, cs1, cs2, h: hs.ss.h }
}
private readMessageA(hs: HandshakeState, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
private readMessageA (hs: HandshakeState, message: MessageBuffer): {plaintext: bytes, valid: boolean} {
if (isValidPublicKey(message.ne)) {
hs.re = message.ne;
hs.re = message.ne
}
this.mixHash(hs.ss, hs.re);
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re));
const {plaintext: ns, valid: valid1} = this.decryptAndHash(hs.ss, message.ns);
this.mixHash(hs.ss, hs.re)
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re))
const { plaintext: ns, valid: valid1 } = this.decryptAndHash(hs.ss, message.ns)
if (valid1 && ns.length === 32 && isValidPublicKey(ns)) {
hs.rs = ns;
hs.rs = ns
}
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.rs));
const {plaintext, valid: valid2} = this.decryptAndHash(hs.ss, message.ciphertext);
return {plaintext, valid: (valid1 && valid2)};
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.rs))
const { plaintext, valid: valid2 } = this.decryptAndHash(hs.ss, message.ciphertext)
return { plaintext, valid: (valid1 && valid2) }
}
private readMessageB(hs: HandshakeState, message: MessageBuffer): {h: bytes; plaintext: bytes; valid: boolean; cs1: CipherState; cs2: CipherState} {
private readMessageB (hs: HandshakeState, message: MessageBuffer): {h: bytes, plaintext: bytes, valid: boolean, cs1: CipherState, cs2: CipherState} {
if (isValidPublicKey(message.ne)) {
hs.re = message.ne;
hs.re = message.ne
}
this.mixHash(hs.ss, hs.re);
this.mixHash(hs.ss, hs.re)
if (!hs.e) {
throw new Error("Handshake state should contain ephemeral key by now.");
throw new Error('Handshake state should contain ephemeral key by now.')
}
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re));
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re));
const {plaintext, valid} = this.decryptAndHash(hs.ss, message.ciphertext);
const { cs1, cs2 } = this.split(hs.ss);
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re))
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re))
const { plaintext, valid } = this.decryptAndHash(hs.ss, message.ciphertext)
const { cs1, cs2 } = this.split(hs.ss)
return { h: hs.ss.h, valid, plaintext, cs1, cs2 };
return { h: hs.ss.h, valid, plaintext, cs1, cs2 }
}
private initializeInitiator(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
const name = "Noise_IK_25519_ChaChaPoly_SHA256";
const ss = this.initializeSymmetric(name);
this.mixHash(ss, prologue);
this.mixHash(ss, rs);
const re = Buffer.alloc(32);
private initializeInitiator (prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
const name = 'Noise_IK_25519_ChaChaPoly_SHA256'
const ss = this.initializeSymmetric(name)
this.mixHash(ss, prologue)
this.mixHash(ss, rs)
const re = Buffer.alloc(32)
return { ss, s, rs, re, psk };
return { ss, s, rs, re, psk }
}
private initializeResponder(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
const name = "Noise_IK_25519_ChaChaPoly_SHA256";
const ss = this.initializeSymmetric(name);
this.mixHash(ss, prologue);
this.mixHash(ss, s.publicKey);
const re = Buffer.alloc(32);
private initializeResponder (prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
const name = 'Noise_IK_25519_ChaChaPoly_SHA256'
const ss = this.initializeSymmetric(name)
this.mixHash(ss, prologue)
this.mixHash(ss, s.publicKey)
const re = Buffer.alloc(32)
return { ss, s, rs, re, psk };
return { ss, s, rs, re, psk }
}
}

View File

@ -1,186 +1,185 @@
import { Buffer } from 'buffer';
import { Buffer } from 'buffer'
import { bytes32, bytes } from '../@types/basic'
import { KeyPair } from '../@types/libp2p'
import {generateKeypair, isValidPublicKey} from '../utils';
import {CipherState, HandshakeState, MessageBuffer, NoiseSession} from "../@types/handshake";
import {AbstractHandshake} from "./abstract-handshake";
import { generateKeypair, isValidPublicKey } from '../utils'
import { CipherState, HandshakeState, MessageBuffer, NoiseSession } from '../@types/handshake'
import { AbstractHandshake } from './abstract-handshake'
export class XX extends AbstractHandshake {
private initializeInitiator(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
const name = "Noise_XX_25519_ChaChaPoly_SHA256";
const ss = this.initializeSymmetric(name);
this.mixHash(ss, prologue);
const re = Buffer.alloc(32);
private initializeInitiator (prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
const name = 'Noise_XX_25519_ChaChaPoly_SHA256'
const ss = this.initializeSymmetric(name)
this.mixHash(ss, prologue)
const re = Buffer.alloc(32)
return { ss, s, rs, psk, re };
return { ss, s, rs, psk, re }
}
private initializeResponder(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
const name = "Noise_XX_25519_ChaChaPoly_SHA256";
const ss = this.initializeSymmetric(name);
this.mixHash(ss, prologue);
const re = Buffer.alloc(32);
private initializeResponder (prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
const name = 'Noise_XX_25519_ChaChaPoly_SHA256'
const ss = this.initializeSymmetric(name)
this.mixHash(ss, prologue)
const re = Buffer.alloc(32)
return { ss, s, rs, psk, re };
return { ss, s, rs, psk, re }
}
private writeMessageA(hs: HandshakeState, payload: bytes, e?: KeyPair): MessageBuffer {
const ns = Buffer.alloc(0);
private writeMessageA (hs: HandshakeState, payload: bytes, e?: KeyPair): MessageBuffer {
const ns = Buffer.alloc(0)
if (e) {
hs.e = e;
if (e !== undefined) {
hs.e = e
} else {
hs.e = generateKeypair();
hs.e = generateKeypair()
}
const ne = hs.e.publicKey;
const ne = hs.e.publicKey
this.mixHash(hs.ss, ne);
const ciphertext = this.encryptAndHash(hs.ss, payload);
this.mixHash(hs.ss, ne)
const ciphertext = this.encryptAndHash(hs.ss, payload)
return {ne, ns, ciphertext};
return { ne, ns, ciphertext }
}
private writeMessageB(hs: HandshakeState, payload: bytes): MessageBuffer {
hs.e = generateKeypair();
const ne = hs.e.publicKey;
this.mixHash(hs.ss, ne);
private writeMessageB (hs: HandshakeState, payload: bytes): MessageBuffer {
hs.e = generateKeypair()
const ne = hs.e.publicKey
this.mixHash(hs.ss, ne)
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re));
const spk = Buffer.from(hs.s.publicKey);
const ns = this.encryptAndHash(hs.ss, spk);
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re))
const spk = Buffer.from(hs.s.publicKey)
const ns = this.encryptAndHash(hs.ss, spk)
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re));
const ciphertext = this.encryptAndHash(hs.ss, payload);
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re))
const ciphertext = this.encryptAndHash(hs.ss, payload)
return { ne, ns, ciphertext };
return { ne, ns, ciphertext }
}
private writeMessageC(hs: HandshakeState, payload: bytes) {
const spk = Buffer.from(hs.s.publicKey);
const ns = this.encryptAndHash(hs.ss, spk);
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re));
const ciphertext = this.encryptAndHash(hs.ss, payload);
const ne = this.createEmptyKey();
const messageBuffer: MessageBuffer = {ne, ns, ciphertext};
const { cs1, cs2 } = this.split(hs.ss);
private writeMessageC (hs: HandshakeState, payload: bytes) {
const spk = Buffer.from(hs.s.publicKey)
const ns = this.encryptAndHash(hs.ss, spk)
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re))
const ciphertext = this.encryptAndHash(hs.ss, payload)
const ne = this.createEmptyKey()
const messageBuffer: MessageBuffer = { ne, ns, ciphertext }
const { cs1, cs2 } = this.split(hs.ss)
return { h: hs.ss.h, messageBuffer, cs1, cs2 };
return { h: hs.ss.h, messageBuffer, cs1, cs2 }
}
private readMessageA(hs: HandshakeState, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
private readMessageA (hs: HandshakeState, message: MessageBuffer): {plaintext: bytes, valid: boolean} {
if (isValidPublicKey(message.ne)) {
hs.re = message.ne;
hs.re = message.ne
}
this.mixHash(hs.ss, hs.re);
return this.decryptAndHash(hs.ss, message.ciphertext);
this.mixHash(hs.ss, hs.re)
return this.decryptAndHash(hs.ss, message.ciphertext)
}
private readMessageB(hs: HandshakeState, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
private readMessageB (hs: HandshakeState, message: MessageBuffer): {plaintext: bytes, valid: boolean} {
if (isValidPublicKey(message.ne)) {
hs.re = message.ne;
hs.re = message.ne
}
this.mixHash(hs.ss, hs.re);
this.mixHash(hs.ss, hs.re)
if (!hs.e) {
throw new Error("Handshake state `e` param is missing.");
throw new Error('Handshake state `e` param is missing.')
}
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re));
const {plaintext: ns, valid: valid1} = this.decryptAndHash(hs.ss, message.ns);
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re))
const { plaintext: ns, valid: valid1 } = this.decryptAndHash(hs.ss, message.ns)
if (valid1 && ns.length === 32 && isValidPublicKey(ns)) {
hs.rs = ns;
hs.rs = ns
}
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs));
const {plaintext, valid: valid2} = this.decryptAndHash(hs.ss, message.ciphertext);
return {plaintext, valid: (valid1 && valid2)};
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs))
const { plaintext, valid: valid2 } = this.decryptAndHash(hs.ss, message.ciphertext)
return { plaintext, valid: (valid1 && valid2) }
}
private readMessageC(hs: HandshakeState, message: MessageBuffer): {h: bytes; plaintext: bytes; valid: boolean; cs1: CipherState; cs2: CipherState} {
const {plaintext: ns, valid: valid1} = this.decryptAndHash(hs.ss, message.ns);
private readMessageC (hs: HandshakeState, message: MessageBuffer): {h: bytes, plaintext: bytes, valid: boolean, cs1: CipherState, cs2: CipherState} {
const { plaintext: ns, valid: valid1 } = this.decryptAndHash(hs.ss, message.ns)
if (valid1 && ns.length === 32 && isValidPublicKey(ns)) {
hs.rs = ns;
hs.rs = ns
}
if (!hs.e) {
throw new Error("Handshake state `e` param is missing.");
throw new Error('Handshake state `e` param is missing.')
}
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs));
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs))
const {plaintext, valid: valid2} = this.decryptAndHash(hs.ss, message.ciphertext);
const { cs1, cs2 } = this.split(hs.ss);
const { plaintext, valid: valid2 } = this.decryptAndHash(hs.ss, message.ciphertext)
const { cs1, cs2 } = this.split(hs.ss)
return { h: hs.ss.h, plaintext, valid: (valid1 && valid2), cs1, cs2 };
return { h: hs.ss.h, plaintext, valid: (valid1 && valid2), cs1, cs2 }
}
public initSession(initiator: boolean, prologue: bytes32, s: KeyPair): NoiseSession {
const psk = this.createEmptyKey();
const rs = Buffer.alloc(32); // no static key yet
let hs;
public initSession (initiator: boolean, prologue: bytes32, s: KeyPair): NoiseSession {
const psk = this.createEmptyKey()
const rs = Buffer.alloc(32) // no static key yet
let hs
if (initiator) {
hs = this.initializeInitiator(prologue, s, rs, psk);
hs = this.initializeInitiator(prologue, s, rs, psk)
} else {
hs = this.initializeResponder(prologue, s, rs, psk);
hs = this.initializeResponder(prologue, s, rs, psk)
}
return {
hs,
i: initiator,
mc: 0,
};
mc: 0
}
}
public sendMessage(session: NoiseSession, message: bytes, ephemeral?: KeyPair): MessageBuffer {
let messageBuffer: MessageBuffer;
public sendMessage (session: NoiseSession, message: bytes, ephemeral?: KeyPair): MessageBuffer {
let messageBuffer: MessageBuffer
if (session.mc === 0) {
messageBuffer = this.writeMessageA(session.hs, message, ephemeral);
messageBuffer = this.writeMessageA(session.hs, message, ephemeral)
} else if (session.mc === 1) {
messageBuffer = this.writeMessageB(session.hs, message);
messageBuffer = this.writeMessageB(session.hs, message)
} else if (session.mc === 2) {
const { h, messageBuffer: resultingBuffer, cs1, cs2 } = this.writeMessageC(session.hs, message);
messageBuffer = resultingBuffer;
session.h = h;
session.cs1 = cs1;
session.cs2 = cs2;
const { h, messageBuffer: resultingBuffer, cs1, cs2 } = this.writeMessageC(session.hs, message)
messageBuffer = resultingBuffer
session.h = h
session.cs1 = cs1
session.cs2 = cs2
} else if (session.mc > 2) {
if (session.i) {
if (!session.cs1) {
throw new Error("CS1 (cipher state) is not defined")
throw new Error('CS1 (cipher state) is not defined')
}
messageBuffer = this.writeMessageRegular(session.cs1, message);
messageBuffer = this.writeMessageRegular(session.cs1, message)
} else {
if (!session.cs2) {
throw new Error("CS2 (cipher state) is not defined")
throw new Error('CS2 (cipher state) is not defined')
}
messageBuffer = this.writeMessageRegular(session.cs2, message);
messageBuffer = this.writeMessageRegular(session.cs2, message)
}
} else {
throw new Error("Session invalid.")
throw new Error('Session invalid.')
}
session.mc++;
return messageBuffer;
session.mc++
return messageBuffer
}
public recvMessage(session: NoiseSession, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
let plaintext: bytes = Buffer.alloc(0);
let valid = false;
public recvMessage (session: NoiseSession, message: MessageBuffer): {plaintext: bytes, valid: boolean} {
let plaintext: bytes = Buffer.alloc(0)
let valid = false
if (session.mc === 0) {
({plaintext, valid} = this.readMessageA(session.hs, message));
({ plaintext, valid } = this.readMessageA(session.hs, message))
} else if (session.mc === 1) {
({plaintext, valid} = this.readMessageB(session.hs, message));
({ plaintext, valid } = this.readMessageB(session.hs, message))
} else if (session.mc === 2) {
const { h, plaintext: resultingPlaintext, valid: resultingValid, cs1, cs2 } = this.readMessageC(session.hs, message);
plaintext = resultingPlaintext;
valid = resultingValid;
session.h = h;
session.cs1 = cs1;
session.cs2 = cs2;
const { h, plaintext: resultingPlaintext, valid: resultingValid, cs1, cs2 } = this.readMessageC(session.hs, message)
plaintext = resultingPlaintext
valid = resultingValid
session.h = h
session.cs1 = cs1
session.cs2 = cs2
}
session.mc++;
return {plaintext, valid};
session.mc++
return { plaintext, valid }
}
}

View File

@ -1,7 +1,7 @@
import {Noise} from "./noise";
export * from "./noise";
import { Noise } from './noise'
export * from './noise'
/**
* Default configuration, it will generate new noise static key and enable noise pipes (IK handshake).
*/
export const NOISE = new Noise();
export const NOISE = new Noise()

View File

@ -1,30 +1,29 @@
import {bytes, bytes32} from "./@types/basic";
import PeerId from "peer-id";
import { bytes32 } from './@types/basic'
import PeerId from 'peer-id'
/**
* Storage for static keys of previously connected peers.
*/
class Keycache {
private storage = new Map<bytes, bytes32>();
private readonly storage = new Map<Uint8Array, bytes32>()
public store(peerId: PeerId, key: bytes32): void {
this.storage.set(peerId.id, key);
public store (peerId: PeerId, key: bytes32): void {
this.storage.set(peerId.id, key)
}
public load(peerId?: PeerId): bytes32 | null {
if(!peerId) {
return null;
public load (peerId?: PeerId): bytes32 | null {
if (!peerId) {
return null
}
return this.storage.get(peerId.id) || null;
return this.storage.get(peerId.id) ?? null
}
public resetStorage(): void {
this.storage.clear();
public resetStorage (): void {
this.storage.clear()
}
}
const KeyCache = new Keycache();
const KeyCache = new Keycache()
export {
KeyCache,
KeyCache
}

View File

@ -1,47 +1,44 @@
import debug from "debug";
import {DUMP_SESSION_KEYS} from './constants';
import { KeyPair } from "./@types/libp2p";
import { NoiseSession, SymmetricState } from "./@types/handshake";
import debug from 'debug'
import { DUMP_SESSION_KEYS } from './constants'
import { KeyPair } from './@types/libp2p'
import { NoiseSession } from './@types/handshake'
export const logger = debug('libp2p:noise');
export const logger = debug('libp2p:noise')
let keyLogger;
if(DUMP_SESSION_KEYS){
let keyLogger
if (DUMP_SESSION_KEYS) {
keyLogger = logger
}
else {
keyLogger = () => {}
} else {
keyLogger = () => { /* do nothing */ }
}
export function logLocalStaticKeys(s: KeyPair): void {
export function logLocalStaticKeys (s: KeyPair): void {
keyLogger(`LOCAL_STATIC_PUBLIC_KEY ${s.publicKey.toString('hex')}`)
keyLogger(`LOCAL_STATIC_PRIVATE_KEY ${s.privateKey.toString('hex')}`)
}
export function logLocalEphemeralKeys(e: KeyPair|undefined): void {
if(e){
export function logLocalEphemeralKeys (e: KeyPair|undefined): void {
if (e) {
keyLogger(`LOCAL_PUBLIC_EPHEMERAL_KEY ${e.publicKey.toString('hex')}`)
keyLogger(`LOCAL_PRIVATE_EPHEMERAL_KEY ${e.privateKey.toString('hex')}`)
}
else{
} else {
keyLogger('Missing local ephemeral keys.')
}
}
export function logRemoteStaticKey(rs: Buffer): void {
keyLogger(`REMOTE_STATIC_PUBLIC_KEY ${rs.toString('hex')}`)
export function logRemoteStaticKey (rs: Buffer): void {
keyLogger(`REMOTE_STATIC_PUBLIC_KEY ${rs.toString('hex')}`)
}
export function logRemoteEphemeralKey(re: Buffer): void {
keyLogger(`REMOTE_EPHEMERAL_PUBLIC_KEY ${re.toString('hex')}`)
export function logRemoteEphemeralKey (re: Buffer): void {
keyLogger(`REMOTE_EPHEMERAL_PUBLIC_KEY ${re.toString('hex')}`)
}
export function logCipherState(session: NoiseSession): void {
if(session.cs1 && session.cs2){
export function logCipherState (session: NoiseSession): void {
if (session.cs1 && session.cs2) {
keyLogger(`CIPHER_STATE_1 ${session.cs1.n} ${session.cs1.k.toString('hex')}`)
keyLogger(`CIPHER_STATE_2 ${session.cs2.n} ${session.cs2.k.toString('hex')}`)
}
else{
} else {
keyLogger('Missing cipher state.')
}
}

View File

@ -1,72 +1,73 @@
import {box} from 'tweetnacl';
import {Buffer} from "buffer";
import Wrap from 'it-pb-rpc';
import DuplexPair from 'it-pair/duplex';
import ensureBuffer from 'it-buffer';
import pipe from 'it-pipe';
import {encode, decode} from 'it-length-prefixed';
import x25519 from 'bcrypto/lib/js/x25519'
import { Buffer } from 'buffer'
import Wrap from 'it-pb-rpc'
import DuplexPair from 'it-pair/duplex'
import ensureBuffer from 'it-buffer'
import pipe from 'it-pipe'
import { encode, decode } from 'it-length-prefixed'
import {XXHandshake} from "./handshake-xx";
import {IKHandshake} from "./handshake-ik";
import {XXFallbackHandshake} from "./handshake-xx-fallback";
import {generateKeypair, getPayload} from "./utils";
import {uint16BEDecode, uint16BEEncode} from "./encoder";
import {decryptStream, encryptStream} from "./crypto";
import {bytes} from "./@types/basic";
import {INoiseConnection, KeyPair, SecureOutbound} from "./@types/libp2p";
import {Duplex} from "it-pair";
import {IHandshake} from "./@types/handshake-interface";
import {KeyCache} from "./keycache";
import {logger} from "./logger";
import PeerId from "peer-id";
import {NOISE_MSG_MAX_LENGTH_BYTES} from "./constants";
import { XXHandshake } from './handshake-xx'
import { IKHandshake } from './handshake-ik'
import { XXFallbackHandshake } from './handshake-xx-fallback'
import { generateKeypair, getPayload } from './utils'
import { uint16BEDecode, uint16BEEncode } from './encoder'
import { decryptStream, encryptStream } from './crypto'
import { bytes } from './@types/basic'
import { INoiseConnection, KeyPair, SecureOutbound } from './@types/libp2p'
import { Duplex } from 'it-pair'
import { IHandshake } from './@types/handshake-interface'
import { KeyCache } from './keycache'
import { logger } from './logger'
import PeerId from 'peer-id'
import { NOISE_MSG_MAX_LENGTH_BYTES } from './constants'
export type WrappedConnection = ReturnType<typeof Wrap>;
export type WrappedConnection = ReturnType<typeof Wrap>
type HandshakeParams = {
connection: WrappedConnection;
isInitiator: boolean;
localPeer: PeerId;
remotePeer?: PeerId;
};
interface HandshakeParams {
connection: WrappedConnection
isInitiator: boolean
localPeer: PeerId
remotePeer?: PeerId
}
export class Noise implements INoiseConnection {
public protocol = "/noise";
public protocol = '/noise'
private readonly prologue = Buffer.alloc(0);
private readonly staticKeys: KeyPair;
private readonly earlyData?: bytes;
private useNoisePipes: boolean;
private readonly prologue = Buffer.alloc(0)
private readonly staticKeys: KeyPair
private readonly earlyData?: bytes
private readonly useNoisePipes: boolean
/**
*
* @param staticNoiseKey x25519 private key, reuse for faster handshakes
* @param earlyData
* @param {bytes} staticNoiseKey - x25519 private key, reuse for faster handshakes
* @param {bytes} earlyData
*/
constructor(staticNoiseKey?: bytes, earlyData?: bytes) {
this.earlyData = earlyData || Buffer.alloc(0);
//disabled until properly specked
this.useNoisePipes = false;
constructor (staticNoiseKey?: bytes, earlyData?: bytes) {
this.earlyData = earlyData ?? Buffer.alloc(0)
// disabled until properly specked
this.useNoisePipes = false
if (staticNoiseKey) {
const publicKey = Buffer.from(box.keyPair.fromSecretKey(staticNoiseKey).publicKey);
const publicKey = x25519.publicKeyCreate(staticNoiseKey)
this.staticKeys = {
privateKey: staticNoiseKey,
publicKey,
publicKey
}
} else {
this.staticKeys = generateKeypair();
this.staticKeys = generateKeypair()
}
}
/**
* Encrypt outgoing data to the remote party (handshake as initiator)
*
* @param {PeerId} localPeer - PeerId of the receiving peer
* @param connection - streaming iterable duplex that will be encrypted
* @param {any} connection - streaming iterable duplex that will be encrypted
* @param {PeerId} remotePeer - PeerId of the remote peer. Used to validate the integrity of the remote peer.
* @returns {Promise<SecureOutbound>}
*/
public async secureOutbound(localPeer: PeerId, connection: any, remotePeer: PeerId): Promise<SecureOutbound> {
public async secureOutbound (localPeer: PeerId, connection: any, remotePeer: PeerId): Promise<SecureOutbound> {
const wrappedConnection = Wrap(
connection,
{
@ -74,172 +75,173 @@ export class Noise implements INoiseConnection {
lengthDecoder: uint16BEDecode,
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
}
);
)
const handshake = await this.performHandshake({
connection: wrappedConnection,
isInitiator: true,
localPeer,
remotePeer,
});
const conn = await this.createSecureConnection(wrappedConnection, handshake);
return {
conn,
remoteEarlyData: handshake.remoteEarlyData,
remotePeer: handshake.remotePeer,
}
}
/**
* Decrypt incoming data (handshake as responder).
* @param {PeerId} localPeer - PeerId of the receiving peer.
* @param connection - streaming iterable duplex that will be encryption.
* @param {PeerId} remotePeer - optional PeerId of the initiating peer, if known. This may only exist during transport upgrades.
* @returns {Promise<SecureOutbound>}
*/
public async secureInbound(localPeer: PeerId, connection: any, remotePeer?: PeerId): Promise<SecureOutbound> {
const wrappedConnection = Wrap(
connection,
{
lengthEncoder: uint16BEEncode,
lengthDecoder: uint16BEDecode,
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
}
);
const handshake = await this.performHandshake({
connection: wrappedConnection,
isInitiator: false,
localPeer,
remotePeer
});
const conn = await this.createSecureConnection(wrappedConnection, handshake);
})
const conn = await this.createSecureConnection(wrappedConnection, handshake)
return {
conn,
remoteEarlyData: handshake.remoteEarlyData,
remotePeer: handshake.remotePeer
};
}
}
/**
* Decrypt incoming data (handshake as responder).
*
* @param {PeerId} localPeer - PeerId of the receiving peer.
* @param {any} connection - streaming iterable duplex that will be encryption.
* @param {PeerId} remotePeer - optional PeerId of the initiating peer, if known. This may only exist during transport upgrades.
* @returns {Promise<SecureOutbound>}
*/
public async secureInbound (localPeer: PeerId, connection: any, remotePeer?: PeerId): Promise<SecureOutbound> {
const wrappedConnection = Wrap(
connection,
{
lengthEncoder: uint16BEEncode,
lengthDecoder: uint16BEDecode,
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
}
)
const handshake = await this.performHandshake({
connection: wrappedConnection,
isInitiator: false,
localPeer,
remotePeer
})
const conn = await this.createSecureConnection(wrappedConnection, handshake)
return {
conn,
remoteEarlyData: handshake.remoteEarlyData,
remotePeer: handshake.remotePeer
}
}
/**
* If Noise pipes supported, tries IK handshake first with XX as fallback if it fails.
* If noise pipes disabled or remote peer static key is unknown, use XX.
* @param params
*
* @param {HandshakeParams} params
*/
private async performHandshake(params: HandshakeParams): Promise<IHandshake> {
const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData);
let tryIK = this.useNoisePipes;
if(params.isInitiator && KeyCache.load(params.remotePeer) === null) {
//if we are initiator and remote static key is unknown, don't try IK
tryIK = false;
private async performHandshake (params: HandshakeParams): Promise<IHandshake> {
const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData)
let tryIK = this.useNoisePipes
if (params.isInitiator && KeyCache.load(params.remotePeer) === null) {
// if we are initiator and remote static key is unknown, don't try IK
tryIK = false
}
// Try IK if acting as responder or initiator that has remote's static key.
if (tryIK) {
// Try IK first
const { remotePeer, connection, isInitiator } = params;
const { remotePeer, connection, isInitiator } = params
const ikHandshake = new IKHandshake(
isInitiator,
payload,
this.prologue,
this.staticKeys,
connection,
//safe to cast as we did checks
KeyCache.load(params.remotePeer) || Buffer.alloc(32),
remotePeer as PeerId,
);
// safe to cast as we did checks
KeyCache.load(params.remotePeer) ?? Buffer.alloc(32),
remotePeer as PeerId
)
try {
return await this.performIKHandshake(ikHandshake);
return await this.performIKHandshake(ikHandshake)
} catch (e) {
// IK failed, go to XX fallback
let ephemeralKeys;
let ephemeralKeys
if (params.isInitiator) {
ephemeralKeys = ikHandshake.getLocalEphemeralKeys();
ephemeralKeys = ikHandshake.getLocalEphemeralKeys()
}
return await this.performXXFallbackHandshake(params, payload, e.initialMsg, ephemeralKeys);
return await this.performXXFallbackHandshake(params, payload, e.initialMsg, ephemeralKeys)
}
} else {
// run XX handshake
return await this.performXXHandshake(params, payload);
return await this.performXXHandshake(params, payload)
}
}
private async performXXFallbackHandshake(
private async performXXFallbackHandshake (
params: HandshakeParams,
payload: bytes,
initialMsg: bytes,
ephemeralKeys?: KeyPair,
ephemeralKeys?: KeyPair
): Promise<XXFallbackHandshake> {
const { isInitiator, remotePeer, connection } = params;
const { isInitiator, remotePeer, connection } = params
const handshake =
new XXFallbackHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, initialMsg, remotePeer, ephemeralKeys);
new XXFallbackHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, initialMsg, remotePeer, ephemeralKeys)
try {
await handshake.propose();
await handshake.exchange();
await handshake.finish();
await handshake.propose()
await handshake.exchange()
await handshake.finish()
} catch (e) {
logger(e);
throw new Error(`Error occurred during XX Fallback handshake: ${e.message}`);
logger(e)
const err = e as Error
throw new Error(`Error occurred during XX Fallback handshake: ${err.message}`)
}
return handshake;
return handshake
}
private async performXXHandshake(
private async performXXHandshake (
params: HandshakeParams,
payload: bytes,
payload: bytes
): Promise<XXHandshake> {
const { isInitiator, remotePeer, connection } = params;
const handshake = new XXHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer);
const { isInitiator, remotePeer, connection } = params
const handshake = new XXHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer)
try {
await handshake.propose();
await handshake.exchange();
await handshake.finish();
await handshake.propose()
await handshake.exchange()
await handshake.finish()
if (this.useNoisePipes && handshake.remotePeer) {
KeyCache.store(handshake.remotePeer, handshake.getRemoteStaticKey());
KeyCache.store(handshake.remotePeer, handshake.getRemoteStaticKey())
}
} catch (e) {
throw new Error(`Error occurred during XX handshake: ${e.message}`);
const err = e as Error
throw new Error(`Error occurred during XX handshake: ${err.message}`)
}
return handshake;
return handshake
}
private async performIKHandshake(
handshake: IKHandshake,
private async performIKHandshake (
handshake: IKHandshake
): Promise<IKHandshake> {
await handshake.stage0()
await handshake.stage1()
await handshake.stage0();
await handshake.stage1();
return handshake;
return handshake
}
private async createSecureConnection(
private async createSecureConnection (
connection: WrappedConnection,
handshake: IHandshake,
handshake: IHandshake
): Promise<Duplex> {
// Create encryption box/unbox wrapper
const [secure, user] = DuplexPair();
const network = connection.unwrap();
const [secure, user] = DuplexPair()
const network = connection.unwrap()
pipe(
await pipe(
secure, // write to wrapper
ensureBuffer, // ensure any type of data is converted to buffer
encryptStream(handshake), // data is encrypted
encode({ lengthEncoder: uint16BEEncode }), // prefix with message length
network, // send to the remote peer
decode({ lengthDecoder: uint16BEDecode}), // read message length prefix
decode({ lengthDecoder: uint16BEDecode }), // read message length prefix
ensureBuffer, // ensure any type of data is converted to buffer
decryptStream(handshake), // decrypt the incoming data
secure // pipe to the wrapper
);
)
return user;
return user
}
}

View File

@ -1,33 +1,25 @@
/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars*/
(function(global, factory) { /* global define, require, module */
/* eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars */
(function (global, factory) { /* global define, require, module */
/* AMD */ if (typeof define === 'function' && define.amd) { define(['protobufjs/minimal'], factory) }
/* AMD */ if (typeof define === 'function' && define.amd)
define(["protobufjs/minimal"], factory);
/* CommonJS */ else if (typeof require === 'function' && typeof module === 'object' && module && module.exports) { module.exports = factory(require('protobufjs/minimal')) }
})(this, function ($protobuf) {
// Common aliases
var $Reader = $protobuf.Reader; var $Writer = $protobuf.Writer; var $util = $protobuf.util
/* CommonJS */ else if (typeof require === 'function' && typeof module === 'object' && module && module.exports)
module.exports = factory(require("protobufjs/minimal"));
// Exported root namespace
var $root = $protobuf.roots.default || ($protobuf.roots.default = {})
})(this, function($protobuf) {
"use strict";
// Common aliases
var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util;
// Exported root namespace
var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {});
$root.pb = (function() {
/**
$root.pb = (function () {
/**
* Namespace pb.
* @exports pb
* @namespace
*/
var pb = {};
pb.NoiseHandshakePayload = (function() {
/**
var pb = {}
pb.NoiseHandshakePayload = (function () {
/**
* Properties of a NoiseHandshakePayload.
* @memberof pb
* @interface INoiseHandshakePayload
@ -35,8 +27,8 @@
* @property {Uint8Array|null} [identitySig] NoiseHandshakePayload identitySig
* @property {Uint8Array|null} [data] NoiseHandshakePayload data
*/
/**
/**
* Constructs a new NoiseHandshakePayload.
* @memberof pb
* @classdesc Represents a NoiseHandshakePayload.
@ -44,38 +36,39 @@
* @constructor
* @param {pb.INoiseHandshakePayload=} [properties] Properties to set
*/
function NoiseHandshakePayload(properties) {
if (properties)
for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i)
if (properties[keys[i]] != null)
this[keys[i]] = properties[keys[i]];
}
/**
function NoiseHandshakePayload (properties) {
if (properties) {
for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) {
if (properties[keys[i]] != null) { this[keys[i]] = properties[keys[i]] }
}
}
}
/**
* NoiseHandshakePayload identityKey.
* @member {Uint8Array} identityKey
* @memberof pb.NoiseHandshakePayload
* @instance
*/
NoiseHandshakePayload.prototype.identityKey = $util.newBuffer([]);
/**
NoiseHandshakePayload.prototype.identityKey = $util.newBuffer([])
/**
* NoiseHandshakePayload identitySig.
* @member {Uint8Array} identitySig
* @memberof pb.NoiseHandshakePayload
* @instance
*/
NoiseHandshakePayload.prototype.identitySig = $util.newBuffer([]);
/**
NoiseHandshakePayload.prototype.identitySig = $util.newBuffer([])
/**
* NoiseHandshakePayload data.
* @member {Uint8Array} data
* @memberof pb.NoiseHandshakePayload
* @instance
*/
NoiseHandshakePayload.prototype.data = $util.newBuffer([]);
/**
NoiseHandshakePayload.prototype.data = $util.newBuffer([])
/**
* Creates a new NoiseHandshakePayload instance using the specified properties.
* @function create
* @memberof pb.NoiseHandshakePayload
@ -83,11 +76,11 @@
* @param {pb.INoiseHandshakePayload=} [properties] Properties to set
* @returns {pb.NoiseHandshakePayload} NoiseHandshakePayload instance
*/
NoiseHandshakePayload.create = function create(properties) {
return new NoiseHandshakePayload(properties);
};
/**
NoiseHandshakePayload.create = function create (properties) {
return new NoiseHandshakePayload(properties)
}
/**
* Encodes the specified NoiseHandshakePayload message. Does not implicitly {@link pb.NoiseHandshakePayload.verify|verify} messages.
* @function encode
* @memberof pb.NoiseHandshakePayload
@ -96,19 +89,15 @@
* @param {$protobuf.Writer} [writer] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
NoiseHandshakePayload.encode = function encode(message, writer) {
if (!writer)
writer = $Writer.create();
if (message.identityKey != null && message.hasOwnProperty("identityKey"))
writer.uint32(/* id 1, wireType 2 =*/10).bytes(message.identityKey);
if (message.identitySig != null && message.hasOwnProperty("identitySig"))
writer.uint32(/* id 2, wireType 2 =*/18).bytes(message.identitySig);
if (message.data != null && message.hasOwnProperty("data"))
writer.uint32(/* id 3, wireType 2 =*/26).bytes(message.data);
return writer;
};
/**
NoiseHandshakePayload.encode = function encode (message, writer) {
if (!writer) { writer = $Writer.create() }
if (message.identityKey != null && message.hasOwnProperty('identityKey')) { writer.uint32(/* id 1, wireType 2 = */10).bytes(message.identityKey) }
if (message.identitySig != null && message.hasOwnProperty('identitySig')) { writer.uint32(/* id 2, wireType 2 = */18).bytes(message.identitySig) }
if (message.data != null && message.hasOwnProperty('data')) { writer.uint32(/* id 3, wireType 2 = */26).bytes(message.data) }
return writer
}
/**
* Encodes the specified NoiseHandshakePayload message, length delimited. Does not implicitly {@link pb.NoiseHandshakePayload.verify|verify} messages.
* @function encodeDelimited
* @memberof pb.NoiseHandshakePayload
@ -117,11 +106,11 @@
* @param {$protobuf.Writer} [writer] Writer to encode to
* @returns {$protobuf.Writer} Writer
*/
NoiseHandshakePayload.encodeDelimited = function encodeDelimited(message, writer) {
return this.encode(message, writer).ldelim();
};
/**
NoiseHandshakePayload.encodeDelimited = function encodeDelimited (message, writer) {
return this.encode(message, writer).ldelim()
}
/**
* Decodes a NoiseHandshakePayload message from the specified reader or buffer.
* @function decode
* @memberof pb.NoiseHandshakePayload
@ -132,31 +121,30 @@
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
NoiseHandshakePayload.decode = function decode(reader, length) {
if (!(reader instanceof $Reader))
reader = $Reader.create(reader);
var end = length === undefined ? reader.len : reader.pos + length, message = new $root.pb.NoiseHandshakePayload();
while (reader.pos < end) {
var tag = reader.uint32();
switch (tag >>> 3) {
case 1:
message.identityKey = reader.bytes();
break;
case 2:
message.identitySig = reader.bytes();
break;
case 3:
message.data = reader.bytes();
break;
default:
reader.skipType(tag & 7);
break;
}
}
return message;
};
/**
NoiseHandshakePayload.decode = function decode (reader, length) {
if (!(reader instanceof $Reader)) { reader = $Reader.create(reader) }
var end = length === undefined ? reader.len : reader.pos + length; var message = new $root.pb.NoiseHandshakePayload()
while (reader.pos < end) {
var tag = reader.uint32()
switch (tag >>> 3) {
case 1:
message.identityKey = reader.bytes()
break
case 2:
message.identitySig = reader.bytes()
break
case 3:
message.data = reader.bytes()
break
default:
reader.skipType(tag & 7)
break
}
}
return message
}
/**
* Decodes a NoiseHandshakePayload message from the specified reader or buffer, length delimited.
* @function decodeDelimited
* @memberof pb.NoiseHandshakePayload
@ -166,13 +154,12 @@
* @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing
*/
NoiseHandshakePayload.decodeDelimited = function decodeDelimited(reader) {
if (!(reader instanceof $Reader))
reader = new $Reader(reader);
return this.decode(reader, reader.uint32());
};
/**
NoiseHandshakePayload.decodeDelimited = function decodeDelimited (reader) {
if (!(reader instanceof $Reader)) { reader = new $Reader(reader) }
return this.decode(reader, reader.uint32())
}
/**
* Verifies a NoiseHandshakePayload message.
* @function verify
* @memberof pb.NoiseHandshakePayload
@ -180,22 +167,21 @@
* @param {Object.<string,*>} message Plain object to verify
* @returns {string|null} `null` if valid, otherwise the reason why it is not
*/
NoiseHandshakePayload.verify = function verify(message) {
if (typeof message !== "object" || message === null)
return "object expected";
if (message.identityKey != null && message.hasOwnProperty("identityKey"))
if (!(message.identityKey && typeof message.identityKey.length === "number" || $util.isString(message.identityKey)))
return "identityKey: buffer expected";
if (message.identitySig != null && message.hasOwnProperty("identitySig"))
if (!(message.identitySig && typeof message.identitySig.length === "number" || $util.isString(message.identitySig)))
return "identitySig: buffer expected";
if (message.data != null && message.hasOwnProperty("data"))
if (!(message.data && typeof message.data.length === "number" || $util.isString(message.data)))
return "data: buffer expected";
return null;
};
/**
NoiseHandshakePayload.verify = function verify (message) {
if (typeof message !== 'object' || message === null) { return 'object expected' }
if (message.identityKey != null && message.hasOwnProperty('identityKey')) {
if (!(message.identityKey && typeof message.identityKey.length === 'number' || $util.isString(message.identityKey))) { return 'identityKey: buffer expected' }
}
if (message.identitySig != null && message.hasOwnProperty('identitySig')) {
if (!(message.identitySig && typeof message.identitySig.length === 'number' || $util.isString(message.identitySig))) { return 'identitySig: buffer expected' }
}
if (message.data != null && message.hasOwnProperty('data')) {
if (!(message.data && typeof message.data.length === 'number' || $util.isString(message.data))) { return 'data: buffer expected' }
}
return null
}
/**
* Creates a NoiseHandshakePayload message from a plain object. Also converts values to their respective internal types.
* @function fromObject
* @memberof pb.NoiseHandshakePayload
@ -203,29 +189,22 @@
* @param {Object.<string,*>} object Plain object
* @returns {pb.NoiseHandshakePayload} NoiseHandshakePayload
*/
NoiseHandshakePayload.fromObject = function fromObject(object) {
if (object instanceof $root.pb.NoiseHandshakePayload)
return object;
var message = new $root.pb.NoiseHandshakePayload();
if (object.identityKey != null)
if (typeof object.identityKey === "string")
$util.base64.decode(object.identityKey, message.identityKey = $util.newBuffer($util.base64.length(object.identityKey)), 0);
else if (object.identityKey.length)
message.identityKey = object.identityKey;
if (object.identitySig != null)
if (typeof object.identitySig === "string")
$util.base64.decode(object.identitySig, message.identitySig = $util.newBuffer($util.base64.length(object.identitySig)), 0);
else if (object.identitySig.length)
message.identitySig = object.identitySig;
if (object.data != null)
if (typeof object.data === "string")
$util.base64.decode(object.data, message.data = $util.newBuffer($util.base64.length(object.data)), 0);
else if (object.data.length)
message.data = object.data;
return message;
};
/**
NoiseHandshakePayload.fromObject = function fromObject (object) {
if (object instanceof $root.pb.NoiseHandshakePayload) { return object }
var message = new $root.pb.NoiseHandshakePayload()
if (object.identityKey != null) {
if (typeof object.identityKey === 'string') { $util.base64.decode(object.identityKey, message.identityKey = $util.newBuffer($util.base64.length(object.identityKey)), 0) } else if (object.identityKey.length) { message.identityKey = object.identityKey }
}
if (object.identitySig != null) {
if (typeof object.identitySig === 'string') { $util.base64.decode(object.identitySig, message.identitySig = $util.newBuffer($util.base64.length(object.identitySig)), 0) } else if (object.identitySig.length) { message.identitySig = object.identitySig }
}
if (object.data != null) {
if (typeof object.data === 'string') { $util.base64.decode(object.data, message.data = $util.newBuffer($util.base64.length(object.data)), 0) } else if (object.data.length) { message.data = object.data }
}
return message
}
/**
* Creates a plain object from a NoiseHandshakePayload message. Also converts values to other types if specified.
* @function toObject
* @memberof pb.NoiseHandshakePayload
@ -234,58 +213,45 @@
* @param {$protobuf.IConversionOptions} [options] Conversion options
* @returns {Object.<string,*>} Plain object
*/
NoiseHandshakePayload.toObject = function toObject(message, options) {
if (!options)
options = {};
var object = {};
if (options.defaults) {
if (options.bytes === String)
object.identityKey = "";
else {
object.identityKey = [];
if (options.bytes !== Array)
object.identityKey = $util.newBuffer(object.identityKey);
}
if (options.bytes === String)
object.identitySig = "";
else {
object.identitySig = [];
if (options.bytes !== Array)
object.identitySig = $util.newBuffer(object.identitySig);
}
if (options.bytes === String)
object.data = "";
else {
object.data = [];
if (options.bytes !== Array)
object.data = $util.newBuffer(object.data);
}
}
if (message.identityKey != null && message.hasOwnProperty("identityKey"))
object.identityKey = options.bytes === String ? $util.base64.encode(message.identityKey, 0, message.identityKey.length) : options.bytes === Array ? Array.prototype.slice.call(message.identityKey) : message.identityKey;
if (message.identitySig != null && message.hasOwnProperty("identitySig"))
object.identitySig = options.bytes === String ? $util.base64.encode(message.identitySig, 0, message.identitySig.length) : options.bytes === Array ? Array.prototype.slice.call(message.identitySig) : message.identitySig;
if (message.data != null && message.hasOwnProperty("data"))
object.data = options.bytes === String ? $util.base64.encode(message.data, 0, message.data.length) : options.bytes === Array ? Array.prototype.slice.call(message.data) : message.data;
return object;
};
/**
NoiseHandshakePayload.toObject = function toObject (message, options) {
if (!options) { options = {} }
var object = {}
if (options.defaults) {
if (options.bytes === String) { object.identityKey = '' } else {
object.identityKey = []
if (options.bytes !== Array) { object.identityKey = $util.newBuffer(object.identityKey) }
}
if (options.bytes === String) { object.identitySig = '' } else {
object.identitySig = []
if (options.bytes !== Array) { object.identitySig = $util.newBuffer(object.identitySig) }
}
if (options.bytes === String) { object.data = '' } else {
object.data = []
if (options.bytes !== Array) { object.data = $util.newBuffer(object.data) }
}
}
if (message.identityKey != null && message.hasOwnProperty('identityKey')) { object.identityKey = options.bytes === String ? $util.base64.encode(message.identityKey, 0, message.identityKey.length) : options.bytes === Array ? Array.prototype.slice.call(message.identityKey) : message.identityKey }
if (message.identitySig != null && message.hasOwnProperty('identitySig')) { object.identitySig = options.bytes === String ? $util.base64.encode(message.identitySig, 0, message.identitySig.length) : options.bytes === Array ? Array.prototype.slice.call(message.identitySig) : message.identitySig }
if (message.data != null && message.hasOwnProperty('data')) { object.data = options.bytes === String ? $util.base64.encode(message.data, 0, message.data.length) : options.bytes === Array ? Array.prototype.slice.call(message.data) : message.data }
return object
}
/**
* Converts this NoiseHandshakePayload to JSON.
* @function toJSON
* @memberof pb.NoiseHandshakePayload
* @instance
* @returns {Object.<string,*>} JSON object
*/
NoiseHandshakePayload.prototype.toJSON = function toJSON() {
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
};
return NoiseHandshakePayload;
})();
return pb;
})();
NoiseHandshakePayload.prototype.toJSON = function toJSON () {
return this.constructor.toObject(this, $protobuf.util.toJSONOptions)
}
return $root;
});
return NoiseHandshakePayload
})()
return pb
})()
return $root
})

View File

@ -1,119 +1,119 @@
import hkdf from 'futoin-hkdf';
import {box} from 'tweetnacl';
import {Buffer} from "buffer";
import PeerId from "peer-id";
import {keys} from 'libp2p-crypto';
import {KeyPair} from "./@types/libp2p";
import {bytes, bytes32} from "./@types/basic";
import {Hkdf, INoisePayload} from "./@types/handshake";
import {pb} from "./proto/payload";
import HKDF from 'bcrypto/lib/hkdf'
import x25519 from 'bcrypto/lib/js/x25519'
import SHA256 from 'bcrypto/lib/js/sha256'
import { Buffer } from 'buffer'
import PeerId from 'peer-id'
import { keys } from 'libp2p-crypto'
import { KeyPair } from './@types/libp2p'
import { bytes, bytes32 } from './@types/basic'
import { Hkdf, INoisePayload } from './@types/handshake'
import { pb } from './proto/payload'
import uint8ArrayEquals from 'uint8arrays/equals'
const NoiseHandshakePayloadProto = pb.NoiseHandshakePayload;
const NoiseHandshakePayloadProto = pb.NoiseHandshakePayload
export function generateKeypair(): KeyPair {
const keyPair = box.keyPair();
const publicKey = Buffer.from(keyPair.publicKey);
const privateKey = Buffer.from(keyPair.secretKey);
export function generateKeypair (): KeyPair {
const privateKey = x25519.privateKeyGenerate()
const publicKey = x25519.publicKeyCreate(privateKey)
return {
publicKey,
privateKey,
privateKey
}
}
export async function getPayload(
export async function getPayload (
localPeer: PeerId,
staticPublicKey: bytes,
earlyData?: bytes,
earlyData?: bytes
): Promise<bytes> {
const signedPayload = await signPayload(localPeer, getHandshakePayload(staticPublicKey));
const earlyDataPayload = earlyData || Buffer.alloc(0);
const signedPayload = await signPayload(localPeer, getHandshakePayload(staticPublicKey))
const earlyDataPayload = earlyData ?? Buffer.alloc(0)
return await createHandshakePayload(
return createHandshakePayload(
localPeer.marshalPubKey(),
signedPayload,
earlyDataPayload
);
)
}
export async function createHandshakePayload(
libp2pPublicKey: bytes,
signedPayload: bytes,
earlyData?: bytes,
): Promise<bytes> {
export function createHandshakePayload (
libp2pPublicKey: Uint8Array,
signedPayload: Uint8Array,
earlyData?: Uint8Array
): bytes {
const payloadInit = NoiseHandshakePayloadProto.create({
identityKey: libp2pPublicKey,
identityKey: Buffer.from(libp2pPublicKey),
identitySig: signedPayload,
data: earlyData || null,
});
data: earlyData ?? null
})
return Buffer.from(NoiseHandshakePayloadProto.encode(payloadInit).finish());
return Buffer.from(NoiseHandshakePayloadProto.encode(payloadInit).finish())
}
export async function signPayload(peerId: PeerId, payload: bytes): Promise<bytes> {
return peerId.privKey.sign(payload);
export async function signPayload (peerId: PeerId, payload: bytes): Promise<bytes> {
return Buffer.from(await peerId.privKey.sign(payload))
}
export async function getPeerIdFromPayload(payload: pb.INoiseHandshakePayload): Promise<PeerId> {
return await PeerId.createFromPubKey(Buffer.from(payload.identityKey as Uint8Array));
export async function getPeerIdFromPayload (payload: pb.INoiseHandshakePayload): Promise<PeerId> {
return await PeerId.createFromPubKey(Buffer.from(payload.identityKey as Uint8Array))
}
export async function decodePayload(payload: bytes|Uint8Array): Promise<pb.INoiseHandshakePayload> {
export function decodePayload (payload: bytes|Uint8Array): pb.INoiseHandshakePayload {
return NoiseHandshakePayloadProto.toObject(
NoiseHandshakePayloadProto.decode(Buffer.from(payload))
) as INoisePayload;
) as INoisePayload
}
export function getHandshakePayload(publicKey: bytes): bytes {
return Buffer.concat([Buffer.from("noise-libp2p-static-key:"), publicKey]);
export function getHandshakePayload (publicKey: bytes): bytes {
return Buffer.concat([Buffer.from('noise-libp2p-static-key:'), publicKey])
}
async function isValidPeerId(peerId: bytes, publicKeyProtobuf: bytes) {
const generatedPeerId = await PeerId.createFromPubKey(publicKeyProtobuf);
return generatedPeerId.id.equals(peerId);
async function isValidPeerId (peerId: Uint8Array, publicKeyProtobuf: bytes) {
const generatedPeerId = await PeerId.createFromPubKey(publicKeyProtobuf)
return uint8ArrayEquals(generatedPeerId.id, peerId)
}
/**
* Verifies signed payload, throws on any irregularities.
*
* @param {bytes} noiseStaticKey - owner's noise static key
* @param {bytes} payload - decoded payload
* @param {PeerId} remotePeer - owner's libp2p peer ID
* @returns {Promise<PeerId>} - peer ID of payload owner
*/
export async function verifySignedPayload(
export async function verifySignedPayload (
noiseStaticKey: bytes,
payload: pb.INoiseHandshakePayload,
remotePeer: PeerId
): Promise<PeerId> {
const identityKey = Buffer.from(payload.identityKey as Uint8Array);
const identityKey = Buffer.from(payload.identityKey as Uint8Array)
if (!(await isValidPeerId(remotePeer.id, identityKey))) {
throw new Error("Peer ID doesn't match libp2p public key.");
throw new Error("Peer ID doesn't match libp2p public key.")
}
const generatedPayload = getHandshakePayload(noiseStaticKey);
const generatedPayload = getHandshakePayload(noiseStaticKey)
// Unmarshaling from PublicKey protobuf
const publicKey = keys.unmarshalPublicKey(identityKey);
if (!publicKey.verify(generatedPayload, payload.identitySig as Buffer)) {
throw new Error("Static key doesn't match to peer that signed payload!");
const publicKey = keys.unmarshalPublicKey(identityKey)
// TODO remove this after libp2p-crypto ships proper types
// eslint-disable-next-line @typescript-eslint/no-misused-promises
if (!payload.identitySig || !publicKey.verify(generatedPayload, Buffer.from(payload.identitySig))) {
throw new Error("Static key doesn't match to peer that signed payload!")
}
return remotePeer;
return await PeerId.createFromPubKey(identityKey)
}
export function getHkdf(ck: bytes32, ikm: bytes): Hkdf {
const okm = hkdf(ikm, 96, {salt: ck, hash: 'SHA-256'});
export function getHkdf (ck: bytes32, ikm: bytes): Hkdf {
const info = Buffer.alloc(0)
const prk = HKDF.extract(SHA256, ikm, ck)
const okm = HKDF.expand(SHA256, prk, info, 96)
const k1 = okm.slice(0, 32);
const k2 = okm.slice(32, 64);
const k3 = okm.slice(64, 96);
const k1 = okm.slice(0, 32)
const k2 = okm.slice(32, 64)
const k3 = okm.slice(64, 96)
return [k1, k2, k3];
return [k1, k2, k3]
}
export function isValidPublicKey(pk: bytes): boolean {
if(pk.length !== 32 || pk.equals(Buffer.alloc(32))){
return false;
}
return true;
export function isValidPublicKey (pk: bytes): boolean {
return x25519.publicKeyVerify(pk.slice(0, 32))
}

2
test/fixtures/node-globals.js vendored Normal file
View File

@ -0,0 +1,2 @@
// @ts-nocheck
export const { Buffer } = require('buffer')

20
test/fixtures/peer.ts vendored
View File

@ -1,4 +1,4 @@
import PeerId from 'peer-id';
import PeerId from 'peer-id'
// ed25519 keys
const peers = [{
@ -17,20 +17,20 @@ const peers = [{
id: '12D3KooWPCofiCjhdtezP4eMnqBjjutFZNHjV39F5LWNrCvaLnzT',
privKey: 'CAESYLhUut01XPu+yIPbtZ3WnxOd26FYuTMRn/BbdFYsZE2KxueKRlo9yIAxmFReoNFUKztUU4G2aUiTbqDQaA6i0MDG54pGWj3IgDGYVF6g0VQrO1RTgbZpSJNuoNBoDqLQwA==',
pubKey: 'CAESIMbnikZaPciAMZhUXqDRVCs7VFOBtmlIk26g0GgOotDA'
}];
}]
export async function createPeerIdsFromFixtures (length) {
return Promise.all(
Array.from({ length }).map((_, i) => PeerId.createFromJSON(peers[i]))
export async function createPeerIdsFromFixtures (length: number): Promise<PeerId[]> {
return await Promise.all(
Array.from({ length }).map(async (_, i) => await PeerId.createFromJSON(peers[i]))
)
}
export async function createPeerIds (length) {
const peerIds: any[] = [];
export async function createPeerIds (length: number): Promise<PeerId[]> {
const peerIds: PeerId[] = []
for (let i = 0; i < length; i++) {
const id = await PeerId.create({ keyType: 'ed25519', bits: 256 });
peerIds.push(id);
const id = await PeerId.create({ keyType: 'Ed25519', bits: 256 })
peerIds.push(id)
}
return peerIds;
return peerIds
}

View File

@ -0,0 +1,65 @@
import { Buffer } from 'buffer'
import { IK } from '../../src/handshakes/ik'
import { KeyPair } from '../../src/@types/libp2p'
import { createHandshakePayload, generateKeypair, getHandshakePayload } from '../../src/utils'
import { assert, expect } from 'chai'
import { generateEd25519Keys } from '../utils'
describe('IK handshake', () => {
const prologue = Buffer.alloc(0)
it('Test complete IK handshake', async () => {
try {
const ikI = new IK()
const ikR = new IK()
// Generate static noise keys
const kpInitiator: KeyPair = await generateKeypair()
const kpResponder: KeyPair = await generateKeypair()
// Generate libp2p keys
const libp2pInitKeys = await generateEd25519Keys()
const libp2pRespKeys = await generateEd25519Keys()
// Create sessions
const initiatorSession = await ikI.initSession(true, prologue, kpInitiator, kpResponder.publicKey)
const responderSession = await ikR.initSession(false, prologue, kpResponder, Buffer.alloc(32))
/* Stage 0 */
// initiator creates payload
const initSignedPayload = await libp2pInitKeys.sign(getHandshakePayload(kpInitiator.publicKey))
libp2pInitKeys.marshal().slice(0, 32)
const libp2pInitPubKey = libp2pInitKeys.marshal().slice(32, 64)
const payloadInitEnc = await createHandshakePayload(libp2pInitPubKey, initSignedPayload)
// initiator sends message
const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc])
const messageBuffer = ikI.sendMessage(initiatorSession, message)
expect(messageBuffer.ne.length).not.equal(0)
// responder receives message
ikR.recvMessage(responderSession, messageBuffer)
/* Stage 1 */
// responder creates payload
libp2pRespKeys.marshal().slice(0, 32)
const libp2pRespPubKey = libp2pRespKeys.marshal().slice(32, 64)
const respSignedPayload = await libp2pRespKeys.sign(getHandshakePayload(kpResponder.publicKey))
const payloadRespEnc = await createHandshakePayload(libp2pRespPubKey, respSignedPayload)
const message1 = Buffer.concat([message, payloadRespEnc])
const messageBuffer2 = ikR.sendMessage(responderSession, message1)
// initiator receives message
ikI.recvMessage(initiatorSession, messageBuffer2)
assert(initiatorSession?.cs1?.k.equals(responderSession?.cs1?.k ?? new Uint8Array()))
assert(initiatorSession?.cs2?.k.equals(responderSession?.cs2?.k ?? new Uint8Array()))
} catch (e) {
return assert(false, e.message)
}
})
})

View File

@ -1,67 +0,0 @@
import {Buffer} from "buffer";
import {IK} from "../../src/handshakes/ik";
import {KeyPair} from "../../src/@types/libp2p";
import {createHandshakePayload, generateKeypair, getHandshakePayload} from "../../src/utils";
import {assert, expect} from "chai";
import {generateEd25519Keys} from "../utils";
describe("IK handshake", () => {
const prologue = Buffer.alloc(0);
it("Test complete IK handshake", async () => {
try {
const ikI = new IK();
const ikR = new IK();
// Generate static noise keys
const kpInitiator: KeyPair = await generateKeypair();
const kpResponder: KeyPair = await generateKeypair();
// Generate libp2p keys
const libp2pInitKeys = await generateEd25519Keys();
const libp2pRespKeys = await generateEd25519Keys();
// Create sessions
const initiatorSession = await ikI.initSession(true, prologue, kpInitiator, kpResponder.publicKey);
const responderSession = await ikR.initSession(false, prologue, kpResponder, Buffer.alloc(32));
/* Stage 0 */
// initiator creates payload
const initSignedPayload = await libp2pInitKeys.sign(getHandshakePayload(kpInitiator.publicKey));
const libp2pInitPrivKey = libp2pInitKeys.marshal().slice(0, 32);
const libp2pInitPubKey = libp2pInitKeys.marshal().slice(32, 64);
const payloadInitEnc = await createHandshakePayload(libp2pInitPubKey, initSignedPayload);
// initiator sends message
const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc]);
const messageBuffer = ikI.sendMessage(initiatorSession, message);
expect(messageBuffer.ne.length).not.equal(0);
// responder receives message
const plaintext = ikR.recvMessage(responderSession, messageBuffer);
/* Stage 1 */
// responder creates payload
const libp2pRespPrivKey = libp2pRespKeys.marshal().slice(0, 32);
const libp2pRespPubKey = libp2pRespKeys.marshal().slice(32, 64);
const respSignedPayload = await libp2pRespKeys.sign(getHandshakePayload(kpResponder.publicKey));
const payloadRespEnc = await createHandshakePayload(libp2pRespPubKey, respSignedPayload);
const message1 = Buffer.concat([message, payloadRespEnc]);
const messageBuffer2 = ikR.sendMessage(responderSession, message1);
// initiator receives message
const plaintext2 = ikI.recvMessage(initiatorSession, messageBuffer2);
assert(initiatorSession.cs1.k.equals(responderSession.cs1.k));
assert(initiatorSession.cs2.k.equals(responderSession.cs2.k));
} catch (e) {
console.error(e);
return assert(false, e.message);
}
});
});

143
test/handshakes/xx.spec.ts Normal file
View File

@ -0,0 +1,143 @@
import { expect, assert } from 'chai'
import { Buffer } from 'buffer'
import { XX } from '../../src/handshakes/xx'
import { KeyPair } from '../../src/@types/libp2p'
import { generateEd25519Keys } from '../utils'
import { createHandshakePayload, generateKeypair, getHandshakePayload, getHkdf } from '../../src/utils'
describe('XX Handshake', () => {
const prologue = Buffer.alloc(0)
it('Test creating new XX session', async () => {
try {
const xx = new XX()
const kpInitiator: KeyPair = await generateKeypair()
await generateKeypair()
await xx.initSession(true, prologue, kpInitiator)
} catch (e) {
assert(false, e.message)
}
})
it('Test get HKDF', () => {
const ckBytes = Buffer.from('4e6f6973655f58585f32353531395f58436861436861506f6c795f53484132353600000000000000000000000000000000000000000000000000000000000000', 'hex')
const ikm = Buffer.from('a3eae50ea37a47e8a7aa0c7cd8e16528670536dcd538cebfd724fb68ce44f1910ad898860666227d4e8dd50d22a9a64d1c0a6f47ace092510161e9e442953da3', 'hex')
const ck = Buffer.alloc(32)
ckBytes.copy(ck)
const [k1, k2, k3] = getHkdf(ck, ikm)
expect(k1.toString('hex')).to.equal('cc5659adff12714982f806e2477a8d5ddd071def4c29bb38777b7e37046f6914')
expect(k2.toString('hex')).to.equal('a16ada915e551ab623f38be674bb4ef15d428ae9d80688899c9ef9b62ef208fa')
expect(k3.toString('hex')).to.equal('ff67bf9727e31b06efc203907e6786667d2c7a74ac412b4d31a80ba3fd766f68')
})
async function doHandshake (xx) {
const kpInit = await generateKeypair()
const kpResp = await generateKeypair()
// initiator setup
const libp2pInitKeys = await generateEd25519Keys()
const initSignedPayload = await libp2pInitKeys.sign(getHandshakePayload(kpInit.publicKey))
// responder setup
const libp2pRespKeys = await generateEd25519Keys()
const respSignedPayload = await libp2pRespKeys.sign(getHandshakePayload(kpResp.publicKey))
// initiator: new XX noise session
const nsInit = xx.initSession(true, prologue, kpInit)
// responder: new XX noise session
const nsResp = xx.initSession(false, prologue, kpResp)
/* STAGE 0 */
// initiator creates payload
libp2pInitKeys.marshal().slice(0, 32)
const libp2pInitPubKey = libp2pInitKeys.marshal().slice(32, 64)
const payloadInitEnc = await createHandshakePayload(libp2pInitPubKey, initSignedPayload)
// initiator sends message
const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc])
const messageBuffer = xx.sendMessage(nsInit, message)
expect(messageBuffer.ne.length).not.equal(0)
// responder receives message
xx.recvMessage(nsResp, messageBuffer)
/* STAGE 1 */
// responder creates payload
libp2pRespKeys.marshal().slice(0, 32)
const libp2pRespPubKey = libp2pRespKeys.marshal().slice(32, 64)
const payloadRespEnc = await createHandshakePayload(libp2pRespPubKey, respSignedPayload)
const message1 = Buffer.concat([message, payloadRespEnc])
const messageBuffer2 = xx.sendMessage(nsResp, message1)
expect(messageBuffer2.ne.length).not.equal(0)
expect(messageBuffer2.ns.length).not.equal(0)
// initiator receive payload
xx.recvMessage(nsInit, messageBuffer2)
/* STAGE 2 */
// initiator send message
const messageBuffer3 = xx.sendMessage(nsInit, Buffer.alloc(0))
// responder receive message
xx.recvMessage(nsResp, messageBuffer3)
assert(nsInit.cs1.k.equals(nsResp.cs1.k))
assert(nsInit.cs2.k.equals(nsResp.cs2.k))
return { nsInit, nsResp }
}
it('Test handshake', async () => {
try {
const xx = new XX()
await doHandshake(xx)
} catch (e) {
assert(false, e.message)
}
})
it('Test symmetric encrypt and decrypt', async () => {
try {
const xx = new XX()
const { nsInit, nsResp } = await doHandshake(xx)
const ad = Buffer.from('authenticated')
const message = Buffer.from('HelloCrypto')
const ciphertext = xx.encryptWithAd(nsInit.cs1, ad, message)
assert(!Buffer.from('HelloCrypto').equals(ciphertext), 'Encrypted message should not be same as plaintext.')
const { plaintext: decrypted, valid } = xx.decryptWithAd(nsResp.cs1, ad, ciphertext)
assert(Buffer.from('HelloCrypto').equals(decrypted), 'Decrypted text not equal to original message.')
assert(valid)
} catch (e) {
assert(false, e.message)
}
})
it('Test multiple messages encryption and decryption', async () => {
const xx = new XX()
const { nsInit, nsResp } = await doHandshake(xx)
const ad = Buffer.from('authenticated')
const message = Buffer.from('ethereum1')
const encrypted = xx.encryptWithAd(nsInit.cs1, ad, message)
const { plaintext: decrypted } = xx.decryptWithAd(nsResp.cs1, ad, encrypted)
assert.equal('ethereum1', decrypted.toString('utf8'), 'Decrypted text not equal to original message.')
const message2 = Buffer.from('ethereum2')
const encrypted2 = xx.encryptWithAd(nsInit.cs1, ad, message2)
const { plaintext: decrypted2 } = xx.decryptWithAd(nsResp.cs1, ad, encrypted2)
assert.equal('ethereum2', decrypted2.toString('utf-8'), 'Decrypted text not equal to original message.')
})
})

View File

@ -1,143 +0,0 @@
import { expect, assert } from "chai";
import { Buffer } from 'buffer';
import { XX } from "../../src/handshakes/xx";
import { KeyPair } from "../../src/@types/libp2p";
import { generateEd25519Keys } from "../utils";
import {createHandshakePayload, generateKeypair, getHandshakePayload, getHkdf} from "../../src/utils";
describe("XX Handshake", () => {
const prologue = Buffer.alloc(0);
it("Test creating new XX session", async () => {
try {
const xx = new XX();
const kpInitiator: KeyPair = await generateKeypair();
const kpResponder: KeyPair = await generateKeypair();
const session = await xx.initSession(true, prologue, kpInitiator);
} catch (e) {
assert(false, e.message);
}
});
it("Test get HKDF", async () => {
const ckBytes = Buffer.from('4e6f6973655f58585f32353531395f58436861436861506f6c795f53484132353600000000000000000000000000000000000000000000000000000000000000', 'hex');
const ikm = Buffer.from('a3eae50ea37a47e8a7aa0c7cd8e16528670536dcd538cebfd724fb68ce44f1910ad898860666227d4e8dd50d22a9a64d1c0a6f47ace092510161e9e442953da3', 'hex');
const ck = Buffer.alloc(32);
ckBytes.copy(ck);
const [k1, k2, k3] = getHkdf(ck, ikm);
expect(k1.toString('hex')).to.equal('cc5659adff12714982f806e2477a8d5ddd071def4c29bb38777b7e37046f6914');
expect(k2.toString('hex')).to.equal('a16ada915e551ab623f38be674bb4ef15d428ae9d80688899c9ef9b62ef208fa');
expect(k3.toString('hex')).to.equal('ff67bf9727e31b06efc203907e6786667d2c7a74ac412b4d31a80ba3fd766f68');
});
async function doHandshake(xx) {
const kpInit = await generateKeypair();
const kpResp = await generateKeypair();
// initiator setup
const libp2pInitKeys = await generateEd25519Keys();
const initSignedPayload = await libp2pInitKeys.sign(getHandshakePayload(kpInit.publicKey));
// responder setup
const libp2pRespKeys = await generateEd25519Keys();
const respSignedPayload = await libp2pRespKeys.sign(getHandshakePayload(kpResp.publicKey));
// initiator: new XX noise session
const nsInit = xx.initSession(true, prologue, kpInit);
// responder: new XX noise session
const nsResp = xx.initSession(false, prologue, kpResp);
/* STAGE 0 */
// initiator creates payload
const libp2pInitPrivKey = libp2pInitKeys.marshal().slice(0, 32);
const libp2pInitPubKey = libp2pInitKeys.marshal().slice(32, 64);
const payloadInitEnc = await createHandshakePayload(libp2pInitPubKey, initSignedPayload);
// initiator sends message
const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc]);
const messageBuffer = xx.sendMessage(nsInit, message);
expect(messageBuffer.ne.length).not.equal(0);
// responder receives message
xx.recvMessage(nsResp, messageBuffer);
/* STAGE 1 */
// responder creates payload
const libp2pRespPrivKey = libp2pRespKeys.marshal().slice(0, 32);
const libp2pRespPubKey = libp2pRespKeys.marshal().slice(32, 64);
const payloadRespEnc = await createHandshakePayload(libp2pRespPubKey, respSignedPayload);
const message1 = Buffer.concat([message, payloadRespEnc]);
const messageBuffer2 = xx.sendMessage(nsResp, message1);
expect(messageBuffer2.ne.length).not.equal(0);
expect(messageBuffer2.ns.length).not.equal(0);
// initiator receive payload
xx.recvMessage(nsInit, messageBuffer2);
/* STAGE 2 */
// initiator send message
const messageBuffer3 = xx.sendMessage(nsInit, Buffer.alloc(0));
// responder receive message
xx.recvMessage(nsResp, messageBuffer3);
assert(nsInit.cs1.k.equals(nsResp.cs1.k));
assert(nsInit.cs2.k.equals(nsResp.cs2.k));
return { nsInit, nsResp };
}
it("Test handshake", async () => {
try {
const xx = new XX();
await doHandshake(xx);
} catch (e) {
assert(false, e.message);
}
});
it("Test symmetric encrypt and decrypt", async () => {
try {
const xx = new XX();
const { nsInit, nsResp } = await doHandshake(xx);
const ad = Buffer.from("authenticated");
const message = Buffer.from("HelloCrypto");
const ciphertext = xx.encryptWithAd(nsInit.cs1, ad, message);
assert(!Buffer.from("HelloCrypto").equals(ciphertext), "Encrypted message should not be same as plaintext.");
const {plaintext: decrypted, valid} = xx.decryptWithAd(nsResp.cs1, ad, ciphertext);
assert(Buffer.from("HelloCrypto").equals(decrypted), "Decrypted text not equal to original message.");
assert(valid);
} catch (e) {
assert(false, e.message);
}
});
it("Test multiple messages encryption and decryption", async () => {
const xx = new XX();
const { nsInit, nsResp } = await doHandshake(xx);
const ad = Buffer.from("authenticated");
const message = Buffer.from("ethereum1");
const encrypted = xx.encryptWithAd(nsInit.cs1, ad, message);
const {plaintext: decrypted} = xx.decryptWithAd(nsResp.cs1, ad, encrypted);
assert.equal("ethereum1", decrypted.toString("utf8"), "Decrypted text not equal to original message.");
const message2 = Buffer.from("ethereum2");
const encrypted2 = xx.encryptWithAd(nsInit.cs1, ad, message2);
const {plaintext: decrypted2} = xx.decryptWithAd(nsResp.cs1, ad, encrypted2);
assert.equal("ethereum2", decrypted2.toString("utf-8"), "Decrypted text not equal to original message.");
});
});

79
test/ik-handshake.spec.ts Normal file
View File

@ -0,0 +1,79 @@
import Wrap from 'it-pb-rpc'
import Duplex from 'it-pair/duplex'
import { Buffer } from 'buffer'
import { assert, expect } from 'chai'
import { createPeerIdsFromFixtures } from './fixtures/peer'
import { generateKeypair, getPayload } from '../src/utils'
import { IKHandshake } from '../src/handshake-ik'
describe('IK Handshake', () => {
let peerA, peerB
before(async () => {
[peerA, peerB] = await createPeerIdsFromFixtures(3)
})
it('should finish both stages as initiator and responder', async () => {
try {
const duplex = Duplex()
const connectionFrom = Wrap(duplex[0])
const connectionTo = Wrap(duplex[1])
const prologue = Buffer.alloc(0)
const staticKeysInitiator = generateKeypair()
const staticKeysResponder = generateKeypair()
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey)
const handshakeInit = new IKHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, staticKeysResponder.publicKey, peerB)
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
const handshakeResp = new IKHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, staticKeysInitiator.publicKey)
await handshakeInit.stage0()
await handshakeResp.stage0()
await handshakeResp.stage1()
await handshakeInit.stage1()
// Test shared key
if (handshakeInit.session.cs1 && handshakeResp.session.cs1 && handshakeInit.session.cs2 && handshakeResp.session.cs2) {
assert(handshakeInit.session.cs1.k.equals(handshakeResp.session.cs1.k))
assert(handshakeInit.session.cs2.k.equals(handshakeResp.session.cs2.k))
} else {
assert(false)
}
// Test encryption and decryption
const encrypted = handshakeInit.encrypt(Buffer.from('encryptthis'), handshakeInit.session)
const { plaintext: decrypted } = handshakeResp.decrypt(encrypted, handshakeResp.session)
assert(decrypted.equals(Buffer.from('encryptthis')))
} catch (e) {
assert(false, e.message)
}
})
it("should throw error if responder's static key changed", async () => {
try {
const duplex = Duplex()
const connectionFrom = Wrap(duplex[0])
const connectionTo = Wrap(duplex[1])
const prologue = Buffer.alloc(0)
const staticKeysInitiator = generateKeypair()
const staticKeysResponder = generateKeypair()
const oldScammyKeys = generateKeypair()
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey)
const handshakeInit = new IKHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, oldScammyKeys.publicKey, peerB)
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
const handshakeResp = new IKHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, staticKeysInitiator.publicKey)
await handshakeInit.stage0()
await handshakeResp.stage0()
} catch (e) {
expect(e.message).to.include("Error occurred while verifying initiator's signed payload")
}
})
})

View File

@ -1,80 +0,0 @@
import Wrap from "it-pb-rpc";
import Duplex from 'it-pair/duplex';
import {Buffer} from "buffer";
import {assert, expect} from "chai";
import {createPeerIdsFromFixtures} from "./fixtures/peer";
import {generateKeypair, getPayload} from "../src/utils";
import {IKHandshake} from "../src/handshake-ik";
describe("IK Handshake", () => {
let peerA, peerB, fakePeer;
before(async () => {
[peerA, peerB, fakePeer] = await createPeerIdsFromFixtures(3);
});
it("should finish both stages as initiator and responder", async() => {
try {
const duplex = Duplex();
const connectionFrom = Wrap(duplex[0]);
const connectionTo = Wrap(duplex[1]);
const prologue = Buffer.alloc(0);
const staticKeysInitiator = generateKeypair();
const staticKeysResponder = generateKeypair();
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey);
const handshakeInit = new IKHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, staticKeysResponder.publicKey, peerB);
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey);
const handshakeResp = new IKHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, staticKeysInitiator.publicKey);
await handshakeInit.stage0();
await handshakeResp.stage0();
await handshakeResp.stage1();
await handshakeInit.stage1();
// Test shared key
if (handshakeInit.session.cs1 && handshakeResp.session.cs1 && handshakeInit.session.cs2 && handshakeResp.session.cs2) {
assert(handshakeInit.session.cs1.k.equals(handshakeResp.session.cs1.k));
assert(handshakeInit.session.cs2.k.equals(handshakeResp.session.cs2.k));
} else {
assert(false);
}
// Test encryption and decryption
const encrypted = handshakeInit.encrypt(Buffer.from("encryptthis"), handshakeInit.session);
const {plaintext: decrypted} = handshakeResp.decrypt(encrypted, handshakeResp.session);
assert(decrypted.equals(Buffer.from("encryptthis")));
} catch (e) {
console.error(e);
assert(false, e.message);
}
});
it("should throw error if responder's static key changed", async() => {
try {
const duplex = Duplex();
const connectionFrom = Wrap(duplex[0]);
const connectionTo = Wrap(duplex[1]);
const prologue = Buffer.alloc(0);
const staticKeysInitiator = generateKeypair();
const staticKeysResponder = generateKeypair();
const oldScammyKeys = generateKeypair();
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey);
const handshakeInit = new IKHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, oldScammyKeys.publicKey, peerB);
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey);
const handshakeResp = new IKHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, staticKeysInitiator.publicKey);
await handshakeInit.stage0();
await handshakeResp.stage0();
} catch (e) {
expect(e.message).to.include("Error occurred while verifying initiator's signed payload");
}
});
});

11
test/index.spec.ts Normal file
View File

@ -0,0 +1,11 @@
import { expect } from 'chai'
import { Noise } from '../src'
describe('Index', () => {
it('should expose class with tag and required functions', () => {
const noise = new Noise()
expect(noise.protocol).to.equal('/noise')
expect(typeof (noise.secureInbound)).to.equal('function')
expect(typeof (noise.secureOutbound)).to.equal('function')
})
})

View File

@ -1,11 +0,0 @@
import { expect } from "chai";
import { Noise } from "../src";
describe("Index", () => {
it("should expose class with tag and required functions", () => {
const noise = new Noise();
expect(noise.protocol).to.equal('/noise');
expect(typeof(noise.secureInbound)).to.equal('function');
expect(typeof(noise.secureOutbound)).to.equal('function');
})
});

35
test/keycache.spec.ts Normal file
View File

@ -0,0 +1,35 @@
import { assert } from 'chai'
import { KeyCache } from '../src/keycache'
import { createPeerIds, createPeerIdsFromFixtures } from './fixtures/peer'
import uint8ArrayEquals from 'uint8arrays/equals'
describe('KeyCache', () => {
let peerA
before(async () => {
[peerA] = await createPeerIdsFromFixtures(2)
})
it('should store and load same key successfully', async () => {
try {
const key = Buffer.from('this is id 007')
await KeyCache.store(peerA, key)
const result = await KeyCache.load(peerA)
assert(result !== null && uint8ArrayEquals(result, key), 'Stored and loaded key are not the same')
} catch (e) {
const err = e as Error
assert(false, `Test failed - ${err.message}`)
}
})
it('should return undefined if key not found', async () => {
try {
const [newPeer] = await createPeerIds(1)
const result = await KeyCache.load(newPeer)
assert(result === null)
} catch (e) {
const err = e as Error
assert(false, `Test failed - ${err.message}`)
}
})
})

View File

@ -1,34 +0,0 @@
import { expect, assert } from "chai";
import { KeyCache } from "../src/keycache";
import {createPeerIds, createPeerIdsFromFixtures} from "./fixtures/peer";
describe("KeyCache", () => {
let peerA, peerB;
before(async () => {
[peerA, peerB] = await createPeerIdsFromFixtures(2);
});
it("should store and load same key successfully", async() => {
try {
const key = Buffer.from("this is id 007");
await KeyCache.store(peerA, key);
const result = await KeyCache.load(peerA);
assert(result.equals(key), "Stored and loaded key are not the same");
} catch (e) {
console.error(e);
assert(false, `Test failed - ${e.message}`)
}
});
it("should return undefined if key not found", async() => {
try {
const [newPeer] = await createPeerIds(1);
const result = await KeyCache.load(newPeer);
assert(!result);
} catch (e) {
console.error(e);
assert(false, `Test failed - ${e.message}`)
}
});
});

367
test/noise.spec.ts Normal file
View File

@ -0,0 +1,367 @@
import { assert, expect } from 'chai'
import DuplexPair from 'it-pair/duplex'
import { createPeerIdsFromFixtures } from './fixtures/peer'
import Wrap from 'it-pb-rpc'
import sinon from 'sinon'
import BufferList from 'bl'
import { randomBytes } from 'libp2p-crypto'
import { Buffer } from 'buffer'
import uint8ArrayEquals from 'uint8arrays/equals'
import { Noise } from '../src'
import { XXHandshake } from '../src/handshake-xx'
import { createHandshakePayload, generateKeypair, getHandshakePayload, getPayload, signPayload } from '../src/utils'
import { decode0, decode2, encode1, uint16BEDecode, uint16BEEncode } from '../src/encoder'
import { XX } from '../src/handshakes/xx'
import { getKeyPairFromPeerId } from './utils'
import { KeyCache } from '../src/keycache'
import { NOISE_MSG_MAX_LENGTH_BYTES } from '../src/constants'
describe('Noise', () => {
let remotePeer, localPeer
const sandbox = sinon.createSandbox()
before(async () => {
[localPeer, remotePeer] = await createPeerIdsFromFixtures(2)
})
afterEach(function () {
sandbox.restore()
})
it('should communicate through encrypted streams without noise pipes', async () => {
try {
const noiseInit = new Noise(undefined, undefined)
const noiseResp = new Noise(undefined, undefined)
const [inboundConnection, outboundConnection] = DuplexPair()
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
])
const wrappedInbound = Wrap(inbound.conn)
const wrappedOutbound = Wrap(outbound.conn)
wrappedOutbound.writeLP(Buffer.from('test'))
const response = await wrappedInbound.readLP()
expect(response.toString()).equal('test')
} catch (e) {
assert(false, e.message)
}
})
it('should test that secureOutbound is spec compliant', async () => {
const noiseInit = new Noise(undefined, undefined)
const [inboundConnection, outboundConnection] = DuplexPair()
const [outbound, { wrapped, handshake }] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
(async () => {
const wrapped = Wrap(
inboundConnection,
{
lengthEncoder: uint16BEEncode,
lengthDecoder: uint16BEDecode,
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
}
)
const prologue = Buffer.alloc(0)
const staticKeys = generateKeypair()
const xx = new XX()
const payload = await getPayload(remotePeer, staticKeys.publicKey)
const handshake = new XXHandshake(false, payload, prologue, staticKeys, wrapped, localPeer, xx)
let receivedMessageBuffer = decode0((await wrapped.readLP()).slice())
// The first handshake message contains the initiator's ephemeral public key
expect(receivedMessageBuffer.ne.length).equal(32)
xx.recvMessage(handshake.session, receivedMessageBuffer)
// Stage 1
const { publicKey: libp2pPubKey } = getKeyPairFromPeerId(remotePeer)
const signedPayload = await signPayload(remotePeer, getHandshakePayload(staticKeys.publicKey))
const handshakePayload = await createHandshakePayload(libp2pPubKey, signedPayload)
const messageBuffer = xx.sendMessage(handshake.session, handshakePayload)
wrapped.writeLP(encode1(messageBuffer))
// Stage 2 - finish handshake
receivedMessageBuffer = decode2((await wrapped.readLP()).slice())
xx.recvMessage(handshake.session, receivedMessageBuffer)
return { wrapped, handshake }
})()
])
try {
const wrappedOutbound = Wrap(outbound.conn)
wrappedOutbound.write(new BufferList([Buffer.from('test')]))
// Check that noise message is prefixed with 16-bit big-endian unsigned integer
const receivedEncryptedPayload = (await wrapped.read()).slice()
const dataLength = receivedEncryptedPayload.readInt16BE(0)
const data = receivedEncryptedPayload.slice(2, dataLength + 2)
const { plaintext: decrypted, valid } = handshake.decrypt(data, handshake.session)
// Decrypted data should match
assert(decrypted.equals(Buffer.from('test')))
assert(valid)
} catch (e) {
assert(false, e.message)
}
})
it('should test large payloads', async function () {
this.timeout(10000)
try {
const noiseInit = new Noise(undefined, undefined)
const noiseResp = new Noise(undefined, undefined)
const [inboundConnection, outboundConnection] = DuplexPair()
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
])
const wrappedInbound = Wrap(inbound.conn)
const wrappedOutbound = Wrap(outbound.conn)
const largePlaintext = randomBytes(100000)
wrappedOutbound.writeLP(Buffer.from(largePlaintext))
const response = await wrappedInbound.read(100000)
expect(response.length).equals(largePlaintext.length)
} catch (e) {
assert(false, e.message)
}
})
it.skip('should communicate through encrypted streams with noise pipes', async () => {
try {
const staticKeysInitiator = generateKeypair()
const noiseInit = new Noise(staticKeysInitiator.privateKey)
const staticKeysResponder = generateKeypair()
const noiseResp = new Noise(staticKeysResponder.privateKey)
// Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.publicKey)
KeyCache.store(remotePeer, staticKeysResponder.publicKey)
// @ts-expect-error
const xxSpy = sandbox.spy(noiseInit, 'performXXHandshake')
// @ts-expect-error
const xxFallbackSpy = sandbox.spy(noiseInit, 'performXXFallbackHandshake')
const [inboundConnection, outboundConnection] = DuplexPair()
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
])
const wrappedInbound = Wrap(inbound.conn)
const wrappedOutbound = Wrap(outbound.conn)
wrappedOutbound.writeLP(Buffer.from('test v2'))
const response = await wrappedInbound.readLP()
expect(response.toString()).equal('test v2')
assert(xxSpy.notCalled)
assert(xxFallbackSpy.notCalled)
} catch (e) {
assert(false, e.message)
}
})
it.skip('IK -> XX fallback: initiator has invalid remote static key', async () => {
try {
const staticKeysInitiator = generateKeypair()
const noiseInit = new Noise(staticKeysInitiator.privateKey)
const noiseResp = new Noise()
// @ts-expect-error
const xxSpy = sandbox.spy(noiseInit, 'performXXFallbackHandshake')
// Prepare key cache for noise pipes
KeyCache.resetStorage()
KeyCache.store(localPeer, staticKeysInitiator.publicKey)
KeyCache.store(remotePeer, generateKeypair().publicKey)
const [inboundConnection, outboundConnection] = DuplexPair()
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
])
const wrappedInbound = Wrap(inbound.conn)
const wrappedOutbound = Wrap(outbound.conn)
wrappedOutbound.writeLP(Buffer.from('test fallback'))
const response = await wrappedInbound.readLP()
expect(response.toString()).equal('test fallback')
assert(xxSpy.calledOnce, 'XX Fallback method was never called.')
} catch (e) {
assert(false, e.message)
}
})
// this didn't work before but we didn't verify decryption
it.skip('IK -> XX fallback: responder has disabled noise pipes', async () => {
try {
const staticKeysInitiator = generateKeypair()
const noiseInit = new Noise(staticKeysInitiator.privateKey)
const staticKeysResponder = generateKeypair()
const noiseResp = new Noise(staticKeysResponder.privateKey, undefined)
// @ts-expect-error
const xxSpy = sandbox.spy(noiseInit, 'performXXFallbackHandshake')
// Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.publicKey)
KeyCache.store(remotePeer, staticKeysResponder.publicKey)
const [inboundConnection, outboundConnection] = DuplexPair()
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
])
const wrappedInbound = Wrap(inbound.conn)
const wrappedOutbound = Wrap(outbound.conn)
wrappedOutbound.writeLP(Buffer.from('test fallback'))
const response = await wrappedInbound.readLP()
expect(response.toString()).equal('test fallback')
assert(xxSpy.calledOnce, 'XX Fallback method was never called.')
} catch (e) {
assert(false, e.message)
}
})
it.skip('Initiator starts with XX (pipes disabled), responder has enabled noise pipes', async () => {
try {
const staticKeysInitiator = generateKeypair()
const noiseInit = new Noise(staticKeysInitiator.privateKey, undefined)
const staticKeysResponder = generateKeypair()
const noiseResp = new Noise(staticKeysResponder.privateKey)
// @ts-expect-error
const xxInitSpy = sandbox.spy(noiseInit, 'performXXHandshake')
// @ts-expect-error
const xxRespSpy = sandbox.spy(noiseResp, 'performXXFallbackHandshake')
// Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.publicKey)
const [inboundConnection, outboundConnection] = DuplexPair()
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
])
const wrappedInbound = Wrap(inbound.conn)
const wrappedOutbound = Wrap(outbound.conn)
wrappedOutbound.writeLP(Buffer.from('test fallback'))
const response = await wrappedInbound.readLP()
expect(response.toString()).equal('test fallback')
assert(xxInitSpy.calledOnce, 'XX method was never called.')
assert(xxRespSpy.calledOnce, 'XX Fallback method was never called.')
} catch (e) {
assert(false, e.message)
}
})
it.skip('IK: responder has no remote static key', async () => {
try {
const staticKeysInitiator = generateKeypair()
const noiseInit = new Noise(staticKeysInitiator.privateKey)
const staticKeysResponder = generateKeypair()
const noiseResp = new Noise(staticKeysResponder.privateKey)
// @ts-expect-error
const ikInitSpy = sandbox.spy(noiseInit, 'performIKHandshake')
// @ts-expect-error
const xxFallbackInitSpy = sandbox.spy(noiseInit, 'performXXFallbackHandshake')
// @ts-expect-error
const ikRespSpy = sandbox.spy(noiseResp, 'performIKHandshake')
// Prepare key cache for noise pipes
KeyCache.resetStorage()
KeyCache.store(remotePeer, staticKeysResponder.publicKey)
const [inboundConnection, outboundConnection] = DuplexPair()
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
])
const wrappedInbound = Wrap(inbound.conn)
const wrappedOutbound = Wrap(outbound.conn)
wrappedOutbound.writeLP(Buffer.from('test fallback'))
const response = await wrappedInbound.readLP()
expect(response.toString()).equal('test fallback')
assert(ikInitSpy.calledOnce, 'IK handshake was not called.')
assert(ikRespSpy.calledOnce, 'IK handshake was not called.')
assert(xxFallbackInitSpy.notCalled, 'XX Fallback method was called.')
} catch (e) {
assert(false, e.message)
}
})
it('should working without remote peer provided in incoming connection', async () => {
try {
const staticKeysInitiator = generateKeypair()
const noiseInit = new Noise(staticKeysInitiator.privateKey)
const staticKeysResponder = generateKeypair()
const noiseResp = new Noise(staticKeysResponder.privateKey)
// Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.publicKey)
KeyCache.store(remotePeer, staticKeysResponder.publicKey)
const [inboundConnection, outboundConnection] = DuplexPair()
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection)
])
const wrappedInbound = Wrap(inbound.conn)
const wrappedOutbound = Wrap(outbound.conn)
wrappedOutbound.writeLP(Buffer.from('test v2'))
const response = await wrappedInbound.readLP()
expect(response.toString()).equal('test v2')
assert(uint8ArrayEquals(inbound.remotePeer.marshalPubKey(), localPeer.marshalPubKey()))
assert(uint8ArrayEquals(outbound.remotePeer.marshalPubKey(), remotePeer.marshalPubKey()))
} catch (e) {
assert(false, e.message)
}
})
it('should accept and return early data from remote peer', async () => {
try {
const localPeerEarlyData = Buffer.from('early data')
const staticKeysInitiator = generateKeypair()
const noiseInit = new Noise(staticKeysInitiator.privateKey, localPeerEarlyData)
const staticKeysResponder = generateKeypair()
const noiseResp = new Noise(staticKeysResponder.privateKey)
// Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.publicKey)
KeyCache.store(remotePeer, staticKeysResponder.publicKey)
const [inboundConnection, outboundConnection] = DuplexPair()
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection)
])
assert(inbound.remoteEarlyData.equals(localPeerEarlyData))
assert(outbound.remoteEarlyData.equals(Buffer.alloc(0)))
} catch (e) {
assert(false, e.message)
}
})
})

View File

@ -1,365 +0,0 @@
import {assert, expect} from "chai";
import DuplexPair from 'it-pair/duplex';
import {Noise} from "../src";
import {createPeerIdsFromFixtures} from "./fixtures/peer";
import Wrap from "it-pb-rpc";
import sinon from "sinon";
import {randomBytes} from 'libp2p-crypto';
import {XXHandshake} from "../src/handshake-xx";
import {createHandshakePayload, generateKeypair, getHandshakePayload, getPayload, signPayload} from "../src/utils";
import {decode0, decode2, encode1, uint16BEDecode, uint16BEEncode} from "../src/encoder";
import {XX} from "../src/handshakes/xx";
import {Buffer} from "buffer";
import {getKeyPairFromPeerId} from "./utils";
import {KeyCache} from "../src/keycache";
import {NOISE_MSG_MAX_LENGTH_BYTES} from "../src/constants";
import BufferList from "bl";
describe("Noise", () => {
let remotePeer, localPeer;
let sandbox = sinon.createSandbox();
before(async () => {
[localPeer, remotePeer] = await createPeerIdsFromFixtures(2);
});
afterEach(function() {
sandbox.restore();
});
it("should communicate through encrypted streams without noise pipes", async() => {
try {
const noiseInit = new Noise(undefined, undefined, false);
const noiseResp = new Noise(undefined, undefined, false);
const [inboundConnection, outboundConnection] = DuplexPair();
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
]);
const wrappedInbound = Wrap(inbound.conn);
const wrappedOutbound = Wrap(outbound.conn);
wrappedOutbound.writeLP(Buffer.from("test"));
const response = await wrappedInbound.readLP();
expect(response.toString()).equal("test");
} catch (e) {
assert(false, e.message);
}
});
it("should test that secureOutbound is spec compliant", async() => {
const noiseInit = new Noise(undefined, undefined, false);
const [inboundConnection, outboundConnection] = DuplexPair();
const [outbound, { wrapped, handshake }] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
(async () => {
const wrapped = Wrap(
inboundConnection,
{
lengthEncoder: uint16BEEncode,
lengthDecoder: uint16BEDecode,
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
}
);
const prologue = Buffer.alloc(0);
const staticKeys = generateKeypair();
const xx = new XX();
const payload = await getPayload(remotePeer, staticKeys.publicKey);
const handshake = new XXHandshake(false, payload, prologue, staticKeys, wrapped, localPeer, xx);
let receivedMessageBuffer = decode0((await wrapped.readLP()).slice());
// The first handshake message contains the initiator's ephemeral public key
expect(receivedMessageBuffer.ne.length).equal(32);
xx.recvMessage(handshake.session, receivedMessageBuffer);
// Stage 1
const { publicKey: libp2pPubKey } = getKeyPairFromPeerId(remotePeer);
const signedPayload = await signPayload(remotePeer, getHandshakePayload(staticKeys.publicKey));
const handshakePayload = await createHandshakePayload(libp2pPubKey, signedPayload);
const messageBuffer = xx.sendMessage(handshake.session, handshakePayload);
wrapped.writeLP(encode1(messageBuffer));
// Stage 2 - finish handshake
receivedMessageBuffer = decode2((await wrapped.readLP()).slice());
xx.recvMessage(handshake.session, receivedMessageBuffer);
return {wrapped, handshake};
})(),
]);
try {
const wrappedOutbound = Wrap(outbound.conn);
wrappedOutbound.write(new BufferList([Buffer.from("test")]));
// Check that noise message is prefixed with 16-bit big-endian unsigned integer
const receivedEncryptedPayload = (await wrapped.read()).slice();
const dataLength = receivedEncryptedPayload.readInt16BE(0);
const data = receivedEncryptedPayload.slice(2, dataLength + 2);
const {plaintext: decrypted, valid} = handshake.decrypt(data, handshake.session);
// Decrypted data should match
assert(decrypted.equals(Buffer.from("test")));
assert(valid);
} catch (e) {
assert(false, e.message);
}
});
it("should test large payloads", async function() {
this.timeout(10000);
try {
const noiseInit = new Noise(undefined, undefined, false);
const noiseResp = new Noise(undefined, undefined, false);
const [inboundConnection, outboundConnection] = DuplexPair();
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
]);
const wrappedInbound = Wrap(inbound.conn);
const wrappedOutbound = Wrap(outbound.conn);
const largePlaintext = randomBytes(100000);
wrappedOutbound.writeLP(largePlaintext);
const response = await wrappedInbound.read(100000);
expect(response.length).equals(largePlaintext.length);
} catch (e) {
console.log(e);
assert(false, e.message);
}
});
it.skip("should communicate through encrypted streams with noise pipes", async() => {
try {
const staticKeysInitiator = generateKeypair();
const noiseInit = new Noise(staticKeysInitiator.privateKey);
const staticKeysResponder = generateKeypair();
const noiseResp = new Noise(staticKeysResponder.privateKey);
// Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.publicKey);
KeyCache.store(remotePeer, staticKeysResponder.publicKey);
const xxSpy = sandbox.spy(noiseInit, "performXXHandshake");
const xxFallbackSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake");
const [inboundConnection, outboundConnection] = DuplexPair();
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
]);
const wrappedInbound = Wrap(inbound.conn);
const wrappedOutbound = Wrap(outbound.conn);
wrappedOutbound.writeLP(Buffer.from("test v2"));
const response = await wrappedInbound.readLP();
expect(response.toString()).equal("test v2");
assert(xxSpy.notCalled);
assert(xxFallbackSpy.notCalled);
} catch (e) {
console.error(e);
assert(false, e.message);
}
});
it.skip("IK -> XX fallback: initiator has invalid remote static key", async() => {
try {
const staticKeysInitiator = generateKeypair();
const noiseInit = new Noise(staticKeysInitiator.privateKey);
const noiseResp = new Noise();
const xxSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake");
// Prepare key cache for noise pipes
KeyCache.resetStorage();
KeyCache.store(localPeer, staticKeysInitiator.publicKey);
KeyCache.store(remotePeer, generateKeypair().publicKey);
const [inboundConnection, outboundConnection] = DuplexPair();
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
]);
const wrappedInbound = Wrap(inbound.conn);
const wrappedOutbound = Wrap(outbound.conn);
wrappedOutbound.writeLP(Buffer.from("test fallback"));
const response = await wrappedInbound.readLP();
expect(response.toString()).equal("test fallback");
assert(xxSpy.calledOnce, "XX Fallback method was never called.");
} catch (e) {
console.error(e);
assert(false, e.message);
}
});
//this didn't work before but we didn't verify decryption
it.skip("IK -> XX fallback: responder has disabled noise pipes", async() => {
try {
const staticKeysInitiator = generateKeypair();
const noiseInit = new Noise(staticKeysInitiator.privateKey);
const staticKeysResponder = generateKeypair();
const noiseResp = new Noise(staticKeysResponder.privateKey, undefined, false);
const xxSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake");
// Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.publicKey);
KeyCache.store(remotePeer, staticKeysResponder.publicKey);
const [inboundConnection, outboundConnection] = DuplexPair();
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
]);
const wrappedInbound = Wrap(inbound.conn);
const wrappedOutbound = Wrap(outbound.conn);
wrappedOutbound.writeLP(Buffer.from("test fallback"));
const response = await wrappedInbound.readLP();
expect(response.toString()).equal("test fallback");
assert(xxSpy.calledOnce, "XX Fallback method was never called.");
} catch (e) {
console.error(e);
assert(false, e.message);
}
});
it.skip("Initiator starts with XX (pipes disabled), responder has enabled noise pipes", async() => {
try {
const staticKeysInitiator = generateKeypair();
const noiseInit = new Noise(staticKeysInitiator.privateKey, undefined, false);
const staticKeysResponder = generateKeypair();
const noiseResp = new Noise(staticKeysResponder.privateKey);
const xxInitSpy = sandbox.spy(noiseInit, "performXXHandshake");
const xxRespSpy = sandbox.spy(noiseResp, "performXXFallbackHandshake");
// Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.publicKey);
const [inboundConnection, outboundConnection] = DuplexPair();
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
]);
const wrappedInbound = Wrap(inbound.conn);
const wrappedOutbound = Wrap(outbound.conn);
wrappedOutbound.writeLP(Buffer.from("test fallback"));
const response = await wrappedInbound.readLP();
expect(response.toString()).equal("test fallback");
assert(xxInitSpy.calledOnce, "XX method was never called.");
assert(xxRespSpy.calledOnce, "XX Fallback method was never called.");
} catch (e) {
console.error(e);
assert(false, e.message);
}
});
it.skip("IK: responder has no remote static key", async() => {
try {
const staticKeysInitiator = generateKeypair();
const noiseInit = new Noise(staticKeysInitiator.privateKey);
const staticKeysResponder = generateKeypair();
const noiseResp = new Noise(staticKeysResponder.privateKey);
const ikInitSpy = sandbox.spy(noiseInit, "performIKHandshake");
const xxFallbackInitSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake");
const ikRespSpy = sandbox.spy(noiseResp, "performIKHandshake");
// Prepare key cache for noise pipes
KeyCache.resetStorage();
KeyCache.store(remotePeer, staticKeysResponder.publicKey);
const [inboundConnection, outboundConnection] = DuplexPair();
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
]);
const wrappedInbound = Wrap(inbound.conn);
const wrappedOutbound = Wrap(outbound.conn);
wrappedOutbound.writeLP(Buffer.from("test fallback"));
const response = await wrappedInbound.readLP();
expect(response.toString()).equal("test fallback");
assert(ikInitSpy.calledOnce, "IK handshake was not called.");
assert(ikRespSpy.calledOnce, "IK handshake was not called.");
assert(xxFallbackInitSpy.notCalled, "XX Fallback method was called.");
} catch (e) {
console.error(e);
assert(false, e.message);
}
});
it("should working without remote peer provided in incoming connection", async() => {
try {
const staticKeysInitiator = generateKeypair();
const noiseInit = new Noise(staticKeysInitiator.privateKey);
const staticKeysResponder = generateKeypair();
const noiseResp = new Noise(staticKeysResponder.privateKey);
// Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.publicKey);
KeyCache.store(remotePeer, staticKeysResponder.publicKey);
const [inboundConnection, outboundConnection] = DuplexPair();
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection),
]);
const wrappedInbound = Wrap(inbound.conn);
const wrappedOutbound = Wrap(outbound.conn);
wrappedOutbound.writeLP(Buffer.from("test v2"));
const response = await wrappedInbound.readLP();
expect(response.toString()).equal("test v2");
assert(inbound.remotePeer.marshalPubKey().equals(localPeer.marshalPubKey()));
assert(outbound.remotePeer.marshalPubKey().equals(remotePeer.marshalPubKey()));
} catch (e) {
console.error(e);
assert(false, e.message);
}
});
it("should accept and return early data from remote peer", async() => {
try {
const localPeerEarlyData = Buffer.from('early data')
const staticKeysInitiator = generateKeypair();
const noiseInit = new Noise(staticKeysInitiator.privateKey, localPeerEarlyData);
const staticKeysResponder = generateKeypair();
const noiseResp = new Noise(staticKeysResponder.privateKey);
// Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.publicKey);
KeyCache.store(remotePeer, staticKeysResponder.publicKey);
const [inboundConnection, outboundConnection] = DuplexPair();
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection),
]);
assert(inbound.remoteEarlyData.equals(localPeerEarlyData))
assert(outbound.remoteEarlyData.equals(Buffer.alloc(0)))
} catch (e) {
console.error(e);
assert(false, e.message);
}
});
});

View File

@ -1,13 +1,14 @@
import {keys} from 'libp2p-crypto';
import {KeyPair, PeerId} from "../src/@types/libp2p";
import { keys, PrivateKey } from 'libp2p-crypto'
import { KeyPair } from '../src/@types/libp2p'
import PeerId from 'peer-id'
export async function generateEd25519Keys() {
return await keys.generateKeyPair('ed25519');
export async function generateEd25519Keys (): Promise<PrivateKey> {
return await keys.generateKeyPair('Ed25519', 32)
}
export function getKeyPairFromPeerId(peerId: PeerId): KeyPair {
export function getKeyPairFromPeerId (peerId: PeerId): KeyPair {
return {
privateKey: peerId.privKey.marshal().slice(0, 32),
publicKey: peerId.marshalPubKey(),
privateKey: Buffer.from(peerId.privKey.marshal().slice(0, 32)),
publicKey: Buffer.from(peerId.marshalPubKey())
}
}

View File

@ -0,0 +1,76 @@
import Wrap from 'it-pb-rpc'
import { Buffer } from 'buffer'
import Duplex from 'it-pair/duplex'
import {
generateKeypair,
getPayload
} from '../src/utils'
import { XXFallbackHandshake } from '../src/handshake-xx-fallback'
import { createPeerIdsFromFixtures } from './fixtures/peer'
import { assert } from 'chai'
import { encode0 } from '../src/encoder'
describe('XX Fallback Handshake', () => {
let peerA, peerB
before(async () => {
[peerA, peerB] = await createPeerIdsFromFixtures(2)
})
it('should test that both parties can fallback to XX and finish handshake', async () => {
try {
const duplex = Duplex()
const connectionFrom = Wrap(duplex[0])
const connectionTo = Wrap(duplex[1])
const prologue = Buffer.alloc(0)
const staticKeysInitiator = generateKeypair()
const staticKeysResponder = generateKeypair()
const ephemeralKeys = generateKeypair()
// Initial msg for responder is IK first message from initiator
const handshakePayload = await getPayload(peerA, staticKeysInitiator.publicKey)
const initialMsgR = encode0({
ne: ephemeralKeys.publicKey,
ns: Buffer.alloc(0),
ciphertext: handshakePayload
})
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
const handshakeResp =
new XXFallbackHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, initialMsgR, peerA)
await handshakeResp.propose()
await handshakeResp.exchange()
// Initial message for initiator is XX Message B from responder
// This is the point where initiator falls back from IK
const initialMsgI = await connectionFrom.readLP()
const handshakeInit =
new XXFallbackHandshake(true, handshakePayload, prologue, staticKeysInitiator, connectionFrom, initialMsgI.slice(0), peerB, ephemeralKeys)
await handshakeInit.propose()
await handshakeInit.exchange()
await handshakeInit.finish()
await handshakeResp.finish()
const sessionInitator = handshakeInit.session
const sessionResponder = handshakeResp.session
// Test shared key
if (sessionInitator.cs1 !== undefined &&
sessionResponder.cs1 !== undefined &&
sessionInitator.cs2 !== undefined &&
sessionResponder.cs2 !== undefined) {
assert(sessionInitator.cs1.k.equals(sessionResponder.cs1.k))
assert(sessionInitator.cs2.k.equals(sessionResponder.cs2.k))
} else {
assert(false)
}
} catch (e) {
assert(false, e.message)
}
})
})

View File

@ -1,74 +0,0 @@
import Wrap from "it-pb-rpc";
import {Buffer} from "buffer";
import Duplex from 'it-pair/duplex';
import {
generateKeypair,
getPayload,
} from "../src/utils";
import {XXFallbackHandshake} from "../src/handshake-xx-fallback";
import {createPeerIdsFromFixtures} from "./fixtures/peer";
import {assert} from "chai";
import {decode1, encode0, encode1} from "../src/encoder";
describe("XX Fallback Handshake", () => {
let peerA, peerB, fakePeer;
before(async () => {
[peerA, peerB] = await createPeerIdsFromFixtures(2);
});
it("should test that both parties can fallback to XX and finish handshake", async () => {
try {
const duplex = Duplex();
const connectionFrom = Wrap(duplex[0]);
const connectionTo = Wrap(duplex[1]);
const prologue = Buffer.alloc(0);
const staticKeysInitiator = generateKeypair();
const staticKeysResponder = generateKeypair();
const ephemeralKeys = generateKeypair();
// Initial msg for responder is IK first message from initiator
const handshakePayload = await getPayload(peerA, staticKeysInitiator.publicKey);
const initialMsgR = encode0({
ne: ephemeralKeys.publicKey,
ns: Buffer.alloc(0),
ciphertext: handshakePayload,
});
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey);
const handshakeResp =
new XXFallbackHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, initialMsgR, peerA);
await handshakeResp.propose();
await handshakeResp.exchange();
// Initial message for initiator is XX Message B from responder
// This is the point where initiator falls back from IK
const initialMsgI = await connectionFrom.readLP();
const handshakeInit =
new XXFallbackHandshake(true, handshakePayload, prologue, staticKeysInitiator, connectionFrom, initialMsgI, peerB, ephemeralKeys);
await handshakeInit.propose();
await handshakeInit.exchange();
await handshakeInit.finish();
await handshakeResp.finish();
const sessionInitator = handshakeInit.session;
const sessionResponder = handshakeResp.session;
// Test shared key
if (sessionInitator.cs1 && sessionResponder.cs1 && sessionInitator.cs2 && sessionResponder.cs2) {
assert(sessionInitator.cs1.k.equals(sessionResponder.cs1.k));
assert(sessionInitator.cs2.k.equals(sessionResponder.cs2.k));
} else {
assert(false);
}
} catch (e) {
console.error(e);
assert(false, e.message);
}
});
})

120
test/xx-handshake.spec.ts Normal file
View File

@ -0,0 +1,120 @@
import { assert, expect } from 'chai'
import Duplex from 'it-pair/duplex'
import { Buffer } from 'buffer'
import Wrap from 'it-pb-rpc'
import { XXHandshake } from '../src/handshake-xx'
import { generateKeypair, getPayload } from '../src/utils'
import { createPeerIdsFromFixtures } from './fixtures/peer'
describe('XX Handshake', () => {
let peerA, peerB, fakePeer
before(async () => {
[peerA, peerB, fakePeer] = await createPeerIdsFromFixtures(3)
})
it('should propose, exchange and finish handshake', async () => {
try {
const duplex = Duplex()
const connectionFrom = Wrap(duplex[0])
const connectionTo = Wrap(duplex[1])
const prologue = Buffer.alloc(0)
const staticKeysInitiator = generateKeypair()
const staticKeysResponder = generateKeypair()
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey)
const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, peerB)
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, peerA)
await handshakeInitator.propose()
await handshakeResponder.propose()
await handshakeResponder.exchange()
await handshakeInitator.exchange()
await handshakeInitator.finish()
await handshakeResponder.finish()
const sessionInitator = handshakeInitator.session
const sessionResponder = handshakeResponder.session
// Test shared key
if (sessionInitator.cs1 && sessionResponder.cs1 && sessionInitator.cs2 && sessionResponder.cs2) {
assert(sessionInitator.cs1.k.equals(sessionResponder.cs1.k))
assert(sessionInitator.cs2.k.equals(sessionResponder.cs2.k))
} else {
assert(false)
}
// Test encryption and decryption
const encrypted = handshakeInitator.encrypt(Buffer.from('encryptthis'), handshakeInitator.session)
const { plaintext: decrypted, valid } = handshakeResponder.decrypt(encrypted, handshakeResponder.session)
assert(decrypted.equals(Buffer.from('encryptthis')))
assert(valid)
} catch (e) {
assert(false, e.message)
}
})
it('Initiator should fail to exchange handshake if given wrong public key in payload', async () => {
try {
const duplex = Duplex()
const connectionFrom = Wrap(duplex[0])
const connectionTo = Wrap(duplex[1])
const prologue = Buffer.alloc(0)
const staticKeysInitiator = generateKeypair()
const staticKeysResponder = generateKeypair()
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey)
const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, fakePeer)
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, peerA)
await handshakeInitator.propose()
await handshakeResponder.propose()
await handshakeResponder.exchange()
await handshakeInitator.exchange()
assert(false, 'Should throw exception')
} catch (e) {
expect(e.message).equals("Error occurred while verifying signed payload: Peer ID doesn't match libp2p public key.")
}
})
it('Responder should fail to exchange handshake if given wrong public key in payload', async () => {
try {
const duplex = Duplex()
const connectionFrom = Wrap(duplex[0])
const connectionTo = Wrap(duplex[1])
const prologue = Buffer.alloc(0)
const staticKeysInitiator = generateKeypair()
const staticKeysResponder = generateKeypair()
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey)
const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, peerB)
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, fakePeer)
await handshakeInitator.propose()
await handshakeResponder.propose()
await handshakeResponder.exchange()
await handshakeInitator.exchange()
await handshakeInitator.finish()
await handshakeResponder.finish()
assert(false, 'Should throw exception')
} catch (e) {
expect(e.message).equals("Error occurred while verifying signed payload: Peer ID doesn't match libp2p public key.")
}
})
})

View File

@ -1,121 +0,0 @@
import {assert, expect} from "chai";
import Duplex from 'it-pair/duplex';
import {Buffer} from "buffer";
import Wrap from "it-pb-rpc";
import {XXHandshake} from "../src/handshake-xx";
import {generateKeypair, getPayload} from "../src/utils";
import {createPeerIdsFromFixtures} from "./fixtures/peer";
describe("XX Handshake", () => {
let peerA, peerB, fakePeer;
before(async () => {
[peerA, peerB, fakePeer] = await createPeerIdsFromFixtures(3);
});
it("should propose, exchange and finish handshake", async() => {
try {
const duplex = Duplex();
const connectionFrom = Wrap(duplex[0]);
const connectionTo = Wrap(duplex[1]);
const prologue = Buffer.alloc(0);
const staticKeysInitiator = generateKeypair();
const staticKeysResponder = generateKeypair();
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey);
const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, peerB);
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey);
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, peerA);
await handshakeInitator.propose();
await handshakeResponder.propose();
await handshakeResponder.exchange();
await handshakeInitator.exchange();
await handshakeInitator.finish();
await handshakeResponder.finish();
const sessionInitator = handshakeInitator.session;
const sessionResponder = handshakeResponder.session;
// Test shared key
if (sessionInitator.cs1 && sessionResponder.cs1 && sessionInitator.cs2 && sessionResponder.cs2) {
assert(sessionInitator.cs1.k.equals(sessionResponder.cs1.k));
assert(sessionInitator.cs2.k.equals(sessionResponder.cs2.k));
} else {
assert(false);
}
// Test encryption and decryption
const encrypted = handshakeInitator.encrypt(Buffer.from("encryptthis"), handshakeInitator.session);
const {plaintext: decrypted, valid} = handshakeResponder.decrypt(encrypted, handshakeResponder.session);
assert(decrypted.equals(Buffer.from("encryptthis")));
assert(valid);
} catch (e) {
assert(false, e.message);
}
});
it("Initiator should fail to exchange handshake if given wrong public key in payload", async() => {
try {
const duplex = Duplex();
const connectionFrom = Wrap(duplex[0]);
const connectionTo = Wrap(duplex[1]);
const prologue = Buffer.alloc(0);
const staticKeysInitiator = generateKeypair();
const staticKeysResponder = generateKeypair();
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey);
const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, fakePeer);
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey);
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, peerA);
await handshakeInitator.propose();
await handshakeResponder.propose();
await handshakeResponder.exchange();
await handshakeInitator.exchange();
assert(false, "Should throw exception");
} catch (e) {
expect(e.message).equals("Error occurred while verifying signed payload: Peer ID doesn't match libp2p public key.")
}
});
it("Responder should fail to exchange handshake if given wrong public key in payload", async() => {
try {
const duplex = Duplex();
const connectionFrom = Wrap(duplex[0]);
const connectionTo = Wrap(duplex[1]);
const prologue = Buffer.alloc(0);
const staticKeysInitiator = generateKeypair();
const staticKeysResponder = generateKeypair();
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey);
const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, peerB);
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey);
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, fakePeer);
await handshakeInitator.propose();
await handshakeResponder.propose();
await handshakeResponder.exchange();
await handshakeInitator.exchange();
await handshakeInitator.finish();
await handshakeResponder.finish();
assert(false, "Should throw exception");
} catch (e) {
expect(e.message).equals("Error occurred while verifying signed payload: Peer ID doesn't match libp2p public key.")
}
});
});

View File

@ -1,8 +1,13 @@
{
"compilerOptions": {
"target": "es6",
"outDir": "dist",
"incremental": true,
"composite": true,
"target": "ES2018",
"module": "commonjs",
"strict": true,
"allowJs": true,
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"noImplicitAny": false,
@ -12,9 +17,12 @@
]
},
"include": [
"**/src/**/*.ts"
"**/test/**/*.ts",
"**/src/**/*.ts",
"**/src/**/*.js"
],
"exclude": [
"node_modules"
"node_modules",
"dist"
]
}

View File

@ -1,24 +0,0 @@
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
entry: "./src/index.ts",
mode: "production",
output: {
filename: "../bundle/bundle.js"
},
node: {
fs: "empty"
},
resolve: {
extensions: [".ts", ".js"],
},
module: {
rules: [
{test: /\.ts$/, exclude: [/node_modules/], use: {loader: "babel-loader", options: require("./babel.web.config")}}
],
},
plugins: [
new BundleAnalyzerPlugin()
]
};

View File

@ -1,19 +0,0 @@
module.exports = {
entry: "./src/index.ts",
mode: "production",
output: {
filename: "dist/bundle.js"
},
node: {
fs: "empty"
},
resolve: {
extensions: [".ts", ".js"],
},
module: {
rules: [
{test: /\.ts$/, use: {loader: "ts-loader", options: {transpileOnly: true}}}
],
},
};

10155
yarn.lock

File diff suppressed because it is too large Load Diff