├── .aegir.js
├── .github
├── dependabot.yml
└── workflows
│ ├── automerge.yml
│ ├── js-test-and-release.yml
│ ├── semantic-pull-request.yml
│ └── stale.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── benchmark
├── ed25519
│ ├── compat.cjs
│ ├── index.js
│ └── package.json
├── ephemeral-keys.cjs
├── key-stretcher.cjs
└── rsa.cjs
├── package.json
├── src
├── aes
│ ├── cipher-mode.ts
│ ├── ciphers-browser.ts
│ ├── ciphers.ts
│ └── index.ts
├── ciphers
│ ├── aes-gcm.browser.ts
│ ├── aes-gcm.ts
│ └── interface.ts
├── hmac
│ ├── index-browser.ts
│ ├── index.ts
│ └── lengths.ts
├── index.ts
├── keys
│ ├── ecdh-browser.ts
│ ├── ecdh.ts
│ ├── ed25519-browser.ts
│ ├── ed25519-class.ts
│ ├── ed25519.ts
│ ├── ephemeral-keys.ts
│ ├── exporter.ts
│ ├── importer.ts
│ ├── index.ts
│ ├── interface.ts
│ ├── jwk2pem.ts
│ ├── key-stretcher.ts
│ ├── keys.proto
│ ├── keys.ts
│ ├── rsa-browser.ts
│ ├── rsa-class.ts
│ ├── rsa-utils.ts
│ ├── rsa.ts
│ ├── secp256k1-class.ts
│ └── secp256k1.ts
├── pbkdf2.ts
├── random-bytes.ts
├── util.ts
└── webcrypto.ts
├── stats.md
├── test
├── aes
│ └── aes.spec.ts
├── crypto.spec.ts
├── fixtures
│ ├── aes.ts
│ ├── go-aes.ts
│ ├── go-elliptic-key.ts
│ ├── go-key-ed25519.ts
│ ├── go-key-rsa.ts
│ ├── go-key-secp256k1.ts
│ ├── go-stretch-key.ts
│ └── secp256k1.ts
├── helpers
│ └── test-garbage-error-handling.ts
├── hmac
│ └── hmac.spec.ts
├── keys
│ ├── ed25519.spec.ts
│ ├── ephemeral-keys.spec.ts
│ ├── importer.spec.ts
│ ├── key-stretcher.spec.ts
│ ├── rsa.spec.ts
│ └── secp256k1.spec.ts
├── random-bytes.spec.ts
├── util.spec.ts
└── workaround.spec.ts
└── tsconfig.json
/.aegir.js:
--------------------------------------------------------------------------------
1 |
2 | /** @type {import('aegir/types').PartialOptions} */
3 | export default {
4 | build: {
5 | bundlesizeMax: '70kB'
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "10:00"
8 | open-pull-requests-limit: 10
9 | commit-message:
10 | prefix: "deps"
11 | prefix-development: "deps(dev)"
12 |
--------------------------------------------------------------------------------
/.github/workflows/automerge.yml:
--------------------------------------------------------------------------------
1 | # File managed by web3-bot. DO NOT EDIT.
2 | # See https://github.com/protocol/.github/ for details.
3 |
4 | name: Automerge
5 | on: [ pull_request ]
6 |
7 | jobs:
8 | automerge:
9 | uses: protocol/.github/.github/workflows/automerge.yml@master
10 | with:
11 | job: 'automerge'
12 |
--------------------------------------------------------------------------------
/.github/workflows/js-test-and-release.yml:
--------------------------------------------------------------------------------
1 | # File managed by web3-bot. DO NOT EDIT.
2 | # See https://github.com/protocol/.github/ for details.
3 |
4 | name: test & maybe release
5 | on:
6 | push:
7 | branches:
8 | - master
9 | pull_request:
10 |
11 | jobs:
12 |
13 | check:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v3
17 | - uses: actions/setup-node@v3
18 | with:
19 | node-version: lts/*
20 | - uses: ipfs/aegir/actions/cache-node-modules@master
21 | - run: npm run --if-present lint
22 | - run: npm run --if-present dep-check
23 |
24 | test-node:
25 | needs: check
26 | runs-on: ${{ matrix.os }}
27 | strategy:
28 | matrix:
29 | os: [windows-latest, ubuntu-latest, macos-latest]
30 | node: [lts/*]
31 | fail-fast: true
32 | steps:
33 | - uses: actions/checkout@v3
34 | - uses: actions/setup-node@v3
35 | with:
36 | node-version: ${{ matrix.node }}
37 | - uses: ipfs/aegir/actions/cache-node-modules@master
38 | - run: npm run --if-present test:node
39 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1
40 | with:
41 | flags: node
42 |
43 | test-chrome:
44 | needs: check
45 | runs-on: ubuntu-latest
46 | steps:
47 | - uses: actions/checkout@v3
48 | - uses: actions/setup-node@v3
49 | with:
50 | node-version: lts/*
51 | - uses: ipfs/aegir/actions/cache-node-modules@master
52 | - run: npm run --if-present test:chrome
53 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1
54 | with:
55 | flags: chrome
56 |
57 | test-chrome-webworker:
58 | needs: check
59 | runs-on: ubuntu-latest
60 | steps:
61 | - uses: actions/checkout@v3
62 | - uses: actions/setup-node@v3
63 | with:
64 | node-version: lts/*
65 | - uses: ipfs/aegir/actions/cache-node-modules@master
66 | - run: npm run --if-present test:chrome-webworker
67 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1
68 | with:
69 | flags: chrome-webworker
70 |
71 | test-firefox:
72 | needs: check
73 | runs-on: ubuntu-latest
74 | steps:
75 | - uses: actions/checkout@v3
76 | - uses: actions/setup-node@v3
77 | with:
78 | node-version: lts/*
79 | - uses: ipfs/aegir/actions/cache-node-modules@master
80 | - run: npm run --if-present test:firefox
81 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1
82 | with:
83 | flags: firefox
84 |
85 | test-firefox-webworker:
86 | needs: check
87 | runs-on: ubuntu-latest
88 | steps:
89 | - uses: actions/checkout@v3
90 | - uses: actions/setup-node@v3
91 | with:
92 | node-version: lts/*
93 | - uses: ipfs/aegir/actions/cache-node-modules@master
94 | - run: npm run --if-present test:firefox-webworker
95 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1
96 | with:
97 | flags: firefox-webworker
98 |
99 | test-webkit:
100 | needs: check
101 | runs-on: ${{ matrix.os }}
102 | strategy:
103 | matrix:
104 | os: [ubuntu-latest, macos-latest]
105 | node: [lts/*]
106 | fail-fast: true
107 | steps:
108 | - uses: actions/checkout@v3
109 | - uses: actions/setup-node@v3
110 | with:
111 | node-version: lts/*
112 | - uses: ipfs/aegir/actions/cache-node-modules@master
113 | - run: npm run --if-present test:webkit
114 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1
115 | with:
116 | flags: webkit
117 |
118 | test-webkit-webworker:
119 | needs: check
120 | runs-on: ${{ matrix.os }}
121 | strategy:
122 | matrix:
123 | os: [ubuntu-latest, macos-latest]
124 | node: [lts/*]
125 | fail-fast: true
126 | steps:
127 | - uses: actions/checkout@v3
128 | - uses: actions/setup-node@v3
129 | with:
130 | node-version: lts/*
131 | - uses: ipfs/aegir/actions/cache-node-modules@master
132 | - run: npm run --if-present test:webkit-webworker
133 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1
134 | with:
135 | flags: webkit-webworker
136 |
137 | test-electron-main:
138 | needs: check
139 | runs-on: ubuntu-latest
140 | steps:
141 | - uses: actions/checkout@v3
142 | - uses: actions/setup-node@v3
143 | with:
144 | node-version: lts/*
145 | - uses: ipfs/aegir/actions/cache-node-modules@master
146 | - run: npx xvfb-maybe npm run --if-present test:electron-main
147 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1
148 | with:
149 | flags: electron-main
150 |
151 | test-electron-renderer:
152 | needs: check
153 | runs-on: ubuntu-latest
154 | steps:
155 | - uses: actions/checkout@v3
156 | - uses: actions/setup-node@v3
157 | with:
158 | node-version: lts/*
159 | - uses: ipfs/aegir/actions/cache-node-modules@master
160 | - run: npx xvfb-maybe npm run --if-present test:electron-renderer
161 | - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1
162 | with:
163 | flags: electron-renderer
164 |
165 | release:
166 | needs: [test-node, test-chrome, test-chrome-webworker, test-firefox, test-firefox-webworker, test-webkit, test-webkit-webworker, test-electron-main, test-electron-renderer]
167 | runs-on: ubuntu-latest
168 | if: github.event_name == 'push' && github.ref == 'refs/heads/master'
169 | steps:
170 | - uses: actions/checkout@v3
171 | with:
172 | fetch-depth: 0
173 | - uses: actions/setup-node@v3
174 | with:
175 | node-version: lts/*
176 | - uses: ipfs/aegir/actions/cache-node-modules@master
177 | - uses: ipfs/aegir/actions/docker-login@master
178 | with:
179 | docker-token: ${{ secrets.DOCKER_TOKEN }}
180 | docker-username: ${{ secrets.DOCKER_USERNAME }}
181 | - run: npm run --if-present release
182 | env:
183 | GITHUB_TOKEN: ${{ secrets.UCI_GITHUB_TOKEN || github.token }}
184 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
185 |
--------------------------------------------------------------------------------
/.github/workflows/semantic-pull-request.yml:
--------------------------------------------------------------------------------
1 | name: Semantic PR
2 |
3 | on:
4 | pull_request_target:
5 | types:
6 | - opened
7 | - edited
8 | - synchronize
9 |
10 | jobs:
11 | main:
12 | uses: pl-strflt/.github/.github/workflows/reusable-semantic-pull-request.yml@v0.3
13 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: Close and mark stale issue
2 |
3 | on:
4 | schedule:
5 | - cron: '0 0 * * *'
6 |
7 | permissions:
8 | issues: write
9 | pull-requests: write
10 |
11 | jobs:
12 | stale:
13 | uses: pl-strflt/.github/.github/workflows/reusable-stale-issue.yml@v0.3
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | dist
4 | .docs
5 | .coverage
6 | node_modules
7 | package-lock.json
8 | yarn.lock
9 | .vscode
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This project is dual licensed under MIT and Apache-2.0.
2 |
3 | MIT: https://www.opensource.org/licenses/mit
4 | Apache-2.0: https://www.apache.org/licenses/license-2.0
5 |
--------------------------------------------------------------------------------
/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
2 |
3 | http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
6 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 📁 Archived - this module has been merged into [js-libp2p](https://github.com/libp2p/js-libp2p/tree/master/packages/crypto)
2 |
3 | # @libp2p/crypto
4 |
5 | [](http://libp2p.io/)
6 | [](https://discuss.libp2p.io)
7 | [](https://codecov.io/gh/libp2p/js-libp2p-crypto)
8 | [](https://github.com/libp2p/js-libp2p-crypto/actions/workflows/js-test-and-release.yml?query=branch%3Amaster)
9 |
10 | > Crypto primitives for libp2p
11 |
12 | ## Table of contents
13 |
14 | - [Install](#install)
15 | - [Browser `
56 | ```
57 |
58 | This repo contains the JavaScript implementation of the crypto primitives needed for libp2p. This is based on this [go implementation](https://github.com/libp2p/go-libp2p-crypto).
59 |
60 | ## Lead Maintainer
61 |
62 | [Jacob Heun](https://github.com/jacobheun/)
63 |
64 | ## Usage
65 |
66 | ```js
67 | const crypto = require('libp2p-crypto')
68 |
69 | // Now available to you:
70 | //
71 | // crypto.aes
72 | // crypto.hmac
73 | // crypto.keys
74 | // etc.
75 | //
76 | // See full API details below...
77 | ```
78 |
79 | ### Web Crypto API
80 |
81 | The `libp2p-crypto` library depends on the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) in the browser. Web Crypto is available in all modern browsers, however browsers restrict its usage to [Secure Contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).
82 |
83 | **This means you will not be able to use some `libp2p-crypto` functions in the browser when the page is served over HTTP.** To enable the Web Crypto API and allow `libp2p-crypto` to work fully, please serve your page over HTTPS.
84 |
85 | ## API
86 |
87 | ### `crypto.aes`
88 |
89 | Exposes an interface to AES encryption (formerly Rijndael), as defined in U.S. Federal Information Processing Standards Publication 197.
90 |
91 | This uses `CTR` mode.
92 |
93 | #### `crypto.aes.create(key, iv)`
94 |
95 | - `key: Uint8Array` The key, if length `16` then `AES 128` is used. For length `32`, `AES 256` is used.
96 | - `iv: Uint8Array` Must have length `16`.
97 |
98 | Returns `Promise<{decrypt, encrypt}>`
99 |
100 | ##### `decrypt(data)`
101 |
102 | - `data: Uint8Array`
103 |
104 | Returns `Promise`
105 |
106 | ##### `encrypt(data)`
107 |
108 | - `data: Uint8Array`
109 |
110 | Returns `Promise`
111 |
112 | ```js
113 | const crypto = require('libp2p-crypto')
114 |
115 | // Setting up Key and IV
116 |
117 | // A 16 bytes array, 128 Bits, AES-128 is chosen
118 | const key128 = Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
119 |
120 | // A 16 bytes array, 128 Bits,
121 | const IV = Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
122 |
123 | async function main () {
124 | const decryptedMessage = 'Hello, world!'
125 |
126 | // Encrypting
127 | const cipher = await crypto.aes.create(key128, IV)
128 | const encryptedBuffer = await cipher.encrypt(Uint8Array.from(decryptedMessage))
129 | console.log(encryptedBuffer)
130 | // prints:
131 |
132 | // Decrypting
133 | const decipher = await crypto.aes.create(key128, IV)
134 | const decryptedBuffer = await cipher.decrypt(encryptedBuffer)
135 |
136 | console.log(decryptedBuffer)
137 | // prints:
138 |
139 | console.log(decryptedBuffer.toString('utf-8'))
140 | // prints: Hello, world!
141 | }
142 |
143 | main()
144 | ```
145 |
146 | ### `crypto.hmac`
147 |
148 | Exposes an interface to the Keyed-Hash Message Authentication Code (HMAC) as defined in U.S. Federal Information Processing Standards Publication 198. An HMAC is a cryptographic hash that uses a key to sign a message. The receiver verifies the hash by recomputing it using the same key.
149 |
150 | #### `crypto.hmac.create(hash, secret)`
151 |
152 | - `hash: String`
153 | - `secret: Uint8Array`
154 |
155 | Returns `Promise<{digest}>`
156 |
157 | ##### `digest(data)`
158 |
159 | - `data: Uint8Array`
160 |
161 | Returns `Promise`
162 |
163 | Example:
164 |
165 | ```js
166 | const crypto = require('libp2p-crypto')
167 |
168 | async function main () {
169 | const hash = 'SHA1' // 'SHA256' || 'SHA512'
170 | const hmac = await crypto.hmac.create(hash, uint8ArrayFromString('secret'))
171 | const sig = await hmac.digest(uint8ArrayFromString('hello world'))
172 | console.log(sig)
173 | }
174 |
175 | main()
176 | ```
177 |
178 | ### `crypto.keys`
179 |
180 | **Supported Key Types**
181 |
182 | The [`generateKeyPair`](#generatekeypairtype-bits), [`marshalPublicKey`](#marshalpublickeykey-type), and [`marshalPrivateKey`](#marshalprivatekeykey-type) functions accept a string `type` argument.
183 |
184 | Currently the `'RSA'`, `'ed25519'`, and `secp256k1` types are supported, although ed25519 and secp256k1 keys support only signing and verification of messages. For encryption / decryption support, RSA keys should be used.
185 |
186 | ### `crypto.keys.generateKeyPair(type, bits)`
187 |
188 | - `type: String`, see [Supported Key Types](#supported-key-types) above.
189 | - `bits: Number` Minimum of 1024
190 |
191 | Returns `Promise<{privateKey, publicKey}>`
192 |
193 | Generates a keypair of the given type and bitsize.
194 |
195 | ### `crypto.keys.generateEphemeralKeyPair(curve)`
196 |
197 | - `curve: String`, one of `'P-256'`, `'P-384'`, `'P-521'` is currently supported
198 |
199 | Returns `Promise`
200 |
201 | Generates an ephemeral public key and returns a function that will compute the shared secret key.
202 |
203 | Focuses only on ECDH now, but can be made more general in the future.
204 |
205 | Resolves to an object of the form:
206 |
207 | ```js
208 | {
209 | key: Uint8Array,
210 | genSharedKey: Function
211 | }
212 | ```
213 |
214 | ### `crypto.keys.keyStretcher(cipherType, hashType, secret)`
215 |
216 | - `cipherType: String`, one of `'AES-128'`, `'AES-256'`, `'Blowfish'`
217 | - `hashType: String`, one of `'SHA1'`, `SHA256`, `SHA512`
218 | - `secret: Uint8Array`
219 |
220 | Returns `Promise`
221 |
222 | Generates a set of keys for each party by stretching the shared key.
223 |
224 | Resolves to an object of the form:
225 |
226 | ```js
227 | {
228 | k1: {
229 | iv: Uint8Array,
230 | cipherKey: Uint8Array,
231 | macKey: Uint8Array
232 | },
233 | k2: {
234 | iv: Uint8Array,
235 | cipherKey: Uint8Array,
236 | macKey: Uint8Array
237 | }
238 | }
239 | ```
240 |
241 | ### `crypto.keys.marshalPublicKey(key, [type])`
242 |
243 | - `key: keys.rsa.RsaPublicKey | keys.ed25519.Ed25519PublicKey | keys.secp256k1.Secp256k1PublicKey`
244 | - `type: String`, see [Supported Key Types](#supported-key-types) above. Defaults to 'rsa'.
245 |
246 | Returns `Uint8Array`
247 |
248 | Converts a public key object into a protobuf serialized public key.
249 |
250 | ### `crypto.keys.unmarshalPublicKey(buf)`
251 |
252 | - `buf: Uint8Array`
253 |
254 | Returns `RsaPublicKey|Ed25519PublicKey|Secp256k1PublicKey`
255 |
256 | Converts a protobuf serialized public key into its representative object.
257 |
258 | ### `crypto.keys.marshalPrivateKey(key, [type])`
259 |
260 | - `key: keys.rsa.RsaPrivateKey | keys.ed25519.Ed25519PrivateKey | keys.secp256k1.Secp256k1PrivateKey`
261 | - `type: String`, see [Supported Key Types](#supported-key-types) above.
262 |
263 | Returns `Uint8Array`
264 |
265 | Converts a private key object into a protobuf serialized private key.
266 |
267 | ### `crypto.keys.unmarshalPrivateKey(buf)`
268 |
269 | - `buf: Uint8Array`
270 |
271 | Returns `Promise`
272 |
273 | Converts a protobuf serialized private key into its representative object.
274 |
275 | ### `crypto.keys.import(encryptedKey, password)`
276 |
277 | - `encryptedKey: string`
278 | - `password: string`
279 |
280 | Returns `Promise`
281 |
282 | Converts an exported private key into its representative object. Supported formats are 'pem' (RSA only) and 'libp2p-key'.
283 |
284 | ### `privateKey.export(password, format)`
285 |
286 | - `password: string`
287 | - `format: string` the format to export to: 'pem' (rsa only), 'libp2p-key'
288 |
289 | Returns `string`
290 |
291 | Exports the password protected `PrivateKey`. RSA keys will be exported as password protected PEM by default. Ed25519 and Secp256k1 keys will be exported as password protected AES-GCM base64 encoded strings ('libp2p-key' format).
292 |
293 | ### `crypto.randomBytes(number)`
294 |
295 | - `number: Number`
296 |
297 | Returns `Uint8Array`
298 |
299 | Generates a Uint8Array with length `number` populated by random bytes.
300 |
301 | ### `crypto.pbkdf2(password, salt, iterations, keySize, hash)`
302 |
303 | - `password: String`
304 | - `salt: String`
305 | - `iterations: Number`
306 | - `keySize: Number` in bytes
307 | - `hash: String` the hashing algorithm ('sha1', 'sha2-512', ...)
308 |
309 | Computes the Password Based Key Derivation Function 2; returning a new password.
310 |
311 | ## Contribute
312 |
313 | Feel free to join in. All welcome. Open an [issue](https://github.com/libp2p/js-libp2p-crypto/issues)!
314 |
315 | This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).
316 |
317 | [](https://github.com/ipfs/community/blob/master/contributing.md)
318 |
319 | ## API Docs
320 |
321 | -
322 |
323 | ## License
324 |
325 | Licensed under either of
326 |
327 | - Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / )
328 | - MIT ([LICENSE-MIT](LICENSE-MIT) / )
329 |
330 | ## Contribution
331 |
332 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
333 |
--------------------------------------------------------------------------------
/benchmark/ed25519/compat.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | // @ts-expect-error types are missing
3 | const forge = require('node-forge/lib/forge')
4 |
5 | /*
6 | * Make sure that every Ed25519 implementation can use keys generated
7 | * by every other implementation to sign and verify messages signed
8 | * by themselves and by every other implementation using those keys.
9 | *
10 | * Nb. some modules return different structures from their key generation
11 | * routine - we normalise to `{ privateKey: seed, publicKey }`.
12 | *
13 | * Most implementations return the seed as the private key but supercop.wasm
14 | * returns a hash of the seed. We ignore supercop's private key in favour
15 | * of the seed here, since we can re-create it using the createKeyPair
16 | * function because key generation is deterministic for a given seed.
17 | */
18 |
19 | const { concat } = require('uint8arrays/concat')
20 | const { fromString } = require('uint8arrays/from-string')
21 |
22 | const native = require('ed25519')
23 | const noble = require('@noble/ed25519')
24 | const { randomBytes } = noble.utils
25 | const { subtle } = require('crypto').webcrypto
26 | require('node-forge/lib/ed25519')
27 | const stable = require('@stablelib/ed25519')
28 | const supercopWasm = require('supercop.wasm')
29 |
30 | const ALGORITHM = 'NODE-ED25519'
31 | const ED25519_PKCS8_PREFIX = fromString('302e020100300506032b657004220420', 'hex')
32 |
33 | const implementations = [{
34 | name: '@noble/ed25519',
35 | before: () => {},
36 | generateKeyPair: async () => {
37 | const privateKey = noble.utils.randomPrivateKey()
38 | const publicKey = await noble.getPublicKey(privateKey)
39 |
40 | return {
41 | privateKey,
42 | publicKey
43 | }
44 | },
45 | sign: (message, keyPair) => noble.sign(message, keyPair.privateKey),
46 | verify: (message, signature, keyPair) => noble.verify(signature, message, keyPair.publicKey)
47 | }, {
48 | name: '@stablelib/ed25519',
49 | before: () => {},
50 | generateKeyPair: async () => {
51 | const key = stable.generateKeyPair()
52 |
53 | return {
54 | privateKey: key.secretKey.subarray(0, 32),
55 | publicKey: key.publicKey
56 | }
57 | },
58 | sign: (message, keyPair) => stable.sign(concat([keyPair.privateKey, keyPair.publicKey]), message),
59 | verify: (message, signature, keyPair) => stable.verify(keyPair.publicKey, message, signature)
60 | }, {
61 | name: 'node-forge/ed25519',
62 | before: () => {},
63 | generateKeyPair: async () => {
64 | const seed = randomBytes(32)
65 | const key = await forge.pki.ed25519.generateKeyPair({ seed })
66 |
67 | return {
68 | privateKey: key.privateKey.subarray(0, 32),
69 | publicKey: key.publicKey
70 | }
71 | },
72 | sign: (message, keyPair) => forge.pki.ed25519.sign({ message, privateKey: keyPair.privateKey }),
73 | verify: (message, signature, keyPair) => forge.pki.ed25519.verify({ signature, message, publicKey: keyPair.publicKey })
74 | }, {
75 | name: 'supercop.wasm',
76 | before: () => {
77 | return new Promise(resolve => {
78 | supercopWasm.ready(() => {
79 | resolve()
80 | })
81 | })
82 | },
83 | generateKeyPair: async () => {
84 | const seed = supercopWasm.createSeed()
85 | const key = supercopWasm.createKeyPair(seed)
86 |
87 | return {
88 | privateKey: seed,
89 | publicKey: key.publicKey
90 | }
91 | },
92 | sign: (message, keyPair) => {
93 | const key = supercopWasm.createKeyPair(keyPair.privateKey)
94 |
95 | return supercopWasm.sign(message, key.publicKey, key.secretKey)
96 | },
97 | verify: (message, signature, keyPair) => {
98 | return supercopWasm.verify(signature, message, keyPair.publicKey)
99 | }
100 | }, {
101 | name: 'native Ed25519',
102 | generateKeyPair: async () => {
103 | const seed = randomBytes(32)
104 | const key = native.MakeKeypair(seed)
105 |
106 | return {
107 | privateKey: key.privateKey.subarray(0, 32),
108 | publicKey: key.publicKey
109 | }
110 | },
111 | sign: (message, keyPair) => native.Sign(message, keyPair.privateKey),
112 | verify: (message, signature, keyPair) => native.Verify(message, signature, keyPair.publicKey)
113 | }, {
114 | name: 'node.js web crypto',
115 | generateKeyPair: async () => {
116 | const key = await subtle.generateKey({
117 | name: 'NODE-ED25519',
118 | namedCurve: 'NODE-ED25519'
119 | }, true, ['sign', 'verify'])
120 | const jwk = await subtle.exportKey('jwk', key.privateKey)
121 |
122 | return {
123 | privateKey: fromString(jwk.d, 'base64url'),
124 | publicKey: fromString(jwk.x, 'base64url')
125 | }
126 | },
127 | sign: async (message, keyPair) => {
128 | const pkcs8 = concat([
129 | ED25519_PKCS8_PREFIX,
130 | keyPair.privateKey
131 | ], ED25519_PKCS8_PREFIX.length + 32)
132 | const cryptoKey = await subtle.importKey('pkcs8', pkcs8, {
133 | name: ALGORITHM,
134 | namedCurve: ALGORITHM
135 | }, true, ['sign'])
136 |
137 | const signature = await subtle.sign(ALGORITHM, cryptoKey, message)
138 |
139 | return new Uint8Array(signature)
140 | },
141 | verify: async (message, signature, keyPair) => {
142 | const cryptoKey = await subtle.importKey('raw', keyPair.publicKey, {
143 | name: ALGORITHM,
144 | namedCurve: ALGORITHM,
145 | public: true
146 | }, true, ['verify'])
147 |
148 | return subtle.verify(ALGORITHM, cryptoKey, signature, message)
149 | }
150 | }]
151 |
152 | async function test (a, b) {
153 | console.info(`test ${a.name} against ${b.name}`)
154 | const message = Buffer.from('hello world ' + Math.random())
155 |
156 | const keyPair = await a.generateKeyPair()
157 |
158 | if (keyPair.privateKey.length !== 32) {
159 | throw new Error('Private key not 32 bytes')
160 | }
161 |
162 | if (keyPair.publicKey.length !== 32) {
163 | throw new Error('Public key not 32 bytes')
164 | }
165 |
166 | // make sure we can sign and verify with keys created by the other implementation
167 | const pairs = [[a, a], [a, b], [b, a], [b, b]]
168 |
169 | for (const [a, b] of pairs) {
170 | console.info('test', a.name, 'against', b.name)
171 | const signature = await a.sign(message, keyPair)
172 | const isSigned = await b.verify(message, signature, keyPair)
173 |
174 | if (!isSigned) {
175 | console.error(`${b.name} could not verify signature created by ${a.name}`)
176 | }
177 | }
178 | }
179 |
180 | async function main () {
181 | for (const a of implementations) {
182 | if (a.before) {
183 | await a.before()
184 | }
185 |
186 | for (const b of implementations) {
187 | if (b.before) {
188 | await b.before()
189 | }
190 |
191 | await test(a, b)
192 | await test(b, a)
193 | }
194 | }
195 | }
196 |
197 | main()
198 | .catch(err => {
199 | console.error(err)
200 | process.exit(1)
201 | })
202 |
--------------------------------------------------------------------------------
/benchmark/ed25519/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | // @ts-expect-error types are missing
3 | import forge from 'node-forge/lib/forge.js'
4 | import Benchmark from 'benchmark'
5 | import native from 'ed25519'
6 | import * as noble from '@noble/ed25519'
7 | import 'node-forge/lib/ed25519.js'
8 | import stable from '@stablelib/ed25519'
9 | import supercopWasm from 'supercop.wasm'
10 | import ed25519WasmPro from 'ed25519-wasm-pro'
11 | import * as libp2pCrypto from '../../dist/src/index.js'
12 |
13 | const { randomBytes } = noble.utils
14 |
15 | const suite = new Benchmark.Suite('ed25519 implementations')
16 |
17 | suite.add('@libp2p/crypto', async (d) => {
18 | const message = Buffer.from('hello world ' + Math.random())
19 |
20 | const key = await libp2pCrypto.keys.generateKeyPair('Ed25519')
21 |
22 | const signature = await key.sign(message)
23 | const res = await key.public.verify(message, signature)
24 |
25 | if (!res) {
26 | throw new Error('could not verify @libp2p/crypto signature')
27 | }
28 |
29 | d.resolve()
30 | }, { defer: true })
31 |
32 | suite.add('@noble/ed25519', async (d) => {
33 | const message = Buffer.from('hello world ' + Math.random())
34 | const privateKey = noble.utils.randomPrivateKey()
35 | const publicKey = await noble.getPublicKey(privateKey)
36 | const signature = await noble.sign(message, privateKey)
37 | const isSigned = await noble.verify(signature, message, publicKey)
38 |
39 | if (!isSigned) {
40 | throw new Error('could not verify noble signature')
41 | }
42 |
43 | d.resolve()
44 | }, { defer: true })
45 |
46 | suite.add('@stablelib/ed25519', async (d) => {
47 | const message = Buffer.from('hello world ' + Math.random())
48 | const key = stable.generateKeyPair()
49 | const signature = await stable.sign(key.secretKey, message)
50 | const isSigned = await stable.verify(key.publicKey, message, signature)
51 |
52 | if (!isSigned) {
53 | throw new Error('could not verify stablelib signature')
54 | }
55 |
56 | d.resolve()
57 | }, { defer: true })
58 |
59 | suite.add('node-forge/ed25519', async (d) => {
60 | const message = Buffer.from('hello world ' + Math.random())
61 | const seed = randomBytes(32)
62 | const key = await forge.pki.ed25519.generateKeyPair({ seed })
63 | const signature = await forge.pki.ed25519.sign({ message, privateKey: key.privateKey })
64 | const res = await forge.pki.ed25519.verify({ signature, message, publicKey: key.publicKey })
65 |
66 | if (!res) {
67 | throw new Error('could not verify node-forge signature')
68 | }
69 |
70 | d.resolve()
71 | }, { defer: true })
72 |
73 | suite.add('supercop.wasm', async (d) => {
74 | const message = Buffer.from('hello world ' + Math.random())
75 | const seed = supercopWasm.createSeed()
76 | const keys = supercopWasm.createKeyPair(seed)
77 | const signature = supercopWasm.sign(message, keys.publicKey, keys.secretKey)
78 | const isSigned = await supercopWasm.verify(signature, message, keys.publicKey)
79 |
80 | if (!isSigned) {
81 | throw new Error('could not verify supercop.wasm signature')
82 | }
83 |
84 | d.resolve()
85 | }, { defer: true })
86 |
87 | suite.add('ed25519-wasm-pro', async (d) => {
88 | const message = Buffer.from('hello world ' + Math.random())
89 | const seed = ed25519WasmPro.createSeed()
90 | const keys = ed25519WasmPro.createKeyPair(seed)
91 | const signature = ed25519WasmPro.sign(message, keys.publicKey, keys.secretKey)
92 | const isSigned = await ed25519WasmPro.verify(signature, message, keys.publicKey)
93 |
94 | if (!isSigned) {
95 | throw new Error('could not verify ed25519-wasm-pro signature')
96 | }
97 |
98 | d.resolve()
99 | }, { defer: true })
100 |
101 | suite.add('ed25519 (native module)', async (d) => {
102 | const message = Buffer.from('hello world ' + Math.random())
103 | const seed = randomBytes(32)
104 | const key = native.MakeKeypair(seed)
105 | const signature = native.Sign(message, key)
106 | const res = native.Verify(message, signature, key.publicKey)
107 |
108 | if (!res) {
109 | throw new Error('could not verify native signature')
110 | }
111 |
112 | d.resolve()
113 | }, { defer: true })
114 |
115 | async function main () {
116 | await Promise.all([
117 | new Promise((resolve) => {
118 | supercopWasm.ready(() => resolve())
119 | }),
120 | new Promise((resolve) => {
121 | ed25519WasmPro.ready(() => resolve())
122 | })
123 | ])
124 | noble.utils.precompute(8)
125 |
126 | suite
127 | .on('cycle', (event) => console.log(String(event.target)))
128 | .on('complete', function () {
129 | console.log('fastest is ' + this.filter('fastest').map('name'))
130 | })
131 | .run({ async: true })
132 | }
133 |
134 | main()
135 | .catch(err => {
136 | console.error(err)
137 | process.exit(1)
138 | })
139 |
--------------------------------------------------------------------------------
/benchmark/ed25519/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "libp2p-crypto-ed25519-benchmarks",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "start": "node .",
8 | "compat": "node compat.js"
9 | },
10 | "license": "MIT",
11 | "dependencies": {
12 | "@noble/ed25519": "^1.3.0",
13 | "@stablelib/ed25519": "^1.0.2",
14 | "benchmark": "^2.1.4",
15 | "ed25519": "^0.0.5",
16 | "ed25519-wasm-pro": "^1.1.1",
17 | "node-forge": "^1.0.0",
18 | "supercop.wasm": "^5.0.1"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/benchmark/ephemeral-keys.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | const crypto = require('../dist/src/index.js')
3 |
4 | const Benchmark = require('benchmark')
5 |
6 | const suite = new Benchmark.Suite('ephemeral-keys')
7 |
8 | const secrets = []
9 | const curves = ['P-256', 'P-384', 'P-521']
10 |
11 | curves.forEach((curve) => {
12 | suite.add(`ephemeral key with secrect ${curve}`, async (d) => {
13 | const res = await crypto.keys.generateEphemeralKeyPair('P-256')
14 | const secret = await res.genSharedKey(res.key)
15 | secrets.push(secret)
16 | d.resolve()
17 | }, { defer: true })
18 | })
19 |
20 | suite
21 | .on('cycle', (event) => console.log(String(event.target)))
22 | .run({ async: true })
23 |
--------------------------------------------------------------------------------
/benchmark/key-stretcher.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | const crypto = require('../dist/src/index.js')
3 |
4 | const Benchmark = require('benchmark')
5 |
6 | const suite = new Benchmark.Suite('key-stretcher')
7 |
8 | const keys = []
9 |
10 | const ciphers = ['AES-128', 'AES-256', 'Blowfish']
11 | const hashes = ['SHA1', 'SHA256', 'SHA512']
12 |
13 | ;(async () => {
14 | const res = await crypto.keys.generateEphemeralKeyPair('P-256')
15 | const secret = await res.genSharedKey(res.key)
16 |
17 | ciphers.forEach((cipher) => hashes.forEach((hash) => {
18 | setup(cipher, hash, secret)
19 | }))
20 |
21 | suite
22 | .on('cycle', (event) => console.log(String(event.target)))
23 | .run({ async: true })
24 | })()
25 |
26 | function setup (cipher, hash, secret) {
27 | suite.add(`keyStretcher ${cipher} ${hash}`, async (d) => {
28 | const k = await crypto.keys.keyStretcher(cipher, hash, secret)
29 | keys.push(k)
30 | d.resolve()
31 | }, { defer: true })
32 | }
33 |
--------------------------------------------------------------------------------
/benchmark/rsa.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | const crypto = require('../dist/src/index.js')
3 |
4 | const Benchmark = require('benchmark')
5 |
6 | const suite = new Benchmark.Suite('rsa')
7 |
8 | const keys = []
9 | const bits = [1024, 2048, 4096]
10 |
11 | bits.forEach((bit) => {
12 | suite.add(`generateKeyPair ${bit}bits`, async (d) => {
13 | const key = await crypto.keys.generateKeyPair('RSA', bit)
14 | keys.push(key)
15 | d.resolve()
16 | }, {
17 | defer: true
18 | })
19 | })
20 |
21 | suite.add('sign and verify', async (d) => {
22 | const key = keys[0]
23 | const text = key.genSecret()
24 |
25 | const sig = await key.sign(text)
26 | const res = await key.public.verify(text, sig)
27 |
28 | if (res !== true) { throw new Error('failed to verify') }
29 | d.resolve()
30 | }, {
31 | defer: true
32 | })
33 |
34 | suite
35 | .on('cycle', (event) => console.log(String(event.target)))
36 | .run({ async: true })
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@libp2p/crypto",
3 | "version": "1.0.17",
4 | "description": "Crypto primitives for libp2p",
5 | "license": "Apache-2.0 OR MIT",
6 | "homepage": "https://github.com/libp2p/js-libp2p-crypto#readme",
7 | "repository": {
8 | "type": "git",
9 | "url": "git+https://github.com/libp2p/js-libp2p-crypto.git"
10 | },
11 | "bugs": {
12 | "url": "https://github.com/libp2p/js-libp2p-crypto/issues"
13 | },
14 | "keywords": [
15 | "IPFS",
16 | "crypto",
17 | "libp2p",
18 | "rsa",
19 | "secp256k1"
20 | ],
21 | "engines": {
22 | "node": ">=16.0.0",
23 | "npm": ">=7.0.0"
24 | },
25 | "type": "module",
26 | "types": "./dist/src/index.d.ts",
27 | "typesVersions": {
28 | "*": {
29 | "*": [
30 | "*",
31 | "dist/*",
32 | "dist/src/*",
33 | "dist/src/*/index"
34 | ],
35 | "src/*": [
36 | "*",
37 | "dist/*",
38 | "dist/src/*",
39 | "dist/src/*/index"
40 | ]
41 | }
42 | },
43 | "files": [
44 | "src",
45 | "dist",
46 | "!dist/test",
47 | "!**/*.tsbuildinfo"
48 | ],
49 | "exports": {
50 | ".": {
51 | "types": "./src/index.d.ts",
52 | "import": "./dist/src/index.js"
53 | },
54 | "./aes": {
55 | "types": "./dist/src/aes/index.d.ts",
56 | "import": "./dist/src/aes/index.js"
57 | },
58 | "./ciphers": {
59 | "types": "./dist/src/ciphers/index.d.ts",
60 | "import": "./dist/src/ciphers/index.js"
61 | },
62 | "./hmac": {
63 | "types": "./dist/src/hmac/index.d.ts",
64 | "import": "./dist/src/hmac/index.js"
65 | },
66 | "./keys": {
67 | "types": "./dist/src/keys/index.d.ts",
68 | "import": "./dist/src/keys/index.js"
69 | }
70 | },
71 | "eslintConfig": {
72 | "extends": "ipfs",
73 | "parserOptions": {
74 | "sourceType": "module"
75 | },
76 | "ignorePatterns": [
77 | "src/*.d.ts"
78 | ]
79 | },
80 | "release": {
81 | "branches": [
82 | "master"
83 | ],
84 | "plugins": [
85 | [
86 | "@semantic-release/commit-analyzer",
87 | {
88 | "preset": "conventionalcommits",
89 | "releaseRules": [
90 | {
91 | "breaking": true,
92 | "release": "major"
93 | },
94 | {
95 | "revert": true,
96 | "release": "patch"
97 | },
98 | {
99 | "type": "feat",
100 | "release": "minor"
101 | },
102 | {
103 | "type": "fix",
104 | "release": "patch"
105 | },
106 | {
107 | "type": "docs",
108 | "release": "patch"
109 | },
110 | {
111 | "type": "test",
112 | "release": "patch"
113 | },
114 | {
115 | "type": "deps",
116 | "release": "patch"
117 | },
118 | {
119 | "scope": "no-release",
120 | "release": false
121 | }
122 | ]
123 | }
124 | ],
125 | [
126 | "@semantic-release/release-notes-generator",
127 | {
128 | "preset": "conventionalcommits",
129 | "presetConfig": {
130 | "types": [
131 | {
132 | "type": "feat",
133 | "section": "Features"
134 | },
135 | {
136 | "type": "fix",
137 | "section": "Bug Fixes"
138 | },
139 | {
140 | "type": "chore",
141 | "section": "Trivial Changes"
142 | },
143 | {
144 | "type": "docs",
145 | "section": "Documentation"
146 | },
147 | {
148 | "type": "deps",
149 | "section": "Dependencies"
150 | },
151 | {
152 | "type": "test",
153 | "section": "Tests"
154 | }
155 | ]
156 | }
157 | }
158 | ],
159 | "@semantic-release/changelog",
160 | "@semantic-release/npm",
161 | "@semantic-release/github",
162 | "@semantic-release/git"
163 | ]
164 | },
165 | "scripts": {
166 | "clean": "aegir clean",
167 | "lint": "aegir lint",
168 | "dep-check": "aegir dep-check -i protons",
169 | "build": "aegir build",
170 | "test": "aegir test",
171 | "test:chrome": "aegir test -t browser",
172 | "test:chrome-webworker": "aegir test -t webworker",
173 | "test:firefox": "aegir test -t browser -- --browser firefox",
174 | "test:firefox-webworker": "aegir test -t webworker -- --browser firefox",
175 | "test:webkit": "bash -c '[ \"${CI}\" == \"true\" ] && playwright install-deps'; aegir test -t browser -- --browser webkit",
176 | "test:node": "aegir test -t node --cov",
177 | "test:electron-main": "aegir test -t electron-main",
178 | "release": "aegir release",
179 | "docs": "aegir docs",
180 | "generate": "protons ./src/keys/keys.proto"
181 | },
182 | "dependencies": {
183 | "@libp2p/interface-keys": "^1.0.2",
184 | "@libp2p/interfaces": "^3.2.0",
185 | "@noble/ed25519": "^1.6.0",
186 | "@noble/secp256k1": "^1.5.4",
187 | "multiformats": "^11.0.0",
188 | "node-forge": "^1.1.0",
189 | "protons-runtime": "^5.0.0",
190 | "uint8arraylist": "^2.4.3",
191 | "uint8arrays": "^4.0.2"
192 | },
193 | "devDependencies": {
194 | "@types/mocha": "^10.0.0",
195 | "aegir": "^39.0.5",
196 | "benchmark": "^2.1.4",
197 | "protons": "^7.0.2"
198 | },
199 | "browser": {
200 | "./dist/src/aes/ciphers.js": "./dist/src/aes/ciphers-browser.js",
201 | "./dist/src/ciphers/aes-gcm.js": "./dist/src/ciphers/aes-gcm.browser.js",
202 | "./dist/src/hmac/index.js": "./dist/src/hmac/index-browser.js",
203 | "./dist/src/keys/ecdh.js": "./dist/src/keys/ecdh-browser.js",
204 | "./dist/src/keys/ed25519.js": "./dist/src/keys/ed25519-browser.js",
205 | "./dist/src/keys/rsa.js": "./dist/src/keys/rsa-browser.js"
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/src/aes/cipher-mode.ts:
--------------------------------------------------------------------------------
1 | import { CodeError } from '@libp2p/interfaces/errors'
2 |
3 | const CIPHER_MODES = {
4 | 16: 'aes-128-ctr',
5 | 32: 'aes-256-ctr'
6 | }
7 |
8 | export function cipherMode (key: Uint8Array): string {
9 | if (key.length === 16 || key.length === 32) {
10 | return CIPHER_MODES[key.length]
11 | }
12 |
13 | const modes = Object.entries(CIPHER_MODES).map(([k, v]) => `${k} (${v})`).join(' / ')
14 | throw new CodeError(`Invalid key length ${key.length} bytes. Must be ${modes}`, 'ERR_INVALID_KEY_LENGTH')
15 | }
16 |
--------------------------------------------------------------------------------
/src/aes/ciphers-browser.ts:
--------------------------------------------------------------------------------
1 |
2 | import 'node-forge/lib/aes.js'
3 | // @ts-expect-error types are missing
4 | import forge from 'node-forge/lib/forge.js'
5 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
6 | import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
7 |
8 | export interface Cipher {
9 | update: (data: Uint8Array) => Uint8Array
10 | }
11 |
12 | export function createCipheriv (mode: any, key: Uint8Array, iv: Uint8Array): Cipher {
13 | const cipher2 = forge.cipher.createCipher('AES-CTR', uint8ArrayToString(key, 'ascii'))
14 | cipher2.start({ iv: uint8ArrayToString(iv, 'ascii') })
15 | return {
16 | update: (data: Uint8Array) => {
17 | cipher2.update(forge.util.createBuffer(uint8ArrayToString(data, 'ascii')))
18 | return uint8ArrayFromString(cipher2.output.getBytes(), 'ascii')
19 | }
20 | }
21 | }
22 |
23 | export function createDecipheriv (mode: any, key: Uint8Array, iv: Uint8Array): Cipher {
24 | const cipher2 = forge.cipher.createDecipher('AES-CTR', uint8ArrayToString(key, 'ascii'))
25 | cipher2.start({ iv: uint8ArrayToString(iv, 'ascii') })
26 | return {
27 | update: (data: Uint8Array) => {
28 | cipher2.update(forge.util.createBuffer(uint8ArrayToString(data, 'ascii')))
29 | return uint8ArrayFromString(cipher2.output.getBytes(), 'ascii')
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/aes/ciphers.ts:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto'
2 |
3 | export const createCipheriv = crypto.createCipheriv
4 | export const createDecipheriv = crypto.createDecipheriv
5 |
--------------------------------------------------------------------------------
/src/aes/index.ts:
--------------------------------------------------------------------------------
1 | import { cipherMode } from './cipher-mode.js'
2 | import * as ciphers from './ciphers.js'
3 |
4 | export interface AESCipher {
5 | encrypt: (data: Uint8Array) => Promise
6 | decrypt: (data: Uint8Array) => Promise
7 | }
8 |
9 | export async function create (key: Uint8Array, iv: Uint8Array): Promise { // eslint-disable-line require-await
10 | const mode = cipherMode(key)
11 | const cipher = ciphers.createCipheriv(mode, key, iv)
12 | const decipher = ciphers.createDecipheriv(mode, key, iv)
13 |
14 | const res: AESCipher = {
15 | async encrypt (data) { // eslint-disable-line require-await
16 | return cipher.update(data)
17 | },
18 |
19 | async decrypt (data) { // eslint-disable-line require-await
20 | return decipher.update(data)
21 | }
22 | }
23 |
24 | return res
25 | }
26 |
--------------------------------------------------------------------------------
/src/ciphers/aes-gcm.browser.ts:
--------------------------------------------------------------------------------
1 | import { concat } from 'uint8arrays/concat'
2 | import { fromString } from 'uint8arrays/from-string'
3 | import webcrypto from '../webcrypto.js'
4 | import type { CreateOptions, AESCipher } from './interface.js'
5 |
6 | // WebKit on Linux does not support deriving a key from an empty PBKDF2 key.
7 | // So, as a workaround, we provide the generated key as a constant. We test that
8 | // this generated key is accurate in test/workaround.spec.ts
9 | // Generated via:
10 | // await crypto.subtle.exportKey('jwk',
11 | // await crypto.subtle.deriveKey(
12 | // { name: 'PBKDF2', salt: new Uint8Array(16), iterations: 32767, hash: { name: 'SHA-256' } },
13 | // await crypto.subtle.importKey('raw', new Uint8Array(0), { name: 'PBKDF2' }, false, ['deriveKey']),
14 | // { name: 'AES-GCM', length: 128 }, true, ['encrypt', 'decrypt'])
15 | // )
16 | export const derivedEmptyPasswordKey = { alg: 'A128GCM', ext: true, k: 'scm9jmO_4BJAgdwWGVulLg', key_ops: ['encrypt', 'decrypt'], kty: 'oct' }
17 |
18 | // Based off of code from https://github.com/luke-park/SecureCompatibleEncryptionExamples
19 |
20 | export function create (opts?: CreateOptions): AESCipher {
21 | const algorithm = opts?.algorithm ?? 'AES-GCM'
22 | let keyLength = opts?.keyLength ?? 16
23 | const nonceLength = opts?.nonceLength ?? 12
24 | const digest = opts?.digest ?? 'SHA-256'
25 | const saltLength = opts?.saltLength ?? 16
26 | const iterations = opts?.iterations ?? 32767
27 |
28 | const crypto = webcrypto.get()
29 | keyLength *= 8 // Browser crypto uses bits instead of bytes
30 |
31 | /**
32 | * Uses the provided password to derive a pbkdf2 key. The key
33 | * will then be used to encrypt the data.
34 | */
35 | async function encrypt (data: Uint8Array, password: string | Uint8Array): Promise { // eslint-disable-line require-await
36 | const salt = crypto.getRandomValues(new Uint8Array(saltLength))
37 | const nonce = crypto.getRandomValues(new Uint8Array(nonceLength))
38 | const aesGcm = { name: algorithm, iv: nonce }
39 |
40 | if (typeof password === 'string') {
41 | password = fromString(password)
42 | }
43 |
44 | let cryptoKey: CryptoKey
45 | if (password.length === 0) {
46 | cryptoKey = await crypto.subtle.importKey('jwk', derivedEmptyPasswordKey, { name: 'AES-GCM' }, true, ['encrypt'])
47 | try {
48 | const deriveParams = { name: 'PBKDF2', salt, iterations, hash: { name: digest } }
49 | const runtimeDerivedEmptyPassword = await crypto.subtle.importKey('raw', password, { name: 'PBKDF2' }, false, ['deriveKey'])
50 | cryptoKey = await crypto.subtle.deriveKey(deriveParams, runtimeDerivedEmptyPassword, { name: algorithm, length: keyLength }, true, ['encrypt'])
51 | } catch {
52 | cryptoKey = await crypto.subtle.importKey('jwk', derivedEmptyPasswordKey, { name: 'AES-GCM' }, true, ['encrypt'])
53 | }
54 | } else {
55 | // Derive a key using PBKDF2.
56 | const deriveParams = { name: 'PBKDF2', salt, iterations, hash: { name: digest } }
57 | const rawKey = await crypto.subtle.importKey('raw', password, { name: 'PBKDF2' }, false, ['deriveKey'])
58 | cryptoKey = await crypto.subtle.deriveKey(deriveParams, rawKey, { name: algorithm, length: keyLength }, true, ['encrypt'])
59 | }
60 |
61 | // Encrypt the string.
62 | const ciphertext = await crypto.subtle.encrypt(aesGcm, cryptoKey, data)
63 | return concat([salt, aesGcm.iv, new Uint8Array(ciphertext)])
64 | }
65 |
66 | /**
67 | * Uses the provided password to derive a pbkdf2 key. The key
68 | * will then be used to decrypt the data. The options used to create
69 | * this decryption cipher must be the same as those used to create
70 | * the encryption cipher.
71 | */
72 | async function decrypt (data: Uint8Array, password: string | Uint8Array): Promise {
73 | const salt = data.subarray(0, saltLength)
74 | const nonce = data.subarray(saltLength, saltLength + nonceLength)
75 | const ciphertext = data.subarray(saltLength + nonceLength)
76 | const aesGcm = { name: algorithm, iv: nonce }
77 |
78 | if (typeof password === 'string') {
79 | password = fromString(password)
80 | }
81 |
82 | let cryptoKey: CryptoKey
83 | if (password.length === 0) {
84 | try {
85 | const deriveParams = { name: 'PBKDF2', salt, iterations, hash: { name: digest } }
86 | const runtimeDerivedEmptyPassword = await crypto.subtle.importKey('raw', password, { name: 'PBKDF2' }, false, ['deriveKey'])
87 | cryptoKey = await crypto.subtle.deriveKey(deriveParams, runtimeDerivedEmptyPassword, { name: algorithm, length: keyLength }, true, ['decrypt'])
88 | } catch {
89 | cryptoKey = await crypto.subtle.importKey('jwk', derivedEmptyPasswordKey, { name: 'AES-GCM' }, true, ['decrypt'])
90 | }
91 | } else {
92 | // Derive the key using PBKDF2.
93 | const deriveParams = { name: 'PBKDF2', salt, iterations, hash: { name: digest } }
94 | const rawKey = await crypto.subtle.importKey('raw', password, { name: 'PBKDF2' }, false, ['deriveKey'])
95 | cryptoKey = await crypto.subtle.deriveKey(deriveParams, rawKey, { name: algorithm, length: keyLength }, true, ['decrypt'])
96 | }
97 |
98 | // Decrypt the string.
99 | const plaintext = await crypto.subtle.decrypt(aesGcm, cryptoKey, ciphertext)
100 | return new Uint8Array(plaintext)
101 | }
102 |
103 | const cipher: AESCipher = {
104 | encrypt,
105 | decrypt
106 | }
107 |
108 | return cipher
109 | }
110 |
--------------------------------------------------------------------------------
/src/ciphers/aes-gcm.ts:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto'
2 | import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
3 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
4 | import type { CreateOptions, AESCipher } from './interface.js'
5 |
6 | // Based off of code from https://github.com/luke-park/SecureCompatibleEncryptionExamples
7 |
8 | export function create (opts?: CreateOptions): AESCipher {
9 | const algorithm = opts?.algorithm ?? 'aes-128-gcm'
10 | const keyLength = opts?.keyLength ?? 16
11 | const nonceLength = opts?.nonceLength ?? 12
12 | const digest = opts?.digest ?? 'sha256'
13 | const saltLength = opts?.saltLength ?? 16
14 | const iterations = opts?.iterations ?? 32767
15 | const algorithmTagLength = opts?.algorithmTagLength ?? 16
16 |
17 | async function encryptWithKey (data: Uint8Array, key: Uint8Array): Promise { // eslint-disable-line require-await
18 | const nonce = crypto.randomBytes(nonceLength)
19 |
20 | // Create the cipher instance.
21 | const cipher = crypto.createCipheriv(algorithm, key, nonce)
22 |
23 | // Encrypt and prepend nonce.
24 | const ciphertext = uint8ArrayConcat([cipher.update(data), cipher.final()])
25 |
26 | // @ts-expect-error getAuthTag is not a function
27 | return uint8ArrayConcat([nonce, ciphertext, cipher.getAuthTag()])
28 | }
29 |
30 | /**
31 | * Uses the provided password to derive a pbkdf2 key. The key
32 | * will then be used to encrypt the data.
33 | */
34 | async function encrypt (data: Uint8Array, password: string | Uint8Array): Promise { // eslint-disable-line require-await
35 | // Generate a 128-bit salt using a CSPRNG.
36 | const salt = crypto.randomBytes(saltLength)
37 |
38 | if (typeof password === 'string') {
39 | password = uint8ArrayFromString(password)
40 | }
41 |
42 | // Derive a key using PBKDF2.
43 | const key = crypto.pbkdf2Sync(password, salt, iterations, keyLength, digest)
44 |
45 | // Encrypt and prepend salt.
46 | return uint8ArrayConcat([salt, await encryptWithKey(Uint8Array.from(data), key)])
47 | }
48 |
49 | /**
50 | * Decrypts the given cipher text with the provided key. The `key` should
51 | * be a cryptographically safe key and not a plaintext password. To use
52 | * a plaintext password, use `decrypt`. The options used to create
53 | * this decryption cipher must be the same as those used to create
54 | * the encryption cipher.
55 | */
56 | async function decryptWithKey (ciphertextAndNonce: Uint8Array, key: Uint8Array): Promise { // eslint-disable-line require-await
57 | // Create Uint8Arrays of nonce, ciphertext and tag.
58 | const nonce = ciphertextAndNonce.subarray(0, nonceLength)
59 | const ciphertext = ciphertextAndNonce.subarray(nonceLength, ciphertextAndNonce.length - algorithmTagLength)
60 | const tag = ciphertextAndNonce.subarray(ciphertext.length + nonceLength)
61 |
62 | // Create the cipher instance.
63 | const cipher = crypto.createDecipheriv(algorithm, key, nonce)
64 |
65 | // Decrypt and return result.
66 | // @ts-expect-error getAuthTag is not a function
67 | cipher.setAuthTag(tag)
68 | return uint8ArrayConcat([cipher.update(ciphertext), cipher.final()])
69 | }
70 |
71 | /**
72 | * Uses the provided password to derive a pbkdf2 key. The key
73 | * will then be used to decrypt the data. The options used to create
74 | * this decryption cipher must be the same as those used to create
75 | * the encryption cipher.
76 | *
77 | * @param {Uint8Array} data - The data to decrypt
78 | * @param {string|Uint8Array} password - A plain password
79 | */
80 | async function decrypt (data: Uint8Array, password: string | Uint8Array): Promise { // eslint-disable-line require-await
81 | // Create Uint8Arrays of salt and ciphertextAndNonce.
82 | const salt = data.subarray(0, saltLength)
83 | const ciphertextAndNonce = data.subarray(saltLength)
84 |
85 | if (typeof password === 'string') {
86 | password = uint8ArrayFromString(password)
87 | }
88 |
89 | // Derive the key using PBKDF2.
90 | const key = crypto.pbkdf2Sync(password, salt, iterations, keyLength, digest)
91 |
92 | // Decrypt and return result.
93 | return decryptWithKey(ciphertextAndNonce, key)
94 | }
95 |
96 | const cipher: AESCipher = {
97 | encrypt,
98 | decrypt
99 | }
100 |
101 | return cipher
102 | }
103 |
--------------------------------------------------------------------------------
/src/ciphers/interface.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface CreateOptions {
3 | algorithm?: string
4 | nonceLength?: number
5 | keyLength?: number
6 | digest?: string
7 | saltLength?: number
8 | iterations?: number
9 | algorithmTagLength?: number
10 | }
11 |
12 | export interface AESCipher {
13 | encrypt: (data: Uint8Array, password: string | Uint8Array) => Promise
14 | decrypt: (data: Uint8Array, password: string | Uint8Array) => Promise
15 | }
16 |
--------------------------------------------------------------------------------
/src/hmac/index-browser.ts:
--------------------------------------------------------------------------------
1 | import webcrypto from '../webcrypto.js'
2 | import lengths from './lengths.js'
3 |
4 | const hashTypes = {
5 | SHA1: 'SHA-1',
6 | SHA256: 'SHA-256',
7 | SHA512: 'SHA-512'
8 | }
9 |
10 | const sign = async (key: CryptoKey, data: Uint8Array): Promise => {
11 | const buf = await webcrypto.get().subtle.sign({ name: 'HMAC' }, key, data)
12 | return new Uint8Array(buf, 0, buf.byteLength)
13 | }
14 |
15 | export async function create (hashType: 'SHA1' | 'SHA256' | 'SHA512', secret: Uint8Array): Promise<{ digest: (data: Uint8Array) => Promise, length: number }> {
16 | const hash = hashTypes[hashType]
17 |
18 | const key = await webcrypto.get().subtle.importKey(
19 | 'raw',
20 | secret,
21 | {
22 | name: 'HMAC',
23 | hash: { name: hash }
24 | },
25 | false,
26 | ['sign']
27 | )
28 |
29 | return {
30 | async digest (data: Uint8Array) { // eslint-disable-line require-await
31 | return sign(key, data)
32 | },
33 | length: lengths[hashType]
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/hmac/index.ts:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto'
2 | import lengths from './lengths.js'
3 |
4 | export interface HMAC {
5 | digest: (data: Uint8Array) => Promise
6 | length: number
7 | }
8 |
9 | export async function create (hash: 'SHA1' | 'SHA256' | 'SHA512', secret: Uint8Array): Promise {
10 | const res = {
11 | async digest (data: Uint8Array) { // eslint-disable-line require-await
12 | const hmac = crypto.createHmac(hash.toLowerCase(), secret)
13 | hmac.update(data)
14 | return hmac.digest()
15 | },
16 | length: lengths[hash]
17 | }
18 |
19 | return res
20 | }
21 |
--------------------------------------------------------------------------------
/src/hmac/lengths.ts:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | SHA1: 20,
4 | SHA256: 32,
5 | SHA512: 64
6 | }
7 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as aes from './aes/index.js'
2 | import * as hmac from './hmac/index.js'
3 | import * as keys from './keys/index.js'
4 | import pbkdf2 from './pbkdf2.js'
5 | import randomBytes from './random-bytes.js'
6 |
7 | export { aes }
8 | export { hmac }
9 | export { keys }
10 | export { randomBytes }
11 | export { pbkdf2 }
12 |
--------------------------------------------------------------------------------
/src/keys/ecdh-browser.ts:
--------------------------------------------------------------------------------
1 | import { CodeError } from '@libp2p/interfaces/errors'
2 | import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
3 | import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
4 | import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
5 | import { base64urlToBuffer } from '../util.js'
6 | import webcrypto from '../webcrypto.js'
7 | import type { ECDHKey, ECDHKeyPair, JWKEncodedPrivateKey, JWKEncodedPublicKey } from './interface.js'
8 |
9 | const bits = {
10 | 'P-256': 256,
11 | 'P-384': 384,
12 | 'P-521': 521
13 | }
14 |
15 | const curveTypes = Object.keys(bits)
16 | const names = curveTypes.join(' / ')
17 |
18 | export async function generateEphmeralKeyPair (curve: string): Promise {
19 | if (curve !== 'P-256' && curve !== 'P-384' && curve !== 'P-521') {
20 | throw new CodeError(`Unknown curve: ${curve}. Must be ${names}`, 'ERR_INVALID_CURVE')
21 | }
22 |
23 | const pair = await webcrypto.get().subtle.generateKey(
24 | {
25 | name: 'ECDH',
26 | namedCurve: curve
27 | },
28 | true,
29 | ['deriveBits']
30 | )
31 |
32 | // forcePrivate is used for testing only
33 | const genSharedKey = async (theirPub: Uint8Array, forcePrivate?: ECDHKeyPair): Promise => {
34 | let privateKey
35 |
36 | if (forcePrivate != null) {
37 | privateKey = await webcrypto.get().subtle.importKey(
38 | 'jwk',
39 | unmarshalPrivateKey(curve, forcePrivate),
40 | {
41 | name: 'ECDH',
42 | namedCurve: curve
43 | },
44 | false,
45 | ['deriveBits']
46 | )
47 | } else {
48 | privateKey = pair.privateKey
49 | }
50 |
51 | const key = await webcrypto.get().subtle.importKey(
52 | 'jwk',
53 | unmarshalPublicKey(curve, theirPub),
54 | {
55 | name: 'ECDH',
56 | namedCurve: curve
57 | },
58 | false,
59 | []
60 | )
61 |
62 | const buffer = await webcrypto.get().subtle.deriveBits(
63 | {
64 | name: 'ECDH',
65 | // @ts-expect-error namedCurve is missing from the types
66 | namedCurve: curve,
67 | public: key
68 | },
69 | privateKey,
70 | bits[curve]
71 | )
72 |
73 | return new Uint8Array(buffer, 0, buffer.byteLength)
74 | }
75 |
76 | const publicKey = await webcrypto.get().subtle.exportKey('jwk', pair.publicKey)
77 |
78 | const ecdhKey: ECDHKey = {
79 | key: marshalPublicKey(publicKey),
80 | genSharedKey
81 | }
82 |
83 | return ecdhKey
84 | }
85 |
86 | const curveLengths = {
87 | 'P-256': 32,
88 | 'P-384': 48,
89 | 'P-521': 66
90 | }
91 |
92 | // Marshal converts a jwk encoded ECDH public key into the
93 | // form specified in section 4.3.6 of ANSI X9.62. (This is the format
94 | // go-ipfs uses)
95 | function marshalPublicKey (jwk: JsonWebKey): Uint8Array {
96 | if (jwk.crv == null || jwk.x == null || jwk.y == null) {
97 | throw new CodeError('JWK was missing components', 'ERR_INVALID_PARAMETERS')
98 | }
99 |
100 | if (jwk.crv !== 'P-256' && jwk.crv !== 'P-384' && jwk.crv !== 'P-521') {
101 | throw new CodeError(`Unknown curve: ${jwk.crv}. Must be ${names}`, 'ERR_INVALID_CURVE')
102 | }
103 |
104 | const byteLen = curveLengths[jwk.crv]
105 |
106 | return uint8ArrayConcat([
107 | Uint8Array.from([4]), // uncompressed point
108 | base64urlToBuffer(jwk.x, byteLen),
109 | base64urlToBuffer(jwk.y, byteLen)
110 | ], 1 + byteLen * 2)
111 | }
112 |
113 | // Unmarshal converts a point, serialized by Marshal, into an jwk encoded key
114 | function unmarshalPublicKey (curve: string, key: Uint8Array): JWKEncodedPublicKey {
115 | if (curve !== 'P-256' && curve !== 'P-384' && curve !== 'P-521') {
116 | throw new CodeError(`Unknown curve: ${curve}. Must be ${names}`, 'ERR_INVALID_CURVE')
117 | }
118 |
119 | const byteLen = curveLengths[curve]
120 |
121 | if (!uint8ArrayEquals(key.subarray(0, 1), Uint8Array.from([4]))) {
122 | throw new CodeError('Cannot unmarshal public key - invalid key format', 'ERR_INVALID_KEY_FORMAT')
123 | }
124 |
125 | return {
126 | kty: 'EC',
127 | crv: curve,
128 | x: uint8ArrayToString(key.subarray(1, byteLen + 1), 'base64url'),
129 | y: uint8ArrayToString(key.subarray(1 + byteLen), 'base64url'),
130 | ext: true
131 | }
132 | }
133 |
134 | const unmarshalPrivateKey = (curve: string, key: ECDHKeyPair): JWKEncodedPrivateKey => ({
135 | ...unmarshalPublicKey(curve, key.public),
136 | d: uint8ArrayToString(key.private, 'base64url')
137 | })
138 |
--------------------------------------------------------------------------------
/src/keys/ecdh.ts:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto'
2 | import { CodeError } from '@libp2p/interfaces/errors'
3 | import type { ECDHKey, ECDHKeyPair } from './interface.js'
4 |
5 | const curves = {
6 | 'P-256': 'prime256v1',
7 | 'P-384': 'secp384r1',
8 | 'P-521': 'secp521r1'
9 | }
10 |
11 | const curveTypes = Object.keys(curves)
12 | const names = curveTypes.join(' / ')
13 |
14 | export async function generateEphmeralKeyPair (curve: string): Promise { // eslint-disable-line require-await
15 | if (curve !== 'P-256' && curve !== 'P-384' && curve !== 'P-521') {
16 | throw new CodeError(`Unknown curve: ${curve}. Must be ${names}`, 'ERR_INVALID_CURVE')
17 | }
18 |
19 | const ecdh = crypto.createECDH(curves[curve])
20 | ecdh.generateKeys()
21 |
22 | return {
23 | key: ecdh.getPublicKey() as Uint8Array,
24 |
25 | async genSharedKey (theirPub: Uint8Array, forcePrivate?: ECDHKeyPair): Promise { // eslint-disable-line require-await
26 | if (forcePrivate != null) {
27 | ecdh.setPrivateKey(forcePrivate.private)
28 | }
29 |
30 | return ecdh.computeSecret(theirPub)
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/keys/ed25519-browser.ts:
--------------------------------------------------------------------------------
1 | import * as ed from '@noble/ed25519'
2 | import type { Uint8ArrayKeyPair } from './interface'
3 |
4 | const PUBLIC_KEY_BYTE_LENGTH = 32
5 | const PRIVATE_KEY_BYTE_LENGTH = 64 // private key is actually 32 bytes but for historical reasons we concat private and public keys
6 | const KEYS_BYTE_LENGTH = 32
7 |
8 | export { PUBLIC_KEY_BYTE_LENGTH as publicKeyLength }
9 | export { PRIVATE_KEY_BYTE_LENGTH as privateKeyLength }
10 |
11 | export async function generateKey (): Promise {
12 | // the actual private key (32 bytes)
13 | const privateKeyRaw = ed.utils.randomPrivateKey()
14 | const publicKey = await ed.getPublicKey(privateKeyRaw)
15 |
16 | // concatenated the public key to the private key
17 | const privateKey = concatKeys(privateKeyRaw, publicKey)
18 |
19 | return {
20 | privateKey,
21 | publicKey
22 | }
23 | }
24 |
25 | /**
26 | * Generate keypair from a 32 byte uint8array
27 | */
28 | export async function generateKeyFromSeed (seed: Uint8Array): Promise {
29 | if (seed.length !== KEYS_BYTE_LENGTH) {
30 | throw new TypeError('"seed" must be 32 bytes in length.')
31 | } else if (!(seed instanceof Uint8Array)) {
32 | throw new TypeError('"seed" must be a node.js Buffer, or Uint8Array.')
33 | }
34 |
35 | // based on node forges algorithm, the seed is used directly as private key
36 | const privateKeyRaw = seed
37 | const publicKey = await ed.getPublicKey(privateKeyRaw)
38 |
39 | const privateKey = concatKeys(privateKeyRaw, publicKey)
40 |
41 | return {
42 | privateKey,
43 | publicKey
44 | }
45 | }
46 |
47 | export async function hashAndSign (privateKey: Uint8Array, msg: Uint8Array): Promise {
48 | const privateKeyRaw = privateKey.subarray(0, KEYS_BYTE_LENGTH)
49 |
50 | return ed.sign(msg, privateKeyRaw)
51 | }
52 |
53 | export async function hashAndVerify (publicKey: Uint8Array, sig: Uint8Array, msg: Uint8Array): Promise {
54 | return ed.verify(sig, msg, publicKey)
55 | }
56 |
57 | function concatKeys (privateKeyRaw: Uint8Array, publicKey: Uint8Array): Uint8Array {
58 | const privateKey = new Uint8Array(PRIVATE_KEY_BYTE_LENGTH)
59 | for (let i = 0; i < KEYS_BYTE_LENGTH; i++) {
60 | privateKey[i] = privateKeyRaw[i]
61 | privateKey[KEYS_BYTE_LENGTH + i] = publicKey[i]
62 | }
63 | return privateKey
64 | }
65 |
--------------------------------------------------------------------------------
/src/keys/ed25519-class.ts:
--------------------------------------------------------------------------------
1 | import { CodeError } from '@libp2p/interfaces/errors'
2 | import { base58btc } from 'multiformats/bases/base58'
3 | import { identity } from 'multiformats/hashes/identity'
4 | import { sha256 } from 'multiformats/hashes/sha2'
5 | import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
6 | import * as crypto from './ed25519.js'
7 | import { exporter } from './exporter.js'
8 | import * as pbm from './keys.js'
9 | import type { Multibase } from 'multiformats'
10 |
11 | export class Ed25519PublicKey {
12 | private readonly _key: Uint8Array
13 |
14 | constructor (key: Uint8Array) {
15 | this._key = ensureKey(key, crypto.publicKeyLength)
16 | }
17 |
18 | async verify (data: Uint8Array, sig: Uint8Array): Promise { // eslint-disable-line require-await
19 | return crypto.hashAndVerify(this._key, sig, data)
20 | }
21 |
22 | marshal (): Uint8Array {
23 | return this._key
24 | }
25 |
26 | get bytes (): Uint8Array {
27 | return pbm.PublicKey.encode({
28 | Type: pbm.KeyType.Ed25519,
29 | Data: this.marshal()
30 | }).subarray()
31 | }
32 |
33 | equals (key: any): boolean {
34 | return uint8ArrayEquals(this.bytes, key.bytes)
35 | }
36 |
37 | async hash (): Promise {
38 | const { bytes } = await sha256.digest(this.bytes)
39 |
40 | return bytes
41 | }
42 | }
43 |
44 | export class Ed25519PrivateKey {
45 | private readonly _key: Uint8Array
46 | private readonly _publicKey: Uint8Array
47 |
48 | // key - 64 byte Uint8Array containing private key
49 | // publicKey - 32 byte Uint8Array containing public key
50 | constructor (key: Uint8Array, publicKey: Uint8Array) {
51 | this._key = ensureKey(key, crypto.privateKeyLength)
52 | this._publicKey = ensureKey(publicKey, crypto.publicKeyLength)
53 | }
54 |
55 | async sign (message: Uint8Array): Promise { // eslint-disable-line require-await
56 | return crypto.hashAndSign(this._key, message)
57 | }
58 |
59 | get public (): Ed25519PublicKey {
60 | return new Ed25519PublicKey(this._publicKey)
61 | }
62 |
63 | marshal (): Uint8Array {
64 | return this._key
65 | }
66 |
67 | get bytes (): Uint8Array {
68 | return pbm.PrivateKey.encode({
69 | Type: pbm.KeyType.Ed25519,
70 | Data: this.marshal()
71 | }).subarray()
72 | }
73 |
74 | equals (key: any): boolean {
75 | return uint8ArrayEquals(this.bytes, key.bytes)
76 | }
77 |
78 | async hash (): Promise {
79 | const { bytes } = await sha256.digest(this.bytes)
80 |
81 | return bytes
82 | }
83 |
84 | /**
85 | * Gets the ID of the key.
86 | *
87 | * The key id is the base58 encoding of the identity multihash containing its public key.
88 | * The public key is a protobuf encoding containing a type and the DER encoding
89 | * of the PKCS SubjectPublicKeyInfo.
90 | *
91 | * @returns {Promise}
92 | */
93 | async id (): Promise {
94 | const encoding = identity.digest(this.public.bytes)
95 | return base58btc.encode(encoding.bytes).substring(1)
96 | }
97 |
98 | /**
99 | * Exports the key into a password protected `format`
100 | */
101 | async export (password: string, format = 'libp2p-key'): Promise> {
102 | if (format === 'libp2p-key') {
103 | return exporter(this.bytes, password)
104 | } else {
105 | throw new CodeError(`export format '${format}' is not supported`, 'ERR_INVALID_EXPORT_FORMAT')
106 | }
107 | }
108 | }
109 |
110 | export function unmarshalEd25519PrivateKey (bytes: Uint8Array): Ed25519PrivateKey {
111 | // Try the old, redundant public key version
112 | if (bytes.length > crypto.privateKeyLength) {
113 | bytes = ensureKey(bytes, crypto.privateKeyLength + crypto.publicKeyLength)
114 | const privateKeyBytes = bytes.subarray(0, crypto.privateKeyLength)
115 | const publicKeyBytes = bytes.subarray(crypto.privateKeyLength, bytes.length)
116 | return new Ed25519PrivateKey(privateKeyBytes, publicKeyBytes)
117 | }
118 |
119 | bytes = ensureKey(bytes, crypto.privateKeyLength)
120 | const privateKeyBytes = bytes.subarray(0, crypto.privateKeyLength)
121 | const publicKeyBytes = bytes.subarray(crypto.publicKeyLength)
122 | return new Ed25519PrivateKey(privateKeyBytes, publicKeyBytes)
123 | }
124 |
125 | export function unmarshalEd25519PublicKey (bytes: Uint8Array): Ed25519PublicKey {
126 | bytes = ensureKey(bytes, crypto.publicKeyLength)
127 | return new Ed25519PublicKey(bytes)
128 | }
129 |
130 | export async function generateKeyPair (): Promise {
131 | const { privateKey, publicKey } = await crypto.generateKey()
132 | return new Ed25519PrivateKey(privateKey, publicKey)
133 | }
134 |
135 | export async function generateKeyPairFromSeed (seed: Uint8Array): Promise {
136 | const { privateKey, publicKey } = await crypto.generateKeyFromSeed(seed)
137 | return new Ed25519PrivateKey(privateKey, publicKey)
138 | }
139 |
140 | function ensureKey (key: Uint8Array, length: number): Uint8Array {
141 | key = Uint8Array.from(key ?? [])
142 | if (key.length !== length) {
143 | throw new CodeError(`Key must be a Uint8Array of length ${length}, got ${key.length}`, 'ERR_INVALID_KEY_TYPE')
144 | }
145 | return key
146 | }
147 |
--------------------------------------------------------------------------------
/src/keys/ed25519.ts:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto'
2 | import { promisify } from 'util'
3 | import { fromString as uint8arrayFromString } from 'uint8arrays/from-string'
4 | import { toString as uint8arrayToString } from 'uint8arrays/to-string'
5 | import type { Uint8ArrayKeyPair } from './interface.js'
6 |
7 | const keypair = promisify(crypto.generateKeyPair)
8 |
9 | const PUBLIC_KEY_BYTE_LENGTH = 32
10 | const PRIVATE_KEY_BYTE_LENGTH = 64 // private key is actually 32 bytes but for historical reasons we concat private and public keys
11 | const KEYS_BYTE_LENGTH = 32
12 | const SIGNATURE_BYTE_LENGTH = 64
13 |
14 | export { PUBLIC_KEY_BYTE_LENGTH as publicKeyLength }
15 | export { PRIVATE_KEY_BYTE_LENGTH as privateKeyLength }
16 |
17 | function derivePublicKey (privateKey: Uint8Array): Uint8Array {
18 | const keyObject = crypto.createPrivateKey({
19 | format: 'jwk',
20 | key: {
21 | crv: 'Ed25519',
22 | x: '',
23 | d: uint8arrayToString(privateKey, 'base64url'),
24 | kty: 'OKP'
25 | }
26 | })
27 | const jwk = keyObject.export({
28 | format: 'jwk'
29 | })
30 |
31 | if (jwk.x == null || jwk.x === '') {
32 | throw new Error('Could not export JWK public key')
33 | }
34 |
35 | return uint8arrayFromString(jwk.x, 'base64url')
36 | }
37 |
38 | export async function generateKey (): Promise {
39 | const key = await keypair('ed25519', {
40 | publicKeyEncoding: { type: 'spki', format: 'jwk' },
41 | privateKeyEncoding: { type: 'pkcs8', format: 'jwk' }
42 | })
43 |
44 | // @ts-expect-error node types are missing jwk as a format
45 | const privateKeyRaw = uint8arrayFromString(key.privateKey.d, 'base64url')
46 | // @ts-expect-error node types are missing jwk as a format
47 | const publicKeyRaw = uint8arrayFromString(key.privateKey.x, 'base64url')
48 |
49 | return {
50 | privateKey: concatKeys(privateKeyRaw, publicKeyRaw),
51 | publicKey: publicKeyRaw
52 | }
53 | }
54 |
55 | /**
56 | * Generate keypair from a 32 byte uint8array
57 | */
58 | export async function generateKeyFromSeed (seed: Uint8Array): Promise {
59 | if (seed.length !== KEYS_BYTE_LENGTH) {
60 | throw new TypeError('"seed" must be 32 bytes in length.')
61 | } else if (!(seed instanceof Uint8Array)) {
62 | throw new TypeError('"seed" must be a node.js Buffer, or Uint8Array.')
63 | }
64 |
65 | // based on node forges algorithm, the seed is used directly as private key
66 | const publicKeyRaw = derivePublicKey(seed)
67 |
68 | return {
69 | privateKey: concatKeys(seed, publicKeyRaw),
70 | publicKey: publicKeyRaw
71 | }
72 | }
73 |
74 | export async function hashAndSign (key: Uint8Array, msg: Uint8Array): Promise {
75 | if (!(key instanceof Uint8Array)) {
76 | throw new TypeError('"key" must be a node.js Buffer, or Uint8Array.')
77 | }
78 |
79 | let privateKey: Uint8Array
80 | let publicKey: Uint8Array
81 |
82 | if (key.byteLength === PRIVATE_KEY_BYTE_LENGTH) {
83 | privateKey = key.subarray(0, 32)
84 | publicKey = key.subarray(32)
85 | } else if (key.byteLength === KEYS_BYTE_LENGTH) {
86 | privateKey = key.subarray(0, 32)
87 | publicKey = derivePublicKey(privateKey)
88 | } else {
89 | throw new TypeError('"key" must be 64 or 32 bytes in length.')
90 | }
91 |
92 | const obj = crypto.createPrivateKey({
93 | format: 'jwk',
94 | key: {
95 | crv: 'Ed25519',
96 | d: uint8arrayToString(privateKey, 'base64url'),
97 | x: uint8arrayToString(publicKey, 'base64url'),
98 | kty: 'OKP'
99 | }
100 | })
101 |
102 | return crypto.sign(null, msg, obj)
103 | }
104 |
105 | export async function hashAndVerify (key: Uint8Array, sig: Uint8Array, msg: Uint8Array): Promise {
106 | if (key.byteLength !== PUBLIC_KEY_BYTE_LENGTH) {
107 | throw new TypeError('"key" must be 32 bytes in length.')
108 | } else if (!(key instanceof Uint8Array)) {
109 | throw new TypeError('"key" must be a node.js Buffer, or Uint8Array.')
110 | }
111 |
112 | if (sig.byteLength !== SIGNATURE_BYTE_LENGTH) {
113 | throw new TypeError('"sig" must be 64 bytes in length.')
114 | } else if (!(sig instanceof Uint8Array)) {
115 | throw new TypeError('"sig" must be a node.js Buffer, or Uint8Array.')
116 | }
117 |
118 | const obj = crypto.createPublicKey({
119 | format: 'jwk',
120 | key: {
121 | crv: 'Ed25519',
122 | x: uint8arrayToString(key, 'base64url'),
123 | kty: 'OKP'
124 | }
125 | })
126 |
127 | return crypto.verify(null, msg, obj, sig)
128 | }
129 |
130 | function concatKeys (privateKeyRaw: Uint8Array, publicKey: Uint8Array): Uint8Array {
131 | const privateKey = new Uint8Array(PRIVATE_KEY_BYTE_LENGTH)
132 | for (let i = 0; i < KEYS_BYTE_LENGTH; i++) {
133 | privateKey[i] = privateKeyRaw[i]
134 | privateKey[KEYS_BYTE_LENGTH + i] = publicKey[i]
135 | }
136 | return privateKey
137 | }
138 |
--------------------------------------------------------------------------------
/src/keys/ephemeral-keys.ts:
--------------------------------------------------------------------------------
1 | import { generateEphmeralKeyPair } from './ecdh.js'
2 |
3 | /**
4 | * Generates an ephemeral public key and returns a function that will compute
5 | * the shared secret key.
6 | *
7 | * Focuses only on ECDH now, but can be made more general in the future.
8 | */
9 | export default generateEphmeralKeyPair
10 |
--------------------------------------------------------------------------------
/src/keys/exporter.ts:
--------------------------------------------------------------------------------
1 | import { base64 } from 'multiformats/bases/base64'
2 | import * as ciphers from '../ciphers/aes-gcm.js'
3 | import type { Multibase } from 'multiformats'
4 |
5 | /**
6 | * Exports the given PrivateKey as a base64 encoded string.
7 | * The PrivateKey is encrypted via a password derived PBKDF2 key
8 | * leveraging the aes-gcm cipher algorithm.
9 | */
10 | export async function exporter (privateKey: Uint8Array, password: string): Promise> {
11 | const cipher = ciphers.create()
12 | const encryptedKey = await cipher.encrypt(privateKey, password)
13 | return base64.encode(encryptedKey)
14 | }
15 |
--------------------------------------------------------------------------------
/src/keys/importer.ts:
--------------------------------------------------------------------------------
1 | import { base64 } from 'multiformats/bases/base64'
2 | import * as ciphers from '../ciphers/aes-gcm.js'
3 |
4 | /**
5 | * Attempts to decrypt a base64 encoded PrivateKey string
6 | * with the given password. The privateKey must have been exported
7 | * using the same password and underlying cipher (aes-gcm)
8 | */
9 | export async function importer (privateKey: string, password: string): Promise {
10 | const encryptedKey = base64.decode(privateKey)
11 | const cipher = ciphers.create()
12 | return cipher.decrypt(encryptedKey, password)
13 | }
14 |
--------------------------------------------------------------------------------
/src/keys/index.ts:
--------------------------------------------------------------------------------
1 | import 'node-forge/lib/asn1.js'
2 | import 'node-forge/lib/pbe.js'
3 | import { CodeError } from '@libp2p/interfaces/errors'
4 | // @ts-expect-error types are missing
5 | import forge from 'node-forge/lib/forge.js'
6 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
7 | import * as Ed25519 from './ed25519-class.js'
8 | import generateEphemeralKeyPair from './ephemeral-keys.js'
9 | import { importer } from './importer.js'
10 | import { keyStretcher } from './key-stretcher.js'
11 | import * as keysPBM from './keys.js'
12 | import * as RSA from './rsa-class.js'
13 | import * as Secp256k1 from './secp256k1-class.js'
14 | import type { PrivateKey, PublicKey } from '@libp2p/interface-keys'
15 |
16 | export { keyStretcher }
17 | export { generateEphemeralKeyPair }
18 | export { keysPBM }
19 |
20 | export type KeyTypes = 'RSA' | 'Ed25519' | 'secp256k1'
21 |
22 | export const supportedKeys = {
23 | rsa: RSA,
24 | ed25519: Ed25519,
25 | secp256k1: Secp256k1
26 | }
27 |
28 | function unsupportedKey (type: string): CodeError> {
29 | const supported = Object.keys(supportedKeys).join(' / ')
30 | return new CodeError(`invalid or unsupported key type ${type}. Must be ${supported}`, 'ERR_UNSUPPORTED_KEY_TYPE')
31 | }
32 |
33 | function typeToKey (type: string): typeof RSA | typeof Ed25519 | typeof Secp256k1 {
34 | type = type.toLowerCase()
35 |
36 | if (type === 'rsa' || type === 'ed25519' || type === 'secp256k1') {
37 | return supportedKeys[type]
38 | }
39 |
40 | throw unsupportedKey(type)
41 | }
42 |
43 | // Generates a keypair of the given type and bitsize
44 | export async function generateKeyPair (type: KeyTypes, bits?: number): Promise { // eslint-disable-line require-await
45 | return typeToKey(type).generateKeyPair(bits ?? 2048)
46 | }
47 |
48 | // Generates a keypair of the given type and bitsize
49 | // seed is a 32 byte uint8array
50 | export async function generateKeyPairFromSeed (type: KeyTypes, seed: Uint8Array, bits?: number): Promise { // eslint-disable-line require-await
51 | if (type.toLowerCase() !== 'ed25519') {
52 | throw new CodeError('Seed key derivation is unimplemented for RSA or secp256k1', 'ERR_UNSUPPORTED_KEY_DERIVATION_TYPE')
53 | }
54 |
55 | return Ed25519.generateKeyPairFromSeed(seed)
56 | }
57 |
58 | // Converts a protobuf serialized public key into its
59 | // representative object
60 | export function unmarshalPublicKey (buf: Uint8Array): PublicKey {
61 | const decoded = keysPBM.PublicKey.decode(buf)
62 | const data = decoded.Data ?? new Uint8Array()
63 |
64 | switch (decoded.Type) {
65 | case keysPBM.KeyType.RSA:
66 | return supportedKeys.rsa.unmarshalRsaPublicKey(data)
67 | case keysPBM.KeyType.Ed25519:
68 | return supportedKeys.ed25519.unmarshalEd25519PublicKey(data)
69 | case keysPBM.KeyType.Secp256k1:
70 | return supportedKeys.secp256k1.unmarshalSecp256k1PublicKey(data)
71 | default:
72 | throw unsupportedKey(decoded.Type ?? 'RSA')
73 | }
74 | }
75 |
76 | // Converts a public key object into a protobuf serialized public key
77 | export function marshalPublicKey (key: { bytes: Uint8Array }, type?: string): Uint8Array {
78 | type = (type ?? 'rsa').toLowerCase()
79 | typeToKey(type) // check type
80 | return key.bytes
81 | }
82 |
83 | // Converts a protobuf serialized private key into its
84 | // representative object
85 | export async function unmarshalPrivateKey (buf: Uint8Array): Promise { // eslint-disable-line require-await
86 | const decoded = keysPBM.PrivateKey.decode(buf)
87 | const data = decoded.Data ?? new Uint8Array()
88 |
89 | switch (decoded.Type) {
90 | case keysPBM.KeyType.RSA:
91 | return supportedKeys.rsa.unmarshalRsaPrivateKey(data)
92 | case keysPBM.KeyType.Ed25519:
93 | return supportedKeys.ed25519.unmarshalEd25519PrivateKey(data)
94 | case keysPBM.KeyType.Secp256k1:
95 | return supportedKeys.secp256k1.unmarshalSecp256k1PrivateKey(data)
96 | default:
97 | throw unsupportedKey(decoded.Type ?? 'RSA')
98 | }
99 | }
100 |
101 | // Converts a private key object into a protobuf serialized private key
102 | export function marshalPrivateKey (key: { bytes: Uint8Array }, type?: string): Uint8Array {
103 | type = (type ?? 'rsa').toLowerCase()
104 | typeToKey(type) // check type
105 | return key.bytes
106 | }
107 |
108 | /**
109 | *
110 | * @param {string} encryptedKey
111 | * @param {string} password
112 | */
113 | export async function importKey (encryptedKey: string, password: string): Promise { // eslint-disable-line require-await
114 | try {
115 | const key = await importer(encryptedKey, password)
116 | return await unmarshalPrivateKey(key)
117 | } catch (_) {
118 | // Ignore and try the old pem decrypt
119 | }
120 |
121 | // Only rsa supports pem right now
122 | const key = forge.pki.decryptRsaPrivateKey(encryptedKey, password)
123 | if (key === null) {
124 | throw new CodeError('Cannot read the key, most likely the password is wrong or not a RSA key', 'ERR_CANNOT_DECRYPT_PEM')
125 | }
126 | let der = forge.asn1.toDer(forge.pki.privateKeyToAsn1(key))
127 | der = uint8ArrayFromString(der.getBytes(), 'ascii')
128 | return supportedKeys.rsa.unmarshalRsaPrivateKey(der)
129 | }
130 |
--------------------------------------------------------------------------------
/src/keys/interface.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface JWKKeyPair {
3 | privateKey: JsonWebKey
4 | publicKey: JsonWebKey
5 | }
6 |
7 | export interface Uint8ArrayKeyPair {
8 | privateKey: Uint8Array
9 | publicKey: Uint8Array
10 | }
11 |
12 | export interface ECDHKeyPair {
13 | private: Uint8Array
14 | public: Uint8Array
15 | }
16 |
17 | export interface ECDHKey {
18 | key: Uint8Array
19 | genSharedKey: (theirPub: Uint8Array, forcePrivate?: ECDHKeyPair) => Promise
20 | }
21 |
22 | export interface JWKEncodedPublicKey { kty: string, crv: 'P-256' | 'P-384' | 'P-521', x: string, y: string, ext: boolean }
23 |
24 | export interface JWKEncodedPrivateKey extends JWKEncodedPublicKey { d: string}
25 |
26 | export interface EnhancedKey {
27 | iv: Uint8Array
28 | cipherKey: Uint8Array
29 | macKey: Uint8Array
30 | }
31 |
32 | export interface EnhancedKeyPair {
33 | k1: EnhancedKey
34 | k2: EnhancedKey
35 | }
36 |
--------------------------------------------------------------------------------
/src/keys/jwk2pem.ts:
--------------------------------------------------------------------------------
1 | import 'node-forge/lib/rsa.js'
2 | // @ts-expect-error types are missing
3 | import forge from 'node-forge/lib/forge.js'
4 | import { base64urlToBigInteger } from '../util.js'
5 |
6 | export interface JWK {
7 | encrypt: (msg: string) => string
8 | decrypt: (msg: string) => string
9 | }
10 |
11 | function convert (key: any, types: string[]): Array {
12 | return types.map(t => base64urlToBigInteger(key[t]))
13 | }
14 |
15 | export function jwk2priv (key: JsonWebKey): JWK {
16 | return forge.pki.setRsaPrivateKey(...convert(key, ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi']))
17 | }
18 |
19 | export function jwk2pub (key: JsonWebKey): JWK {
20 | return forge.pki.setRsaPublicKey(...convert(key, ['n', 'e']))
21 | }
22 |
--------------------------------------------------------------------------------
/src/keys/key-stretcher.ts:
--------------------------------------------------------------------------------
1 | import { CodeError } from '@libp2p/interfaces/errors'
2 | import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
3 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
4 | import * as hmac from '../hmac/index.js'
5 | import type { EnhancedKey, EnhancedKeyPair } from './interface.js'
6 |
7 | const cipherMap = {
8 | 'AES-128': {
9 | ivSize: 16,
10 | keySize: 16
11 | },
12 | 'AES-256': {
13 | ivSize: 16,
14 | keySize: 32
15 | },
16 | Blowfish: {
17 | ivSize: 8,
18 | keySize: 32
19 | }
20 | }
21 |
22 | /**
23 | * Generates a set of keys for each party by stretching the shared key.
24 | * (myIV, theirIV, myCipherKey, theirCipherKey, myMACKey, theirMACKey)
25 | */
26 | export async function keyStretcher (cipherType: 'AES-128' | 'AES-256' | 'Blowfish', hash: 'SHA1' | 'SHA256' | 'SHA512', secret: Uint8Array): Promise {
27 | const cipher = cipherMap[cipherType]
28 |
29 | if (cipher == null) {
30 | const allowed = Object.keys(cipherMap).join(' / ')
31 | throw new CodeError(`unknown cipher type '${cipherType}'. Must be ${allowed}`, 'ERR_INVALID_CIPHER_TYPE')
32 | }
33 |
34 | if (hash == null) {
35 | throw new CodeError('missing hash type', 'ERR_MISSING_HASH_TYPE')
36 | }
37 |
38 | const cipherKeySize = cipher.keySize
39 | const ivSize = cipher.ivSize
40 | const hmacKeySize = 20
41 | const seed = uint8ArrayFromString('key expansion')
42 | const resultLength = 2 * (ivSize + cipherKeySize + hmacKeySize)
43 |
44 | const m = await hmac.create(hash, secret)
45 | let a = await m.digest(seed)
46 |
47 | const result = []
48 | let j = 0
49 |
50 | while (j < resultLength) {
51 | const b = await m.digest(uint8ArrayConcat([a, seed]))
52 | let todo = b.length
53 |
54 | if (j + todo > resultLength) {
55 | todo = resultLength - j
56 | }
57 |
58 | result.push(b)
59 | j += todo
60 | a = await m.digest(a)
61 | }
62 |
63 | const half = resultLength / 2
64 | const resultBuffer = uint8ArrayConcat(result)
65 | const r1 = resultBuffer.subarray(0, half)
66 | const r2 = resultBuffer.subarray(half, resultLength)
67 |
68 | const createKey = (res: Uint8Array): EnhancedKey => ({
69 | iv: res.subarray(0, ivSize),
70 | cipherKey: res.subarray(ivSize, ivSize + cipherKeySize),
71 | macKey: res.subarray(ivSize + cipherKeySize)
72 | })
73 |
74 | return {
75 | k1: createKey(r1),
76 | k2: createKey(r2)
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/keys/keys.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | enum KeyType {
4 | RSA = 0;
5 | Ed25519 = 1;
6 | Secp256k1 = 2;
7 | }
8 | message PublicKey {
9 | // the proto2 version of this field is "required" which means it will have
10 | // no default value. the default for proto3 is "singluar" which omits the
11 | // value on the wire if it's the default so for proto3 we make it "optional"
12 | // to ensure a value is always written on to the wire
13 | optional KeyType Type = 1;
14 |
15 | // the proto2 version of this field is "required" which means it will have
16 | // no default value. the default for proto3 is "singluar" which omits the
17 | // value on the wire if it's the default so for proto3 we make it "optional"
18 | // to ensure a value is always written on to the wire
19 | optional bytes Data = 2;
20 | }
21 | message PrivateKey {
22 | // the proto2 version of this field is "required" which means it will have
23 | // no default value. the default for proto3 is "singluar" which omits the
24 | // value on the wire if it's the default so for proto3 we make it "optional"
25 | // to ensure a value is always written on to the wire
26 | optional KeyType Type = 1;
27 |
28 | // the proto2 version of this field is "required" which means it will have
29 | // no default value. the default for proto3 is "singluar" which omits the
30 | // value on the wire if it's the default so for proto3 we make it "optional"
31 | // to ensure a value is always written on to the wire
32 | optional bytes Data = 2;
33 | }
34 |
--------------------------------------------------------------------------------
/src/keys/keys.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/export */
2 | /* eslint-disable complexity */
3 | /* eslint-disable @typescript-eslint/no-namespace */
4 | /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
5 | /* eslint-disable @typescript-eslint/no-empty-interface */
6 |
7 | import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime'
8 | import type { Codec } from 'protons-runtime'
9 | import type { Uint8ArrayList } from 'uint8arraylist'
10 |
11 | export enum KeyType {
12 | RSA = 'RSA',
13 | Ed25519 = 'Ed25519',
14 | Secp256k1 = 'Secp256k1'
15 | }
16 |
17 | enum __KeyTypeValues {
18 | RSA = 0,
19 | Ed25519 = 1,
20 | Secp256k1 = 2
21 | }
22 |
23 | export namespace KeyType {
24 | export const codec = (): Codec => {
25 | return enumeration(__KeyTypeValues)
26 | }
27 | }
28 | export interface PublicKey {
29 | Type?: KeyType
30 | Data?: Uint8Array
31 | }
32 |
33 | export namespace PublicKey {
34 | let _codec: Codec
35 |
36 | export const codec = (): Codec => {
37 | if (_codec == null) {
38 | _codec = message((obj, w, opts = {}) => {
39 | if (opts.lengthDelimited !== false) {
40 | w.fork()
41 | }
42 |
43 | if (obj.Type != null) {
44 | w.uint32(8)
45 | KeyType.codec().encode(obj.Type, w)
46 | }
47 |
48 | if (obj.Data != null) {
49 | w.uint32(18)
50 | w.bytes(obj.Data)
51 | }
52 |
53 | if (opts.lengthDelimited !== false) {
54 | w.ldelim()
55 | }
56 | }, (reader, length) => {
57 | const obj: any = {}
58 |
59 | const end = length == null ? reader.len : reader.pos + length
60 |
61 | while (reader.pos < end) {
62 | const tag = reader.uint32()
63 |
64 | switch (tag >>> 3) {
65 | case 1:
66 | obj.Type = KeyType.codec().decode(reader)
67 | break
68 | case 2:
69 | obj.Data = reader.bytes()
70 | break
71 | default:
72 | reader.skipType(tag & 7)
73 | break
74 | }
75 | }
76 |
77 | return obj
78 | })
79 | }
80 |
81 | return _codec
82 | }
83 |
84 | export const encode = (obj: Partial): Uint8Array => {
85 | return encodeMessage(obj, PublicKey.codec())
86 | }
87 |
88 | export const decode = (buf: Uint8Array | Uint8ArrayList): PublicKey => {
89 | return decodeMessage(buf, PublicKey.codec())
90 | }
91 | }
92 |
93 | export interface PrivateKey {
94 | Type?: KeyType
95 | Data?: Uint8Array
96 | }
97 |
98 | export namespace PrivateKey {
99 | let _codec: Codec
100 |
101 | export const codec = (): Codec => {
102 | if (_codec == null) {
103 | _codec = message((obj, w, opts = {}) => {
104 | if (opts.lengthDelimited !== false) {
105 | w.fork()
106 | }
107 |
108 | if (obj.Type != null) {
109 | w.uint32(8)
110 | KeyType.codec().encode(obj.Type, w)
111 | }
112 |
113 | if (obj.Data != null) {
114 | w.uint32(18)
115 | w.bytes(obj.Data)
116 | }
117 |
118 | if (opts.lengthDelimited !== false) {
119 | w.ldelim()
120 | }
121 | }, (reader, length) => {
122 | const obj: any = {}
123 |
124 | const end = length == null ? reader.len : reader.pos + length
125 |
126 | while (reader.pos < end) {
127 | const tag = reader.uint32()
128 |
129 | switch (tag >>> 3) {
130 | case 1:
131 | obj.Type = KeyType.codec().decode(reader)
132 | break
133 | case 2:
134 | obj.Data = reader.bytes()
135 | break
136 | default:
137 | reader.skipType(tag & 7)
138 | break
139 | }
140 | }
141 |
142 | return obj
143 | })
144 | }
145 |
146 | return _codec
147 | }
148 |
149 | export const encode = (obj: Partial): Uint8Array => {
150 | return encodeMessage(obj, PrivateKey.codec())
151 | }
152 |
153 | export const decode = (buf: Uint8Array | Uint8ArrayList): PrivateKey => {
154 | return decodeMessage(buf, PrivateKey.codec())
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/keys/rsa-browser.ts:
--------------------------------------------------------------------------------
1 | import { CodeError } from '@libp2p/interfaces/errors'
2 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
3 | import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
4 | import randomBytes from '../random-bytes.js'
5 | import webcrypto from '../webcrypto.js'
6 | import { jwk2pub, jwk2priv } from './jwk2pem.js'
7 | import * as utils from './rsa-utils.js'
8 | import type { JWKKeyPair } from './interface.js'
9 |
10 | export { utils }
11 |
12 | export async function generateKey (bits: number): Promise {
13 | const pair = await webcrypto.get().subtle.generateKey(
14 | {
15 | name: 'RSASSA-PKCS1-v1_5',
16 | modulusLength: bits,
17 | publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
18 | hash: { name: 'SHA-256' }
19 | },
20 | true,
21 | ['sign', 'verify']
22 | )
23 |
24 | const keys = await exportKey(pair)
25 |
26 | return {
27 | privateKey: keys[0],
28 | publicKey: keys[1]
29 | }
30 | }
31 |
32 | // Takes a jwk key
33 | export async function unmarshalPrivateKey (key: JsonWebKey): Promise {
34 | const privateKey = await webcrypto.get().subtle.importKey(
35 | 'jwk',
36 | key,
37 | {
38 | name: 'RSASSA-PKCS1-v1_5',
39 | hash: { name: 'SHA-256' }
40 | },
41 | true,
42 | ['sign']
43 | )
44 |
45 | const pair = [
46 | privateKey,
47 | await derivePublicFromPrivate(key)
48 | ]
49 |
50 | const keys = await exportKey({
51 | privateKey: pair[0],
52 | publicKey: pair[1]
53 | })
54 |
55 | return {
56 | privateKey: keys[0],
57 | publicKey: keys[1]
58 | }
59 | }
60 |
61 | export { randomBytes as getRandomValues }
62 |
63 | export async function hashAndSign (key: JsonWebKey, msg: Uint8Array): Promise {
64 | const privateKey = await webcrypto.get().subtle.importKey(
65 | 'jwk',
66 | key,
67 | {
68 | name: 'RSASSA-PKCS1-v1_5',
69 | hash: { name: 'SHA-256' }
70 | },
71 | false,
72 | ['sign']
73 | )
74 |
75 | const sig = await webcrypto.get().subtle.sign(
76 | { name: 'RSASSA-PKCS1-v1_5' },
77 | privateKey,
78 | Uint8Array.from(msg)
79 | )
80 |
81 | return new Uint8Array(sig, 0, sig.byteLength)
82 | }
83 |
84 | export async function hashAndVerify (key: JsonWebKey, sig: Uint8Array, msg: Uint8Array): Promise {
85 | const publicKey = await webcrypto.get().subtle.importKey(
86 | 'jwk',
87 | key,
88 | {
89 | name: 'RSASSA-PKCS1-v1_5',
90 | hash: { name: 'SHA-256' }
91 | },
92 | false,
93 | ['verify']
94 | )
95 |
96 | return webcrypto.get().subtle.verify(
97 | { name: 'RSASSA-PKCS1-v1_5' },
98 | publicKey,
99 | sig,
100 | msg
101 | )
102 | }
103 |
104 | async function exportKey (pair: CryptoKeyPair): Promise<[JsonWebKey, JsonWebKey]> {
105 | if (pair.privateKey == null || pair.publicKey == null) {
106 | throw new CodeError('Private and public key are required', 'ERR_INVALID_PARAMETERS')
107 | }
108 |
109 | return Promise.all([
110 | webcrypto.get().subtle.exportKey('jwk', pair.privateKey),
111 | webcrypto.get().subtle.exportKey('jwk', pair.publicKey)
112 | ])
113 | }
114 |
115 | async function derivePublicFromPrivate (jwKey: JsonWebKey): Promise {
116 | return webcrypto.get().subtle.importKey(
117 | 'jwk',
118 | {
119 | kty: jwKey.kty,
120 | n: jwKey.n,
121 | e: jwKey.e
122 | },
123 | {
124 | name: 'RSASSA-PKCS1-v1_5',
125 | hash: { name: 'SHA-256' }
126 | },
127 | true,
128 | ['verify']
129 | )
130 | }
131 |
132 | /*
133 |
134 | RSA encryption/decryption for the browser with webcrypto workaround
135 | "bloody dark magic. webcrypto's why."
136 |
137 | Explanation:
138 | - Convert JWK to nodeForge
139 | - Convert msg Uint8Array to nodeForge buffer: ByteBuffer is a "binary-string backed buffer", so let's make our Uint8Array a binary string
140 | - Convert resulting nodeForge buffer to Uint8Array: it returns a binary string, turn that into a Uint8Array
141 |
142 | */
143 |
144 | function convertKey (key: JsonWebKey, pub: boolean, msg: Uint8Array, handle: (msg: string, key: { encrypt: (msg: string) => string, decrypt: (msg: string) => string }) => string): Uint8Array {
145 | const fkey = pub ? jwk2pub(key) : jwk2priv(key)
146 | const fmsg = uint8ArrayToString(Uint8Array.from(msg), 'ascii')
147 | const fomsg = handle(fmsg, fkey)
148 | return uint8ArrayFromString(fomsg, 'ascii')
149 | }
150 |
151 | export function encrypt (key: JsonWebKey, msg: Uint8Array): Uint8Array {
152 | return convertKey(key, true, msg, (msg, key) => key.encrypt(msg))
153 | }
154 |
155 | export function decrypt (key: JsonWebKey, msg: Uint8Array): Uint8Array {
156 | return convertKey(key, false, msg, (msg, key) => key.decrypt(msg))
157 | }
158 |
--------------------------------------------------------------------------------
/src/keys/rsa-class.ts:
--------------------------------------------------------------------------------
1 |
2 | import { CodeError } from '@libp2p/interfaces/errors'
3 | import { sha256 } from 'multiformats/hashes/sha2'
4 | // @ts-expect-error types are missing
5 | import forge from 'node-forge/lib/forge.js'
6 | import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
7 | import 'node-forge/lib/sha512.js'
8 | import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
9 | import { exporter } from './exporter.js'
10 | import * as pbm from './keys.js'
11 | import * as crypto from './rsa.js'
12 | import type { Multibase } from 'multiformats'
13 |
14 | export class RsaPublicKey {
15 | private readonly _key: JsonWebKey
16 |
17 | constructor (key: JsonWebKey) {
18 | this._key = key
19 | }
20 |
21 | async verify (data: Uint8Array, sig: Uint8Array): Promise { // eslint-disable-line require-await
22 | return crypto.hashAndVerify(this._key, sig, data)
23 | }
24 |
25 | marshal (): Uint8Array {
26 | return crypto.utils.jwkToPkix(this._key)
27 | }
28 |
29 | get bytes (): Uint8Array {
30 | return pbm.PublicKey.encode({
31 | Type: pbm.KeyType.RSA,
32 | Data: this.marshal()
33 | }).subarray()
34 | }
35 |
36 | encrypt (bytes: Uint8Array): Uint8Array {
37 | return crypto.encrypt(this._key, bytes)
38 | }
39 |
40 | equals (key: any): boolean {
41 | return uint8ArrayEquals(this.bytes, key.bytes)
42 | }
43 |
44 | async hash (): Promise {
45 | const { bytes } = await sha256.digest(this.bytes)
46 |
47 | return bytes
48 | }
49 | }
50 |
51 | export class RsaPrivateKey {
52 | private readonly _key: JsonWebKey
53 | private readonly _publicKey: JsonWebKey
54 |
55 | constructor (key: JsonWebKey, publicKey: JsonWebKey) {
56 | this._key = key
57 | this._publicKey = publicKey
58 | }
59 |
60 | genSecret (): Uint8Array {
61 | return crypto.getRandomValues(16)
62 | }
63 |
64 | async sign (message: Uint8Array): Promise { // eslint-disable-line require-await
65 | return crypto.hashAndSign(this._key, message)
66 | }
67 |
68 | get public (): RsaPublicKey {
69 | if (this._publicKey == null) {
70 | throw new CodeError('public key not provided', 'ERR_PUBKEY_NOT_PROVIDED')
71 | }
72 |
73 | return new RsaPublicKey(this._publicKey)
74 | }
75 |
76 | decrypt (bytes: Uint8Array): Uint8Array {
77 | return crypto.decrypt(this._key, bytes)
78 | }
79 |
80 | marshal (): Uint8Array {
81 | return crypto.utils.jwkToPkcs1(this._key)
82 | }
83 |
84 | get bytes (): Uint8Array {
85 | return pbm.PrivateKey.encode({
86 | Type: pbm.KeyType.RSA,
87 | Data: this.marshal()
88 | }).subarray()
89 | }
90 |
91 | equals (key: any): boolean {
92 | return uint8ArrayEquals(this.bytes, key.bytes)
93 | }
94 |
95 | async hash (): Promise {
96 | const { bytes } = await sha256.digest(this.bytes)
97 |
98 | return bytes
99 | }
100 |
101 | /**
102 | * Gets the ID of the key.
103 | *
104 | * The key id is the base58 encoding of the SHA-256 multihash of its public key.
105 | * The public key is a protobuf encoding containing a type and the DER encoding
106 | * of the PKCS SubjectPublicKeyInfo.
107 | */
108 | async id (): Promise {
109 | const hash = await this.public.hash()
110 | return uint8ArrayToString(hash, 'base58btc')
111 | }
112 |
113 | /**
114 | * Exports the key into a password protected PEM format
115 | */
116 | async export (password: string, format = 'pkcs-8'): Promise> { // eslint-disable-line require-await
117 | if (format === 'pkcs-8') {
118 | const buffer = new forge.util.ByteBuffer(this.marshal())
119 | const asn1 = forge.asn1.fromDer(buffer)
120 | const privateKey = forge.pki.privateKeyFromAsn1(asn1)
121 |
122 | const options = {
123 | algorithm: 'aes256',
124 | count: 10000,
125 | saltSize: 128 / 8,
126 | prfAlgorithm: 'sha512'
127 | }
128 | return forge.pki.encryptRsaPrivateKey(privateKey, password, options)
129 | } else if (format === 'libp2p-key') {
130 | return exporter(this.bytes, password)
131 | } else {
132 | throw new CodeError(`export format '${format}' is not supported`, 'ERR_INVALID_EXPORT_FORMAT')
133 | }
134 | }
135 | }
136 |
137 | export async function unmarshalRsaPrivateKey (bytes: Uint8Array): Promise {
138 | const jwk = crypto.utils.pkcs1ToJwk(bytes)
139 | const keys = await crypto.unmarshalPrivateKey(jwk)
140 | return new RsaPrivateKey(keys.privateKey, keys.publicKey)
141 | }
142 |
143 | export function unmarshalRsaPublicKey (bytes: Uint8Array): RsaPublicKey {
144 | const jwk = crypto.utils.pkixToJwk(bytes)
145 | return new RsaPublicKey(jwk)
146 | }
147 |
148 | export async function fromJwk (jwk: JsonWebKey): Promise {
149 | const keys = await crypto.unmarshalPrivateKey(jwk)
150 | return new RsaPrivateKey(keys.privateKey, keys.publicKey)
151 | }
152 |
153 | export async function generateKeyPair (bits: number): Promise {
154 | const keys = await crypto.generateKey(bits)
155 | return new RsaPrivateKey(keys.privateKey, keys.publicKey)
156 | }
157 |
--------------------------------------------------------------------------------
/src/keys/rsa-utils.ts:
--------------------------------------------------------------------------------
1 | import 'node-forge/lib/asn1.js'
2 | import 'node-forge/lib/rsa.js'
3 | import { CodeError } from '@libp2p/interfaces/errors'
4 | // @ts-expect-error types are missing
5 | import forge from 'node-forge/lib/forge.js'
6 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
7 | import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
8 | import { bigIntegerToUintBase64url, base64urlToBigInteger } from './../util.js'
9 |
10 | // Convert a PKCS#1 in ASN1 DER format to a JWK key
11 | export function pkcs1ToJwk (bytes: Uint8Array): JsonWebKey {
12 | const asn1 = forge.asn1.fromDer(uint8ArrayToString(bytes, 'ascii'))
13 | const privateKey = forge.pki.privateKeyFromAsn1(asn1)
14 |
15 | // https://tools.ietf.org/html/rfc7518#section-6.3.1
16 | return {
17 | kty: 'RSA',
18 | n: bigIntegerToUintBase64url(privateKey.n),
19 | e: bigIntegerToUintBase64url(privateKey.e),
20 | d: bigIntegerToUintBase64url(privateKey.d),
21 | p: bigIntegerToUintBase64url(privateKey.p),
22 | q: bigIntegerToUintBase64url(privateKey.q),
23 | dp: bigIntegerToUintBase64url(privateKey.dP),
24 | dq: bigIntegerToUintBase64url(privateKey.dQ),
25 | qi: bigIntegerToUintBase64url(privateKey.qInv),
26 | alg: 'RS256'
27 | }
28 | }
29 |
30 | // Convert a JWK key into PKCS#1 in ASN1 DER format
31 | export function jwkToPkcs1 (jwk: JsonWebKey): Uint8Array {
32 | if (jwk.n == null || jwk.e == null || jwk.d == null || jwk.p == null || jwk.q == null || jwk.dp == null || jwk.dq == null || jwk.qi == null) {
33 | throw new CodeError('JWK was missing components', 'ERR_INVALID_PARAMETERS')
34 | }
35 |
36 | const asn1 = forge.pki.privateKeyToAsn1({
37 | n: base64urlToBigInteger(jwk.n),
38 | e: base64urlToBigInteger(jwk.e),
39 | d: base64urlToBigInteger(jwk.d),
40 | p: base64urlToBigInteger(jwk.p),
41 | q: base64urlToBigInteger(jwk.q),
42 | dP: base64urlToBigInteger(jwk.dp),
43 | dQ: base64urlToBigInteger(jwk.dq),
44 | qInv: base64urlToBigInteger(jwk.qi)
45 | })
46 |
47 | return uint8ArrayFromString(forge.asn1.toDer(asn1).getBytes(), 'ascii')
48 | }
49 |
50 | // Convert a PKCIX in ASN1 DER format to a JWK key
51 | export function pkixToJwk (bytes: Uint8Array): JsonWebKey {
52 | const asn1 = forge.asn1.fromDer(uint8ArrayToString(bytes, 'ascii'))
53 | const publicKey = forge.pki.publicKeyFromAsn1(asn1)
54 |
55 | return {
56 | kty: 'RSA',
57 | n: bigIntegerToUintBase64url(publicKey.n),
58 | e: bigIntegerToUintBase64url(publicKey.e)
59 | }
60 | }
61 |
62 | // Convert a JWK key to PKCIX in ASN1 DER format
63 | export function jwkToPkix (jwk: JsonWebKey): Uint8Array {
64 | if (jwk.n == null || jwk.e == null) {
65 | throw new CodeError('JWK was missing components', 'ERR_INVALID_PARAMETERS')
66 | }
67 |
68 | const asn1 = forge.pki.publicKeyToAsn1({
69 | n: base64urlToBigInteger(jwk.n),
70 | e: base64urlToBigInteger(jwk.e)
71 | })
72 |
73 | return uint8ArrayFromString(forge.asn1.toDer(asn1).getBytes(), 'ascii')
74 | }
75 |
--------------------------------------------------------------------------------
/src/keys/rsa.ts:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto'
2 | import { promisify } from 'util'
3 | import { CodeError } from '@libp2p/interfaces/errors'
4 | import randomBytes from '../random-bytes.js'
5 | import * as utils from './rsa-utils.js'
6 | import type { JWKKeyPair } from './interface.js'
7 |
8 | const keypair = promisify(crypto.generateKeyPair)
9 |
10 | export { utils }
11 |
12 | export async function generateKey (bits: number): Promise { // eslint-disable-line require-await
13 | // @ts-expect-error node types are missing jwk as a format
14 | const key = await keypair('rsa', {
15 | modulusLength: bits,
16 | publicKeyEncoding: { type: 'pkcs1', format: 'jwk' },
17 | privateKeyEncoding: { type: 'pkcs1', format: 'jwk' }
18 | })
19 |
20 | return {
21 | // @ts-expect-error node types are missing jwk as a format
22 | privateKey: key.privateKey,
23 | // @ts-expect-error node types are missing jwk as a format
24 | publicKey: key.publicKey
25 | }
26 | }
27 |
28 | // Takes a jwk key
29 | export async function unmarshalPrivateKey (key: JsonWebKey): Promise { // eslint-disable-line require-await
30 | if (key == null) {
31 | throw new CodeError('Missing key parameter', 'ERR_MISSING_KEY')
32 | }
33 | return {
34 | privateKey: key,
35 | publicKey: {
36 | kty: key.kty,
37 | n: key.n,
38 | e: key.e
39 | }
40 | }
41 | }
42 |
43 | export { randomBytes as getRandomValues }
44 |
45 | export async function hashAndSign (key: JsonWebKey, msg: Uint8Array): Promise {
46 | return crypto.createSign('RSA-SHA256')
47 | .update(msg)
48 | // @ts-expect-error node types are missing jwk as a format
49 | .sign({ format: 'jwk', key })
50 | }
51 |
52 | export async function hashAndVerify (key: JsonWebKey, sig: Uint8Array, msg: Uint8Array): Promise { // eslint-disable-line require-await
53 | return crypto.createVerify('RSA-SHA256')
54 | .update(msg)
55 | // @ts-expect-error node types are missing jwk as a format
56 | .verify({ format: 'jwk', key }, sig)
57 | }
58 |
59 | const padding = crypto.constants.RSA_PKCS1_PADDING
60 |
61 | export function encrypt (key: JsonWebKey, bytes: Uint8Array): Uint8Array {
62 | // @ts-expect-error node types are missing jwk as a format
63 | return crypto.publicEncrypt({ format: 'jwk', key, padding }, bytes)
64 | }
65 |
66 | export function decrypt (key: JsonWebKey, bytes: Uint8Array): Uint8Array {
67 | // @ts-expect-error node types are missing jwk as a format
68 | return crypto.privateDecrypt({ format: 'jwk', key, padding }, bytes)
69 | }
70 |
--------------------------------------------------------------------------------
/src/keys/secp256k1-class.ts:
--------------------------------------------------------------------------------
1 | import { CodeError } from '@libp2p/interfaces/errors'
2 | import { sha256 } from 'multiformats/hashes/sha2'
3 | import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
4 | import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
5 | import { exporter } from './exporter.js'
6 | import * as keysProtobuf from './keys.js'
7 | import * as crypto from './secp256k1.js'
8 | import type { Multibase } from 'multiformats'
9 |
10 | export class Secp256k1PublicKey {
11 | private readonly _key: Uint8Array
12 |
13 | constructor (key: Uint8Array) {
14 | crypto.validatePublicKey(key)
15 | this._key = key
16 | }
17 |
18 | async verify (data: Uint8Array, sig: Uint8Array): Promise {
19 | return crypto.hashAndVerify(this._key, sig, data)
20 | }
21 |
22 | marshal (): Uint8Array {
23 | return crypto.compressPublicKey(this._key)
24 | }
25 |
26 | get bytes (): Uint8Array {
27 | return keysProtobuf.PublicKey.encode({
28 | Type: keysProtobuf.KeyType.Secp256k1,
29 | Data: this.marshal()
30 | }).subarray()
31 | }
32 |
33 | equals (key: any): boolean {
34 | return uint8ArrayEquals(this.bytes, key.bytes)
35 | }
36 |
37 | async hash (): Promise {
38 | const { bytes } = await sha256.digest(this.bytes)
39 |
40 | return bytes
41 | }
42 | }
43 |
44 | export class Secp256k1PrivateKey {
45 | private readonly _key: Uint8Array
46 | private readonly _publicKey: Uint8Array
47 |
48 | constructor (key: Uint8Array, publicKey?: Uint8Array) {
49 | this._key = key
50 | this._publicKey = publicKey ?? crypto.computePublicKey(key)
51 | crypto.validatePrivateKey(this._key)
52 | crypto.validatePublicKey(this._publicKey)
53 | }
54 |
55 | async sign (message: Uint8Array): Promise {
56 | return crypto.hashAndSign(this._key, message)
57 | }
58 |
59 | get public (): Secp256k1PublicKey {
60 | return new Secp256k1PublicKey(this._publicKey)
61 | }
62 |
63 | marshal (): Uint8Array {
64 | return this._key
65 | }
66 |
67 | get bytes (): Uint8Array {
68 | return keysProtobuf.PrivateKey.encode({
69 | Type: keysProtobuf.KeyType.Secp256k1,
70 | Data: this.marshal()
71 | }).subarray()
72 | }
73 |
74 | equals (key: any): boolean {
75 | return uint8ArrayEquals(this.bytes, key.bytes)
76 | }
77 |
78 | async hash (): Promise {
79 | const { bytes } = await sha256.digest(this.bytes)
80 |
81 | return bytes
82 | }
83 |
84 | /**
85 | * Gets the ID of the key.
86 | *
87 | * The key id is the base58 encoding of the SHA-256 multihash of its public key.
88 | * The public key is a protobuf encoding containing a type and the DER encoding
89 | * of the PKCS SubjectPublicKeyInfo.
90 | */
91 | async id (): Promise {
92 | const hash = await this.public.hash()
93 | return uint8ArrayToString(hash, 'base58btc')
94 | }
95 |
96 | /**
97 | * Exports the key into a password protected `format`
98 | */
99 | async export (password: string, format = 'libp2p-key'): Promise> {
100 | if (format === 'libp2p-key') {
101 | return exporter(this.bytes, password)
102 | } else {
103 | throw new CodeError(`export format '${format}' is not supported`, 'ERR_INVALID_EXPORT_FORMAT')
104 | }
105 | }
106 | }
107 |
108 | export function unmarshalSecp256k1PrivateKey (bytes: Uint8Array): Secp256k1PrivateKey {
109 | return new Secp256k1PrivateKey(bytes)
110 | }
111 |
112 | export function unmarshalSecp256k1PublicKey (bytes: Uint8Array): Secp256k1PublicKey {
113 | return new Secp256k1PublicKey(bytes)
114 | }
115 |
116 | export async function generateKeyPair (): Promise {
117 | const privateKeyBytes = crypto.generateKey()
118 | return new Secp256k1PrivateKey(privateKeyBytes)
119 | }
120 |
--------------------------------------------------------------------------------
/src/keys/secp256k1.ts:
--------------------------------------------------------------------------------
1 | import { CodeError } from '@libp2p/interfaces/errors'
2 | import * as secp from '@noble/secp256k1'
3 | import { sha256 } from 'multiformats/hashes/sha2'
4 |
5 | const PRIVATE_KEY_BYTE_LENGTH = 32
6 |
7 | export { PRIVATE_KEY_BYTE_LENGTH as privateKeyLength }
8 |
9 | export function generateKey (): Uint8Array {
10 | return secp.utils.randomPrivateKey()
11 | }
12 |
13 | /**
14 | * Hash and sign message with private key
15 | */
16 | export async function hashAndSign (key: Uint8Array, msg: Uint8Array): Promise {
17 | const { digest } = await sha256.digest(msg)
18 | try {
19 | return await secp.sign(digest, key)
20 | } catch (err) {
21 | throw new CodeError(String(err), 'ERR_INVALID_INPUT')
22 | }
23 | }
24 |
25 | /**
26 | * Hash message and verify signature with public key
27 | */
28 | export async function hashAndVerify (key: Uint8Array, sig: Uint8Array, msg: Uint8Array): Promise {
29 | try {
30 | const { digest } = await sha256.digest(msg)
31 | return secp.verify(sig, digest, key)
32 | } catch (err) {
33 | throw new CodeError(String(err), 'ERR_INVALID_INPUT')
34 | }
35 | }
36 |
37 | export function compressPublicKey (key: Uint8Array): Uint8Array {
38 | const point = secp.Point.fromHex(key).toRawBytes(true)
39 | return point
40 | }
41 |
42 | export function decompressPublicKey (key: Uint8Array): Uint8Array {
43 | const point = secp.Point.fromHex(key).toRawBytes(false)
44 | return point
45 | }
46 |
47 | export function validatePrivateKey (key: Uint8Array): void {
48 | try {
49 | secp.getPublicKey(key, true)
50 | } catch (err) {
51 | throw new CodeError(String(err), 'ERR_INVALID_PRIVATE_KEY')
52 | }
53 | }
54 |
55 | export function validatePublicKey (key: Uint8Array): void {
56 | try {
57 | secp.Point.fromHex(key)
58 | } catch (err) {
59 | throw new CodeError(String(err), 'ERR_INVALID_PUBLIC_KEY')
60 | }
61 | }
62 |
63 | export function computePublicKey (privateKey: Uint8Array): Uint8Array {
64 | try {
65 | return secp.getPublicKey(privateKey, true)
66 | } catch (err) {
67 | throw new CodeError(String(err), 'ERR_INVALID_PRIVATE_KEY')
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/pbkdf2.ts:
--------------------------------------------------------------------------------
1 | import { CodeError } from '@libp2p/interfaces/errors'
2 | // @ts-expect-error types are missing
3 | import forgePbkdf2 from 'node-forge/lib/pbkdf2.js'
4 | // @ts-expect-error types are missing
5 | import forgeUtil from 'node-forge/lib/util.js'
6 |
7 | /**
8 | * Maps an IPFS hash name to its node-forge equivalent.
9 | *
10 | * See https://github.com/multiformats/multihash/blob/master/hashtable.csv
11 | *
12 | * @private
13 | */
14 | const hashName = {
15 | sha1: 'sha1',
16 | 'sha2-256': 'sha256',
17 | 'sha2-512': 'sha512'
18 | }
19 |
20 | /**
21 | * Computes the Password-Based Key Derivation Function 2.
22 | */
23 | export default function pbkdf2 (password: string, salt: string, iterations: number, keySize: number, hash: string): string {
24 | if (hash !== 'sha1' && hash !== 'sha2-256' && hash !== 'sha2-512') {
25 | const types = Object.keys(hashName).join(' / ')
26 | throw new CodeError(`Hash '${hash}' is unknown or not supported. Must be ${types}`, 'ERR_UNSUPPORTED_HASH_TYPE')
27 | }
28 |
29 | const hasher = hashName[hash]
30 | const dek = forgePbkdf2(
31 | password,
32 | salt,
33 | iterations,
34 | keySize,
35 | hasher
36 | )
37 |
38 | return forgeUtil.encode64(dek, null)
39 | }
40 |
--------------------------------------------------------------------------------
/src/random-bytes.ts:
--------------------------------------------------------------------------------
1 | import { CodeError } from '@libp2p/interfaces/errors'
2 | import { utils } from '@noble/secp256k1'
3 |
4 | export default function randomBytes (length: number): Uint8Array {
5 | if (isNaN(length) || length <= 0) {
6 | throw new CodeError('random bytes length must be a Number bigger than 0', 'ERR_INVALID_LENGTH')
7 | }
8 | return utils.randomBytes(length)
9 | }
10 |
--------------------------------------------------------------------------------
/src/util.ts:
--------------------------------------------------------------------------------
1 | import 'node-forge/lib/util.js'
2 | import 'node-forge/lib/jsbn.js'
3 | // @ts-expect-error types are missing
4 | import forge from 'node-forge/lib/forge.js'
5 | import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
6 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
7 | import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
8 |
9 | export function bigIntegerToUintBase64url (num: { abs: () => any }, len?: number): string {
10 | // Call `.abs()` to convert to unsigned
11 | let buf = Uint8Array.from(num.abs().toByteArray()) // toByteArray converts to big endian
12 |
13 | // toByteArray() gives us back a signed array, which will include a leading 0
14 | // byte if the most significant bit of the number is 1:
15 | // https://docs.microsoft.com/en-us/windows/win32/seccertenroll/about-integer
16 | // Our number will always be positive so we should remove the leading padding.
17 | buf = buf[0] === 0 ? buf.subarray(1) : buf
18 |
19 | if (len != null) {
20 | if (buf.length > len) throw new Error('byte array longer than desired length')
21 | buf = uint8ArrayConcat([new Uint8Array(len - buf.length), buf])
22 | }
23 |
24 | return uint8ArrayToString(buf, 'base64url')
25 | }
26 |
27 | // Convert a base64url encoded string to a BigInteger
28 | export function base64urlToBigInteger (str: string): typeof forge.jsbn.BigInteger {
29 | const buf = base64urlToBuffer(str)
30 | return new forge.jsbn.BigInteger(uint8ArrayToString(buf, 'base16'), 16)
31 | }
32 |
33 | export function base64urlToBuffer (str: string, len?: number): Uint8Array {
34 | let buf = uint8ArrayFromString(str, 'base64urlpad')
35 |
36 | if (len != null) {
37 | if (buf.length > len) throw new Error('byte array longer than desired length')
38 | buf = uint8ArrayConcat([new Uint8Array(len - buf.length), buf])
39 | }
40 |
41 | return buf
42 | }
43 |
--------------------------------------------------------------------------------
/src/webcrypto.ts:
--------------------------------------------------------------------------------
1 | /* eslint-env browser */
2 |
3 | // Check native crypto exists and is enabled (In insecure context `self.crypto`
4 | // exists but `self.crypto.subtle` does not).
5 | export default {
6 | get (win = globalThis) {
7 | const nativeCrypto = win.crypto
8 |
9 | if (nativeCrypto == null || nativeCrypto.subtle == null) {
10 | throw Object.assign(
11 | new Error(
12 | 'Missing Web Crypto API. ' +
13 | 'The most likely cause of this error is that this page is being accessed ' +
14 | 'from an insecure context (i.e. not HTTPS). For more information and ' +
15 | 'possible resolutions see ' +
16 | 'https://github.com/libp2p/js-libp2p-crypto/blob/master/README.md#web-crypto-api'
17 | ),
18 | { code: 'ERR_MISSING_WEB_CRYPTO' }
19 | )
20 | }
21 |
22 | return nativeCrypto
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/stats.md:
--------------------------------------------------------------------------------
1 | # Stats
2 |
3 | ## Size
4 |
5 | | | non-minified | minified |
6 | |-------|--------------|----------|
7 | |before | `1.8M` | `949K` |
8 | |after | `606K` | `382K` |
9 |
10 | ## Performance
11 |
12 | ### RSA
13 |
14 | #### Before
15 |
16 | ##### Node `6.6.0`
17 |
18 | ```
19 | generateKeyPair 1024bits x 3.51 ops/sec ±29.45% (22 runs sampled)
20 | generateKeyPair 2048bits x 0.17 ops/sec ±145.40% (5 runs sampled)
21 | generateKeyPair 4096bits x 0.02 ops/sec ±96.53% (5 runs sampled)
22 | sign and verify x 95.98 ops/sec ±1.51% (71 runs sampled)
23 | ```
24 |
25 | ##### Browser (Chrome `53.0.2785.116`)
26 |
27 | ```
28 | generateKeyPair 1024bits x 3.56 ops/sec ±27.16% (23 runs sampled)
29 | generateKeyPair 2048bits x 0.49 ops/sec ±69.32% (8 runs sampled)
30 | generateKeyPair 4096bits x 0.03 ops/sec ±77.11% (5 runs sampled)
31 | sign and verify x 109 ops/sec ±2.00% (53 runs sampled)
32 | ```
33 |
34 | #### After
35 |
36 | ##### Node `6.6.0`
37 |
38 | ```
39 | generateKeyPair 1024bits x 42.45 ops/sec ±9.87% (52 runs sampled)
40 | generateKeyPair 2048bits x 7.46 ops/sec ±23.80% (16 runs sampled)
41 | generateKeyPair 4096bits x 1.50 ops/sec ±58.59% (13 runs sampled)
42 | sign and verify x 1,080 ops/sec ±2.23% (74 runs sampled)
43 | ```
44 |
45 | ##### Browser (Chrome `53.0.2785.116`)
46 |
47 | ```
48 | generateKeyPair 1024bits x 5.89 ops/sec ±18.94% (19 runs sampled)
49 | generateKeyPair 2048bits x 1.32 ops/sec ±36.84% (10 runs sampled)
50 | generateKeyPair 4096bits x 0.20 ops/sec ±62.49% (5 runs sampled)
51 | sign and verify x 608 ops/sec ±6.75% (56 runs sampled)
52 | ```
53 |
54 | ### Key Stretcher
55 |
56 |
57 | #### Before
58 |
59 | ##### Node `6.6.0`
60 |
61 | ```
62 | keyStretcher AES-128 SHA1 x 3,863 ops/sec ±3.80% (70 runs sampled)
63 | keyStretcher AES-128 SHA256 x 3,862 ops/sec ±5.33% (64 runs sampled)
64 | keyStretcher AES-128 SHA512 x 3,369 ops/sec ±1.73% (73 runs sampled)
65 | keyStretcher AES-256 SHA1 x 3,008 ops/sec ±4.81% (67 runs sampled)
66 | keyStretcher AES-256 SHA256 x 2,900 ops/sec ±7.01% (64 runs sampled)
67 | keyStretcher AES-256 SHA512 x 2,553 ops/sec ±4.45% (73 runs sampled)
68 | keyStretcher Blowfish SHA1 x 28,045 ops/sec ±7.32% (61 runs sampled)
69 | keyStretcher Blowfish SHA256 x 18,860 ops/sec ±5.36% (67 runs sampled)
70 | keyStretcher Blowfish SHA512 x 12,142 ops/sec ±12.44% (72 runs sampled)
71 | ```
72 |
73 | ##### Browser (Chrome `53.0.2785.116`)
74 |
75 | ```
76 | keyStretcher AES-128 SHA1 x 4,168 ops/sec ±4.08% (49 runs sampled)
77 | keyStretcher AES-128 SHA256 x 4,239 ops/sec ±6.36% (48 runs sampled)
78 | keyStretcher AES-128 SHA512 x 3,600 ops/sec ±5.15% (51 runs sampled)
79 | keyStretcher AES-256 SHA1 x 3,009 ops/sec ±6.82% (48 runs sampled)
80 | keyStretcher AES-256 SHA256 x 3,086 ops/sec ±9.56% (19 runs sampled)
81 | keyStretcher AES-256 SHA512 x 2,470 ops/sec ±2.22% (54 runs sampled)
82 | keyStretcher Blowfish SHA1 x 7,143 ops/sec ±15.17% (9 runs sampled)
83 | keyStretcher Blowfish SHA256 x 17,846 ops/sec ±4.74% (46 runs sampled)
84 | keyStretcher Blowfish SHA512 x 7,726 ops/sec ±1.81% (50 runs sampled)
85 | ```
86 |
87 | #### After
88 |
89 | ##### Node `6.6.0`
90 |
91 | ```
92 | keyStretcher AES-128 SHA1 x 6,680 ops/sec ±3.62% (65 runs sampled)
93 | keyStretcher AES-128 SHA256 x 8,124 ops/sec ±4.37% (66 runs sampled)
94 | keyStretcher AES-128 SHA512 x 11,683 ops/sec ±4.56% (66 runs sampled)
95 | keyStretcher AES-256 SHA1 x 5,531 ops/sec ±4.69% (68 runs sampled)
96 | keyStretcher AES-256 SHA256 x 6,725 ops/sec ±4.87% (66 runs sampled)
97 | keyStretcher AES-256 SHA512 x 9,042 ops/sec ±3.87% (64 runs sampled)
98 | keyStretcher Blowfish SHA1 x 40,757 ops/sec ±5.38% (60 runs sampled)
99 | keyStretcher Blowfish SHA256 x 41,845 ops/sec ±4.89% (64 runs sampled)
100 | keyStretcher Blowfish SHA512 x 42,345 ops/sec ±4.86% (63 runs sampled)
101 | ```
102 |
103 | ##### Browser (Chrome `53.0.2785.116`)
104 |
105 | ```
106 | keyStretcher AES-128 SHA1 x 479 ops/sec ±2.12% (54 runs sampled)
107 | keyStretcher AES-128 SHA256 x 668 ops/sec ±2.02% (53 runs sampled)
108 | keyStretcher AES-128 SHA512 x 1,112 ops/sec ±1.61% (54 runs sampled)
109 | keyStretcher AES-256 SHA1 x 460 ops/sec ±1.37% (54 runs sampled)
110 | keyStretcher AES-256 SHA256 x 596 ops/sec ±1.56% (54 runs sampled)
111 | keyStretcher AES-256 SHA512 x 808 ops/sec ±3.27% (52 runs sampled)
112 | keyStretcher Blowfish SHA1 x 3,015 ops/sec ±3.51% (52 runs sampled)
113 | keyStretcher Blowfish SHA256 x 2,755 ops/sec ±3.82% (53 runs sampled)
114 | keyStretcher Blowfish SHA512 x 2,955 ops/sec ±5.35% (51 runs sampled)
115 | ```
116 |
117 | ### Ephemeral Keys
118 |
119 | #### Before
120 |
121 | ##### Node `6.6.0`
122 |
123 | ```
124 | ephemeral key with secrect P-256 x 89.93 ops/sec ±39.45% (72 runs sampled)
125 | ephemeral key with secrect P-384 x 110 ops/sec ±1.28% (71 runs sampled)
126 | ephemeral key with secrect P-521 x 112 ops/sec ±1.70% (72 runs sampled)
127 | ```
128 |
129 | ##### Browser (Chrome `53.0.2785.116`)
130 |
131 | ```
132 | ephemeral key with secrect P-256 x 6.27 ops/sec ±15.89% (35 runs sampled)
133 | ephemeral key with secrect P-384 x 6.84 ops/sec ±1.21% (35 runs sampled)
134 | ephemeral key with secrect P-521 x 6.60 ops/sec ±1.84% (34 runs sampled)
135 | ```
136 |
137 | #### After
138 |
139 | ##### Node `6.6.0`
140 |
141 | ```
142 | ephemeral key with secrect P-256 x 555 ops/sec ±1.61% (75 runs sampled)
143 | ephemeral key with secrect P-384 x 547 ops/sec ±4.40% (68 runs sampled)
144 | ephemeral key with secrect P-521 x 583 ops/sec ±4.84% (72 runs sampled)
145 | ```
146 |
147 | ##### Browser (Chrome `53.0.2785.116`)
148 |
149 | ```
150 | ephemeral key with secrect P-256 x 796 ops/sec ±2.36% (53 runs sampled)
151 | ephemeral key with secrect P-384 x 788 ops/sec ±2.66% (53 runs sampled)
152 | ephemeral key with secrect P-521 x 808 ops/sec ±1.83% (54 runs sampled)
153 | ```
154 |
--------------------------------------------------------------------------------
/test/aes/aes.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint max-nested-callbacks: ["error", 8] */
2 | /* eslint-disable valid-jsdoc */
3 | /* eslint-env mocha */
4 | import { expect } from 'aegir/chai'
5 | import * as crypto from '../../src/index.js'
6 | import fixtures from './../fixtures/aes.js'
7 | import goFixtures from './../fixtures/go-aes.js'
8 | import type { AESCipher } from '../../src/aes/index.js'
9 |
10 | const bytes = [{
11 | length: 16,
12 | hash: 'AES-128'
13 | }, {
14 | length: 32,
15 | hash: 'AES-256'
16 | }]
17 |
18 | describe('AES-CTR', () => {
19 | bytes.forEach(({ length, hash }) => {
20 | it(`${hash} - encrypt and decrypt`, async () => {
21 | const key = new Uint8Array(length)
22 | key.fill(5)
23 |
24 | const iv = new Uint8Array(16)
25 | iv.fill(1)
26 |
27 | const cipher = await crypto.aes.create(key, iv)
28 |
29 | await encryptAndDecrypt(cipher)
30 | await encryptAndDecrypt(cipher)
31 | await encryptAndDecrypt(cipher)
32 | await encryptAndDecrypt(cipher)
33 | await encryptAndDecrypt(cipher)
34 | })
35 | })
36 |
37 | bytes.forEach(({ length, hash }) => {
38 | it(`${hash} - fixed - encrypt and decrypt`, async () => {
39 | const key = new Uint8Array(length)
40 | key.fill(5)
41 |
42 | const iv = new Uint8Array(16)
43 | iv.fill(1)
44 |
45 | const cipher = await crypto.aes.create(key, iv)
46 | // @ts-expect-error cannot index fixtures like this
47 | const fixture = fixtures[length]
48 |
49 | for (let i = 0; i < fixture.inputs.length; i++) {
50 | const input = fixture.inputs[i]
51 | const output = fixture.outputs[i]
52 | const encrypted = await cipher.encrypt(input)
53 | expect(encrypted).to.have.length(output.length)
54 | expect(encrypted).to.eql(output)
55 | const decrypted = await cipher.decrypt(encrypted)
56 | expect(decrypted).to.eql(input)
57 | }
58 | })
59 | })
60 |
61 | bytes.forEach(({ length, hash }) => {
62 | // @ts-expect-error cannot index fixtures like this
63 | if (goFixtures[length] == null) {
64 | return
65 | }
66 |
67 | it(`${hash} - go interop - encrypt and decrypt`, async () => {
68 | const key = new Uint8Array(length)
69 | key.fill(5)
70 |
71 | const iv = new Uint8Array(16)
72 | iv.fill(1)
73 |
74 | const cipher = await crypto.aes.create(key, iv)
75 | // @ts-expect-error cannot index fixtures like this
76 | const fixture = goFixtures[length]
77 |
78 | for (let i = 0; i < fixture.inputs.length; i++) {
79 | const input = fixture.inputs[i]
80 | const output = fixture.outputs[i]
81 | const encrypted = await cipher.encrypt(input)
82 | expect(encrypted).to.have.length(output.length)
83 | expect(encrypted).to.eql(output)
84 | const decrypted = await cipher.decrypt(encrypted)
85 | expect(decrypted).to.eql(input)
86 | }
87 | })
88 | })
89 |
90 | it('checks key length', () => {
91 | const key = new Uint8Array(5)
92 | const iv = new Uint8Array(16)
93 | return expect(crypto.aes.create(key, iv)).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_LENGTH')
94 | })
95 | })
96 |
97 | async function encryptAndDecrypt (cipher: AESCipher): Promise {
98 | const data = new Uint8Array(100)
99 | data.fill(Math.ceil(Math.random() * 100))
100 |
101 | const encrypted = await cipher.encrypt(data)
102 | const decrypted = await cipher.decrypt(encrypted)
103 |
104 | expect(decrypted).to.be.eql(data)
105 | }
106 |
--------------------------------------------------------------------------------
/test/crypto.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint max-nested-callbacks: ["error", 8] */
2 | /* eslint-env mocha */
3 | import { expect } from 'aegir/chai'
4 | import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
5 | import * as crypto from '../src/index.js'
6 | import { RsaPrivateKey, RsaPublicKey } from '../src/keys/rsa-class.js'
7 | import fixtures from './fixtures/go-key-rsa.js'
8 |
9 | describe('libp2p-crypto', function () {
10 | this.timeout(20 * 1000)
11 | let key: RsaPrivateKey
12 | before(async () => {
13 | const generated = await crypto.keys.generateKeyPair('RSA', 512)
14 |
15 | if (!(generated instanceof RsaPrivateKey)) {
16 | throw new Error('Key was incorrect type')
17 | }
18 |
19 | key = generated
20 | })
21 |
22 | it('marshalPublicKey and unmarshalPublicKey', () => {
23 | const key2 = crypto.keys.unmarshalPublicKey(crypto.keys.marshalPublicKey(key.public))
24 |
25 | if (!(key2 instanceof RsaPublicKey)) {
26 | throw new Error('Wrong key type unmarshalled')
27 | }
28 |
29 | expect(key2.equals(key.public)).to.be.eql(true)
30 |
31 | expect(() => {
32 | crypto.keys.marshalPublicKey(key.public, 'invalid-key-type')
33 | }).to.throw()
34 | })
35 |
36 | it('marshalPrivateKey and unmarshalPrivateKey', async () => {
37 | expect(() => {
38 | crypto.keys.marshalPrivateKey(key, 'invalid-key-type')
39 | }).to.throw()
40 |
41 | const key2 = await crypto.keys.unmarshalPrivateKey(crypto.keys.marshalPrivateKey(key))
42 |
43 | if (!(key2 instanceof RsaPrivateKey)) {
44 | throw new Error('Wrong key type unmarshalled')
45 | }
46 |
47 | expect(key2.equals(key)).to.be.eql(true)
48 | expect(key2.public.equals(key.public)).to.be.eql(true)
49 | })
50 |
51 | it('generateKeyPair', () => {
52 | // @ts-expect-error key type is invalid
53 | return expect(crypto.keys.generateKeyPair('invalid-key-type', 512)).to.eventually.be.rejected.with.property('code', 'ERR_UNSUPPORTED_KEY_TYPE')
54 | })
55 |
56 | it('generateKeyPairFromSeed', () => {
57 | const seed = crypto.randomBytes(32)
58 |
59 | // @ts-expect-error key type is invalid
60 | return expect(crypto.keys.generateKeyPairFromSeed('invalid-key-type', seed, 512)).to.eventually.be.rejected.with.property('code', 'ERR_UNSUPPORTED_KEY_DERIVATION_TYPE')
61 | })
62 |
63 | // https://github.com/libp2p/js-libp2p-crypto/issues/314
64 | function isSafari (): boolean {
65 | return typeof navigator !== 'undefined' && navigator.userAgent.includes('AppleWebKit') && !navigator.userAgent.includes('Chrome') && navigator.userAgent.includes('Mac')
66 | }
67 |
68 | // marshalled keys seem to be slightly different
69 | // unsure as to if this is just a difference in encoding
70 | // or a bug
71 | describe('go interop', () => {
72 | it('unmarshals private key', async () => {
73 | if (isSafari()) {
74 | // eslint-disable-next-line no-console
75 | console.warn('Skipping test in Safari. Known bug: https://github.com/libp2p/js-libp2p-crypto/issues/314')
76 | return
77 | }
78 |
79 | const key = await crypto.keys.unmarshalPrivateKey(fixtures.private.key)
80 | const hash = fixtures.private.hash
81 | expect(fixtures.private.key).to.eql(key.bytes)
82 | const digest = await key.hash()
83 | expect(digest).to.eql(hash)
84 | })
85 |
86 | it('unmarshals public key', async () => {
87 | const key = crypto.keys.unmarshalPublicKey(fixtures.public.key)
88 | const hash = fixtures.public.hash
89 | expect(crypto.keys.marshalPublicKey(key)).to.eql(fixtures.public.key)
90 | const digest = await key.hash()
91 | expect(digest).to.eql(hash)
92 | })
93 |
94 | it('unmarshal -> marshal, private key', async () => {
95 | const key = await crypto.keys.unmarshalPrivateKey(fixtures.private.key)
96 | const marshalled = crypto.keys.marshalPrivateKey(key)
97 | if (isSafari()) {
98 | // eslint-disable-next-line no-console
99 | console.warn('Running differnt test in Safari. Known bug: https://github.com/libp2p/js-libp2p-crypto/issues/314')
100 | const key2 = await crypto.keys.unmarshalPrivateKey(marshalled)
101 | expect(key2.bytes).to.eql(key.bytes)
102 | return
103 | }
104 | expect(marshalled).to.eql(fixtures.private.key)
105 | })
106 |
107 | it('unmarshal -> marshal, public key', () => {
108 | const key = crypto.keys.unmarshalPublicKey(fixtures.public.key)
109 | const marshalled = crypto.keys.marshalPublicKey(key)
110 | expect(uint8ArrayEquals(fixtures.public.key, marshalled)).to.eql(true)
111 | })
112 | })
113 |
114 | describe('pbkdf2', () => {
115 | it('generates a derived password using sha1', () => {
116 | const p1 = crypto.pbkdf2('password', 'at least 16 character salt', 500, 512 / 8, 'sha1')
117 | expect(p1).to.exist()
118 | expect(p1).to.be.a('string')
119 | })
120 |
121 | it('generates a derived password using sha2-512', () => {
122 | const p1 = crypto.pbkdf2('password', 'at least 16 character salt', 500, 512 / 8, 'sha2-512')
123 | expect(p1).to.exist()
124 | expect(p1).to.be.a('string')
125 | })
126 |
127 | it('generates the same derived password with the same options', () => {
128 | const p1 = crypto.pbkdf2('password', 'at least 16 character salt', 10, 512 / 8, 'sha1')
129 | const p2 = crypto.pbkdf2('password', 'at least 16 character salt', 10, 512 / 8, 'sha1')
130 | const p3 = crypto.pbkdf2('password', 'at least 16 character salt', 11, 512 / 8, 'sha1')
131 | expect(p2).to.equal(p1)
132 | expect(p3).to.not.equal(p2)
133 | })
134 |
135 | it('throws on invalid hash name', () => {
136 | const fn = (): string => crypto.pbkdf2('password', 'at least 16 character salt', 500, 512 / 8, 'shaX-xxx')
137 | expect(fn).to.throw().with.property('code', 'ERR_UNSUPPORTED_HASH_TYPE')
138 | })
139 | })
140 |
141 | describe('randomBytes', () => {
142 | it('throws with invalid number passed', () => {
143 | expect(() => {
144 | crypto.randomBytes(-1)
145 | }).to.throw()
146 | })
147 |
148 | it('generates different random things', () => {
149 | const buf1 = crypto.randomBytes(10)
150 | expect(buf1.length).to.equal(10)
151 | const buf2 = crypto.randomBytes(10)
152 | expect(buf1).to.not.eql(buf2)
153 | })
154 | })
155 | })
156 |
--------------------------------------------------------------------------------
/test/fixtures/aes.ts:
--------------------------------------------------------------------------------
1 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
2 |
3 | export default {
4 | 16: {
5 | inputs: [
6 | uint8ArrayFromString('Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLw', 'base64'),
7 | uint8ArrayFromString('GBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGA', 'base64'),
8 | uint8ArrayFromString('BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBw', 'base64'),
9 | uint8ArrayFromString('GRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGQ', 'base64'),
10 | uint8ArrayFromString('MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMA', 'base64')
11 | ],
12 | outputs: [
13 | uint8ArrayFromString('eWgAlzmJFu/qlzoPZBbkblX4+Q+RgN8ZwK+EqWLL52rgZs70HdUkAhrVXh2G24hJ1LAhX8ZblIuE/LZzdKCSwgBhtQDBlRUz+GEgPlaZ7kMmIjePRsFjax9DWmE3P0XLIelK7Q', 'base64'),
14 | uint8ArrayFromString('bax/s27eT9tXTEbMpm645VoxoPxOOkkzmNoDyAp8mHWJKBd/ODnLH2XjH8bfVmJ4ZFZ0kI5/RK/56BZTFkQ85pIWmcFDBTP979JQH/5nuZF7Y82vnJC/Qx/sK2LF6x8yReRkQA', 'base64'),
15 | uint8ArrayFromString('v11+v7QqdpAcvNO/04KqmYbws1NLFypEnsh7mzmpmIUhclodg1tGaVMtKC9NYGEIKAFu9WqsmJIFcoQAsx8sThNtXMfiJAxKtPHga1MNpxv7ZcFiMVrhxUvVkDTrglz324vRhA', 'base64'),
16 | uint8ArrayFromString('v241vvosvogW0YPIcB89t/dfBPlFk+5KEkfFc43iZlxbgLWsQ20RpTQKNx834f2MmiNoPndnxZh9hoy1qkxLcsO8RMUcL3RSIoDoeg7leqEk1KGkkVbX6d4yj1mDIILEbSTM/g', 'base64'),
17 | uint8ArrayFromString('YY4IJUWtfLRhRi1bxH64h9Voq1nnPysqB/VLpc22GDKq2YBwctfRkYfrs9QFUY7HNd0n76cV7aiR+fpsAvdZSeTj/5t5nc1gKyBw0a1gjyvcjBrNIiI1nSmnfevzVQ0OXW3pug', 'base64')
18 | ]
19 | },
20 | 32: {
21 | inputs: [
22 | uint8ArrayFromString('RERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERA', 'base64'),
23 | uint8ArrayFromString('CgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg', 'base64'),
24 | uint8ArrayFromString('S0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw', 'base64'),
25 | uint8ArrayFromString('IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw', 'base64'),
26 | uint8ArrayFromString('SEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISA', 'base64')
27 | ],
28 | outputs: [
29 | uint8ArrayFromString('xjtba97WH+ZwAceuEeDC4YpBOqAf+WYQSi5VHuj5eGwmpWkbDGJibIKjEIl0aJm8Bfuj9AA6Ac8ZTrzSzd0whEjRC0MOKtpHlPx+tzwgXSR9Z791UvG+sAGBdnCLmbI4PVs0xg', 'base64'),
30 | uint8ArrayFromString('zvFjePijOR7YVv/AWcGwEU4+UJW96xudr/gHE95Ab8fMoxoQX91GIO8EOqL97QgzXgOlut/SdGXkUmdMSiwzdb2MhOa88xielV2T4nHDHxgJExuEtJgaQX2QVqcpkJ7MTC61bg', 'base64'),
31 | uint8ArrayFromString('maHJQlcu8leI7pfGgXo+zY78bbvutz9f8GHc0SWQ7VT7lZjeWcjQd9UmQRMC/MF9Xky2xvN5/RAt/lIvks4paf7t7I121sXkO30tyD0YbhrrXK//VXc5oJFrzhw+CqZZBxT28w', 'base64'),
32 | uint8ArrayFromString('T9cWHYSC1vjtfOo2104A0/beNls1AjEoAMq8Gbh5pOu9YQ4AU6ZYPjcxT5mIwYXOrGPPSfbYwGsyzqfyGbQ/uMk9WvLfwA2MH/BwnfpajgMoDGo/SSpPUhQpu60XVTv91L9tLg', 'base64'),
33 | uint8ArrayFromString('yeA4QKfgfDETM9Di2DIMSQ//nGxis5BuIZcrQOOZeCcVlyk99RQfF23VbTcjKHptKQogsBm4W7Cxhor8oAJsK97vrgKRSiKD7dbrZhrMfEBlhrotNx00N6tfrFbyZY2Z3qGAUw', 'base64')
34 | ]
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/test/fixtures/go-aes.ts:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | 16: {
4 | inputs: [
5 | Uint8Array.from([47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47]),
6 | Uint8Array.from([24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24]),
7 | Uint8Array.from([7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7]),
8 | Uint8Array.from([25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25]),
9 | Uint8Array.from([48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48])
10 | ],
11 | outputs: [
12 | Uint8Array.from([121, 104, 0, 151, 57, 137, 22, 239, 234, 151, 58, 15, 100, 22, 228, 110, 85, 248, 249, 15, 145, 128, 223, 25, 192, 175, 132, 169, 98, 203, 231, 106, 224, 102, 206, 244, 29, 213, 36, 2, 26, 213, 94, 29, 134, 219, 136, 73, 212, 176, 33, 95, 198, 91, 148, 139, 132, 252, 182, 115, 116, 160, 146, 194, 0, 97, 181, 0, 193, 149, 21, 51, 248, 97, 32, 62, 86, 153, 238, 67, 38, 34, 55, 143, 70, 193, 99, 107, 31, 67, 90, 97, 55, 63, 69, 203, 33, 233, 74, 237]),
13 | Uint8Array.from([109, 172, 127, 179, 110, 222, 79, 219, 87, 76, 70, 204, 166, 110, 184, 229, 90, 49, 160, 252, 78, 58, 73, 51, 152, 218, 3, 200, 10, 124, 152, 117, 137, 40, 23, 127, 56, 57, 203, 31, 101, 227, 31, 198, 223, 86, 98, 120, 100, 86, 116, 144, 142, 127, 68, 175, 249, 232, 22, 83, 22, 68, 60, 230, 146, 22, 153, 193, 67, 5, 51, 253, 239, 210, 80, 31, 254, 103, 185, 145, 123, 99, 205, 175, 156, 144, 191, 67, 31, 236, 43, 98, 197, 235, 31, 50, 69, 228, 100, 64]),
14 | Uint8Array.from([191, 93, 126, 191, 180, 42, 118, 144, 28, 188, 211, 191, 211, 130, 170, 153, 134, 240, 179, 83, 75, 23, 42, 68, 158, 200, 123, 155, 57, 169, 152, 133, 33, 114, 90, 29, 131, 91, 70, 105, 83, 45, 40, 47, 77, 96, 97, 8, 40, 1, 110, 245, 106, 172, 152, 146, 5, 114, 132, 0, 179, 31, 44, 78, 19, 109, 92, 199, 226, 36, 12, 74, 180, 241, 224, 107, 83, 13, 167, 27, 251, 101, 193, 98, 49, 90, 225, 197, 75, 213, 144, 52, 235, 130, 92, 247, 219, 139, 209, 132]),
15 | Uint8Array.from([191, 110, 53, 190, 250, 44, 190, 136, 22, 209, 131, 200, 112, 31, 61, 183, 247, 95, 4, 249, 69, 147, 238, 74, 18, 71, 197, 115, 141, 226, 102, 92, 91, 128, 181, 172, 67, 109, 17, 165, 52, 10, 55, 31, 55, 225, 253, 140, 154, 35, 104, 62, 119, 103, 197, 152, 125, 134, 140, 181, 170, 76, 75, 114, 195, 188, 68, 197, 28, 47, 116, 82, 34, 128, 232, 122, 14, 229, 122, 161, 36, 212, 161, 164, 145, 86, 215, 233, 222, 50, 143, 89, 131, 32, 130, 196, 109, 36, 204, 254]),
16 | Uint8Array.from([97, 142, 8, 37, 69, 173, 124, 180, 97, 70, 45, 91, 196, 126, 184, 135, 213, 104, 171, 89, 231, 63, 43, 42, 7, 245, 75, 165, 205, 182, 24, 50, 170, 217, 128, 112, 114, 215, 209, 145, 135, 235, 179, 212, 5, 81, 142, 199, 53, 221, 39, 239, 167, 21, 237, 168, 145, 249, 250, 108, 2, 247, 89, 73, 228, 227, 255, 155, 121, 157, 205, 96, 43, 32, 112, 209, 173, 96, 143, 43, 220, 140, 26, 205, 34, 34, 53, 157, 41, 167, 125, 235, 243, 85, 13, 14, 93, 109, 233, 186])
17 | ]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/test/fixtures/go-elliptic-key.ts:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | curve: 'P-256',
4 | bob: {
5 | private: Uint8Array.from([
6 | 181, 217, 162, 151, 225, 36, 53, 253, 107, 66, 27, 27, 232, 72, 0, 0, 103, 167, 84, 62, 203, 91, 97, 137, 131, 193, 230, 126, 98, 242, 216, 170
7 | ]),
8 | public: Uint8Array.from([
9 | 4, 53, 59, 128, 56, 162, 250, 72, 141, 206, 117, 232, 57, 96, 39, 39, 247, 7, 27, 57, 251, 232, 120, 186, 21, 239, 176, 139, 195, 129, 125, 85, 11, 188, 191, 32, 227, 0, 6, 163, 101, 68, 208, 1, 43, 131, 124, 112, 102, 91, 104, 79, 16, 119, 152, 208, 4, 147, 155, 83, 20, 146, 104, 55, 90
10 | ])
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/fixtures/go-key-ed25519.ts:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | // Generation code from https://github.com/libp2p/js-libp2p-crypto/issues/175#issuecomment-634467463
4 | //
5 | // package main
6 | //
7 | // import (
8 | // "crypto/rand"
9 | // "fmt"
10 | // "strings"
11 |
12 | // "github.com/libp2p/go-libp2p-core/crypto"
13 | // )
14 |
15 | // func main() {
16 | // priv, pub, _ := crypto.GenerateEd25519Key(rand.Reader)
17 | // pubkeyBytes, _ := pub.Bytes()
18 | // privkeyBytes, _ := priv.Bytes()
19 | // data := []byte("hello! and welcome to some awesome crypto primitives")
20 | // sig, _ := priv.Sign(data)
21 | // fmt.Println("{\n publicKey: Uint8Array.from(", strings.Replace(fmt.Sprint(pubkeyBytes), " ", ",", -1), "),")
22 | // fmt.Println(" privateKey: Uint8Array.from(", strings.Replace(fmt.Sprint(privkeyBytes), " ", ",", -1), "),")
23 | // fmt.Println(" data: Uint8Array.from(", strings.Replace(fmt.Sprint(data), " ", ",", -1), "),")
24 | // fmt.Println(" signature: Uint8Array.from(", strings.Replace(fmt.Sprint(sig), " ", ",", -1), ")\n}")
25 | // }
26 | //
27 |
28 | // The legacy key unnecessarily appends the publickey. (It's already included) See https://github.com/libp2p/js-libp2p-crypto/issues/175
29 | redundantPubKey: {
30 | privateKey: Uint8Array.from([8, 1, 18, 96, 201, 208, 1, 110, 176, 16, 230, 37, 66, 184, 149, 252, 78, 56, 206, 136, 2, 38, 118, 152, 226, 197, 117, 200, 54, 189, 156, 218, 184, 7, 118, 57, 233, 49, 221, 97, 164, 158, 241, 129, 73, 166, 225, 255, 193, 118, 22, 84, 55, 15, 249, 168, 225, 180, 198, 191, 14, 75, 187, 243, 150, 91, 232, 37, 233, 49, 221, 97, 164, 158, 241, 129, 73, 166, 225, 255, 193, 118, 22, 84, 55, 15, 249, 168, 225, 180, 198, 191, 14, 75, 187, 243, 150, 91, 232, 37]),
31 | publicKey: Uint8Array.from([8, 1, 18, 32, 233, 49, 221, 97, 164, 158, 241, 129, 73, 166, 225, 255, 193, 118, 22, 84, 55, 15, 249, 168, 225, 180, 198, 191, 14, 75, 187, 243, 150, 91, 232, 37]),
32 | data: Uint8Array.from([104, 101, 108, 108, 111, 33, 32, 97, 110, 100, 32, 119, 101, 108, 99, 111, 109, 101, 32, 116, 111, 32, 115, 111, 109, 101, 32, 97, 119, 101, 115, 111, 109, 101, 32, 99, 114, 121, 112, 116, 111, 32, 112, 114, 105, 109, 105, 116, 105, 118, 101, 115]),
33 | signature: Uint8Array.from([7, 230, 175, 164, 228, 58, 78, 208, 62, 243, 73, 142, 83, 195, 176, 217, 166, 62, 41, 165, 168, 164, 75, 179, 163, 86, 102, 32, 18, 84, 150, 237, 39, 207, 213, 20, 134, 237, 50, 41, 176, 183, 229, 133, 38, 255, 42, 228, 68, 186, 100, 14, 175, 156, 243, 118, 125, 125, 120, 212, 124, 103, 252, 12])
34 | },
35 | verify: {
36 | publicKey: Uint8Array.from([8, 1, 18, 32, 163, 176, 195, 47, 254, 208, 49, 5, 192, 102, 32, 63, 58, 202, 171, 153, 146, 164, 25, 212, 25, 91, 146, 26, 117, 165, 148, 6, 207, 90, 217, 126]),
37 | privateKey: Uint8Array.from([8, 1, 18, 64, 232, 56, 175, 20, 240, 160, 19, 47, 92, 88, 115, 221, 164, 13, 36, 162, 158, 136, 247, 31, 29, 231, 76, 143, 12, 91, 193, 4, 88, 33, 67, 23, 163, 176, 195, 47, 254, 208, 49, 5, 192, 102, 32, 63, 58, 202, 171, 153, 146, 164, 25, 212, 25, 91, 146, 26, 117, 165, 148, 6, 207, 90, 217, 126]),
38 | data: Uint8Array.from([104, 101, 108, 108, 111, 33, 32, 97, 110, 100, 32, 119, 101, 108, 99, 111, 109, 101, 32, 116, 111, 32, 115, 111, 109, 101, 32, 97, 119, 101, 115, 111, 109, 101, 32, 99, 114, 121, 112, 116, 111, 32, 112, 114, 105, 109, 105, 116, 105, 118, 101, 115]),
39 | signature: Uint8Array.from([160, 125, 30, 62, 213, 189, 239, 92, 87, 76, 205, 169, 251, 149, 187, 57, 96, 85, 175, 213, 22, 132, 229, 60, 196, 18, 117, 194, 12, 174, 135, 31, 39, 168, 174, 103, 78, 55, 37, 222, 37, 172, 222, 239, 153, 63, 197, 152, 67, 167, 191, 215, 161, 212, 216, 163, 81, 77, 45, 228, 151, 79, 101, 1])
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/test/fixtures/go-key-rsa.ts:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | private: {
4 | hash: Uint8Array.from([
5 | 18, 32, 168, 125, 165, 65, 34, 157, 209, 4, 24, 158, 80, 196, 125, 86, 103, 0, 228, 145, 109, 252, 153, 7, 189, 9, 16, 37, 239, 36, 48, 78, 214, 212
6 | ]),
7 | key: Uint8Array.from([
8 | 8, 0, 18, 192, 2, 48, 130, 1, 60, 2, 1, 0, 2, 65, 0, 230, 157, 160, 242, 74, 222, 87, 0, 77, 180, 91, 175, 217, 166, 2, 95, 193, 239, 195, 140, 224, 57, 84, 207, 46, 172, 113, 196, 20, 133, 117, 205, 45, 7, 224, 41, 40, 195, 254, 124, 14, 84, 223, 147, 67, 198, 48, 36, 53, 161, 112, 46, 153, 90, 19, 123, 94, 247, 5, 116, 1, 238, 32, 15, 2, 3, 1, 0, 1, 2, 65, 0, 191, 59, 140, 255, 254, 23, 123, 91, 148, 19, 240, 71, 213, 26, 181, 51, 68, 181, 150, 153, 214, 65, 148, 83, 45, 103, 239, 250, 225, 237, 125, 173, 111, 244, 37, 124, 87, 178, 86, 10, 14, 207, 63, 105, 213, 37, 81, 23, 230, 4, 222, 179, 144, 40, 252, 163, 190, 7, 241, 221, 28, 54, 225, 209, 2, 33, 0, 235, 132, 229, 150, 99, 182, 176, 194, 198, 65, 210, 160, 184, 70, 82, 49, 235, 199, 14, 11, 92, 66, 237, 45, 220, 72, 235, 1, 244, 145, 205, 57, 2, 33, 0, 250, 171, 146, 180, 188, 194, 14, 152, 52, 64, 38, 52, 158, 86, 46, 109, 66, 100, 122, 43, 88, 167, 143, 98, 104, 143, 160, 60, 171, 185, 31, 135, 2, 33, 0, 206, 47, 255, 203, 100, 170, 137, 31, 75, 240, 78, 84, 212, 95, 4, 16, 158, 73, 27, 27, 136, 255, 50, 163, 166, 169, 211, 204, 87, 111, 217, 201, 2, 33, 0, 177, 51, 194, 213, 3, 175, 7, 84, 47, 115, 189, 206, 106, 180, 47, 195, 203, 48, 110, 112, 224, 14, 43, 189, 124, 127, 51, 222, 79, 226, 225, 87, 2, 32, 67, 23, 190, 222, 106, 22, 115, 139, 217, 244, 178, 53, 153, 99, 5, 176, 72, 77, 193, 61, 67, 134, 37, 238, 69, 66, 159, 28, 39, 5, 238, 125
9 | ])
10 | },
11 | public: {
12 | hash: Uint8Array.from([
13 | 18, 32, 112, 151, 163, 167, 204, 243, 175, 123, 208, 162, 90, 84, 199, 174, 202, 110, 0, 119, 27, 202, 7, 149, 161, 251, 215, 168, 163, 54, 93, 54, 195, 20
14 | ]),
15 | key: Uint8Array.from([
16 | 8, 0, 18, 94, 48, 92, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 75, 0, 48, 72, 2, 65, 0, 230, 157, 160, 242, 74, 222, 87, 0, 77, 180, 91, 175, 217, 166, 2, 95, 193, 239, 195, 140, 224, 57, 84, 207, 46, 172, 113, 196, 20, 133, 117, 205, 45, 7, 224, 41, 40, 195, 254, 124, 14, 84, 223, 147, 67, 198, 48, 36, 53, 161, 112, 46, 153, 90, 19, 123, 94, 247, 5, 116, 1, 238, 32, 15, 2, 3, 1, 0, 1
17 | ])
18 | },
19 | verify: {
20 | signature: Uint8Array.from([
21 | 3, 116, 81, 57, 91, 194, 7, 1, 230, 236, 229, 142, 36, 209, 208, 107, 47, 52, 164, 236, 139, 35, 155, 97, 43, 64, 145, 91, 19, 218, 149, 63, 99, 164, 191, 110, 145, 37, 18, 7, 98, 112, 144, 35, 29, 186, 169, 150, 165, 88, 145, 170, 197, 110, 42, 163, 188, 10, 42, 63, 34, 93, 91, 94, 199, 110, 10, 82, 238, 80, 93, 93, 77, 130, 22, 216, 229, 172, 36, 229, 82, 162, 20, 78, 19, 46, 82, 243, 43, 80, 115, 125, 145, 231, 194, 224, 30, 187, 55, 228, 74, 52, 203, 191, 254, 148, 136, 218, 62, 147, 171, 130, 251, 181, 105, 29, 238, 207, 197, 249, 61, 105, 202, 172, 160, 174, 43, 124, 115, 130, 169, 30, 76, 41, 52, 200, 2, 26, 53, 190, 43, 20, 203, 10, 217, 250, 47, 102, 92, 103, 197, 22, 108, 184, 74, 218, 82, 202, 180, 98, 13, 114, 12, 92, 1, 139, 150, 170, 8, 92, 32, 116, 168, 219, 157, 162, 28, 77, 29, 29, 74, 136, 144, 49, 173, 245, 253, 76, 167, 200, 169, 163, 7, 49, 133, 120, 99, 191, 53, 10, 66, 26, 234, 240, 139, 235, 134, 30, 55, 248, 150, 100, 242, 150, 159, 198, 44, 78, 150, 7, 133, 139, 59, 76, 3, 225, 94, 13, 89, 122, 34, 95, 95, 107, 74, 169, 171, 169, 222, 25, 191, 182, 148, 116, 66, 67, 102, 12, 193, 217, 247, 243, 148, 233, 161, 157
22 | ]),
23 | data: Uint8Array.from([
24 | 10, 16, 27, 128, 228, 220, 147, 176, 53, 105, 175, 171, 32, 213, 35, 236, 203, 60, 18, 171, 2, 8, 0, 18, 166, 2, 48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, 10, 2, 130, 1, 1, 0, 181, 113, 138, 108, 208, 103, 166, 102, 37, 36, 204, 250, 228, 165, 44, 64, 176, 210, 205, 141, 241, 55, 200, 110, 98, 68, 85, 199, 254, 19, 86, 204, 63, 250, 167, 38, 59, 249, 146, 228, 73, 171, 63, 18, 96, 104, 191, 137, 186, 244, 255, 90, 16, 119, 195, 52, 177, 213, 254, 187, 174, 84, 174, 173, 12, 236, 53, 234, 3, 209, 82, 37, 78, 111, 214, 135, 76, 195, 9, 242, 134, 188, 153, 84, 139, 231, 51, 146, 177, 60, 12, 25, 158, 91, 215, 152, 7, 0, 84, 35, 36, 230, 227, 67, 198, 72, 50, 110, 37, 209, 98, 193, 65, 93, 173, 199, 4, 198, 102, 99, 148, 144, 224, 217, 114, 53, 144, 245, 251, 114, 211, 20, 82, 163, 123, 75, 16, 192, 106, 213, 128, 2, 11, 200, 203, 84, 41, 199, 224, 155, 171, 217, 64, 109, 116, 188, 151, 183, 173, 52, 205, 164, 93, 13, 251, 65, 182, 160, 154, 185, 239, 33, 184, 84, 159, 105, 101, 173, 194, 251, 123, 84, 92, 66, 61, 180, 45, 104, 162, 224, 214, 233, 64, 220, 165, 2, 104, 116, 150, 2, 234, 203, 112, 21, 124, 23, 48, 66, 30, 63, 30, 36, 246, 135, 203, 218, 115, 22, 189, 39, 39, 125, 205, 65, 222, 220, 77, 18, 84, 121, 161, 153, 125, 25, 139, 137, 170, 239, 150, 106, 119, 168, 216, 140, 113, 121, 26, 53, 118, 110, 53, 192, 244, 252, 145, 85, 2, 3, 1, 0, 1, 26, 17, 80, 45, 50, 53, 54, 44, 80, 45, 51, 56, 52, 44, 80, 45, 53, 50, 49, 34, 24, 65, 69, 83, 45, 50, 53, 54, 44, 65, 69, 83, 45, 49, 50, 56, 44, 66, 108, 111, 119, 102, 105, 115, 104, 42, 13, 83, 72, 65, 50, 53, 54, 44, 83, 72, 65, 53, 49, 50, 10, 16, 220, 83, 240, 105, 6, 203, 78, 83, 210, 115, 6, 106, 98, 82, 1, 161, 18, 171, 2, 8, 0, 18, 166, 2, 48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, 10, 2, 130, 1, 1, 0, 185, 234, 19, 191, 164, 33, 65, 94, 87, 42, 74, 83, 224, 25, 142, 44, 26, 7, 92, 242, 189, 42, 170, 197, 178, 92, 45, 240, 107, 141, 128, 59, 122, 252, 48, 140, 4, 85, 85, 203, 3, 197, 8, 127, 120, 98, 44, 169, 135, 196, 70, 137, 117, 180, 177, 134, 170, 35, 165, 88, 105, 30, 114, 138, 11, 96, 68, 99, 18, 149, 223, 166, 105, 12, 176, 77, 48, 214, 22, 236, 17, 154, 213, 209, 158, 169, 202, 5, 100, 210, 83, 90, 201, 38, 205, 246, 231, 106, 63, 86, 222, 143, 157, 173, 62, 4, 85, 232, 20, 188, 6, 209, 186, 132, 192, 117, 146, 181, 233, 26, 0, 240, 138, 206, 91, 170, 114, 137, 217, 132, 139, 242, 144, 213, 103, 101, 190, 146, 188, 250, 188, 134, 255, 70, 125, 78, 65, 136, 239, 190, 206, 139, 155, 140, 163, 233, 170, 247, 205, 87, 209, 19, 29, 173, 10, 147, 43, 28, 90, 46, 6, 197, 217, 186, 66, 68, 126, 76, 64, 184, 8, 170, 23, 79, 243, 223, 119, 133, 118, 50, 226, 44, 246, 176, 10, 161, 219, 83, 54, 68, 248, 5, 14, 177, 114, 54, 63, 11, 71, 136, 142, 56, 151, 123, 230, 61, 80, 15, 180, 42, 49, 220, 148, 99, 231, 20, 230, 220, 85, 207, 187, 37, 210, 137, 171, 125, 71, 14, 53, 100, 91, 83, 209, 50, 132, 165, 253, 25, 161, 5, 97, 164, 163, 83, 95, 53, 2, 3, 1, 0, 1, 26, 17, 80, 45, 50, 53, 54, 44, 80, 45, 51, 56, 52, 44, 80, 45, 53, 50, 49, 34, 15, 65, 69, 83, 45, 50, 53, 54, 44, 65, 69, 83, 45, 49, 50, 56, 42, 13, 83, 72, 65, 50, 53, 54, 44, 83, 72, 65, 53, 49, 50, 4, 97, 54, 203, 112, 136, 34, 231, 162, 19, 154, 131, 27, 105, 26, 121, 238, 120, 25, 203, 66, 232, 53, 198, 20, 19, 96, 119, 218, 90, 64, 170, 3, 132, 116, 1, 87, 116, 232, 165, 161, 198, 117, 167, 60, 145, 1, 253, 108, 50, 150, 117, 8, 140, 133, 48, 30, 236, 36, 84, 186, 22, 144, 87, 101
25 | ]),
26 | publicKey: Uint8Array.from([
27 | 8, 0, 18, 166, 2, 48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, 10, 2, 130, 1, 1, 0, 181, 113, 138, 108, 208, 103, 166, 102, 37, 36, 204, 250, 228, 165, 44, 64, 176, 210, 205, 141, 241, 55, 200, 110, 98, 68, 85, 199, 254, 19, 86, 204, 63, 250, 167, 38, 59, 249, 146, 228, 73, 171, 63, 18, 96, 104, 191, 137, 186, 244, 255, 90, 16, 119, 195, 52, 177, 213, 254, 187, 174, 84, 174, 173, 12, 236, 53, 234, 3, 209, 82, 37, 78, 111, 214, 135, 76, 195, 9, 242, 134, 188, 153, 84, 139, 231, 51, 146, 177, 60, 12, 25, 158, 91, 215, 152, 7, 0, 84, 35, 36, 230, 227, 67, 198, 72, 50, 110, 37, 209, 98, 193, 65, 93, 173, 199, 4, 198, 102, 99, 148, 144, 224, 217, 114, 53, 144, 245, 251, 114, 211, 20, 82, 163, 123, 75, 16, 192, 106, 213, 128, 2, 11, 200, 203, 84, 41, 199, 224, 155, 171, 217, 64, 109, 116, 188, 151, 183, 173, 52, 205, 164, 93, 13, 251, 65, 182, 160, 154, 185, 239, 33, 184, 84, 159, 105, 101, 173, 194, 251, 123, 84, 92, 66, 61, 180, 45, 104, 162, 224, 214, 233, 64, 220, 165, 2, 104, 116, 150, 2, 234, 203, 112, 21, 124, 23, 48, 66, 30, 63, 30, 36, 246, 135, 203, 218, 115, 22, 189, 39, 39, 125, 205, 65, 222, 220, 77, 18, 84, 121, 161, 153, 125, 25, 139, 137, 170, 239, 150, 106, 119, 168, 216, 140, 113, 121, 26, 53, 118, 110, 53, 192, 244, 252, 145, 85, 2, 3, 1, 0, 1
28 | ])
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/test/fixtures/go-key-secp256k1.ts:
--------------------------------------------------------------------------------
1 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
2 |
3 | // The keypair and signature below were generated in a gore repl session (https://github.com/motemen/gore)
4 | // using the secp256k1 fork of go-libp2p-crypto by github user @vyzo
5 | //
6 | // gore> :import github.com/vyzo/go-libp2p-crypto
7 | // gore> :import crypto/rand
8 | // gore> :import io/ioutil
9 | // gore> priv, pub, err := crypto.GenerateKeyPairWithReader(crypto.Secp256k1, 256, rand.Reader)
10 | // gore> privBytes, err := priv.Bytes()
11 | // gore> pubBytes, err := pub.Bytes()
12 | // gore> msg := []byte("hello! and welcome to some awesome crypto primitives")
13 | // gore> sig, err := priv.Sign(msg)
14 | // gore> ioutil.WriteFile("/tmp/secp-go-priv.bin", privBytes, 0644)
15 | // gore> ioutil.WriteFile("/tmp/secp-go-pub.bin", pubBytes, 0644)
16 | // gore> ioutil.WriteFile("/tmp/secp-go-sig.bin", sig, 0644)
17 | //
18 | // The generated files were then read in a node repl with e.g.:
19 | // > fs.readFileSync('/tmp/secp-go-pub.bin').toString('hex')
20 | // '08021221029c0ce5d53646ed47112560297a3e59b78b8cbd4bae37c7a0c236eeb91d0fbeaf'
21 | //
22 | // and the results copy/pasted in here
23 |
24 | export default {
25 | privateKey: uint8ArrayFromString('08021220358f15db8c2014d570e8e3a622454e2273975a3cca443ec0c45375b13d381d18', 'base16'),
26 | publicKey: uint8ArrayFromString('08021221029c0ce5d53646ed47112560297a3e59b78b8cbd4bae37c7a0c236eeb91d0fbeaf', 'base16'),
27 | message: uint8ArrayFromString('hello! and welcome to some awesome crypto primitives'),
28 | signature: uint8ArrayFromString('304402200e4c629e9f5d99439115e60989cd40087f6978c36078b0b50cf3d30af5c38d4102204110342c8e7f0809897c1c7a66e49e1c6b7cb0a6ed6993640ec2fe742c1899a9', 'base16')
29 | }
30 |
--------------------------------------------------------------------------------
/test/fixtures/go-stretch-key.ts:
--------------------------------------------------------------------------------
1 |
2 | export default [{
3 | cipher: 'AES-256' as 'AES-256',
4 | hash: 'SHA256' as 'SHA256',
5 | secret: Uint8Array.from([
6 | 195, 191, 209, 165, 209, 201, 127, 122, 136, 111, 31, 66, 111, 68, 38, 155, 216, 204, 46, 181, 200, 188, 170, 204, 104, 74, 239, 251, 173, 114, 222, 234
7 | ]),
8 | k1: {
9 | iv: Uint8Array.from([
10 | 208, 132, 203, 169, 253, 52, 40, 83, 161, 91, 17, 71, 33, 136, 67, 96
11 | ]),
12 | cipherKey: Uint8Array.from([
13 | 156, 48, 241, 157, 92, 248, 153, 186, 114, 127, 195, 114, 106, 104, 215, 133, 35, 11, 131, 137, 123, 70, 74, 26, 15, 60, 189, 32, 67, 221, 115, 137
14 | ]),
15 | macKey: Uint8Array.from([
16 | 6, 179, 91, 245, 224, 56, 153, 120, 77, 140, 29, 5, 15, 213, 187, 65, 137, 230, 202, 120
17 | ])
18 | },
19 | k2: {
20 | iv: Uint8Array.from([
21 | 236, 17, 34, 141, 90, 106, 197, 56, 197, 184, 157, 135, 91, 88, 112, 19
22 | ]),
23 | cipherKey: Uint8Array.from([
24 | 151, 145, 195, 219, 76, 195, 102, 109, 187, 231, 100, 150, 132, 245, 251, 130, 254, 37, 178, 55, 227, 34, 114, 39, 238, 34, 2, 193, 107, 130, 32, 87
25 | ]),
26 | macKey: Uint8Array.from([
27 | 3, 229, 77, 212, 241, 217, 23, 113, 220, 126, 38, 255, 18, 117, 108, 205, 198, 89, 1, 236
28 | ])
29 | }
30 | }]
31 |
--------------------------------------------------------------------------------
/test/fixtures/secp256k1.ts:
--------------------------------------------------------------------------------
1 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
2 |
3 | export default {
4 | // protobuf marshalled key pair generated with libp2p-crypto-secp256k1
5 | // and marshalled with libp2p-crypto.marshalPublicKey / marshalPrivateKey
6 | pbmPrivateKey: uint8ArrayFromString('08021220e0600103010000000100000000000000be1dc82c2e000000e8d6030301000000', 'base16'),
7 | pbmPublicKey: uint8ArrayFromString('0802122103a9a7272a726fa083abf31ba44037f8347fbc5e5d3113d62a7c6bc26752fd8ee1', 'base16')
8 | }
9 |
--------------------------------------------------------------------------------
/test/helpers/test-garbage-error-handling.ts:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | import util from 'util'
3 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
4 |
5 | const garbage = [uint8ArrayFromString('00010203040506070809', 'base16'), {}, null, false, undefined, true, 1, 0, uint8ArrayFromString(''), 'aGVsbG93b3JsZA==', 'helloworld', '']
6 |
7 | export function testGarbage (fncName: string, fnc: (...args: Uint8Array[]) => Promise, num?: number, skipBuffersAndStrings?: boolean): void {
8 | const count = num ?? 1
9 |
10 | garbage.forEach((garbage) => {
11 | if (skipBuffersAndStrings === true && (garbage instanceof Uint8Array || (typeof garbage) === 'string')) {
12 | // skip this garbage because it's a Uint8Array or a String and we were told do do that
13 | return
14 | }
15 | const args: any[] = []
16 | for (let i = 0; i < count; i++) {
17 | args.push(garbage)
18 | }
19 | it(fncName + '(' + args.map(garbage => util.inspect(garbage)).join(', ') + ')', async () => {
20 | try {
21 | await fnc.apply(null, args)
22 | } catch (err) {
23 | return // expected
24 | }
25 | throw new Error('Expected error to be thrown')
26 | })
27 | })
28 | }
29 |
--------------------------------------------------------------------------------
/test/hmac/hmac.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint max-nested-callbacks: ["error", 8] */
2 | /* eslint-env mocha */
3 | import { expect } from 'aegir/chai'
4 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
5 | import * as crypto from '../../src/index.js'
6 |
7 | const hashes = ['SHA1', 'SHA256', 'SHA512'] as ['SHA1', 'SHA256', 'SHA512']
8 |
9 | describe('HMAC', () => {
10 | hashes.forEach((hash) => {
11 | it(`${hash} - sign and verify`, async () => {
12 | const hmac = await crypto.hmac.create(hash, uint8ArrayFromString('secret'))
13 | const sig = await hmac.digest(uint8ArrayFromString('hello world'))
14 | expect(sig).to.have.length(hmac.length)
15 | })
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/test/keys/ed25519.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | import { expect } from 'aegir/chai'
3 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
4 | import * as crypto from '../../src/index.js'
5 | import { Ed25519PrivateKey } from '../../src/keys/ed25519-class.js'
6 | import fixtures from '../fixtures/go-key-ed25519.js'
7 | import { testGarbage } from '../helpers/test-garbage-error-handling.js'
8 |
9 | const ed25519 = crypto.keys.supportedKeys.ed25519
10 |
11 | /** @typedef {import("libp2p-crypto").PrivateKey} PrivateKey */
12 |
13 | describe('ed25519', function () {
14 | this.timeout(20 * 1000)
15 | let key: Ed25519PrivateKey
16 | before(async () => {
17 | const generated = await crypto.keys.generateKeyPair('Ed25519', 512)
18 |
19 | if (!(generated instanceof Ed25519PrivateKey)) {
20 | throw new Error('Key was incorrect type')
21 | }
22 |
23 | key = generated
24 | })
25 |
26 | it('generates a valid key', async () => {
27 | expect(key).to.be.an.instanceof(ed25519.Ed25519PrivateKey)
28 | const digest = await key.hash()
29 | expect(digest).to.have.length(34)
30 | })
31 |
32 | it('generates a valid key from seed', async () => {
33 | const seed = crypto.randomBytes(32)
34 | const seededkey = await crypto.keys.generateKeyPairFromSeed('Ed25519', seed, 512)
35 | expect(seededkey).to.be.an.instanceof(ed25519.Ed25519PrivateKey)
36 | const digest = await seededkey.hash()
37 | expect(digest).to.have.length(34)
38 | })
39 |
40 | it('generates the same key from the same seed', async () => {
41 | const seed = crypto.randomBytes(32)
42 | const seededkey1 = await crypto.keys.generateKeyPairFromSeed('Ed25519', seed, 512)
43 | const seededkey2 = await crypto.keys.generateKeyPairFromSeed('Ed25519', seed, 512)
44 | expect(seededkey1.equals(seededkey2)).to.eql(true)
45 | expect(seededkey1.public.equals(seededkey2.public)).to.eql(true)
46 | })
47 |
48 | it('generates different keys for different seeds', async () => {
49 | const seed1 = crypto.randomBytes(32)
50 | const seededkey1 = await crypto.keys.generateKeyPairFromSeed('Ed25519', seed1, 512)
51 | const seed2 = crypto.randomBytes(32)
52 | const seededkey2 = await crypto.keys.generateKeyPairFromSeed('Ed25519', seed2, 512)
53 | expect(seededkey1.equals(seededkey2)).to.eql(false)
54 | expect(seededkey1.public.equals(seededkey2.public)).to.eql(false)
55 | })
56 |
57 | it('signs', async () => {
58 | const text = crypto.randomBytes(512)
59 | const sig = await key.sign(text)
60 | const res = await key.public.verify(text, sig)
61 | expect(res).to.be.eql(true)
62 | })
63 |
64 | it('encoding', () => {
65 | const keyMarshal = key.marshal()
66 | const key2 = ed25519.unmarshalEd25519PrivateKey(keyMarshal)
67 | const keyMarshal2 = key2.marshal()
68 |
69 | expect(keyMarshal).to.eql(keyMarshal2)
70 |
71 | const pk = key.public
72 | const pkMarshal = pk.marshal()
73 | const pk2 = ed25519.unmarshalEd25519PublicKey(pkMarshal)
74 | const pkMarshal2 = pk2.marshal()
75 |
76 | expect(pkMarshal).to.eql(pkMarshal2)
77 | })
78 |
79 | it('key id', async () => {
80 | const key = await crypto.keys.unmarshalPrivateKey(fixtures.verify.privateKey)
81 | const id = await key.id()
82 | expect(id).to.eql('12D3KooWLqLxEfJ9nDdEe8Kh8PFvNPQRYDQBwyL7CMM7HhVd5LsX')
83 | })
84 |
85 | it('should export a password encrypted libp2p-key', async () => {
86 | const key = await crypto.keys.generateKeyPair('Ed25519')
87 |
88 | if (!(key instanceof Ed25519PrivateKey)) {
89 | throw new Error('Key was incorrect type')
90 | }
91 |
92 | const encryptedKey = await key.export('my secret')
93 | // Import the key
94 | const importedKey = await crypto.keys.importKey(encryptedKey, 'my secret')
95 |
96 | if (!(importedKey instanceof Ed25519PrivateKey)) {
97 | throw new Error('Key was incorrect type')
98 | }
99 |
100 | expect(key.equals(importedKey)).to.equal(true)
101 | })
102 |
103 | it('should export a libp2p-key with no password to encrypt', async () => {
104 | const key = await crypto.keys.generateKeyPair('Ed25519')
105 |
106 | if (!(key instanceof Ed25519PrivateKey)) {
107 | throw new Error('Key was incorrect type')
108 | }
109 |
110 | const encryptedKey = await key.export('')
111 | // Import the key
112 | const importedKey = await crypto.keys.importKey(encryptedKey, '')
113 |
114 | if (!(importedKey instanceof Ed25519PrivateKey)) {
115 | throw new Error('Key was incorrect type')
116 | }
117 |
118 | expect(key.equals(importedKey)).to.equal(true)
119 | })
120 |
121 | it('should fail to import libp2p-key with wrong password', async () => {
122 | const key = await crypto.keys.generateKeyPair('Ed25519')
123 | const encryptedKey = await key.export('my secret', 'libp2p-key')
124 | try {
125 | await crypto.keys.importKey(encryptedKey, 'not my secret')
126 | } catch (err) {
127 | expect(err).to.exist()
128 | return
129 | }
130 | expect.fail('should have thrown')
131 | })
132 |
133 | describe('key equals', () => {
134 | it('equals itself', () => {
135 | expect(
136 | key.equals(key)
137 | ).to.eql(
138 | true
139 | )
140 |
141 | expect(
142 | key.public.equals(key.public)
143 | ).to.eql(
144 | true
145 | )
146 | })
147 |
148 | it('not equals other key', async () => {
149 | const key2 = await crypto.keys.generateKeyPair('Ed25519', 512)
150 |
151 | if (!(key2 instanceof Ed25519PrivateKey)) {
152 | throw new Error('Key was incorrect type')
153 | }
154 |
155 | expect(key.equals(key2)).to.eql(false)
156 | expect(key2.equals(key)).to.eql(false)
157 | expect(key.public.equals(key2.public)).to.eql(false)
158 | expect(key2.public.equals(key.public)).to.eql(false)
159 | })
160 | })
161 |
162 | it('sign and verify', async () => {
163 | const data = uint8ArrayFromString('hello world')
164 | const sig = await key.sign(data)
165 | const valid = await key.public.verify(data, sig)
166 | expect(valid).to.eql(true)
167 | })
168 |
169 | it('sign and verify from seed', async () => {
170 | const seed = new Uint8Array(32).fill(1)
171 | const seededkey = await crypto.keys.generateKeyPairFromSeed('Ed25519', seed)
172 | const data = uint8ArrayFromString('hello world')
173 | const sig = await seededkey.sign(data)
174 | const valid = await seededkey.public.verify(data, sig)
175 | expect(valid).to.eql(true)
176 | })
177 |
178 | it('fails to verify for different data', async () => {
179 | const data = uint8ArrayFromString('hello world')
180 | const sig = await key.sign(data)
181 | const valid = await key.public.verify(uint8ArrayFromString('hello'), sig)
182 | expect(valid).to.be.eql(false)
183 | })
184 |
185 | describe('throws error instead of crashing', () => {
186 | const key = crypto.keys.unmarshalPublicKey(fixtures.verify.publicKey)
187 | testGarbage('key.verify', key.verify.bind(key), 2)
188 | testGarbage('crypto.keys.unmarshalPrivateKey', crypto.keys.unmarshalPrivateKey.bind(crypto.keys))
189 | })
190 |
191 | describe('go interop', () => {
192 | // @ts-check
193 | it('verifies with data from go', async () => {
194 | const key = crypto.keys.unmarshalPublicKey(fixtures.verify.publicKey)
195 | const ok = await key.verify(fixtures.verify.data, fixtures.verify.signature)
196 | expect(ok).to.eql(true)
197 | })
198 |
199 | it('does not include the redundant public key when marshalling privatekey', async () => {
200 | const key = await crypto.keys.unmarshalPrivateKey(fixtures.redundantPubKey.privateKey)
201 | const bytes = key.marshal()
202 | expect(bytes.length).to.equal(64)
203 | expect(bytes.subarray(32)).to.eql(key.public.marshal())
204 | })
205 |
206 | it('verifies with data from go with redundant public key', async () => {
207 | const key = crypto.keys.unmarshalPublicKey(fixtures.redundantPubKey.publicKey)
208 | const ok = await key.verify(fixtures.redundantPubKey.data, fixtures.redundantPubKey.signature)
209 | expect(ok).to.eql(true)
210 | })
211 |
212 | it('generates the same signature as go', async () => {
213 | const key = await crypto.keys.unmarshalPrivateKey(fixtures.verify.privateKey)
214 | const sig = await key.sign(fixtures.verify.data)
215 | expect(sig).to.eql(fixtures.verify.signature)
216 | })
217 |
218 | it('generates the same signature as go with redundant public key', async () => {
219 | const key = await crypto.keys.unmarshalPrivateKey(fixtures.redundantPubKey.privateKey)
220 | const sig = await key.sign(fixtures.redundantPubKey.data)
221 | expect(sig).to.eql(fixtures.redundantPubKey.signature)
222 | })
223 | })
224 | })
225 |
--------------------------------------------------------------------------------
/test/keys/ephemeral-keys.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint max-nested-callbacks: ["error", 8] */
2 | /* eslint-env mocha */
3 | import { expect } from 'aegir/chai'
4 | import * as crypto from '../../src/index.js'
5 | import fixtures from '../fixtures/go-elliptic-key.js'
6 |
7 | const curves = ['P-256', 'P-384'] // 'P-521' fails in tests :( no clue why
8 | const lengths: Record = {
9 | 'P-256': 65,
10 | 'P-384': 97,
11 | 'P-521': 133
12 | }
13 |
14 | const secretLengths: Record = {
15 | 'P-256': 32,
16 | 'P-384': 48,
17 | 'P-521': 66
18 | }
19 |
20 | describe('generateEphemeralKeyPair', () => {
21 | curves.forEach((curve) => {
22 | it(`generate and shared key ${curve}`, async () => {
23 | const keys = await Promise.all([
24 | crypto.keys.generateEphemeralKeyPair(curve),
25 | crypto.keys.generateEphemeralKeyPair(curve)
26 | ])
27 |
28 | expect(keys[0].key).to.have.length(lengths[curve])
29 | expect(keys[1].key).to.have.length(lengths[curve])
30 |
31 | const shared = await keys[0].genSharedKey(keys[1].key)
32 | expect(shared).to.have.length(secretLengths[curve])
33 | })
34 | })
35 |
36 | describe('go interop', () => {
37 | it('generates a shared secret', async () => {
38 | const curve = fixtures.curve
39 |
40 | const keys = await Promise.all([
41 | crypto.keys.generateEphemeralKeyPair(curve),
42 | crypto.keys.generateEphemeralKeyPair(curve)
43 | ])
44 |
45 | const alice = keys[0]
46 | const bob = keys[1]
47 | bob.key = fixtures.bob.public
48 |
49 | const secrets = await Promise.all([
50 | alice.genSharedKey(bob.key),
51 | bob.genSharedKey(alice.key, fixtures.bob)
52 | ])
53 |
54 | expect(secrets[0]).to.eql(secrets[1])
55 | expect(secrets[0]).to.have.length(32)
56 | })
57 | })
58 |
59 | it('handles bad curve name', async () => {
60 | await expect(crypto.keys.generateEphemeralKeyPair('bad name')).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_CURVE')
61 | })
62 | })
63 |
--------------------------------------------------------------------------------
/test/keys/importer.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint max-nested-callbacks: ["error", 8] */
2 | /* eslint-env mocha */
3 | import { expect } from 'aegir/chai'
4 | import { exporter } from '../../src/keys/exporter.js'
5 | import { importer } from '../../src/keys/importer.js'
6 |
7 | describe('libp2p-crypto importer/exporter', function () {
8 | it('roundtrips', async () => {
9 | for (const password of ['', 'password']) {
10 | const secret = new Uint8Array(32)
11 | for (let i = 0; i < secret.length; i++) {
12 | secret[i] = i
13 | }
14 |
15 | const exported = await exporter(secret, password)
16 | const imported = await importer(exported, password)
17 | expect(imported).to.deep.equal(secret)
18 | }
19 | })
20 | })
21 |
--------------------------------------------------------------------------------
/test/keys/key-stretcher.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint max-nested-callbacks: ["error", 8] */
2 | /* eslint-env mocha */
3 | import { expect } from 'aegir/chai'
4 | import * as crypto from '../../src/index.js'
5 | import fixtures from '../fixtures/go-stretch-key.js'
6 | import type { ECDHKey } from '../../src/keys/interface.js'
7 |
8 | describe('keyStretcher', () => {
9 | describe('generate', () => {
10 | const ciphers = ['AES-128', 'AES-256', 'Blowfish'] as Array<'AES-128' | 'AES-256' | 'Blowfish'>
11 | const hashes = ['SHA1', 'SHA256', 'SHA512'] as Array<'SHA1' | 'SHA256' | 'SHA512'>
12 | let res: ECDHKey
13 | let secret: Uint8Array
14 |
15 | before(async () => {
16 | res = await crypto.keys.generateEphemeralKeyPair('P-256')
17 | secret = await res.genSharedKey(res.key)
18 | })
19 |
20 | ciphers.forEach((cipher) => {
21 | hashes.forEach((hash) => {
22 | it(`${cipher} - ${hash}`, async () => {
23 | const keys = await crypto.keys.keyStretcher(cipher, hash, secret)
24 | expect(keys.k1).to.exist()
25 | expect(keys.k2).to.exist()
26 | })
27 | })
28 | })
29 |
30 | it('handles invalid cipher type', () => {
31 | // @ts-expect-error cipher name is invalid
32 | return expect(crypto.keys.keyStretcher('invalid-cipher', 'SHA256', 'secret')).to.eventually.be.rejected().with.property('code', 'ERR_INVALID_CIPHER_TYPE')
33 | })
34 |
35 | it('handles missing hash type', () => {
36 | // @ts-expect-error secret name is invalid
37 | return expect(crypto.keys.keyStretcher('AES-128', undefined, 'secret')).to.eventually.be.rejected().with.property('code', 'ERR_MISSING_HASH_TYPE')
38 | })
39 | })
40 |
41 | describe('go interop', () => {
42 | fixtures.forEach((test) => {
43 | it(`${test.cipher} - ${test.hash}`, async () => {
44 | const cipher = test.cipher
45 | const hash = test.hash
46 | const secret = test.secret
47 | const keys = await crypto.keys.keyStretcher(cipher, hash, secret)
48 |
49 | expect(keys.k1.iv).to.be.eql(test.k1.iv)
50 | expect(keys.k1.cipherKey).to.be.eql(test.k1.cipherKey)
51 | expect(keys.k1.macKey).to.be.eql(test.k1.macKey)
52 |
53 | expect(keys.k2.iv).to.be.eql(test.k2.iv)
54 | expect(keys.k2.cipherKey).to.be.eql(test.k2.cipherKey)
55 | expect(keys.k2.macKey).to.be.eql(test.k2.macKey)
56 | })
57 | })
58 | })
59 | })
60 |
--------------------------------------------------------------------------------
/test/keys/rsa.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint max-nested-callbacks: ["error", 8] */
2 | /* eslint-env mocha */
3 | import { expect } from 'aegir/chai'
4 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
5 | import * as crypto from '../../src/index.js'
6 | import { RsaPrivateKey } from '../../src/keys/rsa-class.js'
7 | import fixtures from '../fixtures/go-key-rsa.js'
8 | import { testGarbage } from '../helpers/test-garbage-error-handling.js'
9 |
10 | const rsa = crypto.keys.supportedKeys.rsa
11 |
12 | /** @typedef {import('libp2p-crypto').keys.supportedKeys.rsa.RsaPrivateKey} RsaPrivateKey */
13 |
14 | describe('RSA', function () {
15 | this.timeout(20 * 1000)
16 | let key: RsaPrivateKey
17 |
18 | before(async () => {
19 | key = await rsa.generateKeyPair(512)
20 | })
21 |
22 | it('generates a valid key', async () => {
23 | expect(key).to.be.an.instanceof(rsa.RsaPrivateKey)
24 | const digest = await key.hash()
25 | expect(digest).to.have.length(34)
26 | })
27 |
28 | it('signs', async () => {
29 | const text = key.genSecret()
30 | const sig = await key.sign(text)
31 | const res = await key.public.verify(text, sig)
32 | expect(res).to.be.eql(true)
33 | })
34 |
35 | it('encoding', async () => {
36 | const keyMarshal = key.marshal()
37 | const key2 = await rsa.unmarshalRsaPrivateKey(keyMarshal)
38 | const keyMarshal2 = key2.marshal()
39 |
40 | expect(keyMarshal).to.eql(keyMarshal2)
41 |
42 | const pk = key.public
43 | const pkMarshal = pk.marshal()
44 | const pk2 = rsa.unmarshalRsaPublicKey(pkMarshal)
45 | const pkMarshal2 = pk2.marshal()
46 |
47 | expect(pkMarshal).to.eql(pkMarshal2)
48 | })
49 |
50 | it('key id', async () => {
51 | const key = await crypto.keys.unmarshalPrivateKey(uint8ArrayFromString('CAASqAkwggSkAgEAAoIBAQCk0O+6oNRxhcdZe2GxEDrFBkDV4TZFZnp2ly/dL1cGMBql/8oXPZgei6h7+P5zzfDq2YCfwbjbf0IVY1AshRl6B5VGE1WS+9p1y1OZxJf5os6V1ENnTi6FTcyuBl4BN8dmIKOif0hqgqflaT5OhfYZDXfbJyVQj4vb2+Stu2Xpph3nwqAnTw/7GC/7jrt2Cq6Tu1PoZi36wSwEPYW3eQ1HAYxZjTYYDXl2iyHygnTcbkGRwAQ7vjk+mW7u60zyoolCm9f6Y7c/orJ33DDUocbaGJLlHcfd8bioBwaZy/2m7q43X8pQs0Q1/iwUt0HHZj1YARmHKbh0zR31ciFiV37dAgMBAAECggEADtJBNKnA4QKURj47r0YT2uLwkqtBi6UnDyISalQXAdXyl4n0nPlrhBewC5H9I+HZr+zmTbeIjaiYgz7el1pSy7AB4v7bG7AtWZlyx6mvtwHGjR+8/f3AXjl8Vgv5iSeAdXUq8fJ7SyS7v3wi38HZOzCEXj9bci6ud5ODMYJgLE4gZD0+i1+/V9cpuYfGpS/gLTLEMQLiw/9o8NSZ7sAnxg0UlYhotqaQY23hvXPBOe+0oa95zl2n6XTxCafa3dQl/B6CD1tUq9dhbQew4bxqMq/mhRO9pREEqZ083Uh+u4PTc1BeHgIQaS864pHPb+AY1F7KDvPtHhdojnghp8d70QKBgQDeRYFxo6sd04ohY86Z/i9icVYIyCvfXAKnaMKeGUjK7ou6sDJwFX8W97+CzXpZ/vffsk/l5GGhC50KqrITxHAy/h5IjyDODfps7NMIp0Dm9sO4PWibbw3OOVBRc8w3b3i7I8MrUUA1nLHE1T1HA1rKOTz5jYhE0fi9XKiT1ciKOQKBgQC903w+n9y7M7eaMW7Z5/13kZ7PS3HlM681eaPrk8J4J+c6miFF40/8HOsmarS38v0fgTeKkriPz5A7aLzRHhSiOnp350JNM6c3sLwPEs2qx/CRuWWx1rMERatfDdUH6mvlK6QHu0QgSfQR27EO6a6XvVSJXbvFmimjmtIaz/IpxQKBgQDWJ9HYVAGC81abZTaiWK3/A4QJYhQjWNuVwPICsgnYvI4Uib+PDqcs0ffLZ38DRw48kek5bxpBuJbOuDhro1EXUJCNCJpq7jzixituovd9kTRyR3iKii2bDM2+LPwOTXDdnk9lZRugjCEbrPkleq33Ob7uEtfAty4aBTTHe6uEwQKBgQCB+2q8RyMSXNuADhFlzOFXGrOwJm0bEUUMTPrduRQUyt4e1qOqA3klnXe3mqGcxBpnlEe/76/JacvNom6Ikxx16a0qpYRU8OWz0KU1fR6vrrEgV98241k5t6sdL4+MGA1Bo5xyXtzLb1hdUh3vpDwVU2OrnC+To3iXus/b5EBiMQKBgEI1OaBcFiyjgLGEyFKoZbtzH1mdatTExfrAQqCjOVjQByoMpGhHTXwEaosvyYu63Pa8AJPT7juSGaiKYEJFcXO9BiNyVfmQiqSHJcYeuh+fmO9IlHRHgy5xaIIC00AHS2vC/gXwmXAdPis6BZqDJeiCuOLWJ94QXn8JBT8IgGAI', 'base64pad'))
52 | const id = await key.id()
53 | expect(id).to.eql('QmQgsppVMDUpe83wcAqaemKbYvHeF127gnSFQ1xFnBodVw')
54 | })
55 |
56 | describe('key equals', () => {
57 | it('equals itself', () => {
58 | expect(key.equals(key)).to.eql(true)
59 |
60 | expect(key.public.equals(key.public)).to.eql(true)
61 | })
62 |
63 | it('not equals other key', async () => {
64 | const key2 = await crypto.keys.generateKeyPair('RSA', 512)
65 |
66 | if (!(key2 instanceof RsaPrivateKey)) {
67 | throw new Error('Key was incorrect type')
68 | }
69 |
70 | expect(key.equals(key2)).to.eql(false)
71 | expect(key2.equals(key)).to.eql(false)
72 | expect(key.public.equals(key2.public)).to.eql(false)
73 | expect(key2.public.equals(key.public)).to.eql(false)
74 | })
75 | })
76 |
77 | it('sign and verify', async () => {
78 | const data = uint8ArrayFromString('hello world')
79 | const sig = await key.sign(data)
80 | const valid = await key.public.verify(data, sig)
81 | expect(valid).to.be.eql(true)
82 | })
83 |
84 | it('encrypt and decrypt', () => {
85 | const data = uint8ArrayFromString('hello world')
86 | const enc = key.public.encrypt(data)
87 | const dec = key.decrypt(enc)
88 | expect(dec).to.be.eql(data)
89 | })
90 |
91 | it('encrypt decrypt browser/node interop', async () => {
92 | // @ts-check
93 | /**
94 | * @type {any}
95 | */
96 | const id = await crypto.keys.unmarshalPrivateKey(uint8ArrayFromString('CAASqAkwggSkAgEAAoIBAQCk0O+6oNRxhcdZe2GxEDrFBkDV4TZFZnp2ly/dL1cGMBql/8oXPZgei6h7+P5zzfDq2YCfwbjbf0IVY1AshRl6B5VGE1WS+9p1y1OZxJf5os6V1ENnTi6FTcyuBl4BN8dmIKOif0hqgqflaT5OhfYZDXfbJyVQj4vb2+Stu2Xpph3nwqAnTw/7GC/7jrt2Cq6Tu1PoZi36wSwEPYW3eQ1HAYxZjTYYDXl2iyHygnTcbkGRwAQ7vjk+mW7u60zyoolCm9f6Y7c/orJ33DDUocbaGJLlHcfd8bioBwaZy/2m7q43X8pQs0Q1/iwUt0HHZj1YARmHKbh0zR31ciFiV37dAgMBAAECggEADtJBNKnA4QKURj47r0YT2uLwkqtBi6UnDyISalQXAdXyl4n0nPlrhBewC5H9I+HZr+zmTbeIjaiYgz7el1pSy7AB4v7bG7AtWZlyx6mvtwHGjR+8/f3AXjl8Vgv5iSeAdXUq8fJ7SyS7v3wi38HZOzCEXj9bci6ud5ODMYJgLE4gZD0+i1+/V9cpuYfGpS/gLTLEMQLiw/9o8NSZ7sAnxg0UlYhotqaQY23hvXPBOe+0oa95zl2n6XTxCafa3dQl/B6CD1tUq9dhbQew4bxqMq/mhRO9pREEqZ083Uh+u4PTc1BeHgIQaS864pHPb+AY1F7KDvPtHhdojnghp8d70QKBgQDeRYFxo6sd04ohY86Z/i9icVYIyCvfXAKnaMKeGUjK7ou6sDJwFX8W97+CzXpZ/vffsk/l5GGhC50KqrITxHAy/h5IjyDODfps7NMIp0Dm9sO4PWibbw3OOVBRc8w3b3i7I8MrUUA1nLHE1T1HA1rKOTz5jYhE0fi9XKiT1ciKOQKBgQC903w+n9y7M7eaMW7Z5/13kZ7PS3HlM681eaPrk8J4J+c6miFF40/8HOsmarS38v0fgTeKkriPz5A7aLzRHhSiOnp350JNM6c3sLwPEs2qx/CRuWWx1rMERatfDdUH6mvlK6QHu0QgSfQR27EO6a6XvVSJXbvFmimjmtIaz/IpxQKBgQDWJ9HYVAGC81abZTaiWK3/A4QJYhQjWNuVwPICsgnYvI4Uib+PDqcs0ffLZ38DRw48kek5bxpBuJbOuDhro1EXUJCNCJpq7jzixituovd9kTRyR3iKii2bDM2+LPwOTXDdnk9lZRugjCEbrPkleq33Ob7uEtfAty4aBTTHe6uEwQKBgQCB+2q8RyMSXNuADhFlzOFXGrOwJm0bEUUMTPrduRQUyt4e1qOqA3klnXe3mqGcxBpnlEe/76/JacvNom6Ikxx16a0qpYRU8OWz0KU1fR6vrrEgV98241k5t6sdL4+MGA1Bo5xyXtzLb1hdUh3vpDwVU2OrnC+To3iXus/b5EBiMQKBgEI1OaBcFiyjgLGEyFKoZbtzH1mdatTExfrAQqCjOVjQByoMpGhHTXwEaosvyYu63Pa8AJPT7juSGaiKYEJFcXO9BiNyVfmQiqSHJcYeuh+fmO9IlHRHgy5xaIIC00AHS2vC/gXwmXAdPis6BZqDJeiCuOLWJ94QXn8JBT8IgGAI', 'base64pad'))
97 |
98 | if (!(id instanceof RsaPrivateKey)) {
99 | throw new Error('Key was incorrect type')
100 | }
101 |
102 | const msg = uint8ArrayFromString('hello')
103 |
104 | // browser
105 | const dec1 = id.decrypt(uint8ArrayFromString('YRFUDx8UjbWSfDS84cDA4WowaaOmd1qFNAv5QutodCKYb9uPtU/tDiAvJzOGu5DCJRo2J0l/35P2weiB4/C2Cb1aZgXKMx/QQC+2jSJiymhqcZaYerjTvkCFwkjCaqthoVo/YXxsaFZ1q7bdTZUDH1TaJR7hWfSyzyPcA8c0w43MIsw16pY8ZaPSclvnCwhoTg1JGjMk6te3we7+wR8QU7VrPhs54mZWxrpu3NQ8xZ6xQqIedsEiNhBUccrCSzYghgsP0Ae/8iKyGyl3U6IegsJNn8jcocvzOJrmU03rgIFPjvuBdaqB38xDSTjbA123KadB28jNoSZh18q/yH3ZIg==', 'base64pad'))
106 | expect(dec1).to.be.eql(msg)
107 | // node
108 | const dec2 = id.decrypt(uint8ArrayFromString('e6yxssqXsWc27ozDy0PGKtMkCS28KwFyES2Ijz89yiz+w6bSFkNOhHPKplpPzgQEuNoUGdbseKlJFyRYHjIT8FQFBHZM8UgSkgoimbY5on4xSxXs7E5/+twjqKdB7oNveTaTf7JCwaeUYnKSjbiYFEawtMiQE91F8sTT7TmSzOZ48tUhnddAAZ3Ac/O3Z9MSAKOCDipi+JdZtXRT8KimGt36/7hjjosYmPuHR1Xy/yMTL6SMbXtBM3yAuEgbQgP+q/7kHMHji3/JvTpYdIUU+LVtkMusXNasRA+UWG2zAht18vqjFMsm9JTiihZw9jRHD4vxAhf75M992tnC+0ZuQg==', 'base64pad'))
109 | expect(dec2).to.be.eql(msg)
110 | })
111 |
112 | it('fails to verify for different data', async () => {
113 | const data = uint8ArrayFromString('hello world')
114 | const sig = await key.sign(data)
115 | const valid = await key.public.verify(uint8ArrayFromString('hello'), sig)
116 | expect(valid).to.be.eql(false)
117 | })
118 |
119 | describe('export and import', () => {
120 | it('password protected PKCS #8', async () => {
121 | const pem = await key.export('my secret', 'pkcs-8')
122 | expect(pem).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----')
123 | const clone = await crypto.keys.importKey(pem, 'my secret')
124 |
125 | if (!(clone instanceof RsaPrivateKey)) {
126 | throw new Error('Wrong kind of key imported')
127 | }
128 |
129 | expect(clone).to.exist()
130 | expect(key.equals(clone)).to.eql(true)
131 | })
132 |
133 | it('defaults to PKCS #8', async () => {
134 | const pem = await key.export('another secret')
135 | expect(pem).to.startsWith('-----BEGIN ENCRYPTED PRIVATE KEY-----')
136 | const clone = await crypto.keys.importKey(pem, 'another secret')
137 |
138 | if (!(clone instanceof RsaPrivateKey)) {
139 | throw new Error('Wrong kind of key imported')
140 | }
141 |
142 | expect(clone).to.exist()
143 | expect(key.equals(clone)).to.eql(true)
144 | })
145 |
146 | it('should export a password encrypted libp2p-key', async () => {
147 | const encryptedKey = await key.export('my secret', 'libp2p-key')
148 | // Import the key
149 | const importedKey = await crypto.keys.importKey(encryptedKey, 'my secret')
150 |
151 | if (!(importedKey instanceof RsaPrivateKey)) {
152 | throw new Error('Wrong kind of key imported')
153 | }
154 |
155 | expect(key.equals(importedKey)).to.equal(true)
156 | })
157 |
158 | it('should fail to import libp2p-key with wrong password', async () => {
159 | const encryptedKey = await key.export('my secret', 'libp2p-key')
160 | try {
161 | await crypto.keys.importKey(encryptedKey, 'not my secret')
162 | } catch (err) {
163 | expect(err).to.exist()
164 | return
165 | }
166 | expect.fail('should have thrown')
167 | })
168 |
169 | it('needs correct password', async () => {
170 | const pem = await key.export('another secret')
171 | try {
172 | await crypto.keys.importKey(pem, 'not the secret')
173 | } catch (err) {
174 | return // expected
175 | }
176 | throw new Error('Expected error to be thrown')
177 | })
178 |
179 | it('handles invalid export type', () => {
180 | return expect(key.export('secret', 'invalid-type')).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_EXPORT_FORMAT')
181 | })
182 | })
183 |
184 | describe('throws error instead of crashing', () => {
185 | const key = crypto.keys.unmarshalPublicKey(fixtures.verify.publicKey)
186 | testGarbage('key.verify', key.verify.bind(key), 2, true)
187 | testGarbage(
188 | 'crypto.keys.unmarshalPrivateKey',
189 | crypto.keys.unmarshalPrivateKey.bind(crypto.keys)
190 | )
191 | })
192 |
193 | describe('go interop', () => {
194 | it('verifies with data from go', async () => {
195 | const key = crypto.keys.unmarshalPublicKey(fixtures.verify.publicKey)
196 | const ok = await key.verify(fixtures.verify.data, fixtures.verify.signature)
197 | expect(ok).to.equal(true)
198 | })
199 | })
200 |
201 | describe('openssl interop', () => {
202 | it('can read a private key', async () => {
203 | /*
204 | * Generated with
205 | * openssl genpkey -algorithm RSA
206 | * -pkeyopt rsa_keygen_bits:3072
207 | * -pkeyopt rsa_keygen_pubexp:65537
208 | */
209 | const pem = `-----BEGIN PRIVATE KEY-----
210 | MIIG/wIBADANBgkqhkiG9w0BAQEFAASCBukwggblAgEAAoIBgQDp0Whyqa8KmdvK
211 | 0MsQGJEBzDAEHAZc0C6cr0rkb6Xwo+yB5kjZBRDORk0UXtYGE1pYt4JhUTmMzcWO
212 | v2xTIsdbVMQlNtput2U8kIqS1cSTkX5HxOJtCiIzntMzuR/bGPSOexkyFQ8nCUqb
213 | ROS7cln/ixprra2KMAKldCApN3ue2jo/JI1gyoS8sekhOASAa0ufMPpC+f70sc75
214 | Y53VLnGBNM43iM/2lsK+GI2a13d6rRy86CEM/ygnh/EDlyNDxo+SQmy6GmSv/lmR
215 | xgWQE2dIfK504KIxFTOphPAQAr9AsmcNnCQLhbz7YTsBz8WcytHGQ0Z5pnBQJ9AV
216 | CX9E6DFHetvs0CNLVw1iEO06QStzHulmNEI/3P8I1TIxViuESJxSu3pSNwG1bSJZ
217 | +Qee24vvlz/slBzK5gZWHvdm46v7vl5z7SA+whncEtjrswd8vkJk9fI/YTUbgOC0
218 | HWMdc2t/LTZDZ+LUSZ/b2n5trvdJSsOKTjEfuf0wICC08pUUk8MCAwEAAQKCAYEA
219 | ywve+DQCneIezHGk5cVvp2/6ApeTruXalJZlIxsRr3eq2uNwP4X2oirKpPX2RjBo
220 | NMKnpnsyzuOiu+Pf3hJFrTpfWzHXXm5Eq+OZcwnQO5YNY6XGO4qhSNKT9ka9Mzbo
221 | qRKdPrCrB+s5rryVJXKYVSInP3sDSQ2IPsYpZ6GW6Mv56PuFCpjTzElzejV7M0n5
222 | 0bRmn+MZVMVUR54KYiaCywFgUzmr3yfs1cfcsKqMRywt2J58lRy/chTLZ6LILQMv
223 | 4V01neVJiRkTmUfIWvc1ENIFM9QJlky9AvA5ASvwTTRz8yOnxoOXE/y4OVyOePjT
224 | cz9eumu9N5dPuUIMmsYlXmRNaeGZPD9bIgKY5zOlfhlfZSuOLNH6EHBNr6JAgfwL
225 | pdP43sbg2SSNKpBZ0iSMvpyTpbigbe3OyhnFH/TyhcC2Wdf62S9/FRsvjlRPbakW
226 | YhKAA2kmJoydcUDO5ccEga8b7NxCdhRiczbiU2cj70pMIuOhDlGAznyxsYbtyxaB
227 | AoHBAPy6Cbt6y1AmuId/HYfvms6i8B+/frD1CKyn+sUDkPf81xSHV7RcNrJi1S1c
228 | V55I0y96HulsR+GmcAW1DF3qivWkdsd/b4mVkizd/zJm3/Dm8p8QOnNTtdWvYoEB
229 | VzfAhBGaR/xflSLxZh2WE8ZHQ3IcRCXV9ZFgJ7PMeTprBJXzl0lTptvrHyo9QK1v
230 | obLrL/KuXWS0ql1uSnJr1vtDI5uW8WU4GDENeU5b/CJHpKpjVxlGg+7pmLknxlBl
231 | oBnZnQKBwQDs2Ky29qZ69qnPWowKceMJ53Z6uoUeSffRZ7xuBjowpkylasEROjuL
232 | nyAihIYB7fd7R74CnRVYLI+O2qXfNKJ8HN+TgcWv8LudkRcnZDSvoyPEJAPyZGfr
233 | olRCXD3caqtarlZO7vXSAl09C6HcL2KZ8FuPIEsuO0Aw25nESMg9eVMaIC6s2eSU
234 | NUt6xfZw1JC0c+f0LrGuFSjxT2Dr5WKND9ageI6afuauMuosjrrOMl2g0dMcSnVz
235 | KrtYa7Wi1N8CgcBFnuJreUplDCWtfgEen40f+5b2yAQYr4fyOFxGxdK73jVJ/HbW
236 | wsh2n+9mDZg9jIZQ/+1gFGpA6V7W06dSf/hD70ihcKPDXSbloUpaEikC7jxMQWY4
237 | uwjOkwAp1bq3Kxu21a+bAKHO/H1LDTrpVlxoJQ1I9wYtRDXrvBpxU2XyASbeFmNT
238 | FhSByFn27Ve4OD3/NrWXtoVwM5/ioX6ZvUcj55McdTWE3ddbFNACiYX9QlyOI/TY
239 | bhWafDCPmU9fj6kCgcEAjyQEfi9jPj2FM0RODqH1zS6OdG31tfCOTYicYQJyeKSI
240 | /hAezwKaqi9phHMDancfcupQ89Nr6vZDbNrIFLYC3W+1z7hGeabMPNZLYAs3rE60
241 | dv4tRHlaNRbORazp1iTBmvRyRRI2js3O++3jzOb2eILDUyT5St+UU/LkY7R5EG4a
242 | w1df3idx9gCftXufDWHqcqT6MqFl0QgIzo5izS68+PPxitpRlR3M3Mr4rCU20Rev
243 | blphdF+rzAavYyj1hYuRAoHBANmxwbq+QqsJ19SmeGMvfhXj+T7fNZQFh2F0xwb2
244 | rMlf4Ejsnx97KpCLUkoydqAs2q0Ws9Nkx2VEVx5KfUD7fWhgbpdnEPnQkfeXv9sD
245 | vZTuAoqInN1+vj1TME6EKR/6D4OtQygSNpecv23EuqEvyXWqRVsRt9Qd2B0H4k7h
246 | gnjREs10u7zyqBIZH7KYVgyh27WxLr859ap8cKAH6Fb+UOPtZo3sUeeume60aebn
247 | 4pMwXeXP+LO8NIfRXV8mgrm86g==
248 | -----END PRIVATE KEY-----
249 | `
250 | const key = await crypto.keys.importKey(pem, '')
251 | expect(key).to.exist()
252 | const id = await key.id()
253 | expect(id).to.equal('QmfWu2Xp8DZzCkZZzoPB9rcrq4R4RZid6AWE6kmrUAzuHy')
254 | })
255 |
256 | // AssertionError: expected 'this only supports pkcs5PBES2' to not exist
257 | it.skip('can read a private encrypted key (v1)', async () => {
258 | /*
259 | * Generated with
260 | * openssl genpkey -algorithm RSA
261 | * -pkeyopt rsa_keygen_bits:1024
262 | * -pkeyopt rsa_keygen_pubexp:65537
263 | * -out foo.pem
264 | * openssl pkcs8 -in foo.pem -topk8 -passout pass:mypassword
265 | */
266 | const pem = `-----BEGIN ENCRYPTED PRIVATE KEY-----
267 | MIICoTAbBgkqhkiG9w0BBQMwDgQI2563Jugj/KkCAggABIICgPxHkKtUUE8EWevq
268 | eX9nTjqpbsv0QoXQMhegfxDELJLU8tj6V0bWNt7QDdfQ1n6FRgnNvNGick6gyqHH
269 | yH9qC2oXwkDFP7OrHp2NEZd7DHQLLc+L4KJ/0dzsiZ1U9no7XzQMUay9Bc918ADE
270 | pN2/EqigWkaG4gNjkAeKWr6+BNRevDXlSvls7YDboNcTiACi5zJkthivB9g3vT1m
271 | gPdN6Gf/mmqtBTDHeqj5QsmXYqeCyo5b26JgYsziABVZDHph4ekPUsTvudRpE9Ex
272 | baXwdYEAZxVpSbTvQ3A5qysjSZeM9ttfRTSSwL391q7dViz4+aujpk0Vj7piH+1B
273 | CkfO8/XudRdRlnOe+KjMidktKCsMGCIOW92IlfMvIQ/Zn1GTYj9bRXONFNJ2WPND
274 | UmCKnL7cmworwg/weRorrGKBWIGspU+tDASOPSvIGKo6Hoxm4CN1TpDRY7DAGlgm
275 | Y3TEbMYfpXyzkPjvAhJDt03D3J9PrTO6uM5d7YUaaTmJ2TQFQVF2Lc3Uz8lDJLs0
276 | ZYtfQ/4H+YY2RrX7ua7t6ArUcYXZtv0J4lRYWjwV8fGPUVc0d8xLJU0Yjf4BD7K8
277 | rsavHo9b5YvBUX7SgUyxAEembEOe3SjQ+gPu2U5wovcjUuC9eItEEsXGrx30BQ0E
278 | 8BtK2+hp0eMkW5/BYckJkH+Yl8ypbzRGRRIZzLgeI4JveSx/mNhewfgTr+ORPThZ
279 | mBdkD5r+ixWF174naw53L8U9wF8kiK7pIE1N9TR4USEeovLwX6Ni/2MMDZedOfof
280 | 2f77eUdLsK19/5/lcgAAYaXauXWhy2d2r3SayFrC9woy0lh2VLKRMBjcx1oWb7dp
281 | 0uxzo5Y=
282 | -----END ENCRYPTED PRIVATE KEY-----
283 | `
284 | const key = await crypto.keys.importKey(pem, 'mypassword')
285 | expect(key).to.exist()
286 | })
287 |
288 | it('can read a private encrypted key (v2 aes-128-cbc)', async () => {
289 | /*
290 | * Generated with
291 | * openssl genpkey -algorithm RSA
292 | * -pkeyopt rsa_keygen_bits:1024
293 | * -pkeyopt rsa_keygen_pubexp:65537
294 | * -out foo.pem
295 | * openssl pkcs8 -in foo.pem -topk8 -v2 aes-128-cbc -passout pass:mypassword
296 | */
297 | const pem = `-----BEGIN ENCRYPTED PRIVATE KEY-----
298 | MIICzzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIP5QK2RfqUl4CAggA
299 | MB0GCWCGSAFlAwQBAgQQj3OyM9gnW2dd/eRHkxjGrgSCAoCpM5GZB0v27cxzZsGc
300 | O4/xqgwB0c/bSJ6QogtYU2KVoc7ZNQ5q9jtzn3I4ONvneOkpm9arzYz0FWnJi2C3
301 | BPiF0D1NkfvjvMLv56bwiG2A1oBECacyAb2pXYeJY7SdtYKvcbgs3jx65uCm6TF2
302 | BylteH+n1ewTQN9DLfASp1n81Ajq9lQGaK03SN2MUtcAPp7N9gnxJrlmDGeqlPRs
303 | KpQYRcot+kE6Ew8a5jAr7mAxwpqvr3SM4dMvADZmRQsM4Uc/9+YMUdI52DG87EWc
304 | 0OUB+fnQ8jw4DZgOE9KKM5/QTWc3aEw/dzXr/YJsrv01oLazhqVHnEMG0Nfr0+DP
305 | q+qac1AsCsOb71VxaRlRZcVEkEfAq3gidSPD93qmlDrCnmLYTilcLanXUepda7ez
306 | qhjkHtpwBLN5xRZxOn3oUuLGjk8VRwfmFX+RIMYCyihjdmbEDYpNUVkQVYFGi/F/
307 | 1hxOyl9yhGdL0hb9pKHH10GGIgoqo4jSTLlb4ennihGMHCjehAjLdx/GKJkOWShy
308 | V9hj8rAuYnRNb+tUW7ChXm1nLq14x9x1tX0ciVVn3ap/NoMkbFTr8M3pJ4bQlpAn
309 | wCT2erYqwQtgSpOJcrFeph9TjIrNRVE7Zlmr7vayJrB/8/oPssVdhf82TXkna4fB
310 | PcmO0YWLa117rfdeNM/Duy0ThSdTl39Qd+4FxqRZiHjbt+l0iSa/nOjTv1TZ/QqF
311 | wqrO6EtcM45fbFJ1Y79o2ptC2D6MB4HKJq9WCt064/8zQCVx3XPbb3X8Z5o/6koy
312 | ePGbz+UtSb9xczvqpRCOiFLh2MG1dUgWuHazjOtUcVWvilKnkjCMzZ9s1qG0sUDj
313 | nPyn
314 | -----END ENCRYPTED PRIVATE KEY-----
315 | `
316 | const key = await crypto.keys.importKey(pem, 'mypassword')
317 | expect(key).to.exist()
318 | })
319 |
320 | it('can read a private encrypted key (v2 aes-256-cbc)', async () => {
321 | /*
322 | * Generated with
323 | * openssl genpkey -algorithm RSA
324 | * -pkeyopt rsa_keygen_bits:1024
325 | * -pkeyopt rsa_keygen_pubexp:65537
326 | * -out foo.pem
327 | * openssl pkcs8 -in foo.pem -topk8 -v2 aes-256-cbc -passout pass:mypassword
328 | */
329 | const pem = `-----BEGIN ENCRYPTED PRIVATE KEY-----
330 | MIICzzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIhuL894loRucCAggA
331 | MB0GCWCGSAFlAwQBKgQQEoEtsjW3iC9/u0uGvkxX7wSCAoAsX3l6JoR2OGbT8CkY
332 | YT3RQFqquOgItYOHw6E3tir2YrmxEAo99nxoL8pdto37KSC32eAGnfv5R1zmHHSx
333 | 0M3/y2AWiCBTX95EEzdtGC1hK3PBa/qpp/xEmcrsjYN6NXxMAkhC0hMP/HdvqMAg
334 | ee7upvaYJsJcl8QLFNayAWr8b8cZA/RBhGEIRl59Eyj6nNtxDt3bCrfe06o1CPCV
335 | 50/fRZEwFOi/C6GYvPN6MrPZO3ALBWgopLT2yQqycTKtfxYWIdOsMBkAjKf2D6Pk
336 | u2mqBsaP4b71jIIeT4euSJLsoJV+O39s8YHXtW8GtOqp7V5kIlnm90lZ9wzeLTZ7
337 | HJsD/jEdYto5J3YWm2wwEDccraffJSm7UDtJBvQdIx832kxeFCcGQjW38Zl1qqkg
338 | iTH1PLTypxj2ZuviS2EkXVFb/kVU6leWwOt6fqWFC58UvJKeCk/6veazz3PDnTWM
339 | 92ClUqFd+CZn9VT4CIaJaAc6v5NLpPp+T9sRX9AtequPm7FyTeevY9bElfyk9gW9
340 | JDKgKxs6DGWDa16RL5vzwtU+G3o6w6IU+mEwa6/c+hN+pRFs/KBNLLSP9OHBx7BJ
341 | X/32Ft+VFhJaK+lQ+f+hve7od/bgKnz4c/Vtp7Dh51DgWgCpBgb8p0vqu02vTnxD
342 | BXtDv3h75l5PhvdWfVIzpMWRYFvPR+vJi066FjAz2sjYc0NMLSYtZWyWoIInjhoX
343 | Dp5CQujCtw/ZSSlwde1DKEWAW4SeDZAOQNvuz0rU3eosNUJxEmh3aSrcrRtDpw+Y
344 | mBUuWAZMpz7njBi7h+JDfmSW/GAaMwrVFC2gef5375R0TejAh+COAjItyoeYEvv8
345 | DQd8
346 | -----END ENCRYPTED PRIVATE KEY-----
347 | `
348 | const key = await crypto.keys.importKey(pem, 'mypassword')
349 | expect(key).to.exist()
350 | })
351 |
352 | it('can read a private encrypted key (v2 des)', async () => {
353 | /*
354 | * Generated with
355 | * openssl genpkey -algorithm RSA
356 | * -pkeyopt rsa_keygen_bits:1024
357 | * -pkeyopt rsa_keygen_pubexp:65537
358 | * -out foo.pem
359 | * openssl pkcs8 -in foo.pem -topk8 -v2 des -passout pass:mypassword
360 | */
361 | const pem = `-----BEGIN ENCRYPTED PRIVATE KEY-----
362 | MIICwzA9BgkqhkiG9w0BBQ0wMDAbBgkqhkiG9w0BBQwwDgQI0lXp62ozXvwCAggA
363 | MBEGBSsOAwIHBAiR3Id5vH0u4wSCAoDQQYOrrkPFPIa0S5fQGXnJw1F/66g92Gs1
364 | TkGydn4ouabWb++Vbi2chee1oyZsN2l8YNzDi0Gb2PfjsGpg2aJk0a3/efgA0u6T
365 | leEH1dA/7Hr9NVspgHkaXpHt3X6wdbznLYJeAelfj7sDXpOkULGWCkCst0Txb6bi
366 | Oxv4c0yYykiuUrp+2xvHbF9c2PrcDb58u/OBZcCg3QB1gTugQKM+ZIBRhcTEFLrm
367 | 8gWbzBfwYiUm6aJce4zoafP0NSlEOBbpbr73A08Q1IK6pISwltOUhhTvspSZnK41
368 | y2CHt5Drnpl1pfOw9Q0svO3VrUP+omxP1SFP17ZfaRGw2uHd08HJZs438x5dIQoH
369 | QgjlZ8A5rcT3FjnytSh3fln2ZxAGuObghuzmOEL/+8fkGER9QVjmQlsL6OMfB4j4
370 | ZAkLf74uaTdegF3SqDQaGUwWgk7LyualmUXWTBoeP9kRIsRQLGzAEmd6duBPypED
371 | HhKXP/ZFA1kVp3x1fzJ2llMFB3m1JBwy4PiohqrIJoR+YvKUvzVQtbOjxtCEAj87
372 | JFnlQj0wjTd6lfNn+okewMNjKINZx+08ui7XANNU/l18lHIIz3ssXJSmqMW+hRZ9
373 | 9oB2tntLrnRMhkVZDVHadq7eMFOPu0rkekuaZm9CO2vu4V7Qa2h+gOoeczYza0H7
374 | A+qCKbprxyL8SKI5vug2hE+mfC1leXVRtUYm1DnE+oet99bFd0fN20NwTw0rOeRg
375 | 0Z+/ZpQNizrXxfd3sU7zaJypWCxZ6TD/U/AKBtcb2gqmUjObZhbfbWq6jU2Ye//w
376 | EBqQkwAUXR1tNekF8CWLOrfC/wbLRxVRkayb8bQUfdgukLpz0bgw
377 | -----END ENCRYPTED PRIVATE KEY-----
378 | `
379 | const key = await crypto.keys.importKey(pem, 'mypassword')
380 | expect(key).to.exist()
381 | })
382 |
383 | it('can read a private encrypted key (v2 des3)', async () => {
384 | /*
385 | * Generated with
386 | * openssl genpkey -algorithm RSA
387 | * -pkeyopt rsa_keygen_bits:1024
388 | * -pkeyopt rsa_keygen_pubexp:65537
389 | * -out foo.pem
390 | * openssl pkcs8 -in foo.pem -topk8 -v2 des3 -passout pass:mypassword
391 | */
392 | const pem = `-----BEGIN ENCRYPTED PRIVATE KEY-----
393 | MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQISznrfHd+D58CAggA
394 | MBQGCCqGSIb3DQMHBAhx0DnnUvDiHASCAoCceplm+Cmwlgvn4hNsv6e4c/S1iA7w
395 | 2hU7Jt8JgRCIMWjP2FthXOAFLa2fD4g3qncYXcDAFBXNyoh25OgOwstO14YkxhDi
396 | wG4TeppGUt9IlyyCol6Z4WhQs1TGm5OcD5xDta+zBXsBnlgmKLD5ZXPEYB+3v/Dg
397 | SvM4sQz6NgkVHN52hchERsnknwSOghiK9mIBH0RZU5LgzlDy2VoBCiEPVdZ7m4F2
398 | dft5e82zFS58vwDeNN/0r7fC54TyJf/8k3q94+4Hp0mseZ67LR39cvnEKuDuFROm
399 | kLPLekWt5R2NGdunSQlA79BkrNB1ADruO8hQOOHMO9Y3/gNPWLKk+qrfHcUni+w3
400 | Ofq+rdfakHRb8D6PUmsp3wQj6fSOwOyq3S50VwP4P02gKcZ1om1RvEzTbVMyL3sh
401 | hZcVB3vViu3DO2/56wo29lPVTpj9bSYjw/CO5jNpPBab0B/Gv7JAR0z4Q8gn6OPy
402 | qf+ddyW4Kcb6QUtMrYepghDthOiS3YJV/zCNdL3gTtVs5Ku9QwQ8FeM0/5oJZPlC
403 | TxGuOFEJnYRWqIdByCP8mp/qXS5alSR4uoYQSd7vZG4vkhkPNSAwux/qK1IWfqiW
404 | 3XlZzrbD//9IzFVqGRs4nRIFq85ULK0zAR57HEKIwGyn2brEJzrxpV6xsHBp+m4w
405 | 6r0+PtwuWA0NauTCUzJ1biUdH8t0TgBL6YLaMjlrfU7JstH3TpcZzhJzsjfy0+zV
406 | NT2TO3kSzXpQ5M2VjOoHPm2fqxD/js+ThDB3QLi4+C7HqakfiTY1lYzXl9/vayt6
407 | DUD29r9pYL9ErB9tYko2rat54EY7k7Ts6S5jf+8G7Zz234We1APhvqaG
408 | -----END ENCRYPTED PRIVATE KEY-----
409 | `
410 | const key = await crypto.keys.importKey(pem, 'mypassword')
411 | expect(key).to.exist()
412 | })
413 | })
414 | })
415 |
--------------------------------------------------------------------------------
/test/keys/secp256k1.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | import { expect } from 'aegir/chai'
3 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
4 | import * as crypto from '../../src/index.js'
5 | import * as Secp256k1 from '../../src/keys/secp256k1-class.js'
6 | import * as secp256k1Crypto from '../../src/keys/secp256k1.js'
7 | import fixtures from '../fixtures/go-key-secp256k1.js'
8 |
9 | const secp256k1 = crypto.keys.supportedKeys.secp256k1
10 | const keysPBM = crypto.keys.keysPBM
11 | const randomBytes = crypto.randomBytes
12 |
13 | describe('secp256k1 keys', () => {
14 | let key: Secp256k1.Secp256k1PrivateKey
15 |
16 | before(async () => {
17 | key = await secp256k1.generateKeyPair()
18 | })
19 |
20 | it('generates a valid key', async () => {
21 | expect(key).to.be.an.instanceof(secp256k1.Secp256k1PrivateKey)
22 | expect(key.public).to.be.an.instanceof(secp256k1.Secp256k1PublicKey)
23 |
24 | const digest = await key.hash()
25 | expect(digest).to.have.length(34)
26 |
27 | const publicDigest = await key.public.hash()
28 | expect(publicDigest).to.have.length(34)
29 | })
30 |
31 | it('optionally accepts a `bits` argument when generating a key', async () => {
32 | const _key = await secp256k1.generateKeyPair()
33 | expect(_key).to.be.an.instanceof(secp256k1.Secp256k1PrivateKey)
34 | })
35 |
36 | it('signs', async () => {
37 | const text = randomBytes(512)
38 | const sig = await key.sign(text)
39 | const res = await key.public.verify(text, sig)
40 | expect(res).to.equal(true)
41 | })
42 |
43 | it('encoding', () => {
44 | const keyMarshal = key.marshal()
45 | const key2 = secp256k1.unmarshalSecp256k1PrivateKey(keyMarshal)
46 | const keyMarshal2 = key2.marshal()
47 |
48 | expect(keyMarshal).to.eql(keyMarshal2)
49 |
50 | const pk = key.public
51 | const pkMarshal = pk.marshal()
52 | const pk2 = secp256k1.unmarshalSecp256k1PublicKey(pkMarshal)
53 | const pkMarshal2 = pk2.marshal()
54 |
55 | expect(pkMarshal).to.eql(pkMarshal2)
56 | })
57 |
58 | it('key id', async () => {
59 | const decoded = keysPBM.PrivateKey.decode(fixtures.privateKey)
60 | const key = secp256k1.unmarshalSecp256k1PrivateKey(decoded.Data ?? new Uint8Array())
61 | const id = await key.id()
62 | expect(id).to.eql('QmPCyMBGEyifPtx5aa6k6wkY9N1eBf9vHK1eKfNc35q9uq')
63 | })
64 |
65 | it('should export a password encrypted libp2p-key', async () => {
66 | const key = await crypto.keys.generateKeyPair('secp256k1')
67 |
68 | if (!(key instanceof Secp256k1.Secp256k1PrivateKey)) {
69 | throw new Error('Generated wrong key type')
70 | }
71 |
72 | const encryptedKey = await key.export('my secret')
73 | // Import the key
74 | const importedKey = await crypto.keys.importKey(encryptedKey, 'my secret')
75 |
76 | if (!(importedKey instanceof Secp256k1.Secp256k1PrivateKey)) {
77 | throw new Error('Imported wrong key type')
78 | }
79 |
80 | expect(key.equals(importedKey)).to.equal(true)
81 | })
82 |
83 | it('should fail to import libp2p-key with wrong password', async () => {
84 | const key = await crypto.keys.generateKeyPair('secp256k1')
85 | const encryptedKey = await key.export('my secret', 'libp2p-key')
86 |
87 | await expect(crypto.keys.importKey(encryptedKey, 'not my secret')).to.eventually.be.rejected()
88 | })
89 |
90 | describe('key equals', () => {
91 | it('equals itself', () => {
92 | expect(key.equals(key)).to.eql(true)
93 |
94 | expect(key.public.equals(key.public)).to.eql(true)
95 | })
96 |
97 | it('not equals other key', async () => {
98 | const key2 = await secp256k1.generateKeyPair()
99 | expect(key.equals(key2)).to.eql(false)
100 | expect(key2.equals(key)).to.eql(false)
101 | expect(key.public.equals(key2.public)).to.eql(false)
102 | expect(key2.public.equals(key.public)).to.eql(false)
103 | })
104 | })
105 |
106 | it('sign and verify', async () => {
107 | const data = uint8ArrayFromString('hello world')
108 | const sig = await key.sign(data)
109 | const valid = await key.public.verify(data, sig)
110 | expect(valid).to.eql(true)
111 | })
112 |
113 | it('fails to verify for different data', async () => {
114 | const data = uint8ArrayFromString('hello world')
115 | const sig = await key.sign(data)
116 | const valid = await key.public.verify(uint8ArrayFromString('hello'), sig)
117 | expect(valid).to.eql(false)
118 | })
119 | })
120 |
121 | describe('crypto functions', () => {
122 | let privKey: Uint8Array
123 | let pubKey: Uint8Array
124 |
125 | before(() => {
126 | privKey = secp256k1Crypto.generateKey()
127 | pubKey = secp256k1Crypto.computePublicKey(privKey)
128 | })
129 |
130 | it('generates valid keys', () => {
131 | expect(() => {
132 | secp256k1Crypto.validatePrivateKey(privKey)
133 | secp256k1Crypto.validatePublicKey(pubKey)
134 | }).to.not.throw()
135 | })
136 |
137 | it('does not validate an invalid key', () => {
138 | expect(() => { secp256k1Crypto.validatePublicKey(uint8ArrayFromString('42')) }).to.throw()
139 | expect(() => { secp256k1Crypto.validatePrivateKey(uint8ArrayFromString('42')) }).to.throw()
140 | })
141 |
142 | it('validates a correct signature', async () => {
143 | const sig = await secp256k1Crypto.hashAndSign(privKey, uint8ArrayFromString('hello'))
144 | const valid = await secp256k1Crypto.hashAndVerify(pubKey, sig, uint8ArrayFromString('hello'))
145 | expect(valid).to.equal(true)
146 | })
147 |
148 | it('does not validate when validating a message with an invalid signature', async () => {
149 | const result = await secp256k1Crypto.hashAndVerify(pubKey, uint8ArrayFromString('invalid-sig'), uint8ArrayFromString('hello'))
150 |
151 | expect(result).to.be.false()
152 | })
153 |
154 | it('errors if given a null Uint8Array to sign', async () => {
155 | // @ts-expect-error incorrect args
156 | await expect(secp256k1Crypto.hashAndSign(privKey, null)).to.eventually.be.rejected()
157 | })
158 |
159 | it('errors when signing with an invalid key', async () => {
160 | await expect(secp256k1Crypto.hashAndSign(uint8ArrayFromString('42'), uint8ArrayFromString('Hello'))).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_INPUT')
161 | })
162 |
163 | it('errors if given a null Uint8Array to validate', async () => {
164 | const sig = await secp256k1Crypto.hashAndSign(privKey, uint8ArrayFromString('hello'))
165 |
166 | // @ts-expect-error incorrect args
167 | await expect(secp256k1Crypto.hashAndVerify(privKey, sig, null)).to.eventually.be.rejected()
168 | })
169 |
170 | it('throws when compressing an invalid public key', () => {
171 | expect(() => secp256k1Crypto.compressPublicKey(uint8ArrayFromString('42'))).to.throw()
172 | })
173 |
174 | it('throws when decompressing an invalid public key', () => {
175 | expect(() => secp256k1Crypto.decompressPublicKey(uint8ArrayFromString('42'))).to.throw()
176 | })
177 |
178 | it('compresses/decompresses a valid public key', () => {
179 | const decompressed = secp256k1Crypto.decompressPublicKey(pubKey)
180 | expect(decompressed).to.exist()
181 | expect(decompressed.length).to.be.eql(65)
182 | const recompressed = secp256k1Crypto.compressPublicKey(decompressed)
183 | expect(recompressed).to.eql(pubKey)
184 | })
185 | })
186 |
187 | describe('go interop', () => {
188 | it('loads a private key marshaled by go-libp2p-crypto', () => {
189 | // we need to first extract the key data from the protobuf, which is
190 | // normally handled by js-libp2p-crypto
191 | const decoded = keysPBM.PrivateKey.decode(fixtures.privateKey)
192 | expect(decoded.Type).to.eql(keysPBM.KeyType.Secp256k1)
193 |
194 | const key = secp256k1.unmarshalSecp256k1PrivateKey(decoded.Data ?? new Uint8Array())
195 | expect(key).to.be.an.instanceof(secp256k1.Secp256k1PrivateKey)
196 | expect(key.bytes).to.eql(fixtures.privateKey)
197 | })
198 |
199 | it('loads a public key marshaled by go-libp2p-crypto', () => {
200 | const decoded = keysPBM.PublicKey.decode(fixtures.publicKey)
201 | expect(decoded.Type).to.be.eql(keysPBM.KeyType.Secp256k1)
202 |
203 | const key = secp256k1.unmarshalSecp256k1PublicKey(decoded.Data ?? new Uint8Array())
204 | expect(key).to.be.an.instanceof(secp256k1.Secp256k1PublicKey)
205 | expect(key.bytes).to.eql(fixtures.publicKey)
206 | })
207 |
208 | it('generates the same signature as go-libp2p-crypto', async () => {
209 | const decoded = keysPBM.PrivateKey.decode(fixtures.privateKey)
210 | expect(decoded.Type).to.eql(keysPBM.KeyType.Secp256k1)
211 |
212 | const key = secp256k1.unmarshalSecp256k1PrivateKey(decoded.Data ?? new Uint8Array())
213 | const sig = await key.sign(fixtures.message)
214 | expect(sig).to.eql(fixtures.signature)
215 | })
216 | })
217 |
--------------------------------------------------------------------------------
/test/random-bytes.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | import { expect } from 'aegir/chai'
3 | import randomBytes from '../src/random-bytes.js'
4 |
5 | describe('randomBytes', () => {
6 | it('produces random bytes', () => {
7 | expect(randomBytes(16)).to.have.length(16)
8 | })
9 |
10 | it('throws if length is 0', () => {
11 | expect(() => randomBytes(0)).to.throw(Error).with.property('code', 'ERR_INVALID_LENGTH')
12 | })
13 |
14 | it('throws if length is < 0', () => {
15 | expect(() => randomBytes(-1)).to.throw(Error).with.property('code', 'ERR_INVALID_LENGTH')
16 | })
17 |
18 | it('throws if length is not a number', () => {
19 | // @ts-expect-error invalid params
20 | expect(() => randomBytes('hi')).to.throw(Error).with.property('code', 'ERR_INVALID_LENGTH')
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/test/util.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint max-nested-callbacks: ["error", 8] */
2 | /* eslint-env mocha */
3 | import { expect } from 'aegir/chai'
4 | import 'node-forge/lib/jsbn.js'
5 | // @ts-expect-error types are missing
6 | import forge from 'node-forge/lib/forge.js'
7 | import * as util from '../src/util.js'
8 |
9 | describe('Util', () => {
10 | let bn: typeof forge.jsbn.BigInteger
11 |
12 | before(() => {
13 | bn = new forge.jsbn.BigInteger('dead', 16)
14 | })
15 |
16 | it('should convert BigInteger to a uint base64url encoded string', () => {
17 | expect(util.bigIntegerToUintBase64url(bn)).to.eql('3q0')
18 | })
19 |
20 | it('should convert BigInteger to a uint base64url encoded string with padding', () => {
21 | const bnpad = new forge.jsbn.BigInteger('ff', 16)
22 | expect(util.bigIntegerToUintBase64url(bnpad, 2)).to.eql('AP8')
23 | })
24 |
25 | it('should convert base64url encoded string to BigInteger', () => {
26 | const num = util.base64urlToBigInteger('3q0')
27 | expect(num.equals(bn)).to.be.true()
28 | })
29 |
30 | it('should convert base64url encoded string to Uint8Array with padding', () => {
31 | const buf = util.base64urlToBuffer('AP8', 2)
32 | expect(Uint8Array.from([0, 255])).to.eql(buf)
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/test/workaround.spec.ts:
--------------------------------------------------------------------------------
1 |
2 | /* eslint-env mocha */
3 | import { expect } from 'aegir/chai'
4 | import { derivedEmptyPasswordKey } from '../src/ciphers/aes-gcm.browser.js'
5 |
6 | describe('Constant derived key is generated correctly', () => {
7 | it('Generates correctly', async () => {
8 | if ((typeof navigator !== 'undefined' && navigator.userAgent.includes('Safari')) || typeof crypto === 'undefined') {
9 | // WebKit Linux can't generate this. Hence the workaround.
10 | return
11 | }
12 |
13 | const generatedKey = await crypto.subtle.exportKey('jwk',
14 | await crypto.subtle.deriveKey(
15 | { name: 'PBKDF2', salt: new Uint8Array(16), iterations: 32767, hash: { name: 'SHA-256' } },
16 | await crypto.subtle.importKey('raw', new Uint8Array(0), { name: 'PBKDF2' }, false, ['deriveKey']),
17 | { name: 'AES-GCM', length: 128 }, true, ['encrypt', 'decrypt'])
18 | )
19 |
20 | // Webkit macos flips these. Sort them so they match.
21 | derivedEmptyPasswordKey.key_ops.sort()
22 | generatedKey?.key_ops?.sort()
23 |
24 | expect(generatedKey).to.eql(derivedEmptyPasswordKey)
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "aegir/src/config/tsconfig.aegir.json",
3 | "compilerOptions": {
4 | "outDir": "dist"
5 | },
6 | "include": [
7 | "src",
8 | "test"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------