From 951bac92782ffb858e33046ce2eaff8c93f88a3a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Miro=20Mets=C3=A4nheimo?= Date: Wed, 27 May 2020 23:03:12 +0300 Subject: [PATCH] Version 1.2.1 * Fixed an issue where address importer threw an error on mnemonic words that had a checksum hex value with a leading zero * Added the possibility to validate mnemonic words and nano addresses --- README.md | 17 +++++++++++++++-- index.ts | 38 ++++++++++++++++++++++++++++++-------- lib/address-importer.ts | 24 +++++++++++++++++------- lib/bip39-mnemonic.ts | 14 +++++++------- lib/nano-address.ts | 24 +++++------------------- package-lock.json | 2 +- package.json | 2 +- test/test.js | 6 +++++- 8 files changed, 81 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index d5c380d..b78e481 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ The toolkit supports creating and importing wallets and signing blocks on-device * Runs in all web browsers and mobile frameworks built with Javascript * Convert Nano units * Sign any strings with the private key, for example using a password for the user created from the user ID. +* Validate addresses and mnemonic words --- @@ -189,15 +190,27 @@ For example implementing client side login with the password being the user's e- ```javascript import { tools } from 'nanocurrency-web' -const privateKey = '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3'; +const privateKey = '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3' const signed = tools.sign(privateKey, 'foo@bar.com') ``` +#### Validating values + +```javascript +import { tools } from 'nanocurrency-web' + +// Validate Nano address +const valid = tools.validateAddress('nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d') + +// Validate mnemonic words +const valid = tools.validateMnemonic('edge defense waste choose enrich upon flee junk siren film clown finish luggage leader kid quick brick print evidence swap drill paddle truly occur') +``` + ### In web ```html - + diff --git a/index.ts b/index.ts index 58d294e..0e16249 100644 --- a/index.ts +++ b/index.ts @@ -1,14 +1,18 @@ +import BigNumber from 'bignumber.js' + import AddressGenerator from './lib/address-generator' import AddressImporter, { Account, Wallet } from './lib/address-importer' -import BlockSigner, { SendBlock, ReceiveBlock, RepresentativeBlock, SignedBlock } from './lib/block-signer' -import BigNumber from 'bignumber.js' +import BlockSigner, { ReceiveBlock, RepresentativeBlock, SendBlock, SignedBlock } from './lib/block-signer' +import NanoAddress from './lib/nano-address' import NanoConverter from './lib/nano-converter' import Signer from './lib/signer' import Convert from './lib/util/convert' +const nanoAddress = new NanoAddress() const generator = new AddressGenerator() const importer = new AddressImporter() -const signer = new Signer(); +const signer = new Signer() + const wallet = { /** @@ -74,17 +78,17 @@ const wallet = { /** * Import Nano cryptocurrency accounts from a legacy hex seed - * + * * This function imports a wallet from a seed. The private key is derived from the seed using * simply a blake2b hash function. The public key is derived from the private key using the ed25519 curve * algorithm. - * + * * The Nano address is derived from the public key using standard Nano encoding. * The address is prefixed with 'nano_'. - * + * * @param {string} seed - The seed * @returns the wallet derived from the seed (seed, account) - * + * */ fromLegacySeed: (seed: string): Wallet => { return importer.fromLegacySeed(seed); @@ -214,7 +218,7 @@ const tools = { /** * Sign any strings with the user's private key - * + * * @param {string} privateKey The private key to sign with * @param {...string} input Data to sign */ @@ -223,6 +227,24 @@ const tools = { return signer.sign(privateKey, ...data); }, + /** + * Validate a Nano address + * + * @param {string} input The address to validate + */ + validateAddress: (input: string): boolean => { + return nanoAddress.validateNanoAddress(input); + }, + + /** + * Validate mnemonic words + * + * @param {string} input The address to validate + */ + validateMnemonic: (input: string): boolean => { + return importer.validateMnemonic(input); + }, + } export { diff --git a/lib/address-importer.ts b/lib/address-importer.ts index 291538d..2a3a1f1 100644 --- a/lib/address-importer.ts +++ b/lib/address-importer.ts @@ -9,8 +9,8 @@ export default class AddressImporter { /** * Import a wallet using a mnemonic phrase - * - * @param {string} mnemonic - The mnemonic words to import the wallet from + * + * @param {string} mnemonic - The mnemonic words to import the wallet from * @param {string} [seedPassword] - (Optional) The password to use to secure the mnemonic * @returns {Wallet} - The wallet derived from the mnemonic phrase */ @@ -24,10 +24,20 @@ export default class AddressImporter { return this.nano(seed, 0, 0, mnemonic) } + /** + * Validate mnemonic words + * + * @param mnemonic {string} mnemonic - The mnemonic words to validate + */ + validateMnemonic(mnemonic: string): boolean { + const bip39 = new Bip39Mnemonic() + return bip39.validateMnemonic(mnemonic); + } + /** * Import a wallet using a seed - * - * @param {string} seed - The seed to import the wallet from + * + * @param {string} seed - The seed to import the wallet from * @param {number} [from] - (Optional) The start index of the private keys to derive from * @param {number} [to] - (Optional) The end index of the private keys to derive to * @returns {Wallet} The wallet derived from the mnemonic phrase @@ -42,11 +52,11 @@ export default class AddressImporter { return this.nano(seed, from, to, undefined) } - + /** * Import a wallet using a legacy seed - * + * * @param {string} seed - The seed to import the wallet from * @param {number} [from] - (Optional) The start index of the private keys to derive from * @param {number} [to] - (Optional) The end index of the private keys to derive to @@ -82,7 +92,7 @@ export default class AddressImporter { /** * Derives the private keys - * + * * @param {string} seed - The seed to use for private key derivation * @param {number} from - The start index of private keys to derive from * @param {number} to - The end index of private keys to derive to diff --git a/lib/bip39-mnemonic.ts b/lib/bip39-mnemonic.ts index 6adf533..ae1053e 100644 --- a/lib/bip39-mnemonic.ts +++ b/lib/bip39-mnemonic.ts @@ -1,21 +1,21 @@ +//@ts-ignore +import { PBKDF2, SHA256, algo, enc, lib } from 'crypto-js' + import Convert from './util/convert' import Util from './util/util' import words from './words' -//@ts-ignore -import { algo, enc, lib, PBKDF2, SHA256 } from 'crypto-js' - export default class Bip39Mnemonic { password: string - constructor(password: string) { + constructor(password?: string) { this.password = password } /** * Creates a new wallet - * + * * @param {string} [entropy] - (Optional) the entropy to use instead of generating * @returns {MnemonicSeed} The mnemonic phrase and a seed derived from the (generated) entropy */ @@ -53,7 +53,7 @@ export default class Bip39Mnemonic { /** * Validates a mnemonic phrase - * + * * @param {string} mnemonic - The mnemonic phrase to validate * @returns {boolean} Is the mnemonic phrase valid */ @@ -84,7 +84,7 @@ export default class Bip39Mnemonic { const newChecksum = this.calculateChecksum(entropyHex) const inputChecksum = Convert.binaryToHexString(checksumBits) - if (newChecksum != inputChecksum) { + if (parseInt(newChecksum, 16) != parseInt(inputChecksum, 16)) { return false } diff --git a/lib/nano-address.ts b/lib/nano-address.ts index 8057380..42d2820 100644 --- a/lib/nano-address.ts +++ b/lib/nano-address.ts @@ -1,8 +1,8 @@ -import Convert from './util/convert' - //@ts-ignore import { blake2b } from 'blakejs' +import Convert from './util/convert' + export default class NanoAddress { readonly alphabet = '13456789abcdefghijkmnopqrstuwxyz' @@ -82,43 +82,29 @@ export default class NanoAddress { * @param {string} address Nano address */ validateNanoAddress = (address: string): boolean => { - /** Ensure the address is provided */ if (address === undefined) { throw Error('Address must be defined.') } - /** Ensure the address is a string */ if (typeof address !== 'string') { throw TypeError('Address must be a string.') } - /** The array of allowed prefixes */ const allowedPrefixes: string[] = ['nano', 'xrb'] - - /** The regex pattern for validating the address */ const pattern = new RegExp( `^(${allowedPrefixes.join('|')})_[13]{1}[13456789abcdefghijkmnopqrstuwxyz]{59}$`, ) - /** Validate the syntax of the address */ - if (!pattern.test(address)) return false + if (!pattern.test(address)) { + return false + } - /** The expected checksum as a base32-encoded string */ const expectedChecksum = address.slice(-8) - - /** The public key as a base32-encoded string */ const publicKey = address.slice(address.indexOf('_') + 1, -8) - - /** The public key as an array buffer */ const publicKeyBuffer = this.decodeNanoBase32(publicKey) - - /** The actual checksum as an array buffer */ const actualChecksumBuffer = blake2b(publicKeyBuffer, null, 5).reverse() - - /** The actual checksum as a base32-encoded string */ const actualChecksum = this.encodeNanoBase32(actualChecksumBuffer) - /** Validate the provided checksum against the derived checksum */ return expectedChecksum === actualChecksum } diff --git a/package-lock.json b/package-lock.json index 72a9fbb..363a605 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "nanocurrency-web", - "version": "1.2.0", + "version": "1.2.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index c3b1482..3bffa75 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nanocurrency-web", - "version": "1.2.0", + "version": "1.2.1", "description": "Toolkit for Nano cryptocurrency client side offline integrations", "author": "Miro Metsänheimo ", "license": "MIT", diff --git a/test/test.js b/test/test.js index 8658280..3edf7bf 100644 --- a/test/test.js +++ b/test/test.js @@ -57,7 +57,7 @@ describe('generate wallet test', () => { }) // Test vectors from https://docs.nano.org/integration-guides/key-management/ -describe('import wallet with official test vectors test', () => { +describe('import wallet with test vectors test', () => { it('should successfully import a wallet with the official Nano test vectors mnemonic', () => { const result = wallet.fromMnemonic( @@ -73,6 +73,10 @@ describe('import wallet with official test vectors test', () => { expect(result.accounts[0].address).to.equal('nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d') }) + it('should successfully import a wallet with the checksum starting with a zero', () => { + wallet.fromMnemonic('food define cancel major spoon trash cigar basic aim bless wolf win ability seek paddle bench seed century group they mercy address monkey cake') + }) + it('should successfully import a wallet with the official Nano test vectors seed', () => { const result = wallet.fromSeed('0dc285fde768f7ff29b66ce7252d56ed92fe003b605907f7a4f683c3dc8586d34a914d3c71fc099bb38ee4a59e5b081a3497b7a323e90cc68f67b5837690310c') expect(result).to.have.own.property('mnemonic') -- 2.34.1