From 08e7a140f315f5f000c382da26d97d6472485cf7 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Miro=20Mets=C3=A4nheimo?= Date: Wed, 9 Oct 2019 20:23:07 +0300 Subject: [PATCH] Implemented most features (commit overdue) * Generate random entropy * BIP39 mnemonic phrase and seed from entropy * Mnemonic phrase importing and validation * BIP32 key derivation from seed * Nano address encoding and validation * Nano unit converter * Modified ed25519 curve for Nano * Block signing for receive and send blocks --- index.ts | 117 ++ lib/address-generator.ts | 38 + lib/address-importer.ts | 53 + lib/bip32-key-derivation.ts | 62 ++ lib/bip39-mnemonic.ts | 100 ++ lib/block-signer.ts | 118 ++ lib/ed25519.ts | 250 +++++ lib/nano-address.ts | 88 ++ lib/nano-converter.ts | 48 + lib/util/convert.ts | 118 ++ lib/util/curve25519.ts | 711 ++++++++++++ lib/util/util.ts | 30 + lib/words.ts | 2054 +++++++++++++++++++++++++++++++++++ package-lock.json | 30 + package.json | 27 + tsconfig.json | 18 + 16 files changed, 3862 insertions(+) create mode 100644 index.ts create mode 100644 lib/address-generator.ts create mode 100644 lib/address-importer.ts create mode 100644 lib/bip32-key-derivation.ts create mode 100644 lib/bip39-mnemonic.ts create mode 100644 lib/block-signer.ts create mode 100644 lib/ed25519.ts create mode 100644 lib/nano-address.ts create mode 100644 lib/nano-converter.ts create mode 100644 lib/util/convert.ts create mode 100644 lib/util/curve25519.ts create mode 100644 lib/util/util.ts create mode 100644 lib/words.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 tsconfig.json diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..2fcfdf4 --- /dev/null +++ b/index.ts @@ -0,0 +1,117 @@ +import { AddressGenerator } from './lib/address-generator' +import { AddressImporter } from './lib/address-importer' +import BlockSigner, { SendBlock, ReceiveBlock } from './lib/block-signer' + +const generator = new AddressGenerator() +const importer = new AddressImporter() +const wallet = { + + /** + * Generate a new Nano cryptocurrency wallet + * + * This function generates a wallet from random entropy. Wallet includes + * a BIP39 mnemonic phrase in line with the Nano Ledger implementation and + * a seed, the account is derived using BIP32 deterministic hierarchial algorithm + * with input parameters 44'/165' and index 0. + * + * 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 + * 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} [seedPassword] Optional seed password + * @returns the generated mnemonic, seed and account + */ + generate: (entropy?: string, seedPassword?: string) => { + return generator.generateWallet(entropy, seedPassword) + }, + + /** + * Import a Nano cryptocurrency wallet from a mnemonic phrase + * + * This function imports a wallet from a mnemonic phrase. Wallet includes the mnemonic phrase, + * a seed derived with BIP39 standard and an account derived using BIP32 deterministic hierarchial + * algorithm with input parameters 44'/165' and index 0. + * + * The Nano address is derived from the public key using standard Nano encoding. + * The address is prefixed with 'nano_'. + * + * @param {string} mnemonic The mnemonic phrase. Words are separated with a space + * @param {string} [seedPassword] Optional seed password + * @throws Throws an error if the mnemonic phrase doesn't pass validations + * @returns the imported mnemonic, seed and account + */ + fromMnemonic: (mnemonic: string, seedPassword?: string) => { + return importer.fromMnemonic(mnemonic, seedPassword) + }, + + /** + * Import a Nano cryptocurrency wallet from a seed + * + * This function imports a wallet from a seed. Wallet includes the seed and an account derived using + * BIP39 standard and an account derived using BIP32 deterministic hierarchial algorithm with input + * parameters 44'/165' and index 0. + * + * 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 importes seed and account + */ + fromSeed: (seed: string) => { + return importer.fromSeed(seed) + }, + + /** + * Derive accounts for the seed + * + * This function derives Nano accounts with the BIP32 deterministic hierarchial algorithm + * from the given seed with input parameters 44'/165' and indexes based on the from and to + * parameters. + * + * @param {string} seed The seed + * @param {number} from The start index + * @param {number} to The end index + */ + accounts: (seed: string, from: number, to: number) => { + return importer.fromSeed(seed, from, to).accounts + }, + +} + +const blockSigner = new BlockSigner() +const block = { + + /** + * Sign a send block with the input parameters + * + * @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 a receive block with the input parameters + * + * @param {SendBlock} 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) + }, + + // TODO: change representative block + +} + +export default { + wallet, + block, +} diff --git a/lib/address-generator.ts b/lib/address-generator.ts new file mode 100644 index 0000000..b58ed60 --- /dev/null +++ b/lib/address-generator.ts @@ -0,0 +1,38 @@ +import Bip32KeyDerivation from './bip32-key-derivation' +import Bip39Mnemonic from './bip39-mnemonic' +import { Ed25519 } from './ed25519' +import { NanoAddress } from './nano-address' + +export class AddressGenerator { + + /** + * Generates the wallet + * + * @param {String} seedPassword Password for the seed + */ + generateWallet(entropy = '', seedPassword: string = '') { + const bip39 = new Bip39Mnemonic(seedPassword) + const wallet = bip39.createWallet(entropy) + + const bip44 = new Bip32KeyDerivation(`44'/165'/0'`, wallet.seed) + const privateKey = bip44.derivePath().key + + const ed25519 = new Ed25519() + const keyPair = ed25519.generateKeys(privateKey) + + const nano = new NanoAddress() + const address = nano.deriveAddress(keyPair.publicKey) + + return { + mnemonic: wallet.mnemonic, + seed: wallet.seed, + accounts: [{ + accountIndex: 0, + privateKey: keyPair.privateKey, + publicKey: keyPair.publicKey, + address, + }], + } + } + +} diff --git a/lib/address-importer.ts b/lib/address-importer.ts new file mode 100644 index 0000000..5241ce9 --- /dev/null +++ b/lib/address-importer.ts @@ -0,0 +1,53 @@ +import Bip32KeyDerivation from './bip32-key-derivation' +import Bip39Mnemonic from './bip39-mnemonic' +import { Ed25519 } from './ed25519' +import { NanoAddress } from './nano-address' + +export class AddressImporter { + + fromMnemonic(mnemonic: string, seedPassword = '') { + const bip39 = new Bip39Mnemonic(seedPassword) + if (!bip39.validateMnemonic(mnemonic)) { + throw 'Invalid mnemonic phrase' + } + + const seed = bip39.mnemonicToSeed(mnemonic) + return this.nano(seed, 0, 0, mnemonic) + } + + fromSeed(seed: string, from = 0, to = 0) { + 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) { + const accounts = [] + + for (let i = from; i <= to; i++) { + const bip44 = new Bip32KeyDerivation(`44'/165'/${i}'`, seed) + const privateKey = bip44.derivePath().key + + const ed25519 = new Ed25519() + const keyPair = ed25519.generateKeys(privateKey) + + const nano = new NanoAddress() + const address = nano.deriveAddress(keyPair.publicKey) + accounts.push({ + accountIndex: i, + privateKey: keyPair.privateKey, + publicKey: keyPair.publicKey, + address, + }) + } + + return { + mnemonic, + seed, + accounts, + } + } + +} diff --git a/lib/bip32-key-derivation.ts b/lib/bip32-key-derivation.ts new file mode 100644 index 0000000..6d22086 --- /dev/null +++ b/lib/bip32-key-derivation.ts @@ -0,0 +1,62 @@ +import { Convert } from './util/convert' + +const CryptoJS = require('crypto-js') + +const ED25519_CURVE = 'ed25519 seed' +const HARDENED_OFFSET = 0x80000000 + +export default class Bip32KeyDerivation { + + path: string + seed: string + + constructor(path: string, seed: string) { + this.path = path + this.seed = seed + } + + derivePath = () => { + const { key, chainCode } = this.getKeyFromSeed() + const segments = this.path + .split('/') + .map(v => v.replace('\'', '')) + .map(el => parseInt(el, 10)) + return segments.reduce( + (parentKeys, segment) => + this.CKDPriv(parentKeys, segment + HARDENED_OFFSET), + { key, chainCode } + ) + } + + private getKeyFromSeed = () => { + 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) => { + const ib = [] + ib.push((index >> 24) & 0xff) + ib.push((index >> 16) & 0xff) + ib.push((index >> 8) & 0xff) + ib.push(index & 0xff) + const data = '00' + key + Convert.ab2hex(new Uint8Array(ib).buffer) + + return this.derive( + CryptoJS.enc.Hex.parse(data), + CryptoJS.enc.Hex.parse(chainCode)) + } + + private derive = (data: string, base: string) => { + 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) + const IR = I.slice(I.length / 2) + + return { + key: IL, + chainCode: IR, + } + } + +} diff --git a/lib/bip39-mnemonic.ts b/lib/bip39-mnemonic.ts new file mode 100644 index 0000000..8a598ce --- /dev/null +++ b/lib/bip39-mnemonic.ts @@ -0,0 +1,100 @@ +import words from './words' +import { Util } from './util/Util' +import { Convert } from './util/convert' + +const CryptoJS = require('crypto-js') + +export default class Bip39Mnemonic { + + password: string + + constructor(password: string) { + this.password = password + } + + createWallet = (entropy: string): { mnemonic: string, seed: string } => { + if (entropy.length !== 32) { + throw 'Invalid entropy length' + } + + if (!entropy) { + entropy = this.randomHex(64) + } + + const entropyBinary = Convert.hexStringToBinary(entropy) + const entropySha256Binary = Convert.hexStringToBinary(this.calculateChecksum(entropy)) + const entropyBinaryWithChecksum = entropyBinary + entropySha256Binary + + const mnemonicWords = [] + for (let i = 0; i < entropyBinaryWithChecksum.length; i += 11) { + mnemonicWords.push(words[parseInt(entropyBinaryWithChecksum.substr(i, 11), 2)]) + } + + const mnemonicFinal = mnemonicWords.join(' ') + const seed = this.mnemonicToSeed(mnemonicFinal) + + return { + mnemonic: mnemonicFinal, + seed, + } + } + + validateMnemonic = (mnemonic: string): boolean => { + const wordArray = Util.normalizeUTF8(mnemonic).split(' ') + if (wordArray.length % 3 !== 0) { + return false + } + + const bits = wordArray.map((w: string) => { + const wordIndex = words.indexOf(w) + if (wordIndex === -1) { + return false + } + return (wordIndex.toString(2)).padStart(11, '0') + }).join('') + + const dividerIndex = Math.floor(bits.length / 33) * 32 + const entropyBits = bits.slice(0, dividerIndex) + const checksumBits = bits.slice(dividerIndex) + const entropyBytes = entropyBits.match(/(.{1,8})/g).map((bin: string) => parseInt(bin, 2)) + + if (entropyBytes.length < 16) return false + if (entropyBytes.length > 32) return false + if (entropyBytes.length % 4 !== 0) return false + + const entropyHex = Convert.bytesToHexString(entropyBytes) + const newChecksum = this.calculateChecksum(entropyHex) + const inputChecksum = Convert.binaryToHexString(checksumBits) + + if (newChecksum != inputChecksum) { + return false + } + + return true + } + + mnemonicToSeed = (mnemonic: string): string => { + const normalizedMnemonic = Util.normalizeUTF8(mnemonic) + const normalizedPassword = 'mnemonic' + Util.normalizeUTF8(this.password) + + return CryptoJS.PBKDF2( + normalizedMnemonic, + normalizedPassword, + { + keySize: 512 / 32, + iterations: 2048, + hasher: CryptoJS.algo.SHA512, + }) + .toString(CryptoJS.enc.Hex) + } + + private randomHex = (length: number): string => { + return CryptoJS.lib.WordArray.random(length / 2).toString() + } + + private calculateChecksum = (entropyHex: string): string => { + const entropySha256 = CryptoJS.SHA256(CryptoJS.enc.Hex.parse(entropyHex)).toString() + return entropySha256.substr(0, entropySha256.length / 32) + } + +} diff --git a/lib/block-signer.ts b/lib/block-signer.ts new file mode 100644 index 0000000..0ee3734 --- /dev/null +++ b/lib/block-signer.ts @@ -0,0 +1,118 @@ +import BigNumber from 'bignumber.js' +import * as blake from 'blakejs' +import { Ed25519 } from './ed25519' +import { NanoAddress } from './nano-address' +import NanoConverter from './nano-converter' +import { Convert } from './util/convert' + +export default class BlockSigner { + + nanoAddress = new NanoAddress() + ed25519 = new Ed25519() + + preamble = 0x6.toString().padStart(64, '0') + + send(data: SendBlock, privateKey: string) { + const balance = NanoConverter.convert(data.walletBalanceRaw, 'RAW', 'NANO') + const newBalance = new BigNumber(balance).minus(new BigNumber(data.amount)) + const rawBalance = NanoConverter.convert(newBalance, 'NANO', 'RAW') + const hexBalance = Convert.dec2hex(rawBalance, 16).toUpperCase() + const account = this.nanoAddressToHexString(data.fromAddress) + const link = this.nanoAddressToHexString(data.toAddress) + const representative = this.nanoAddressToHexString(data.representativeAddress) + + const signatureBytes = this.ed25519.sign( + this.generateHash(this.preamble, account, data.frontier, representative, hexBalance, link), + Convert.hex2ab(privateKey)) + + return { + type: 'state', + account: data.fromAddress, + previous: data.frontier, + representative: data.representativeAddress, + balance: rawBalance, + link, + signature: Convert.ab2hex(signatureBytes), + work: data.work, + } + } + + 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)) + blake.blake2bUpdate(ctx, Convert.hex2ab(account)) + blake.blake2bUpdate(ctx, Convert.hex2ab(previous)) + blake.blake2bUpdate(ctx, Convert.hex2ab(representative)) + blake.blake2bUpdate(ctx, Convert.hex2ab(balance)) + blake.blake2bUpdate(ctx, Convert.hex2ab(link)) + return blake.blake2bFinal(ctx) + } + + private nanoAddressToHexString(addr: string): string { + addr = addr.slice(-60) + const isValid = /^[13456789abcdefghijkmnopqrstuwxyz]+$/.test(addr) + if (isValid) { + const keyBytes = this.nanoAddress.decodeNanoBase32(addr.substring(0, 52)) + const hashBytes = this.nanoAddress.decodeNanoBase32(addr.substring(52, 60)) + const blakeHash = blake.blake2b(keyBytes, undefined, 5).reverse() + if (Convert.ab2hex(hashBytes) == Convert.ab2hex(blakeHash)) { + const key = Convert.ab2hex(keyBytes).toUpperCase() + return key + } + throw 'Checksum mismatch' + } else { + throw 'Illegal characters' + } + } + +} + +export interface SendBlock { + walletBalanceRaw: string + fromAddress: string + toAddress: string + representativeAddress: string + frontier: string + amount: string + work: string +} + +export interface ReceiveBlock { + walletBalanceRaw: string + walletAddress: string + representativeAddress: string + frontier: string + hash: string + amount: string + work: string +} diff --git a/lib/ed25519.ts b/lib/ed25519.ts new file mode 100644 index 0000000..9a11a22 --- /dev/null +++ b/lib/ed25519.ts @@ -0,0 +1,250 @@ +import * as blake from 'blakejs' +import { Convert } from './util/convert' +import { Curve25519 } from './util/curve25519' + +export class Ed25519 { + + curve: Curve25519 + X: Int32Array + Y: Int32Array + L: Uint8Array + + constructor() { + this.curve = new Curve25519() + this.X = this.curve.gf([0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169]) + this.Y = this.curve.gf([0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666]) + this.L = new Uint8Array([0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10]) + } + + pack(r: Uint8Array, p: Int32Array[]): void { + const CURVE = this.curve + const tx = CURVE.gf(), + ty = CURVE.gf(), + zi = CURVE.gf() + CURVE.inv25519(zi, p[2]) + CURVE.M(tx, p[0], zi) + CURVE.M(ty, p[1], zi) + CURVE.pack25519(r, ty) + r[31] ^= CURVE.par25519(tx) << 7 + } + + modL(r: Uint8Array, x: Uint32Array | Float64Array): void { + let carry, i, j, k + for (i = 63; i >= 32; --i) { + carry = 0 + for (j = i - 32, k = i - 12; j < k; ++j) { + x[j] += carry - 16 * x[i] * this.L[j - (i - 32)] + carry = (x[j] + 128) >> 8 + x[j] -= carry * 256 + } + x[j] += carry + x[i] = 0 + } + + carry = 0 + for (j = 0; j < 32; j++) { + x[j] += carry - (x[31] >> 4) * this.L[j] + carry = x[j] >> 8 + x[j] &= 255 + } + + for (j = 0; j < 32; j++) { + x[j] -= carry * this.L[j] + } + + for (i = 0; i < 32; i++) { + x[i + 1] += x[i] >>> 8 + r[i] = x[i] & 0xff + } + } + + reduce(r: Uint8Array): void { + const x = new Uint32Array(64) + for (let i = 0; i < 64; i++) { + x[i] = r[i] + } + + this.modL(r, x) + } + + scalarmult(p: Int32Array[], q: Int32Array[], s: Uint8Array): void { + const CURVE = this.curve + CURVE.set25519(p[0], CURVE.gf0) + CURVE.set25519(p[1], CURVE.gf1) + CURVE.set25519(p[2], CURVE.gf1) + CURVE.set25519(p[3], CURVE.gf0) + for (let i = 255; i >= 0; --i) { + const b = (s[(i / 8) | 0] >>> (i & 7)) & 1 + CURVE.cswap(p, q, b) + CURVE.add(q, p) + CURVE.add(p, p) + CURVE.cswap(p, q, b) + } + } + + scalarbase(p: Int32Array[], s: Uint8Array): void { + const CURVE = this.curve + const q = [CURVE.gf(), CURVE.gf(), CURVE.gf(), CURVE.gf()] + CURVE.set25519(q[0], this.X) + CURVE.set25519(q[1], this.Y) + CURVE.set25519(q[2], CURVE.gf1) + CURVE.M(q[3], this.X, this.Y) + this.scalarmult(p, q, s) + } + + /** + * Generate an ed25519 keypair + * @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 } { + const pk = new Uint8Array(32) + const p = [this.curve.gf(), this.curve.gf(), this.curve.gf(), this.curve.gf()] + const h = blake + .blake2b(Convert.hex2ab(seed), undefined, 64) + .slice(0, 32) + + h[0] &= 0xf8 + h[31] &= 0x7f + h[31] |= 0x40 + + this.scalarbase(p, h) + this.pack(pk, p) + + return { + privateKey: seed, + publicKey: Convert.ab2hex(pk), + } + } + + /** + * Generate a message signature + * @param {Uint8Array} msg Message to be signed as byte array + * @param {Uint8Array} secretKey Secret key as byte array + * @param {Uint8Array} Returns the signature as 64 byte typed array + */ + sign(msg: Uint8Array, secretKey: Uint8Array): Uint8Array { + const signedMsg = this.naclSign(msg, secretKey) + const sig = new Uint8Array(64) + + for (let i = 0; i < sig.length; i++) { + sig[i] = signedMsg[i] + } + + return sig + } + + private naclSign(msg: Uint8Array, secretKey: Uint8Array): Uint8Array { + if (secretKey.length !== 32) { + throw new Error('bad secret key size') + } + + const signedMsg = new Uint8Array(64 + msg.length) + this.cryptoSign(signedMsg, msg, msg.length, secretKey) + + return signedMsg + } + + private cryptoSign(sm: Uint8Array, m: Uint8Array, n: number, sk: Uint8Array): number { + const CURVE = this.curve + const d = new Uint8Array(64) + const h = new Uint8Array(64) + const r = new Uint8Array(64) + const x = new Float64Array(64) + const p = [CURVE.gf(), CURVE.gf(), CURVE.gf(), CURVE.gf()] + + let i + let j + + const pk = Convert.hex2ab(this.generateKeys(Convert.ab2hex(sk)).publicKey) + + this.cryptoHash(d, sk, 32) + d[0] &= 248 + d[31] &= 127 + d[31] |= 64 + + const smlen = n + 64 + for (i = 0; i < n; i++) { + sm[64 + i] = m[i] + } + + for (i = 0; i < 32; i++) { + sm[32 + i] = d[32 + i] + } + + this.cryptoHash(r, sm.subarray(32), n + 32) + this.reduce(r) + this.scalarbase(p, r) + this.pack(sm, p) + + for (i = 32; i < 64; i++) { + sm[i] = pk[i - 32] + } + + this.cryptoHash(h, sm, n + 64) + this.reduce(h) + + for (i = 0; i < 64; i++) { + x[i] = 0 + } + + for (i = 0; i < 32; i++) { + x[i] = r[i] + } + + for (i = 0; i < 32; i++) { + for (j = 0; j < 32; j++) { + x[i + j] += h[i] * d[j] + } + } + + this.modL(sm.subarray(32), x) + + return smlen + } + + private cryptoHash(out: Uint8Array, m: Uint8Array, n: number): number { + const input = new Uint8Array(n) + for (let i = 0; i < n; ++i) { + input[i] = m[i] + } + + const hash = blake.blake2b(input) + for (let i = 0; i < 64; ++i) { + out[i] = hash[i] + } + + 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) + // } + +} diff --git a/lib/nano-address.ts b/lib/nano-address.ts new file mode 100644 index 0000000..6db5c9c --- /dev/null +++ b/lib/nano-address.ts @@ -0,0 +1,88 @@ +import * as blake from 'blakejs' +import { Convert } from './util/convert' + +export class NanoAddress { + + readonly alphabet = '13456789abcdefghijkmnopqrstuwxyz' + readonly prefix = 'nano_' + + deriveAddress = (publicKey: string): string => { + const publicKeyBytes = Convert.hex2ab(publicKey) + const checksum = blake + .blake2b(publicKeyBytes, undefined, 5) + .reverse() + + const encoded = this.encodeNanoBase32(publicKeyBytes) + const encodedChecksum = this.encodeNanoBase32(checksum) + + return this.prefix + encoded + encodedChecksum + } + + encodeNanoBase32 = (publicKey: Uint8Array): string => { + const length = publicKey.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) | publicKey[i] + bits += 8 + + while (bits >= 5) { + output += this.alphabet[(value >>> (bits + offset - 5)) & 31] + bits -= 5 + } + } + + if (bits > 0) { + output += this.alphabet[(value << (5 - (bits + offset))) & 31] + } + + return output + } + + decodeNanoBase32 = (input: string): Uint8Array => { + 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) | this.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 + } + + readChar(char: string): number { + const idx = this.alphabet.indexOf(char) + + if (idx === -1) { + throw `Invalid character found: ${char}` + } + + return idx + } + +} diff --git a/lib/nano-converter.ts b/lib/nano-converter.ts new file mode 100644 index 0000000..6f8ece3 --- /dev/null +++ b/lib/nano-converter.ts @@ -0,0 +1,48 @@ +import BigNumber from 'bignumber.js' + +export default class NanoConverter { + + /** + * Converts the input value to the wanted unit + * + * @param input {BigNumber} value + * @param inputUnit {Unit} the unit to convert from + * @param outputUnit {Unit} the unit to convert to + */ + static convert(input: BigNumber, inputUnit: string, outputUnit: string): string { + let value = new BigNumber(input.toString()) + + switch (inputUnit) { + case 'RAW': + value = value + break + case 'NANO': + case 'MRAI': + value = value.shiftedBy(30) + break + case 'KRAI': + value = value.shiftedBy(27) + break + case 'RAI': + value = value.shiftedBy(24) + break + default: + throw `Unkown input unit ${inputUnit}, expected one of the following: RAW, NANO, MRAI, KRAI, RAI` + } + + switch (outputUnit) { + case 'RAW': + return value.toFixed(0) + case 'NANO': + case 'MRAI': + return value.shiftedBy(-30).toFixed(15, 1) + case 'KRAI': + return value.shiftedBy(-27).toFixed(12, 1) + case 'RAI': + return value.shiftedBy(-24).toFixed(9, 1) + default: + throw `Unknown output unit ${outputUnit}, expected one of the following: RAW, NANO, MRAI, KRAI, RAI` + } + } + +} diff --git a/lib/util/convert.ts b/lib/util/convert.ts new file mode 100644 index 0000000..490f6f0 --- /dev/null +++ b/lib/util/convert.ts @@ -0,0 +1,118 @@ +export class Convert { + + /** + * Convert a string (UTF-8 encoded) to a byte array + * + * @param {String} str UTF-8 encoded string + * @return {Uint8Array} Byte array + */ + static str2bin(str: string): Uint8Array { + str = str.replace(/\r\n/g, '\n') + const bin = new Uint8Array(str.length * 3) + let p = 0 + for (let i = 0, len = str.length; i < len; i++) { + const c = str.charCodeAt(i) + if (c < 128) { + bin[p++] = c + } else if (c < 2048) { + bin[p++] = (c >>> 6) | 192 + bin[p++] = (c & 63) | 128 + } else { + bin[p++] = (c >>> 12) | 224 + bin[p++] = ((c >>> 6) & 63) | 128 + bin[p++] = (c & 63) | 128 + } + } + return bin.subarray(0, p) + } + + /** + * Convert Array of 8 bytes (int64) to hex string + * + * @param {Uint8Array} bin Array of bytes + * @return {String} Hex encoded string + */ + static ab2hex = (buf: ArrayBuffer): string => { + return Array.prototype.map.call(new Uint8Array(buf), x => ('00' + x.toString(16)).slice(-2)).join('') + } + + /** + * Convert hex string to array of 8 bytes (int64) + * + * @param {String} bin Array of bytes + * @return {Uint8Array} Array of 8 bytes (int64) + */ + static hex2ab = (hex: string): Uint8Array => { + const ab = [] + for (let i = 0; i < hex.length; i += 2) { + ab.push(parseInt(hex.substr(i, 2), 16)) + } + return new Uint8Array(ab) + } + + /** + * Convert a decimal number to hex string + * + * @param {String} str Decimal to be converted + * @param {Number} bytes Length of the output to be padded + * @returns Hexadecimal representation of the inputed decimal + */ + static dec2hex = (str: number | string, bytes: number): string => { + const decimals = str.toString().split('') + const sum = [] + let hex = [] + let i: number + let s: number + + while (decimals.length) { + const dec = decimals.shift() + if (!dec) { + throw 'Invalid decimal' + } + + s = 1 * +dec + for (i = 0; s || i < sum.length; i++) { + s += (sum[i] || 0) * 10 + sum[i] = s % 16 + s = (s - sum[i]) / 16 + } + } + + while (sum.length) { + const dec = sum.pop() + if (!dec) { + throw 'Invalid decimal' + } + + hex.push(dec.toString(16)) + } + + let joined = hex.join('') + + if (joined.length % 2 != 0) { + joined = '0' + joined + } + + if (bytes > joined.length / 2) { + const diff = bytes - joined.length / 2 + for (let i = 0; i < diff; i++) { + joined = '00' + joined + } + } + + return joined + } + + static bytesToHexString = (bytes: number[]): string => { + return [...bytes].map(b => b.toString(16).padStart(2, '0')).join('') + } + + static hexStringToBinary = (hex: string): string => { + return [...hex].map(c => (parseInt(c, 16).toString(2)).padStart(4, '0')).join('') + } + + static binaryToHexString = (bin: string): string => { + return parseInt(bin, 2).toString(16) + } + +} diff --git a/lib/util/curve25519.ts b/lib/util/curve25519.ts new file mode 100644 index 0000000..21ed61f --- /dev/null +++ b/lib/util/curve25519.ts @@ -0,0 +1,711 @@ +import { Util } from './util' + +export class Curve25519 { + + gf0: Int32Array + gf1: Int32Array + D: Int32Array + D2: Int32Array + I: Int32Array + _9: Uint8Array + _121665: Int32Array + + constructor() { + this.gf0 = this.gf() + this.gf1 = this.gf([1]) + this._9 = new Uint8Array(32) + this._9[0] = 9 + this._121665 = this.gf([0xdb41, 1]) + this.D = this.gf([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]) + this.D2 = this.gf([0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406]) + this.I = this.gf([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]) + } + + gf(init?: number[]): Int32Array { + const r = new Int32Array(16) + if (init) { + for (let i = 0; i < init.length; i++) { + r[i] = init[i] + } + } + + return r + } + + A(o: Int32Array, a: Int32Array, b: Int32Array): void { + for (let i = 0; i < 16; i++) { + o[i] = a[i] + b[i] + } + } + + Z(o: Int32Array, a: Int32Array, b: Int32Array): void { + for (let i = 0; i < 16; i++) { + o[i] = a[i] - b[i] + } + } + + // Avoid loops for better performance + M(o: Int32Array, a: Int32Array, b: Int32Array): void { + let v, c, + t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0, t6 = 0, t7 = 0, + t8 = 0, t9 = 0, t10 = 0, t11 = 0, t12 = 0, t13 = 0, t14 = 0, t15 = 0, + t16 = 0, t17 = 0, t18 = 0, t19 = 0, t20 = 0, t21 = 0, t22 = 0, t23 = 0, + t24 = 0, t25 = 0, t26 = 0, t27 = 0, t28 = 0, t29 = 0, t30 = 0 + const b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3], + b4 = b[4], + b5 = b[5], + b6 = b[6], + b7 = b[7], + b8 = b[8], + b9 = b[9], + b10 = b[10], + b11 = b[11], + b12 = b[12], + b13 = b[13], + b14 = b[14], + b15 = b[15] + + v = a[0] + t0 += v * b0 + t1 += v * b1 + t2 += v * b2 + t3 += v * b3 + t4 += v * b4 + t5 += v * b5 + t6 += v * b6 + t7 += v * b7 + t8 += v * b8 + t9 += v * b9 + t10 += v * b10 + t11 += v * b11 + t12 += v * b12 + t13 += v * b13 + t14 += v * b14 + t15 += v * b15 + v = a[1] + t1 += v * b0 + t2 += v * b1 + t3 += v * b2 + t4 += v * b3 + t5 += v * b4 + t6 += v * b5 + t7 += v * b6 + t8 += v * b7 + t9 += v * b8 + t10 += v * b9 + t11 += v * b10 + t12 += v * b11 + t13 += v * b12 + t14 += v * b13 + t15 += v * b14 + t16 += v * b15 + v = a[2] + t2 += v * b0 + t3 += v * b1 + t4 += v * b2 + t5 += v * b3 + t6 += v * b4 + t7 += v * b5 + t8 += v * b6 + t9 += v * b7 + t10 += v * b8 + t11 += v * b9 + t12 += v * b10 + t13 += v * b11 + t14 += v * b12 + t15 += v * b13 + t16 += v * b14 + t17 += v * b15 + v = a[3] + t3 += v * b0 + t4 += v * b1 + t5 += v * b2 + t6 += v * b3 + t7 += v * b4 + t8 += v * b5 + t9 += v * b6 + t10 += v * b7 + t11 += v * b8 + t12 += v * b9 + t13 += v * b10 + t14 += v * b11 + t15 += v * b12 + t16 += v * b13 + t17 += v * b14 + t18 += v * b15 + v = a[4] + t4 += v * b0 + t5 += v * b1 + t6 += v * b2 + t7 += v * b3 + t8 += v * b4 + t9 += v * b5 + t10 += v * b6 + t11 += v * b7 + t12 += v * b8 + t13 += v * b9 + t14 += v * b10 + t15 += v * b11 + t16 += v * b12 + t17 += v * b13 + t18 += v * b14 + t19 += v * b15 + v = a[5] + t5 += v * b0 + t6 += v * b1 + t7 += v * b2 + t8 += v * b3 + t9 += v * b4 + t10 += v * b5 + t11 += v * b6 + t12 += v * b7 + t13 += v * b8 + t14 += v * b9 + t15 += v * b10 + t16 += v * b11 + t17 += v * b12 + t18 += v * b13 + t19 += v * b14 + t20 += v * b15 + v = a[6] + t6 += v * b0 + t7 += v * b1 + t8 += v * b2 + t9 += v * b3 + t10 += v * b4 + t11 += v * b5 + t12 += v * b6 + t13 += v * b7 + t14 += v * b8 + t15 += v * b9 + t16 += v * b10 + t17 += v * b11 + t18 += v * b12 + t19 += v * b13 + t20 += v * b14 + t21 += v * b15 + v = a[7] + t7 += v * b0 + t8 += v * b1 + t9 += v * b2 + t10 += v * b3 + t11 += v * b4 + t12 += v * b5 + t13 += v * b6 + t14 += v * b7 + t15 += v * b8 + t16 += v * b9 + t17 += v * b10 + t18 += v * b11 + t19 += v * b12 + t20 += v * b13 + t21 += v * b14 + t22 += v * b15 + v = a[8] + t8 += v * b0 + t9 += v * b1 + t10 += v * b2 + t11 += v * b3 + t12 += v * b4 + t13 += v * b5 + t14 += v * b6 + t15 += v * b7 + t16 += v * b8 + t17 += v * b9 + t18 += v * b10 + t19 += v * b11 + t20 += v * b12 + t21 += v * b13 + t22 += v * b14 + t23 += v * b15 + v = a[9] + t9 += v * b0 + t10 += v * b1 + t11 += v * b2 + t12 += v * b3 + t13 += v * b4 + t14 += v * b5 + t15 += v * b6 + t16 += v * b7 + t17 += v * b8 + t18 += v * b9 + t19 += v * b10 + t20 += v * b11 + t21 += v * b12 + t22 += v * b13 + t23 += v * b14 + t24 += v * b15 + v = a[10] + t10 += v * b0 + t11 += v * b1 + t12 += v * b2 + t13 += v * b3 + t14 += v * b4 + t15 += v * b5 + t16 += v * b6 + t17 += v * b7 + t18 += v * b8 + t19 += v * b9 + t20 += v * b10 + t21 += v * b11 + t22 += v * b12 + t23 += v * b13 + t24 += v * b14 + t25 += v * b15 + v = a[11] + t11 += v * b0 + t12 += v * b1 + t13 += v * b2 + t14 += v * b3 + t15 += v * b4 + t16 += v * b5 + t17 += v * b6 + t18 += v * b7 + t19 += v * b8 + t20 += v * b9 + t21 += v * b10 + t22 += v * b11 + t23 += v * b12 + t24 += v * b13 + t25 += v * b14 + t26 += v * b15 + v = a[12] + t12 += v * b0 + t13 += v * b1 + t14 += v * b2 + t15 += v * b3 + t16 += v * b4 + t17 += v * b5 + t18 += v * b6 + t19 += v * b7 + t20 += v * b8 + t21 += v * b9 + t22 += v * b10 + t23 += v * b11 + t24 += v * b12 + t25 += v * b13 + t26 += v * b14 + t27 += v * b15 + v = a[13] + t13 += v * b0 + t14 += v * b1 + t15 += v * b2 + t16 += v * b3 + t17 += v * b4 + t18 += v * b5 + t19 += v * b6 + t20 += v * b7 + t21 += v * b8 + t22 += v * b9 + t23 += v * b10 + t24 += v * b11 + t25 += v * b12 + t26 += v * b13 + t27 += v * b14 + t28 += v * b15 + v = a[14] + t14 += v * b0 + t15 += v * b1 + t16 += v * b2 + t17 += v * b3 + t18 += v * b4 + t19 += v * b5 + t20 += v * b6 + t21 += v * b7 + t22 += v * b8 + t23 += v * b9 + t24 += v * b10 + t25 += v * b11 + t26 += v * b12 + t27 += v * b13 + t28 += v * b14 + t29 += v * b15 + v = a[15] + t15 += v * b0 + t16 += v * b1 + t17 += v * b2 + t18 += v * b3 + t19 += v * b4 + t20 += v * b5 + t21 += v * b6 + t22 += v * b7 + t23 += v * b8 + t24 += v * b9 + t25 += v * b10 + t26 += v * b11 + t27 += v * b12 + t28 += v * b13 + t29 += v * b14 + t30 += v * b15 + + t0 += 38 * t16 + t1 += 38 * t17 + t2 += 38 * t18 + t3 += 38 * t19 + t4 += 38 * t20 + t5 += 38 * t21 + t6 += 38 * t22 + t7 += 38 * t23 + t8 += 38 * t24 + t9 += 38 * t25 + t10 += 38 * t26 + t11 += 38 * t27 + t12 += 38 * t28 + t13 += 38 * t29 + t14 += 38 * t30 + + c = 1 + v = t0 + c + 65535; c = Math.floor(v / 65536); t0 = v - c * 65536 + v = t1 + c + 65535; c = Math.floor(v / 65536); t1 = v - c * 65536 + v = t2 + c + 65535; c = Math.floor(v / 65536); t2 = v - c * 65536 + v = t3 + c + 65535; c = Math.floor(v / 65536); t3 = v - c * 65536 + v = t4 + c + 65535; c = Math.floor(v / 65536); t4 = v - c * 65536 + v = t5 + c + 65535; c = Math.floor(v / 65536); t5 = v - c * 65536 + v = t6 + c + 65535; c = Math.floor(v / 65536); t6 = v - c * 65536 + v = t7 + c + 65535; c = Math.floor(v / 65536); t7 = v - c * 65536 + v = t8 + c + 65535; c = Math.floor(v / 65536); t8 = v - c * 65536 + v = t9 + c + 65535; c = Math.floor(v / 65536); t9 = v - c * 65536 + v = t10 + c + 65535; c = Math.floor(v / 65536); t10 = v - c * 65536 + v = t11 + c + 65535; c = Math.floor(v / 65536); t11 = v - c * 65536 + v = t12 + c + 65535; c = Math.floor(v / 65536); t12 = v - c * 65536 + v = t13 + c + 65535; c = Math.floor(v / 65536); t13 = v - c * 65536 + v = t14 + c + 65535; c = Math.floor(v / 65536); t14 = v - c * 65536 + v = t15 + c + 65535; c = Math.floor(v / 65536); t15 = v - c * 65536 + t0 += c - 1 + 37 * (c - 1) + + c = 1 + v = t0 + c + 65535; c = Math.floor(v / 65536); t0 = v - c * 65536 + v = t1 + c + 65535; c = Math.floor(v / 65536); t1 = v - c * 65536 + v = t2 + c + 65535; c = Math.floor(v / 65536); t2 = v - c * 65536 + v = t3 + c + 65535; c = Math.floor(v / 65536); t3 = v - c * 65536 + v = t4 + c + 65535; c = Math.floor(v / 65536); t4 = v - c * 65536 + v = t5 + c + 65535; c = Math.floor(v / 65536); t5 = v - c * 65536 + v = t6 + c + 65535; c = Math.floor(v / 65536); t6 = v - c * 65536 + v = t7 + c + 65535; c = Math.floor(v / 65536); t7 = v - c * 65536 + v = t8 + c + 65535; c = Math.floor(v / 65536); t8 = v - c * 65536 + v = t9 + c + 65535; c = Math.floor(v / 65536); t9 = v - c * 65536 + v = t10 + c + 65535; c = Math.floor(v / 65536); t10 = v - c * 65536 + v = t11 + c + 65535; c = Math.floor(v / 65536); t11 = v - c * 65536 + v = t12 + c + 65535; c = Math.floor(v / 65536); t12 = v - c * 65536 + v = t13 + c + 65535; c = Math.floor(v / 65536); t13 = v - c * 65536 + v = t14 + c + 65535; c = Math.floor(v / 65536); t14 = v - c * 65536 + v = t15 + c + 65535; c = Math.floor(v / 65536); t15 = v - c * 65536 + t0 += c - 1 + 37 * (c - 1) + + o[0] = t0 + o[1] = t1 + o[2] = t2 + o[3] = t3 + o[4] = t4 + o[5] = t5 + o[6] = t6 + o[7] = t7 + o[8] = t8 + o[9] = t9 + o[10] = t10 + o[11] = t11 + o[12] = t12 + o[13] = t13 + o[14] = t14 + o[15] = t15 + } + + S(o: Int32Array, a: Int32Array): void { + this.M(o, a, a) + } + + add(p: Int32Array[], q: Int32Array[]): void { + const a = this.gf(), b = this.gf(), c = this.gf(), + d = this.gf(), e = this.gf(), f = this.gf(), + g = this.gf(), h = this.gf(), t = this.gf() + + this.Z(a, p[1], p[0]) + this.Z(t, q[1], q[0]) + this.M(a, a, t) + this.A(b, p[0], p[1]) + this.A(t, q[0], q[1]) + this.M(b, b, t) + this.M(c, p[3], q[3]) + this.M(c, c, this.D2) + this.M(d, p[2], q[2]) + this.A(d, d, d) + this.Z(e, b, a) + this.Z(f, d, c) + this.A(g, d, c) + this.A(h, b, a) + this.M(p[0], e, f) + this.M(p[1], h, g) + this.M(p[2], g, f) + this.M(p[3], e, h) + } + + set25519(r: Int32Array, a: Int32Array): void { + for (let i = 0; i < 16; i++) { + r[i] = a[i] + } + } + + car25519(o: Int32Array): void { + let i, v, c = 1 + for (i = 0; i < 16; i++) { + v = o[i] + c + 65535 + c = Math.floor(v / 65536) + o[i] = v - c * 65536 + } + + o[0] += c - 1 + 37 * (c - 1) + } + + // b is 0 or 1 + sel25519(p: Int32Array, q: Int32Array, b: number): void { + let i, t + const c = ~(b - 1) + for (i = 0; i < 16; i++) { + t = c & (p[i] ^ q[i]) + p[i] ^= t + q[i] ^= t + } + } + + inv25519(o: Int32Array, i: Int32Array): void { + let a + const c = this.gf() + for (a = 0; a < 16; a++) { + c[a] = i[a] + } + + for (a = 253; a >= 0; a--) { + this.S(c, c) + if (a !== 2 && a !== 4) { + this.M(c, c, i) + } + } + + for (a = 0; a < 16; a++) { + o[a] = c[a] + } + } + + neq25519(a: Int32Array, b: Int32Array): boolean { + const c = new Uint8Array(32), d = new Uint8Array(32) + this.pack25519(c, a) + this.pack25519(d, b) + return !Util.compare(c, d) + } + + par25519(a: Int32Array): number { + const d = new Uint8Array(32) + this.pack25519(d, a) + return d[0] & 1 + } + + pow2523(o: Int32Array, i: Int32Array): void { + let a + const c = this.gf() + for (a = 0; a < 16; a++) { + c[a] = i[a] + } + + for (a = 250; a >= 0; a--) { + this.S(c, c) + if (a !== 1) this.M(c, c, i) + } + + for (a = 0; a < 16; a++) { + o[a] = c[a] + } + } + + cswap(p: Int32Array[], q: Int32Array[], b: number): void { + for (let i = 0; i < 4; i++) { + this.sel25519(p[i], q[i], b) + } + } + + pack25519(o: Uint8Array, n: Int32Array): void { + let i + const m = this.gf() + const t = this.gf() + for (i = 0; i < 16; i++) { + t[i] = n[i] + } + + this.car25519(t) + this.car25519(t) + this.car25519(t) + for (let j = 0; j < 2; j++) { + m[0] = t[0] - 0xffed + for (i = 1; i < 15; i++) { + m[i] = t[i] - 0xffff - ((m[i - 1] >>> 16) & 1) + m[i - 1] &= 0xffff + } + + m[15] = t[15] - 0x7fff - ((m[14] >>> 16) & 1) + const b = (m[15] >>> 16) & 1 + m[14] &= 0xffff + this.sel25519(t, m, 1 - b) + } + + for (i = 0; i < 16; i++) { + o[2 * i] = t[i] & 0xff + o[2 * i + 1] = t[i] >>> 8 + } + } + + unpack25519(o: Int32Array, n: Int32Array): void { + for (let i = 0; i < 16; i++) { + o[i] = n[2 * i] + (n[2 * i + 1] << 8) + } + + o[15] &= 0x7fff + } + + unpackNeg(r: Int32Array[], p: Int32Array): number { + const t = this.gf(), + chk = this.gf(), + num = this.gf(), + den = this.gf(), + den2 = this.gf(), + den4 = this.gf(), + den6 = this.gf() + + this.set25519(r[2], this.gf1) + this.unpack25519(r[1], p) + this.S(num, r[1]) + this.M(den, num, this.D) + this.Z(num, num, r[2]) + this.A(den, r[2], den) + + this.S(den2, den) + this.S(den4, den2) + this.M(den6, den4, den2) + this.M(t, den6, num) + this.M(t, t, den) + + this.pow2523(t, t) + this.M(t, t, num) + this.M(t, t, den) + this.M(t, t, den) + this.M(r[0], t, den) + + this.S(chk, r[0]) + this.M(chk, chk, den) + if (this.neq25519(chk, num)) { + this.M(r[0], r[0], this.I) + } + + this.S(chk, r[0]) + this.M(chk, chk, den) + if (this.neq25519(chk, num)) { + return -1 + } + + if (this.par25519(r[0]) === (p[31] >>> 7)) { + this.Z(r[0], this.gf0, r[0]) + } + + this.M(r[3], r[0], r[1]) + + return 0 + } + + /** + * Internal scalar mult function + * @param {Uint8Array} q Result + * @param {Uint8Array} s Secret key + * @param {Uint8Array} p Public key + */ + cryptoScalarmult(q: Uint8Array, s: Uint8Array, p: Uint8Array): void { + const x = new Int32Array(80) + let r, i + const a = this.gf(), b = this.gf(), c = this.gf(), + d = this.gf(), e = this.gf(), f = this.gf() + + this.unpack25519(x, p) + for (i = 0; i < 16; i++) { + b[i] = x[i] + d[i] = a[i] = c[i] = 0 + } + + a[0] = d[0] = 1 + for (i = 254; i >= 0; --i) { + r = (s[i >>> 3] >>> (i & 7)) & 1 + this.sel25519(a, b, r) + this.sel25519(c, d, r) + this.A(e, a, c) + this.Z(a, a, c) + this.A(c, b, d) + this.Z(b, b, d) + this.S(d, e) + this.S(f, a) + this.M(a, c, a) + this.M(c, b, e) + this.A(e, a, c) + this.Z(a, a, c) + this.S(b, a) + this.Z(c, d, f) + this.M(a, c, this._121665) + this.A(a, a, d) + this.M(c, c, a) + this.M(a, d, f) + this.M(d, b, x) + this.S(b, e) + this.sel25519(a, b, r) + this.sel25519(c, d, r) + } + + for (i = 0; i < 16; i++) { + x[i + 16] = a[i] + x[i + 32] = c[i] + x[i + 48] = b[i] + x[i + 64] = d[i] + } + + const x32 = x.subarray(32) + const x16 = x.subarray(16) + this.inv25519(x32, x32) + this.M(x16, x16, x32) + this.pack25519(q, x16) + } + + /** + * Generate the common key as the produkt of sk1 * pk2 + * @param {Uint8Array} sk A 32 byte secret key of pair 1 + * @param {Uint8Array} pk A 32 byte public key of pair 2 + * @return {Uint8Array} sk * pk + */ + scalarMult(sk: Uint8Array, pk: Uint8Array) { + const q = new Uint8Array(32) + this.cryptoScalarmult(q, sk, pk) + + return q + } + + /** + * Generate a curve 25519 keypair + * @param {Uint8Array} seed A 32 byte cryptographic secure random array. This is basically the secret key + * @param {Object} Returns sk (Secret key) and pk (Public key) as 32 byte typed arrays + */ + generateKeys(seed: Uint8Array): { sk: Uint8Array, pk: Uint8Array } { + const sk = seed.slice() + const pk = new Uint8Array(32) + if (sk.length !== 32) { + throw 'Invalid secret key size, expected 32 bytes' + } + + sk[0] &= 0xf8 + sk[31] &= 0x7f + sk[31] |= 0x40 + + this.cryptoScalarmult(pk, sk, this._9) + + return { + sk, + pk, + } + } + +} diff --git a/lib/util/util.ts b/lib/util/util.ts new file mode 100644 index 0000000..bf0f721 --- /dev/null +++ b/lib/util/util.ts @@ -0,0 +1,30 @@ +export class Util { + + /** + * Time constant comparison of two arrays + * + * @param {Uint8Array} lh First array of bytes + * @param {Uint8Array} rh Second array of bytes + * @return {Boolean} True if the arrays are equal (length and content), false otherwise + */ + static compare(lh: Uint8Array, rh: Uint8Array): boolean { + if (lh.length !== rh.length) { + return false + } + + let i + let d = 0 + const len = lh.length + + for (i = 0; i < len; i++) { + d |= lh[i] ^ rh[i] + } + + return d === 0 + } + + static normalizeUTF8 = (str: string): string => { + return str ? str.normalize('NFKD') : '' + } + +} \ No newline at end of file diff --git a/lib/words.ts b/lib/words.ts new file mode 100644 index 0000000..2708a2a --- /dev/null +++ b/lib/words.ts @@ -0,0 +1,2054 @@ +const words = [ + + 'abandon', + 'ability', + 'able', + 'about', + 'above', + 'absent', + 'absorb', + 'abstract', + 'absurd', + 'abuse', + 'access', + 'accident', + 'account', + 'accuse', + 'achieve', + 'acid', + 'acoustic', + 'acquire', + 'across', + 'act', + 'action', + 'actor', + 'actress', + 'actual', + 'adapt', + 'add', + 'addict', + 'address', + 'adjust', + 'admit', + 'adult', + 'advance', + 'advice', + 'aerobic', + 'affair', + 'afford', + 'afraid', + 'again', + 'age', + 'agent', + 'agree', + 'ahead', + 'aim', + 'air', + 'airport', + 'aisle', + 'alarm', + 'album', + 'alcohol', + 'alert', + 'alien', + 'all', + 'alley', + 'allow', + 'almost', + 'alone', + 'alpha', + 'already', + 'also', + 'alter', + 'always', + 'amateur', + 'amazing', + 'among', + 'amount', + 'amused', + 'analyst', + 'anchor', + 'ancient', + 'anger', + 'angle', + 'angry', + 'animal', + 'ankle', + 'announce', + 'annual', + 'another', + 'answer', + 'antenna', + 'antique', + 'anxiety', + 'any', + 'apart', + 'apology', + 'appear', + 'apple', + 'approve', + 'april', + 'arch', + 'arctic', + 'area', + 'arena', + 'argue', + 'arm', + 'armed', + 'armor', + 'army', + 'around', + 'arrange', + 'arrest', + 'arrive', + 'arrow', + 'art', + 'artefact', + 'artist', + 'artwork', + 'ask', + 'aspect', + 'assault', + 'asset', + 'assist', + 'assume', + 'asthma', + 'athlete', + 'atom', + 'attack', + 'attend', + 'attitude', + 'attract', + 'auction', + 'audit', + 'august', + 'aunt', + 'author', + 'auto', + 'autumn', + 'average', + 'avocado', + 'avoid', + 'awake', + 'aware', + 'away', + 'awesome', + 'awful', + 'awkward', + 'axis', + 'baby', + 'bachelor', + 'bacon', + 'badge', + 'bag', + 'balance', + 'balcony', + 'ball', + 'bamboo', + 'banana', + 'banner', + 'bar', + 'barely', + 'bargain', + 'barrel', + 'base', + 'basic', + 'basket', + 'battle', + 'beach', + 'bean', + 'beauty', + 'because', + 'become', + 'beef', + 'before', + 'begin', + 'behave', + 'behind', + 'believe', + 'below', + 'belt', + 'bench', + 'benefit', + 'best', + 'betray', + 'better', + 'between', + 'beyond', + 'bicycle', + 'bid', + 'bike', + 'bind', + 'biology', + 'bird', + 'birth', + 'bitter', + 'black', + 'blade', + 'blame', + 'blanket', + 'blast', + 'bleak', + 'bless', + 'blind', + 'blood', + 'blossom', + 'blouse', + 'blue', + 'blur', + 'blush', + 'board', + 'boat', + 'body', + 'boil', + 'bomb', + 'bone', + 'bonus', + 'book', + 'boost', + 'border', + 'boring', + 'borrow', + 'boss', + 'bottom', + 'bounce', + 'box', + 'boy', + 'bracket', + 'brain', + 'brand', + 'brass', + 'brave', + 'bread', + 'breeze', + 'brick', + 'bridge', + 'brief', + 'bright', + 'bring', + 'brisk', + 'broccoli', + 'broken', + 'bronze', + 'broom', + 'brother', + 'brown', + 'brush', + 'bubble', + 'buddy', + 'budget', + 'buffalo', + 'build', + 'bulb', + 'bulk', + 'bullet', + 'bundle', + 'bunker', + 'burden', + 'burger', + 'burst', + 'bus', + 'business', + 'busy', + 'butter', + 'buyer', + 'buzz', + 'cabbage', + 'cabin', + 'cable', + 'cactus', + 'cage', + 'cake', + 'call', + 'calm', + 'camera', + 'camp', + 'can', + 'canal', + 'cancel', + 'candy', + 'cannon', + 'canoe', + 'canvas', + 'canyon', + 'capable', + 'capital', + 'captain', + 'car', + 'carbon', + 'card', + 'cargo', + 'carpet', + 'carry', + 'cart', + 'case', + 'cash', + 'casino', + 'castle', + 'casual', + 'cat', + 'catalog', + 'catch', + 'category', + 'cattle', + 'caught', + 'cause', + 'caution', + 'cave', + 'ceiling', + 'celery', + 'cement', + 'census', + 'century', + 'cereal', + 'certain', + 'chair', + 'chalk', + 'champion', + 'change', + 'chaos', + 'chapter', + 'charge', + 'chase', + 'chat', + 'cheap', + 'check', + 'cheese', + 'chef', + 'cherry', + 'chest', + 'chicken', + 'chief', + 'child', + 'chimney', + 'choice', + 'choose', + 'chronic', + 'chuckle', + 'chunk', + 'churn', + 'cigar', + 'cinnamon', + 'circle', + 'citizen', + 'city', + 'civil', + 'claim', + 'clap', + 'clarify', + 'claw', + 'clay', + 'clean', + 'clerk', + 'clever', + 'click', + 'client', + 'cliff', + 'climb', + 'clinic', + 'clip', + 'clock', + 'clog', + 'close', + 'cloth', + 'cloud', + 'clown', + 'club', + 'clump', + 'cluster', + 'clutch', + 'coach', + 'coast', + 'coconut', + 'code', + 'coffee', + 'coil', + 'coin', + 'collect', + 'color', + 'column', + 'combine', + 'come', + 'comfort', + 'comic', + 'common', + 'company', + 'concert', + 'conduct', + 'confirm', + 'congress', + 'connect', + 'consider', + 'control', + 'convince', + 'cook', + 'cool', + 'copper', + 'copy', + 'coral', + 'core', + 'corn', + 'correct', + 'cost', + 'cotton', + 'couch', + 'country', + 'couple', + 'course', + 'cousin', + 'cover', + 'coyote', + 'crack', + 'cradle', + 'craft', + 'cram', + 'crane', + 'crash', + 'crater', + 'crawl', + 'crazy', + 'cream', + 'credit', + 'creek', + 'crew', + 'cricket', + 'crime', + 'crisp', + 'critic', + 'crop', + 'cross', + 'crouch', + 'crowd', + 'crucial', + 'cruel', + 'cruise', + 'crumble', + 'crunch', + 'crush', + 'cry', + 'crystal', + 'cube', + 'culture', + 'cup', + 'cupboard', + 'curious', + 'current', + 'curtain', + 'curve', + 'cushion', + 'custom', + 'cute', + 'cycle', + 'dad', + 'damage', + 'damp', + 'dance', + 'danger', + 'daring', + 'dash', + 'daughter', + 'dawn', + 'day', + 'deal', + 'debate', + 'debris', + 'decade', + 'december', + 'decide', + 'decline', + 'decorate', + 'decrease', + 'deer', + 'defense', + 'define', + 'defy', + 'degree', + 'delay', + 'deliver', + 'demand', + 'demise', + 'denial', + 'dentist', + 'deny', + 'depart', + 'depend', + 'deposit', + 'depth', + 'deputy', + 'derive', + 'describe', + 'desert', + 'design', + 'desk', + 'despair', + 'destroy', + 'detail', + 'detect', + 'develop', + 'device', + 'devote', + 'diagram', + 'dial', + 'diamond', + 'diary', + 'dice', + 'diesel', + 'diet', + 'differ', + 'digital', + 'dignity', + 'dilemma', + 'dinner', + 'dinosaur', + 'direct', + 'dirt', + 'disagree', + 'discover', + 'disease', + 'dish', + 'dismiss', + 'disorder', + 'display', + 'distance', + 'divert', + 'divide', + 'divorce', + 'dizzy', + 'doctor', + 'document', + 'dog', + 'doll', + 'dolphin', + 'domain', + 'donate', + 'donkey', + 'donor', + 'door', + 'dose', + 'double', + 'dove', + 'draft', + 'dragon', + 'drama', + 'drastic', + 'draw', + 'dream', + 'dress', + 'drift', + 'drill', + 'drink', + 'drip', + 'drive', + 'drop', + 'drum', + 'dry', + 'duck', + 'dumb', + 'dune', + 'during', + 'dust', + 'dutch', + 'duty', + 'dwarf', + 'dynamic', + 'eager', + 'eagle', + 'early', + 'earn', + 'earth', + 'easily', + 'east', + 'easy', + 'echo', + 'ecology', + 'economy', + 'edge', + 'edit', + 'educate', + 'effort', + 'egg', + 'eight', + 'either', + 'elbow', + 'elder', + 'electric', + 'elegant', + 'element', + 'elephant', + 'elevator', + 'elite', + 'else', + 'embark', + 'embody', + 'embrace', + 'emerge', + 'emotion', + 'employ', + 'empower', + 'empty', + 'enable', + 'enact', + 'end', + 'endless', + 'endorse', + 'enemy', + 'energy', + 'enforce', + 'engage', + 'engine', + 'enhance', + 'enjoy', + 'enlist', + 'enough', + 'enrich', + 'enroll', + 'ensure', + 'enter', + 'entire', + 'entry', + 'envelope', + 'episode', + 'equal', + 'equip', + 'era', + 'erase', + 'erode', + 'erosion', + 'error', + 'erupt', + 'escape', + 'essay', + 'essence', + 'estate', + 'eternal', + 'ethics', + 'evidence', + 'evil', + 'evoke', + 'evolve', + 'exact', + 'example', + 'excess', + 'exchange', + 'excite', + 'exclude', + 'excuse', + 'execute', + 'exercise', + 'exhaust', + 'exhibit', + 'exile', + 'exist', + 'exit', + 'exotic', + 'expand', + 'expect', + 'expire', + 'explain', + 'expose', + 'express', + 'extend', + 'extra', + 'eye', + 'eyebrow', + 'fabric', + 'face', + 'faculty', + 'fade', + 'faint', + 'faith', + 'fall', + 'false', + 'fame', + 'family', + 'famous', + 'fan', + 'fancy', + 'fantasy', + 'farm', + 'fashion', + 'fat', + 'fatal', + 'father', + 'fatigue', + 'fault', + 'favorite', + 'feature', + 'february', + 'federal', + 'fee', + 'feed', + 'feel', + 'female', + 'fence', + 'festival', + 'fetch', + 'fever', + 'few', + 'fiber', + 'fiction', + 'field', + 'figure', + 'file', + 'film', + 'filter', + 'final', + 'find', + 'fine', + 'finger', + 'finish', + 'fire', + 'firm', + 'first', + 'fiscal', + 'fish', + 'fit', + 'fitness', + 'fix', + 'flag', + 'flame', + 'flash', + 'flat', + 'flavor', + 'flee', + 'flight', + 'flip', + 'float', + 'flock', + 'floor', + 'flower', + 'fluid', + 'flush', + 'fly', + 'foam', + 'focus', + 'fog', + 'foil', + 'fold', + 'follow', + 'food', + 'foot', + 'force', + 'forest', + 'forget', + 'fork', + 'fortune', + 'forum', + 'forward', + 'fossil', + 'foster', + 'found', + 'fox', + 'fragile', + 'frame', + 'frequent', + 'fresh', + 'friend', + 'fringe', + 'frog', + 'front', + 'frost', + 'frown', + 'frozen', + 'fruit', + 'fuel', + 'fun', + 'funny', + 'furnace', + 'fury', + 'future', + 'gadget', + 'gain', + 'galaxy', + 'gallery', + 'game', + 'gap', + 'garage', + 'garbage', + 'garden', + 'garlic', + 'garment', + 'gas', + 'gasp', + 'gate', + 'gather', + 'gauge', + 'gaze', + 'general', + 'genius', + 'genre', + 'gentle', + 'genuine', + 'gesture', + 'ghost', + 'giant', + 'gift', + 'giggle', + 'ginger', + 'giraffe', + 'girl', + 'give', + 'glad', + 'glance', + 'glare', + 'glass', + 'glide', + 'glimpse', + 'globe', + 'gloom', + 'glory', + 'glove', + 'glow', + 'glue', + 'goat', + 'goddess', + 'gold', + 'good', + 'goose', + 'gorilla', + 'gospel', + 'gossip', + 'govern', + 'gown', + 'grab', + 'grace', + 'grain', + 'grant', + 'grape', + 'grass', + 'gravity', + 'great', + 'green', + 'grid', + 'grief', + 'grit', + 'grocery', + 'group', + 'grow', + 'grunt', + 'guard', + 'guess', + 'guide', + 'guilt', + 'guitar', + 'gun', + 'gym', + 'habit', + 'hair', + 'half', + 'hammer', + 'hamster', + 'hand', + 'happy', + 'harbor', + 'hard', + 'harsh', + 'harvest', + 'hat', + 'have', + 'hawk', + 'hazard', + 'head', + 'health', + 'heart', + 'heavy', + 'hedgehog', + 'height', + 'hello', + 'helmet', + 'help', + 'hen', + 'hero', + 'hidden', + 'high', + 'hill', + 'hint', + 'hip', + 'hire', + 'history', + 'hobby', + 'hockey', + 'hold', + 'hole', + 'holiday', + 'hollow', + 'home', + 'honey', + 'hood', + 'hope', + 'horn', + 'horror', + 'horse', + 'hospital', + 'host', + 'hotel', + 'hour', + 'hover', + 'hub', + 'huge', + 'human', + 'humble', + 'humor', + 'hundred', + 'hungry', + 'hunt', + 'hurdle', + 'hurry', + 'hurt', + 'husband', + 'hybrid', + 'ice', + 'icon', + 'idea', + 'identify', + 'idle', + 'ignore', + 'ill', + 'illegal', + 'illness', + 'image', + 'imitate', + 'immense', + 'immune', + 'impact', + 'impose', + 'improve', + 'impulse', + 'inch', + 'include', + 'income', + 'increase', + 'index', + 'indicate', + 'indoor', + 'industry', + 'infant', + 'inflict', + 'inform', + 'inhale', + 'inherit', + 'initial', + 'inject', + 'injury', + 'inmate', + 'inner', + 'innocent', + 'input', + 'inquiry', + 'insane', + 'insect', + 'inside', + 'inspire', + 'install', + 'intact', + 'interest', + 'into', + 'invest', + 'invite', + 'involve', + 'iron', + 'island', + 'isolate', + 'issue', + 'item', + 'ivory', + 'jacket', + 'jaguar', + 'jar', + 'jazz', + 'jealous', + 'jeans', + 'jelly', + 'jewel', + 'job', + 'join', + 'joke', + 'journey', + 'joy', + 'judge', + 'juice', + 'jump', + 'jungle', + 'junior', + 'junk', + 'just', + 'kangaroo', + 'keen', + 'keep', + 'ketchup', + 'key', + 'kick', + 'kid', + 'kidney', + 'kind', + 'kingdom', + 'kiss', + 'kit', + 'kitchen', + 'kite', + 'kitten', + 'kiwi', + 'knee', + 'knife', + 'knock', + 'know', + 'lab', + 'label', + 'labor', + 'ladder', + 'lady', + 'lake', + 'lamp', + 'language', + 'laptop', + 'large', + 'later', + 'latin', + 'laugh', + 'laundry', + 'lava', + 'law', + 'lawn', + 'lawsuit', + 'layer', + 'lazy', + 'leader', + 'leaf', + 'learn', + 'leave', + 'lecture', + 'left', + 'leg', + 'legal', + 'legend', + 'leisure', + 'lemon', + 'lend', + 'length', + 'lens', + 'leopard', + 'lesson', + 'letter', + 'level', + 'liar', + 'liberty', + 'library', + 'license', + 'life', + 'lift', + 'light', + 'like', + 'limb', + 'limit', + 'link', + 'lion', + 'liquid', + 'list', + 'little', + 'live', + 'lizard', + 'load', + 'loan', + 'lobster', + 'local', + 'lock', + 'logic', + 'lonely', + 'long', + 'loop', + 'lottery', + 'loud', + 'lounge', + 'love', + 'loyal', + 'lucky', + 'luggage', + 'lumber', + 'lunar', + 'lunch', + 'luxury', + 'lyrics', + 'machine', + 'mad', + 'magic', + 'magnet', + 'maid', + 'mail', + 'main', + 'major', + 'make', + 'mammal', + 'man', + 'manage', + 'mandate', + 'mango', + 'mansion', + 'manual', + 'maple', + 'marble', + 'march', + 'margin', + 'marine', + 'market', + 'marriage', + 'mask', + 'mass', + 'master', + 'match', + 'material', + 'math', + 'matrix', + 'matter', + 'maximum', + 'maze', + 'meadow', + 'mean', + 'measure', + 'meat', + 'mechanic', + 'medal', + 'media', + 'melody', + 'melt', + 'member', + 'memory', + 'mention', + 'menu', + 'mercy', + 'merge', + 'merit', + 'merry', + 'mesh', + 'message', + 'metal', + 'method', + 'middle', + 'midnight', + 'milk', + 'million', + 'mimic', + 'mind', + 'minimum', + 'minor', + 'minute', + 'miracle', + 'mirror', + 'misery', + 'miss', + 'mistake', + 'mix', + 'mixed', + 'mixture', + 'mobile', + 'model', + 'modify', + 'mom', + 'moment', + 'monitor', + 'monkey', + 'monster', + 'month', + 'moon', + 'moral', + 'more', + 'morning', + 'mosquito', + 'mother', + 'motion', + 'motor', + 'mountain', + 'mouse', + 'move', + 'movie', + 'much', + 'muffin', + 'mule', + 'multiply', + 'muscle', + 'museum', + 'mushroom', + 'music', + 'must', + 'mutual', + 'myself', + 'mystery', + 'myth', + 'naive', + 'name', + 'napkin', + 'narrow', + 'nasty', + 'nation', + 'nature', + 'near', + 'neck', + 'need', + 'negative', + 'neglect', + 'neither', + 'nephew', + 'nerve', + 'nest', + 'net', + 'network', + 'neutral', + 'never', + 'news', + 'next', + 'nice', + 'night', + 'noble', + 'noise', + 'nominee', + 'noodle', + 'normal', + 'north', + 'nose', + 'notable', + 'note', + 'nothing', + 'notice', + 'novel', + 'now', + 'nuclear', + 'number', + 'nurse', + 'nut', + 'oak', + 'obey', + 'object', + 'oblige', + 'obscure', + 'observe', + 'obtain', + 'obvious', + 'occur', + 'ocean', + 'october', + 'odor', + 'off', + 'offer', + 'office', + 'often', + 'oil', + 'okay', + 'old', + 'olive', + 'olympic', + 'omit', + 'once', + 'one', + 'onion', + 'online', + 'only', + 'open', + 'opera', + 'opinion', + 'oppose', + 'option', + 'orange', + 'orbit', + 'orchard', + 'order', + 'ordinary', + 'organ', + 'orient', + 'original', + 'orphan', + 'ostrich', + 'other', + 'outdoor', + 'outer', + 'output', + 'outside', + 'oval', + 'oven', + 'over', + 'own', + 'owner', + 'oxygen', + 'oyster', + 'ozone', + 'pact', + 'paddle', + 'page', + 'pair', + 'palace', + 'palm', + 'panda', + 'panel', + 'panic', + 'panther', + 'paper', + 'parade', + 'parent', + 'park', + 'parrot', + 'party', + 'pass', + 'patch', + 'path', + 'patient', + 'patrol', + 'pattern', + 'pause', + 'pave', + 'payment', + 'peace', + 'peanut', + 'pear', + 'peasant', + 'pelican', + 'pen', + 'penalty', + 'pencil', + 'people', + 'pepper', + 'perfect', + 'permit', + 'person', + 'pet', + 'phone', + 'photo', + 'phrase', + 'physical', + 'piano', + 'picnic', + 'picture', + 'piece', + 'pig', + 'pigeon', + 'pill', + 'pilot', + 'pink', + 'pioneer', + 'pipe', + 'pistol', + 'pitch', + 'pizza', + 'place', + 'planet', + 'plastic', + 'plate', + 'play', + 'please', + 'pledge', + 'pluck', + 'plug', + 'plunge', + 'poem', + 'poet', + 'point', + 'polar', + 'pole', + 'police', + 'pond', + 'pony', + 'pool', + 'popular', + 'portion', + 'position', + 'possible', + 'post', + 'potato', + 'pottery', + 'poverty', + 'powder', + 'power', + 'practice', + 'praise', + 'predict', + 'prefer', + 'prepare', + 'present', + 'pretty', + 'prevent', + 'price', + 'pride', + 'primary', + 'print', + 'priority', + 'prison', + 'private', + 'prize', + 'problem', + 'process', + 'produce', + 'profit', + 'program', + 'project', + 'promote', + 'proof', + 'property', + 'prosper', + 'protect', + 'proud', + 'provide', + 'public', + 'pudding', + 'pull', + 'pulp', + 'pulse', + 'pumpkin', + 'punch', + 'pupil', + 'puppy', + 'purchase', + 'purity', + 'purpose', + 'purse', + 'push', + 'put', + 'puzzle', + 'pyramid', + 'quality', + 'quantum', + 'quarter', + 'question', + 'quick', + 'quit', + 'quiz', + 'quote', + 'rabbit', + 'raccoon', + 'race', + 'rack', + 'radar', + 'radio', + 'rail', + 'rain', + 'raise', + 'rally', + 'ramp', + 'ranch', + 'random', + 'range', + 'rapid', + 'rare', + 'rate', + 'rather', + 'raven', + 'raw', + 'razor', + 'ready', + 'real', + 'reason', + 'rebel', + 'rebuild', + 'recall', + 'receive', + 'recipe', + 'record', + 'recycle', + 'reduce', + 'reflect', + 'reform', + 'refuse', + 'region', + 'regret', + 'regular', + 'reject', + 'relax', + 'release', + 'relief', + 'rely', + 'remain', + 'remember', + 'remind', + 'remove', + 'render', + 'renew', + 'rent', + 'reopen', + 'repair', + 'repeat', + 'replace', + 'report', + 'require', + 'rescue', + 'resemble', + 'resist', + 'resource', + 'response', + 'result', + 'retire', + 'retreat', + 'return', + 'reunion', + 'reveal', + 'review', + 'reward', + 'rhythm', + 'rib', + 'ribbon', + 'rice', + 'rich', + 'ride', + 'ridge', + 'rifle', + 'right', + 'rigid', + 'ring', + 'riot', + 'ripple', + 'risk', + 'ritual', + 'rival', + 'river', + 'road', + 'roast', + 'robot', + 'robust', + 'rocket', + 'romance', + 'roof', + 'rookie', + 'room', + 'rose', + 'rotate', + 'rough', + 'round', + 'route', + 'royal', + 'rubber', + 'rude', + 'rug', + 'rule', + 'run', + 'runway', + 'rural', + 'sad', + 'saddle', + 'sadness', + 'safe', + 'sail', + 'salad', + 'salmon', + 'salon', + 'salt', + 'salute', + 'same', + 'sample', + 'sand', + 'satisfy', + 'satoshi', + 'sauce', + 'sausage', + 'save', + 'say', + 'scale', + 'scan', + 'scare', + 'scatter', + 'scene', + 'scheme', + 'school', + 'science', + 'scissors', + 'scorpion', + 'scout', + 'scrap', + 'screen', + 'script', + 'scrub', + 'sea', + 'search', + 'season', + 'seat', + 'second', + 'secret', + 'section', + 'security', + 'seed', + 'seek', + 'segment', + 'select', + 'sell', + 'seminar', + 'senior', + 'sense', + 'sentence', + 'series', + 'service', + 'session', + 'settle', + 'setup', + 'seven', + 'shadow', + 'shaft', + 'shallow', + 'share', + 'shed', + 'shell', + 'sheriff', + 'shield', + 'shift', + 'shine', + 'ship', + 'shiver', + 'shock', + 'shoe', + 'shoot', + 'shop', + 'short', + 'shoulder', + 'shove', + 'shrimp', + 'shrug', + 'shuffle', + 'shy', + 'sibling', + 'sick', + 'side', + 'siege', + 'sight', + 'sign', + 'silent', + 'silk', + 'silly', + 'silver', + 'similar', + 'simple', + 'since', + 'sing', + 'siren', + 'sister', + 'situate', + 'six', + 'size', + 'skate', + 'sketch', + 'ski', + 'skill', + 'skin', + 'skirt', + 'skull', + 'slab', + 'slam', + 'sleep', + 'slender', + 'slice', + 'slide', + 'slight', + 'slim', + 'slogan', + 'slot', + 'slow', + 'slush', + 'small', + 'smart', + 'smile', + 'smoke', + 'smooth', + 'snack', + 'snake', + 'snap', + 'sniff', + 'snow', + 'soap', + 'soccer', + 'social', + 'sock', + 'soda', + 'soft', + 'solar', + 'soldier', + 'solid', + 'solution', + 'solve', + 'someone', + 'song', + 'soon', + 'sorry', + 'sort', + 'soul', + 'sound', + 'soup', + 'source', + 'south', + 'space', + 'spare', + 'spatial', + 'spawn', + 'speak', + 'special', + 'speed', + 'spell', + 'spend', + 'sphere', + 'spice', + 'spider', + 'spike', + 'spin', + 'spirit', + 'split', + 'spoil', + 'sponsor', + 'spoon', + 'sport', + 'spot', + 'spray', + 'spread', + 'spring', + 'spy', + 'square', + 'squeeze', + 'squirrel', + 'stable', + 'stadium', + 'staff', + 'stage', + 'stairs', + 'stamp', + 'stand', + 'start', + 'state', + 'stay', + 'steak', + 'steel', + 'stem', + 'step', + 'stereo', + 'stick', + 'still', + 'sting', + 'stock', + 'stomach', + 'stone', + 'stool', + 'story', + 'stove', + 'strategy', + 'street', + 'strike', + 'strong', + 'struggle', + 'student', + 'stuff', + 'stumble', + 'style', + 'subject', + 'submit', + 'subway', + 'success', + 'such', + 'sudden', + 'suffer', + 'sugar', + 'suggest', + 'suit', + 'summer', + 'sun', + 'sunny', + 'sunset', + 'super', + 'supply', + 'supreme', + 'sure', + 'surface', + 'surge', + 'surprise', + 'surround', + 'survey', + 'suspect', + 'sustain', + 'swallow', + 'swamp', + 'swap', + 'swarm', + 'swear', + 'sweet', + 'swift', + 'swim', + 'swing', + 'switch', + 'sword', + 'symbol', + 'symptom', + 'syrup', + 'system', + 'table', + 'tackle', + 'tag', + 'tail', + 'talent', + 'talk', + 'tank', + 'tape', + 'target', + 'task', + 'taste', + 'tattoo', + 'taxi', + 'teach', + 'team', + 'tell', + 'ten', + 'tenant', + 'tennis', + 'tent', + 'term', + 'test', + 'text', + 'thank', + 'that', + 'theme', + 'then', + 'theory', + 'there', + 'they', + 'thing', + 'this', + 'thought', + 'three', + 'thrive', + 'throw', + 'thumb', + 'thunder', + 'ticket', + 'tide', + 'tiger', + 'tilt', + 'timber', + 'time', + 'tiny', + 'tip', + 'tired', + 'tissue', + 'title', + 'toast', + 'tobacco', + 'today', + 'toddler', + 'toe', + 'together', + 'toilet', + 'token', + 'tomato', + 'tomorrow', + 'tone', + 'tongue', + 'tonight', + 'tool', + 'tooth', + 'top', + 'topic', + 'topple', + 'torch', + 'tornado', + 'tortoise', + 'toss', + 'total', + 'tourist', + 'toward', + 'tower', + 'town', + 'toy', + 'track', + 'trade', + 'traffic', + 'tragic', + 'train', + 'transfer', + 'trap', + 'trash', + 'travel', + 'tray', + 'treat', + 'tree', + 'trend', + 'trial', + 'tribe', + 'trick', + 'trigger', + 'trim', + 'trip', + 'trophy', + 'trouble', + 'truck', + 'true', + 'truly', + 'trumpet', + 'trust', + 'truth', + 'try', + 'tube', + 'tuition', + 'tumble', + 'tuna', + 'tunnel', + 'turkey', + 'turn', + 'turtle', + 'twelve', + 'twenty', + 'twice', + 'twin', + 'twist', + 'two', + 'type', + 'typical', + 'ugly', + 'umbrella', + 'unable', + 'unaware', + 'uncle', + 'uncover', + 'under', + 'undo', + 'unfair', + 'unfold', + 'unhappy', + 'uniform', + 'unique', + 'unit', + 'universe', + 'unknown', + 'unlock', + 'until', + 'unusual', + 'unveil', + 'update', + 'upgrade', + 'uphold', + 'upon', + 'upper', + 'upset', + 'urban', + 'urge', + 'usage', + 'use', + 'used', + 'useful', + 'useless', + 'usual', + 'utility', + 'vacant', + 'vacuum', + 'vague', + 'valid', + 'valley', + 'valve', + 'van', + 'vanish', + 'vapor', + 'various', + 'vast', + 'vault', + 'vehicle', + 'velvet', + 'vendor', + 'venture', + 'venue', + 'verb', + 'verify', + 'version', + 'very', + 'vessel', + 'veteran', + 'viable', + 'vibrant', + 'vicious', + 'victory', + 'video', + 'view', + 'village', + 'vintage', + 'violin', + 'virtual', + 'virus', + 'visa', + 'visit', + 'visual', + 'vital', + 'vivid', + 'vocal', + 'voice', + 'void', + 'volcano', + 'volume', + 'vote', + 'voyage', + 'wage', + 'wagon', + 'wait', + 'walk', + 'wall', + 'walnut', + 'want', + 'warfare', + 'warm', + 'warrior', + 'wash', + 'wasp', + 'waste', + 'water', + 'wave', + 'way', + 'wealth', + 'weapon', + 'wear', + 'weasel', + 'weather', + 'web', + 'wedding', + 'weekend', + 'weird', + 'welcome', + 'west', + 'wet', + 'whale', + 'what', + 'wheat', + 'wheel', + 'when', + 'where', + 'whip', + 'whisper', + 'wide', + 'width', + 'wife', + 'wild', + 'will', + 'win', + 'window', + 'wine', + 'wing', + 'wink', + 'winner', + 'winter', + 'wire', + 'wisdom', + 'wise', + 'wish', + 'witness', + 'wolf', + 'woman', + 'wonder', + 'wood', + 'wool', + 'word', + 'work', + 'world', + 'worry', + 'worth', + 'wrap', + 'wreck', + 'wrestle', + 'wrist', + 'write', + 'wrong', + 'yard', + 'year', + 'yellow', + 'you', + 'young', + 'youth', + 'zebra', + 'zero', + 'zone', + 'zoo', + +] + +export default words diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..7783b0d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,30 @@ +{ + "name": "nanocurrency-web", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "12.7.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.12.tgz", + "integrity": "sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ==", + "dev": true + }, + "bignumber": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bignumber/-/bignumber-1.1.0.tgz", + "integrity": "sha1-5qsKdD2l8+oBjlwXWX0SH3howVk=" + }, + "blakejs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.0.tgz", + "integrity": "sha1-ad+S75U6qIylGjLfarHFShVfx6U=" + }, + "typescript": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz", + "integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4bf0015 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "nanocurrency-web", + "version": "1.0.0", + "description": "Toolset for Nano cryptocurrency client side offline integrations", + "author": "Miro Metsänheimo ", + "license": "MIT", + "homepage": "https://github.com/numsu/nanocurrency-web-js#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/numsu/nanocurrency-web-js.git" + }, + "bugs": { + "url": "https://github.com/numsu/nanocurrency-web-js/issues" + }, + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "bignumber": "^1.1.0", + "blakejs": "^1.1.0" + }, + "devDependencies": { + "@types/node": "^12.7.12", + "typescript": "3.6.3" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e3007c0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "declaration": true, + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "downlevelIteration": true, + "types": [ + "node", + "index.d.ts" + ], + "lib": [ + "es2017" + ] + } +} \ No newline at end of file -- 2.34.1