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
This commit is contained in:
Miro Metsänheimo 2019-10-09 20:23:07 +03:00
commit 08e7a140f3
16 changed files with 3862 additions and 0 deletions

117
index.ts Normal file
View file

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

38
lib/address-generator.ts Normal file
View file

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

53
lib/address-importer.ts Normal file
View file

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

View file

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

100
lib/bip39-mnemonic.ts Normal file
View file

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

118
lib/block-signer.ts Normal file
View file

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

250
lib/ed25519.ts Normal file
View file

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

88
lib/nano-address.ts Normal file
View file

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

48
lib/nano-converter.ts Normal file
View file

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

118
lib/util/convert.ts Normal file
View file

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

711
lib/util/curve25519.ts Normal file
View file

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

30
lib/util/util.ts Normal file
View file

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

2054
lib/words.ts Normal file

File diff suppressed because it is too large Load diff

30
package-lock.json generated Normal file
View file

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

27
package.json Normal file
View file

@ -0,0 +1,27 @@
{
"name": "nanocurrency-web",
"version": "1.0.0",
"description": "Toolset for Nano cryptocurrency client side offline integrations",
"author": "Miro Metsänheimo <miro@metsanheimo.fi>",
"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"
}
}

18
tsconfig.json Normal file
View file

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