├── .github
├── get-pkg-version.js
├── set-pkg-replacer-name.js
└── workflows
│ └── main.yml
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── benchmarks
└── test.js
├── lib
└── index.js
├── native
├── .rustfmt.toml
├── Cargo.lock
├── Cargo.toml
├── build.rs
└── src
│ ├── generate.rs
│ ├── hash.rs
│ ├── lib.rs
│ ├── load_create.rs
│ ├── misc.rs
│ ├── secret.rs
│ ├── sig.rs
│ ├── unbox.rs
│ └── utils.rs
├── package.json
└── test-extra
└── index.js
/.github/get-pkg-version.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var pkg = require('../package.json');
4 | console.log(pkg.version.replace(/-.*/g, ''));
5 |
--------------------------------------------------------------------------------
/.github/set-pkg-replacer-name.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var fs = require('fs');
4 | var pkg = require('../package.json');
5 | pkg.name = 'ssb-keys';
6 | var newPkgString = JSON.stringify(pkg, null, 2) + '\n';
7 | fs.writeFileSync(__dirname + '/../package.json', newPkgString);
8 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches: [master]
8 |
9 | env:
10 | CARGO_TERM_COLOR: always
11 |
12 | jobs:
13 | test:
14 | name: Test
15 |
16 | strategy:
17 | matrix:
18 | node-version: [12.x]
19 | os: [ubuntu-latest, macos-latest, windows-latest]
20 |
21 | runs-on: ${{ matrix.os }}
22 |
23 | steps:
24 | - name: Checkout the repo
25 | uses: actions/checkout@v2
26 | - name: Set up Rust
27 | uses: actions-rs/toolchain@v1
28 | with:
29 | toolchain: stable
30 | - name: Set up Node.js ${{ matrix.node-version }}
31 | uses: actions/setup-node@v2
32 | with:
33 | node-version: ${{ matrix.node-version }}
34 | - name: Set up pnpm 5
35 | uses: pnpm/action-setup@v1.2.1
36 | with:
37 | version: 5.7.x
38 |
39 | - name: pnpm install
40 | run: pnpm install
41 |
42 | - name: Compile .node
43 | run: npm run build-debug
44 |
45 | - name: Test
46 | run: npm test
47 |
48 | build-release:
49 | name: Build release for Node
50 | needs: test
51 | if: ${{ startsWith(github.event.head_commit.message, 'release') }}
52 |
53 | strategy:
54 | matrix:
55 | node-version: [10.x, 12.x, 14.x]
56 | os: [ubuntu-latest, macos-latest, windows-latest]
57 |
58 | runs-on: ${{ matrix.os }}
59 |
60 | steps:
61 | - name: Checkout the repo
62 | uses: actions/checkout@v2
63 | - name: Set up Rust
64 | uses: actions-rs/toolchain@v1
65 | with:
66 | toolchain: stable
67 | - name: Set up Node.js ${{ matrix.node-version }}
68 | uses: actions/setup-node@v2
69 | with:
70 | node-version: ${{ matrix.node-version }}
71 | - name: Set up pnpm 5
72 | uses: pnpm/action-setup@v1.2.1
73 | with:
74 | version: 5.7.x
75 |
76 | - name: pnpm install
77 | run: pnpm install
78 |
79 | - name: Compile .node
80 | run: npm run build-release
81 |
82 | - name: Tag .node as prebuild
83 | run: npm run tag-prebuild
84 |
85 | - name: Upload prebuild artifacts
86 | uses: actions/upload-artifact@v2
87 | with:
88 | name: prebuilds
89 | path: prebuilds/
90 |
91 | build-release-electron:
92 | name: Build release for Electron
93 | needs: test
94 | if: ${{ startsWith(github.event.head_commit.message, 'release') }}
95 |
96 | strategy:
97 | matrix:
98 | electron-version: [7, 8, 9, 10]
99 | node-version: [12.x]
100 | os: [ubuntu-latest, macos-latest, windows-latest]
101 |
102 | runs-on: ${{ matrix.os }}
103 |
104 | steps:
105 | - name: Checkout the repo
106 | uses: actions/checkout@v2
107 | - name: Set up Rust
108 | uses: actions-rs/toolchain@v1
109 | with:
110 | toolchain: stable
111 | - name: Set up Node.js ${{ matrix.node-version }}
112 | uses: actions/setup-node@v2
113 | with:
114 | node-version: ${{ matrix.node-version }}
115 | - name: Set up pnpm 5
116 | uses: pnpm/action-setup@v1.2.1
117 | with:
118 | version: 5.7.x
119 |
120 | - name: pnpm install
121 | run: pnpm install
122 |
123 | - name: Install Electron ${{ matrix.electron-version }}
124 | run: pnpm install electron@${{ matrix.electron-version }}
125 |
126 | - name: Compile .node
127 | run: npm run build-release-electron
128 |
129 | - name: Tag .node as prebuild
130 | run: npm run tag-prebuild-electron
131 |
132 | - name: Upload prebuild artifacts
133 | uses: actions/upload-artifact@v2
134 | with:
135 | name: prebuilds
136 | path: prebuilds/
137 |
138 | publish:
139 | name: NPM Publish
140 | needs: [build-release, build-release-electron]
141 | if: ${{ startsWith(github.event.head_commit.message, 'release') }}
142 |
143 | strategy:
144 | matrix:
145 | node-version: [12.x]
146 | os: [ubuntu-latest]
147 |
148 | runs-on: ${{ matrix.os }}
149 |
150 | steps:
151 | - name: Checkout the repo
152 | uses: actions/checkout@v2
153 | - name: Set up Node.js ${{ matrix.node-version }}
154 | uses: actions/setup-node@v2
155 | with:
156 | node-version: ${{ matrix.node-version }}
157 | registry-url: https://registry.npmjs.org/
158 |
159 | - name: Download prebuild artifacts
160 | uses: actions/download-artifact@v2
161 | with:
162 | name: prebuilds
163 | path: prebuilds
164 |
165 | - name: Check prebuilds
166 | run: ls -laR prebuilds
167 |
168 | - name: NPM Publish
169 | run: npm publish --access public
170 | env:
171 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
172 |
173 | replace:
174 | name: Update replacer branch
175 | needs: [publish]
176 | if: ${{ startsWith(github.event.head_commit.message, 'release') }}
177 |
178 | strategy:
179 | matrix:
180 | node-version: [12.x]
181 | os: [ubuntu-latest]
182 |
183 | runs-on: ${{ matrix.os }}
184 |
185 | steps:
186 | - name: Checkout the repo
187 | uses: actions/checkout@v2
188 | - name: Set up Node.js ${{ matrix.node-version }}
189 | uses: actions/setup-node@v2
190 | with:
191 | node-version: ${{ matrix.node-version }}
192 |
193 | - name: Download prebuild artifacts
194 | uses: actions/download-artifact@v2
195 | with:
196 | name: prebuilds
197 | path: prebuilds
198 |
199 | - name: Check prebuilds
200 | run: ls -laR prebuilds
201 |
202 | - name: Set package.json name to "ssb-keys"
203 | run: node .github/set-pkg-replacer-name.js
204 |
205 | - name: Detect version to replace
206 | run: echo "BRANCH_VERSION=$(node .github/get-pkg-version.js)" >> $GITHUB_ENV
207 |
208 | - name: Delete git branch replace-${{ env.BRANCH_VERSION }}
209 | uses: dawidd6/action-delete-branch@v3
210 | continue-on-error: true
211 | with:
212 | github_token: ${{ secrets.GITHUB_TOKEN }}
213 | branches: replace-${{ env.BRANCH_VERSION }}
214 |
215 | - name: Push git branch replace-${{ env.BRANCH_VERSION }}
216 | uses: github-actions-x/commit@v2.7
217 | with:
218 | github-token: ${{ secrets.GITHUB_TOKEN }}
219 | push-branch: 'replace-${{ env.BRANCH_VERSION }}'
220 | commit-message: 'update replacer branch'
221 | force-add: 'true'
222 | files: package.json prebuilds/
223 | name: Andre Staltz
224 | email: andre@staltz.com
225 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | native/target
2 | native/index.node
3 | native/artifacts.json
4 | **/*~
5 | **/node_modules
6 | **/.DS_Store
7 | .vscode
8 | pnpm-lock.yaml
9 | /test-compat
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .github
2 | native/target
3 | native/index.node
4 | native/artifacts.json
5 | **/*~
6 | **/node_modules
7 | **/.DS_Store
8 | .vscode
9 | .gitignore
10 | pnpm-lock.yaml
11 | /test-compat
12 | /test-extra
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ssb-keys-neon
2 |
3 | > A drop-in replacement of `ssb-keys`, implemented in Rust and delivered as a native module in Node.js
4 |
5 | ```
6 | npm install ssb-keys-neon
7 | ```
8 |
9 | ## Motivation
10 |
11 | It would make a lot of sense for SSB to be implemented in Rust, a language for safety and performance. [Sunrise Choir](https://github.com/sunrise-choir/) has written a lot of SSB modules in Rust, but these are not yet running in production, simply because it requires a lot of work to migrate from Node.js (and/or Electron) to Rust.
12 |
13 | [Neon](https://neon-bindings.com) allows you to create native modules for Node.js written in Rust. This is great, because it allows us to use Sunrise Choir's modules directly inside Node.js!
14 |
15 | ssb-keys-neon does exactly that, it runs [ssb-crypto](https://github.com/sunrise-choir/ssb-crypto), [ssb-keyfile](https://github.com/sunrise-choir/ssb-keyfile), [ssb-multiformats](https://github.com/sunrise-choir/ssb-multiformats) (Rust crates) under the hood, but provides an API that **perfectly mirrors** that of `ssb-keys`. There is no code you need to migrate, just replace `ssb-keys` with `ssb-keys-neon` and it's done.
16 |
17 | We even run the same test suite for `ssb-keys` on `ssb-keys-neon`.
18 |
19 | ## Usage
20 |
21 | There are two ways you can use this npm package.
22 |
23 | ### Option 1: (easiest) automatically replace
24 |
25 | In your **package.json**, assuming you already have `ssb-keys`, you can replace its *implementation* by pointing to the GitHub repo for `ssb-keys-neon`:
26 |
27 | ```diff
28 | // ...
29 | "dependencies": {
30 | "ssb-ebt": "^5.6.7",
31 | "ssb-friends": "^4.1.4",
32 | "ssb-invite": "^2.1.3",
33 | - "ssb-keys": "7.2.0",
34 | + "ssb-keys": "staltz/ssb-keys-neon#replace-7.2.0",
35 | "ssb-lan": "^0.2.0",
36 | "ssb-logging": "^1.0.0",
37 | "ssb-markdown": "^6.0.4",
38 | }
39 | // ...
40 | ```
41 |
42 | This is the easiest method because you only need to change `package.json`, your code can still `require('ssb-keys')` and under the hood it will load `ssb-keys-neon`.
43 |
44 | Note that you **cannot specify version ranges**. If you previously had `"ssb-keys": "7.x.x"`, you will have to specify an exact version when you write `"staltz/ssb-keys-neon#replace-7.2.0"`, you cannot write `"staltz/ssb-keys-neon#replace-7.x.x"`.
45 |
46 | ### Option 2: manually replace
47 |
48 | This method gives you more control over the usage of `ssb-keys-neon`, as well as allows you to specify version ranges. Just *remove ssb-keys* from your package.json, and *add ssb-keys-neon*:
49 |
50 | ```diff
51 | // ...
52 | "dependencies": {
53 | "ssb-ebt": "^5.6.7",
54 | "ssb-friends": "^4.1.4",
55 | "ssb-invite": "^2.1.3",
56 | - "ssb-keys": "8.0.0",
57 | + "ssb-keys-neon": ">=8.0.0-1",
58 | "ssb-lan": "^0.2.0",
59 | "ssb-logging": "^1.0.0",
60 | "ssb-markdown": "^6.0.4",
61 | }
62 | // ...
63 | ```
64 |
65 | **Then**, you also have to replace usages of ssb-keys manually in JavaScript source files:
66 |
67 | ```diff
68 | -var ssbKeys = require('ssb-keys')
69 | +var ssbKeys = require('ssb-keys-neon')
70 |
71 | // ...
72 | ```
73 |
74 | ## Versioning and support
75 |
76 | `ssb-keys-neon@X.Y.Z-num` is compatible with `ssb-keys@X.Y.Z`. Versions older than 8.0.0 still use the old name `ssb-neon-keys` so be sure to type it correctly.
77 |
78 | Versions of ssb-keys that are mirrored by ssb-keys-neon currently include (and which platforms are guaranteed to be supported):
79 |
80 |
81 | 8.0.0 (click here to see which platforms are supported)
82 |
83 | As of `ssb-keys-neon@8.0.0-5`
84 |
85 | - macOS (darwin-x64)
86 | - Node 10.x
87 | - Node 12.x
88 | - Node 14.x
89 | - Electron 7.x
90 | - Electron 8.x
91 | - Electron 9.x
92 | - Electron 10.x
93 | - Linux (linux-x64)
94 | - Node 10.x
95 | - Node 12.x
96 | - Node 14.x
97 | - Electron 7.x
98 | - Electron 8.x
99 | - Electron 9.x
100 | - Electron 10.x
101 | - Windows (win32-x64)
102 | - Node 10.x
103 | - Node 12.x
104 | - Node 14.x
105 | - Electron 7.x
106 | - Electron 8.x
107 | - Electron 9.x
108 | - Electron 10.x
109 |
110 |
111 |
112 |
113 | 7.2.2 (click here to see which platforms are supported)
114 |
115 | **As of `ssb-neon-keys@7.2.2-2`**
116 |
117 | - macOS (darwin-x64)
118 | - Node 10.x
119 | - Node 12.x
120 | - Node 14.x
121 | - Electron 7.x
122 | - Electron 8.x
123 | - Electron 9.x
124 | - Electron 10.x
125 | - Linux (linux-x64)
126 | - Node 10.x
127 | - Node 12.x
128 | - Node 14.x
129 | - Electron 7.x
130 | - Electron 8.x
131 | - Electron 9.x
132 | - Electron 10.x
133 | - Windows (win32-x64)
134 | - Node 10.x
135 | - Node 12.x
136 | - Node 14.x
137 | - Electron 7.x
138 | - Electron 8.x
139 | - Electron 9.x
140 | - Electron 10.x
141 |
142 |
143 |
144 |
145 | 7.2.1 (click here to see which platforms are supported)
146 |
147 | **As of `ssb-neon-keys@7.2.1-3`**
148 |
149 | - macOS (darwin-x64)
150 | - Node 10.x
151 | - Node 12.x
152 | - Node 14.x
153 | - Electron 7.x
154 | - Electron 8.x
155 | - Electron 9.x
156 | - Electron 10.x
157 | - Linux (linux-x64)
158 | - Node 10.x
159 | - Node 12.x
160 | - Node 14.x
161 | - Electron 7.x
162 | - Electron 8.x
163 | - Electron 9.x
164 | - Electron 10.x
165 | - Windows (win32-x64)
166 | - Node 10.x
167 | - Node 12.x
168 | - Node 14.x
169 | - Electron 7.x
170 | - Electron 8.x
171 | - Electron 9.x
172 | - Electron 10.x
173 |
174 |
175 |
176 |
177 | 7.2.0 (click here to see which platforms are supported)
178 |
179 | **As of `ssb-neon-keys@7.2.0-18`**
180 |
181 | - macOS (darwin-x64)
182 | - Node 10.x
183 | - Node 12.x
184 | - Node 14.x
185 | - Electron 7.x
186 | - Electron 8.x
187 | - Electron 9.x
188 | - Electron 10.x
189 | - Linux (linux-x64)
190 | - Node 10.x
191 | - Node 12.x
192 | - Node 14.x
193 | - Electron 7.x
194 | - Electron 8.x
195 | - Electron 9.x
196 | - Electron 10.x
197 | - Windows (win32-x64)
198 | - Node 10.x
199 | - Node 12.x
200 | - Node 14.x
201 | - Electron 7.x
202 | - Electron 8.x
203 | - Electron 9.x
204 | - Electron 10.x
205 |
206 |
207 |
208 | ## License
209 |
210 | LGPL-3.0
211 |
--------------------------------------------------------------------------------
/benchmarks/test.js:
--------------------------------------------------------------------------------
1 | const os = require('os');
2 | const path = require('path');
3 | const crypto = require('crypto');
4 | const ssbKeys = require('ssb-keys');
5 | const ssbKeysNeon = require('../');
6 |
7 | function test(ssbKeys) {
8 | const filename = path.join(
9 | os.tmpdir(),
10 | 'ssbkeys' + Math.floor(Math.random() * 10e4),
11 | );
12 | const rainbow = 'somewhere-over-the-rainbow-way-up-high';
13 | const ed25519Str = 'ed25519';
14 | const buf = Buffer.from(rainbow);
15 | const hmackey = crypto.randomBytes(32);
16 | const ptxt = {okay: true};
17 |
18 | const before = Date.now();
19 | const alice = ssbKeys.loadOrCreateSync(filename);
20 | const alicePrivate = alice.private;
21 | const bob = ssbKeys.generate(ed25519Str);
22 | const bobId = bob.id;
23 | const bobPrivate = bob.private;
24 | const recps = [alice.public, bob.public];
25 | ssbKeys.getTag(bobId);
26 | ssbKeys.verifyObj(alice, ssbKeys.signObj(alicePrivate, hmackey, ptxt));
27 | ssbKeys.hash(rainbow);
28 | for (let i = 0; i < 10e3; i++) {
29 | ssbKeys.unbox(ssbKeys.box(ptxt, recps), bobPrivate);
30 | ssbKeys.secretUnbox(ssbKeys.secretBox(ptxt, buf), buf);
31 | }
32 | const after = Date.now();
33 |
34 | return after - before;
35 | }
36 |
37 | test(ssbKeysNeon); // warm up the CPU
38 | const js1 = test(ssbKeys);
39 | const ne1 = test(ssbKeysNeon);
40 | const js2 = test(ssbKeys);
41 | const ne2 = test(ssbKeysNeon);
42 | const js3 = test(ssbKeys);
43 | const ne3 = test(ssbKeysNeon);
44 | const ssbKeysDuration = Math.round((js1 + js2 + js3) / 3);
45 | const ssbKeysNeonDuration = Math.round((ne1 + ne2 + ne3) / 3);
46 | const speedup = ((100 * ssbKeysNeonDuration) / ssbKeysDuration).toFixed(1);
47 | console.log(`ssb-keys ran in ${ssbKeysDuration}ms`);
48 | console.log(`ssb-neon-keys ran in ${ssbKeysNeonDuration}ms (${speedup}%)`);
49 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('neon-load-or-build')({
2 | moduleName: 'ssb-keys-neon',
3 | dir: __dirname + '/..',
4 | });
5 |
--------------------------------------------------------------------------------
/native/.rustfmt.toml:
--------------------------------------------------------------------------------
1 | tab_spaces = 2
--------------------------------------------------------------------------------
/native/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "aead"
7 | version = "0.3.2"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331"
10 | dependencies = [
11 | "generic-array",
12 | ]
13 |
14 | [[package]]
15 | name = "aho-corasick"
16 | version = "0.7.6"
17 | source = "registry+https://github.com/rust-lang/crates.io-index"
18 | checksum = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
19 | dependencies = [
20 | "memchr",
21 | ]
22 |
23 | [[package]]
24 | name = "arrayvec"
25 | version = "0.5.1"
26 | source = "registry+https://github.com/rust-lang/crates.io-index"
27 | checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
28 |
29 | [[package]]
30 | name = "base64"
31 | version = "0.13.0"
32 | source = "registry+https://github.com/rust-lang/crates.io-index"
33 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
34 |
35 | [[package]]
36 | name = "block-buffer"
37 | version = "0.9.0"
38 | source = "registry+https://github.com/rust-lang/crates.io-index"
39 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
40 | dependencies = [
41 | "generic-array",
42 | ]
43 |
44 | [[package]]
45 | name = "byteorder"
46 | version = "1.3.4"
47 | source = "registry+https://github.com/rust-lang/crates.io-index"
48 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
49 |
50 | [[package]]
51 | name = "cc"
52 | version = "1.0.50"
53 | source = "registry+https://github.com/rust-lang/crates.io-index"
54 | checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
55 |
56 | [[package]]
57 | name = "cfg-if"
58 | version = "0.1.10"
59 | source = "registry+https://github.com/rust-lang/crates.io-index"
60 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
61 |
62 | [[package]]
63 | name = "cfg-if"
64 | version = "1.0.0"
65 | source = "registry+https://github.com/rust-lang/crates.io-index"
66 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
67 |
68 | [[package]]
69 | name = "cipher"
70 | version = "0.2.5"
71 | source = "registry+https://github.com/rust-lang/crates.io-index"
72 | checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801"
73 | dependencies = [
74 | "generic-array",
75 | ]
76 |
77 | [[package]]
78 | name = "cpuid-bool"
79 | version = "0.1.2"
80 | source = "registry+https://github.com/rust-lang/crates.io-index"
81 | checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
82 |
83 | [[package]]
84 | name = "crypto-mac"
85 | version = "0.10.0"
86 | source = "registry+https://github.com/rust-lang/crates.io-index"
87 | checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6"
88 | dependencies = [
89 | "generic-array",
90 | "subtle",
91 | ]
92 |
93 | [[package]]
94 | name = "curve25519-dalek"
95 | version = "3.0.0"
96 | source = "registry+https://github.com/rust-lang/crates.io-index"
97 | checksum = "c8492de420e9e60bc9a1d66e2dbb91825390b738a388606600663fc529b4b307"
98 | dependencies = [
99 | "byteorder",
100 | "digest",
101 | "rand_core",
102 | "subtle",
103 | "zeroize",
104 | ]
105 |
106 | [[package]]
107 | name = "digest"
108 | version = "0.9.0"
109 | source = "registry+https://github.com/rust-lang/crates.io-index"
110 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
111 | dependencies = [
112 | "generic-array",
113 | ]
114 |
115 | [[package]]
116 | name = "ed25519"
117 | version = "1.0.2"
118 | source = "registry+https://github.com/rust-lang/crates.io-index"
119 | checksum = "07dfc993ea376e864fe29a4099a61ca0bb994c6d7745a61bf60ddb3d64e05237"
120 | dependencies = [
121 | "signature",
122 | ]
123 |
124 | [[package]]
125 | name = "ed25519-dalek"
126 | version = "1.0.1"
127 | source = "registry+https://github.com/rust-lang/crates.io-index"
128 | checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
129 | dependencies = [
130 | "curve25519-dalek",
131 | "ed25519",
132 | "rand",
133 | "sha2",
134 | "zeroize",
135 | ]
136 |
137 | [[package]]
138 | name = "generic-array"
139 | version = "0.14.4"
140 | source = "registry+https://github.com/rust-lang/crates.io-index"
141 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
142 | dependencies = [
143 | "typenum",
144 | "version_check",
145 | ]
146 |
147 | [[package]]
148 | name = "getrandom"
149 | version = "0.1.15"
150 | source = "registry+https://github.com/rust-lang/crates.io-index"
151 | checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
152 | dependencies = [
153 | "cfg-if 0.1.10",
154 | "libc",
155 | "wasi",
156 | ]
157 |
158 | [[package]]
159 | name = "hmac"
160 | version = "0.10.1"
161 | source = "registry+https://github.com/rust-lang/crates.io-index"
162 | checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
163 | dependencies = [
164 | "crypto-mac",
165 | "digest",
166 | ]
167 |
168 | [[package]]
169 | name = "itoa"
170 | version = "0.4.5"
171 | source = "registry+https://github.com/rust-lang/crates.io-index"
172 | checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
173 |
174 | [[package]]
175 | name = "libc"
176 | version = "0.2.66"
177 | source = "registry+https://github.com/rust-lang/crates.io-index"
178 | checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
179 |
180 | [[package]]
181 | name = "memchr"
182 | version = "2.3.0"
183 | source = "registry+https://github.com/rust-lang/crates.io-index"
184 | checksum = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223"
185 |
186 | [[package]]
187 | name = "neon"
188 | version = "0.10.1"
189 | source = "registry+https://github.com/rust-lang/crates.io-index"
190 | checksum = "28e15415261d880aed48122e917a45e87bb82cf0260bb6db48bbab44b7464373"
191 | dependencies = [
192 | "neon-build 0.10.1",
193 | "neon-runtime",
194 | "semver",
195 | "smallvec",
196 | ]
197 |
198 | [[package]]
199 | name = "neon-build"
200 | version = "0.5.1"
201 | source = "registry+https://github.com/rust-lang/crates.io-index"
202 | checksum = "6a47d5ac62595f747bf4c41b533b8af0cf7ed275745c0378e623169c07c3dc92"
203 | dependencies = [
204 | "cfg-if 0.1.10",
205 | ]
206 |
207 | [[package]]
208 | name = "neon-build"
209 | version = "0.10.1"
210 | source = "registry+https://github.com/rust-lang/crates.io-index"
211 | checksum = "8bac98a702e71804af3dacfde41edde4a16076a7bbe889ae61e56e18c5b1c811"
212 | dependencies = [
213 | "neon-sys",
214 | ]
215 |
216 | [[package]]
217 | name = "neon-runtime"
218 | version = "0.10.1"
219 | source = "registry+https://github.com/rust-lang/crates.io-index"
220 | checksum = "4676720fa8bb32c64c3d9f49c47a47289239ec46b4bdb66d0913cc512cb0daca"
221 | dependencies = [
222 | "cfg-if 1.0.0",
223 | "neon-sys",
224 | "smallvec",
225 | ]
226 |
227 | [[package]]
228 | name = "neon-sys"
229 | version = "0.10.1"
230 | source = "registry+https://github.com/rust-lang/crates.io-index"
231 | checksum = "a5ebc923308ac557184455b4aaa749470554cbac70eb4daa8b18cdc16bef7df6"
232 | dependencies = [
233 | "cc",
234 | "regex",
235 | ]
236 |
237 | [[package]]
238 | name = "once_cell"
239 | version = "1.12.0"
240 | source = "registry+https://github.com/rust-lang/crates.io-index"
241 | checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
242 |
243 | [[package]]
244 | name = "opaque-debug"
245 | version = "0.3.0"
246 | source = "registry+https://github.com/rust-lang/crates.io-index"
247 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
248 |
249 | [[package]]
250 | name = "poly1305"
251 | version = "0.6.0"
252 | source = "registry+https://github.com/rust-lang/crates.io-index"
253 | checksum = "d9b42192ab143ed7619bf888a7f9c6733a9a2153b218e2cd557cfdb52fbf9bb1"
254 | dependencies = [
255 | "universal-hash",
256 | ]
257 |
258 | [[package]]
259 | name = "ppv-lite86"
260 | version = "0.2.9"
261 | source = "registry+https://github.com/rust-lang/crates.io-index"
262 | checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20"
263 |
264 | [[package]]
265 | name = "private-box"
266 | version = "0.6.0"
267 | source = "registry+https://github.com/rust-lang/crates.io-index"
268 | checksum = "edd373b34a8efae8c2aab2a203ef95a4af8858a249e52fe97d76f6c67e39a135"
269 | dependencies = [
270 | "ssb-crypto",
271 | "zerocopy",
272 | ]
273 |
274 | [[package]]
275 | name = "proc-macro2"
276 | version = "1.0.24"
277 | source = "registry+https://github.com/rust-lang/crates.io-index"
278 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
279 | dependencies = [
280 | "unicode-xid",
281 | ]
282 |
283 | [[package]]
284 | name = "quote"
285 | version = "1.0.2"
286 | source = "registry+https://github.com/rust-lang/crates.io-index"
287 | checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
288 | dependencies = [
289 | "proc-macro2",
290 | ]
291 |
292 | [[package]]
293 | name = "rand"
294 | version = "0.7.3"
295 | source = "registry+https://github.com/rust-lang/crates.io-index"
296 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
297 | dependencies = [
298 | "getrandom",
299 | "rand_chacha",
300 | "rand_core",
301 | "rand_hc",
302 | ]
303 |
304 | [[package]]
305 | name = "rand_chacha"
306 | version = "0.2.2"
307 | source = "registry+https://github.com/rust-lang/crates.io-index"
308 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
309 | dependencies = [
310 | "ppv-lite86",
311 | "rand_core",
312 | ]
313 |
314 | [[package]]
315 | name = "rand_core"
316 | version = "0.5.1"
317 | source = "registry+https://github.com/rust-lang/crates.io-index"
318 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
319 | dependencies = [
320 | "getrandom",
321 | ]
322 |
323 | [[package]]
324 | name = "rand_hc"
325 | version = "0.2.0"
326 | source = "registry+https://github.com/rust-lang/crates.io-index"
327 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
328 | dependencies = [
329 | "rand_core",
330 | ]
331 |
332 | [[package]]
333 | name = "regex"
334 | version = "1.3.3"
335 | source = "registry+https://github.com/rust-lang/crates.io-index"
336 | checksum = "b5508c1941e4e7cb19965abef075d35a9a8b5cdf0846f30b4050e9b55dc55e87"
337 | dependencies = [
338 | "aho-corasick",
339 | "memchr",
340 | "regex-syntax",
341 | "thread_local",
342 | ]
343 |
344 | [[package]]
345 | name = "regex-syntax"
346 | version = "0.6.13"
347 | source = "registry+https://github.com/rust-lang/crates.io-index"
348 | checksum = "e734e891f5b408a29efbf8309e656876276f49ab6a6ac208600b4419bd893d90"
349 |
350 | [[package]]
351 | name = "ryu"
352 | version = "1.0.2"
353 | source = "registry+https://github.com/rust-lang/crates.io-index"
354 | checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
355 |
356 | [[package]]
357 | name = "salsa20"
358 | version = "0.7.2"
359 | source = "registry+https://github.com/rust-lang/crates.io-index"
360 | checksum = "399f290ffc409596022fce5ea5d4138184be4784f2b28c62c59f0d8389059a15"
361 | dependencies = [
362 | "cipher",
363 | "zeroize",
364 | ]
365 |
366 | [[package]]
367 | name = "semver"
368 | version = "0.9.0"
369 | source = "registry+https://github.com/rust-lang/crates.io-index"
370 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
371 | dependencies = [
372 | "semver-parser",
373 | ]
374 |
375 | [[package]]
376 | name = "semver-parser"
377 | version = "0.7.0"
378 | source = "registry+https://github.com/rust-lang/crates.io-index"
379 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
380 |
381 | [[package]]
382 | name = "serde"
383 | version = "1.0.118"
384 | source = "registry+https://github.com/rust-lang/crates.io-index"
385 | checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
386 | dependencies = [
387 | "serde_derive",
388 | ]
389 |
390 | [[package]]
391 | name = "serde_derive"
392 | version = "1.0.118"
393 | source = "registry+https://github.com/rust-lang/crates.io-index"
394 | checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
395 | dependencies = [
396 | "proc-macro2",
397 | "quote",
398 | "syn",
399 | ]
400 |
401 | [[package]]
402 | name = "serde_json"
403 | version = "1.0.60"
404 | source = "registry+https://github.com/rust-lang/crates.io-index"
405 | checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
406 | dependencies = [
407 | "itoa",
408 | "ryu",
409 | "serde",
410 | ]
411 |
412 | [[package]]
413 | name = "sha2"
414 | version = "0.9.2"
415 | source = "registry+https://github.com/rust-lang/crates.io-index"
416 | checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8"
417 | dependencies = [
418 | "block-buffer",
419 | "cfg-if 1.0.0",
420 | "cpuid-bool",
421 | "digest",
422 | "opaque-debug",
423 | ]
424 |
425 | [[package]]
426 | name = "signature"
427 | version = "1.2.2"
428 | source = "registry+https://github.com/rust-lang/crates.io-index"
429 | checksum = "29f060a7d147e33490ec10da418795238fd7545bba241504d6b31a409f2e6210"
430 |
431 | [[package]]
432 | name = "smallvec"
433 | version = "1.8.0"
434 | source = "registry+https://github.com/rust-lang/crates.io-index"
435 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
436 |
437 | [[package]]
438 | name = "ssb-crypto"
439 | version = "0.2.3"
440 | source = "registry+https://github.com/rust-lang/crates.io-index"
441 | checksum = "a055e461165ec87134bb33a521e7bb4e49f306ed14db31cab221dc649b2762a2"
442 | dependencies = [
443 | "base64",
444 | "curve25519-dalek",
445 | "ed25519-dalek",
446 | "hmac",
447 | "rand",
448 | "sha2",
449 | "subtle",
450 | "x25519-dalek",
451 | "xsalsa20poly1305",
452 | "zerocopy",
453 | "zeroize",
454 | ]
455 |
456 | [[package]]
457 | name = "ssb-keyfile"
458 | version = "0.5.4"
459 | source = "registry+https://github.com/rust-lang/crates.io-index"
460 | checksum = "8dc0b4660dcbc0f17195e012306eb1cb80e6d4037458e93529a81a4a1240504d"
461 | dependencies = [
462 | "base64",
463 | "serde",
464 | "serde_json",
465 | "ssb-crypto",
466 | "thiserror",
467 | ]
468 |
469 | [[package]]
470 | name = "ssb-keys-neon"
471 | version = "8.0.0"
472 | dependencies = [
473 | "arrayvec",
474 | "base64",
475 | "neon",
476 | "neon-build 0.5.1",
477 | "private-box",
478 | "ssb-crypto",
479 | "ssb-keyfile",
480 | ]
481 |
482 | [[package]]
483 | name = "subtle"
484 | version = "2.4.0"
485 | source = "registry+https://github.com/rust-lang/crates.io-index"
486 | checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
487 |
488 | [[package]]
489 | name = "syn"
490 | version = "1.0.54"
491 | source = "registry+https://github.com/rust-lang/crates.io-index"
492 | checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
493 | dependencies = [
494 | "proc-macro2",
495 | "quote",
496 | "unicode-xid",
497 | ]
498 |
499 | [[package]]
500 | name = "synstructure"
501 | version = "0.12.4"
502 | source = "registry+https://github.com/rust-lang/crates.io-index"
503 | checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
504 | dependencies = [
505 | "proc-macro2",
506 | "quote",
507 | "syn",
508 | "unicode-xid",
509 | ]
510 |
511 | [[package]]
512 | name = "thiserror"
513 | version = "1.0.22"
514 | source = "registry+https://github.com/rust-lang/crates.io-index"
515 | checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e"
516 | dependencies = [
517 | "thiserror-impl",
518 | ]
519 |
520 | [[package]]
521 | name = "thiserror-impl"
522 | version = "1.0.22"
523 | source = "registry+https://github.com/rust-lang/crates.io-index"
524 | checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56"
525 | dependencies = [
526 | "proc-macro2",
527 | "quote",
528 | "syn",
529 | ]
530 |
531 | [[package]]
532 | name = "thread_local"
533 | version = "1.1.4"
534 | source = "registry+https://github.com/rust-lang/crates.io-index"
535 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
536 | dependencies = [
537 | "once_cell",
538 | ]
539 |
540 | [[package]]
541 | name = "typenum"
542 | version = "1.12.0"
543 | source = "registry+https://github.com/rust-lang/crates.io-index"
544 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
545 |
546 | [[package]]
547 | name = "unicode-xid"
548 | version = "0.2.0"
549 | source = "registry+https://github.com/rust-lang/crates.io-index"
550 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
551 |
552 | [[package]]
553 | name = "universal-hash"
554 | version = "0.4.0"
555 | source = "registry+https://github.com/rust-lang/crates.io-index"
556 | checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402"
557 | dependencies = [
558 | "generic-array",
559 | "subtle",
560 | ]
561 |
562 | [[package]]
563 | name = "version_check"
564 | version = "0.9.2"
565 | source = "registry+https://github.com/rust-lang/crates.io-index"
566 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
567 |
568 | [[package]]
569 | name = "wasi"
570 | version = "0.9.0+wasi-snapshot-preview1"
571 | source = "registry+https://github.com/rust-lang/crates.io-index"
572 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
573 |
574 | [[package]]
575 | name = "x25519-dalek"
576 | version = "1.1.0"
577 | source = "registry+https://github.com/rust-lang/crates.io-index"
578 | checksum = "bc614d95359fd7afc321b66d2107ede58b246b844cf5d8a0adcca413e439f088"
579 | dependencies = [
580 | "curve25519-dalek",
581 | "rand_core",
582 | "zeroize",
583 | ]
584 |
585 | [[package]]
586 | name = "xsalsa20poly1305"
587 | version = "0.6.0"
588 | source = "registry+https://github.com/rust-lang/crates.io-index"
589 | checksum = "0304c336e98d753428f7b3d8899d60b8a87a961ef50bdfc44af0c1bea2651ce5"
590 | dependencies = [
591 | "aead",
592 | "poly1305",
593 | "salsa20",
594 | "subtle",
595 | "zeroize",
596 | ]
597 |
598 | [[package]]
599 | name = "zerocopy"
600 | version = "0.3.0"
601 | source = "registry+https://github.com/rust-lang/crates.io-index"
602 | checksum = "6580539ad917b7c026220c4b3f2c08d52ce54d6ce0dc491e66002e35388fab46"
603 | dependencies = [
604 | "byteorder",
605 | "zerocopy-derive",
606 | ]
607 |
608 | [[package]]
609 | name = "zerocopy-derive"
610 | version = "0.2.0"
611 | source = "registry+https://github.com/rust-lang/crates.io-index"
612 | checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb"
613 | dependencies = [
614 | "proc-macro2",
615 | "syn",
616 | "synstructure",
617 | ]
618 |
619 | [[package]]
620 | name = "zeroize"
621 | version = "1.2.0"
622 | source = "registry+https://github.com/rust-lang/crates.io-index"
623 | checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36"
624 | dependencies = [
625 | "zeroize_derive",
626 | ]
627 |
628 | [[package]]
629 | name = "zeroize_derive"
630 | version = "1.0.1"
631 | source = "registry+https://github.com/rust-lang/crates.io-index"
632 | checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16"
633 | dependencies = [
634 | "proc-macro2",
635 | "quote",
636 | "syn",
637 | "synstructure",
638 | ]
639 |
--------------------------------------------------------------------------------
/native/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "ssb-keys-neon"
3 | version = "8.0.0"
4 | authors = ["Andre Staltz "]
5 | license = "LGPL-3.0"
6 | build = "build.rs"
7 | exclude = ["artifacts.json", "index.node"]
8 | edition = "2018"
9 |
10 | [lib]
11 | name = "ssb_neon_keys"
12 | crate-type = ["cdylib"]
13 |
14 | [build-dependencies]
15 | neon-build = "0.5.1"
16 |
17 | [dependencies]
18 | neon = "0.10.1"
19 | ssb-keyfile = "0.5.4"
20 | ssb-crypto = "0.2.3"
21 | private-box = "0.6.0"
22 | base64 = "0.13.0"
23 | arrayvec = "0.5.1"
24 |
--------------------------------------------------------------------------------
/native/build.rs:
--------------------------------------------------------------------------------
1 | extern crate neon_build;
2 |
3 | fn main() {
4 | neon_build::setup(); // must be called in build.rs
5 |
6 | // add project-specific build logic here...
7 | }
8 |
--------------------------------------------------------------------------------
/native/src/generate.rs:
--------------------------------------------------------------------------------
1 | use super::utils::{make_keys_obj, HandleExt, OptionExt};
2 | use neon::prelude::*;
3 | use ssb_crypto::Keypair;
4 |
5 | pub fn neon_generate(mut cx: FunctionContext) -> JsResult {
6 | let args_length = cx.len();
7 | if args_length == 0 {
8 | let keypair = Keypair::generate();
9 | return make_keys_obj(&mut cx, &keypair);
10 | }
11 |
12 | // First argument: curve (default = "ed25519")
13 | let curve = if let Some(s) = cx
14 | .argument::(0)
15 | .unwrap()
16 | .try_downcast::()
17 | {
18 | s.value()
19 | } else {
20 | "ed25519".to_string()
21 | };
22 |
23 | // The only valid curve types: ['ed25519']
24 | if curve != "ed25519" {
25 | return cx.throw_error("curve argument only supports: ed25519");
26 | }
27 |
28 | // Second argument: seed
29 | let maybe_seed = cx
30 | .argument_opt(1)
31 | .map(|v| {
32 | v.try_downcast::()
33 | .or_throw(&mut cx, "seed argument must be a buffer")
34 | })
35 | .transpose()?;
36 |
37 | // Use seed if given, else, generate from random
38 | let keypair = match maybe_seed {
39 | Some(seed_buffer) => {
40 | let seed_bytes = cx.borrow(&seed_buffer, |data| data.as_slice::());
41 | Keypair::from_seed(&seed_bytes).or_throw(&mut cx, "seed buffer must be 32 bytes")
42 | }
43 | None => Ok(Keypair::generate()),
44 | }?;
45 |
46 | make_keys_obj(&mut cx, &keypair)
47 | }
48 |
--------------------------------------------------------------------------------
/native/src/hash.rs:
--------------------------------------------------------------------------------
1 | use super::utils::{self, StringExt};
2 | use arrayvec::ArrayVec;
3 | use neon::prelude::*;
4 | use ssb_crypto::hash;
5 |
6 | pub fn neon_hash(mut cx: FunctionContext) -> JsResult {
7 | let args_length = cx.len();
8 |
9 | let data = cx.argument::(0)?;
10 | if !(data.is_a::() || data.is_a::()) {
11 | return cx.throw_error("expected 1st argument to `hash` to be a string or buffer");
12 | }
13 |
14 | let enc = {
15 | let fallback = cx.string("binary").upcast::();
16 | if args_length == 2 {
17 | cx.argument::(1).and_then(|v| {
18 | if v.is_a::() {
19 | Ok(v)
20 | } else if v.is_a::() || v.is_a::() {
21 | Ok(fallback)
22 | } else {
23 | cx.throw_error("expected encoding string as the 2nd argument to `hash`")
24 | }
25 | })
26 | } else {
27 | Ok(fallback)
28 | }
29 | }?;
30 |
31 | let data_buffer = utils::buffer_from(&mut cx, ArrayVec::from([data, enc]))?;
32 | let data_bytes = cx.borrow(&data_buffer, |bytes| bytes.as_slice::());
33 |
34 | Ok(cx.string(hash(data_bytes).as_base64().with_suffix(".sha256")))
35 | }
36 |
--------------------------------------------------------------------------------
/native/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod generate;
2 | mod hash;
3 | mod load_create;
4 | mod misc;
5 | mod secret;
6 | mod sig;
7 | mod unbox;
8 | mod utils;
9 |
10 | use self::generate::neon_generate;
11 | use self::hash::neon_hash;
12 | use self::load_create::{
13 | neon_create, neon_create_sync, neon_load, neon_load_or_create, neon_load_or_create_sync,
14 | neon_load_sync,
15 | };
16 | use self::misc::neon_get_tag;
17 | use self::secret::{neon_secret_box, neon_secret_unbox};
18 | use self::sig::{neon_sign, neon_sign_obj, neon_verify, neon_verify_obj};
19 | use self::unbox::{neon_box, neon_sk_to_curve, neon_unbox, neon_unbox_body, neon_unbox_key};
20 | use neon::prelude::*;
21 |
22 | register_module!(mut cx, {
23 | cx.export_function("generate", neon_generate)?;
24 | cx.export_function("load", neon_load)?;
25 | cx.export_function("loadSync", neon_load_sync)?;
26 | cx.export_function("create", neon_create)?;
27 | cx.export_function("createSync", neon_create_sync)?;
28 | cx.export_function("loadOrCreate", neon_load_or_create)?;
29 | cx.export_function("loadOrCreateSync", neon_load_or_create_sync)?;
30 | cx.export_function("signObj", neon_sign_obj)?;
31 | cx.export_function("verifyObj", neon_verify_obj)?;
32 | cx.export_function("sign", neon_sign)?;
33 | cx.export_function("verify", neon_verify)?;
34 | cx.export_function("getTag", neon_get_tag)?;
35 | cx.export_function("hash", neon_hash)?;
36 | cx.export_function("box", neon_box)?;
37 | cx.export_function("unbox", neon_unbox)?;
38 | cx.export_function("unboxKey", neon_unbox_key)?;
39 | cx.export_function("unboxBody", neon_unbox_body)?;
40 | cx.export_function("ssbSecretKeyToPrivateBoxSecret", neon_sk_to_curve)?;
41 | cx.export_function("secretBox", neon_secret_box)?;
42 | cx.export_function("secretUnbox", neon_secret_unbox)?;
43 | Ok(())
44 | });
45 |
--------------------------------------------------------------------------------
/native/src/load_create.rs:
--------------------------------------------------------------------------------
1 | use super::utils::{make_keys_obj, ContextExt};
2 | use neon::prelude::*;
3 |
4 | use ssb_crypto::Keypair;
5 | use ssb_keyfile::KeyFileError as SSBError;
6 |
7 | use std::io::Error;
8 | use std::path::Path;
9 |
10 | fn internal_create>(path: P) -> Result {
11 | if path.as_ref().is_dir() {
12 | ssb_keyfile::generate_at_path(path.as_ref().join("secret"))
13 | } else {
14 | ssb_keyfile::generate_at_path(&path)
15 | }
16 | }
17 |
18 | fn internal_load>(path: P) -> Result {
19 | if path.as_ref().is_dir() {
20 | ssb_keyfile::read_from_path(path.as_ref().join("secret"))
21 | } else {
22 | ssb_keyfile::read_from_path(path)
23 | }
24 | }
25 |
26 | struct CreateTask {
27 | argument: String,
28 | }
29 |
30 | impl Task for CreateTask {
31 | type Output = Keypair;
32 | type Error = Error;
33 | type JsEvent = JsObject;
34 |
35 | fn perform(&self) -> Result {
36 | internal_create(&self.argument)
37 | }
38 |
39 | fn complete(self, mut cx: TaskContext, result: Result) -> JsResult {
40 | let keypair = result.or_else(|e| cx.throw_error(e.to_string()))?;
41 |
42 | make_keys_obj(&mut cx, &keypair)
43 | }
44 | }
45 |
46 | struct LoadTask {
47 | argument: String,
48 | }
49 |
50 | impl Task for LoadTask {
51 | type Output = Keypair;
52 | type Error = SSBError;
53 | type JsEvent = JsObject;
54 |
55 | fn perform(&self) -> Result {
56 | internal_load(&self.argument)
57 | }
58 |
59 | fn complete(self, mut cx: TaskContext, result: Result) -> JsResult {
60 | let keypair = result.or_else(|e| cx.throw_error(e.to_string()))?;
61 |
62 | make_keys_obj(&mut cx, &keypair)
63 | }
64 | }
65 |
66 | struct LoadOrCreateTask {
67 | argument: String,
68 | }
69 |
70 | impl Task for LoadOrCreateTask {
71 | type Output = Keypair;
72 | type Error = Error;
73 | type JsEvent = JsObject;
74 |
75 | fn perform(&self) -> Result {
76 | internal_load(&self.argument).or_else(|_| internal_create(&self.argument))
77 | }
78 |
79 | fn complete(self, mut cx: TaskContext, result: Result) -> JsResult {
80 | let keypair = result.or_else(|e| cx.throw_error(e.to_string()))?;
81 |
82 | make_keys_obj(&mut cx, &keypair)
83 | }
84 | }
85 |
86 | pub fn neon_create(mut cx: FunctionContext) -> JsResult {
87 | // TODO support all arguments (path, curve, isLegacy, cb)
88 | let path = cx
89 | .arg_as::(0, "expected string as the first argument to `create`")?
90 | .value();
91 |
92 | let cb = cx.arg_as::(1, "expected a callback function given to `create`")?;
93 | let task = CreateTask { argument: path };
94 | task.schedule(cb);
95 | Ok(cx.undefined())
96 | }
97 |
98 | pub fn neon_create_sync(mut cx: FunctionContext) -> JsResult {
99 | // TODO support all arguments (path, curve, isLegacy)
100 | let path = cx
101 | .arg_as::(0, "expected string as only argument to `loadSync`")?
102 | .value();
103 |
104 | let keypair = internal_create(&path).or_else(|e| cx.throw_error(e.to_string()))?;
105 |
106 | make_keys_obj(&mut cx, &keypair)
107 | }
108 |
109 | pub fn neon_load(mut cx: FunctionContext) -> JsResult {
110 | let path = cx
111 | .arg_as::(0, "expected string as the first argument to `load`")?
112 | .value();
113 |
114 | let cb = cx.arg_as::(1, "expected a callback function given to `load`")?;
115 |
116 | let task = LoadTask { argument: path };
117 | task.schedule(cb);
118 | Ok(cx.undefined())
119 | }
120 |
121 | pub fn neon_load_sync(mut cx: FunctionContext) -> JsResult {
122 | let path = cx
123 | .arg_as::(0, "expected string as only argument to `loadSync`")?
124 | .value();
125 |
126 | let keypair = internal_load(&path).or_else(|e| cx.throw_error(e.to_string()))?;
127 |
128 | make_keys_obj(&mut cx, &keypair)
129 | }
130 |
131 | pub fn neon_load_or_create(mut cx: FunctionContext) -> JsResult {
132 | let path = cx
133 | .arg_as::(0, "expected string as the first argument to `loadOrCreate`")?
134 | .value();
135 |
136 | let cb = cx.arg_as::(1, "expected a callback function given to `loadOrCreate`")?;
137 |
138 | let task = LoadOrCreateTask { argument: path };
139 | task.schedule(cb);
140 | Ok(cx.undefined())
141 | }
142 |
143 | pub fn neon_load_or_create_sync(mut cx: FunctionContext) -> JsResult {
144 | let path = cx
145 | .arg_as::(0, "expected string as only argument to `loadOrCreateSync`")?
146 | .value();
147 |
148 | let keypair = internal_load(&path)
149 | .or_else(|_| internal_create(&path))
150 | .or_else(|e| cx.throw_error(e.to_string()))?;
151 |
152 | make_keys_obj(&mut cx, &keypair)
153 | }
154 |
--------------------------------------------------------------------------------
/native/src/misc.rs:
--------------------------------------------------------------------------------
1 | use super::utils::ContextExt;
2 | use neon::prelude::*;
3 |
4 | pub fn neon_get_tag(mut cx: FunctionContext) -> JsResult {
5 | let mut input = cx
6 | .arg_as::(0, "expected string as the 1st argument to `getTag`")?
7 | .value();
8 | let output = input.split_off(input.find('.').unwrap_or(input.len()) + 1);
9 |
10 | Ok(cx.string(output))
11 | }
12 |
--------------------------------------------------------------------------------
/native/src/secret.rs:
--------------------------------------------------------------------------------
1 | use super::utils::{self, ContextExt, OptionExt};
2 | use arrayvec::ArrayVec;
3 | use neon::prelude::*;
4 | use ssb_crypto::secretbox::{Hmac, Key, Nonce};
5 |
6 | pub fn neon_secret_box(mut cx: FunctionContext) -> JsResult {
7 | let arg1 = cx.argument::(0)?;
8 |
9 | if arg1.is_a::() || arg1.is_a::() {
10 | return Ok(cx.undefined().upcast());
11 | }
12 |
13 | let mut plaintext = utils::json_stringify(&mut cx, ArrayVec::from([arg1]))?
14 | .value()
15 | .into_bytes();
16 |
17 | let js_key = cx.arg_as::(1, "2nd argument must be the key as a buffer")?;
18 |
19 | let key_bytes = cx.borrow(&js_key, |bytes| bytes.as_slice::());
20 | let key = Key::from_slice(&key_bytes[0..32])
21 | .or_throw(&mut cx, "expected `secretbox` key to be at least 32 bytes")?;
22 | let nonce = Nonce::from_slice(&key_bytes[0..24]).unwrap(); // infallible
23 |
24 | let hmac = key.seal(&mut plaintext, &nonce);
25 | // `plaintext` now contains the cyphertext. "Attached" format begins with the hmac:
26 | plaintext.splice(0..0, hmac.0.iter().cloned());
27 | let buffer = utils::bytes_to_buffer(&mut cx, &plaintext)?;
28 | Ok(buffer.upcast())
29 | }
30 |
31 | pub fn neon_secret_unbox(mut cx: FunctionContext) -> JsResult {
32 | let arg1 = cx.argument::(0)?;
33 |
34 | if !arg1.is_a::() {
35 | return Ok(cx.undefined().upcast());
36 | }
37 |
38 | let buffer = arg1.downcast::().or_throw(&mut cx)?;
39 | let cyphertext = cx.borrow(&buffer, |bytes| bytes.as_slice::());
40 |
41 | let js_key = cx.arg_as::(1, "2nd argument must be the key as a buffer")?;
42 |
43 | let key_bytes = cx.borrow(&js_key, |bytes| bytes.as_slice::());
44 | if key_bytes.len() < 32 {
45 | cx.throw_error("expected `secretbox` key to be at least 32 bytes")?;
46 | }
47 | let key = Key::from_slice(&key_bytes[0..32]).unwrap(); // infallible
48 | let nonce = Nonce::from_slice(&key_bytes[0..24]).unwrap(); // infallible
49 |
50 | let mut plaintext = vec![0; cyphertext.len() - Hmac::SIZE];
51 |
52 | if !key.open_attached_into(cyphertext, &nonce, &mut plaintext) {
53 | return cx.throw_error("failed to decrypt in secretUnbox");
54 | }
55 | let plaintext_str = String::from_utf8(plaintext);
56 | if plaintext_str.is_err() {
57 | return Ok(cx.undefined().upcast());
58 | }
59 | let plaintext_str = cx.string(plaintext_str.unwrap());
60 |
61 | let out = utils::json_parse(&mut cx, plaintext_str);
62 | if out.is_err() {
63 | return Ok(cx.undefined().upcast());
64 | }
65 | let out = out.unwrap();
66 |
67 | Ok(out.upcast())
68 | }
69 |
--------------------------------------------------------------------------------
/native/src/sig.rs:
--------------------------------------------------------------------------------
1 | use super::utils::{
2 | self, get_string_or_field, type_name, ContextExt, HandleExt, OptionExt, StringExt, ValueExt,
3 | };
4 | use arrayvec::ArrayVec;
5 | use neon::prelude::*;
6 |
7 | // TODO NetworkKey isn't a great name, I guess
8 | use ssb_crypto::{Keypair, NetworkKey as AuthKey, PublicKey, Signature};
9 |
10 | // sign: (keys: obj | string, hmac_key: Buffer | string, str: string) => string
11 | pub fn neon_sign(mut cx: FunctionContext) -> JsResult {
12 | // FIXME: detect `curve` from keys.curve or from u.getTag and validate it
13 | let argc = cx.len();
14 | if argc < 2 {
15 | return cx.throw_error("sign requires at least two arguments: (keys, msg)");
16 | }
17 |
18 | let keypair = {
19 | let arg = cx.argument(0)?;
20 | let private_str = get_string_or_field(&mut cx, arg, "private").or_throw(
21 | &mut cx,
22 | "expected 1st argument to be the keys object or the private key string",
23 | )?;
24 |
25 | Keypair::from_base64(&private_str).or_throw(&mut cx, "cannot decode private key bytes")?
26 | };
27 |
28 | // TODO this is exactly the same inside neon_verify_obj, maybe could refactor
29 | let hmac_key = {
30 | if argc == 3 && cx.argument::(1)?.is_truthy(&mut cx) {
31 | let authkey = cx.argument::(1).and_then(|v| {
32 | if let Some(buf) = v.try_downcast::() {
33 | let bytes = cx.borrow(&buf, |data| data.as_slice::());
34 | AuthKey::from_slice(bytes).or_throw(&mut cx, "hmac_key buffer must be 32 bytes")
35 | } else if let Some(s) = v.try_downcast::() {
36 | AuthKey::from_base64(&s.value())
37 | .or_throw(&mut cx, "expected 2nd argument to be a base64 string")
38 | } else {
39 | cx.throw_error("expected 2nd argument to be a Buffer for the hmac_key")
40 | }
41 | })?;
42 | Some(authkey)
43 | } else {
44 | None
45 | }
46 | };
47 |
48 | let msg = cx
49 | .arg_as::(argc - 1, "expected 2nd arg to be a plaintext string")?
50 | .value()
51 | .into_bytes();
52 |
53 | let sig = match hmac_key {
54 | None => keypair.sign(msg.as_slice()),
55 | Some(hmac_key) => {
56 | let tag = hmac_key.authenticate(msg.as_slice());
57 | keypair.sign(&tag.0)
58 | }
59 | };
60 |
61 | let signature = cx.string(sig.as_base64().with_suffix(".sig.ed25519"));
62 |
63 | Ok(signature)
64 | }
65 |
66 | // verify: (keys: obj | string, signature: string, hmac_key, str: string) => boolean
67 | pub fn neon_verify(mut cx: FunctionContext) -> JsResult {
68 | // FIXME: detect `curve` from keys.curve or from u.getTag and validate it
69 | let argc = cx.len();
70 | if argc < 3 {
71 | return cx.throw_error("verify requires at least two arguments: (keys, msg)");
72 | }
73 |
74 | let public_key = {
75 | let arg = cx.argument(0)?;
76 | let public_str = get_string_or_field(&mut cx, arg, "public").or_throw(
77 | &mut cx,
78 | "expected `public` argument to be the keys object or the public key string",
79 | )?;
80 | PublicKey::from_base64(&public_str).or_throw(&mut cx, "cannot base64 decode the public key")?
81 | };
82 |
83 | let signature = {
84 | let mut sig = cx
85 | .arg_as::(1, "expected 2nd arg to be a signature string")?
86 | .value();
87 | match sig.rfind(".sig.ed25519") {
88 | None => return cx.throw_error("Invalid signature string, is missing dot suffix"),
89 | Some(dot_index) => sig.truncate(dot_index)
90 | };
91 | Signature::from_base64(&sig).or_throw(&mut cx, "unable to decode signature base64 string")?
92 | };
93 |
94 | // TODO this is almost the same inside neon_verify_obj, maybe could refactor
95 | let hmac_key = {
96 | if argc == 4 && cx.argument::(2)?.is_truthy(&mut cx) {
97 | let authkey = cx.argument::(2).and_then(|v| {
98 | if let Some(buf) = v.try_downcast::() {
99 | let bytes = cx.borrow(&buf, |data| data.as_slice::());
100 | AuthKey::from_slice(bytes).or_throw(&mut cx, "hmac_key buffer must be 32 bytes")
101 | } else if let Some(s) = v.try_downcast::() {
102 | AuthKey::from_base64(&s.value())
103 | .or_throw(&mut cx, "expected 3rd argument to be a base64 string")
104 | } else {
105 | cx.throw_error("expected 3rd argument to be a Buffer for the hmac_key")
106 | }
107 | })?;
108 | Some(authkey)
109 | } else {
110 | None
111 | }
112 | };
113 |
114 | let msg = cx
115 | .arg_as::(argc - 1 , "expected last arg to be a plaintext string")?
116 | .value();
117 |
118 | let passed = match hmac_key {
119 | None => public_key.verify(&signature, msg.into_bytes().as_slice()),
120 | Some(hmac_key) => {
121 | let tag = hmac_key.authenticate(msg.into_bytes().as_slice());
122 | public_key.verify(&signature, &tag.0)
123 | }
124 | };
125 |
126 | Ok(cx.boolean(passed))
127 | }
128 |
129 | // sign: (keys: obj | string, hmac_key?: string, o: obj) => string
130 | pub fn neon_sign_obj(mut cx: FunctionContext) -> JsResult {
131 | // FIXME: detect `curve` from keys.curve or from u.getTag and validate it
132 | let argc = cx.len();
133 | if argc < 2 {
134 | return cx.throw_error("signObj requires at least two arguments: (keys, msg)");
135 | }
136 |
137 | let keypair = {
138 | let arg = cx.argument(0)?;
139 | let private_str = get_string_or_field(&mut cx, arg, "private").or_throw(
140 | &mut cx,
141 | "expected 1st argument to be the keys object or the private key string",
142 | )?;
143 |
144 | Keypair::from_base64(&private_str).or_throw(&mut cx, "cannot decode private key bytes")?
145 | };
146 |
147 | // TODO this is exactly the same inside neon_verify_obj, maybe could refactor
148 | let hmac_key = {
149 | if argc == 3 && cx.argument::(1)?.is_truthy(&mut cx) {
150 | let authkey = cx.argument::(1).and_then(|v| {
151 | if let Some(buf) = v.try_downcast::() {
152 | let bytes = cx.borrow(&buf, |data| data.as_slice::());
153 | AuthKey::from_slice(bytes).or_throw(&mut cx, "hmac_key buffer must be 32 bytes")
154 | } else if let Some(s) = v.try_downcast::() {
155 | AuthKey::from_base64(&s.value())
156 | .or_throw(&mut cx, "expected 2nd argument to be a base64 string")
157 | } else {
158 | cx.throw_error("expected 2nd argument to be a Buffer for the hmac_key")
159 | }
160 | })?;
161 | Some(authkey)
162 | } else {
163 | None
164 | }
165 | };
166 |
167 | // TODO this is exactly the same inside neon_verify_obj, maybe could refactor
168 | let out_obj = {
169 | let (index, ord) = if argc == 2 { (1, "2nd") } else { (2, "3rd") };
170 | let v = cx.argument::(index)?;
171 | let obj = if v.is_a::() {
172 | Ok(v.downcast::().unwrap())
173 | } else {
174 | cx.throw_error(format!(
175 | "expected {} arg to be object, was a {}",
176 | ord,
177 | type_name(&v)
178 | ))
179 | }?;
180 | utils::clone_js_obj(&mut cx, obj)?
181 | };
182 |
183 | let msg = {
184 | let null = cx.null();
185 | let args = ArrayVec::from([out_obj.upcast(), null.upcast(), cx.number(2).upcast()]);
186 | utils::json_stringify(&mut cx, args)?.value().into_bytes()
187 | };
188 |
189 | // TODO this is exactly the same inside neon_verify_obj, maybe could refactor
190 | let sig = match hmac_key {
191 | None => keypair.sign(msg.as_slice()),
192 | Some(hmac_key) => {
193 | let tag = hmac_key.authenticate(msg.as_slice());
194 | keypair.sign(&tag.0)
195 | }
196 | };
197 | let signature = cx.string(sig.as_base64().with_suffix(".sig.ed25519"));
198 |
199 | out_obj
200 | .set(&mut cx, "signature", signature)
201 | .or_else(|_| cx.throw_error("failed to set the `signature` field in the object"))?;
202 |
203 | Ok(out_obj)
204 | }
205 |
206 | // verify: (keys: obj | string, hmac_key?: string, o: obj) => boolean
207 | pub fn neon_verify_obj(mut cx: FunctionContext) -> JsResult {
208 | // FIXME: detect `curve` from keys.curve or from u.getTag and validate it
209 | let argc = cx.len();
210 | if argc < 2 {
211 | return cx.throw_error("verifyObj requires at least two arguments: (keys, msg)");
212 | }
213 |
214 | let public_key = {
215 | let arg = cx.argument(0)?;
216 | let public_str = get_string_or_field(&mut cx, arg, "public").or_throw(
217 | &mut cx,
218 | "expected `public` argument to be the keys object or the public key string",
219 | )?;
220 | PublicKey::from_base64(&public_str).or_throw(&mut cx, "cannot base64 decode the public key")?
221 | };
222 |
223 | let hmac_key = {
224 | if argc == 3 && cx.argument::(1)?.is_truthy(&mut cx) {
225 | let authkey = cx.argument::(1).and_then(|v| {
226 | if let Some(buf) = v.try_downcast::() {
227 | let bytes = cx.borrow(&buf, |data| data.as_slice::());
228 | AuthKey::from_slice(bytes).or_throw(&mut cx, "hmac_key buffer must be 32 bytes")
229 | } else if let Some(s) = v.try_downcast::() {
230 | AuthKey::from_base64(&s.value())
231 | .or_throw(&mut cx, "expected 2nd argument to be a base64 string")
232 | } else {
233 | cx.throw_error("expected 2nd argument to be a Buffer for the hmac_key")
234 | }
235 | })?;
236 | Some(authkey)
237 | } else {
238 | None
239 | }
240 | };
241 |
242 | let verify_obj = {
243 | let (index, ord) = if argc == 2 { (1, "2nd") } else { (2, "3rd") };
244 | let v = cx.argument::(index)?;
245 | let obj = if v.is_a::() {
246 | Ok(v.downcast::().unwrap())
247 | } else {
248 | cx.throw_error(format!(
249 | "expected {} arg to be object, was a {}",
250 | ord,
251 | type_name(&v)
252 | ))
253 | }?;
254 | utils::clone_js_obj(&mut cx, obj)?
255 | };
256 |
257 | let signature = {
258 | let mut sig = verify_obj
259 | .get(&mut cx, "signature")
260 | .or_else(|_| cx.throw_error("obj.signature field is missing from obj"))?
261 | .downcast::()
262 | .or_throw(&mut cx)
263 | .or_else(|_| cx.throw_error("obj.signature field is corrupted or not a string"))?
264 | .value();
265 | match sig.rfind(".sig.ed25519") {
266 | None => return cx.throw_error("Invalid signature string, is missing dot suffix"),
267 | Some(dot_index) => sig.truncate(dot_index)
268 | };
269 | Signature::from_base64(&sig).or_throw(&mut cx, "unable to decode signature base64 string")?
270 | };
271 |
272 | let msg = {
273 | let undef = cx.undefined();
274 | verify_obj
275 | .set(&mut cx, "signature", undef) // `delete` keyword in JS would be better
276 | .or_else(|_| cx.throw_error("failed to remove the `signature` field from the object"))?;
277 |
278 | let args = ArrayVec::from([
279 | verify_obj.upcast(),
280 | cx.null().upcast(),
281 | cx.number(2).upcast(),
282 | ]);
283 | utils::json_stringify(&mut cx, args)?.value().into_bytes()
284 | };
285 |
286 | let passed = match hmac_key {
287 | None => public_key.verify(&signature, msg.as_slice()),
288 | Some(hmac_key) => {
289 | let tag = hmac_key.authenticate(msg.as_slice());
290 | public_key.verify(&signature, &tag.0)
291 | }
292 | };
293 |
294 | Ok(cx.boolean(passed))
295 | }
296 |
--------------------------------------------------------------------------------
/native/src/unbox.rs:
--------------------------------------------------------------------------------
1 | use super::utils::{self, get_string_or_field, ContextExt, OptionExt};
2 | use arrayvec::ArrayVec;
3 | use neon::prelude::*;
4 | use ssb_crypto::ephemeral::sk_to_curve;
5 | use ssb_crypto::{Keypair, PublicKey};
6 |
7 | pub fn neon_box(mut cx: FunctionContext) -> JsResult {
8 | let arg1 = cx.argument::(0)?;
9 | let msg = utils::json_stringify(&mut cx, ArrayVec::from([arg1]))?
10 | .value()
11 | .into_bytes();
12 |
13 | let recps: Vec = cx
14 | .arg_as::(1, "expected 2nd argument to be an array of recipients")?
15 | .to_vec(&mut cx)?
16 | .iter()
17 | .flat_map(|recp| {
18 | let public_str = get_string_or_field(&mut cx, *recp, "public").or_throw(
19 | &mut cx,
20 | "each recipient must be a keys object or public key string",
21 | )?;
22 | PublicKey::from_base64(&public_str).or_throw(&mut cx, "cannot base64 decode the public key")
23 | })
24 | .collect();
25 |
26 | let multiboxed = private_box::encrypt(msg.as_slice(), recps.as_slice());
27 | let mut out = base64::encode_config(multiboxed.as_slice(), base64::STANDARD);
28 | out.push_str(".box");
29 |
30 | Ok(cx.string(out))
31 | }
32 |
33 | pub fn neon_unbox(mut cx: FunctionContext) -> JsResult {
34 | let cyphertext = {
35 | let ctxt_str = cx
36 | .arg_as::(0, "expected 1st argument to be the cyphertext as a string")?
37 | .value();
38 | base64::decode_config(ctxt_str.trim_end_matches(".box"), base64::STANDARD)
39 | };
40 | if cyphertext.is_err() {
41 | return Ok(cx.undefined().upcast());
42 | }
43 | let cyphertext = cyphertext.unwrap();
44 |
45 | let private_key = {
46 | let v = cx.argument(1)?;
47 | let private_str = get_string_or_field(&mut cx, v, "private").or_throw(
48 | &mut cx,
49 | "expected 2nd argument to be the keys object or the private key string",
50 | )?;
51 | Keypair::from_base64(&private_str).or_throw(
52 | &mut cx,
53 | "cannot base64 decode the private key given to `signObj`",
54 | )?
55 | };
56 |
57 | let msg = private_box::decrypt(cyphertext.as_slice(), &private_key).ok_or(0);
58 | if msg.is_err() {
59 | return Ok(cx.undefined().upcast());
60 | }
61 | let msg = msg.unwrap();
62 | let msg_str = String::from_utf8(msg);
63 | if msg_str.is_err() {
64 | return Ok(cx.undefined().upcast());
65 | }
66 | let msg_str = cx.string(msg_str.unwrap());
67 |
68 | let out = utils::json_parse(&mut cx, msg_str);
69 | if out.is_err() {
70 | return Ok(cx.undefined().upcast());
71 | }
72 | let out = out.unwrap();
73 |
74 | Ok(out.upcast())
75 | }
76 |
77 | pub fn neon_unbox_key(mut cx: FunctionContext) -> JsResult {
78 | let cyphertext = {
79 | let ctxt_str = cx
80 | .arg_as::(0, "expected 1st argument to be the cyphertext as a string")?
81 | .value();
82 | base64::decode_config(ctxt_str.trim_end_matches(".box"), base64::STANDARD)
83 | };
84 | if cyphertext.is_err() {
85 | return Ok(cx.undefined().upcast());
86 | }
87 | let cyphertext = cyphertext.unwrap();
88 |
89 | let keypair = {
90 | let v = cx.argument(1)?;
91 | let private_str = get_string_or_field(&mut cx, v, "private").or_throw(
92 | &mut cx,
93 | "expected 2nd argument to be the keys object or the private key string",
94 | )?;
95 | Keypair::from_base64(&private_str).or_throw(
96 | &mut cx,
97 | "cannot base64 decode the private key given to `signObj`",
98 | )?
99 | };
100 |
101 | let opened_key = private_box::decrypt_key(&cyphertext, &keypair);
102 | if opened_key.is_none() {
103 | return Ok(cx.undefined().upcast());
104 | }
105 |
106 | let buffer = utils::bytes_to_buffer(&mut cx, &opened_key.unwrap().as_array())?;
107 | Ok(buffer.upcast())
108 | }
109 |
110 | // TODO should also allow JsBuffer ciphertext
111 | pub fn neon_unbox_body(mut cx: FunctionContext) -> JsResult {
112 | let cyphertext = {
113 | let ctxt_str = cx
114 | .arg_as::(0, "expected 1st argument to be the cyphertext as a string")?
115 | .value();
116 | base64::decode_config(ctxt_str.trim_end_matches(".box"), base64::STANDARD)
117 | };
118 | if cyphertext.is_err() {
119 | return Ok(cx.undefined().upcast());
120 | }
121 | let cyphertext = cyphertext.unwrap();
122 |
123 | let opened_key_buf =
124 | cx.arg_as::(1, "expected 2nd argument to be a buffer for the opened key")?;
125 | let opened_key = cx.borrow(&opened_key_buf, |data| data.as_slice::());
126 |
127 | let msg = private_box::decrypt_body_with_key_bytes(&cyphertext, &opened_key);
128 |
129 | if msg.is_none() {
130 | return Ok(cx.undefined().upcast());
131 | }
132 | let msg_str = String::from_utf8(msg.unwrap());
133 | if msg_str.is_err() {
134 | return Ok(cx.undefined().upcast());
135 | }
136 | let msg_str = cx.string(msg_str.unwrap());
137 |
138 | let out = utils::json_parse(&mut cx, msg_str);
139 | if out.is_err() {
140 | return Ok(cx.undefined().upcast());
141 | }
142 | let out = out.unwrap();
143 |
144 | Ok(out.upcast())
145 | }
146 |
147 | // ssbSecretKeyToPrivateBoxSecret
148 | pub fn neon_sk_to_curve(mut cx: FunctionContext) -> JsResult {
149 | let keypair = {
150 | let v = cx.argument(0)?;
151 | let private_str = get_string_or_field(&mut cx, v, "private").or_throw(
152 | &mut cx,
153 | "expected 1st argument to be the keys object or the private key string",
154 | )?;
155 |
156 | Keypair::from_base64(&private_str).or_throw(
157 | &mut cx,
158 | "cannot base64 decode the private key given to `signObj`",
159 | )?
160 | };
161 |
162 | let curve = sk_to_curve(&keypair.secret);
163 | if curve.is_none() {
164 | return cx.throw_error("failed to run ssbSecretKeyToPrivateBoxSecret");
165 | }
166 |
167 | let buffer = utils::bytes_to_buffer(&mut cx, &curve.unwrap().0)?;
168 | Ok(buffer.upcast())
169 | }
170 |
--------------------------------------------------------------------------------
/native/src/utils.rs:
--------------------------------------------------------------------------------
1 | use arrayvec::ArrayVec;
2 | use neon::handle::Managed;
3 | use neon::object::This;
4 | use neon::prelude::*;
5 | use ssb_crypto::Keypair;
6 |
7 | pub fn make_keys_obj<'a>(cx: &mut impl Context<'a>, kp: &Keypair) -> JsResult<'a, JsObject> {
8 | let keys_obj = JsObject::new(cx);
9 | let curve_val = cx.string("ed25519");
10 | let id_val = cx.string(kp.public.as_base64().wrap('@', ".ed25519"));
11 | let private_val = cx.string(kp.as_base64().with_suffix(".ed25519"));
12 | let public_val = cx.string(kp.public.as_base64().with_suffix(".ed25519"));
13 | keys_obj.set(cx, "curve", curve_val)?;
14 | keys_obj.set(cx, "id", id_val)?;
15 | keys_obj.set(cx, "private", private_val)?;
16 | keys_obj.set(cx, "public", public_val)?;
17 | Ok(keys_obj)
18 | }
19 |
20 | pub fn call_builtin<'a, T>(
21 | cx: &mut impl Context<'a>,
22 | module: &str,
23 | name: &str,
24 | args: impl IntoIterator- >,
25 | ) -> JsResult<'a, T>
26 | where
27 | T: Value + Managed,
28 | {
29 | let func = cx
30 | .global()
31 | .get(cx, module)?
32 | .downcast::()
33 | .or_throw(cx)?
34 | .get(cx, name)?
35 | .downcast::()
36 | .or_throw(cx)?;
37 |
38 | let null = cx.null();
39 | func.call(cx, null, args)?.downcast::().or_throw(cx)
40 | }
41 |
42 | // TODO publish to some neon-helpers library?
43 | pub fn json_stringify<'a>(
44 | cx: &mut impl Context<'a>,
45 | args: impl IntoIterator
- >,
46 | ) -> JsResult<'a, JsString> {
47 | call_builtin(cx, "JSON", "stringify", args)
48 | }
49 |
50 | pub fn json_parse<'a>(
51 | cx: &mut impl Context<'a>,
52 | arg: Handle<'a, JsString>,
53 | ) -> JsResult<'a, JsObject> {
54 | call_builtin(cx, "JSON", "parse", ArrayVec::from([arg.upcast()]))
55 | }
56 |
57 | pub fn buffer_from<'a>(
58 | cx: &mut impl Context<'a>,
59 | args: impl IntoIterator
- >,
60 | ) -> JsResult<'a, JsBuffer> {
61 | call_builtin(cx, "Buffer", "from", args)
62 | }
63 |
64 | // TODO publish to some neon-helpers library?
65 | pub fn clone_js_obj<'a>(
66 | cx: &mut impl Context<'a>,
67 | obj: Handle,
68 | ) -> JsResult<'a, JsObject> {
69 | let new_obj = cx.empty_object();
70 | let keys = obj.get_own_property_names(cx)?;
71 | for i in 0..keys.len() {
72 | let key = keys
73 | .get(cx, i)?
74 | .downcast::()
75 | .or_throw(cx)?
76 | .value();
77 | let val = obj.get(cx, key.as_str())?;
78 | new_obj.set(cx, key.as_str(), val)?;
79 | }
80 | Ok(new_obj)
81 | }
82 |
83 | pub fn bytes_to_buffer<'a>(cx: &mut impl Context<'a>, bytes: &[u8]) -> JsResult<'a, JsBuffer> {
84 | let mut buffer = cx.buffer(bytes.len() as u32)?;
85 | cx.borrow_mut(&mut buffer, |data| {
86 | data.as_mut_slice().copy_from_slice(bytes)
87 | });
88 | Ok(buffer)
89 | }
90 |
91 | pub fn get_string_or_field<'a, T: This>(
92 | cx: &mut CallContext<'a, T>,
93 | v: Handle,
94 | field: &str,
95 | ) -> Option {
96 | if let Some(s) = v.try_downcast::() {
97 | Some(s.value())
98 | } else if let Some(obj) = v.try_downcast::() {
99 | let f = obj.get(cx, field).ok()?;
100 | let s = f.try_downcast::()?;
101 | Some(s.value())
102 | } else {
103 | None
104 | }
105 | }
106 |
107 | pub fn type_name(v: &Handle) -> &'static str {
108 | if v.is_a::() {
109 | "array"
110 | } else if v.is_a::() {
111 | "array buffer"
112 | } else if v.is_a::() {
113 | "boolean"
114 | } else if v.is_a::() {
115 | "buffer"
116 | } else if v.is_a::() {
117 | "error"
118 | } else if v.is_a::() {
119 | "null"
120 | } else if v.is_a::() {
121 | "number"
122 | } else if v.is_a::() {
123 | "object"
124 | } else if v.is_a::() {
125 | "string"
126 | } else if v.is_a::() {
127 | "undefined"
128 | } else {
129 | "something else" // :)
130 | }
131 | }
132 |
133 | pub trait StringExt {
134 | fn with_suffix(self, s: &str) -> Self;
135 | fn wrap(self, prefix: char, suffix: &str) -> Self;
136 | }
137 |
138 | impl StringExt for String {
139 | fn with_suffix(mut self, s: &str) -> Self {
140 | self.push_str(s);
141 | self
142 | }
143 | fn wrap(mut self, prefix: char, suffix: &str) -> Self {
144 | self.insert(0, prefix);
145 | self.push_str(suffix);
146 | self
147 | }
148 | }
149 |
150 | pub trait OptionExt {
151 | fn or_throw<'a, S: AsRef>(
152 | self,
153 | cx: &mut impl Context<'a>,
154 | msg: S,
155 | ) -> Result;
156 | }
157 |
158 | impl OptionExt for Option {
159 | fn or_throw<'a, S: AsRef>(
160 | self,
161 | cx: &mut impl Context<'a>,
162 | msg: S,
163 | ) -> Result {
164 | // Result::unwrap_err and expect_err require T: Debug, which JsArray doesn't impl
165 | self.ok_or_else(|| cx.throw_error::<_, T>(msg).err().unwrap())
166 | }
167 | }
168 |
169 | // `if let Ok(s) = v.downcast::() { ... }`
170 | // can be used with zero cost (aside from the type tag check)
171 | // when this PR is merged: https://github.com/neon-bindings/neon/pull/606
172 | //
173 | // In the meantime, we'll use this:
174 | pub trait HandleExt<'a> {
175 | fn try_downcast(&self) -> Option>;
176 | }
177 | impl<'a, T: Value> HandleExt<'a> for Handle<'a, T> {
178 | fn try_downcast(&self) -> Option> {
179 | if self.is_a::() {
180 | Some(self.downcast::().unwrap())
181 | } else {
182 | None
183 | }
184 | }
185 | }
186 |
187 | pub trait ValueExt {
188 | fn is_truthy<'a, C: Context<'a>>(&self, cx: &mut C) -> bool;
189 | }
190 |
191 | impl ValueExt for T {
192 | fn is_truthy<'a, C: Context<'a>>(&self, cx: &mut C) -> bool {
193 | let global = cx.global();
194 | let boolean = global
195 | .get(cx, "Boolean")
196 | .unwrap()
197 | .downcast::()
198 | .unwrap();
199 | let args = ArrayVec::from([self.as_value(cx)]);
200 | let b = boolean
201 | .call(cx, global, args)
202 | .unwrap()
203 | .downcast::()
204 | .unwrap();
205 | b.value()
206 | }
207 | }
208 |
209 | // Like `cx.argument::(index)?` but with a custom error msg
210 | pub trait ContextExt<'a> {
211 | fn arg_as(
212 | &mut self,
213 | index: i32,
214 | msg: &str,
215 | ) -> Result, neon::result::Throw>;
216 | }
217 |
218 | impl<'a> ContextExt<'a> for FunctionContext<'a> {
219 | fn arg_as(
220 | &mut self,
221 | index: i32,
222 | msg: &str,
223 | ) -> Result, neon::result::Throw> {
224 | let v = self.argument::(index)?;
225 | v.try_downcast::().or_throw(self, msg)
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ssb-keys-neon",
3 | "version": "8.2.0-1",
4 | "description": "Rust-in-Node.js variant of ssb-keys",
5 | "main": "lib/index.js",
6 | "author": "Andre Staltz ",
7 | "license": "LGPL-3.0",
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/staltz/ssb-keys-neon.git"
11 | },
12 | "dependencies": {
13 | "neon-cli": "~0.5.1",
14 | "neon-load-or-build": "2.2.2"
15 | },
16 | "devDependencies": {
17 | "chloride": "2.4.1",
18 | "cpy-cli": "^3.1.1",
19 | "detect-libc": "~1.0.3",
20 | "electron-build-env": "^0.2.0",
21 | "neon-tag-prebuild": "^1.1.0",
22 | "ssb-keys": "8.2.0",
23 | "tape": "^5.2.2"
24 | },
25 | "scripts": {
26 | "install": "neon-load-or-build",
27 | "build-debug": "neon build",
28 | "build-release": "neon build --release",
29 | "build-release-electron": "electron-build-env neon build --release",
30 | "tag-prebuild": "detect-libc neon-tag-prebuild",
31 | "tag-prebuild-electron": "electron-build-env neon-tag-prebuild",
32 | "setup-compat-tests": "cpy ./node_modules/ssb-keys/test/* ./test-compat",
33 | "pretest": "npm run build-debug",
34 | "test-compat": "npm run setup-compat-tests && tape test-compat/*.js",
35 | "test-extra": "tape test-extra/*.js",
36 | "test": "npm run test-compat && npm run test-extra",
37 | "prepublishOnly": "if [ -z \"$CI\" ]; then echo \"can only npm publish from CI\" && exit 1; fi"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/test-extra/index.js:
--------------------------------------------------------------------------------
1 | let tape = require('tape');
2 |
3 | // This file was used when ssb-keys was lacking tests for some public APIs.
4 | // Nowadays we update ssb-keys tests to cover everything.
5 | // You can still use this file to add ad-hoc tests if you need
6 |
7 | tape('no extra tests', function (t) {
8 | t.end();
9 | });
10 |
--------------------------------------------------------------------------------