import { AddressGenerator } from './lib/address-generator'
-import { AddressImporter } from './lib/address-importer'
-import BlockSigner, { SendBlock, ReceiveBlock } from './lib/block-signer'
+import { AddressImporter, Account, Wallet } from './lib/address-importer'
+import BlockSigner, { TransactionBlock, RepresentativeBlock, SignedBlock } from './lib/block-signer'
const generator = new AddressGenerator()
const importer = new AddressImporter()
* The Nano address is derived from the public key using standard Nano encoding.
* The address is prefixed with 'nano_'.
*
- * Generation uses CryptoJS to generate random entropy. You can give your own entropy
+ * Generation uses CryptoJS to generate random entropy by default. You can give your own entropy
* as a parameter and it will be used instead.
*
* An optional seed password can be used to encrypt the mnemonic phrase so the seed
* cannot be derived correctly without the password. Recovering the password is not possible.
*
- * @param {string} [entropy] Optional entropy to be used instead of the default
+ * @param {string} [entropy] Optional 64 byte hexadecimal string entropy to be used instead of the default
* @param {string} [seedPassword] Optional seed password
* @returns the generated mnemonic, seed and account
*/
- generate: (entropy?: string, seedPassword?: string) => {
+ generate: (entropy?: string, seedPassword?: string): Wallet => {
return generator.generateWallet(entropy, seedPassword)
},
* @throws Throws an error if the mnemonic phrase doesn't pass validations
* @returns the imported mnemonic, seed and account
*/
- fromMnemonic: (mnemonic: string, seedPassword?: string) => {
+ fromMnemonic: (mnemonic: string, seedPassword?: string): Wallet => {
return importer.fromMnemonic(mnemonic, seedPassword)
},
* @param {string} seed The seed
* @returns the importes seed and account
*/
- fromSeed: (seed: string) => {
+ fromSeed: (seed: string): Wallet => {
return importer.fromSeed(seed)
},
* @param {number} from The start index
* @param {number} to The end index
*/
- accounts: (seed: string, from: number, to: number) => {
+ accounts: (seed: string, from: number, to: number): Account[] => {
return importer.fromSeed(seed, from, to).accounts
},
/**
* Sign a send block with the input parameters
*
+ * For a receive block, put your own address to the 'toAddress' property.
+ * All the NANO amounts should be input in RAW format. The addresses should be
+ * valid Nano addresses. Fetch the current balance, frontier (previous block) and
+ * representative address from the blockchain and generate work for the signature.
+ *
+ * The return value of this function is ready to be published to the blockchain.
+ *
+ * NOTICE: Always fetch up-to-date account info from the blockchain
+ * before signing the block
+ *
* @param {SendBlock} data The data for the block
* @param {string} privateKey Private key to sign the block
*/
- send: (data: SendBlock, privateKey: string) => {
- return blockSigner.send(data, privateKey)
+ sign: (data: TransactionBlock, privateKey: string): SignedBlock => {
+ return blockSigner.sign(data, privateKey)
},
/**
- * Sign a receive block with the input parameters
+ * Sign a representative change block with the input parameters
*
- * @param {SendBlock} data The data for the block
+ * For a change block, put your own address to the 'address' property.
+ * All the NANO amounts should be input in RAW format. The addresses should be
+ * valid Nano addresses. Fetch the current balance, previous block from the
+ * blockchain and generate work for the signature. Set the new representative address
+ * as the representative.
+ *
+ * NOTICE: Always fetch up-to-date account info from the blockchain
+ * before signing the block
+ *
+ * @param {RepresentativeBlock} data The data for the block
* @param {string} privateKey Private key to sign the block
+ *
*/
- receive: (data: ReceiveBlock, privateKey: string) => {
- return blockSigner.receive(data, privateKey)
- },
+ representative: (data: RepresentativeBlock, privateKey: string): SignedBlock => {
+ const block: TransactionBlock = {
+ ...data,
+ fromAddress: data.address,
+ amountRaw: '0',
+ toAddress: 'nano_1111111111111111111111111111111111111111111111111111hifc8npp' // Burn address
+ }
- // TODO: change representative block
+ return blockSigner.sign(block, privateKey)
+ },
}
-export default {
+export {
wallet,
block,
}
import Bip39Mnemonic from './bip39-mnemonic'
import { Ed25519 } from './ed25519'
import { NanoAddress } from './nano-address'
+import { Wallet } from './address-importer'
export class AddressGenerator {
*
* @param {String} seedPassword Password for the seed
*/
- generateWallet(entropy = '', seedPassword: string = '') {
+ generateWallet(entropy = '', seedPassword: string = ''): Wallet {
const bip39 = new Bip39Mnemonic(seedPassword)
const wallet = bip39.createWallet(entropy)
export class AddressImporter {
- fromMnemonic(mnemonic: string, seedPassword = '') {
+ fromMnemonic(mnemonic: string, seedPassword = ''): Wallet {
const bip39 = new Bip39Mnemonic(seedPassword)
if (!bip39.validateMnemonic(mnemonic)) {
- throw 'Invalid mnemonic phrase'
+ throw new Error('Invalid mnemonic phrase')
}
const seed = bip39.mnemonicToSeed(mnemonic)
return this.nano(seed, 0, 0, mnemonic)
}
- fromSeed(seed: string, from = 0, to = 0) {
+ fromSeed(seed: string, from = 0, to = 0): Wallet {
+ if (seed.length !== 128) {
+ throw new Error('Invalid seed length, must be a 128 byte hexadecimal string')
+ }
+ if (!/^[0-9a-f]+$/i.test(seed)) {
+ throw new Error('Seed is not a valid hexadecimal string')
+ }
+
return this.nano(seed, from, to, undefined)
}
* Generates the wallet
* @param {String} seedPassword Password for the seed
*/
- private nano(seed: string, from: number, to: number, mnemonic?: string) {
+ private nano(seed: string, from: number, to: number, mnemonic?: string): Wallet {
const accounts = []
for (let i = from; i <= to; i++) {
}
}
+
+export interface Wallet {
+ mnemonic: string
+ seed: string
+ accounts: Account[]
+}
+
+export interface Account {
+ accountIndex: number
+ privateKey: string
+ publicKey: string
+ address: string
+}
this.seed = seed
}
- derivePath = () => {
+ derivePath = (): Chain => {
const { key, chainCode } = this.getKeyFromSeed()
const segments = this.path
.split('/')
)
}
- private getKeyFromSeed = () => {
+ private getKeyFromSeed = (): Chain => {
return this.derive(
CryptoJS.enc.Hex.parse(this.seed),
CryptoJS.enc.Utf8.parse(ED25519_CURVE))
}
- private CKDPriv = ({ key, chainCode }: { key: string, chainCode: string }, index: number) => {
+ private CKDPriv = ({ key, chainCode }: Chain, index: number) => {
const ib = []
ib.push((index >> 24) & 0xff)
ib.push((index >> 16) & 0xff)
CryptoJS.enc.Hex.parse(chainCode))
}
- private derive = (data: string, base: string) => {
+ private derive = (data: string, base: string): Chain => {
const hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA512, base)
const I = hmac.update(data).finalize().toString()
const IL = I.slice(0, I.length / 2)
}
}
+
+export interface Chain {
+ key: string
+ chainCode: string
+}
}
createWallet = (entropy: string): { mnemonic: string, seed: string } => {
- if (entropy.length !== 32) {
- throw 'Invalid entropy length'
+ if (entropy) {
+ if (entropy.length !== 64) {
+ throw new Error('Invalid entropy length, must be a 64 byte hexadecimal string')
+ }
+ if (!/^[0-9a-f]+$/i.test(entropy)) {
+ throw new Error('Entopy is not a valid hexadecimal string')
+ }
}
if (!entropy) {
preamble = 0x6.toString().padStart(64, '0')
- send(data: SendBlock, privateKey: string) {
+ sign(data: TransactionBlock, privateKey: string): SignedBlock {
+ if (!privateKey) {
+ throw new Error('Please input the private key to sign the block')
+ }
+
const balance = NanoConverter.convert(data.walletBalanceRaw, 'RAW', 'NANO')
- const newBalance = new BigNumber(balance).minus(new BigNumber(data.amount))
+ const amount = NanoConverter.convert(data.amountRaw, 'RAW', 'NANO')
+ const newBalance = new BigNumber(balance).minus(new BigNumber(amount))
const rawBalance = NanoConverter.convert(newBalance, 'NANO', 'RAW')
const hexBalance = Convert.dec2hex(rawBalance, 16).toUpperCase()
const account = this.nanoAddressToHexString(data.fromAddress)
}
}
- receive(data: ReceiveBlock, privateKey: string) {
- let balance = '0'
- if (data.walletBalanceRaw != '0') {
- balance = NanoConverter.convert(data.walletBalanceRaw, 'RAW', 'NANO')
- }
-
- const amountNano = NanoConverter.convert(data.amount, 'RAW', 'NANO')
- const newBalance = new BigNumber(balance).plus(new BigNumber(amountNano))
- const rawBalance = NanoConverter.convert(newBalance, 'NANO', 'RAW')
- const hexBalance = Convert.dec2hex(rawBalance, 16).toUpperCase()
- const account = this.nanoAddressToHexString(data.walletAddress)
- const representative = this.nanoAddressToHexString(data.representativeAddress)
- const link = data.hash
-
- const signatureBytes = this.ed25519.sign(
- this.generateHash(this.preamble, account, data.frontier, representative, hexBalance, link),
- Convert.hex2ab(privateKey))
-
- return {
- type: 'state',
- account: data.walletAddress,
- previous: data.frontier,
- representative: data.representativeAddress,
- balance: rawBalance,
- link,
- signature: Convert.ab2hex(signatureBytes),
- work: data.work,
- }
- }
-
private generateHash(preamble: string, account: string, previous: string, representative: string, balance: string, link: string) {
const ctx = blake.blake2bInit(32, undefined)
blake.blake2bUpdate(ctx, Convert.hex2ab(preamble))
const key = Convert.ab2hex(keyBytes).toUpperCase()
return key
}
- throw 'Checksum mismatch'
+ throw new Error('Checksum mismatch')
} else {
- throw 'Illegal characters'
+ throw new Error('Illegal characters')
}
}
}
-export interface SendBlock {
+export interface TransactionBlock {
walletBalanceRaw: string
fromAddress: string
toAddress: string
representativeAddress: string
frontier: string
- amount: string
+ amountRaw: string
work: string
}
-export interface ReceiveBlock {
+export interface RepresentativeBlock {
walletBalanceRaw: string
- walletAddress: string
+ address: string
representativeAddress: string
frontier: string
- hash: string
- amount: string
+ work: string
+}
+
+export interface SignedBlock {
+ type: 'state'
+ account: string
+ previous: string
+ representative: string
+ balance: string
+ link: string
+ signature: string
work: string
}
* @param {String} seed A 32 byte cryptographic secure random hexadecimal string. This is basically the secret key
* @param {Object} Returns sk (Secret key) and pk (Public key) as 32 byte hexadecimal strings
*/
- generateKeys(seed: string): { privateKey: string, publicKey: string } {
+ generateKeys(seed: string): KeyPair {
const pk = new Uint8Array(32)
const p = [this.curve.gf(), this.curve.gf(), this.curve.gf(), this.curve.gf()]
const h = blake
return 0
}
- /**
- * TODO: Replace sha512 with blakejs
- * Verify a message signature
- * @param {Uint8Array} msg Message to be signed as byte array
- * @param {Uint8Array} pk Public key as 32 byte array
- * @param {Uint8Array} sig Signature as 64 byte array
- * @param {Boolean} Returns true if signature is valid
- */
- // verify(msg, pk, sig) {
- // let CURVE = this.curve
- // let p = [CURVE.gf(), CURVE.gf(), CURVE.gf(), CURVE.gf()],
- // q = [CURVE.gf(), CURVE.gf(), CURVE.gf(), CURVE.gf()]
-
- // if (sig.length !== 64) return false
- // if (pk.length !== 32) return false
- // if (CURVE.unpackNeg(q, pk)) return false
-
- // // compute k = SHA512(R || A || M)
- // let k = this.sha512.init().update(sig.subarray(0, 32)).update(pk).digest(msg)
- // this.reduce(k)
- // this.scalarmult(p, q, k)
-
- // let t = new Uint8Array(32)
- // this.scalarbase(q, sig.subarray(32))
- // CURVE.add(p, q)
- // this.pack(t, p)
-
- // return Util.compare(sig.subarray(0, 32), t)
- // }
-
}
+
+export interface KeyPair {
+ privateKey: string
+ publicKey: string
+}
\ No newline at end of file
let s: number
while (decimals.length) {
- const dec = decimals.shift()
- if (!dec) {
- throw 'Invalid decimal'
- }
-
- s = 1 * +dec
+ s = 1 * +decimals.shift()
for (i = 0; s || i < sum.length; i++) {
s += (sum[i] || 0) * 10
sum[i] = s % 16
}
while (sum.length) {
- const dec = sum.pop()
- if (!dec) {
- throw 'Invalid decimal'
- }
-
- hex.push(dec.toString(16))
+ hex.push(sum.pop().toString(16))
}
let joined = hex.join('')
const sk = seed.slice()
const pk = new Uint8Array(32)
if (sk.length !== 32) {
- throw 'Invalid secret key size, expected 32 bytes'
+ throw new Error('Invalid secret key size, expected 32 bytes')
}
sk[0] &= 0xf8
"integrity": "sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ==",
"dev": true
},
+ "ansi-colors": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz",
+ "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==",
+ "dev": true
+ },
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "1.9.3"
+ }
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "requires": {
+ "sprintf-js": "1.0.3"
+ }
+ },
+ "assertion-error": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true
+ },
"bignumber": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/bignumber/-/bignumber-1.1.0.tgz",
"resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.0.tgz",
"integrity": "sha1-ad+S75U6qIylGjLfarHFShVfx6U="
},
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+ "dev": true
+ },
+ "camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true
+ },
+ "chai": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
+ "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
+ "dev": true,
+ "requires": {
+ "assertion-error": "1.1.0",
+ "check-error": "1.0.2",
+ "deep-eql": "3.0.1",
+ "get-func-name": "2.0.0",
+ "pathval": "1.1.0",
+ "type-detect": "4.0.8"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "3.2.1",
+ "escape-string-regexp": "1.0.5",
+ "supports-color": "5.5.0"
+ },
+ "dependencies": {
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "3.0.0"
+ }
+ }
+ }
+ },
+ "check-error": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
+ "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
+ "dev": true
+ },
+ "cliui": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+ "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+ "dev": true,
+ "requires": {
+ "string-width": "3.1.0",
+ "strip-ansi": "5.2.0",
+ "wrap-ansi": "5.1.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "7.0.3",
+ "is-fullwidth-code-point": "2.0.0",
+ "strip-ansi": "5.2.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "4.1.0"
+ }
+ }
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "crypto-js": {
+ "version": "3.1.9-1",
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz",
+ "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg="
+ },
+ "debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.1"
+ }
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+ "dev": true
+ },
+ "deep-eql": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
+ "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
+ "dev": true,
+ "requires": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "define-properties": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+ "dev": true,
+ "requires": {
+ "object-keys": "1.1.1"
+ }
+ },
+ "diff": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+ "dev": true
+ },
+ "emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true
+ },
+ "es-abstract": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.15.0.tgz",
+ "integrity": "sha512-bhkEqWJ2t2lMeaJDuk7okMkJWI/yqgH/EoGwpcvv0XW9RWQsRspI4wt6xuyuvMvvQE3gg/D9HXppgk21w78GyQ==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "1.2.0",
+ "function-bind": "1.1.1",
+ "has": "1.0.3",
+ "has-symbols": "1.0.0",
+ "is-callable": "1.1.4",
+ "is-regex": "1.0.4",
+ "object-inspect": "1.6.0",
+ "object-keys": "1.1.1",
+ "string.prototype.trimleft": "2.1.0",
+ "string.prototype.trimright": "2.1.0"
+ }
+ },
+ "es-to-primitive": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
+ "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
+ "dev": true,
+ "requires": {
+ "is-callable": "1.1.4",
+ "is-date-object": "1.0.1",
+ "is-symbol": "1.0.2"
+ }
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true
+ },
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "3.0.0"
+ }
+ },
+ "flat": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz",
+ "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==",
+ "dev": true,
+ "requires": {
+ "is-buffer": "2.0.4"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true
+ },
+ "get-func-name": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
+ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "1.0.0",
+ "inflight": "1.0.6",
+ "inherits": "2.0.4",
+ "minimatch": "3.0.4",
+ "once": "1.4.0",
+ "path-is-absolute": "1.0.1"
+ }
+ },
+ "growl": {
+ "version": "1.10.5",
+ "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
+ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
+ "dev": true
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "requires": {
+ "function-bind": "1.1.1"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "has-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
+ "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=",
+ "dev": true
+ },
+ "he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "1.4.0",
+ "wrappy": "1.0.2"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "is-buffer": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
+ "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
+ "dev": true
+ },
+ "is-callable": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
+ "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
+ "dev": true
+ },
+ "is-date-object": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
+ "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
+ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
+ "dev": true,
+ "requires": {
+ "has": "1.0.3"
+ }
+ },
+ "is-symbol": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
+ "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "1.0.0"
+ }
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "3.13.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+ "dev": true,
+ "requires": {
+ "argparse": "1.0.10",
+ "esprima": "4.0.1"
+ }
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "requires": {
+ "p-locate": "3.0.0",
+ "path-exists": "3.0.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+ "dev": true
+ },
+ "log-symbols": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
+ "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
+ "dev": true,
+ "requires": {
+ "chalk": "2.4.2"
+ }
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "1.1.11"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+ "dev": true
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "dev": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "mocha": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.1.tgz",
+ "integrity": "sha512-VCcWkLHwk79NYQc8cxhkmI8IigTIhsCwZ6RTxQsqK6go4UvEhzJkYuHm8B2YtlSxcYq2fY+ucr4JBwoD6ci80A==",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "3.2.3",
+ "browser-stdout": "1.3.1",
+ "debug": "3.2.6",
+ "diff": "3.5.0",
+ "escape-string-regexp": "1.0.5",
+ "find-up": "3.0.0",
+ "glob": "7.1.3",
+ "growl": "1.10.5",
+ "he": "1.2.0",
+ "js-yaml": "3.13.1",
+ "log-symbols": "2.2.0",
+ "minimatch": "3.0.4",
+ "mkdirp": "0.5.1",
+ "ms": "2.1.1",
+ "node-environment-flags": "1.0.5",
+ "object.assign": "4.1.0",
+ "strip-json-comments": "2.0.1",
+ "supports-color": "6.0.0",
+ "which": "1.3.1",
+ "wide-align": "1.1.3",
+ "yargs": "13.3.0",
+ "yargs-parser": "13.1.1",
+ "yargs-unparser": "1.6.0"
+ }
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+ "dev": true
+ },
+ "node-environment-flags": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz",
+ "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==",
+ "dev": true,
+ "requires": {
+ "object.getownpropertydescriptors": "2.0.3",
+ "semver": "5.7.1"
+ }
+ },
+ "object-inspect": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz",
+ "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==",
+ "dev": true
+ },
+ "object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true
+ },
+ "object.assign": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
+ "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
+ "dev": true,
+ "requires": {
+ "define-properties": "1.1.3",
+ "function-bind": "1.1.1",
+ "has-symbols": "1.0.0",
+ "object-keys": "1.1.1"
+ }
+ },
+ "object.getownpropertydescriptors": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
+ "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
+ "dev": true,
+ "requires": {
+ "define-properties": "1.1.3",
+ "es-abstract": "1.15.0"
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1.0.2"
+ }
+ },
+ "p-limit": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz",
+ "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==",
+ "dev": true,
+ "requires": {
+ "p-try": "2.2.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "requires": {
+ "p-limit": "2.2.1"
+ }
+ },
+ "p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true
+ },
+ "path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "dev": true
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "pathval": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
+ "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
+ "dev": true
+ },
+ "require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+ "dev": true
+ },
+ "require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "dev": true
+ },
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+ "dev": true
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "dev": true,
+ "requires": {
+ "is-fullwidth-code-point": "2.0.0",
+ "strip-ansi": "4.0.0"
+ }
+ },
+ "string.prototype.trimleft": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz",
+ "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==",
+ "dev": true,
+ "requires": {
+ "define-properties": "1.1.3",
+ "function-bind": "1.1.1"
+ }
+ },
+ "string.prototype.trimright": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz",
+ "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==",
+ "dev": true,
+ "requires": {
+ "define-properties": "1.1.3",
+ "function-bind": "1.1.1"
+ }
+ },
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "3.0.0"
+ }
+ },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz",
+ "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==",
+ "dev": true,
+ "requires": {
+ "has-flag": "3.0.0"
+ }
+ },
+ "type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true
+ },
"typescript": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz",
"integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==",
"dev": true
+ },
+ "which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "requires": {
+ "isexe": "2.0.0"
+ }
+ },
+ "which-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+ "dev": true
+ },
+ "wide-align": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+ "dev": true,
+ "requires": {
+ "string-width": "2.1.1"
+ }
+ },
+ "wrap-ansi": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "3.2.1",
+ "string-width": "3.1.0",
+ "strip-ansi": "5.2.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "7.0.3",
+ "is-fullwidth-code-point": "2.0.0",
+ "strip-ansi": "5.2.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "4.1.0"
+ }
+ }
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "y18n": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
+ "dev": true
+ },
+ "yargs": {
+ "version": "13.3.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz",
+ "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==",
+ "dev": true,
+ "requires": {
+ "cliui": "5.0.0",
+ "find-up": "3.0.0",
+ "get-caller-file": "2.0.5",
+ "require-directory": "2.1.1",
+ "require-main-filename": "2.0.0",
+ "set-blocking": "2.0.0",
+ "string-width": "3.1.0",
+ "which-module": "2.0.0",
+ "y18n": "4.0.0",
+ "yargs-parser": "13.1.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "7.0.3",
+ "is-fullwidth-code-point": "2.0.0",
+ "strip-ansi": "5.2.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "4.1.0"
+ }
+ }
+ }
+ },
+ "yargs-parser": {
+ "version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
+ "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "5.3.1",
+ "decamelize": "1.2.0"
+ }
+ },
+ "yargs-unparser": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz",
+ "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==",
+ "dev": true,
+ "requires": {
+ "flat": "4.1.0",
+ "lodash": "4.17.15",
+ "yargs": "13.3.0"
+ }
}
}
}
"main": "index.js",
"scripts": {
"build": "tsc",
- "test": "echo \"Error: no test specified\" && exit 1"
+ "test": "mocha --reporter spec"
},
"dependencies": {
"bignumber": "1.1.0",
- "blakejs": "1.1.0"
+ "blakejs": "1.1.0",
+ "crypto-js": "3.1.9-1"
},
"devDependencies": {
"@types/bignumber.js": "5.0.0",
"@types/node": "12.7.12",
+ "chai": "4.2.0",
+ "mocha": "6.2.1",
"typescript": "3.6.3"
}
}
--- /dev/null
+'use strict'
+
+const expect = require('chai').expect
+const { wallet, block } = require('../dist/index')
+
+// WARNING: Do not send any funds to the test vectors below
+describe('generate wallet test', () => {
+
+ it('should generate wallet with random entropy', () => {
+ const result = wallet.generate()
+ expect(result).to.have.own.property('mnemonic')
+ expect(result).to.have.own.property('seed')
+ expect(result).to.have.own.property('accounts')
+ })
+
+ it('should generate the correct wallet with the given test vector', () => {
+ const result = wallet.generate('6caf5a42bb8074314aae20295975ece663be7aad945a73613d193b0cc41c7970')
+ expect(result).to.have.own.property('mnemonic')
+ expect(result).to.have.own.property('seed')
+ expect(result).to.have.own.property('accounts')
+ expect(result.mnemonic).to.equal('hole kiss mouse jacket also board click series citizen slight kite smoke desk diary rent mercy inflict antique edge invite slush athlete total brain')
+ expect(result.seed).to.equal('1accdd4c25e06e47310d0c62c290ec166071d024352e003e5366e8ba6ba523f2a0cb34116ac55a238a886778880a9b2a547112fd7cffade81d8d8d084ccb7d36')
+ expect(result.accounts[0].privateKey).to.equal('eb18b748bcc48f824cf8a1fe92f7fc93bfc6f2a1eb9c1d40fa26d335d8a0c30f')
+ expect(result.accounts[0].publicKey).to.equal('a9ef7bbc004813cf75c5fc5c582066182d5c9cffd42eb7eb81cefea8e78c47c5')
+ expect(result.accounts[0].address).to.equal('nano_3chhhgy11k1msxtwdz4wd1i8e83fdkghzo3gpzor5mqyo5mrrjy79zpw1g34')
+ })
+
+ it('should generate the correct wallet with the given test vector and a seed password', () => {
+ // Using the same entropy as before, but a different password
+ const result = wallet.generate('6caf5a42bb8074314aae20295975ece663be7aad945a73613d193b0cc41c7970', 'some password')
+ expect(result).to.have.own.property('mnemonic')
+ expect(result).to.have.own.property('seed')
+ expect(result).to.have.own.property('accounts')
+
+ // Should result in the same mnemonic, but different seed and account
+ expect(result.mnemonic).to.equal('hole kiss mouse jacket also board click series citizen slight kite smoke desk diary rent mercy inflict antique edge invite slush athlete total brain')
+ expect(result.seed).to.not.equal('1accdd4c25e06e47310d0c62c290ec166071d024352e003e5366e8ba6ba523f2a0cb34116ac55a238a886778880a9b2a547112fd7cffade81d8d8d084ccb7d36')
+ expect(result.accounts[0].privateKey).to.not.equal('eb18b748bcc48f824cf8a1fe92f7fc93bfc6f2a1eb9c1d40fa26d335d8a0c30f')
+ expect(result.accounts[0].publicKey).to.not.equal('a9ef7bbc004813cf75c5fc5c582066182d5c9cffd42eb7eb81cefea8e78c47c5')
+ expect(result.accounts[0].address).to.not.equal('nano_3chhhgy11k1msxtwdz4wd1i8e83fdkghzo3gpzor5mqyo5mrrjy79zpw1g34')
+
+ expect(result.seed).to.equal('146e3e2a0530848c9174d45ecec8c3f74a7be3f1ee832f92eb6227284121eb2e48a6b8fc469403984cd5e8f0d1ed05777c78f458d0e98c911841590e5d645dc3')
+ expect(result.accounts[0].privateKey).to.equal('2d5851bd5a89b8c943078be6ad5bbee8aeab77d6a4744c20d1b87d78e3286b93')
+ expect(result.accounts[0].publicKey).to.equal('923b6c7e281c1c5529fd2dc848117781216a1753cfd487fc34009f3591e636d7')
+ expect(result.accounts[0].address).to.equal('nano_36jufjz4i91wcnnztdgab1aqh1b3fado9mynizy5a16z8payefpqo81zsshc')
+ })
+
+ it('should throw when given an entropy with an invalid length', () => {
+ expect(() => wallet.generate('6caf5a42bb8074314aae20295975ece663be7aad945a73613d193b0cc41c797')).to.throw(Error)
+ expect(() => wallet.generate('6caf5a42bb8074314aae20295975ece663be7aad945a73613d193b0cc41c79701')).to.throw(Error)
+ })
+
+ it('should throw when given an entropy containing non-hex characters', () => {
+ expect(() => wallet.generate('6gaf5a42bb8074314aae20295975ece663be7aad945a73613d193b0cc41c7970')).to.throw(Error)
+ })
+
+})
+
+// Test vectors from https://docs.nano.org/integration-guides/key-management/
+describe('import wallet with official test vectors test', () => {
+
+ it('should successfully import a wallet with the official Nano test vectors mnemonic', () => {
+ const result = wallet.fromMnemonic(
+ 'edge defense waste choose enrich upon flee junk siren film clown finish luggage leader kid quick brick print evidence swap drill paddle truly occur',
+ 'some password')
+ expect(result).to.have.own.property('mnemonic')
+ expect(result).to.have.own.property('seed')
+ expect(result).to.have.own.property('accounts')
+ expect(result.mnemonic).to.equal('edge defense waste choose enrich upon flee junk siren film clown finish luggage leader kid quick brick print evidence swap drill paddle truly occur')
+ expect(result.seed).to.equal('0dc285fde768f7ff29b66ce7252d56ed92fe003b605907f7a4f683c3dc8586d34a914d3c71fc099bb38ee4a59e5b081a3497b7a323e90cc68f67b5837690310c')
+ expect(result.accounts[0].privateKey).to.equal('3be4fc2ef3f3b7374e6fc4fb6e7bb153f8a2998b3b3dab50853eabe128024143')
+ expect(result.accounts[0].publicKey).to.equal('5b65b0e8173ee0802c2c3e6c9080d1a16b06de1176c938a924f58670904e82c4')
+ expect(result.accounts[0].address).to.equal('nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d')
+ })
+
+ 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')
+ expect(result).to.have.own.property('seed')
+ expect(result).to.have.own.property('accounts')
+ expect(result.mnemonic).to.be.undefined
+ expect(result.seed).to.equal('0dc285fde768f7ff29b66ce7252d56ed92fe003b605907f7a4f683c3dc8586d34a914d3c71fc099bb38ee4a59e5b081a3497b7a323e90cc68f67b5837690310c')
+ expect(result.accounts[0].privateKey).to.equal('3be4fc2ef3f3b7374e6fc4fb6e7bb153f8a2998b3b3dab50853eabe128024143')
+ expect(result.accounts[0].publicKey).to.equal('5b65b0e8173ee0802c2c3e6c9080d1a16b06de1176c938a924f58670904e82c4')
+ expect(result.accounts[0].address).to.equal('nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d')
+ })
+
+ it('should throw when given a seed with an invalid length', () => {
+ expect(() => wallet.generate('0dc285fde768f7ff29b66ce7252d56ed92fe003b605907f7a4f683c3dc8586d34a914d3c71fc099bb38ee4a59e5b081a3497b7a323e90cc68f67b5837690310')).to.throw(Error)
+ expect(() => wallet.generate('0dc285fde768f7ff29b66ce7252d56ed92fe003b605907f7a4f683c3dc8586d34a914d3c71fc099bb38ee4a59e5b081a3497b7a323e90cc68f67b5837690310cd')).to.throw(Error)
+ })
+
+ it('should throw when given a seed containing non-hex characters', () => {
+ expect(() => wallet.generate('0gc285fde768f7ff29b66ce7252d56ed92fe003b605907f7a4f683c3dc8586d34a914d3c71fc099bb38ee4a59e5b081a3497b7a323e90cc68f67b5837690310c')).to.throw(Error)
+ })
+
+})
+
+describe('derive more accounts from the same seed test', () => {
+
+ it('should derive accounts from the given seed', () => {
+ const result = wallet.accounts(
+ '0dc285fde768f7ff29b66ce7252d56ed92fe003b605907f7a4f683c3dc8586d34a914d3c71fc099bb38ee4a59e5b081a3497b7a323e90cc68f67b5837690310c',
+ 0, 14)
+ expect(result.length).to.equal(15)
+ expect(result[0].privateKey).to.equal('3be4fc2ef3f3b7374e6fc4fb6e7bb153f8a2998b3b3dab50853eabe128024143')
+ expect(result[0].publicKey).to.equal('5b65b0e8173ee0802c2c3e6c9080d1a16b06de1176c938a924f58670904e82c4')
+ expect(result[0].address).to.equal('nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94nkbxe8g4a6x1p69emk8y1d')
+ expect(result[14].privateKey).to.equal('5f12e37c64daf2501c6a6a20614fd8d977fed65b5b5f0b045ec997f2ed2f53ca')
+ expect(result[14].publicKey).to.equal('f93a61018e07825a095e8cf7bdce9242e9c12c5c41a55de597a2be93fa41306b')
+ expect(result[14].address).to.equal('nano_3ybte61rw3w4da6ox59qqq9b6iqbr6p7rif7dqkshaoykhx64e5dbp4o1ua1')
+ })
+
+})
+
+// Test vectors from https://docs.nano.org/integration-guides/key-management/
+describe('block signing tests using official test vectors', () => {
+
+ it('should create a valid signature for a receive block', () => {
+ const result = block.sign({
+ walletBalanceRaw: '18618869000000000000000000000000',
+ fromAddress: 'nano_1e5aqegc1jb7qe964u4adzmcezyo6o146zb8hm6dft8tkp79za3sxwjym5rx',
+ toAddress: 'nano_3kyb49tqpt39ekc49kbej51ecsjqnimnzw1swxz4boix4ctm93w517umuiw8',
+ representativeAddress: 'nano_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou',
+ frontier: '92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D',
+ amountRaw: '7000000000000000000000000000000',
+ work: 'c5cf86de24b24419',
+ }, '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3')
+ expect(result.signature.toUpperCase()).to.equal('EEFFE1EFCCC8F2F6F2F1B79B80ABE855939DD9D6341323186494ADEE775DAADB3B6A6A07A85511F2185F6E739C4A54F1454436E22255A542ED879FD04FEED001')
+ })
+
+ it('should create a valid signature for a send block', () => {
+ const result = block.sign({
+ walletBalanceRaw: '5618869000000000000000000000000',
+ fromAddress: 'nano_1e5aqegc1jb7qe964u4adzmcezyo6o146zb8hm6dft8tkp79za3sxwjym5rx',
+ toAddress: 'nano_1q3hqecaw15cjt7thbtxu3pbzr1eihtzzpzxguoc37bj1wc5ffoh7w74gi6p',
+ representativeAddress: 'nano_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou',
+ frontier: '92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D',
+ amountRaw: '2000000000000000000000000000000',
+ work: 'fbffed7c73b61367',
+ }, '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3')
+ expect(result.signature.toUpperCase()).to.equal('79240D56231EF1885F354473733AF158DC6DA50E53836179565A20C0BE89D473ED3FF8CD11545FF0ED162A0B2C4626FD6BF84518568F8BB965A4884C7C32C205')
+ })
+
+ it('should create a valid signature for a change rep block', () => {
+ const result = block.representative({
+ walletBalanceRaw: '3000000000000000000000000000000',
+ address: 'nano_3igf8hd4sjshoibbbkeitmgkp1o6ug4xads43j6e4gqkj5xk5o83j8ja9php',
+ representativeAddress: 'nano_1anrzcuwe64rwxzcco8dkhpyxpi8kd7zsjc1oeimpc3ppca4mrjtwnqposrs',
+ frontier: '128106287002E595F479ACD615C818117FCB3860EC112670557A2467386249D4',
+ work: '0000000000000000',
+ }, '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3') // Did not find a private key at nano docs for this address
+ expect(result.signature.toUpperCase()).to.equal('A3C3C66D6519CBC0A198E56855942DEACC6EF741021A1B11279269ADC587DE1DA53CD478B8A47553231104CF24D742E1BB852B0546B87038C19BAE20F9082B0D')
+ })
+
+})