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
This commit is contained in:
Miro Metsänheimo 2020-05-27 23:03:12 +03:00
commit 951bac9278
8 changed files with 81 additions and 46 deletions

View file

@ -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
<script src="https://unpkg.com/nanocurrency-web@1.2.0" type="text/javascript"></script>
<script src="https://unpkg.com/nanocurrency-web@1.2.1" type="text/javascript"></script>
<script type="text/javascript">
NanocurrencyWeb.wallet.generate(...);
</script>

View file

@ -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 {

View file

@ -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

View file

@ -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
}

View file

@ -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
}

2
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "nanocurrency-web",
"version": "1.2.0",
"version": "1.2.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View file

@ -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 <miro@metsanheimo.fi>",
"license": "MIT",

View file

@ -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')