diff --git a/package-lock.json b/package-lock.json index 61badf1cd1a7b7f9d947ae1d1d545409957664ef..6c3db0b683dee3a828a58ea67f6939757be8fb42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -547,6 +547,19 @@ "defer-to-connect": "^1.0.1" } }, + "@thi.ng/base-n": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@thi.ng/base-n/-/base-n-0.1.6.tgz", + "integrity": "sha512-vvN0bhnSbuqk7i4BAeajdqZYwfqN4ESZjC/F216AXtg66BUhQp3zuIZh5u1PEsvFr59Z2T8U6HJgT5/PY0daxg==", + "requires": { + "@thi.ng/hex": "^0.2.5" + } + }, + "@thi.ng/hex": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@thi.ng/hex/-/hex-0.2.5.tgz", + "integrity": "sha512-ziJHsLH7zUBjDlZD+q+HszUVMfx9uFE+spSrRTSnt4/fCmXuUxn6hlMAskGifNGBzpE4LB40OG1nvE+0kSYdDw==" + }, "@types/glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", @@ -1084,6 +1097,14 @@ "yargs": "^16.2.0" } }, + "b58": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/b58/-/b58-4.0.3.tgz", + "integrity": "sha512-VDtdiomm0ywbL8YzgevOZ9pcx6LuOZ3d9qYTPDcYUPf7dRYNA8wvK6epYy0FKMWIM5uaDwd3kWt1x+1S9scB1Q==", + "requires": { + "base-x": "^3.0.2" + } + }, "babel-walk": { "version": "3.0.0-canary-5", "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", @@ -1166,6 +1187,14 @@ } } }, + "base-x": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz", + "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1355,6 +1384,14 @@ "node-releases": "^1.1.70" } }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "requires": { + "base-x": "^3.0.2" + } + }, "buf-compare": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buf-compare/-/buf-compare-1.0.1.tgz", @@ -6934,8 +6971,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "2.1.1", diff --git a/package.json b/package.json index c6bf3850757f2396b96c6ff5020fc8985f3c2a61..b12f244c89b7da767181d05ea0213fd5f59c3909 100644 --- a/package.json +++ b/package.json @@ -5,26 +5,27 @@ "postinstall": "run-s clean build", "clean": "rm -rf generated*", "build": "run-s build:**", - "build:mk:npm": "mkdirp generated/npm", - "build:mk:cov-env": "mkdirp generated/cov-env", - "build:mk:minfied": "mkdirp generated/minified", + "build:mk:npm": "mkdirp generated/npm", + "build:mk:cov-env": "mkdirp generated/cov-env", + "build:mk:minfied": "mkdirp generated/minified", "build:mk:vendors": "mkdirp generated/vendors", "build:vendors": "node CI/build.js", "build:npm:all": "rollup src/all.mjs --format esm --file generated/npm/all.mjs", - "build:npm:crypto": "rollup src/crypto.mjs --format esm --file generated/npm/crypto.mjs", + "build:npm:basex": "rollup src/basex.mjs --format esm --file generated/npm/basex.mjs", + "build:npm:crypto": "rollup src/crypto.mjs --format esm --file generated/npm/crypto.mjs", "build:npm:dictionaryBuilder": "rollup src/dictionary-builder.mjs --format esm --file generated/npm/dictionary-builder.mjs", - "build:npm:cp": "cp npm/* generated/npm/", - "build:npm:cp:readme": "cp README* generated/npm/", - "build:npm:test2npm": "node CI/gen-prod-test.js", - "xbuild:npm:coverageForDeadCodeDeletion": "cd generated/npm/ && c8 ava", - - "build:npm:min:all": "terser generated/npm/all.mjs -o generated/minified/all.mjs", - "build:npm:min:crypto": "terser generated/npm/crypto.mjs -o generated/minified/crypto.mjs", + "build:npm:cp": "cp npm/* generated/npm/", + "build:npm:cp:readme": "cp README* generated/npm/", + "build:npm:test2npm": "node CI/gen-prod-test.js", + "xbuild:npm:coverageForDeadCodeDeletion": "cd generated/npm/ && c8 ava", + "build:npm:min:all": "terser generated/npm/all.mjs -o generated/minified/all.mjs", + "build:npm:min:basex": "terser generated/npm/basex.mjs -o generated/minified/basex.mjs", + "build:npm:min:crypto": "terser generated/npm/crypto.mjs -o generated/minified/crypto.mjs", "build:npm:min:dictionaryBuilder": "terser generated/npm/dictionary-builder.mjs -o generated/minified/dictionary-builder.mjs", "test": "run-s test:dev", "test:dev": "run-p test:dev:**", "test:dev:qualityCheck": "xo", - "test:dev:runTests": "ava", + "test:dev:runTests": "ava src/**.test.mjs", "test:dev:duplication": "jscpd ./ -s", "xtest:dev:complexity": "codehawk ./", "test:production": "run-s test:production:**", @@ -33,13 +34,17 @@ "test:production:complexity": "./node_modules/.bin/es6-plato -r -d generated/maintainability ./src/*", "test:production:complexity:badgesAndThreshold": "node CI/plato-badges.js", "test:production:srcCoverage": "cd generated/cov-env/ && c8 ava", - "test:production:testMinified": "cd generated/npm/ && cp -rf ../minified/* ./ && ava", + "test:production:testMinified": "cd generated/npm/ && cp -rf ../minified/* ./ && ava", "test:production:clean": "rm -rf generated/npm/*.test.mjs generated/npm/node_modules" }, "dependencies": { + "@thi.ng/base-n": "^0.1.6", + "b58": "^4.0.3", + "base-x": "^3.0.8", + "bs58": "^4.0.1", + "latinize-to-ascii": "^0.5.2", "scrypt-async-modern": "^3.0.12", - "tweetnacl": "^1.0.3", - "latinize-to-ascii": "^0.5.2" + "tweetnacl": "^1.0.3" }, "devDependencies": { "@jscpd/badge-reporter": "^3.3.23", @@ -58,31 +63,31 @@ "ecma-nacl": "^2.5.0", "codehawk-cli": "^6.0.3" }, - "ava": {}, - "c8": { - "all": true, - "branches": 0.080, - "lines": 0.080, - "functions": 0.080, - "statements": 0.080, - "report-dir": "../coverage", - "temp-dir": "../nyc_output.temp", - "reporter": [ - "text-summary", - "html" - ] - }, + "ava": {}, + "c8": { + "all": true, + "branches": 0.08, + "lines": 0.08, + "functions": 0.08, + "statements": 0.08, + "report-dir": "../coverage", + "temp-dir": "../nyc_output.temp", + "reporter": [ + "text-summary", + "html" + ] + }, "xo": { "rules": { "curly": 0, - "unicorn/no-reduce": 0, - "unicorn/no-array-reduce": 0, - "unicorn/no-array-for-each": 0, - "unicorn/prefer-string-slice": 0, - "unicorn/prevent-abbreviations": 0, - "unicorn/number-literal-case": 0, - "unicorn/no-array-callback-reference": 0, - "guard-for-in": 0 + "unicorn/no-reduce": 0, + "unicorn/no-array-reduce": 0, + "unicorn/no-array-for-each": 0, + "unicorn/prefer-string-slice": 0, + "unicorn/prevent-abbreviations": 0, + "unicorn/number-literal-case": 0, + "unicorn/no-array-callback-reference": 0, + "guard-for-in": 0 } }, "maintainabilityThreshold": { diff --git a/src/all.mjs b/src/all.mjs index 5e7b608d611b92edc6ab45c512d4a8f946048ea5..b63309923ed3347382789537f4dbc56695a65ed9 100644 --- a/src/all.mjs +++ b/src/all.mjs @@ -1,4 +1,5 @@ +import * as basex from './basex.mjs'; import * as crypto from './crypto.mjs'; import * as dictionaryBuilder from './dictionary-builder.mjs'; -export {crypto, dictionaryBuilder}; +export {basex, crypto, dictionaryBuilder}; diff --git a/src/basex.mjs b/src/basex.mjs new file mode 100644 index 0000000000000000000000000000000000000000..d09e9e8c1b9fc54da1a7b3a12b8cf4b32d75c09f --- /dev/null +++ b/src/basex.mjs @@ -0,0 +1,70 @@ +// Inspired by bs58, base-x then @thi.ng/base-n module +const B58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; +export const b58 = basex(B58_ALPHABET); +export const b16 = basex('0123456789abcdef'); + +export default function basex(ALPHABET) { + const ALPHABET_MAP = {}; + const BASE = ALPHABET.length; + const LEADER = ALPHABET.charAt(0); + // Pre-compute lookup table + for (let z = 0; z < ALPHABET.length; z++) { + const x = ALPHABET.charAt(z); + if (ALPHABET_MAP[x] !== undefined) throw new TypeError(x + ' is ambiguous'); + ALPHABET_MAP[x] = z; + } + + function encode(source) { + if (source.length === 0) return ''; + const digits = [0]; + for (let carry of source) { + for (let j = 0; j < digits.length; ++j) { + carry += digits[j] << 8; + digits[j] = carry % BASE; + carry = (carry / BASE) | 0; // eslint-disable-line unicorn/prefer-math-trunc + } + + while (carry > 0) { + digits.push(carry % BASE); + carry = (carry / BASE) | 0; // eslint-disable-line unicorn/prefer-math-trunc + } + } + + let string = ''; + for (let k = 0; source[k] === 0 && k < source.length - 1; ++k) string += LEADER; // Deal with leading zeros + for (let q = digits.length - 1; q >= 0; --q) string += ALPHABET[digits[q]]; // Convert digits to a string + return string; + } + + function decodeUnsafe(string) { + if (typeof string !== 'string') throw new TypeError('Expected String'); + if (string.length === 0) return new Uint8Array(0); + const bytes = [0]; + for (const chr of string) { + const value = ALPHABET_MAP[chr]; + if (value === undefined) return; + let carry = value; + for (let j = 0; j < bytes.length; ++j) { + carry += bytes[j] * BASE; + bytes[j] = carry & 0xff; + carry >>= 8; + } + + while (carry > 0) { + bytes.push(carry & 0xff); + carry >>= 8; + } + } + + for (let k = 0; string[k] === LEADER && k < string.length - 1; ++k) bytes.push(0); // Deal with leading zeros + return new Uint8Array(bytes.reverse()); + } + + function decode(string) { + const buffer = decodeUnsafe(string); + if (buffer) return buffer; + throw new Error('Non-base' + BASE + ' character'); + } + + return {encode, decodeUnsafe, decode}; +} diff --git a/src/basex.test.mjs b/src/basex.test.mjs new file mode 100644 index 0000000000000000000000000000000000000000..9971b3e9b0c072f5db4f82b41ea0686d6e369502 --- /dev/null +++ b/src/basex.test.mjs @@ -0,0 +1,9 @@ +import test from 'ava'; +import * as app from './basex.mjs'; + +// Base58 +const pubKey = 'AoxVA41dGL2s4ogMNdbCw3FFYjFo5FPK36LuiW1tjGbG'; + +test('b58 should decode/encode well', t => { + t.is(app.b58.encode(app.b58.decode(pubKey)), pubKey); +}); diff --git a/src/crypto.mjs b/src/crypto.mjs index 9f2cff1091d2d0bf6985407fc82167609a1690d0..f75a50b197414407fd60bd01fb74e4011d89ad3f 100644 --- a/src/crypto.mjs +++ b/src/crypto.mjs @@ -1,7 +1,8 @@ -export {b58, b16, saltPass2seed, seed2keyPair, idSecPass2rawAll, raw2b58, idSecPass2cleanKeys}; +export {b58, saltPass2seed, seed2keyPair, idSecPass2rawAll, raw2b58, idSecPass2cleanKeys}; // Alt deps : import {generate_keypair} from "ecma-nacl/build/lib/signing/sign.js"; // Alt deps : import scrypt from "ecma-nacl/build/lib/scrypt/scrypt.js"; import nacl from '../generated/vendors/nacl.mjs'; +import {b58} from './basex.mjs'; const generateKeypair = nacl.sign.keyPair.fromSeed; import scrypt from '../generated/vendors/scrypt.mjs'; @@ -41,74 +42,3 @@ async function saltPass2seed(idSec, pass) { }; return scrypt(pass.normalize('NFKC'), idSec.normalize('NFKC'), options); } - -// Inspired by bs58 and base-x module -const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; -const b58 = basex(ALPHABET); -const b16 = basex('0123456789abcdef'); - -function basex(ALPHABET) { - const ALPHABET_MAP = {}; - const BASE = ALPHABET.length; - const LEADER = ALPHABET.charAt(0); - // Pre-compute lookup table - for (let z = 0; z < ALPHABET.length; z++) { - const x = ALPHABET.charAt(z); - if (ALPHABET_MAP[x] !== undefined) throw new TypeError(x + ' is ambiguous'); - ALPHABET_MAP[x] = z; - } - - function encode(source) { - if (source.length === 0) return ''; - const digits = [0]; - for (let carry of source) { - for (let j = 0; j < digits.length; ++j) { - carry += digits[j] << 8; - digits[j] = carry % BASE; - carry = (carry / BASE) | 0; // eslint-disable-line unicorn/prefer-math-trunc - } - - while (carry > 0) { - digits.push(carry % BASE); - carry = (carry / BASE) | 0; // eslint-disable-line unicorn/prefer-math-trunc - } - } - - let string = ''; - for (let k = 0; source[k] === 0 && k < source.length - 1; ++k) string += LEADER; // Deal with leading zeros - for (let q = digits.length - 1; q >= 0; --q) string += ALPHABET[digits[q]]; // Convert digits to a string - return string; - } - - function decodeUnsafe(string) { - if (typeof string !== 'string') throw new TypeError('Expected String'); - if (string.length === 0) return new Uint8Array(0); - const bytes = [0]; - for (const chr of string) { - const value = ALPHABET_MAP[chr]; - if (value === undefined) return; - let carry = value; - for (let j = 0; j < bytes.length; ++j) { - carry += bytes[j] * BASE; - bytes[j] = carry & 0xff; - carry >>= 8; - } - - while (carry > 0) { - bytes.push(carry & 0xff); - carry >>= 8; - } - } - - for (let k = 0; string[k] === LEADER && k < string.length - 1; ++k) bytes.push(0); // Deal with leading zeros - return new Uint8Array(bytes.reverse()); - } - - function decode(string) { - const buffer = decodeUnsafe(string); - if (buffer) return buffer; - throw new Error('Non-base' + BASE + ' character'); - } - - return {encode, decodeUnsafe, decode}; -}