├── .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 | [![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) 6 | [![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) 7 | [![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p-crypto.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-crypto) 8 | [![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p-crypto/js-test-and-release.yml?branch=master\&style=flat-square)](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://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](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 | --------------------------------------------------------------------------------