| |
| |
| |
| |
| 'use strict'; |
| |
| const secp256k1 = require('secp256k1'); |
| const url = require('node:url'); |
| const constants = require('./constants'); |
| const semver = require('semver'); |
| const ip = require('ip'); |
| const crypto = require('node:crypto'); |
| const assert = require('node:assert'); |
| const { randomBytes, createHash } = crypto; |
| const ms = require('ms'); |
| const equihash = require('@tacticalchihuahua/equihash'); |
| const onionRegex = require('onion-regex'); |
| |
| |
| |
| |
| |
| |
| |
| module.exports.isHexaString = function(str) { |
| return Buffer.from(str, 'hex').length === str.length / 2; |
| }; |
| |
| |
| |
| |
| |
| exports.getRandomKeyString = function() { |
| return exports.getRandomKeyBuffer().toString('hex'); |
| }; |
| |
| |
| |
| |
| |
| exports.getRandomKeyBuffer = function() { |
| return crypto.randomBytes(constants.B / 8); |
| }; |
| |
| |
| |
| |
| |
| |
| exports.keyStringIsValid = function(key) { |
| let buf; |
| |
| try { |
| buf = Buffer.from(key, 'hex'); |
| } catch (err) { |
| return false; |
| } |
| |
| return exports.keyBufferIsValid(buf); |
| }; |
| |
| |
| |
| |
| |
| |
| exports.keyBufferIsValid = function(key) { |
| return Buffer.isBuffer(key) && key.length === constants.B / 8; |
| }; |
| |
| |
| |
| |
| |
| |
| |
| exports.getDistance = function(id1, id2) { |
| id1 = !Buffer.isBuffer(id1) |
| ? Buffer.from(id1, 'hex') |
| : id1; |
| id2 = !Buffer.isBuffer(id2) |
| ? Buffer.from(id2, 'hex') |
| : id2; |
| |
| assert(exports.keyBufferIsValid(id1), 'Invalid key supplied'); |
| assert(exports.keyBufferIsValid(id2), 'Invalid key supplied'); |
| |
| return Buffer.alloc(constants.B / 8) |
| .map((b, index) => id1[index] ^ id2[index]); |
| }; |
| |
| |
| |
| |
| |
| |
| |
| exports.compareKeyBuffers = function(b1, b2) { |
| assert(exports.keyBufferIsValid(b1), 'Invalid key supplied'); |
| assert(exports.keyBufferIsValid(b2), 'Invalid key supplied'); |
| |
| for (let index = 0; index < b1.length; index++) { |
| let bits = b1[index]; |
| |
| if (bits !== b2[index]) { |
| return bits < b2[index] ? -1 : 1; |
| } |
| } |
| |
| return 0; |
| }; |
| |
| |
| |
| |
| |
| |
| |
| exports.getBucketIndex = function(referenceKey, foreignKey) { |
| let distance = exports.getDistance(referenceKey, foreignKey); |
| let bucketIndex = constants.B; |
| |
| for (let byteValue of distance) { |
| if (byteValue === 0) { |
| bucketIndex -= 8; |
| continue; |
| } |
| |
| for (let i = 0; i < 8; i++) { |
| if (byteValue & (0x80 >> i)) { |
| return --bucketIndex; |
| } else { |
| bucketIndex--; |
| } |
| } |
| } |
| |
| return bucketIndex; |
| }; |
| |
| |
| |
| |
| |
| |
| |
| exports.getPowerOfTwoBufferForIndex = function(referenceKey, exp) { |
| assert(exp >= 0 && exp < constants.B, 'Index out of range'); |
| |
| const buffer = Buffer.isBuffer(referenceKey) |
| ? Buffer.from(referenceKey) |
| : Buffer.from(referenceKey, 'hex'); |
| const byteValue = parseInt(exp / 8); |
| |
| |
| buffer[constants.K - byteValue - 1] = 1 << (exp % 8); |
| |
| return buffer; |
| }; |
| |
| |
| |
| |
| |
| |
| exports.getRandomBufferInBucketRange = function(referenceKey, index) { |
| let base = exports.getPowerOfTwoBufferForIndex(referenceKey, index); |
| let byte = parseInt(index / 8); |
| |
| for (let i = constants.K - 1; i > (constants.K - byte - 1); i--) { |
| base[i] = parseInt(Math.random() * 256); |
| } |
| |
| |
| |
| for (let j = index - 1; j >= byte * 8; j--) { |
| let one = Math.random() >= 0.5; |
| let shiftAmount = j - byte * 8; |
| |
| base[constants.K - byte - 1] |= one ? (1 << shiftAmount) : 0; |
| } |
| |
| return base; |
| }; |
| |
| |
| |
| |
| |
| exports.validateStorageAdapter = function(storage) { |
| assert(typeof storage === 'object', |
| 'No storage adapter supplied'); |
| assert(typeof storage.get === 'function', |
| 'Store has no get method'); |
| assert(typeof storage.put === 'function', |
| 'Store has no put method'); |
| assert(typeof storage.del === 'function', |
| 'Store has no del method'); |
| assert(typeof storage.createReadStream === 'function', |
| 'Store has no createReadStream method'); |
| }; |
| |
| |
| |
| |
| |
| exports.validateLogger = function(logger) { |
| assert(typeof logger === 'object', |
| 'No logger object supplied'); |
| assert(typeof logger.debug === 'function', |
| 'Logger has no debug method'); |
| assert(typeof logger.info === 'function', |
| 'Logger has no info method'); |
| assert(typeof logger.warn === 'function', |
| 'Logger has no warn method'); |
| assert(typeof logger.error === 'function', |
| 'Logger has no error method'); |
| }; |
| |
| |
| |
| |
| |
| exports.validateTransport = function(transport) { |
| assert(typeof transport === 'object', |
| 'No transport adapter supplied'); |
| assert(typeof transport.read === 'function', |
| 'Transport has no read method'); |
| assert(typeof transport.write === 'function', |
| 'Transport has no write method'); |
| }; |
| |
| |
| |
| |
| |
| module.exports.hash256 = function(input) { |
| return crypto.createHash('sha256').update(input).digest(); |
| }; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| module.exports.eqsolve = equihash.solve; |
| |
| |
| |
| |
| |
| |
| |
| module.exports.eqverify = equihash.verify; |
| |
| |
| |
| |
| |
| module.exports.hash160 = function(input) { |
| return crypto.createHash('ripemd160').update(input).digest(); |
| }; |
| |
| |
| |
| |
| |
| |
| module.exports.getContactURL = function(contact) { |
| const [id, info] = contact; |
| |
| return `${info.protocol}//${info.hostname}:${info.port}/#${id}`; |
| }; |
| |
| |
| |
| |
| |
| module.exports.parseContactURL = function(addr) { |
| const { protocol, hostname, port, hash } = url.parse(addr); |
| const contact = [ |
| (hash ? hash.substr(1) : null) || |
| Buffer.alloc(constants.B / 8).fill(0).toString('hex'), |
| { |
| protocol, |
| hostname, |
| port |
| } |
| ]; |
| |
| return contact; |
| }; |
| |
| |
| |
| |
| |
| |
| module.exports.isCompatibleVersion = function(version) { |
| const local = require('./version').protocol; |
| const remote = version; |
| const sameMajor = semver.major(local) === semver.major(remote); |
| const diffs = ['prerelease', 'prepatch', 'preminor', 'premajor']; |
| |
| if (diffs.indexOf(semver.diff(remote, local)) !== -1) { |
| return false; |
| } else { |
| return sameMajor; |
| } |
| }; |
| |
| |
| |
| |
| |
| |
| |
| module.exports.isValidContact = function(contact, loopback) { |
| const [, info] = contact; |
| const isValidAddr = ip.isV4Format(info.hostname) || |
| ip.isV6Format(info.hostname) || |
| ip.isPublic(info.hostname) || |
| onionRegex.v3({ exact: true }).test(info.hostname); |
| const isValidPort = info.port > 0; |
| const isAllowedAddr = ip.isLoopback(info.hostname) ? !!loopback : true; |
| |
| return isValidPort && isValidAddr && isAllowedAddr; |
| }; |
| |
| |
| |
| |
| |
| |
| module.exports.toBinaryStringFromBuffer = function(buffer) { |
| const mapping = { |
| '0': '0000', |
| '1': '0001', |
| '2': '0010', |
| '3': '0011', |
| '4': '0100', |
| '5': '0101', |
| '6': '0110', |
| '7': '0111', |
| '8': '1000', |
| '9': '1001', |
| 'a': '1010', |
| 'b': '1011', |
| 'c': '1100', |
| 'd': '1101', |
| 'e': '1110', |
| 'f': '1111' |
| }; |
| const hexaString = buffer.toString('hex').toLowerCase(); |
| const bitmaps = []; |
| |
| for (let i = 0; i < hexaString.length; i++) { |
| bitmaps.push(mapping[hexaString[i]]); |
| } |
| |
| return bitmaps.join(''); |
| }; |
| |
| |
| |
| |
| |
| |
| |
| |
| module.exports.satisfiesDifficulty = function(buffer, difficulty) { |
| const binString = module.exports.toBinaryStringFromBuffer(buffer); |
| const prefix = Array(difficulty).fill('0').join(''); |
| |
| return binString.substr(0, difficulty) === prefix; |
| }; |
| |
| |
| |
| |
| module.exports._sha256 = function(input) { |
| return createHash('sha256').update(input).digest(); |
| }; |
| |
| |
| |
| |
| module.exports._rmd160 = function(input) { |
| return createHash('ripemd160').update(input).digest(); |
| }; |
| |
| |
| |
| |
| |
| module.exports.generatePrivateKey = function() { |
| let privKey |
| |
| do { |
| privKey = randomBytes(32); |
| } while (!secp256k1.privateKeyVerify(privKey)) |
| |
| return privKey; |
| }; |
| |
| |
| |
| |
| |
| |
| module.exports.toPublicKeyHash = function(publicKey) { |
| return exports._rmd160(exports._sha256(publicKey)); |
| }; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| module.exports.preventConvoy = function(func, timeout) { |
| return function() { |
| let t = Math.ceil( |
| Math.random() * (typeof timeout !== 'number' ? ms('30m') : timeout) |
| ); |
| return setTimeout(func, t); |
| }; |
| }; |