Merge pull request #13 from numsu/encryption

Version 1.4.0
This commit is contained in:
Miro Metsänheimo 2022-04-24 21:29:56 +03:00 committed by GitHub
commit 6fefc4821b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 1138 additions and 265 deletions

View file

@ -33,7 +33,7 @@ npm install nanocurrency-web
### In web
```html
<script src="https://unpkg.com/nanocurrency-web@1.3.6" type="text/javascript"></script>
<script src="https://unpkg.com/nanocurrency-web@1.4.0" type="text/javascript"></script>
<script type="text/javascript">
NanocurrencyWeb.wallet.generate(...);
</script>
@ -114,7 +114,7 @@ If the account hasn't been opened yet (this is the first block), you will need t
```javascript
import { block } from 'nanocurrency-web'
const privateKey = '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3';
const privateKey = '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3'
const data = {
// Current balance from account info
walletBalanceRaw: '5618869000000000000000000000000',
@ -147,7 +147,7 @@ const signedBlock = block.send(data, privateKey)
```javascript
import { block } from 'nanocurrency-web'
const privateKey = '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3';
const privateKey = '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3'
const data = {
// Your current balance in RAW from account info
walletBalanceRaw: '18618869000000000000000000000000',
@ -180,7 +180,7 @@ const signedBlock = block.receive(data, privateKey)
```javascript
import { block } from 'nanocurrency-web'
const privateKey = '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3';
const privateKey = '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3'
const data = {
// Your current balance, from account info
walletBalanceRaw: '3000000000000000000000000000000',
@ -270,6 +270,19 @@ const valid = tools.validateAddress('nano_1pu7p5n3ghq1i1p4rhmek41f5add1uh34xpb94
const valid = tools.validateMnemonic('edge defense waste choose enrich upon flee junk siren film clown finish luggage leader kid quick brick print evidence swap drill paddle truly occur')
```
#### Encrypting and decrypting strings
You are able to encrypt and decrypt strings to implement end-to-end encryption using the Diffie-Hellman key exchange by using the Nano address and private key. The public and private keys are converted to Curve25519 keys which are suitable for encryption within the library.
```javascript
import { box } from 'nanocurrency-web'
// Encrypt on device 1
const encrypted = box.encrypt(message, recipientAddress, senderPrivateKey)
// Send the encrypted message to the recipient and decrypt on device 2
const decrypted = box.decrypt(encrypted, senderAddress, recipientPrivateKey)
```
## Contributions
You are welcome to contribute to the module. To develop, use the following commands.

View file

@ -3,16 +3,12 @@ import BigNumber from 'bignumber.js'
import AddressGenerator from './lib/address-generator'
import AddressImporter, { Account, Wallet } from './lib/address-importer'
import BlockSigner, { BlockData, ReceiveBlock, RepresentativeBlock, SendBlock, SignedBlock } from './lib/block-signer'
import Box from './lib/box'
import NanoAddress from './lib/nano-address'
import NanoConverter from './lib/nano-converter'
import Signer from './lib/signer'
import Convert from './lib/util/convert'
const nanoAddress = new NanoAddress()
const generator = new AddressGenerator()
const importer = new AddressImporter()
const signer = new Signer()
const wallet = {
/**
@ -38,7 +34,7 @@ const wallet = {
* @returns {Wallet} The wallet
*/
generate: (entropy?: string, seedPassword?: string): Wallet => {
return generator.generateWallet(entropy, seedPassword)
return AddressGenerator.generateWallet(entropy, seedPassword)
},
/**
@ -57,7 +53,7 @@ const wallet = {
* @returns {Wallet} The wallet
*/
generateLegacy: (seed?: string): Wallet => {
return generator.generateLegacyWallet(seed)
return AddressGenerator.generateLegacyWallet(seed)
},
/**
@ -76,7 +72,7 @@ const wallet = {
* @returns {Wallet} The wallet
*/
fromMnemonic: (mnemonic: string, seedPassword?: string): Wallet => {
return importer.fromMnemonic(mnemonic, seedPassword)
return AddressImporter.fromMnemonic(mnemonic, seedPassword)
},
/**
@ -93,7 +89,7 @@ const wallet = {
* @returns {Wallet} The wallet
*/
fromLegacyMnemonic: (mnemonic: string): Wallet => {
return importer.fromLegacyMnemonic(mnemonic)
return AddressImporter.fromLegacyMnemonic(mnemonic)
},
/**
@ -110,7 +106,7 @@ const wallet = {
* @returns {Wallet} The wallet, without the mnemonic phrase because it's not possible to infer backwards
*/
fromSeed: (seed: string): Wallet => {
return importer.fromSeed(seed)
return AddressImporter.fromSeed(seed)
},
/**
@ -127,7 +123,7 @@ const wallet = {
* @returns {Wallet} The wallet
*/
fromLegacySeed: (seed: string): Wallet => {
return importer.fromLegacySeed(seed)
return AddressImporter.fromLegacySeed(seed)
},
/**
@ -143,7 +139,7 @@ const wallet = {
* @returns {Account[]} a list of accounts
*/
accounts: (seed: string, from: number, to: number): Account[] => {
return importer.fromSeed(seed, from, to).accounts
return AddressImporter.fromSeed(seed, from, to).accounts
},
/**
@ -158,12 +154,11 @@ const wallet = {
* @returns {Account[]} a list of accounts
*/
legacyAccounts: (seed: string, from: number, to: number): Account[] => {
return importer.fromLegacySeed(seed, from, to).accounts
return AddressImporter.fromLegacySeed(seed, from, to).accounts
},
}
const blockSigner = new BlockSigner()
const block = {
/**
@ -185,7 +180,7 @@ const block = {
* @returns {SignedBlock} the signed block
*/
send: (data: SendBlock, privateKey: string): SignedBlock => {
return blockSigner.send(data, privateKey)
return BlockSigner.send(data, privateKey)
},
@ -208,7 +203,7 @@ const block = {
* @returns {SignedBlock} the signed block
*/
receive: (data: ReceiveBlock, privateKey: string): SignedBlock => {
return blockSigner.receive(data, privateKey)
return BlockSigner.receive(data, privateKey)
},
@ -236,7 +231,7 @@ const block = {
toAddress: 'nano_1111111111111111111111111111111111111111111111111111hifc8npp', // Burn address
}
return blockSigner.send(block, privateKey)
return BlockSigner.send(block, privateKey)
},
}
@ -266,7 +261,7 @@ const tools = {
*/
sign: (privateKey: string, ...input: string[]): string => {
const data = input.map(Convert.stringToHex)
return signer.sign(privateKey, ...data)
return Signer.sign(privateKey, ...data)
},
/**
@ -279,7 +274,7 @@ const tools = {
*/
verify: (publicKey: string, signature: string, ...input: string[]): boolean => {
const data = input.map(Convert.stringToHex)
return signer.verify(publicKey, signature, ...data)
return Signer.verify(publicKey, signature, ...data)
},
/**
@ -291,11 +286,11 @@ const tools = {
*/
verifyBlock: (publicKey: string, block: BlockData): boolean => {
const preamble = 0x6.toString().padStart(64, '0')
return signer.verify(publicKey, block.signature,
return Signer.verify(publicKey, block.signature,
preamble,
nanoAddress.nanoAddressToHexString(block.account),
NanoAddress.nanoAddressToHexString(block.account),
block.previous,
nanoAddress.nanoAddressToHexString(block.representative),
NanoAddress.nanoAddressToHexString(block.representative),
Convert.dec2hex(block.balance, 16).toUpperCase(),
block.link)
},
@ -307,7 +302,7 @@ const tools = {
* @returns {boolean} valid or not
*/
validateAddress: (input: string): boolean => {
return nanoAddress.validateNanoAddress(input)
return NanoAddress.validateNanoAddress(input)
},
/**
@ -317,7 +312,7 @@ const tools = {
* @returns {boolean} valid or not
*/
validateMnemonic: (input: string): boolean => {
return importer.validateMnemonic(input)
return AddressImporter.validateMnemonic(input)
},
/**
@ -327,11 +322,7 @@ const tools = {
* @returns {string} the public key
*/
addressToPublicKey: (input: string): string => {
const cleaned = input
.replace('nano_', '')
.replace('xrb_', '')
const publicKeyBytes = nanoAddress.decodeNanoBase32(cleaned)
return Convert.ab2hex(publicKeyBytes).slice(0, 64)
return NanoAddress.addressToPublicKey(input)
},
/**
@ -341,7 +332,7 @@ const tools = {
* @returns {string} the nano address
*/
publicKeyToAddress: (input: string): string => {
return nanoAddress.deriveAddress(input)
return NanoAddress.deriveAddress(input)
},
/**
@ -352,16 +343,56 @@ const tools = {
*/
blake2b: (input: string | string[]): string => {
if (Array.isArray(input)) {
return Convert.ab2hex(signer.generateHash(input.map(Convert.stringToHex)))
return Convert.ab2hex(Signer.generateHash(input.map(Convert.stringToHex)))
} else {
return Convert.ab2hex(signer.generateHash([Convert.stringToHex(input)]))
return Convert.ab2hex(Signer.generateHash([Convert.stringToHex(input)]))
}
},
}
const box = {
/**
* Encrypt a message using a Nano address private key for
* end-to-end encrypted messaging.
*
* Encrypts the message using the recipient's public key,
* the sender's private key and a random nonce generated
* inside the library. The message can be opened with the
* recipient's private key and the sender's public key by
* using the decrypt method.
*
* @param {string} message string to encrypt
* @param {string} address nano address of the recipient
* @param {string} privateKey private key of the sender
* @returns {string} encrypted message encoded in Base64
*/
encrypt: (message: string, address: string, privateKey: string): string => {
return Box.encrypt(message, address, privateKey)
},
/**
* Decrypt a message using a Nano address private key.
*
* Decrypts the message by using the sender's public key,
* the recipient's private key and the nonce which is included
* in the encrypted message.
*
* @param {string} encrypted string to decrypt
* @param {string} address nano address of the sender
* @param {string} privateKey private key of the recipient
* @returns {string} decrypted message encoded in UTF-8
*/
decrypt: (encrypted: string, address: string, privateKey: string): string => {
return Box.decrypt(encrypted, address, privateKey)
}
}
export {
wallet,
block,
tools,
box,
}

View file

@ -9,11 +9,9 @@ export default class AddressGenerator {
* @param {string} [entropy] - (Optional) Custom entropy if the caller doesn't want a default generated entropy
* @param {string} [seedPassword] - (Optional) Password for the seed
*/
generateWallet(entropy = '', seedPassword: string = ''): Wallet {
const bip39 = new Bip39Mnemonic(seedPassword)
const mnemonicSeed = bip39.createWallet(entropy)
const wallet = new AddressImporter().fromSeed(mnemonicSeed.seed, 0, 0)
static generateWallet = (entropy = '', seedPassword: string = ''): Wallet => {
const mnemonicSeed = Bip39Mnemonic.createWallet(entropy, seedPassword)
const wallet = AddressImporter.fromSeed(mnemonicSeed.seed, 0, 0)
return {
...wallet,
mnemonic: mnemonicSeed.mnemonic,
@ -22,14 +20,10 @@ export default class AddressGenerator {
/**
* Generates a legacy Nano wallet
*
*/
generateLegacyWallet(seed?: string): Wallet {
const bip39 = new Bip39Mnemonic()
const mnemonicSeed = bip39.createLegacyWallet(seed)
const wallet = new AddressImporter().fromLegacySeed(mnemonicSeed.seed, 0, 0, mnemonicSeed.mnemonic)
return wallet
static generateLegacyWallet = (seed?: string): Wallet => {
const mnemonicSeed = Bip39Mnemonic.createLegacyWallet(seed)
return AddressImporter.fromLegacySeed(mnemonicSeed.seed, 0, 0, mnemonicSeed.mnemonic)
}
}

View file

@ -14,13 +14,12 @@ export default class AddressImporter {
* @param {string} [seedPassword] - (Optional) The password to use to secure the mnemonic
* @returns {Wallet} - The wallet derived from the mnemonic phrase
*/
fromMnemonic(mnemonic: string, seedPassword = ''): Wallet {
const bip39 = new Bip39Mnemonic(seedPassword)
if (!bip39.validateMnemonic(mnemonic)) {
static fromMnemonic = (mnemonic: string, seedPassword = ''): Wallet => {
if (!Bip39Mnemonic.validateMnemonic(mnemonic)) {
throw new Error('Invalid mnemonic phrase')
}
const seed = bip39.mnemonicToSeed(mnemonic)
const seed = Bip39Mnemonic.mnemonicToSeed(mnemonic, seedPassword)
const accounts = this.accounts(seed, 0, 0)
return {
@ -36,13 +35,12 @@ export default class AddressImporter {
* @param {string} mnemonic - The mnemonic words to import the wallet from
* @returns {Wallet} - The wallet derived from the mnemonic phrase
*/
fromLegacyMnemonic(mnemonic: string): Wallet {
const bip39 = new Bip39Mnemonic()
if (!bip39.validateMnemonic(mnemonic)) {
static fromLegacyMnemonic = (mnemonic: string): Wallet => {
if (!Bip39Mnemonic.validateMnemonic(mnemonic)) {
throw new Error('Invalid mnemonic phrase')
}
const seed = bip39.mnemonicToLegacySeed(mnemonic)
const seed = Bip39Mnemonic.mnemonicToLegacySeed(mnemonic)
return this.fromLegacySeed(seed, 0, 0, mnemonic)
}
@ -51,9 +49,8 @@ export default class AddressImporter {
*
* @param mnemonic {string} mnemonic - The mnemonic words to validate
*/
validateMnemonic(mnemonic: string): boolean {
const bip39 = new Bip39Mnemonic()
return bip39.validateMnemonic(mnemonic);
static validateMnemonic = (mnemonic: string): boolean => {
return Bip39Mnemonic.validateMnemonic(mnemonic)
}
/**
@ -64,7 +61,7 @@ export default class AddressImporter {
* @param {number} [to] - (Optional) The end index of the private keys to derive to
* @returns {Wallet} The wallet derived from the mnemonic phrase
*/
fromSeed(seed: string, from = 0, to = 0): Wallet {
static fromSeed = (seed: string, from = 0, to = 0): Wallet => {
if (seed.length !== 128) {
throw new Error('Invalid seed length, must be a 128 byte hexadecimal string')
}
@ -90,7 +87,7 @@ export default class AddressImporter {
* @param {number} [to] - (Optional) The end index of the private keys to derive to
* @returns {Wallet} The wallet derived from the seed
*/
fromLegacySeed(seed: string, from: number = 0, to: number = 0, mnemonic?: string): Wallet {
static fromLegacySeed = (seed: string, from: number = 0, to: number = 0, mnemonic?: string): Wallet => {
if (seed.length !== 64) {
throw new Error('Invalid seed length, must be a 64 byte hexadecimal string')
}
@ -100,7 +97,7 @@ export default class AddressImporter {
const accounts = this.legacyAccounts(seed, from, to)
return {
mnemonic: mnemonic || new Bip39Mnemonic().deriveMnemonic(seed),
mnemonic: mnemonic || Bip39Mnemonic.deriveMnemonic(seed),
seed,
accounts,
}
@ -113,19 +110,13 @@ export default class AddressImporter {
* @param {number} from - The start index of private keys to derive from
* @param {number} to - The end index of private keys to derive to
*/
private accounts(seed: string, from: number, to: number): Account[] {
private static accounts = (seed: string, from: number, to: number): Account[] => {
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)
const privateKey = Bip32KeyDerivation.derivePath(`44'/165'/${i}'`, seed).key
const keyPair = new Ed25519().generateKeys(privateKey)
const address = NanoAddress.deriveAddress(keyPair.publicKey)
accounts.push({
accountIndex: i,
privateKey: keyPair.privateKey,
@ -144,18 +135,12 @@ export default class AddressImporter {
* @param {number} from - The start index of private keys to derive from
* @param {number} to - The end index of private keys to derive to
*/
private legacyAccounts(seed: string, from: number, to: number): Account[] {
const signer = new Signer()
private static legacyAccounts = (seed: string, from: number, to: number): Account[] => {
const accounts: Account[] = []
for (let i = from; i <= to; i++) {
const privateKey = Convert.ab2hex(signer.generateHash([seed, Convert.dec2hex(i, 4)]))
const ed25519 = new Ed25519()
const keyPair = ed25519.generateKeys(privateKey)
const nano = new NanoAddress()
const address = nano.deriveAddress(keyPair.publicKey)
const privateKey = Convert.ab2hex(Signer.generateHash([ seed, Convert.dec2hex(i, 4) ]))
const keyPair = new Ed25519().generateKeys(privateKey)
const address = NanoAddress.deriveAddress(keyPair.publicKey)
accounts.push({
accountIndex: i,
privateKey: keyPair.privateKey,

View file

@ -8,17 +8,9 @@ const HARDENED_OFFSET = 0x80000000
export default class Bip32KeyDerivation {
path: string
seed: string
constructor(path: string, seed: string) {
this.path = path
this.seed = seed
}
derivePath = (): Chain => {
const { key, chainCode } = this.getKeyFromSeed()
const segments = this.path
static derivePath = (path: string, seed: string): Chain => {
const { key, chainCode } = this.getKeyFromSeed(seed)
const segments = path
.split('/')
.map(v => v.replace('\'', ''))
.map(el => parseInt(el, 10))
@ -29,13 +21,13 @@ export default class Bip32KeyDerivation {
)
}
private getKeyFromSeed = (): Chain => {
private static getKeyFromSeed = (seed: string): Chain => {
return this.derive(
enc.Hex.parse(this.seed),
enc.Hex.parse(seed),
enc.Utf8.parse(ED25519_CURVE))
}
private CKDPriv = ({ key, chainCode }: Chain, index: number) => {
private static CKDPriv = ({ key, chainCode }: Chain, index: number) => {
const ib = []
ib.push((index >> 24) & 0xff)
ib.push((index >> 16) & 0xff)
@ -48,7 +40,7 @@ export default class Bip32KeyDerivation {
enc.Hex.parse(chainCode))
}
private derive = (data: string, base: string): Chain => {
private static derive = (data: string, base: string): Chain => {
const hmac = algo.HMAC.create(algo.SHA512, base)
const I = hmac.update(data).finalize().toString()
const IL = I.slice(0, I.length / 2)

View file

@ -7,19 +7,13 @@ import words from './words'
export default class Bip39Mnemonic {
password: string
constructor(password?: string) {
this.password = password
}
/**
* Creates a BIP39 wallet
*
* @param {string} [entropy] - (Optional) the entropy to use instead of generating
* @returns {MnemonicSeed} The mnemonic phrase and a seed derived from the (generated) entropy
*/
createWallet = (entropy: string): MnemonicSeed => {
static createWallet = (entropy: string, password: string): MnemonicSeed => {
if (entropy) {
if (entropy.length !== 64) {
throw new Error('Invalid entropy length, must be a 64 byte hexadecimal string')
@ -34,7 +28,7 @@ export default class Bip39Mnemonic {
}
const mnemonic = this.deriveMnemonic(entropy)
const seed = this.mnemonicToSeed(mnemonic)
const seed = this.mnemonicToSeed(mnemonic, password)
return {
mnemonic,
@ -48,7 +42,7 @@ export default class Bip39Mnemonic {
* @param {string} seed - (Optional) the seed to be used for the wallet
* @returns {MnemonicSeed} The mnemonic phrase and a generated seed if none provided
*/
createLegacyWallet = (seed?: string): MnemonicSeed => {
static createLegacyWallet = (seed?: string): MnemonicSeed => {
if (seed) {
if (seed.length !== 64) {
throw new Error('Invalid entropy length, must be a 64 byte hexadecimal string')
@ -70,7 +64,7 @@ export default class Bip39Mnemonic {
}
}
deriveMnemonic = (entropy: string): string => {
static deriveMnemonic = (entropy: string): string => {
const entropyBinary = Convert.hexStringToBinary(entropy)
const entropySha256Binary = Convert.hexStringToBinary(this.calculateChecksum(entropy))
const entropyBinaryWithChecksum = entropyBinary + entropySha256Binary
@ -89,7 +83,7 @@ export default class Bip39Mnemonic {
* @param {string} mnemonic - The mnemonic phrase to validate
* @returns {boolean} Is the mnemonic phrase valid
*/
validateMnemonic = (mnemonic: string): boolean => {
static validateMnemonic = (mnemonic: string): boolean => {
const wordArray = Util.normalizeUTF8(mnemonic).split(' ')
if (wordArray.length % 3 !== 0) {
return false
@ -136,7 +130,7 @@ export default class Bip39Mnemonic {
*
* @param {string} mnemonic Mnemonic phrase separated by spaces
*/
mnemonicToLegacySeed = (mnemonic: string): string => {
static mnemonicToLegacySeed = (mnemonic: string): string => {
const wordArray = Util.normalizeUTF8(mnemonic).split(' ')
const bits = wordArray.map((w: string) => {
const wordIndex = words.indexOf(w)
@ -159,9 +153,9 @@ export default class Bip39Mnemonic {
*
* @param {string} mnemonic Mnemonic phrase separated by spaces
*/
mnemonicToSeed = (mnemonic: string): string => {
static mnemonicToSeed = (mnemonic: string, password: string): string => {
const normalizedMnemonic = Util.normalizeUTF8(mnemonic)
const normalizedPassword = 'mnemonic' + Util.normalizeUTF8(this.password)
const normalizedPassword = 'mnemonic' + Util.normalizeUTF8(password)
return PBKDF2(
normalizedMnemonic,
@ -174,11 +168,11 @@ export default class Bip39Mnemonic {
.toString(enc.Hex)
}
private randomHex = (length: number): string => {
return lib.WordArray.random(length / 2).toString()
private static randomHex = (length: number): string => {
return lib.WordArray.random(length).toString()
}
private calculateChecksum = (entropyHex: string): string => {
private static calculateChecksum = (entropyHex: string): string => {
const entropySha256 = SHA256(enc.Hex.parse(entropyHex)).toString()
return entropySha256.substr(0, entropySha256.length / 32)
}

View file

@ -1,8 +1,5 @@
import BigNumber from 'bignumber.js'
//@ts-ignore
import { blake2b } from 'blakejs'
import Ed25519 from './ed25519'
import NanoAddress from './nano-address'
import NanoConverter from './nano-converter'
import Signer from './signer'
@ -10,11 +7,7 @@ import Convert from './util/convert'
export default class BlockSigner {
nanoAddress = new NanoAddress()
ed25519 = new Ed25519()
signer = new Signer()
preamble: string = 0x6.toString().padStart(64, '0')
static readonly preamble: string = 0x6.toString().padStart(64, '0')
/**
* Sign a receive block
@ -23,7 +16,7 @@ export default class BlockSigner {
* @param {string} privateKey Private key to sign the data with
* @returns {SignedBlock} the signed block to publish to the blockchain
*/
receive(data: ReceiveBlock, privateKey: string): SignedBlock {
static receive = (data: ReceiveBlock, privateKey: string): SignedBlock => {
const validateInputRaw = (input: string) => !!input && !isNaN(+input)
if (!validateInputRaw(data.walletBalanceRaw)) {
throw new Error('Invalid format in wallet balance')
@ -33,11 +26,11 @@ export default class BlockSigner {
throw new Error('Invalid format in send amount')
}
if (!this.nanoAddress.validateNanoAddress(data.toAddress)) {
if (!NanoAddress.validateNanoAddress(data.toAddress)) {
throw new Error('Invalid toAddress')
}
if (!this.nanoAddress.validateNanoAddress(data.representativeAddress)) {
if (!NanoAddress.validateNanoAddress(data.representativeAddress)) {
throw new Error('Invalid representativeAddress')
}
@ -58,11 +51,11 @@ export default class BlockSigner {
const newBalanceNano = new BigNumber(balanceNano).plus(new BigNumber(amountNano))
const newBalanceRaw = NanoConverter.convert(newBalanceNano, 'NANO', 'RAW')
const newBalanceHex = Convert.dec2hex(newBalanceRaw, 16).toUpperCase()
const account = this.nanoAddress.nanoAddressToHexString(data.toAddress)
const account = NanoAddress.nanoAddressToHexString(data.toAddress)
const link = data.transactionHash
const representative = this.nanoAddress.nanoAddressToHexString(data.representativeAddress)
const representative = NanoAddress.nanoAddressToHexString(data.representativeAddress)
const signature = this.signer.sign(
const signature = Signer.sign(
privateKey,
this.preamble,
account,
@ -90,7 +83,7 @@ export default class BlockSigner {
* @param {string} privateKey Private key to sign the data with
* @returns {SignedBlock} the signed block to publish to the blockchain
*/
send(data: SendBlock, privateKey: string): SignedBlock {
static send = (data: SendBlock, privateKey: string): SignedBlock => {
const validateInputRaw = (input: string) => !!input && !isNaN(+input)
if (!validateInputRaw(data.walletBalanceRaw)) {
throw new Error('Invalid format in wallet balance')
@ -100,15 +93,15 @@ export default class BlockSigner {
throw new Error('Invalid format in send amount')
}
if (!this.nanoAddress.validateNanoAddress(data.toAddress)) {
if (!NanoAddress.validateNanoAddress(data.toAddress)) {
throw new Error('Invalid toAddress')
}
if (!this.nanoAddress.validateNanoAddress(data.fromAddress)) {
if (!NanoAddress.validateNanoAddress(data.fromAddress)) {
throw new Error('Invalid fromAddress')
}
if (!this.nanoAddress.validateNanoAddress(data.representativeAddress)) {
if (!NanoAddress.validateNanoAddress(data.representativeAddress)) {
throw new Error('Invalid representativeAddress')
}
@ -125,11 +118,11 @@ export default class BlockSigner {
const newBalanceNano = new BigNumber(balanceNano).minus(new BigNumber(amountNano))
const newBalanceRaw = NanoConverter.convert(newBalanceNano, 'NANO', 'RAW')
const newBalanceHex = Convert.dec2hex(newBalanceRaw, 16).toUpperCase()
const account = this.nanoAddress.nanoAddressToHexString(data.fromAddress)
const link = this.nanoAddress.nanoAddressToHexString(data.toAddress)
const representative = this.nanoAddress.nanoAddressToHexString(data.representativeAddress)
const account = NanoAddress.nanoAddressToHexString(data.fromAddress)
const link = NanoAddress.nanoAddressToHexString(data.toAddress)
const representative = NanoAddress.nanoAddressToHexString(data.representativeAddress)
const signature = this.signer.sign(
const signature = Signer.sign(
privateKey,
this.preamble,
account,

69
lib/box.ts Normal file
View file

@ -0,0 +1,69 @@
import * as base64 from 'byte-base64'
//@ts-ignore
import { lib } from 'crypto-js'
import Ed25519 from './ed25519'
import NanoAddress from './nano-address'
import Convert from './util/convert'
import Curve25519 from './util/curve25519'
export default class Box {
static readonly NONCE_LENGTH = 24
static encrypt(message: string, address: string, privateKey: string) {
if (!message) {
throw new Error('No message to encrypt')
}
const publicKey = NanoAddress.addressToPublicKey(address)
const { privateKey: convertedPrivateKey, publicKey: convertedPublicKey } = new Ed25519().convertKeys({
privateKey,
publicKey,
})
const nonce = Convert.hex2ab(lib.WordArray.random(this.NONCE_LENGTH).toString())
const encrypted = new Curve25519().box(
Convert.str2bin(message),
nonce,
Convert.hex2ab(convertedPublicKey),
Convert.hex2ab(convertedPrivateKey),
)
const full = new Uint8Array(nonce.length + encrypted.length)
full.set(nonce)
full.set(encrypted, nonce.length)
return base64.bytesToBase64(full)
}
static decrypt(encrypted: string, address: string, privateKey: string) {
if (!encrypted) {
throw new Error('No message to decrypt')
}
const publicKey = NanoAddress.addressToPublicKey(address)
const { privateKey: convertedPrivateKey, publicKey: convertedPublicKey } = new Ed25519().convertKeys({
privateKey,
publicKey,
})
const decodedEncryptedMessageBytes = base64.base64ToBytes(encrypted)
const nonce = decodedEncryptedMessageBytes.slice(0, this.NONCE_LENGTH)
const encryptedMessage = decodedEncryptedMessageBytes.slice(this.NONCE_LENGTH, encrypted.length)
const decrypted = new Curve25519().boxOpen(
encryptedMessage,
nonce,
Convert.hex2ab(convertedPublicKey),
Convert.hex2ab(convertedPrivateKey),
)
if (!decrypted) {
throw new Error('Could not decrypt message')
}
return Convert.bin2str(decrypted)
}
}

View file

@ -118,6 +118,24 @@ export default class Ed25519 {
}
}
/**
* Convert ed25519 keypair to curve25519 keypair suitable for Diffie-Hellman key exchange
*
* @param {KeyPair} keyPair ed25519 keypair
* @returns {KeyPair} keyPair Curve25519 keypair
*/
convertKeys(keyPair: KeyPair): KeyPair {
const publicKey = Convert.ab2hex(this.curve.convertEd25519PublicKeyToCurve25519(Convert.hex2ab(keyPair.publicKey)))
if (!publicKey) {
return null
}
const privateKey = Convert.ab2hex(this.curve.convertEd25519SecretKeyToCurve25519(Convert.hex2ab(keyPair.privateKey)))
return {
publicKey,
privateKey,
}
}
/**
* Generate a message signature
* @param {Uint8Array} msg Message to be signed as byte array
@ -143,7 +161,7 @@ export default class Ed25519 {
* @param {Uint8Array} Returns the signature as 64 byte typed array
*/
verify(msg: Uint8Array, publicKey: Uint8Array, signature: Uint8Array): boolean {
const CURVE = this.curve;
const CURVE = this.curve
const p = [CURVE.gf(), CURVE.gf(), CURVE.gf(), CURVE.gf()]
const q = [CURVE.gf(), CURVE.gf(), CURVE.gf(), CURVE.gf()]
@ -197,7 +215,7 @@ export default class Ed25519 {
const pk = Convert.hex2ab(this.generateKeys(Convert.ab2hex(sk)).publicKey)
this.cryptoHash(d, sk, 32)
this.curve.cryptoHash(d, sk, 32)
d[0] &= 248
d[31] &= 127
d[31] |= 64
@ -211,7 +229,7 @@ export default class Ed25519 {
sm[32 + i] = d[32 + i]
}
this.cryptoHash(r, sm.subarray(32), n + 32)
this.curve.cryptoHash(r, sm.subarray(32), n + 32)
this.reduce(r)
this.scalarbase(p, r)
this.pack(sm, p)
@ -220,7 +238,7 @@ export default class Ed25519 {
sm[i] = pk[i - 32]
}
this.cryptoHash(h, sm, n + 64)
this.curve.cryptoHash(h, sm, n + 64)
this.reduce(h)
for (i = 0; i < 64; i++) {
@ -242,20 +260,6 @@ export default class Ed25519 {
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 = blake2b(input)
for (let i = 0; i < 64; ++i) {
out[i] = hash[i]
}
return 0
}
}
export interface KeyPair {

View file

@ -5,10 +5,10 @@ import Convert from './util/convert'
export default class NanoAddress {
readonly alphabet = '13456789abcdefghijkmnopqrstuwxyz'
readonly prefix = 'nano_'
static readonly alphabet = '13456789abcdefghijkmnopqrstuwxyz'
static readonly prefix = 'nano_'
deriveAddress = (publicKey: string): string => {
static deriveAddress = (publicKey: string): string => {
const publicKeyBytes = Convert.hex2ab(publicKey)
const checksum = blake2b(publicKeyBytes, undefined, 5).reverse()
const encoded = this.encodeNanoBase32(publicKeyBytes)
@ -17,7 +17,7 @@ export default class NanoAddress {
return this.prefix + encoded + encodedChecksum
}
encodeNanoBase32 = (publicKey: Uint8Array): string => {
static encodeNanoBase32 = (publicKey: Uint8Array): string => {
const length = publicKey.length
const leftover = (length * 8) % 5
const offset = leftover === 0 ? 0 : 5 - leftover
@ -43,7 +43,15 @@ export default class NanoAddress {
return output
}
decodeNanoBase32 = (input: string): Uint8Array => {
static addressToPublicKey = (input: string): string => {
const cleaned = input
.replace('nano_', '')
.replace('xrb_', '')
const publicKeyBytes = NanoAddress.decodeNanoBase32(cleaned)
return Convert.ab2hex(publicKeyBytes).slice(0, 64)
}
static decodeNanoBase32 = (input: string): Uint8Array => {
const length = input.length
const leftover = (length * 5) % 8
const offset = leftover === 0 ? 0 : 8 - leftover
@ -81,7 +89,7 @@ export default class NanoAddress {
*
* @param {string} address Nano address
*/
validateNanoAddress = (address: string): boolean => {
static validateNanoAddress = (address: string): boolean => {
if (address === undefined) {
throw Error('Address must be defined.')
}
@ -100,7 +108,7 @@ export default class NanoAddress {
}
const expectedChecksum = address.slice(-8)
const publicKey = address.slice(address.indexOf('_') + 1, -8)
const publicKey = this.stripAddress(address)
const publicKeyBuffer = this.decodeNanoBase32(publicKey)
const actualChecksumBuffer = blake2b(publicKeyBuffer, null, 5).reverse()
const actualChecksum = this.encodeNanoBase32(actualChecksumBuffer)
@ -108,7 +116,7 @@ export default class NanoAddress {
return expectedChecksum === actualChecksum
}
nanoAddressToHexString = (addr: string): string => {
static nanoAddressToHexString = (addr: string): string => {
addr = addr.slice(-60)
const isValid = /^[13456789abcdefghijkmnopqrstuwxyz]+$/.test(addr)
if (isValid) {
@ -125,7 +133,11 @@ export default class NanoAddress {
}
}
private readChar(char: string): number {
static stripAddress(address: string): string {
return address.slice(address.indexOf('_') + 1, -8)
}
private static readChar(char: string): number {
const idx = this.alphabet.indexOf(char)
if (idx === -1) {

View file

@ -9,7 +9,7 @@ export default class NanoConverter {
* @param inputUnit {string} the unit to convert from
* @param outputUnit {string} the unit to convert to
*/
static convert(input: string | BigNumber, inputUnit: string, outputUnit: string): string {
static convert = (input: string | BigNumber, inputUnit: string, outputUnit: string): string => {
let value = new BigNumber(input.toString())
switch (inputUnit) {

View file

@ -7,16 +7,14 @@ import Convert from './util/convert'
export default class Signer {
ed25519 = new Ed25519()
/**
* Signs any data using the ed25519 signature system
*
* @param privateKey Private key to sign the data with
* @param data Data to sign
*/
sign(privateKey: string, ...data: string[]): string {
const signature = this.ed25519.sign(
static sign = (privateKey: string, ...data: string[]): string => {
const signature = new Ed25519().sign(
this.generateHash(data),
Convert.hex2ab(privateKey))
return Convert.ab2hex(signature)
@ -29,11 +27,11 @@ export default class Signer {
* @param signature Signature to verify
* @param data Data to verify
*/
verify(publicKey: string, signature: string, ...data: string[]): boolean {
return this.ed25519.verify(
static verify = (publicKey: string, signature: string, ...data: string[]): boolean => {
return new Ed25519().verify(
this.generateHash(data),
Convert.hex2ab(publicKey),
Convert.hex2ab(signature));
Convert.hex2ab(signature))
}
/**
@ -41,7 +39,7 @@ export default class Signer {
*
* @param data Data to hash
*/
generateHash(data: string[]): Uint8Array {
static generateHash = (data: string[]): Uint8Array => {
const ctx = blake2bInit(32, undefined)
data.forEach(str => blake2bUpdate(ctx, Convert.hex2ab(str)))
return blake2bFinal(ctx)

View file

@ -26,6 +26,21 @@ export default class Convert {
return bin.subarray(0, p)
}
/**
* Convert a byte array to a UTF-8 encoded string
*
* @param {Uint8Array} arr Byte array
* @return {String} UTF-8 encoded string
*/
static bin2str = (arr: Uint8Array) => {
let i, s = []
for (i = 0; i < arr.length; i++) {
s.push(String.fromCharCode(arr[i]))
}
return decodeURIComponent(escape(s.join('')))
}
/**
* Convert Array of 8 bytes (int64) to hex string
*

View file

@ -1,5 +1,16 @@
//@ts-ignore
import { blake2b } from 'blakejs'
import Util from './util'
/**
* Derived from:
* - mipher
* - tweetnacl
* - ed2curve-js
*
* With added types etc
*/
export default class Curve25519 {
gf0: Int32Array
@ -9,6 +20,9 @@ export default class Curve25519 {
I: Int32Array
_9: Uint8Array
_121665: Int32Array
_0: Uint8Array
sigma: Uint8Array
minusp: Uint32Array
constructor() {
this.gf0 = this.gf()
@ -19,6 +33,9 @@ export default class Curve25519 {
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])
this._0 = new Uint8Array(16)
this.sigma = new Uint8Array([101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98, 121, 116, 101, 32, 107])
this.minusp = new Uint32Array([5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252])
}
gf(init?: number[]): Int32Array {
@ -413,6 +430,336 @@ export default class Curve25519 {
o[15] = t15
}
coreSalsa20(o: Uint8Array, p: Uint8Array, k: Uint8Array, c: Uint8Array) {
const j0 = c[ 0] & 0xff | (c[ 1] & 0xff)<<8 | (c[ 2] & 0xff)<<16 | (c[ 3] & 0xff)<<24,
j1 = k[ 0] & 0xff | (k[ 1] & 0xff)<<8 | (k[ 2] & 0xff)<<16 | (k[ 3] & 0xff)<<24,
j2 = k[ 4] & 0xff | (k[ 5] & 0xff)<<8 | (k[ 6] & 0xff)<<16 | (k[ 7] & 0xff)<<24,
j3 = k[ 8] & 0xff | (k[ 9] & 0xff)<<8 | (k[10] & 0xff)<<16 | (k[11] & 0xff)<<24,
j4 = k[12] & 0xff | (k[13] & 0xff)<<8 | (k[14] & 0xff)<<16 | (k[15] & 0xff)<<24,
j5 = c[ 4] & 0xff | (c[ 5] & 0xff)<<8 | (c[ 6] & 0xff)<<16 | (c[ 7] & 0xff)<<24,
j6 = p[ 0] & 0xff | (p[ 1] & 0xff)<<8 | (p[ 2] & 0xff)<<16 | (p[ 3] & 0xff)<<24,
j7 = p[ 4] & 0xff | (p[ 5] & 0xff)<<8 | (p[ 6] & 0xff)<<16 | (p[ 7] & 0xff)<<24,
j8 = p[ 8] & 0xff | (p[ 9] & 0xff)<<8 | (p[10] & 0xff)<<16 | (p[11] & 0xff)<<24,
j9 = p[12] & 0xff | (p[13] & 0xff)<<8 | (p[14] & 0xff)<<16 | (p[15] & 0xff)<<24,
j10 = c[ 8] & 0xff | (c[ 9] & 0xff)<<8 | (c[10] & 0xff)<<16 | (c[11] & 0xff)<<24,
j11 = k[16] & 0xff | (k[17] & 0xff)<<8 | (k[18] & 0xff)<<16 | (k[19] & 0xff)<<24,
j12 = k[20] & 0xff | (k[21] & 0xff)<<8 | (k[22] & 0xff)<<16 | (k[23] & 0xff)<<24,
j13 = k[24] & 0xff | (k[25] & 0xff)<<8 | (k[26] & 0xff)<<16 | (k[27] & 0xff)<<24,
j14 = k[28] & 0xff | (k[29] & 0xff)<<8 | (k[30] & 0xff)<<16 | (k[31] & 0xff)<<24,
j15 = c[12] & 0xff | (c[13] & 0xff)<<8 | (c[14] & 0xff)<<16 | (c[15] & 0xff)<<24
let x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7,
x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14,
x15 = j15, u
for (let i = 0; i < 20; i += 2) {
u = x0 + x12 | 0
x4 ^= u<<7 | u>>>(32-7)
u = x4 + x0 | 0
x8 ^= u<<9 | u>>>(32-9)
u = x8 + x4 | 0
x12 ^= u<<13 | u>>>(32-13)
u = x12 + x8 | 0
x0 ^= u<<18 | u>>>(32-18)
u = x5 + x1 | 0
x9 ^= u<<7 | u>>>(32-7)
u = x9 + x5 | 0
x13 ^= u<<9 | u>>>(32-9)
u = x13 + x9 | 0
x1 ^= u<<13 | u>>>(32-13)
u = x1 + x13 | 0
x5 ^= u<<18 | u>>>(32-18)
u = x10 + x6 | 0
x14 ^= u<<7 | u>>>(32-7)
u = x14 + x10 | 0
x2 ^= u<<9 | u>>>(32-9)
u = x2 + x14 | 0
x6 ^= u<<13 | u>>>(32-13)
u = x6 + x2 | 0
x10 ^= u<<18 | u>>>(32-18)
u = x15 + x11 | 0
x3 ^= u<<7 | u>>>(32-7)
u = x3 + x15 | 0
x7 ^= u<<9 | u>>>(32-9)
u = x7 + x3 | 0
x11 ^= u<<13 | u>>>(32-13)
u = x11 + x7 | 0
x15 ^= u<<18 | u>>>(32-18)
u = x0 + x3 | 0
x1 ^= u<<7 | u>>>(32-7)
u = x1 + x0 | 0
x2 ^= u<<9 | u>>>(32-9)
u = x2 + x1 | 0
x3 ^= u<<13 | u>>>(32-13)
u = x3 + x2 | 0
x0 ^= u<<18 | u>>>(32-18)
u = x5 + x4 | 0
x6 ^= u<<7 | u>>>(32-7)
u = x6 + x5 | 0
x7 ^= u<<9 | u>>>(32-9)
u = x7 + x6 | 0
x4 ^= u<<13 | u>>>(32-13)
u = x4 + x7 | 0
x5 ^= u<<18 | u>>>(32-18)
u = x10 + x9 | 0
x11 ^= u<<7 | u>>>(32-7)
u = x11 + x10 | 0
x8 ^= u<<9 | u>>>(32-9)
u = x8 + x11 | 0
x9 ^= u<<13 | u>>>(32-13)
u = x9 + x8 | 0
x10 ^= u<<18 | u>>>(32-18)
u = x15 + x14 | 0
x12 ^= u<<7 | u>>>(32-7)
u = x12 + x15 | 0
x13 ^= u<<9 | u>>>(32-9)
u = x13 + x12 | 0
x14 ^= u<<13 | u>>>(32-13)
u = x14 + x13 | 0
x15 ^= u<<18 | u>>>(32-18)
}
x0 = x0 + j0 | 0
x1 = x1 + j1 | 0
x2 = x2 + j2 | 0
x3 = x3 + j3 | 0
x4 = x4 + j4 | 0
x5 = x5 + j5 | 0
x6 = x6 + j6 | 0
x7 = x7 + j7 | 0
x8 = x8 + j8 | 0
x9 = x9 + j9 | 0
x10 = x10 + j10 | 0
x11 = x11 + j11 | 0
x12 = x12 + j12 | 0
x13 = x13 + j13 | 0
x14 = x14 + j14 | 0
x15 = x15 + j15 | 0
o[ 0] = x0 >>> 0 & 0xff
o[ 1] = x0 >>> 8 & 0xff
o[ 2] = x0 >>> 16 & 0xff
o[ 3] = x0 >>> 24 & 0xff
o[ 4] = x1 >>> 0 & 0xff
o[ 5] = x1 >>> 8 & 0xff
o[ 6] = x1 >>> 16 & 0xff
o[ 7] = x1 >>> 24 & 0xff
o[ 8] = x2 >>> 0 & 0xff
o[ 9] = x2 >>> 8 & 0xff
o[10] = x2 >>> 16 & 0xff
o[11] = x2 >>> 24 & 0xff
o[12] = x3 >>> 0 & 0xff
o[13] = x3 >>> 8 & 0xff
o[14] = x3 >>> 16 & 0xff
o[15] = x3 >>> 24 & 0xff
o[16] = x4 >>> 0 & 0xff
o[17] = x4 >>> 8 & 0xff
o[18] = x4 >>> 16 & 0xff
o[19] = x4 >>> 24 & 0xff
o[20] = x5 >>> 0 & 0xff
o[21] = x5 >>> 8 & 0xff
o[22] = x5 >>> 16 & 0xff
o[23] = x5 >>> 24 & 0xff
o[24] = x6 >>> 0 & 0xff
o[25] = x6 >>> 8 & 0xff
o[26] = x6 >>> 16 & 0xff
o[27] = x6 >>> 24 & 0xff
o[28] = x7 >>> 0 & 0xff
o[29] = x7 >>> 8 & 0xff
o[30] = x7 >>> 16 & 0xff
o[31] = x7 >>> 24 & 0xff
o[32] = x8 >>> 0 & 0xff
o[33] = x8 >>> 8 & 0xff
o[34] = x8 >>> 16 & 0xff
o[35] = x8 >>> 24 & 0xff
o[36] = x9 >>> 0 & 0xff
o[37] = x9 >>> 8 & 0xff
o[38] = x9 >>> 16 & 0xff
o[39] = x9 >>> 24 & 0xff
o[40] = x10 >>> 0 & 0xff
o[41] = x10 >>> 8 & 0xff
o[42] = x10 >>> 16 & 0xff
o[43] = x10 >>> 24 & 0xff
o[44] = x11 >>> 0 & 0xff
o[45] = x11 >>> 8 & 0xff
o[46] = x11 >>> 16 & 0xff
o[47] = x11 >>> 24 & 0xff
o[48] = x12 >>> 0 & 0xff
o[49] = x12 >>> 8 & 0xff
o[50] = x12 >>> 16 & 0xff
o[51] = x12 >>> 24 & 0xff
o[52] = x13 >>> 0 & 0xff
o[53] = x13 >>> 8 & 0xff
o[54] = x13 >>> 16 & 0xff
o[55] = x13 >>> 24 & 0xff
o[56] = x14 >>> 0 & 0xff
o[57] = x14 >>> 8 & 0xff
o[58] = x14 >>> 16 & 0xff
o[59] = x14 >>> 24 & 0xff
o[60] = x15 >>> 0 & 0xff
o[61] = x15 >>> 8 & 0xff
o[62] = x15 >>> 16 & 0xff
o[63] = x15 >>> 24 & 0xff
}
coreHsalsa20(o: Uint8Array, p: Uint8Array, k: Uint8Array, c: Uint8Array) {
const j0 = c[ 0] & 0xff | (c[ 1] & 0xff)<<8 | (c[ 2] & 0xff)<<16 | (c[ 3] & 0xff)<<24,
j1 = k[ 0] & 0xff | (k[ 1] & 0xff)<<8 | (k[ 2] & 0xff)<<16 | (k[ 3] & 0xff)<<24,
j2 = k[ 4] & 0xff | (k[ 5] & 0xff)<<8 | (k[ 6] & 0xff)<<16 | (k[ 7] & 0xff)<<24,
j3 = k[ 8] & 0xff | (k[ 9] & 0xff)<<8 | (k[10] & 0xff)<<16 | (k[11] & 0xff)<<24,
j4 = k[12] & 0xff | (k[13] & 0xff)<<8 | (k[14] & 0xff)<<16 | (k[15] & 0xff)<<24,
j5 = c[ 4] & 0xff | (c[ 5] & 0xff)<<8 | (c[ 6] & 0xff)<<16 | (c[ 7] & 0xff)<<24,
j6 = p[ 0] & 0xff | (p[ 1] & 0xff)<<8 | (p[ 2] & 0xff)<<16 | (p[ 3] & 0xff)<<24,
j7 = p[ 4] & 0xff | (p[ 5] & 0xff)<<8 | (p[ 6] & 0xff)<<16 | (p[ 7] & 0xff)<<24,
j8 = p[ 8] & 0xff | (p[ 9] & 0xff)<<8 | (p[10] & 0xff)<<16 | (p[11] & 0xff)<<24,
j9 = p[12] & 0xff | (p[13] & 0xff)<<8 | (p[14] & 0xff)<<16 | (p[15] & 0xff)<<24,
j10 = c[ 8] & 0xff | (c[ 9] & 0xff)<<8 | (c[10] & 0xff)<<16 | (c[11] & 0xff)<<24,
j11 = k[16] & 0xff | (k[17] & 0xff)<<8 | (k[18] & 0xff)<<16 | (k[19] & 0xff)<<24,
j12 = k[20] & 0xff | (k[21] & 0xff)<<8 | (k[22] & 0xff)<<16 | (k[23] & 0xff)<<24,
j13 = k[24] & 0xff | (k[25] & 0xff)<<8 | (k[26] & 0xff)<<16 | (k[27] & 0xff)<<24,
j14 = k[28] & 0xff | (k[29] & 0xff)<<8 | (k[30] & 0xff)<<16 | (k[31] & 0xff)<<24,
j15 = c[12] & 0xff | (c[13] & 0xff)<<8 | (c[14] & 0xff)<<16 | (c[15] & 0xff)<<24
let x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7,
x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14,
x15 = j15, u
for (let i = 0; i < 20; i += 2) {
u = x0 + x12 | 0
x4 ^= u<<7 | u>>>(32-7)
u = x4 + x0 | 0
x8 ^= u<<9 | u>>>(32-9)
u = x8 + x4 | 0
x12 ^= u<<13 | u>>>(32-13)
u = x12 + x8 | 0
x0 ^= u<<18 | u>>>(32-18)
u = x5 + x1 | 0
x9 ^= u<<7 | u>>>(32-7)
u = x9 + x5 | 0
x13 ^= u<<9 | u>>>(32-9)
u = x13 + x9 | 0
x1 ^= u<<13 | u>>>(32-13)
u = x1 + x13 | 0
x5 ^= u<<18 | u>>>(32-18)
u = x10 + x6 | 0
x14 ^= u<<7 | u>>>(32-7)
u = x14 + x10 | 0
x2 ^= u<<9 | u>>>(32-9)
u = x2 + x14 | 0
x6 ^= u<<13 | u>>>(32-13)
u = x6 + x2 | 0
x10 ^= u<<18 | u>>>(32-18)
u = x15 + x11 | 0
x3 ^= u<<7 | u>>>(32-7)
u = x3 + x15 | 0
x7 ^= u<<9 | u>>>(32-9)
u = x7 + x3 | 0
x11 ^= u<<13 | u>>>(32-13)
u = x11 + x7 | 0
x15 ^= u<<18 | u>>>(32-18)
u = x0 + x3 | 0
x1 ^= u<<7 | u>>>(32-7)
u = x1 + x0 | 0
x2 ^= u<<9 | u>>>(32-9)
u = x2 + x1 | 0
x3 ^= u<<13 | u>>>(32-13)
u = x3 + x2 | 0
x0 ^= u<<18 | u>>>(32-18)
u = x5 + x4 | 0
x6 ^= u<<7 | u>>>(32-7)
u = x6 + x5 | 0
x7 ^= u<<9 | u>>>(32-9)
u = x7 + x6 | 0
x4 ^= u<<13 | u>>>(32-13)
u = x4 + x7 | 0
x5 ^= u<<18 | u>>>(32-18)
u = x10 + x9 | 0
x11 ^= u<<7 | u>>>(32-7)
u = x11 + x10 | 0
x8 ^= u<<9 | u>>>(32-9)
u = x8 + x11 | 0
x9 ^= u<<13 | u>>>(32-13)
u = x9 + x8 | 0
x10 ^= u<<18 | u>>>(32-18)
u = x15 + x14 | 0
x12 ^= u<<7 | u>>>(32-7)
u = x12 + x15 | 0
x13 ^= u<<9 | u>>>(32-9)
u = x13 + x12 | 0
x14 ^= u<<13 | u>>>(32-13)
u = x14 + x13 | 0
x15 ^= u<<18 | u>>>(32-18)
}
o[ 0] = x0 >>> 0 & 0xff
o[ 1] = x0 >>> 8 & 0xff
o[ 2] = x0 >>> 16 & 0xff
o[ 3] = x0 >>> 24 & 0xff
o[ 4] = x5 >>> 0 & 0xff
o[ 5] = x5 >>> 8 & 0xff
o[ 6] = x5 >>> 16 & 0xff
o[ 7] = x5 >>> 24 & 0xff
o[ 8] = x10 >>> 0 & 0xff
o[ 9] = x10 >>> 8 & 0xff
o[10] = x10 >>> 16 & 0xff
o[11] = x10 >>> 24 & 0xff
o[12] = x15 >>> 0 & 0xff
o[13] = x15 >>> 8 & 0xff
o[14] = x15 >>> 16 & 0xff
o[15] = x15 >>> 24 & 0xff
o[16] = x6 >>> 0 & 0xff
o[17] = x6 >>> 8 & 0xff
o[18] = x6 >>> 16 & 0xff
o[19] = x6 >>> 24 & 0xff
o[20] = x7 >>> 0 & 0xff
o[21] = x7 >>> 8 & 0xff
o[22] = x7 >>> 16 & 0xff
o[23] = x7 >>> 24 & 0xff
o[24] = x8 >>> 0 & 0xff
o[25] = x8 >>> 8 & 0xff
o[26] = x8 >>> 16 & 0xff
o[27] = x8 >>> 24 & 0xff
o[28] = x9 >>> 0 & 0xff
o[29] = x9 >>> 8 & 0xff
o[30] = x9 >>> 16 & 0xff
o[31] = x9 >>> 24 & 0xff
}
S(o: Int32Array, a: Int32Array): void {
this.M(o, a, a)
}
@ -612,6 +959,14 @@ export default class Curve25519 {
return 0
}
vn(x: Uint8Array, xi: number, y: Uint8Array, yi: number, n: number) {
let i, d = 0
for (i = 0; i < n; i++) {
d |= x[xi+i]^y[yi+i]
}
return (1 & ((d - 1) >>> 8)) - 1
}
/**
* Internal scalar mult function
* @param {Uint8Array} q Result
@ -671,6 +1026,289 @@ export default class Curve25519 {
this.pack25519(q, x16)
}
cryptoStreamSalsa20Xor(c: Uint8Array, cpos: number, m: Uint8Array, mpos: number, b: number, n: Uint8Array, k: Uint8Array) {
const z = new Uint8Array(16)
const x = new Uint8Array(64)
let u, i
for (i = 0; i < 16; i++) {
z[i] = 0
}
for (i = 0; i < 8; i++) {
z[i] = n[i]
}
while (b >= 64) {
this.coreSalsa20(x, z, k, this.sigma)
for (i = 0; i < 64; i++) c[cpos+i] = m[mpos+i] ^ x[i]
u = 1
for (i = 8; i < 16; i++) {
u = u + (z[i] & 0xff) | 0
z[i] = u & 0xff
u >>>= 8
}
b -= 64
cpos += 64
mpos += 64
}
if (b > 0) {
this.coreSalsa20(x, z, k, this.sigma)
for (i = 0; i < b; i++) {
c[cpos+i] = m[mpos+i] ^ x[i]
}
}
return 0
}
cryptoStreamSalsa20(c: Uint8Array, cpos: number, b: number, n: Uint8Array, k: Uint8Array) {
const z = new Uint8Array(16), x = new Uint8Array(64)
let u, i
for (i = 0; i < 16; i++) z[i] = 0
for (i = 0; i < 8; i++) z[i] = n[i]
while (b >= 64) {
this.coreSalsa20(x, z, k, this.sigma)
for (i = 0; i < 64; i++) {
c[cpos+i] = x[i]
}
u = 1
for (i = 8; i < 16; i++) {
u = u + (z[i] & 0xff) | 0
z[i] = u & 0xff
u >>>= 8
}
b -= 64
cpos += 64
}
if (b > 0) {
this.coreSalsa20(x, z, k, this.sigma)
for (i = 0; i < b; i++) {
c[cpos+i] = x[i]
}
}
return 0
}
add1305(h: Uint32Array, c: Uint32Array) {
let j, u = 0
for (j = 0; j < 17; j++) {
u = (u + ((h[j] + c[j]) | 0)) | 0
h[j] = u & 255
u >>>= 8
}
}
cryptoOnetimeauth(out: Uint8Array, outpos: number, m: Uint8Array, mpos: number, n: number, k: Uint8Array) {
let s, i, j, u
const x = new Uint32Array(17), r = new Uint32Array(17),
h = new Uint32Array(17), c = new Uint32Array(17),
g = new Uint32Array(17)
for (j = 0; j < 17; j++) {
r[j]=h[j]=0
}
for (j = 0; j < 16; j++) {
r[j]=k[j]
}
r[3]&=15
r[4]&=252
r[7]&=15
r[8]&=252
r[11]&=15
r[12]&=252
r[15]&=15
while (n > 0) {
for (j = 0; j < 17; j++) {
c[j] = 0
}
for (j = 0; (j < 16) && (j < n); ++j) {
c[j] = m[mpos+j]
}
c[j] = 1
mpos += j; n -= j
this.add1305(h, c)
for (i = 0; i < 17; i++) {
x[i] = 0
for (j = 0; j < 17; j++) {
x[i] = (x[i] + (h[j] * ((j <= i) ? r[i - j] : ((320 * r[i + 17 - j])|0))) | 0) | 0
}
}
for (i = 0; i < 17; i++) {
h[i] = x[i]
}
u = 0
for (j = 0; j < 16; j++) {
u = (u + h[j]) | 0
h[j] = u & 255
u >>>= 8
}
u = (u + h[16]) | 0; h[16] = u & 3
u = (5 * (u >>> 2)) | 0
for (j = 0; j < 16; j++) {
u = (u + h[j]) | 0
h[j] = u & 255
u >>>= 8
}
u = (u + h[16]) | 0; h[16] = u
}
for (j = 0; j < 17; j++) {
g[j] = h[j]
}
this.add1305(h, this.minusp)
s = (-(h[16] >>> 7) | 0)
for (j = 0; j < 17; j++) {
h[j] ^= s & (g[j] ^ h[j])
}
for (j = 0; j < 16; j++) {
c[j] = k[j + 16]
}
c[16] = 0
this.add1305(h, c)
for (j = 0; j < 16; j++) {
out[outpos+j] = h[j]
}
return 0
}
cryptoOnetimeauthVerify(h: Uint8Array, hpos: number, m: Uint8Array, mpos: number, n: number, k: Uint8Array) {
const x = new Uint8Array(16)
this.cryptoOnetimeauth(x, 0, m, mpos, n, k)
return this.cryptoVerify16(h, hpos, x, 0)
}
cryptoVerify16(x: Uint8Array, xi: number, y: Uint8Array, yi: number) {
return this.vn(x, xi, y, yi, 16)
}
cryptoBoxBeforenm(k: Uint8Array, y: Uint8Array, x: Uint8Array) {
const s = new Uint8Array(32)
this.cryptoScalarmult(s, x, y)
return this.coreHsalsa20(k, this._0, s, this.sigma)
}
cryptoSecretbox(c: Uint8Array, m: Uint8Array, d: number, n: Uint8Array, k: Uint8Array) {
let i
if (d < 32) {
return -1
}
this.cryptoStreamXor(c, 0, m, 0, d, n, k)
this.cryptoOnetimeauth(c, 16, c, 32, d - 32, c)
for (i = 0; i < 16; i++) {
c[i] = 0
}
return 0
}
cryptoSecretboxOpen(m: Uint8Array, c: Uint8Array, d: number, n: Uint8Array, k: Uint8Array) {
let i
const x = new Uint8Array(32)
if (d < 32) {
return -1
}
this.cryptoStream(x, 0, 32, n, k)
if (this.cryptoOnetimeauthVerify(c, 16, c, 32, d - 32, x) !== 0) {
return -1
}
this.cryptoStreamXor(m, 0, c, 0, d, n, k)
for (i = 0; i < 32; i++) {
m[i] = 0
}
return 0
}
cryptoStream(c: Uint8Array, cpos: number, d: number, n: Uint8Array, k: Uint8Array) {
const s = new Uint8Array(32)
this.coreHsalsa20(s, n, k, this.sigma)
const sn = new Uint8Array(8)
for (var i = 0; i < 8; i++) {
sn[i] = n[i+16]
}
return this.cryptoStreamSalsa20(c, cpos, d, sn, s)
}
cryptoStreamXor(c: Uint8Array, cpos: number, m: Uint8Array, mpos: number, d: number, n: Uint8Array, k: Uint8Array) {
const s = new Uint8Array(32)
this.coreHsalsa20(s, n, k, this.sigma)
const sn = new Uint8Array(8)
for (var i = 0; i < 8; i++) {
sn[i] = n[i+16]
}
return this.cryptoStreamSalsa20Xor(c, cpos, m, mpos, d, sn, s)
}
checkLengths(k: Uint8Array, n: Uint8Array) {
if (k.length !== 32) {
throw new Error('bad key size')
}
if (n.length !== 24) {
throw new Error('bad nonce size')
}
}
checkBoxLengths(pk: Uint8Array, sk: Uint8Array) {
if (pk.length !== 32) {
throw new Error('bad public key size')
}
if (sk.length !== 32) {
throw new Error('bad secret key size')
}
}
checkArrayTypes(...params: any) {
for (let i = 0; i < params.length; i++) {
if (!(params[i] instanceof Uint8Array)) {
throw new TypeError('unexpected type, use Uint8Array')
}
}
}
secretbox(msg: Uint8Array, nonce: Uint8Array, key: Uint8Array) {
this.checkArrayTypes(msg, nonce, key)
this.checkLengths(key, nonce)
const m = new Uint8Array(32 + msg.length)
const c = new Uint8Array(m.length)
for (let i = 0; i < msg.length; i++) {
m[i + 32] = msg[i]
}
this.cryptoSecretbox(c, m, m.length, nonce, key)
return c.subarray(16)
}
secretboxOpen(box: Uint8Array, nonce: Uint8Array, key: Uint8Array) {
this.checkArrayTypes(box, nonce, key)
this.checkLengths(key, nonce)
const c = new Uint8Array(16 + box.length)
const m = new Uint8Array(c.length)
for (let i = 0; i < box.length; i++) {
c[i+16] = box[i]
}
if (c.length < 32) {
return null
}
if (this.cryptoSecretboxOpen(m, c, c.length, nonce, key) !== 0) {
return null
}
return m.subarray(32)
}
box(msg: Uint8Array, nonce: Uint8Array, publicKey: Uint8Array, secretKey: Uint8Array) {
const k = this.boxBefore(publicKey, secretKey)
return this.secretbox(msg, nonce, k)
}
boxOpen(msg: Uint8Array, nonce: Uint8Array, publicKey: Uint8Array, secretKey: Uint8Array) {
const k = this.boxBefore(publicKey, secretKey)
return this.secretboxOpen(msg, nonce, k)
}
boxBefore(publicKey: Uint8Array, secretKey: Uint8Array) {
this.checkArrayTypes(publicKey, secretKey)
this.checkBoxLengths(publicKey, secretKey)
const k = new Uint8Array(32)
this.cryptoBoxBeforenm(k, publicKey, secretKey)
return k
}
/**
* Generate the common key as the produkt of sk1 * pk2
* @param {Uint8Array} sk A 32 byte secret key of pair 1
@ -708,4 +1346,69 @@ export default class Curve25519 {
}
}
/**
* Converts a ed25519 public key to Curve25519 to be used in
* Diffie-Hellman key exchange
*/
convertEd25519PublicKeyToCurve25519(pk: Uint8Array) {
const z = new Uint8Array(32)
const q = [this.gf(), this.gf(), this.gf(), this.gf()]
const a = this.gf()
const b = this.gf()
if (this.unpackNeg(q, pk)) {
return null
}
const y = q[1]
this.A(a, this.gf1, y)
this.Z(b, this.gf1, y)
this.inv25519(b, b)
this.M(a, a, b)
this.pack25519(z, a)
return z
}
/**
* Converts a ed25519 secret key to Curve25519 to be used in
* Diffie-Hellman key exchange
*/
convertEd25519SecretKeyToCurve25519(sk: Uint8Array) {
const d = new Uint8Array(64)
const o = new Uint8Array(32)
let i
this.cryptoHash(d, sk, 32)
d[0] &= 248
d[31] &= 127
d[31] |= 64
for (i = 0; i < 32; i++) {
o[i] = d[i]
}
for (i = 0; i < 64; i++) {
d[i] = 0
}
return o
}
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 = blake2b(input)
for (let i = 0; i < 64; ++i) {
out[i] = hash[i]
}
return 0
}
}

View file

@ -7,7 +7,7 @@ export default class Util {
* @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 {
static compare = (lh: Uint8Array, rh: Uint8Array): boolean => {
if (lh.length !== rh.length) {
return false
}

179
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "nanocurrency-web",
"version": "1.3.6",
"version": "1.4.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -31,21 +31,21 @@
}
},
"@types/estree": {
"version": "0.0.50",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz",
"integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==",
"version": "0.0.51",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz",
"integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==",
"dev": true
},
"@types/json-schema": {
"version": "7.0.9",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"@types/node": {
"version": "17.0.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.14.tgz",
"integrity": "sha512-SbjLmERksKOGzWzPNuW7fJM7fk3YXVTFiZWB/Hs99gwhk+/dnrQRPBQjPW9aO+fi1tAffi9PrwFvsmOKmDTyng==",
"version": "17.0.25",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz",
"integrity": "sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==",
"dev": true
},
"@ungap/promise-all-settled": {
@ -324,9 +324,9 @@
"dev": true
},
"blakejs": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.1.tgz",
"integrity": "sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg=="
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz",
"integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ=="
},
"brace-expansion": {
"version": "1.1.11",
@ -354,15 +354,15 @@
"dev": true
},
"browserslist": {
"version": "4.19.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz",
"integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==",
"version": "4.20.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz",
"integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==",
"dev": true,
"requires": {
"caniuse-lite": "^1.0.30001286",
"electron-to-chromium": "^1.4.17",
"caniuse-lite": "^1.0.30001317",
"electron-to-chromium": "^1.4.84",
"escalade": "^3.1.1",
"node-releases": "^2.0.1",
"node-releases": "^2.0.2",
"picocolors": "^1.0.0"
}
},
@ -372,6 +372,11 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
"byte-base64": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/byte-base64/-/byte-base64-1.1.0.tgz",
"integrity": "sha512-56cXelkJrVMdCY9V/3RfDxTh4VfMFCQ5km7B7GkIGfo4bcPL9aACyJLB0Ms3Ezu5rsHmLB2suis96z4fLM03DA=="
},
"camelcase": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
@ -379,9 +384,9 @@
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001307",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001307.tgz",
"integrity": "sha512-+MXEMczJ4FuxJAUp0jvAl6Df0NI/OfW1RWEE61eSmzS7hw6lz4IKutbhbXendwq8BljfFuHtu26VWsg4afQ7Ng==",
"version": "1.0.30001332",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001332.tgz",
"integrity": "sha512-10T30NYOEQtN6C11YGg411yebhvpnC6Z102+B95eAsN0oB6KUs01ivE8u+G6FMIRtIrVlYXhL+LUwQ3/hXwDWw==",
"dev": true
},
"chai": {
@ -558,9 +563,9 @@
"dev": true
},
"electron-to-chromium": {
"version": "1.4.64",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.64.tgz",
"integrity": "sha512-8mec/99xgLUZCIZZq3wt61Tpxg55jnOSpxGYapE/1Ma9MpFEYYaz4QNYm0CM1rrnCo7i3FRHhbaWjeCLsveGjQ==",
"version": "1.4.118",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.118.tgz",
"integrity": "sha512-maZIKjnYDvF7Fs35nvVcyr44UcKNwybr93Oba2n3HkKDFAtk0svERkLN/HyczJDS3Fo4wU9th9fUQd09ZLtj1w==",
"dev": true
},
"emoji-regex": {
@ -570,9 +575,9 @@
"dev": true
},
"enhanced-resolve": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz",
"integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==",
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz",
"integrity": "sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.4",
@ -751,6 +756,17 @@
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"dependencies": {
"minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
}
}
},
"glob-parent": {
@ -769,9 +785,9 @@
"dev": true
},
"graceful-fs": {
"version": "4.2.9",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
"integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
"version": "4.2.10",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
"dev": true
},
"growl": {
@ -924,9 +940,9 @@
"dev": true
},
"jest-worker": {
"version": "27.4.6",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz",
"integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==",
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
"dev": true,
"requires": {
"@types/node": "*",
@ -962,9 +978,9 @@
"dev": true
},
"loader-runner": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz",
"integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
"integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
"dev": true
},
"locate-path": {
@ -1011,28 +1027,28 @@
"dev": true
},
"micromatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"dev": true,
"requires": {
"braces": "^3.0.1",
"picomatch": "^2.2.3"
"braces": "^3.0.2",
"picomatch": "^2.3.1"
}
},
"mime-db": {
"version": "1.51.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
"integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==",
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true
},
"mime-types": {
"version": "2.1.34",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
"integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"requires": {
"mime-db": "1.51.0"
"mime-db": "1.52.0"
}
},
"mimic-fn": {
@ -1042,18 +1058,18 @@
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz",
"integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"mocha": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.0.tgz",
"integrity": "sha512-kNn7E8g2SzVcq0a77dkphPsDSN7P+iYkqE0ZsGCYWRsoiKjOt+NvXfaagik8vuDa6W5Zw3qxe8Jfpt5qKf+6/Q==",
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz",
"integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==",
"dev": true,
"requires": {
"@ungap/promise-all-settled": "1.1.2",
@ -1069,9 +1085,9 @@
"he": "1.2.0",
"js-yaml": "4.1.0",
"log-symbols": "4.1.0",
"minimatch": "3.0.4",
"minimatch": "4.2.1",
"ms": "2.1.3",
"nanoid": "3.2.0",
"nanoid": "3.3.1",
"serialize-javascript": "6.0.0",
"strip-json-comments": "3.1.1",
"supports-color": "8.1.1",
@ -1089,9 +1105,9 @@
"dev": true
},
"nanoid": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz",
"integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==",
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz",
"integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==",
"dev": true
},
"neo-async": {
@ -1101,9 +1117,9 @@
"dev": true
},
"node-releases": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz",
"integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.3.tgz",
"integrity": "sha512-maHFz6OLqYxz+VQyCAtA3PTX4UP/53pa05fyDNc9CwjvJ0yEh6+xBwKsgCxMNhS8taUKBFYxfuiaD9U/55iFaw==",
"dev": true
},
"normalize-path": {
@ -1336,9 +1352,9 @@
}
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
@ -1453,11 +1469,12 @@
"dev": true
},
"terser": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz",
"integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==",
"dev": true,
"requires": {
"acorn": "^8.5.0",
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20"
@ -1494,9 +1511,9 @@
}
},
"ts-loader": {
"version": "9.2.6",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.2.6.tgz",
"integrity": "sha512-QMTC4UFzHmu9wU2VHZEmWWE9cUajjfcdcws+Gh7FhiO+Dy0RnR1bNz0YCHqhI0yRowCE9arVnNxYHqELOy9Hjw==",
"version": "9.2.8",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.2.8.tgz",
"integrity": "sha512-gxSak7IHUuRtwKf3FIPSW1VpZcqF9+MBrHOvBp9cjHh+525SjtCIJKVGjRKIAfxBwDGDGCFF00rTfzB1quxdSw==",
"dev": true,
"requires": {
"chalk": "^4.1.0",
@ -1512,9 +1529,9 @@
"dev": true
},
"typescript": {
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz",
"integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
"version": "4.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz",
"integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==",
"dev": true
},
"uri-js": {
@ -1537,13 +1554,13 @@
}
},
"webpack": {
"version": "5.68.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.68.0.tgz",
"integrity": "sha512-zUcqaUO0772UuuW2bzaES2Zjlm/y3kRBQDVFVCge+s2Y8mwuUTdperGaAv65/NtRL/1zanpSJOq/MD8u61vo6g==",
"version": "5.72.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.72.0.tgz",
"integrity": "sha512-qmSmbspI0Qo5ld49htys8GY9XhS9CGqFoHTsOVAnjBdg0Zn79y135R+k4IR4rKK6+eKaabMhJwiVB7xw0SJu5w==",
"dev": true,
"requires": {
"@types/eslint-scope": "^3.7.0",
"@types/estree": "^0.0.50",
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^0.0.51",
"@webassemblyjs/ast": "1.11.1",
"@webassemblyjs/wasm-edit": "1.11.1",
"@webassemblyjs/wasm-parser": "1.11.1",
@ -1551,7 +1568,7 @@
"acorn-import-assertions": "^1.7.6",
"browserslist": "^4.14.5",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.8.3",
"enhanced-resolve": "^5.9.2",
"es-module-lexer": "^0.9.0",
"eslint-scope": "5.1.1",
"events": "^3.2.0",

View file

@ -1,6 +1,6 @@
{
"name": "nanocurrency-web",
"version": "1.3.6",
"version": "1.4.0",
"description": "Toolkit for Nano cryptocurrency client side offline integrations",
"author": "Miro Metsänheimo <miro@metsanheimo.fi>",
"license": "MIT",
@ -19,7 +19,9 @@
"crypto",
"wallet",
"block",
"sign"
"sign",
"encrypt",
"decrypt"
],
"main": "dist/index.js",
"types": "dist/index.d.ts",
@ -31,15 +33,16 @@
},
"dependencies": {
"bignumber.js": "9.0.2",
"blakejs": "1.1.1",
"blakejs": "1.2.1",
"byte-base64": "1.1.0",
"crypto-js": "3.1.9-1"
},
"devDependencies": {
"chai": "4.3.6",
"mocha": "9.2.0",
"ts-loader": "9.2.6",
"typescript": "4.5.5",
"webpack": "5.68.0",
"mocha": "9.2.2",
"ts-loader": "9.2.8",
"typescript": "4.6.3",
"webpack": "5.72.0",
"webpack-cli": "4.9.2"
}
}

View file

@ -1,7 +1,7 @@
'use strict'
const expect = require('chai').expect
const { wallet, block, tools } = require('../dist/index')
const { wallet, block, tools, box } = require('../dist/index')
// WARNING: Do not send any funds to the test vectors below
describe('generate wallet test', () => {
@ -261,10 +261,8 @@ describe('unit conversion tests', () => {
describe('Signer tests', () => {
let testWallet;
before(() => {
this.testWallet = wallet.generate();
this.testWallet = wallet.generate()
})
// Private key: 3be4fc2ef3f3b7374e6fc4fb6e7bb153f8a2998b3b3dab50853eabe128024143
@ -330,3 +328,55 @@ describe('Signer tests', () => {
})
})
describe('Box tests', () => {
before(() => {
this.message = 'The quick brown fox jumps over the lazy dog'
this.bob = wallet.generate()
this.alice = wallet.generate()
})
it('should encrypt and decrypt a message', () => {
const encrypted = box.encrypt(this.message, this.alice.accounts[0].address, this.bob.accounts[0].privateKey)
const encrypted2 = box.encrypt(this.message, this.alice.accounts[0].address, this.bob.accounts[0].privateKey)
const encrypted3 = box.encrypt(this.message + 'asd', this.alice.accounts[0].address, this.bob.accounts[0].privateKey)
// Just to be safe
expect(this.message).to.not.equal(encrypted)
expect(encrypted).to.not.equal(encrypted2)
expect(encrypted).to.not.equal(encrypted3)
const decrypted = box.decrypt(encrypted, this.bob.accounts[0].address, this.alice.accounts[0].privateKey)
expect(this.message).to.equal(decrypted)
})
it('should fail to decrypt with wrong public key in encryption', () => {
// Encrypt with wrong public key
const aliceAccounts = wallet.accounts(this.alice.seed, 1, 2)
const encrypted = box.encrypt(this.message, aliceAccounts[0].address, this.bob.accounts[0].privateKey)
expect(() => box.decrypt(encrypted, this.bob.accounts[0].address, this.alice.accounts[0].privateKey)).to.throw()
})
it('should fail to decrypt with wrong public key in decryption', () => {
// Decrypt with wrong public key
const bobAccounts = wallet.accounts(this.bob.seed, 1, 2)
const encrypted = box.encrypt(this.message, this.alice.accounts[0].address, this.bob.accounts[0].privateKey)
expect(() => box.decrypt(encrypted, bobAccounts[0].address, this.alice.accounts[0].privateKey)).to.throw()
})
it('should fail to decrypt with wrong private key in encryption', () => {
// Encrypt with wrong public key
const bobAccounts = wallet.accounts(this.bob.seed, 1, 2)
const encrypted = box.encrypt(this.message, this.alice.accounts[0].address, bobAccounts[0].privateKey)
expect(() => box.decrypt(encrypted, this.bob.accounts[0].address, this.alice.accounts[0].privateKey)).to.throw()
})
it('should fail to decrypt with wrong private key in decryption', () => {
// Encrypt with wrong public key
const aliceAccounts = wallet.accounts(this.alice.seed, 1, 2)
const encrypted = box.encrypt(this.message, this.alice.accounts[0].address, this.bob.accounts[0].privateKey)
expect(() => box.decrypt(encrypted, this.bob.accounts[0].address, aliceAccounts[0].privateKey)).to.throw()
})
})