Version 1.4.0

* 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)
This commit is contained in:
Miro Metsänheimo 2022-04-22 23:54:27 +03:00
commit 7f5cdc33c8
18 changed files with 1138 additions and 265 deletions

View file

@ -33,7 +33,7 @@ npm install nanocurrency-web
### In web ### In web
```html ```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"> <script type="text/javascript">
NanocurrencyWeb.wallet.generate(...); NanocurrencyWeb.wallet.generate(...);
</script> </script>
@ -114,7 +114,7 @@ If the account hasn't been opened yet (this is the first block), you will need t
```javascript ```javascript
import { block } from 'nanocurrency-web' import { block } from 'nanocurrency-web'
const privateKey = '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3'; const privateKey = '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3'
const data = { const data = {
// Current balance from account info // Current balance from account info
walletBalanceRaw: '5618869000000000000000000000000', walletBalanceRaw: '5618869000000000000000000000000',
@ -147,7 +147,7 @@ const signedBlock = block.send(data, privateKey)
```javascript ```javascript
import { block } from 'nanocurrency-web' import { block } from 'nanocurrency-web'
const privateKey = '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3'; const privateKey = '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3'
const data = { const data = {
// Your current balance in RAW from account info // Your current balance in RAW from account info
walletBalanceRaw: '18618869000000000000000000000000', walletBalanceRaw: '18618869000000000000000000000000',
@ -180,7 +180,7 @@ const signedBlock = block.receive(data, privateKey)
```javascript ```javascript
import { block } from 'nanocurrency-web' import { block } from 'nanocurrency-web'
const privateKey = '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3'; const privateKey = '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3'
const data = { const data = {
// Your current balance, from account info // Your current balance, from account info
walletBalanceRaw: '3000000000000000000000000000000', 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') 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 ## Contributions
You are welcome to contribute to the module. To develop, use the following commands. You are welcome to contribute to the module. To develop, use the following commands.

View file

@ -3,16 +3,12 @@ import BigNumber from 'bignumber.js'
import AddressGenerator from './lib/address-generator' import AddressGenerator from './lib/address-generator'
import AddressImporter, { Account, Wallet } from './lib/address-importer' import AddressImporter, { Account, Wallet } from './lib/address-importer'
import BlockSigner, { BlockData, ReceiveBlock, RepresentativeBlock, SendBlock, SignedBlock } from './lib/block-signer' import BlockSigner, { BlockData, ReceiveBlock, RepresentativeBlock, SendBlock, SignedBlock } from './lib/block-signer'
import Box from './lib/box'
import NanoAddress from './lib/nano-address' import NanoAddress from './lib/nano-address'
import NanoConverter from './lib/nano-converter' import NanoConverter from './lib/nano-converter'
import Signer from './lib/signer' import Signer from './lib/signer'
import Convert from './lib/util/convert' import Convert from './lib/util/convert'
const nanoAddress = new NanoAddress()
const generator = new AddressGenerator()
const importer = new AddressImporter()
const signer = new Signer()
const wallet = { const wallet = {
/** /**
@ -38,7 +34,7 @@ const wallet = {
* @returns {Wallet} The wallet * @returns {Wallet} The wallet
*/ */
generate: (entropy?: string, seedPassword?: string): 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 * @returns {Wallet} The wallet
*/ */
generateLegacy: (seed?: string): Wallet => { generateLegacy: (seed?: string): Wallet => {
return generator.generateLegacyWallet(seed) return AddressGenerator.generateLegacyWallet(seed)
}, },
/** /**
@ -76,7 +72,7 @@ const wallet = {
* @returns {Wallet} The wallet * @returns {Wallet} The wallet
*/ */
fromMnemonic: (mnemonic: string, seedPassword?: string): 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 * @returns {Wallet} The wallet
*/ */
fromLegacyMnemonic: (mnemonic: string): 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 * @returns {Wallet} The wallet, without the mnemonic phrase because it's not possible to infer backwards
*/ */
fromSeed: (seed: string): Wallet => { fromSeed: (seed: string): Wallet => {
return importer.fromSeed(seed) return AddressImporter.fromSeed(seed)
}, },
/** /**
@ -127,7 +123,7 @@ const wallet = {
* @returns {Wallet} The wallet * @returns {Wallet} The wallet
*/ */
fromLegacySeed: (seed: string): 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 * @returns {Account[]} a list of accounts
*/ */
accounts: (seed: string, from: number, to: number): Account[] => { 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 * @returns {Account[]} a list of accounts
*/ */
legacyAccounts: (seed: string, from: number, to: number): Account[] => { 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 = { const block = {
/** /**
@ -185,7 +180,7 @@ const block = {
* @returns {SignedBlock} the signed block * @returns {SignedBlock} the signed block
*/ */
send: (data: SendBlock, privateKey: string): SignedBlock => { 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 * @returns {SignedBlock} the signed block
*/ */
receive: (data: ReceiveBlock, privateKey: string): SignedBlock => { 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 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 => { sign: (privateKey: string, ...input: string[]): string => {
const data = input.map(Convert.stringToHex) 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 => { verify: (publicKey: string, signature: string, ...input: string[]): boolean => {
const data = input.map(Convert.stringToHex) 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 => { verifyBlock: (publicKey: string, block: BlockData): boolean => {
const preamble = 0x6.toString().padStart(64, '0') const preamble = 0x6.toString().padStart(64, '0')
return signer.verify(publicKey, block.signature, return Signer.verify(publicKey, block.signature,
preamble, preamble,
nanoAddress.nanoAddressToHexString(block.account), NanoAddress.nanoAddressToHexString(block.account),
block.previous, block.previous,
nanoAddress.nanoAddressToHexString(block.representative), NanoAddress.nanoAddressToHexString(block.representative),
Convert.dec2hex(block.balance, 16).toUpperCase(), Convert.dec2hex(block.balance, 16).toUpperCase(),
block.link) block.link)
}, },
@ -307,7 +302,7 @@ const tools = {
* @returns {boolean} valid or not * @returns {boolean} valid or not
*/ */
validateAddress: (input: string): boolean => { validateAddress: (input: string): boolean => {
return nanoAddress.validateNanoAddress(input) return NanoAddress.validateNanoAddress(input)
}, },
/** /**
@ -317,7 +312,7 @@ const tools = {
* @returns {boolean} valid or not * @returns {boolean} valid or not
*/ */
validateMnemonic: (input: string): boolean => { validateMnemonic: (input: string): boolean => {
return importer.validateMnemonic(input) return AddressImporter.validateMnemonic(input)
}, },
/** /**
@ -327,11 +322,7 @@ const tools = {
* @returns {string} the public key * @returns {string} the public key
*/ */
addressToPublicKey: (input: string): string => { addressToPublicKey: (input: string): string => {
const cleaned = input return NanoAddress.addressToPublicKey(input)
.replace('nano_', '')
.replace('xrb_', '')
const publicKeyBytes = nanoAddress.decodeNanoBase32(cleaned)
return Convert.ab2hex(publicKeyBytes).slice(0, 64)
}, },
/** /**
@ -341,7 +332,7 @@ const tools = {
* @returns {string} the nano address * @returns {string} the nano address
*/ */
publicKeyToAddress: (input: string): string => { publicKeyToAddress: (input: string): string => {
return nanoAddress.deriveAddress(input) return NanoAddress.deriveAddress(input)
}, },
/** /**
@ -352,16 +343,56 @@ const tools = {
*/ */
blake2b: (input: string | string[]): string => { blake2b: (input: string | string[]): string => {
if (Array.isArray(input)) { if (Array.isArray(input)) {
return Convert.ab2hex(signer.generateHash(input.map(Convert.stringToHex))) return Convert.ab2hex(Signer.generateHash(input.map(Convert.stringToHex)))
} else { } 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 { export {
wallet, wallet,
block, block,
tools, tools,
box,
} }

View file

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

View file

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

View file

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

View file

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

View file

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

69
lib/box.ts Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -26,6 +26,21 @@ export default class Convert {
return bin.subarray(0, p) 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 * Convert Array of 8 bytes (int64) to hex string
* *

View file

@ -1,5 +1,16 @@
//@ts-ignore
import { blake2b } from 'blakejs'
import Util from './util' import Util from './util'
/**
* Derived from:
* - mipher
* - tweetnacl
* - ed2curve-js
*
* With added types etc
*/
export default class Curve25519 { export default class Curve25519 {
gf0: Int32Array gf0: Int32Array
@ -9,6 +20,9 @@ export default class Curve25519 {
I: Int32Array I: Int32Array
_9: Uint8Array _9: Uint8Array
_121665: Int32Array _121665: Int32Array
_0: Uint8Array
sigma: Uint8Array
minusp: Uint32Array
constructor() { constructor() {
this.gf0 = this.gf() 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.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.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.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 { gf(init?: number[]): Int32Array {
@ -413,6 +430,336 @@ export default class Curve25519 {
o[15] = t15 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 { S(o: Int32Array, a: Int32Array): void {
this.M(o, a, a) this.M(o, a, a)
} }
@ -612,6 +959,14 @@ export default class Curve25519 {
return 0 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 * Internal scalar mult function
* @param {Uint8Array} q Result * @param {Uint8Array} q Result
@ -671,6 +1026,289 @@ export default class Curve25519 {
this.pack25519(q, x16) 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 * Generate the common key as the produkt of sk1 * pk2
* @param {Uint8Array} sk A 32 byte secret key of pair 1 * @param {Uint8Array} sk A 32 byte secret key of pair 1
@ -708,4 +1346,69 @@ export default class Curve25519 {
} }
} }
/**
* Converts a ed25519 public key to Curve25519 to be used in
* Diffie-Hellman key exchange
*/
convertEd25519PublicKeyToCurve25519(pk: Uint8Array) {
const z = new Uint8Array(32)
const q = [this.gf(), this.gf(), this.gf(), this.gf()]
const a = this.gf()
const b = this.gf()
if (this.unpackNeg(q, pk)) {
return null
}
const y = q[1]
this.A(a, this.gf1, y)
this.Z(b, this.gf1, y)
this.inv25519(b, b)
this.M(a, a, b)
this.pack25519(z, a)
return z
}
/**
* Converts a ed25519 secret key to Curve25519 to be used in
* Diffie-Hellman key exchange
*/
convertEd25519SecretKeyToCurve25519(sk: Uint8Array) {
const d = new Uint8Array(64)
const o = new Uint8Array(32)
let i
this.cryptoHash(d, sk, 32)
d[0] &= 248
d[31] &= 127
d[31] |= 64
for (i = 0; i < 32; i++) {
o[i] = d[i]
}
for (i = 0; i < 64; i++) {
d[i] = 0
}
return o
}
cryptoHash(out: Uint8Array, m: Uint8Array, n: number): number {
const input = new Uint8Array(n)
for (let i = 0; i < n; ++i) {
input[i] = m[i]
}
const hash = blake2b(input)
for (let i = 0; i < 64; ++i) {
out[i] = hash[i]
}
return 0
}
} }

View file

@ -7,7 +7,7 @@ export default class Util {
* @param {Uint8Array} rh Second array of bytes * @param {Uint8Array} rh Second array of bytes
* @return {Boolean} True if the arrays are equal (length and content), false otherwise * @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) { if (lh.length !== rh.length) {
return false return false
} }

179
package-lock.json generated
View file

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

View file

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

View file

@ -1,7 +1,7 @@
'use strict' 'use strict'
const expect = require('chai').expect 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 // WARNING: Do not send any funds to the test vectors below
describe('generate wallet test', () => { describe('generate wallet test', () => {
@ -261,10 +261,8 @@ describe('unit conversion tests', () => {
describe('Signer tests', () => { describe('Signer tests', () => {
let testWallet;
before(() => { before(() => {
this.testWallet = wallet.generate(); this.testWallet = wallet.generate()
}) })
// Private key: 3be4fc2ef3f3b7374e6fc4fb6e7bb153f8a2998b3b3dab50853eabe128024143 // 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()
})
})