diff --git a/package.json b/package.json index 0368e40..3677cb0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "nanocurrency", - "description": "A toolkit for the Nano cryptocurrency, allowing you to derive keys, generate seeds, hashes, signatures, proofs of work and blocks.", + "description": + "A toolkit for the Nano cryptocurrency, allowing you to derive keys, generate seeds, hashes, signatures, proofs of work and blocks.", "version": "0.0.0-development", "author": { "name": "Marvin ROGER", @@ -18,7 +19,6 @@ "dependencies": { "bignumber.js": "^7.2.0", "blakejs": "^1.1.0", - "nano-base32": "^1.0.0", "yargs": "^12.0.1" }, "devDependencies": { @@ -46,23 +46,12 @@ "typedoc-plugin-internal-external": "^1.0.10", "typescript": "^2.8.3" }, - "files": [ - "dist/" - ], + "files": ["dist/"], "homepage": "https://github.com/marvinroger/nanocurrency-js", "jest": { - "testPathIgnorePatterns": [ - "/__tests__/data/", - "/node_modules/" - ] + "testPathIgnorePatterns": ["/__tests__/data/", "/node_modules/"] }, - "keywords": [ - "crypto", - "currency", - "nano", - "pow", - "raiblocks" - ], + "keywords": ["crypto", "currency", "nano", "pow", "raiblocks"], "license": "GPL-3.0", "main": "dist/nanocurrency.cjs.js", "module": "dist/nanocurrency.esm.js", @@ -78,7 +67,8 @@ "build:dev": "yarn build:dev:native && yarn build:dev:js", "build:dev:js": "cross-env NODE_ENV=development rollup -c", "build:dev:native": "cross-env EMCC_ARGS=\"\" cross-os build:native__cross", - "build:native__common": "cross-var docker run --rm -v $PWD:/src trzeci/emscripten emcc -o native.js $EMCC_ARGS -s WASM=1 -s MODULARIZE=1 -s SINGLE_FILE=1 -s \"EXTRA_EXPORTED_RUNTIME_METHODS=[\\\"cwrap\\\"]\" src/native/functions.c src/native/blake2/ref/blake2b-ref.c", + "build:native__common": + "cross-var docker run --rm -v $PWD:/src trzeci/emscripten emcc -o native.js $EMCC_ARGS -s WASM=1 -s MODULARIZE=1 -s SINGLE_FILE=1 -s \"EXTRA_EXPORTED_RUNTIME_METHODS=[\\\"cwrap\\\"]\" src/native/functions.c src/native/blake2/ref/blake2b-ref.c", "build:native__cross": { "darwin": "cross-env PWD=\"$(pwd)\" yarn build:native__common", "linux": "cross-env PWD=\"$(pwd)\" yarn build:native__common", @@ -86,7 +76,8 @@ }, "build:prod": "yarn build:prod:native && yarn build:prod:js", "build:prod:js": "cross-env NODE_ENV=production rollup -c", - "build:prod:native": "cross-env EMCC_ARGS=\"-O3 --closure 1 --llvm-lto 3\" cross-os build:native__cross", + "build:prod:native": + "cross-env EMCC_ARGS=\"-O3 --closure 1 --llvm-lto 3\" cross-os build:native__cross", "generate-docs": "typedoc src/index.ts", "format": "prettier --write \"{src,__tests__}/**/*.{ts,js}\"", "lint": "tslint --project . \"src/**/*.ts\"", diff --git a/src/keys.ts b/src/keys.ts index 1bff087..5ff1840 100644 --- a/src/keys.ts +++ b/src/keys.ts @@ -4,12 +4,12 @@ * Licensed under GPL-3.0 (https://git.io/vAZsK) */ import { blake2b, blake2bFinal, blake2bInit, blake2bUpdate } from 'blakejs'; -import nanoBase32 from 'nano-base32'; import { checkKey, checkSeed } from './check'; import { derivePublicFromSecret } from './nacl'; import { byteArrayToHex, getRandomBytes, hexToByteArray } from './utils'; import { parseAddress } from './parse'; +import { encodeNanoBase32 } from './nano-base32'; /** * Generate a cryptographically secure seed. @@ -104,11 +104,11 @@ export function deriveAddress(publicKey: string, params: DeriveAddressParams = { let prefix = 'xrb_'; if (params.useNanoPrefix === true) prefix = 'nano_'; - const encodedPublicKey = nanoBase32.encode(paddedPublicKeyBytes); + const encodedPublicKey = encodeNanoBase32(paddedPublicKeyBytes); const checksum = blake2b(publicKeyBytes, null, 5).reverse(); - const encodedChecksum = nanoBase32.encode(checksum); + const encodedChecksum = encodeNanoBase32(checksum); return prefix + encodedPublicKey + encodedChecksum; } diff --git a/src/nano-base32.ts b/src/nano-base32.ts new file mode 100644 index 0000000..8657d3b --- /dev/null +++ b/src/nano-base32.ts @@ -0,0 +1,101 @@ +/* +MIT License + +Copyright (c) 2018 Gray Olson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +const alphabet = '13456789abcdefghijkmnopqrstuwxyz'; + +/** + * Encode provided Uint8Array using the Nano-specific Base-32 implementeation. + * @param view Input buffer formatted as a Uint8Array + * @returns + */ +export function encodeNanoBase32(view: Uint8Array) { + const length = view.length; + const leftover = (length * 8) % 5; + const offset = leftover === 0 ? 0 : 5 - leftover; + + let value = 0; + let output = ''; + let bits = 0; + + for (let i = 0; i < length; i++) { + value = (value << 8) | view[i]; + bits += 8; + + while (bits >= 5) { + output += alphabet[(value >>> (bits + offset - 5)) & 31]; + bits -= 5; + } + } + + if (bits > 0) { + output += alphabet[(value << (5 - (bits + offset))) & 31]; + } + + return output; +} + +function readChar(char: string) { + const idx = alphabet.indexOf(char); + + if (idx === -1) { + throw new Error('Invalid character found: ' + char); + } + + return idx; +} + +/** + * Decodes a Nano-implementation Base32 encoded string into a Uint8Array + * @param input A Nano-Base32 encoded string + * @returns + */ +export function decodeNanoBase32(input: string) { + const length = input.length; + const leftover = (length * 5) % 8; + const offset = leftover === 0 ? 0 : 8 - leftover; + + let bits = 0; + let value = 0; + + let index = 0; + let output = new Uint8Array(Math.ceil(length * 5 / 8)); + + for (let i = 0; i < length; i++) { + value = (value << 5) | readChar(input[i]); + bits += 5; + + if (bits >= 8) { + output[index++] = (value >>> (bits + offset - 8)) & 255; + bits -= 8; + } + } + if (bits > 0) { + output[index++] = (value << (bits + offset - 8)) & 255; + } + + if (leftover !== 0) { + output = output.slice(1); + } + return output; +} diff --git a/src/parse.ts b/src/parse.ts index 1e24cb2..c8878e4 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -4,10 +4,10 @@ * Licensed under GPL-3.0 (https://git.io/vAZsK) */ import { blake2b } from 'blakejs'; -import nanoBase32 from 'nano-base32'; import { compareArrays } from './utils'; import { checkString } from './check'; +import { decodeNanoBase32 } from './nano-base32'; /** @hidden */ export interface ParseAddressResult { @@ -30,8 +30,8 @@ export function parseAddress(address: any): ParseAddressResult { prefixLength = 5; } - const publicKeyBytes = nanoBase32.decode(address.substr(prefixLength, 52)); - const checksumBytes = nanoBase32.decode(address.substr(prefixLength + 52)); + const publicKeyBytes = decodeNanoBase32(address.substr(prefixLength, 52)); + const checksumBytes = decodeNanoBase32(address.substr(prefixLength + 52)); const computedChecksumBytes = blake2b(publicKeyBytes, null, 5).reverse(); diff --git a/yarn.lock b/yarn.lock index aea846b..1ef3beb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3879,10 +3879,6 @@ nan@^2.6.2, nan@^2.9.2: version "2.10.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" -nano-base32@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/nano-base32/-/nano-base32-1.0.1.tgz#ba548c879efcfb90da1c4d9e097db4a46c9255ef" - nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"