UNPKG

@erasure/crypto-ipfs

Version:

asymmetric and symmetric crypto for use with erasure, ipfs and private backend. warning: unaudited.

172 lines (162 loc) 5.78 kB
const fernet = require('fernet') const tweetnacl = require('tweetnacl') const pbkdf2 = require('pbkdf2') const getRandomValues = require('get-random-values') const multihash = require('multihashes') const sha256_cid = require('ipfs-only-hash') const ethers = require('ethers') const MAX_UINT32 = Math.pow(2, 32) - 1 const MAX_UINT8 = Math.pow(2, 8) - 1 const FERNET_SECRET_LENGTH = 32 const NONCE_LENGTH = 24 const randomNumber = () => { if (typeof window === 'undefined') { return getRandomValues(new Uint8Array(1))[0] / MAX_UINT8 } return getRandomValues(new Uint32Array(1))[0] / MAX_UINT32 } const randomString = () => { let result = '' const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' const charactersLength = characters.length for (let i = 0; i < FERNET_SECRET_LENGTH; i++) { result += characters.charAt(Math.floor(randomNumber() * charactersLength)) } return result } /// Convert multihash from input of specified type to multihash buffer object /// Valid input types: /// - 'raw': raw data of any form - will caculate chunked ipld content id using sha2-256 /// - 'sha2-256': hex encoded sha2-256 hash - will append multihash prefix /// - 'hex': hex encoded multihash /// - 'b58': base58 encoded multihash async function multihashFrom(input, inputType) { const inputTypes = ['raw', 'sha2-256', 'hex', 'b58'] let contentid if (inputType === 'raw') { contentid = multihash.fromB58String(await sha256_cid.of(Buffer.from(input))) } else if (inputType === 'sha2-256') { input = input.slice(0, 2) === '0x' ? input.slice(2) : input contentid = multihash.fromHexString('1220' + input) } else if (inputType === 'hex') { input = input.slice(0, 2) === '0x' ? input.slice(2) : input contentid = multihash.fromHexString(input) } else if (inputType === 'b58') { contentid = multihash.fromB58String(input) } else { throw new Error( `Invalid inputType: ${inputType} should be one of [${inputTypes}]`, ) } multihash.validate(contentid) return contentid } /// Convert multihash from buffer object to output of specified type /// Valid output types: /// - 'prefix': hex encoded multihash prefix /// - 'digest': hex encoded hash /// - 'hex': hex encoded multihash /// - 'b58': base58 encoded multihash async function multihashTo(contentid, outputType) { const outputTypes = ['prefix', 'digest', 'hex', 'b58'] if (outputType === 'prefix') { return '0x' + multihash.prefix(contentid).toString('hex') } else if (outputType === 'digest') { return '0x' + multihash.toHexString(multihash.decode(contentid).digest) } else if (outputType === 'hex') { return '0x' + multihash.toHexString(contentid) } else if (outputType === 'b58') { return multihash.toB58String(contentid) } else { throw new Error( `Invalid outputType: ${outputType} should be one of [${outputTypes}]`, ) } } const ErasureHelper = { multihash: async ({ input, inputType, outputType }) => multihashTo(await multihashFrom(input, inputType), outputType), constants: { RATIO_TYPES: { NaN: 0, Inf: 1, Dec: 2, }, TOKEN_TYPES: { NaN: 0, NMR: 1, DAI: 2, }, }, encodeCreateCall: (templateABI, abiValues) => { const interface = new ethers.utils.Interface(templateABI) const calldata = interface.functions.initialize.encode(abiValues) return calldata }, crypto: { symmetric: { generateKey: () => { let key = Buffer.from(randomString()).toString('base64') let secret = fernet.decode64toHex(key) while (secret.length !== fernet.hexBits(256)) { key = Buffer.from(randomString()).toString('base64') secret = fernet.decode64toHex(key) } return key }, encryptMessage: (secretKey, msg) => { const secret = new fernet.Secret(secretKey) const token = new fernet.Token({ secret, ttl: 0 }) return token.encode(msg) }, decryptMessage: (secretKey, encryptedMessage) => { const secret = new fernet.Secret(secretKey) const token = new fernet.Token({ secret, ttl: 0, token: encryptedMessage, }) return token.decode() }, }, asymmetric: { generateKeyPair: (sig, salt) => tweetnacl.box.keyPair.fromSecretKey( pbkdf2.pbkdf2Sync(sig, salt, 1000, 32), ), generateNonce: () => tweetnacl.randomBytes(NONCE_LENGTH), encryptMessage: (msg, nonce, publicKey, secretKey) => { const encoder = new TextEncoder() const encodedMessage = encoder.encode(msg) return tweetnacl.box(encodedMessage, nonce, publicKey, secretKey) }, decryptMessage: (box, nonce, publicKey, secretKey) => { const decoder = new TextDecoder() const encodedMessage = tweetnacl.box.open( box, nonce, publicKey, secretKey, ) if (!encodedMessage) { throw new Error('Asymmetric decryption failed. Make sure the public key belongs to the sender and the private key belongs to the receiver') } return decoder.decode(encodedMessage) }, secretBox: { encryptMessage: (msg, nonce, secretKey) => { const encoder = new TextEncoder() const encodedMessage = encoder.encode(msg) return tweetnacl.secretbox(encodedMessage, nonce, secretKey) }, decryptMessage: (box, nonce, secretKey) => { const decoder = new TextDecoder() const encodedMessage = tweetnacl.secretbox.open(box, nonce, secretKey) return decoder.decode(encodedMessage) }, }, }, }, } module.exports = ErasureHelper