]> zoso.dev Git - libnemo.git/commitdiff
Version 1.4.0
authorMiro Metsänheimo <miro@metsanheimo.fi>
Fri, 22 Apr 2022 20:54:27 +0000 (23:54 +0300)
committerMiro Metsänheimo <miro@metsanheimo.fi>
Sun, 24 Apr 2022 18:28:33 +0000 (21:28 +0300)
* Added new functionality to encrypt/decrypt strings with Diffie-Hellman
key exchange with Nano addresses and private keys by converting the keys
to Curve25519 keys suitable for encryption and using Box functionality
from NaCl. The library will generate a random nonce to each encryption
and pass the nonce along with the encrypted message encoded in Base64
* Some code refactoring (use static classes and make sure ed25519 and
curve classes are always freshly created)

18 files changed:
README.md
index.ts
lib/address-generator.ts
lib/address-importer.ts
lib/bip32-key-derivation.ts
lib/bip39-mnemonic.ts
lib/block-signer.ts
lib/box.ts [new file with mode: 0644]
lib/ed25519.ts
lib/nano-address.ts
lib/nano-converter.ts
lib/signer.ts
lib/util/convert.ts
lib/util/curve25519.ts
lib/util/util.ts
package-lock.json
package.json
test/test.js

index 2eeeec56e52821f3b988e9205c151e35717530fa..f0c0df3ba3ecbf7f2dde193629688ff010bf598c 100644 (file)
--- a/README.md
+++ b/README.md
@@ -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.
index 4146b4c7790ef58ecbea453e4fcedb45c306ee00..8217f9edfe12093666346c32f6369863c26637e6 100644 (file)
--- a/index.ts
+++ b/index.ts
@@ -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,
 }
index 46a324644880da6579f17abc2e58657e8c30737f..39772059234ab2d74c1a5590e627c4f61cae01d2 100644 (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)
        }
 
 }
index 1d3ebf04f0b1cc74a4a05c31ae04a161ed43e39d..5e3fd4cd0d0b6e4c08b0e124908d006fb59615cc 100644 (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,
index dd2e55416015555dabc56c26ade2c15383bf45c0..332005110c672d3450dcd3ae7c76631f0648734f 100644 (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)
index 74f7f42cb7377b8b781f625f2fabbee5011a575d..2b291fb65e3d658a352782889a532eba75656545 100644 (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)
        }
index 63ff87fbd94690b279714f50c2bc58907c8a8a49..d4e93303fee5907d0aca26aa7d8075cc1cc2b971 100644 (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,
diff --git a/lib/box.ts b/lib/box.ts
new file mode 100644 (file)
index 0000000..25c4c16
--- /dev/null
@@ -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)
+       }
+
+}
index cbf6b7628a75ce4b9dd2eee94bb68999498c6b44..12f69038ba83eaa4a70e2c615b6f6de1258b5ec5 100644 (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 {
index 2ff72acf02c515adcf3526fd38209f96bea168a5..030a2abe3dc8e46e4adc3a52e407e9aecd3ed4b3 100644 (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) {
index 3c460061579119a47f0d6cae1603c49b1099a2f6..fe60eb2452c3541db37c802a4f4fff71669863e1 100644 (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) {
index 66f8844cfe48184897ca862356575d123db9e2fa..2548b3e5013125a021dbc2598abfa2ba40e58b11 100644 (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)
index a7012d153144d2d3e1705e844e1d1cfaf5240fba..a6634c2a430055e6aead37e4193b0e4769276880 100644 (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
         *
index 7c712222e18c2d7ab368d0ac812584b670197e8d..026ef999e0249996246b8028b7c94694413acdb8 100644 (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
+       }
+
 }
index 7e3bb8bf1b79c51966f557c7f583e223771683e2..439e158670d47039672335c0fd51693fbf2fa3bc 100644 (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
                }
index a7ddf784a6c5fa3e1ab6d4aff484fec02f8f4d81..bbe79d620759cd4250b895d85105a8a062b2e748 100644 (file)
@@ -1,6 +1,6 @@
 {
        "name": "nanocurrency-web",
-       "version": "1.3.6",
+       "version": "1.4.0",
        "lockfileVersion": 1,
        "requires": true,
        "dependencies": {
                        }
                },
                "@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": {
                        "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",
                        "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"
                        }
                },
                        "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",
                        "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": {
                        "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": {
                        "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",
                                "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": {
                        "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": {
                        "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": "*",
                        "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": {
                        "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": {
                        "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",
                                "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",
                        "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": {
                        "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": {
                        }
                },
                "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"
                        "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"
                        }
                },
                "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",
                        "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": {
                        }
                },
                "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",
                                "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",
index 74b88c51229197e525cfa00871f726f02cb24311..c2a66152babc6b29cd70d854e34c6a8e9af97c84 100644 (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",
        },
        "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"
        }
 }
index 8187d30367a72bf3b77b1b73e53f29d1f38d286c..9d94ad9c9c0836cd5becfffb9a2a43221badc4a8 100644 (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()
+       })
+
+})
\ No newline at end of file