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:
parent
d0b804a3b9
commit
951bac9278
8 changed files with 81 additions and 46 deletions
17
README.md
17
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
|
||||
<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>
|
||||
|
|
|
|||
38
index.ts
38
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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
2
package-lock.json
generated
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "nanocurrency-web",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue