Skip to content
This repository has been archived by the owner on Jul 21, 2023. It is now read-only.

feat: type with libp2p-interfaces #191

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
],
"scripts": {
"lint": "aegir lint",
"build": "npm run build:proto && aegir build --no-types",
"build": "npm run build:proto && aegir build",
"build:proto": "pbjs -t static-module -w commonjs -r libp2p-crypto-keys --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/keys/keys.js ./src/keys/keys.proto",
"build:proto-types": "pbts -o src/keys/keys.d.ts src/keys/keys.js",
"test": "aegir test",
"test:node": "aegir test -t node",
"test:browser": "aegir test -t browser -t webworker",
Expand All @@ -30,6 +31,9 @@
"size": "aegir build --bundlesize",
"test:types": "npx tsc"
},
"eslintConfig": {
"extends": "ipfs"
},
"keywords": [
"IPFS",
"libp2p",
Expand All @@ -52,14 +56,18 @@
"protobufjs": "^6.10.2",
"secp256k1": "^4.0.0",
"uint8arrays": "^2.1.4",
"ursa-optional": "^0.10.1"
"ursa-optional": "^0.10.1",
"libp2p-interfaces": "git://github.com/libp2p/js-libp2p-interfaces#feat/interfaces"
},
"devDependencies": {
"@types/chai": "^4.2.12",
"@types/chai-string": "^1.4.2",
"@types/dirty-chai": "^2.0.2",
"@types/mocha": "^8.0.1",
"aegir": "^33.0.0",
"@types/pem-jwk": "^1.5.0",
"@types/node-forge": "^0.10.0",
"@types/secp256k1": "^4.0.2",
"aegir": "^33.2.2",
"benchmark": "^2.1.4",
"chai": "^4.2.0",
"chai-string": "^1.5.0",
Expand Down
7 changes: 7 additions & 0 deletions src/aes/cipher-mode.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ const CIPHER_MODES = {
32: 'aes-256-ctr'
}

/**
* @typedef {'aes-128-ctr'|'aes-256-ctr'} Mode
*
* @param {Uint8Array} key
* @returns {Mode}
*/
module.exports = function (key) {
// @ts-expect-error
const mode = CIPHER_MODES[key.length]
if (!mode) {
const modes = Object.entries(CIPHER_MODES).map(([k, v]) => `${k} (${v})`).join(' / ')
Expand Down
23 changes: 23 additions & 0 deletions src/aes/ciphers-browser.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,48 @@
'use strict'

// @ts-ignore
require('node-forge/lib/aes')
/** @type {import('node-forge')} */
// @ts-ignore
const forge = require('node-forge/lib/forge')
const uint8ArrayToString = require('uint8arrays/to-string')
const uint8ArrayFromString = require('uint8arrays/from-string')

module.exports = {
/**
*
* @param {any} mode
* @param {Uint8Array} key
* @param {Uint8Array} iv
* @returns
*/
createCipheriv: (mode, key, iv) => {
const cipher2 = forge.cipher.createCipher('AES-CTR', uint8ArrayToString(key, 'ascii'))
cipher2.start({ iv: uint8ArrayToString(iv, 'ascii') })
return {
/**
* @param {Uint8Array} data
*/
update: (data) => {
cipher2.update(forge.util.createBuffer(uint8ArrayToString(data, 'ascii')))
return uint8ArrayFromString(cipher2.output.getBytes(), 'ascii')
}
}
},
/**
*
* @param {any} mode
* @param {Uint8Array} key
* @param {Uint8Array} iv
* @returns
*/
createDecipheriv: (mode, key, iv) => {
const cipher2 = forge.cipher.createDecipher('AES-CTR', uint8ArrayToString(key, 'ascii'))
cipher2.start({ iv: uint8ArrayToString(iv, 'ascii') })
return {
/**
* @param {Uint8Array} data
*/
update: (data) => {
cipher2.update(forge.util.createBuffer(uint8ArrayToString(data, 'ascii')))
return uint8ArrayFromString(cipher2.output.getBytes(), 'ascii')
Expand Down
15 changes: 15 additions & 0 deletions src/aes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,31 @@
const ciphers = require('./ciphers')
const cipherMode = require('./cipher-mode')

/**
* Create a new AES Cipher.
*
* @param {Uint8Array} key - The key, if length 16 then AES 128 is used. For length 32, AES 256 is used.
* @param {Uint8Array} iv - Must have length 16.
* @returns {Promise<import('libp2p-interfaces/src/crypto/types').Cipher>}
*/
exports.create = async function (key, iv) { // eslint-disable-line require-await
const mode = cipherMode(key)
const cipher = ciphers.createCipheriv(mode, key, iv)
const decipher = ciphers.createDecipheriv(mode, key, iv)

const res = {
/**
* @param {Uint8Array} data
* @returns {Promise<Uint8Array>}
*/
async encrypt (data) { // eslint-disable-line require-await
return cipher.update(data)
},

/**
* @param {Uint8Array} data
* @returns {Promise<Uint8Array>}
*/
async decrypt (data) { // eslint-disable-line require-await
return decipher.update(data)
}
Expand Down
28 changes: 17 additions & 11 deletions src/ciphers/aes-gcm.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const uint8ArrayFromString = require('uint8arrays/from-string')
* @param {string} [options.digest=sha256]
* @param {number} [options.saltLength=16]
* @param {number} [options.iterations=32767]
* @returns {*}
*/
function create ({
algorithmTagLength = 16,
Expand Down Expand Up @@ -57,12 +56,8 @@ function create ({
// Generate a 128-bit salt using a CSPRNG.
const salt = crypto.randomBytes(saltLength)

if (typeof password === 'string' || password instanceof String) {
password = uint8ArrayFromString(password)
}

// Derive a key using PBKDF2.
const key = crypto.pbkdf2Sync(password, salt, iterations, keyLength, digest)
const key = crypto.pbkdf2Sync(decodePassword(password), salt, iterations, keyLength, digest)

// Encrypt and prepend salt.
return uint8ArrayConcat([salt, await encryptWithKey(Uint8Array.from(data), key)])
Expand Down Expand Up @@ -108,12 +103,8 @@ function create ({
const salt = data.slice(0, saltLength)
const ciphertextAndNonce = data.slice(saltLength)

if (typeof password === 'string' || password instanceof String) {
password = uint8ArrayFromString(password)
}

// Derive the key using PBKDF2.
const key = crypto.pbkdf2Sync(password, salt, iterations, keyLength, digest)
const key = crypto.pbkdf2Sync(decodePassword(password), salt, iterations, keyLength, digest)

// Decrypt and return result.
return decryptWithKey(ciphertextAndNonce, key)
Expand All @@ -124,6 +115,21 @@ function create ({
decrypt
}
}
/**
*
* @param {string | InstanceType<typeof globalThis.String> | Uint8Array} password
* @returns {Uint8Array}
*/

const decodePassword = (password) => {
if (typeof password === 'string') {
return uint8ArrayFromString(password)
} else if (password instanceof String) {
return uint8ArrayFromString(password.toString())
} else {
return password
}
}

module.exports = {
create
Expand Down
15 changes: 14 additions & 1 deletion src/hmac/index-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,24 @@ const hashTypes = {
SHA512: 'SHA-512'
}

/**
*
* @param {CryptoKey} key
* @param {Uint8Array} data
*/
const sign = async (key, data) => {
const buf = await webcrypto.get().subtle.sign({ name: 'HMAC' }, key, data)
return new Uint8Array(buf, buf.byteOffset, buf.byteLength)
return new Uint8Array(buf)
}

/**
* @typedef {import('./index').HashType} HashType
*
* @param {HashType} hashType
* @param {Uint8Array} secret
* @returns {Promise<import('libp2p-interfaces/src/crypto/types').Hasher>}
*/

exports.create = async function (hashType, secret) {
const hash = hashTypes[hashType]

Expand Down
18 changes: 18 additions & 0 deletions src/hmac/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,26 @@
const crypto = require('crypto')
const lengths = require('./lengths')

/**
* Maps an IPFS hash name to its node-forge equivalent.
* See https://github.com/multiformats/multihash/blob/master/hashtable.csv
*
* @typedef {"SHA1" | "SHA256" | "SHA512"} HashType
*/

/**
* Create a new HMAC Digest.
*
* @param {HashType} hash
* @param {Uint8Array} secret
* @returns {Promise<import('libp2p-interfaces/src/crypto/types').Hasher>}
*/
exports.create = async function (hash, secret) { // eslint-disable-line require-await
const res = {
/**
*
* @param {Uint8Array} data
*/
async digest (data) { // eslint-disable-line require-await
const hmac = crypto.createHmac(hash.toLowerCase(), secret)
hmac.update(data)
Expand Down
Loading