├── .gitignore ├── .travis.yml ├── package.json ├── examples ├── stateblocks.js ├── pending.js ├── pull.js ├── votes.js └── receive.js ├── LICENSE ├── functions.js ├── README.md ├── test.js ├── blake2b.js ├── index.js └── nacl.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | node_modules 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "8" 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nano-node", 3 | "version": "0.0.9", 4 | "description": "Publish blocks to raiblocks", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test.js" 8 | }, 9 | "author": "numtel ", 10 | "repository": "numtel/node-nano-node", 11 | "license": "MIT", 12 | "dependencies": {} 13 | } 14 | -------------------------------------------------------------------------------- /examples/stateblocks.js: -------------------------------------------------------------------------------- 1 | const NanoNode = require('..'); 2 | 3 | const node = new NanoNode(12000); 4 | 5 | node.on('block', (block, rinfo) => { 6 | if(block.type === 'state') 7 | console.log(block) 8 | }); 9 | 10 | node.on('error', error => { 11 | console.log(error); 12 | }); 13 | 14 | 15 | node.on('ready', () => { 16 | const address = node.client.address(); 17 | console.log(`server listening ${address.address}:${address.port}`); 18 | // Initial introduction 19 | node.publish({ type: 'keepalive' }); 20 | }); 21 | 22 | // Send keepalive at regular interval to known peers, 23 | // maxPeers will be reached very quickly 24 | setInterval(() => { 25 | console.log('Sending keepalive to ', node.peers.length, 'peers...'); 26 | node.publish({type: 'keepalive'}); 27 | }, 30000); 28 | 29 | -------------------------------------------------------------------------------- /examples/pending.js: -------------------------------------------------------------------------------- 1 | const NanoNode = require('..'); 2 | 3 | const node = new NanoNode(); 4 | 5 | const watchAccount = 6 | NanoNode.keyFromAccount('xrb_3a7yzpzt9m3zn61weq3xakk5r1cryhxh4kqn73umojks1uzgg4ipc8jii5km'); 7 | 8 | node.on('block', block => { 9 | // Print pending plocks for this account 10 | if(block.destination === watchAccount) 11 | console.log(block); 12 | }); 13 | 14 | node.on('error', error => { 15 | console.log(error); 16 | }); 17 | 18 | 19 | node.on('ready', () => { 20 | const address = node.client.address(); 21 | console.log(`server listening ${address.address}:${address.port}`); 22 | // Initial introduction 23 | node.publish({ type: 'keepalive' }); 24 | }); 25 | 26 | // Send keepalive at regular interval to known peers, 27 | // maxPeers will be reached very quickly 28 | setInterval(() => { 29 | console.log('Sending keepalive to ', node.peers.length, 'peers...'); 30 | node.publish({type: 'keepalive'}); 31 | }, 30000); 32 | 33 | -------------------------------------------------------------------------------- /examples/pull.js: -------------------------------------------------------------------------------- 1 | const NanoNode = require('..'); 2 | 3 | const node = new NanoNode(); 4 | const acct = NanoNode.keyFromAccount('xrb_replacethisvalue'); 5 | 6 | node.on('ready', () => { 7 | const address = node.client.address(); 8 | console.log(`server listening ${address.address}:${address.port}`); 9 | // Initial introduction will result in at least a few peers 10 | node.publish({ type: 'keepalive' }); 11 | }); 12 | 13 | let hasFetched = false; 14 | node.on('message', (msg, rinfo) => { 15 | // As soon as maxPeers is reached, fetch the account history 16 | if(!hasFetched && node.peers.length === node.maxPeers) { 17 | hasFetched = true; 18 | node.fetchAccount(acct, (error, result) => { 19 | console.log(error, result); 20 | node.client.close(); 21 | }); 22 | } 23 | }); 24 | 25 | setTimeout(() => { 26 | console.log('Peer count', node.peers.length); 27 | if(node.peers.length === 1) { 28 | console.log('Failed to connect, please try again...'); 29 | } 30 | }, 3000); 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/votes.js: -------------------------------------------------------------------------------- 1 | const NanoNode = require('..'); 2 | 3 | const node = new NanoNode(12000); 4 | 5 | const watchAccount = 'xrb_3arg3asgtigae3xckabaaewkx3bzsh7nwz7jkmjos79ihyaxwphhm6qgjps4'; 6 | const watchPublicKey = NanoNode.accountFromKey(watchAccount); 7 | 8 | node.on('vote', (msg, rinfo) => { 9 | if(msg.account === watchPublicKey) 10 | console.log('Vote from', watchAccount, 'via', rinfo.address, ':', rinfo.port); 11 | }); 12 | 13 | node.on('error', error => { 14 | console.log(error); 15 | }); 16 | 17 | 18 | node.on('ready', () => { 19 | const address = node.client.address(); 20 | console.log(`server listening ${address.address}:${address.port}`); 21 | // Initial introduction 22 | node.publish({ type: 'keepalive' }); 23 | }); 24 | 25 | // Send keepalive at regular interval to known peers, 26 | // maxPeers will be reached very quickly 27 | setInterval(() => { 28 | console.log('Sending keepalive to ', node.peers.length, 'peers...'); 29 | node.publish({type: 'keepalive'}); 30 | }, 30000); 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 numtel 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /examples/receive.js: -------------------------------------------------------------------------------- 1 | const NanoNode = require('..'); 2 | 3 | const node = new NanoNode(); 4 | 5 | node.on('ready', () => { 6 | const address = node.client.address(); 7 | console.log(`server listening ${address.address}:${address.port}`); 8 | // Initial introduction will result in at least a few peers 9 | node.publish({ type: 'keepalive' }); 10 | }); 11 | 12 | // Wait for some initial peer response before sending, not required but 13 | // improves chances of block propagation 14 | setTimeout(() => { 15 | console.log('Publishing block to', node.peers.length, 'peers...'); 16 | console.log(node.publish({ 17 | type: 'publish', 18 | body: { 19 | type: 'receive', 20 | // Latest block hash on this account (frontier) 21 | previous: '6674116837E39DC27D3EDA360EEACBEE38293896C2EC19D91CEF41A8E9649DB2', 22 | // Block hash of the pending 'send' block 23 | source: '2163B77BC0C31A58B2E2B9448F22D360E60EB9B6A5EDBCE328924DD371E65474', 24 | // Calculated based of the block hash 'previous' 25 | work: '985a57eaa0dcb3c2' 26 | } 27 | }, 28 | // Also pass account private key for signing block 29 | 'A95C7460479F4DBB14F6E7D2A1EB4B2C5E75C1F9EE386CC5F5CDCBB8F147E565', 30 | // Optional callback after all messages have been sent 31 | () => { 32 | console.log('Publish complete'); 33 | })); 34 | }, 10000); 35 | 36 | -------------------------------------------------------------------------------- /functions.js: -------------------------------------------------------------------------------- 1 | /* Utility functions exported as static functions on NanoNode class */ 2 | const { blake2b, blake2bInit, blake2bUpdate, blake2bFinal } = require('./blake2b'); 3 | const nacl = require('./nacl'); 4 | /* 5 | BSD 3-Clause License 6 | Copyright (c) 2017, SergiySW 7 | All rights reserved. 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are met: 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | * Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | // Arrays manipulations 31 | function uint8_uint4 (uint8) { 32 | var length = uint8.length; 33 | var uint4 = new Uint8Array(length*2); 34 | for (let i = 0; i < length; i++) { 35 | uint4[i*2] = uint8[i] / 16 | 0; 36 | uint4[i*2+1] = uint8[i] % 16; 37 | } 38 | return uint4; 39 | } 40 | 41 | function uint4_uint8 (uint4) { 42 | var length = uint4.length / 2; 43 | var uint8 = new Uint8Array(length); 44 | for (let i = 0; i < length; i++) uint8[i] = uint4[i*2] * 16 + uint4[i*2+1]; 45 | return uint8; 46 | } 47 | 48 | function uint4_uint5 (uint4) { 49 | var length = uint4.length / 5 * 4; 50 | var uint5 = new Uint8Array(length); 51 | for (let i = 1; i <= length; i++) { 52 | let n = i - 1; 53 | let m = i % 4; 54 | let z = n + ((i - m)/4); 55 | let right = uint4[z] << m; 56 | let left; 57 | if (((length - i) % 4) === 0) left = uint4[z-1] << 4; 58 | else left = uint4[z+1] >> (4 - m); 59 | uint5[n] = (left + right) % 32; 60 | } 61 | return uint5; 62 | } 63 | 64 | function uint5_uint4 (uint5) { 65 | var length = uint5.length / 4 * 5; 66 | var uint4 = new Uint8Array(length); 67 | for (let i = 1; i <= length; i++) { 68 | let n = i - 1; 69 | let m = i % 5; 70 | let z = n - ((i - m)/5); 71 | let right = uint5[z-1] << (5 - m); 72 | let left = uint5[z] >> m; 73 | uint4[n] = (left + right) % 16; 74 | } 75 | return uint4; 76 | } 77 | 78 | function string_uint5 (string) { 79 | var letter_list = letter_list = '13456789abcdefghijkmnopqrstuwxyz'.split(''); 80 | var length = string.length; 81 | var string_array = string.split(''); 82 | var uint5 = new Uint8Array(length); 83 | for (let i = 0; i < length; i++) uint5[i] = letter_list.indexOf(string_array[i]); 84 | return uint5; 85 | } 86 | 87 | function uint5_string (uint5) { 88 | var letter_list = letter_list = '13456789abcdefghijkmnopqrstuwxyz'.split(''); 89 | var string = ""; 90 | for (let i = 0; i < uint5.length; i++) string += letter_list[uint5[i]]; 91 | return string; 92 | } 93 | 94 | function hex_uint8 (hex) { 95 | var length = (hex.length / 2) | 0; 96 | var uint8 = new Uint8Array(length); 97 | for (let i = 0; i < length; i++) uint8[i] = parseInt(hex.substr(i * 2, 2), 16); 98 | return uint8; 99 | } 100 | 101 | 102 | function hex_uint4 (hex) 103 | { 104 | var length = hex.length; 105 | var uint4 = new Uint8Array(length); 106 | for (let i = 0; i < length; i++) uint4[i] = parseInt(hex.substr(i, 1), 16); 107 | return uint4; 108 | } 109 | 110 | 111 | function uint8_hex (uint8) { 112 | var hex = ""; 113 | for (let i = 0; i < uint8.length; i++) 114 | { 115 | aux = uint8[i].toString(16).toUpperCase(); 116 | if(aux.length === 1) 117 | aux = '0'+aux; 118 | hex += aux; 119 | aux = ''; 120 | } 121 | return(hex); 122 | } 123 | 124 | function uint4_hex (uint4) 125 | { 126 | var hex = ""; 127 | for (let i = 0; i < uint4.length; i++) hex += uint4[i].toString(16); 128 | return(hex); 129 | } 130 | 131 | function equal_arrays (array1, array2) { 132 | for (let i = 0; i < array1.length; i++) { 133 | if (array1[i] !== array2[i]) return false; 134 | } 135 | return true; 136 | } 137 | 138 | 139 | function array_crop (array) { 140 | var length = array.length - 1; 141 | var cropped_array = new Uint8Array(length); 142 | for (let i = 0; i < length; i++) 143 | cropped_array[i] = array[i+1]; 144 | return cropped_array; 145 | } 146 | 147 | exports.keyFromAccount = function(account) 148 | { 149 | if(/^xrb_[13][13456789abcdefghijkmnopqrstuwxyz]{59}$/.test(account)) { 150 | var account_crop = account.substring(4,64); 151 | var key_uint4 = array_crop(uint5_uint4(string_uint5(account_crop.substring(0,52)))); 152 | var hash_uint4 = uint5_uint4(string_uint5(account_crop.substring(52,60))); 153 | var key_array = uint4_uint8(key_uint4); 154 | var blake_hash = blake2b(key_array, null, 5).reverse(); 155 | if (equal_arrays(hash_uint4, uint8_uint4(blake_hash))) { 156 | var key = uint4_hex(key_uint4); 157 | return key; 158 | } 159 | else 160 | throw new Error('invalid_checksum'); 161 | } 162 | throw new Error('invalid_account'); 163 | } 164 | 165 | exports.accountFromKey = function(hex) 166 | { 167 | var checksum = ''; 168 | var key_bytes = uint4_uint8( hex_uint4 (hex) ); 169 | var checksum = uint5_string( uint4_uint5( uint8_uint4( blake2b(key_bytes, null, 5).reverse() ) ) ); 170 | var c_account = uint5_string( uint4_uint5( hex_uint4 ('0'+hex) ) ); 171 | return 'xrb_'+ c_account + checksum; 172 | 173 | } 174 | 175 | /* 176 | Calculate key/address pair for a specific account in wallet 177 | @param seed Buffer 32 byte long, or 64 character hex string 178 | @param accountIndex Number 32-bit unsigned integer 179 | @return Object { privateKey, publicKey, address } 180 | */ 181 | exports.accountPair = function(seed, accountIndex) { 182 | const context = blake2bInit(32); 183 | 184 | const indexBuffer = Buffer.alloc(4); 185 | indexBuffer.writeInt32BE(accountIndex); 186 | 187 | if(typeof seed === 'string' && /^[A-Fa-f0-9]{64}$/.test(seed)) 188 | seed = Buffer.from(seed, 'hex'); 189 | else if(!(seed instanceof Buffer)) 190 | throw new Error('invalid_seed'); 191 | 192 | blake2bUpdate(context, seed); 193 | blake2bUpdate(context, indexBuffer); 194 | 195 | const privKey = Buffer.from(blake2bFinal(context)); 196 | const pubKeyHex = Buffer.from(nacl.sign.keyPair.fromSecretKey(privKey).publicKey).toString('hex'); 197 | return { 198 | privateKey: privKey.toString('hex'), 199 | publicKey: pubKeyHex, 200 | address: exports.accountFromKey(pubKeyHex) 201 | }; 202 | } 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node.js Nano Node 2 | 3 | [![Build Status](https://travis-ci.org/numtel/node-nano-node.svg?branch=master)](https://travis-ci.org/numtel/node-nano-node) 4 | 5 | Partial (light, leech) Node for [Nano currency](https://nano.org) (Formerly [Raiblocks](https://raiblocks.net)) for Node.js. 6 | 7 | **Examples:** 8 | 9 | * [Pending](examples/pending.js): How to listen for new pending blocks on a specific account 10 | * [Receive](examples/receive.js): How to publish a block 11 | * [Pull](examples/pull.js): How to fetch an account blockchain history 12 | * [Votes](examples/votes.js): How to listen for representative votes 13 | * [State Blocks](examples/stateblocks.js): How to listen for state blocks 14 | 15 | ## Table of Contents 16 | 17 | * [Installation](#installation) 18 | * [class NanoNode extends EventEmitter](#class-nanonode-extends-eventemitter) 19 | * [Events Emitted](#events-emitted) 20 | * [Properties](#properties) 21 | * [constructor(port)](#constructorport) 22 | * [publish(msg, accountKey, callback)](#publishmsg-accountkey-callback) 23 | * [fetchAccount(publicKey, callback)](#fetchaccountpublickey-callback) 24 | * [Static parseMessage(buf, minimalConfirmAck)](#static-parsemessagebuf-minimalconfirmack) 25 | * [Static renderMessage(msg, accountKey)](#static-rendermessagemsg-accountkey) 26 | * [Static keyFromAccount(account)](#static-keyfromaccountaccount) 27 | * [Static accountFromKey(key)](#static-accountfromkeykey) 28 | * [Static accountPair(seed, index)](#static-accountpairseed-index) 29 | 30 | ## Installation 31 | 32 | ``` 33 | npm install --save nano-node 34 | ``` 35 | 36 | ## class NanoNode extends EventEmitter 37 | 38 | ### Events Emitted 39 | 40 | Name | Listener Arguments | Description 41 | -----|-------------------|----------------------- 42 | `message` | `msg`, `rinfo` | Network message received 43 | `block` | `block`, `rinfo` | Block received (messages of `publish` type) 44 | `vote` | `msg`, `rinfo` | Vote received (messages of `confirm_ack` type) 45 | `ready` | *None* | Server is listening 46 | `error` | `error` | An error has occurred 47 | 48 | ### Properties 49 | 50 | May be set after construction. 51 | 52 | Name | Description 53 | -----|--------------- 54 | `peers` | Array of strings containing hostname concatenated with a colon and the port
Default: `['rai.raiblocks.net:7075']` 55 | `maxPeers` | Maximum number of latest peers to publish new messages
Default: 200 56 | `minimalConfirmAck` | Parsing and validating each `confirm_ack` message as it arrives is very compute intensive due to the 2 blake2b hashes calculated on each receive. Set this value to `false` to parse and validate `comfirm_ack` messages. By default, (`true`) only the `account` public key is parsed. 57 | `tcpTimeout` | Duration to wait when performing TCP operations in milliseconds
Default: 4000 58 | 59 | ### constructor(port) 60 | 61 | * `port` `` Optional, random if unspecified 62 | 63 | Create a new listening UDP service. 64 | 65 | ### publish(msg, accountKey, callback) 66 | 67 | * `msg` `` Required, message definition, supports types `keepalive`, `publish`, `confirm_req`. May also pass fully rendered messages as `Buffer`. 68 | * `accountKey` `` If `publish` message and no signature provided, pass account private key as hex string for block signing. 69 | * `callback` `` Optional, callback function 70 | 71 | Publish a message to known peers. Known peers are managed automatically. Upon receiving a message, the peer is added to the top of the list. Up to `maxPeers` peers are kept. 72 | 73 | **Returns** only on `publish` and `confirm_req` messages: hex block hash 74 | 75 | Publishing regular `keepalive` messages is important to continure receiving messages: 76 | 77 | ```js 78 | const NanoNode = require('nano-node'); 79 | const node = new NanoNode(); 80 | setInterval(() => { 81 | console.log('Sending keepalive to ', node.peers.length, 'peers...'); 82 | node.publish({type: 'keepalive'}); 83 | }, 30000); 84 | ``` 85 | 86 | #### Message Properties 87 | 88 | 89 | Name | Default | Type | Description 90 | -----|--------|-------|-------------- 91 | `type` | *None* | String | Required, `keepalive` or `publish` 92 | `mainnet` | `true` | Boolean | Optional, True (default) for mainnet, false for testnet 93 | `versionMax` | `7` | Integer | Optional 94 | `versionUsing` | `7` | Integer | Optional 95 | `versionMin` | `1` | Integer | Optional 96 | `extensions` | `0` | Integer | Optional, overwritten with block type for `publish` messages 97 | `body` | *None* | Object | Required for `publish` messages 98 | 99 | #### Publish Body Object Properties 100 | 101 | 102 | Name | Required Types | Type | Description 103 | -----|----------------|----|--- 104 | `type` | *All* | String | `send`, `receive`, `open`, `change`, `state` 105 | `previous` | `send`, `receive`, `change`, `state` | 64 character hex string | Hash of previous block in account 106 | `destination` | `send` | 64 character hex string | Account public key of recipient 107 | `balance` | `send`, `state` | 32 character hex string | New balance of account 108 | `source` | `receive`, `open` | 64 character hex string | Hash of pending `send` block 109 | `representative` | `open`, `change`, `state` | 64 character hex string | Public key of representative account to assign 110 | `account` | `open`, `state` | 64 character hex string | Public key of the current account 111 | `link` | `state` | 64 character hex string | Public key of the current account 112 | `signature` | *Optional* | 128 character hex string | Pass precomputed signature in this property. Otherwise, pass `accountKey` argument for block signing. 113 | `work` | *All* | 16 character hex string | Required for all block types, calculated from account public key for `open` type blocks, previous block hash for all other block types. See [raiblocks-pow NPM package](https://github.com/numtel/node-raiblocks-pow) for generating this value. 114 | 115 | ### fetchAccount(publicKey, callback) 116 | 117 | * `publicKey` `` Required, account public key to fetch block history 118 | * `callback` `` `error, result` 119 | 120 | Connect to known peers over TCP and send a `bulk_pull` message for a single account. Wait until timeout from `tcpTimeout` property, then determine the longest valid chain returned. 121 | 122 | Without a full lattice database, the balance of an account can not be determined unless the frontier block is a `send` block. 123 | 124 | **Result object properties:** 125 | 126 | Name | Type | Description 127 | ----|-------|-------------- 128 | `blocks`| Array | Transaction history sorted newest to oldest 129 | `matchProportion`| Number | Between 0 and 1 indicating the proportion of peer responses that match this chain. A value of 1 means all responding peers agree on this length. 130 | `returnCount`| Number | Count of peer responses returned before timeout 131 | 132 | ### Static parseMessage(buf, minimalConfirmAck) 133 | 134 | * `buf` `` Required, full UDP message 135 | * `minimalConfirmAck` `` Optional, default: true. Only parse account value of vote (confirm_ack) messages 136 | 137 | Returns an object with the properties of the message 138 | 139 | ### Static renderMessage(msg, accountKey) 140 | 141 | Useful for obtaining a block's hash without publishing it yet. 142 | 143 | * `msg` `` Required, message properties as described above 144 | * `accountKey` `` Required to sign blocks for `publish`, `confirm_req` messages, otherwise provide signature property 145 | 146 | Returns an object `{ message: , hash: }` `hash` is block hash if available 147 | 148 | ### Static keyFromAccount(account) 149 | 150 | * `account` `` Required, account address to convert 151 | 152 | Return the public key for a given address 153 | 154 | ### Static accountFromKey(key) 155 | 156 | * `key` `` Required, public key to convert 157 | 158 | Return the address for a given account public key 159 | 160 | ### Static accountPair(seed, index) 161 | 162 | * `seed` `` Required, wallet seed as 32 byte Buffer or 64 character hex string 163 | * `index` `` Required, 32-bit unsigned integer specifying account index 164 | 165 | Returns an object `{privateKey: , publicKey: , address: }` 166 | 167 | ## License 168 | 169 | MIT 170 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const NanoNode = require('.'); 3 | 4 | // 2 instances are started, one on a random port and one on a fixed port 5 | const FIXED_PORT = 12121; 6 | const HEADER_EXPECTED = '5243070701'; 7 | 8 | const cases = []; 9 | 10 | // Each case gets 2 NanoNode instances, the second contains the first as a peer 11 | function testCase(caseName, test) { 12 | cases.push(function(cb) { 13 | let node1, node2; 14 | 15 | node1 = new NanoNode(); 16 | node1.peers.splice(0, node1.peers.length); 17 | 18 | node1.on('error', error => { 19 | console.log('node1', error); 20 | process.exit(1); 21 | }); 22 | 23 | const done = () => { 24 | console.log(caseName, 'ok'); 25 | node1.client.close(); 26 | node2.client.close(); 27 | cb(); 28 | }; 29 | 30 | 31 | node1.on('ready', () => { 32 | const address1 = node1.client.address(); 33 | node2 = new NanoNode(FIXED_PORT); 34 | node2.peers.splice(0, node2.peers.length); 35 | 36 | node2.on('error', error => { 37 | console.log('node2', error); 38 | process.exit(1); 39 | }); 40 | 41 | node2.on('ready', () => { 42 | const address2 = node2.client.address(); 43 | assert(address2.port === FIXED_PORT, 'Fixed listening port incorrect'); 44 | // Initial introduction 45 | node2.peers.push('127.0.0.1:' + address1.port); 46 | 47 | test(node1, node2, address1.port, done); 48 | }); 49 | }); 50 | }); 51 | } 52 | 53 | function fixHexCase(obj) { 54 | return Object.keys(obj).reduce((prev, cur) => { 55 | if(cur === 'type') prev[cur] = obj[cur]; 56 | else prev[cur] = Buffer.from(obj[cur], 'hex').toString('hex'); 57 | return prev; 58 | }, {}); 59 | } 60 | 61 | // Keepalive rendered, parsed, and propagated 62 | testCase('keepalive', (node1, node2, node1Port, done) => { 63 | node1.on('message', (msg, rinfo) => { 64 | // Node1 receives keepalive from node2, with node2 as peer 65 | assert(msg.type === 'keepalive'); 66 | assert(msg.body instanceof Array && msg.body.length === 1); 67 | assert(msg.body[0] === '127.0.0.1:' + FIXED_PORT); 68 | }); 69 | 70 | node2.on('message', (msg, rinfo) => { 71 | // Node1 publishes keepalive back to Node2, but without any peers 72 | assert(rinfo.port === node1Port); 73 | assert(msg.type === 'keepalive'); 74 | assert(msg.body instanceof Array && msg.body.length === 0); 75 | done(); 76 | }); 77 | 78 | node2.publish({ 79 | type: 'keepalive', 80 | body: [ '127.0.0.1:' + FIXED_PORT ] 81 | }); 82 | }); 83 | 84 | // Each block type can be rendered, published, received, and parsed successfully 85 | [ 86 | { 87 | obj: { 88 | type: 'send', 89 | previous: '77F062C61390ABA81427DCAB56FE7DD2463CFD048E4B9FD9402D7845A0CF8087', 90 | destination: NanoNode.keyFromAccount('xrb_1tp4yq5x58qyyygxyrmtch5bqdcrhmabbbkykkjqod4jhknuha3rak5ukc1z'), 91 | balance: '00000000000000000000000000000000', 92 | signature: '4CD06C22D01F31AA373AD1A44E9D2C58472CB3137C210E13E0125ED5AADD421BA8AB679B6318B4688AC581A11129AB3A2503845DA33E65DC555F15759102130E', 93 | work: '88d59d8e1b1f8eba' 94 | }, 95 | hex: '000277f062c61390aba81427dcab56fe7dd2463cfd048e4b9fd9402d7845a0cf80876ac2f5c7d19afef79ddf627a53c69bad587cd094a65e94a37aac517ca9b7a038000000000000000000000000000000004cd06c22d01f31aa373ad1a44e9d2c58472cb3137c210e13e0125ed5aadd421ba8ab679b6318b4688ac581a11129ab3a2503845da33e65dc555f15759102130eba8e1f1b8e9dd588' 96 | }, 97 | { 98 | obj: { 99 | type: 'receive', 100 | previous: '77F062C61390ABA81427DCAB56FE7DD2463CFD048E4B9FD9402D7845A0CF8087', 101 | source: '90FB3C55B1CFF0B10AF6D09FA9DFC115D5014B63256A0F5F264E06FA14DA5363', 102 | signature: '4CD06C22D01F31AA373AD1A44E9D2C58472CB3137C210E13E0125ED5AADD421BA8AB679B6318B4688AC581A11129AB3A2503845DA33E65DC555F15759102130E', 103 | work: '88d59d8e1b1f8eba' 104 | }, 105 | hex: '000377f062c61390aba81427dcab56fe7dd2463cfd048e4b9fd9402d7845a0cf808790fb3c55b1cff0b10af6d09fa9dfc115d5014b63256a0f5f264e06fa14da53634cd06c22d01f31aa373ad1a44e9d2c58472cb3137c210e13e0125ed5aadd421ba8ab679b6318b4688ac581a11129ab3a2503845da33e65dc555f15759102130eba8e1f1b8e9dd588' 106 | }, 107 | { 108 | obj: { 109 | type: 'open', 110 | source: '90FB3C55B1CFF0B10AF6D09FA9DFC115D5014B63256A0F5F264E06FA14DA5363', 111 | representative: NanoNode.keyFromAccount('xrb_1tp4yq5x58qyyygxyrmtch5bqdcrhmabbbkykkjqod4jhknuha3rak5ukc1z'), 112 | account: NanoNode.keyFromAccount('xrb_37n4qbnjij3786ow1jh46jk8wjk9s7umwdsy7kphi1kjwim3yupukkrkppgp'), 113 | signature: '4CD06C22D01F31AA373AD1A44E9D2C58472CB3137C210E13E0125ED5AADD421BA8AB679B6318B4688AC581A11129AB3A2503845DA33E65DC555F15759102130E', 114 | work: '88d59d8e1b1f8eba' 115 | }, 116 | hex: '000490fb3c55b1cff0b10af6d09fa9dfc115d5014b63256a0f5f264e06fa14da53636ac2f5c7d19afef79ddf627a53c69bad587cd094a65e94a37aac517ca9b7a0389682ba69184425312bc045e224646e4647c9773e2f3e2cacf80251e4261f6edb4cd06c22d01f31aa373ad1a44e9d2c58472cb3137c210e13e0125ed5aadd421ba8ab679b6318b4688ac581a11129ab3a2503845da33e65dc555f15759102130eba8e1f1b8e9dd588' 117 | }, 118 | { 119 | obj: { 120 | type: 'change', 121 | previous: '77F062C61390ABA81427DCAB56FE7DD2463CFD048E4B9FD9402D7845A0CF8087', 122 | representative: NanoNode.keyFromAccount('xrb_1tp4yq5x58qyyygxyrmtch5bqdcrhmabbbkykkjqod4jhknuha3rak5ukc1z'), 123 | signature: '4CD06C22D01F31AA373AD1A44E9D2C58472CB3137C210E13E0125ED5AADD421BA8AB679B6318B4688AC581A11129AB3A2503845DA33E65DC555F15759102130E', 124 | work: '88d59d8e1b1f8eba' 125 | }, 126 | hex: '000577f062c61390aba81427dcab56fe7dd2463cfd048e4b9fd9402d7845a0cf80876ac2f5c7d19afef79ddf627a53c69bad587cd094a65e94a37aac517ca9b7a0384cd06c22d01f31aa373ad1a44e9d2c58472cb3137c210e13e0125ed5aadd421ba8ab679b6318b4688ac581a11129ab3a2503845da33e65dc555f15759102130eba8e1f1b8e9dd588' 127 | }, 128 | { 129 | obj: { 130 | type: 'state', 131 | previous: '77F062C61390ABA81427DCAB56FE7DD2463CFD048E4B9FD9402D7845A0CF8087', 132 | balance: '00000000000000000000000000000000', 133 | representative: NanoNode.keyFromAccount('xrb_1tp4yq5x58qyyygxyrmtch5bqdcrhmabbbkykkjqod4jhknuha3rak5ukc1z'), 134 | account: NanoNode.keyFromAccount('xrb_37n4qbnjij3786ow1jh46jk8wjk9s7umwdsy7kphi1kjwim3yupukkrkppgp'), 135 | link: '90FB3C55B1CFF0B10AF6D09FA9DFC115D5014B63256A0F5F264E06FA14DA5363', 136 | signature: '4CD06C22D01F31AA373AD1A44E9D2C58472CB3137C210E13E0125ED5AADD421BA8AB679B6318B4688AC581A11129AB3A2503845DA33E65DC555F15759102130E', 137 | work: '88d59d8e1b1f8eba' 138 | }, 139 | hex: '00069682ba69184425312bc045e224646e4647c9773e2f3e2cacf80251e4261f6edb77f062c61390aba81427dcab56fe7dd2463cfd048e4b9fd9402d7845a0cf80876ac2f5c7d19afef79ddf627a53c69bad587cd094a65e94a37aac517ca9b7a0380000000000000000000000000000000090fb3c55b1cff0b10af6d09fa9dfc115d5014b63256a0f5f264e06fa14da53634cd06c22d01f31aa373ad1a44e9d2c58472cb3137c210e13e0125ed5aadd421ba8ab679b6318b4688ac581a11129ab3a2503845da33e65dc555f15759102130e88d59d8e1b1f8eba' 140 | }, 141 | ].forEach((thisCase, index) => { 142 | // s: string message type, i: index of type as hex string 143 | [ { s: 'publish', i: '03' }, { s: 'confirm_req', i: '04' } ].forEach(msgType => { 144 | testCase(msgType.s + '-' + (index + 1) + '-' + thisCase.obj.type, 145 | (node1, node2, node1Port, done) => { 146 | 147 | node1.on('message', (msg, rinfo) => { 148 | assert(msg.type === msgType.s); 149 | const expectedBody = Object.assign({ hash: rendered.hash }, thisCase.obj); 150 | // fixHexCase() removes any hex letter case differences 151 | assert.deepStrictEqual(fixHexCase(msg.body), fixHexCase(expectedBody), 152 | 'parsed body mismatch'); 153 | done(); 154 | }); 155 | 156 | node2.on('message', (msg, rinfo) => { 157 | assert(false, 'unexpected message on node2'); 158 | }); 159 | 160 | const rendered = NanoNode.renderMessage({ 161 | type: msgType.s, 162 | body: thisCase.obj 163 | }); 164 | 165 | // Omit the hex value to see print the rendered value 166 | if(!thisCase.hex) console.log(rendered.message.toString('hex')); 167 | 168 | const expectedRender = HEADER_EXPECTED + msgType.i + thisCase.hex; 169 | 170 | assert(rendered.message.toString('hex') === expectedRender, 171 | 'rendered message mismatch'); 172 | 173 | node2.publish(rendered.message); 174 | } 175 | ); 176 | }); 177 | }); 178 | 179 | // Beginning executing queued test cases 180 | function execCase() { 181 | if(!cases.length) return; 182 | cases.shift()(execCase); 183 | } 184 | execCase(); 185 | -------------------------------------------------------------------------------- /blake2b.js: -------------------------------------------------------------------------------- 1 | // BLAKE.js v1.0.1 2 | // Adaptation of https://github.com/dcposch/blakejs 3 | // 4 | // Blake2B in pure Javascript 5 | // Adapted from the reference implementation in RFC7693 6 | // Ported to Javascript by DC - https://github.com/dcposch 7 | 8 | var ERROR_MSG_INPUT = 'Input must be an string, Buffer or Uint8Array' 9 | 10 | // For convenience, let people hash a string, not just a Uint8Array 11 | function normalizeInput (input) { 12 | var ret 13 | if (input instanceof Uint8Array) { 14 | ret = input 15 | /*} else if (input instanceof Buffer) { 16 | ret = new Uint8Array(input) 17 | } else if (typeof (input) === 'string') { 18 | ret = new Uint8Array(new Buffer(input, 'utf8'))*/ 19 | } else { 20 | throw new Error(ERROR_MSG_INPUT) 21 | } 22 | return ret 23 | } 24 | 25 | // Converts a Uint8Array to a hexadecimal string 26 | // For example, toHex([255, 0, 255]) returns "ff00ff" 27 | function toHex (bytes) { 28 | return Array.prototype.map.call(bytes, function (n) { 29 | return (n < 16 ? '0' : '') + n.toString(16) 30 | }).join('') 31 | } 32 | 33 | // Converts any value in [0...2^32-1] to an 8-character hex string 34 | function uint32ToHex (val) { 35 | return (0x100000000 + val).toString(16).substring(1) 36 | } 37 | 38 | // 64-bit unsigned addition 39 | // Sets v[a,a+1] += v[b,b+1] 40 | // v should be a Uint32Array 41 | function ADD64AA (v, a, b) { 42 | var o0 = v[a] + v[b] 43 | var o1 = v[a + 1] + v[b + 1] 44 | if (o0 >= 0x100000000) { 45 | o1++ 46 | } 47 | v[a] = o0 48 | v[a + 1] = o1 49 | } 50 | 51 | // 64-bit unsigned addition 52 | // Sets v[a,a+1] += b 53 | // b0 is the low 32 bits of b, b1 represents the high 32 bits 54 | function ADD64AC (v, a, b0, b1) { 55 | var o0 = v[a] + b0 56 | if (b0 < 0) { 57 | o0 += 0x100000000 58 | } 59 | var o1 = v[a + 1] + b1 60 | if (o0 >= 0x100000000) { 61 | o1++ 62 | } 63 | v[a] = o0 64 | v[a + 1] = o1 65 | } 66 | 67 | // Little-endian byte access 68 | function B2B_GET32 (arr, i) { 69 | return (arr[i] ^ 70 | (arr[i + 1] << 8) ^ 71 | (arr[i + 2] << 16) ^ 72 | (arr[i + 3] << 24)) 73 | } 74 | 75 | // G Mixing function 76 | // The ROTRs are inlined for speed 77 | function B2B_G (a, b, c, d, ix, iy) { 78 | var x0 = m[ix] 79 | var x1 = m[ix + 1] 80 | var y0 = m[iy] 81 | var y1 = m[iy + 1] 82 | 83 | ADD64AA(v, a, b) // v[a,a+1] += v[b,b+1] ... in JS we must store a uint64 as two uint32s 84 | ADD64AC(v, a, x0, x1) // v[a, a+1] += x ... x0 is the low 32 bits of x, x1 is the high 32 bits 85 | 86 | // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated to the right by 32 bits 87 | var xor0 = v[d] ^ v[a] 88 | var xor1 = v[d + 1] ^ v[a + 1] 89 | v[d] = xor1 90 | v[d + 1] = xor0 91 | 92 | ADD64AA(v, c, d) 93 | 94 | // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 24 bits 95 | xor0 = v[b] ^ v[c] 96 | xor1 = v[b + 1] ^ v[c + 1] 97 | v[b] = (xor0 >>> 24) ^ (xor1 << 8) 98 | v[b + 1] = (xor1 >>> 24) ^ (xor0 << 8) 99 | 100 | ADD64AA(v, a, b) 101 | ADD64AC(v, a, y0, y1) 102 | 103 | // v[d,d+1] = (v[d,d+1] xor v[a,a+1]) rotated right by 16 bits 104 | xor0 = v[d] ^ v[a] 105 | xor1 = v[d + 1] ^ v[a + 1] 106 | v[d] = (xor0 >>> 16) ^ (xor1 << 16) 107 | v[d + 1] = (xor1 >>> 16) ^ (xor0 << 16) 108 | 109 | ADD64AA(v, c, d) 110 | 111 | // v[b,b+1] = (v[b,b+1] xor v[c,c+1]) rotated right by 63 bits 112 | xor0 = v[b] ^ v[c] 113 | xor1 = v[b + 1] ^ v[c + 1] 114 | v[b] = (xor1 >>> 31) ^ (xor0 << 1) 115 | v[b + 1] = (xor0 >>> 31) ^ (xor1 << 1) 116 | } 117 | 118 | // Initialization Vector 119 | var BLAKE2B_IV32 = new Uint32Array([ 120 | 0xF3BCC908, 0x6A09E667, 0x84CAA73B, 0xBB67AE85, 121 | 0xFE94F82B, 0x3C6EF372, 0x5F1D36F1, 0xA54FF53A, 122 | 0xADE682D1, 0x510E527F, 0x2B3E6C1F, 0x9B05688C, 123 | 0xFB41BD6B, 0x1F83D9AB, 0x137E2179, 0x5BE0CD19 124 | ]) 125 | 126 | var SIGMA8 = [ 127 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 128 | 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3, 129 | 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4, 130 | 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8, 131 | 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13, 132 | 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9, 133 | 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11, 134 | 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, 135 | 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5, 136 | 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0, 137 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 138 | 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 139 | ] 140 | 141 | // These are offsets into a uint64 buffer. 142 | // Multiply them all by 2 to make them offsets into a uint32 buffer, 143 | // because this is Javascript and we don't have uint64s 144 | var SIGMA82 = new Uint8Array(SIGMA8.map(function (x) { return x * 2 })) 145 | 146 | // Compression function. 'last' flag indicates last block. 147 | // Note we're representing 16 uint64s as 32 uint32s 148 | var v = new Uint32Array(32) 149 | var m = new Uint32Array(32) 150 | function blake2bCompress (ctx, last) { 151 | var i = 0 152 | 153 | // init work variables 154 | for (i = 0; i < 16; i++) { 155 | v[i] = ctx.h[i] 156 | v[i + 16] = BLAKE2B_IV32[i] 157 | } 158 | 159 | // low 64 bits of offset 160 | v[24] = v[24] ^ ctx.t 161 | v[25] = v[25] ^ (ctx.t / 0x100000000) 162 | // high 64 bits not supported, offset may not be higher than 2**53-1 163 | 164 | // last block flag set ? 165 | if (last) { 166 | v[28] = ~v[28] 167 | v[29] = ~v[29] 168 | } 169 | 170 | // get little-endian words 171 | for (i = 0; i < 32; i++) { 172 | m[i] = B2B_GET32(ctx.b, 4 * i) 173 | } 174 | 175 | // twelve rounds of mixing 176 | // uncomment the DebugPrint calls to log the computation 177 | // and match the RFC sample documentation 178 | // util.debugPrint(' m[16]', m, 64) 179 | for (i = 0; i < 12; i++) { 180 | // util.debugPrint(' (i=' + (i < 10 ? ' ' : '') + i + ') v[16]', v, 64) 181 | B2B_G(0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1]) 182 | B2B_G(2, 10, 18, 26, SIGMA82[i * 16 + 2], SIGMA82[i * 16 + 3]) 183 | B2B_G(4, 12, 20, 28, SIGMA82[i * 16 + 4], SIGMA82[i * 16 + 5]) 184 | B2B_G(6, 14, 22, 30, SIGMA82[i * 16 + 6], SIGMA82[i * 16 + 7]) 185 | B2B_G(0, 10, 20, 30, SIGMA82[i * 16 + 8], SIGMA82[i * 16 + 9]) 186 | B2B_G(2, 12, 22, 24, SIGMA82[i * 16 + 10], SIGMA82[i * 16 + 11]) 187 | B2B_G(4, 14, 16, 26, SIGMA82[i * 16 + 12], SIGMA82[i * 16 + 13]) 188 | B2B_G(6, 8, 18, 28, SIGMA82[i * 16 + 14], SIGMA82[i * 16 + 15]) 189 | } 190 | // util.debugPrint(' (i=12) v[16]', v, 64) 191 | 192 | for (i = 0; i < 16; i++) { 193 | ctx.h[i] = ctx.h[i] ^ v[i] ^ v[i + 16] 194 | } 195 | // util.debugPrint('h[8]', ctx.h, 64) 196 | } 197 | 198 | // Creates a BLAKE2b hashing context 199 | // Requires an output length between 1 and 64 bytes 200 | // Takes an optional Uint8Array key 201 | function blake2bInit (outlen, key) { 202 | if (outlen === 0 || outlen > 64) { 203 | throw new Error('Illegal output length, expected 0 < length <= 64') 204 | } 205 | if (key && key.length > 64) { 206 | throw new Error('Illegal key, expected Uint8Array with 0 < length <= 64') 207 | } 208 | 209 | // state, 'param block' 210 | var ctx = { 211 | b: new Uint8Array(128), 212 | h: new Uint32Array(16), 213 | t: 0, // input count 214 | c: 0, // pointer within buffer 215 | outlen: outlen // output length in bytes 216 | } 217 | 218 | // initialize hash state 219 | for (var i = 0; i < 16; i++) { 220 | ctx.h[i] = BLAKE2B_IV32[i] 221 | } 222 | var keylen = key ? key.length : 0 223 | ctx.h[0] ^= 0x01010000 ^ (keylen << 8) ^ outlen 224 | 225 | // key the hash, if applicable 226 | if (key) { 227 | blake2bUpdate(ctx, key) 228 | // at the end 229 | ctx.c = 128 230 | } 231 | 232 | return ctx 233 | } 234 | 235 | // Updates a BLAKE2b streaming hash 236 | // Requires hash context and Uint8Array (byte array) 237 | function blake2bUpdate (ctx, input) { 238 | for (var i = 0; i < input.length; i++) { 239 | if (ctx.c === 128) { // buffer full ? 240 | ctx.t += ctx.c // add counters 241 | blake2bCompress(ctx, false) // compress (not last) 242 | ctx.c = 0 // counter to zero 243 | } 244 | ctx.b[ctx.c++] = input[i] 245 | } 246 | } 247 | 248 | // Completes a BLAKE2b streaming hash 249 | // Returns a Uint8Array containing the message digest 250 | function blake2bFinal (ctx) { 251 | ctx.t += ctx.c // mark last block offset 252 | 253 | while (ctx.c < 128) { // fill up with zeros 254 | ctx.b[ctx.c++] = 0 255 | } 256 | blake2bCompress(ctx, true) // final block flag = 1 257 | 258 | // little endian convert and store 259 | var out = new Uint8Array(ctx.outlen) 260 | for (var i = 0; i < ctx.outlen; i++) { 261 | out[i] = ctx.h[i >> 2] >> (8 * (i & 3)) 262 | } 263 | return out 264 | } 265 | 266 | // Computes the BLAKE2B hash of a string or byte array, and returns a Uint8Array 267 | // 268 | // Returns a n-byte Uint8Array 269 | // 270 | // Parameters: 271 | // - input - the input bytes, as a string, Buffer or Uint8Array 272 | // - key - optional key Uint8Array, up to 64 bytes 273 | // - outlen - optional output length in bytes, default 64 274 | function blake2b (input, key, outlen) { 275 | // preprocess inputs 276 | outlen = outlen || 64 277 | input = normalizeInput(input) 278 | 279 | // do the math 280 | var ctx = blake2bInit(outlen, key) 281 | blake2bUpdate(ctx, input) 282 | return blake2bFinal(ctx) 283 | } 284 | 285 | // Computes the BLAKE2B hash of a string or byte array 286 | // 287 | // Returns an n-byte hash in hex, all lowercase 288 | // 289 | // Parameters: 290 | // - input - the input bytes, as a string, Buffer, or Uint8Array 291 | // - key - optional key Uint8Array, up to 64 bytes 292 | // - outlen - optional output length in bytes, default 64 293 | function blake2bHex (input, key, outlen) { 294 | var output = blake2b(input, key, outlen) 295 | return toHex(output) 296 | } 297 | 298 | 299 | module.exports = { blake2b, blake2bInit, blake2bUpdate, blake2bFinal }; 300 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events'); 2 | const dgram = require('dgram'); 3 | const net = require('net'); 4 | 5 | const { blake2bInit, blake2bUpdate, blake2bFinal } = require('./blake2b'); 6 | // raiblocks version uses 32-byte secret keys instead of 64-bytes 7 | const nacl = require('./nacl'); 8 | const functions = require('./functions'); 9 | 10 | const IPV4MASK = Buffer.from('00000000000000000000ffff', 'hex'); 11 | const IPV6_PATTERN = /^\[[A-Fa-f0-9:]+\]:[0-9]+$/; 12 | const HEX64CHAR_PATTERN = /^[A-F-a-f0-9]{64}$/; 13 | const BULK_PULL_PREFIX = Buffer.from('52430505010300', 'hex'); 14 | const DEFAULT_FRONTIER_REQ = Buffer.concat([Buffer.alloc(32), Buffer.from('ffffffff', 'hex')]); 15 | 16 | const MESSAGE_TYPES = [ 17 | 'invalid', 18 | 'not_a_type', 19 | 'keepalive', 20 | 'publish', 21 | 'confirm_req', 22 | 'confirm_ack', 23 | 'bulk_pull', 24 | 'bulk_push', 25 | 'frontier_req' 26 | ]; 27 | 28 | const BLOCK_TYPES = { 29 | invalid: 0x00, 30 | not_a_block: 0x01, 31 | send: 0x02, 32 | receive: 0x03, 33 | open: 0x04, 34 | change: 0x05, 35 | state: 0x06 36 | }; 37 | 38 | const BLOCK_TYPES_INDEX = [ 39 | 'invalid', 40 | 'not_a_block', 41 | 'send', 42 | 'receive', 43 | 'open', 44 | 'change', 45 | 'state' 46 | ]; 47 | 48 | const BLOCK_LENGTHS = [ 49 | 0, // invalid 50 | 0, // not_a_block 51 | 80, // send 52 | 64, // receive 53 | 96, // open 54 | 64, // change 55 | 144 // state 56 | ]; 57 | 58 | const REQUIRED_FIELDS = { 59 | // These are listed in the order that they need to be hashed 60 | previous: { types: [ BLOCK_TYPES.send, BLOCK_TYPES.receive, BLOCK_TYPES.change, BLOCK_TYPES.state ], length: 32 }, 61 | destination: { types: [ BLOCK_TYPES.send ], length: 32 }, 62 | balance: { types: [ BLOCK_TYPES.send, BLOCK_TYPES.state ], length: 16 }, 63 | source: { types: [ BLOCK_TYPES.receive, BLOCK_TYPES.open ], length: 32 }, 64 | representative: { types: [ BLOCK_TYPES.open, BLOCK_TYPES.change, BLOCK_TYPES.state ], length: 32 }, 65 | account: { types: [ BLOCK_TYPES.open, BLOCK_TYPES.state ], length: 32 }, 66 | link: { types: [ BLOCK_TYPES.state ], length: 32 } 67 | }; 68 | 69 | // Specify block types whose field hashing order do not match 70 | // the REQUIRED_FIELDS dictionary. 71 | const SPECIAL_ORDERING = { 72 | state: [ 'account', 'previous', 'representative', 'balance', 'link' ] 73 | }; 74 | 75 | // Specify block types whose work value is represented in big-endian format 76 | const BIG_ENDIAN_WORK = [ 77 | 'state' 78 | ]; 79 | 80 | class InvalidMessage extends Error { 81 | constructor(error, rinfo, msg) { 82 | super('invalid_message'); 83 | this.originalError = error; 84 | this.rinfo = rinfo; 85 | this.data = msg; 86 | } 87 | } 88 | 89 | class ParseError extends Error { 90 | constructor(msg, data) { 91 | super(msg); 92 | this.data = data; 93 | } 94 | } 95 | 96 | class NanoNode extends EventEmitter { 97 | /* 98 | @param port Integer random if unspecified 99 | */ 100 | constructor(port) { 101 | super(); 102 | 103 | this.peers = [ 'rai.raiblocks.net:7075' ]; 104 | this.maxPeers = 200; 105 | this.tcpTimeout = 4000; 106 | 107 | this.client = dgram.createSocket('udp4'); 108 | // confirm_ack messages take a lot of processing power to compute the hashes 109 | // with each incoming message, set this value to true to only parse the 110 | // account value from the message packet 111 | this.minimalConfirmAck = true; 112 | 113 | this.client.on('error', error => { 114 | this.emit('error', error); 115 | }); 116 | 117 | this.client.on('message', (msg, rinfo) => { 118 | let buf = Buffer.from(msg); 119 | try { 120 | msg = NanoNode.parseMessage(Buffer.from(msg), this.minimalConfirmAck); 121 | } catch(error) { 122 | this.emit('error', new InvalidMessage(error, rinfo, buf)); 123 | return; 124 | } 125 | 126 | // Add this responding peer to list 127 | const peerAddress = rinfo.address + ':' + rinfo.port; 128 | const peerIndex = this.peers.indexOf(peerAddress); 129 | if(peerIndex !== -1) 130 | this.peers.splice(peerIndex, 1); 131 | this.peers.unshift(peerAddress); 132 | if(this.peers.length > this.maxPeers) 133 | this.peers.pop(); 134 | 135 | this.emit('message', msg, rinfo); 136 | 137 | switch(msg.type) { 138 | case 'keepalive': 139 | // Send keepalive to each peer in message 140 | const probeKeepalive = NanoNode.renderMessage({type: 'keepalive'}).message; 141 | msg.body.forEach(address => { 142 | if(IPV6_PATTERN.test(address)) return; // TODO support packets to IPv6 addresses 143 | const addrParts = parseIp(address); 144 | this.client.send(probeKeepalive, addrParts.port, addrParts.address); 145 | }); 146 | break; 147 | case 'publish': 148 | // XXX be wary of validity, without knowing which account, the 149 | // signature cannot be verified on published blocks 150 | this.emit('block', msg.body, rinfo); 151 | break; 152 | case 'confirm_ack': 153 | this.emit('vote', msg, rinfo); 154 | } 155 | }); 156 | 157 | this.client.on('listening', () => { 158 | this.emit('ready'); 159 | }); 160 | 161 | this.client.bind(port); 162 | } 163 | /* 164 | @param msg Object See README for properties, or pass rendered Buffer 165 | @param accountKey String Optional account private key to sign publish block 166 | @param callback Function Optional 167 | @return String Block hash hex for publish messages 168 | */ 169 | publish(msg, accountKey, callback) { 170 | let msgBuffer; 171 | if(msg instanceof Buffer) { 172 | msgBuffer = { message: msg }; 173 | } else { 174 | msgBuffer = NanoNode.renderMessage(msg, accountKey); 175 | } 176 | let retCount = 0, retLimit = this.peers.length; 177 | this.peers.forEach(address => { 178 | const addrParts = parseIp(address); 179 | this.client.send(msgBuffer.message, addrParts.port, addrParts.address, error => { 180 | error && this.emit('error', error); 181 | retCount++; 182 | callback && retCount === retLimit && callback(); 183 | }); 184 | }); 185 | return msgBuffer.hash; 186 | } 187 | 188 | /* 189 | @param publicKey String Account public key to fetch 190 | @param callback Function error, result 191 | @return Undefined 192 | */ 193 | fetchAccount(publicKey, callback) { 194 | let retCount = 0, retLimit = this.peers.length; 195 | const responses = []; 196 | this.peers.forEach(address => { 197 | const addrParts = parseIp(address); 198 | const client = net.createConnection({ 199 | host: addrParts.address, 200 | port: addrParts.port 201 | }, () => { 202 | // Connected to server 203 | client.write(NanoNode.renderMessage({ 204 | type: 'bulk_pull', 205 | body: publicKey 206 | }).message); 207 | }); 208 | 209 | let blocks = []; 210 | let remaining = Buffer.alloc(0); 211 | client.on('data', (data) => { 212 | const buf = Buffer.concat([ remaining, data ]); 213 | 214 | const parsed = NanoNode.parseChain(buf); 215 | remaining = parsed.remaining; 216 | blocks = blocks.concat(parsed.blocks); 217 | if(remaining === null) { 218 | client.end(); 219 | responses.push(blocks); 220 | } 221 | }); 222 | 223 | client.on('error', error => { 224 | client.destroy(); 225 | clientReturn(); 226 | }); 227 | 228 | client.on('end', () => clientReturn()); 229 | 230 | setTimeout(() => { 231 | client.destroy(); 232 | clientReturn(); 233 | }, this.tcpTimeout); 234 | 235 | function clientReturn() { 236 | retCount++; 237 | callback && retCount === retLimit && callback(null, pullStats(responses)); 238 | } 239 | }); 240 | } 241 | } 242 | 243 | // Static utility functions 244 | Object.assign(NanoNode, functions); 245 | 246 | /* 247 | @param msg Object same format as return from parseMessage 248 | @param accountKey String hex account secret key to sign block (optional) 249 | @return { message: Buffer, hash: null | String } hash will be hex string for publish messages 250 | */ 251 | NanoNode.renderMessage = function(msg, accountKey) { 252 | msg = msg || {}; 253 | 254 | const type = MESSAGE_TYPES.indexOf(msg.type); 255 | if(type === -1) 256 | throw new ParseError('invalid_type', msg); 257 | 258 | const header = Buffer.from([ 259 | 0x52, // magic number 260 | !('mainnet' in msg) || msg.mainnet ? 0x43 : 0x41, // 43 for mainnet, 41 for testnet 261 | 'versionMax' in msg ? msg.versionMax : 0x07, 262 | 'versionUsing' in msg ? msg.versionUsing: 0x07, 263 | 'versionMin' in msg ? msg.versionMin : 0x01, 264 | type, 265 | 0x00, // extensions 16-bits 266 | 0x00 // extensions 16-bits 267 | ]); 268 | 269 | // Block type is used as extension value for publish messages 270 | 'extensions' in msg && header.writeInt16BE(msg.extensions, 6); 271 | 272 | let message; 273 | let hash = null; 274 | if(msg.body instanceof Buffer) { 275 | message = Buffer.concat([ header, msg.body ]); 276 | } else if(msg.body && (msg.type === 'publish' || msg.type === 'confirm_req')) { 277 | if(!('type' in msg.body) || !(msg.body.type in BLOCK_TYPES)) 278 | throw new ParseError('invalid_block_type', msg); 279 | 280 | // Update extension value in header 281 | header.writeInt16BE(BLOCK_TYPES_INDEX.indexOf(msg.body.type), 6); 282 | 283 | const fields = blockFields(msg.body.type); 284 | const values = fields.map(field => { 285 | if(!(field in msg.body)) 286 | throw new ParseError('missing_field_' + field, msg); 287 | 288 | const value = Buffer.from(msg.body[field], 'hex'); 289 | if(value.length !== REQUIRED_FIELDS[field].length) 290 | throw new ParseError('length_mismatch_' + field, msg); 291 | 292 | return value; 293 | }); 294 | 295 | const context = blake2bInit(32, null); 296 | blake2bUpdate(context, Buffer.concat(values)); 297 | hash = blake2bFinal(context); 298 | 299 | let signature 300 | if('signature' in msg.body) { 301 | signature = Buffer.from(msg.body.signature, 'hex'); 302 | } else if(accountKey) { 303 | const accountKeyBuf = Buffer.from(accountKey, 'hex'); 304 | if(accountKeyBuf.length !== 32) 305 | throw new ParseError('length_mismatch_private_key', msg); 306 | signature = Buffer.from(nacl.sign.detached(hash, accountKeyBuf)); 307 | } 308 | let work = Buffer.from(msg.body.work, 'hex') 309 | if(BIG_ENDIAN_WORK.indexOf(msg.body.type) === -1) 310 | work = work.reverse(); 311 | 312 | if(work.length !== 8) 313 | throw new ParseError('length_mismatch_work', msg); 314 | 315 | message = Buffer.concat([header].concat(values).concat([signature, work])); 316 | } else if(msg.type === 'keepalive') { 317 | message = Buffer.concat([header, Buffer.alloc(144)]); 318 | if(msg.body instanceof Array) { 319 | // Put some peers in the keepalive messages 320 | if(msg.body.length > 8) throw new ParseError('too_many_peers', msg); 321 | msg.body.forEach((address, index) => { 322 | if(IPV6_PATTERN.test(address)) 323 | throw new ParseError('ipv6_not_supported'); 324 | const parts = address.split(/[\.:]/); 325 | 326 | // Must match x.x.x.x:xx 327 | if(parts.length !== 5) 328 | throw new ParseError('invalid_address'); 329 | 330 | message[header.length + (16 * index) + 10] = 255; 331 | message[header.length + (16 * index) + 11] = 255; 332 | 333 | parts.forEach((val, i) => { 334 | const pos = header.length + (16 * index) + 12 + i; 335 | val = parseInt(val, 10); 336 | 337 | if(isNaN(val)) 338 | throw new ParseError('invalid_address'); 339 | 340 | if(i === 4) { 341 | // Port value is 2 bytes 342 | message.writeUInt16LE(val, pos); 343 | } else { 344 | message[pos] = val; 345 | } 346 | }); 347 | 348 | }); 349 | } 350 | } else if(msg.type === 'frontier_req') { 351 | // TODO allow specifying body with frontier_req messages 352 | message = DEFAULT_FRONTIER_REQ; 353 | } else if(msg.body && msg.type === 'bulk_pull') { 354 | // TODO support pulling multiple accounts at once 355 | let start, end; 356 | if(typeof msg.body === 'string' && HEX64CHAR_PATTERN.test(msg.body)) { 357 | start = msg.body; 358 | end = msg.body; 359 | } else { 360 | throw new ParseError('invalid_account', msg); 361 | } 362 | message = Buffer.concat([ 363 | header, 364 | Buffer.from(start, 'hex'), 365 | Buffer.from(end, 'hex') 366 | ]); 367 | } 368 | return { message, hash: hash ? Buffer.from(hash).toString('hex') : null }; 369 | } 370 | 371 | function blockFields(blockType) { 372 | if(blockType in SPECIAL_ORDERING) 373 | return SPECIAL_ORDERING[blockType]; 374 | 375 | return Object.keys(REQUIRED_FIELDS).reduce((out, param) => { 376 | if(REQUIRED_FIELDS[param].types.indexOf(BLOCK_TYPES[blockType]) !== -1) out.push(param); 377 | return out; 378 | }, []); 379 | } 380 | 381 | function parseIp(buf, offset) { 382 | if(buf instanceof Buffer) { 383 | const result = []; 384 | if(buf.slice(offset, offset+12).equals(IPV4MASK)) { 385 | // IPv4 386 | for(var i=offset+12; i { 413 | return response.length > 0; 414 | }); 415 | 416 | for(let i=0; i maxLen) { 418 | maxLen = nonEmpties[i].length; 419 | longIndex = i; 420 | matches = 1; 421 | } else if(nonEmpties[i].length === maxLen) { 422 | matches++; 423 | } 424 | } 425 | 426 | return { 427 | blocks: longIndex !== null ? nonEmpties[longIndex] : null, 428 | matchProportion: matches / nonEmpties.length, 429 | returnCount: nonEmpties.length 430 | }; 431 | } 432 | 433 | NanoNode.parseChain = function(buf) { 434 | let offset = 0; 435 | let remaining = Buffer.alloc(0); 436 | let output = []; 437 | 438 | while(buf.length > offset) { 439 | const blockType = buf[offset]; 440 | let blockLen = BLOCK_LENGTHS[blockType] + 1; // + type byte 441 | if(blockType === BLOCK_TYPES.not_a_block) { 442 | remaining = null; 443 | } else { 444 | blockLen += 64 + 8; // + sig + work 445 | if(buf.length < offset+blockLen) { 446 | // This portion will be continued in the next packet 447 | remaining = buf.slice(offset, buf.length); 448 | } else { 449 | const msg = Buffer.concat([ 450 | BULK_PULL_PREFIX, 451 | buf.slice(offset, offset+blockLen) 452 | ]); 453 | output.push(NanoNode.parseMessage(msg).body); 454 | } 455 | } 456 | offset += blockLen; 457 | } 458 | return { blocks: output, remaining: remaining }; 459 | } 460 | 461 | NanoNode.parseMessage = function(buf, minimalConfirmAck) { 462 | const message = {} 463 | if(buf[0] !== 0x52) 464 | throw new ParseError('magic_number', buf); 465 | 466 | message.mainnet = false; 467 | if(buf[1] === 0x43) 468 | message.mainnet = true; 469 | else if(buf[1] !== 0x41) 470 | throw new ParseError('invalid_network', buf); 471 | 472 | message.versionMax = buf[2]; 473 | message.versionUsing = buf[3]; 474 | message.versionMin = buf[4]; 475 | 476 | if(buf[5] >= MESSAGE_TYPES.length) 477 | throw new ParseError('invalid_type', buf); 478 | message.type = MESSAGE_TYPES[buf[5]]; 479 | 480 | message.extensions = buf.readUInt16BE(6); 481 | 482 | switch(message.type) { 483 | case 'keepalive': 484 | // message body contains a list of 8 peers 485 | const peers = []; 486 | if(buf.length !== 152) 487 | throw new ParseError('invalid_block_length', buf); 488 | for(let i=8; i<152; i+=18) { 489 | const peer = parseIp(buf, i); 490 | if(peer !== '[::]:0') 491 | peers.push(peer); 492 | } 493 | message.body = peers; 494 | break; 495 | case 'publish': 496 | case 'confirm_req': 497 | // message body contains a transaction block 498 | const block = { type: BLOCK_TYPES_INDEX[message.extensions] } 499 | if(!block.type) 500 | throw new ParseError('invalid_block_type', buf); 501 | const fields = blockFields(block.type); 502 | 503 | let pos=8; 504 | for(let i=0; i>> 0; this.lo = l|0 >>> 0; }; 11 | var gf = function(init) { 12 | var i, r = new Float64Array(16); 13 | if (init) for (i = 0; i < init.length; i++) r[i] = init[i]; 14 | return r; 15 | }; 16 | 17 | // Pluggable, initialized in high-level API below. 18 | var randombytes = function(/* x, n */) { throw new Error('no PRNG'); }; 19 | 20 | var _0 = new Uint8Array(16); 21 | var _9 = new Uint8Array(32); _9[0] = 9; 22 | 23 | var gf0 = gf(), 24 | gf1 = gf([1]), 25 | _121665 = gf([0xdb41, 1]), 26 | D = gf([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]), 27 | D2 = gf([0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406]), 28 | X = gf([0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169]), 29 | Y = gf([0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666]), 30 | I = gf([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]); 31 | 32 | function L32(x, c) { return (x << c) | (x >>> (32 - c)); } 33 | 34 | function ld32(x, i) { 35 | var u = x[i+3] & 0xff; 36 | u = (u<<8)|(x[i+2] & 0xff); 37 | u = (u<<8)|(x[i+1] & 0xff); 38 | return (u<<8)|(x[i+0] & 0xff); 39 | } 40 | 41 | function dl64(x, i) { 42 | var h = (x[i] << 24) | (x[i+1] << 16) | (x[i+2] << 8) | x[i+3]; 43 | var l = (x[i+4] << 24) | (x[i+5] << 16) | (x[i+6] << 8) | x[i+7]; 44 | return new u64(h, l); 45 | } 46 | 47 | function st32(x, j, u) { 48 | var i; 49 | for (i = 0; i < 4; i++) { x[j+i] = u & 255; u >>>= 8; } 50 | } 51 | 52 | function ts64(x, i, u) { 53 | x[i] = (u.hi >> 24) & 0xff; 54 | x[i+1] = (u.hi >> 16) & 0xff; 55 | x[i+2] = (u.hi >> 8) & 0xff; 56 | x[i+3] = u.hi & 0xff; 57 | x[i+4] = (u.lo >> 24) & 0xff; 58 | x[i+5] = (u.lo >> 16) & 0xff; 59 | x[i+6] = (u.lo >> 8) & 0xff; 60 | x[i+7] = u.lo & 0xff; 61 | } 62 | 63 | function vn(x, xi, y, yi, n) { 64 | var i,d = 0; 65 | for (i = 0; i < n; i++) d |= x[xi+i]^y[yi+i]; 66 | return (1 & ((d - 1) >>> 8)) - 1; 67 | } 68 | 69 | function crypto_verify_16(x, xi, y, yi) { 70 | return vn(x,xi,y,yi,16); 71 | } 72 | 73 | function crypto_verify_32(x, xi, y, yi) { 74 | return vn(x,xi,y,yi,32); 75 | } 76 | 77 | function core(out,inp,k,c,h) { 78 | var w = new Uint32Array(16), x = new Uint32Array(16), 79 | y = new Uint32Array(16), t = new Uint32Array(4); 80 | var i, j, m; 81 | 82 | for (i = 0; i < 4; i++) { 83 | x[5*i] = ld32(c, 4*i); 84 | x[1+i] = ld32(k, 4*i); 85 | x[6+i] = ld32(inp, 4*i); 86 | x[11+i] = ld32(k, 16+4*i); 87 | } 88 | 89 | for (i = 0; i < 16; i++) y[i] = x[i]; 90 | 91 | for (i = 0; i < 20; i++) { 92 | for (j = 0; j < 4; j++) { 93 | for (m = 0; m < 4; m++) t[m] = x[(5*j+4*m)%16]; 94 | t[1] ^= L32((t[0]+t[3])|0, 7); 95 | t[2] ^= L32((t[1]+t[0])|0, 9); 96 | t[3] ^= L32((t[2]+t[1])|0,13); 97 | t[0] ^= L32((t[3]+t[2])|0,18); 98 | for (m = 0; m < 4; m++) w[4*j+(j+m)%4] = t[m]; 99 | } 100 | for (m = 0; m < 16; m++) x[m] = w[m]; 101 | } 102 | 103 | if (h) { 104 | for (i = 0; i < 16; i++) x[i] = (x[i] + y[i]) | 0; 105 | for (i = 0; i < 4; i++) { 106 | x[5*i] = (x[5*i] - ld32(c, 4*i)) | 0; 107 | x[6+i] = (x[6+i] - ld32(inp, 4*i)) | 0; 108 | } 109 | for (i = 0; i < 4; i++) { 110 | st32(out,4*i,x[5*i]); 111 | st32(out,16+4*i,x[6+i]); 112 | } 113 | } else { 114 | for (i = 0; i < 16; i++) st32(out, 4 * i, (x[i] + y[i]) | 0); 115 | } 116 | } 117 | 118 | function crypto_core_salsa20(out,inp,k,c) { 119 | core(out,inp,k,c,false); 120 | return 0; 121 | } 122 | 123 | function crypto_core_hsalsa20(out,inp,k,c) { 124 | core(out,inp,k,c,true); 125 | return 0; 126 | } 127 | 128 | var sigma = new Uint8Array([101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98, 121, 116, 101, 32, 107]); 129 | // "expand 32-byte k" 130 | 131 | function crypto_stream_salsa20_xor(c,cpos,m,mpos,b,n,k) { 132 | var z = new Uint8Array(16), x = new Uint8Array(64); 133 | var u, i; 134 | if (!b) return 0; 135 | for (i = 0; i < 16; i++) z[i] = 0; 136 | for (i = 0; i < 8; i++) z[i] = n[i]; 137 | while (b >= 64) { 138 | crypto_core_salsa20(x,z,k,sigma); 139 | for (i = 0; i < 64; i++) c[cpos+i] = (m?m[mpos+i]:0) ^ x[i]; 140 | u = 1; 141 | for (i = 8; i < 16; i++) { 142 | u = u + (z[i] & 0xff) | 0; 143 | z[i] = u & 0xff; 144 | u >>>= 8; 145 | } 146 | b -= 64; 147 | cpos += 64; 148 | if (m) mpos += 64; 149 | } 150 | if (b > 0) { 151 | crypto_core_salsa20(x,z,k,sigma); 152 | for (i = 0; i < b; i++) c[cpos+i] = (m?m[mpos+i]:0) ^ x[i]; 153 | } 154 | return 0; 155 | } 156 | 157 | function crypto_stream_salsa20(c,cpos,d,n,k) { 158 | return crypto_stream_salsa20_xor(c,cpos,null,0,d,n,k); 159 | } 160 | 161 | function crypto_stream(c,cpos,d,n,k) { 162 | var s = new Uint8Array(32); 163 | crypto_core_hsalsa20(s,n,k,sigma); 164 | return crypto_stream_salsa20(c,cpos,d,n.subarray(16),s); 165 | } 166 | 167 | function crypto_stream_xor(c,cpos,m,mpos,d,n,k) { 168 | var s = new Uint8Array(32); 169 | crypto_core_hsalsa20(s,n,k,sigma); 170 | return crypto_stream_salsa20_xor(c,cpos,m,mpos,d,n.subarray(16),s); 171 | } 172 | 173 | function add1305(h, c) { 174 | var j, u = 0; 175 | for (j = 0; j < 17; j++) { 176 | u = (u + ((h[j] + c[j]) | 0)) | 0; 177 | h[j] = u & 255; 178 | u >>>= 8; 179 | } 180 | } 181 | 182 | var minusp = new Uint32Array([ 183 | 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252 184 | ]); 185 | 186 | function crypto_onetimeauth(out, outpos, m, mpos, n, k) { 187 | var s, i, j, u; 188 | var x = new Uint32Array(17), r = new Uint32Array(17), 189 | h = new Uint32Array(17), c = new Uint32Array(17), 190 | g = new Uint32Array(17); 191 | for (j = 0; j < 17; j++) r[j]=h[j]=0; 192 | for (j = 0; j < 16; j++) r[j]=k[j]; 193 | r[3]&=15; 194 | r[4]&=252; 195 | r[7]&=15; 196 | r[8]&=252; 197 | r[11]&=15; 198 | r[12]&=252; 199 | r[15]&=15; 200 | 201 | while (n > 0) { 202 | for (j = 0; j < 17; j++) c[j] = 0; 203 | for (j = 0; (j < 16) && (j < n); ++j) c[j] = m[mpos+j]; 204 | c[j] = 1; 205 | mpos += j; n -= j; 206 | add1305(h,c); 207 | for (i = 0; i < 17; i++) { 208 | x[i] = 0; 209 | 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; 210 | } 211 | for (i = 0; i < 17; i++) h[i] = x[i]; 212 | u = 0; 213 | for (j = 0; j < 16; j++) { 214 | u = (u + h[j]) | 0; 215 | h[j] = u & 255; 216 | u >>>= 8; 217 | } 218 | u = (u + h[16]) | 0; h[16] = u & 3; 219 | u = (5 * (u >>> 2)) | 0; 220 | for (j = 0; j < 16; j++) { 221 | u = (u + h[j]) | 0; 222 | h[j] = u & 255; 223 | u >>>= 8; 224 | } 225 | u = (u + h[16]) | 0; h[16] = u; 226 | } 227 | 228 | for (j = 0; j < 17; j++) g[j] = h[j]; 229 | add1305(h,minusp); 230 | s = (-(h[16] >>> 7) | 0); 231 | for (j = 0; j < 17; j++) h[j] ^= s & (g[j] ^ h[j]); 232 | 233 | for (j = 0; j < 16; j++) c[j] = k[j + 16]; 234 | c[16] = 0; 235 | add1305(h,c); 236 | for (j = 0; j < 16; j++) out[outpos+j] = h[j]; 237 | return 0; 238 | } 239 | 240 | function crypto_onetimeauth_verify(h, hpos, m, mpos, n, k) { 241 | var x = new Uint8Array(16); 242 | crypto_onetimeauth(x,0,m,mpos,n,k); 243 | return crypto_verify_16(h,hpos,x,0); 244 | } 245 | 246 | function crypto_secretbox(c,m,d,n,k) { 247 | var i; 248 | if (d < 32) return -1; 249 | crypto_stream_xor(c,0,m,0,d,n,k); 250 | crypto_onetimeauth(c, 16, c, 32, d - 32, c); 251 | for (i = 0; i < 16; i++) c[i] = 0; 252 | return 0; 253 | } 254 | 255 | function crypto_secretbox_open(m,c,d,n,k) { 256 | var i; 257 | var x = new Uint8Array(32); 258 | if (d < 32) return -1; 259 | crypto_stream(x,0,32,n,k); 260 | if (crypto_onetimeauth_verify(c, 16,c, 32,d - 32,x) !== 0) return -1; 261 | crypto_stream_xor(m,0,c,0,d,n,k); 262 | for (i = 0; i < 32; i++) m[i] = 0; 263 | return 0; 264 | } 265 | 266 | function set25519(r, a) { 267 | var i; 268 | for (i = 0; i < 16; i++) r[i] = a[i]|0; 269 | } 270 | 271 | function car25519(o) { 272 | var c; 273 | var i; 274 | for (i = 0; i < 16; i++) { 275 | o[i] += 65536; 276 | c = Math.floor(o[i] / 65536); 277 | o[(i+1)*(i<15?1:0)] += c - 1 + 37 * (c-1) * (i===15?1:0); 278 | o[i] -= (c * 65536); 279 | } 280 | } 281 | 282 | function sel25519(p, q, b) { 283 | var t, c = ~(b-1); 284 | for (var i = 0; i < 16; i++) { 285 | t = c & (p[i] ^ q[i]); 286 | p[i] ^= t; 287 | q[i] ^= t; 288 | } 289 | } 290 | 291 | function pack25519(o, n) { 292 | var i, j, b; 293 | var m = gf(), t = gf(); 294 | for (i = 0; i < 16; i++) t[i] = n[i]; 295 | car25519(t); 296 | car25519(t); 297 | car25519(t); 298 | for (j = 0; j < 2; j++) { 299 | m[0] = t[0] - 0xffed; 300 | for (i = 1; i < 15; i++) { 301 | m[i] = t[i] - 0xffff - ((m[i-1]>>16) & 1); 302 | m[i-1] &= 0xffff; 303 | } 304 | m[15] = t[15] - 0x7fff - ((m[14]>>16) & 1); 305 | b = (m[15]>>16) & 1; 306 | m[14] &= 0xffff; 307 | sel25519(t, m, 1-b); 308 | } 309 | for (i = 0; i < 16; i++) { 310 | o[2*i] = t[i] & 0xff; 311 | o[2*i+1] = t[i]>>8; 312 | } 313 | } 314 | 315 | function neq25519(a, b) { 316 | var c = new Uint8Array(32), d = new Uint8Array(32); 317 | pack25519(c, a); 318 | pack25519(d, b); 319 | return crypto_verify_32(c, 0, d, 0); 320 | } 321 | 322 | function par25519(a) { 323 | var d = new Uint8Array(32); 324 | pack25519(d, a); 325 | return d[0] & 1; 326 | } 327 | 328 | function unpack25519(o, n) { 329 | var i; 330 | for (i = 0; i < 16; i++) o[i] = n[2*i] + (n[2*i+1] << 8); 331 | o[15] &= 0x7fff; 332 | } 333 | 334 | function A(o, a, b) { 335 | var i; 336 | for (i = 0; i < 16; i++) o[i] = (a[i] + b[i])|0; 337 | } 338 | 339 | function Z(o, a, b) { 340 | var i; 341 | for (i = 0; i < 16; i++) o[i] = (a[i] - b[i])|0; 342 | } 343 | 344 | function M(o, a, b) { 345 | var i, j, t = new Float64Array(31); 346 | for (i = 0; i < 31; i++) t[i] = 0; 347 | for (i = 0; i < 16; i++) { 348 | for (j = 0; j < 16; j++) { 349 | t[i+j] += a[i] * b[j]; 350 | } 351 | } 352 | for (i = 0; i < 15; i++) { 353 | t[i] += 38 * t[i+16]; 354 | } 355 | for (i = 0; i < 16; i++) o[i] = t[i]; 356 | car25519(o); 357 | car25519(o); 358 | } 359 | 360 | function S(o, a) { 361 | M(o, a, a); 362 | } 363 | 364 | function inv25519(o, i) { 365 | var c = gf(); 366 | var a; 367 | for (a = 0; a < 16; a++) c[a] = i[a]; 368 | for (a = 253; a >= 0; a--) { 369 | S(c, c); 370 | if(a !== 2 && a !== 4) M(c, c, i); 371 | } 372 | for (a = 0; a < 16; a++) o[a] = c[a]; 373 | } 374 | 375 | function pow2523(o, i) { 376 | var c = gf(); 377 | var a; 378 | for (a = 0; a < 16; a++) c[a] = i[a]; 379 | for (a = 250; a >= 0; a--) { 380 | S(c, c); 381 | if(a !== 1) M(c, c, i); 382 | } 383 | for (a = 0; a < 16; a++) o[a] = c[a]; 384 | } 385 | 386 | function crypto_scalarmult(q, n, p) { 387 | var z = new Uint8Array(32); 388 | var x = new Float64Array(80), r, i; 389 | var a = gf(), b = gf(), c = gf(), 390 | d = gf(), e = gf(), f = gf(); 391 | for (i = 0; i < 31; i++) z[i] = n[i]; 392 | z[31]=(n[31]&127)|64; 393 | z[0]&=248; 394 | unpack25519(x,p); 395 | for (i = 0; i < 16; i++) { 396 | b[i]=x[i]; 397 | d[i]=a[i]=c[i]=0; 398 | } 399 | a[0]=d[0]=1; 400 | for (i=254; i>=0; --i) { 401 | r=(z[i>>>3]>>>(i&7))&1; 402 | sel25519(a,b,r); 403 | sel25519(c,d,r); 404 | A(e,a,c); 405 | Z(a,a,c); 406 | A(c,b,d); 407 | Z(b,b,d); 408 | S(d,e); 409 | S(f,a); 410 | M(a,c,a); 411 | M(c,b,e); 412 | A(e,a,c); 413 | Z(a,a,c); 414 | S(b,a); 415 | Z(c,d,f); 416 | M(a,c,_121665); 417 | A(a,a,d); 418 | M(c,c,a); 419 | M(a,d,f); 420 | M(d,b,x); 421 | S(b,e); 422 | sel25519(a,b,r); 423 | sel25519(c,d,r); 424 | } 425 | for (i = 0; i < 16; i++) { 426 | x[i+16]=a[i]; 427 | x[i+32]=c[i]; 428 | x[i+48]=b[i]; 429 | x[i+64]=d[i]; 430 | } 431 | var x32 = x.subarray(32); 432 | var x16 = x.subarray(16); 433 | inv25519(x32,x32); 434 | M(x16,x16,x32); 435 | pack25519(q,x16); 436 | return 0; 437 | } 438 | 439 | function crypto_scalarmult_base(q, n) { 440 | return crypto_scalarmult(q, n, _9); 441 | } 442 | 443 | function crypto_box_keypair(y, x) { 444 | randombytes(x, 32); 445 | return crypto_scalarmult_base(y, x); 446 | } 447 | 448 | function crypto_box_beforenm(k, y, x) { 449 | var s = new Uint8Array(32); 450 | crypto_scalarmult(s, x, y); 451 | return crypto_core_hsalsa20(k, _0, s, sigma); 452 | } 453 | 454 | var crypto_box_afternm = crypto_secretbox; 455 | var crypto_box_open_afternm = crypto_secretbox_open; 456 | 457 | function crypto_box(c, m, d, n, y, x) { 458 | var k = new Uint8Array(32); 459 | crypto_box_beforenm(k, y, x); 460 | return crypto_box_afternm(c, m, d, n, k); 461 | } 462 | 463 | function crypto_box_open(m, c, d, n, y, x) { 464 | var k = new Uint8Array(32); 465 | crypto_box_beforenm(k, y, x); 466 | return crypto_box_open_afternm(m, c, d, n, k); 467 | } 468 | 469 | function add64() { 470 | var a = 0, b = 0, c = 0, d = 0, m16 = 65535, l, h, i; 471 | for (i = 0; i < arguments.length; i++) { 472 | l = arguments[i].lo; 473 | h = arguments[i].hi; 474 | a += (l & m16); b += (l >>> 16); 475 | c += (h & m16); d += (h >>> 16); 476 | } 477 | 478 | b += (a >>> 16); 479 | c += (b >>> 16); 480 | d += (c >>> 16); 481 | 482 | return new u64((c & m16) | (d << 16), (a & m16) | (b << 16)); 483 | } 484 | 485 | function shr64(x, c) { 486 | return new u64((x.hi >>> c), (x.lo >>> c) | (x.hi << (32 - c))); 487 | } 488 | 489 | function xor64() { 490 | var l = 0, h = 0, i; 491 | for (i = 0; i < arguments.length; i++) { 492 | l ^= arguments[i].lo; 493 | h ^= arguments[i].hi; 494 | } 495 | return new u64(h, l); 496 | } 497 | 498 | function R(x, c) { 499 | var h, l, c1 = 32 - c; 500 | if (c < 32) { 501 | h = (x.hi >>> c) | (x.lo << c1); 502 | l = (x.lo >>> c) | (x.hi << c1); 503 | } else if (c < 64) { 504 | h = (x.lo >>> c) | (x.hi << c1); 505 | l = (x.hi >>> c) | (x.lo << c1); 506 | } 507 | return new u64(h, l); 508 | } 509 | 510 | function Ch(x, y, z) { 511 | var h = (x.hi & y.hi) ^ (~x.hi & z.hi), 512 | l = (x.lo & y.lo) ^ (~x.lo & z.lo); 513 | return new u64(h, l); 514 | } 515 | 516 | function Maj(x, y, z) { 517 | var h = (x.hi & y.hi) ^ (x.hi & z.hi) ^ (y.hi & z.hi), 518 | l = (x.lo & y.lo) ^ (x.lo & z.lo) ^ (y.lo & z.lo); 519 | return new u64(h, l); 520 | } 521 | 522 | function Sigma0(x) { return xor64(R(x,28), R(x,34), R(x,39)); } 523 | function Sigma1(x) { return xor64(R(x,14), R(x,18), R(x,41)); } 524 | function sigma0(x) { return xor64(R(x, 1), R(x, 8), shr64(x,7)); } 525 | function sigma1(x) { return xor64(R(x,19), R(x,61), shr64(x,6)); } 526 | 527 | var K = [ 528 | new u64(0x428a2f98, 0xd728ae22), new u64(0x71374491, 0x23ef65cd), 529 | new u64(0xb5c0fbcf, 0xec4d3b2f), new u64(0xe9b5dba5, 0x8189dbbc), 530 | new u64(0x3956c25b, 0xf348b538), new u64(0x59f111f1, 0xb605d019), 531 | new u64(0x923f82a4, 0xaf194f9b), new u64(0xab1c5ed5, 0xda6d8118), 532 | new u64(0xd807aa98, 0xa3030242), new u64(0x12835b01, 0x45706fbe), 533 | new u64(0x243185be, 0x4ee4b28c), new u64(0x550c7dc3, 0xd5ffb4e2), 534 | new u64(0x72be5d74, 0xf27b896f), new u64(0x80deb1fe, 0x3b1696b1), 535 | new u64(0x9bdc06a7, 0x25c71235), new u64(0xc19bf174, 0xcf692694), 536 | new u64(0xe49b69c1, 0x9ef14ad2), new u64(0xefbe4786, 0x384f25e3), 537 | new u64(0x0fc19dc6, 0x8b8cd5b5), new u64(0x240ca1cc, 0x77ac9c65), 538 | new u64(0x2de92c6f, 0x592b0275), new u64(0x4a7484aa, 0x6ea6e483), 539 | new u64(0x5cb0a9dc, 0xbd41fbd4), new u64(0x76f988da, 0x831153b5), 540 | new u64(0x983e5152, 0xee66dfab), new u64(0xa831c66d, 0x2db43210), 541 | new u64(0xb00327c8, 0x98fb213f), new u64(0xbf597fc7, 0xbeef0ee4), 542 | new u64(0xc6e00bf3, 0x3da88fc2), new u64(0xd5a79147, 0x930aa725), 543 | new u64(0x06ca6351, 0xe003826f), new u64(0x14292967, 0x0a0e6e70), 544 | new u64(0x27b70a85, 0x46d22ffc), new u64(0x2e1b2138, 0x5c26c926), 545 | new u64(0x4d2c6dfc, 0x5ac42aed), new u64(0x53380d13, 0x9d95b3df), 546 | new u64(0x650a7354, 0x8baf63de), new u64(0x766a0abb, 0x3c77b2a8), 547 | new u64(0x81c2c92e, 0x47edaee6), new u64(0x92722c85, 0x1482353b), 548 | new u64(0xa2bfe8a1, 0x4cf10364), new u64(0xa81a664b, 0xbc423001), 549 | new u64(0xc24b8b70, 0xd0f89791), new u64(0xc76c51a3, 0x0654be30), 550 | new u64(0xd192e819, 0xd6ef5218), new u64(0xd6990624, 0x5565a910), 551 | new u64(0xf40e3585, 0x5771202a), new u64(0x106aa070, 0x32bbd1b8), 552 | new u64(0x19a4c116, 0xb8d2d0c8), new u64(0x1e376c08, 0x5141ab53), 553 | new u64(0x2748774c, 0xdf8eeb99), new u64(0x34b0bcb5, 0xe19b48a8), 554 | new u64(0x391c0cb3, 0xc5c95a63), new u64(0x4ed8aa4a, 0xe3418acb), 555 | new u64(0x5b9cca4f, 0x7763e373), new u64(0x682e6ff3, 0xd6b2b8a3), 556 | new u64(0x748f82ee, 0x5defb2fc), new u64(0x78a5636f, 0x43172f60), 557 | new u64(0x84c87814, 0xa1f0ab72), new u64(0x8cc70208, 0x1a6439ec), 558 | new u64(0x90befffa, 0x23631e28), new u64(0xa4506ceb, 0xde82bde9), 559 | new u64(0xbef9a3f7, 0xb2c67915), new u64(0xc67178f2, 0xe372532b), 560 | new u64(0xca273ece, 0xea26619c), new u64(0xd186b8c7, 0x21c0c207), 561 | new u64(0xeada7dd6, 0xcde0eb1e), new u64(0xf57d4f7f, 0xee6ed178), 562 | new u64(0x06f067aa, 0x72176fba), new u64(0x0a637dc5, 0xa2c898a6), 563 | new u64(0x113f9804, 0xbef90dae), new u64(0x1b710b35, 0x131c471b), 564 | new u64(0x28db77f5, 0x23047d84), new u64(0x32caab7b, 0x40c72493), 565 | new u64(0x3c9ebe0a, 0x15c9bebc), new u64(0x431d67c4, 0x9c100d4c), 566 | new u64(0x4cc5d4be, 0xcb3e42b6), new u64(0x597f299c, 0xfc657e2a), 567 | new u64(0x5fcb6fab, 0x3ad6faec), new u64(0x6c44198c, 0x4a475817) 568 | ]; 569 | 570 | function crypto_hashblocks(x, m, n) { 571 | var z = [], b = [], a = [], w = [], t, i, j; 572 | 573 | for (i = 0; i < 8; i++) z[i] = a[i] = dl64(x, 8*i); 574 | 575 | var pos = 0; 576 | while (n >= 128) { 577 | for (i = 0; i < 16; i++) w[i] = dl64(m, 8*i+pos); 578 | for (i = 0; i < 80; i++) { 579 | for (j = 0; j < 8; j++) b[j] = a[j]; 580 | t = add64(a[7], Sigma1(a[4]), Ch(a[4], a[5], a[6]), K[i], w[i%16]); 581 | b[7] = add64(t, Sigma0(a[0]), Maj(a[0], a[1], a[2])); 582 | b[3] = add64(b[3], t); 583 | for (j = 0; j < 8; j++) a[(j+1)%8] = b[j]; 584 | if (i%16 === 15) { 585 | for (j = 0; j < 16; j++) { 586 | w[j] = add64(w[j], w[(j+9)%16], sigma0(w[(j+1)%16]), sigma1(w[(j+14)%16])); 587 | } 588 | } 589 | } 590 | 591 | for (i = 0; i < 8; i++) { 592 | a[i] = add64(a[i], z[i]); 593 | z[i] = a[i]; 594 | } 595 | 596 | pos += 128; 597 | n -= 128; 598 | } 599 | 600 | for (i = 0; i < 8; i++) ts64(x, 8*i, z[i]); 601 | return n; 602 | } 603 | 604 | var iv = new Uint8Array([ 605 | 0x6a,0x09,0xe6,0x67,0xf3,0xbc,0xc9,0x08, 606 | 0xbb,0x67,0xae,0x85,0x84,0xca,0xa7,0x3b, 607 | 0x3c,0x6e,0xf3,0x72,0xfe,0x94,0xf8,0x2b, 608 | 0xa5,0x4f,0xf5,0x3a,0x5f,0x1d,0x36,0xf1, 609 | 0x51,0x0e,0x52,0x7f,0xad,0xe6,0x82,0xd1, 610 | 0x9b,0x05,0x68,0x8c,0x2b,0x3e,0x6c,0x1f, 611 | 0x1f,0x83,0xd9,0xab,0xfb,0x41,0xbd,0x6b, 612 | 0x5b,0xe0,0xcd,0x19,0x13,0x7e,0x21,0x79 613 | ]); 614 | 615 | function crypto_hash(out, m, n) { 616 | var h = new Uint8Array(64), x = new Uint8Array(256); 617 | var i, b = n; 618 | 619 | for (i = 0; i < 64; i++) h[i] = iv[i]; 620 | 621 | crypto_hashblocks(h, m, n); 622 | n %= 128; 623 | 624 | for (i = 0; i < 256; i++) x[i] = 0; 625 | for (i = 0; i < n; i++) x[i] = m[b-n+i]; 626 | x[n] = 128; 627 | 628 | n = 256-128*(n<112?1:0); 629 | x[n-9] = 0; 630 | ts64(x, n-8, new u64((b / 0x20000000) | 0, b << 3)); 631 | crypto_hashblocks(h, x, n); 632 | 633 | for (i = 0; i < 64; i++) out[i] = h[i]; 634 | 635 | return 0; 636 | } 637 | 638 | function add(p, q) { 639 | var a = gf(), b = gf(), c = gf(), 640 | d = gf(), e = gf(), f = gf(), 641 | g = gf(), h = gf(), t = gf(); 642 | 643 | Z(a, p[1], p[0]); 644 | Z(t, q[1], q[0]); 645 | M(a, a, t); 646 | A(b, p[0], p[1]); 647 | A(t, q[0], q[1]); 648 | M(b, b, t); 649 | M(c, p[3], q[3]); 650 | M(c, c, D2); 651 | M(d, p[2], q[2]); 652 | A(d, d, d); 653 | Z(e, b, a); 654 | Z(f, d, c); 655 | A(g, d, c); 656 | A(h, b, a); 657 | 658 | M(p[0], e, f); 659 | M(p[1], h, g); 660 | M(p[2], g, f); 661 | M(p[3], e, h); 662 | } 663 | 664 | function cswap(p, q, b) { 665 | var i; 666 | for (i = 0; i < 4; i++) { 667 | sel25519(p[i], q[i], b); 668 | } 669 | } 670 | 671 | function pack(r, p) { 672 | var tx = gf(), ty = gf(), zi = gf(); 673 | inv25519(zi, p[2]); 674 | M(tx, p[0], zi); 675 | M(ty, p[1], zi); 676 | pack25519(r, ty); 677 | r[31] ^= par25519(tx) << 7; 678 | } 679 | 680 | function scalarmult(p, q, s) { 681 | var b, i; 682 | set25519(p[0], gf0); 683 | set25519(p[1], gf1); 684 | set25519(p[2], gf1); 685 | set25519(p[3], gf0); 686 | for (i = 255; i >= 0; --i) { 687 | b = (s[(i/8)|0] >> (i&7)) & 1; 688 | cswap(p, q, b); 689 | add(q, p); 690 | add(p, p); 691 | cswap(p, q, b); 692 | } 693 | } 694 | 695 | function scalarbase(p, s) { 696 | var q = [gf(), gf(), gf(), gf()]; 697 | set25519(q[0], X); 698 | set25519(q[1], Y); 699 | set25519(q[2], gf1); 700 | M(q[3], X, Y); 701 | scalarmult(p, q, s); 702 | } 703 | 704 | function crypto_sign_keypair(pk, sk, seeded) { 705 | var d = new Uint8Array(64); 706 | var p = [gf(), gf(), gf(), gf()]; 707 | var i; 708 | 709 | if (!seeded) randombytes(sk, 32); 710 | 711 | var context = blake2bInit(64); 712 | blake2bUpdate(context, sk); 713 | d = blake2bFinal(context); 714 | 715 | d[0] &= 248; 716 | d[31] &= 127; 717 | d[31] |= 64; 718 | 719 | scalarbase(p, d); 720 | pack(pk, p); 721 | 722 | return 0; 723 | } 724 | 725 | function derivePublicFromSecret(sk) 726 | { 727 | var d = new Uint8Array(64); 728 | var p = [gf(), gf(), gf(), gf()]; 729 | var i; 730 | var pk = new Uint8Array(32); 731 | var context = blake2bInit(64); 732 | blake2bUpdate(context, sk); 733 | d = blake2bFinal(context); 734 | 735 | d[0] &= 248; 736 | d[31] &= 127; 737 | d[31] |= 64; 738 | 739 | scalarbase(p, d); 740 | pack(pk, p); 741 | return pk; 742 | } 743 | 744 | var L = new Float64Array([0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10]); 745 | 746 | function modL(r, x) { 747 | var carry, i, j, k; 748 | for (i = 63; i >= 32; --i) { 749 | carry = 0; 750 | for (j = i - 32, k = i - 12; j < k; ++j) { 751 | x[j] += carry - 16 * x[i] * L[j - (i - 32)]; 752 | carry = (x[j] + 128) >> 8; 753 | x[j] -= carry * 256; 754 | } 755 | x[j] += carry; 756 | x[i] = 0; 757 | } 758 | carry = 0; 759 | for (j = 0; j < 32; j++) { 760 | x[j] += carry - (x[31] >> 4) * L[j]; 761 | carry = x[j] >> 8; 762 | x[j] &= 255; 763 | } 764 | for (j = 0; j < 32; j++) x[j] -= carry * L[j]; 765 | for (i = 0; i < 32; i++) { 766 | x[i+1] += x[i] >> 8; 767 | r[i] = x[i] & 255; 768 | } 769 | } 770 | 771 | function reduce(r) { 772 | var x = new Float64Array(64), i; 773 | for (i = 0; i < 64; i++) x[i] = r[i]; 774 | for (i = 0; i < 64; i++) r[i] = 0; 775 | modL(r, x); 776 | } 777 | 778 | // Note: difference from C - smlen returned, not passed as argument. 779 | function crypto_sign(sm, m, n, sk) { 780 | var d = new Uint8Array(64), h = new Uint8Array(64), r = new Uint8Array(64); 781 | var i, j, x = new Float64Array(64); 782 | var p = [gf(), gf(), gf(), gf()]; 783 | 784 | var pk = derivePublicFromSecret(sk); 785 | 786 | var context = blake2bInit(64, null); 787 | blake2bUpdate(context, sk); 788 | d = blake2bFinal(context); 789 | d[0] &= 248; 790 | d[31] &= 127; 791 | d[31] |= 64; 792 | 793 | var smlen = n + 64; 794 | for (i = 0; i < n; i++) sm[64 + i] = m[i]; 795 | for (i = 0; i < 32; i++) sm[32 + i] = d[32 + i]; 796 | 797 | context = blake2bInit(64, null); 798 | blake2bUpdate(context, sm.subarray(32)); 799 | r = blake2bFinal(context); 800 | 801 | reduce(r); 802 | scalarbase(p, r); 803 | pack(sm, p); 804 | 805 | for (i = 32; i < 64; i++) sm[i] = pk[i-32]; 806 | 807 | context = blake2bInit(64, null); 808 | blake2bUpdate(context, sm); 809 | h = blake2bFinal(context); 810 | 811 | reduce(h); 812 | 813 | for (i = 0; i < 64; i++) x[i] = 0; 814 | for (i = 0; i < 32; i++) x[i] = r[i]; 815 | for (i = 0; i < 32; i++) { 816 | for (j = 0; j < 32; j++) { 817 | x[i+j] += h[i] * d[j]; 818 | } 819 | } 820 | 821 | modL(sm.subarray(32), x); 822 | return smlen; 823 | } 824 | 825 | function unpackneg(r, p) { 826 | var t = gf(), chk = gf(), num = gf(), 827 | den = gf(), den2 = gf(), den4 = gf(), 828 | den6 = gf(); 829 | 830 | set25519(r[2], gf1); 831 | unpack25519(r[1], p); 832 | S(num, r[1]); 833 | M(den, num, D); 834 | Z(num, num, r[2]); 835 | A(den, r[2], den); 836 | 837 | S(den2, den); 838 | S(den4, den2); 839 | M(den6, den4, den2); 840 | M(t, den6, num); 841 | M(t, t, den); 842 | 843 | pow2523(t, t); 844 | M(t, t, num); 845 | M(t, t, den); 846 | M(t, t, den); 847 | M(r[0], t, den); 848 | 849 | S(chk, r[0]); 850 | M(chk, chk, den); 851 | if (neq25519(chk, num)) M(r[0], r[0], I); 852 | 853 | S(chk, r[0]); 854 | M(chk, chk, den); 855 | if (neq25519(chk, num)) return -1; 856 | 857 | if (par25519(r[0]) === (p[31]>>7)) Z(r[0], gf0, r[0]); 858 | 859 | M(r[3], r[0], r[1]); 860 | return 0; 861 | } 862 | 863 | function crypto_sign_open(m, sm, n, pk) { 864 | var i, mlen; 865 | var t = new Uint8Array(32), h = new Uint8Array(64); 866 | var p = [gf(), gf(), gf(), gf()], 867 | q = [gf(), gf(), gf(), gf()]; 868 | 869 | mlen = -1; 870 | if (n < 64) return -1; 871 | 872 | if (unpackneg(q, pk)) return -1; 873 | 874 | for (i = 0; i < n; i++) m[i] = sm[i]; 875 | for (i = 0; i < 32; i++) m[i+32] = pk[i]; 876 | //crypto_hash(h, m, n); 877 | 878 | context = blake2bInit(64, null); 879 | blake2bUpdate(context, m); 880 | h = blake2bFinal(context); 881 | 882 | reduce(h); 883 | scalarmult(p, q, h); 884 | 885 | scalarbase(q, sm.subarray(32)); 886 | add(p, q); 887 | pack(t, p); 888 | 889 | n -= 64; 890 | if (crypto_verify_32(sm, 0, t, 0)) { 891 | for (i = 0; i < n; i++) m[i] = 0; 892 | return -1; 893 | } 894 | 895 | for (i = 0; i < n; i++) m[i] = sm[i + 64]; 896 | mlen = n; 897 | return mlen; 898 | } 899 | 900 | var crypto_secretbox_KEYBYTES = 32, 901 | crypto_secretbox_NONCEBYTES = 24, 902 | crypto_secretbox_ZEROBYTES = 32, 903 | crypto_secretbox_BOXZEROBYTES = 16, 904 | crypto_scalarmult_BYTES = 32, 905 | crypto_scalarmult_SCALARBYTES = 32, 906 | crypto_box_PUBLICKEYBYTES = 32, 907 | crypto_box_SECRETKEYBYTES = 32, 908 | crypto_box_BEFORENMBYTES = 32, 909 | crypto_box_NONCEBYTES = crypto_secretbox_NONCEBYTES, 910 | crypto_box_ZEROBYTES = crypto_secretbox_ZEROBYTES, 911 | crypto_box_BOXZEROBYTES = crypto_secretbox_BOXZEROBYTES, 912 | crypto_sign_BYTES = 64, 913 | crypto_sign_PUBLICKEYBYTES = 32, 914 | crypto_sign_SECRETKEYBYTES = 32, 915 | crypto_sign_SEEDBYTES = 32, 916 | crypto_hash_BYTES = 64; 917 | 918 | nacl.lowlevel = { 919 | crypto_core_hsalsa20: crypto_core_hsalsa20, 920 | crypto_stream_xor: crypto_stream_xor, 921 | crypto_stream: crypto_stream, 922 | crypto_stream_salsa20_xor: crypto_stream_salsa20_xor, 923 | crypto_stream_salsa20: crypto_stream_salsa20, 924 | crypto_onetimeauth: crypto_onetimeauth, 925 | crypto_onetimeauth_verify: crypto_onetimeauth_verify, 926 | crypto_verify_16: crypto_verify_16, 927 | crypto_verify_32: crypto_verify_32, 928 | crypto_secretbox: crypto_secretbox, 929 | crypto_secretbox_open: crypto_secretbox_open, 930 | crypto_scalarmult: crypto_scalarmult, 931 | crypto_scalarmult_base: crypto_scalarmult_base, 932 | crypto_box_beforenm: crypto_box_beforenm, 933 | crypto_box_afternm: crypto_box_afternm, 934 | crypto_box: crypto_box, 935 | crypto_box_open: crypto_box_open, 936 | crypto_box_keypair: crypto_box_keypair, 937 | crypto_hash: crypto_hash, 938 | crypto_sign: crypto_sign, 939 | crypto_sign_keypair: crypto_sign_keypair, 940 | crypto_sign_open: crypto_sign_open, 941 | 942 | crypto_secretbox_KEYBYTES: crypto_secretbox_KEYBYTES, 943 | crypto_secretbox_NONCEBYTES: crypto_secretbox_NONCEBYTES, 944 | crypto_secretbox_ZEROBYTES: crypto_secretbox_ZEROBYTES, 945 | crypto_secretbox_BOXZEROBYTES: crypto_secretbox_BOXZEROBYTES, 946 | crypto_scalarmult_BYTES: crypto_scalarmult_BYTES, 947 | crypto_scalarmult_SCALARBYTES: crypto_scalarmult_SCALARBYTES, 948 | crypto_box_PUBLICKEYBYTES: crypto_box_PUBLICKEYBYTES, 949 | crypto_box_SECRETKEYBYTES: crypto_box_SECRETKEYBYTES, 950 | crypto_box_BEFORENMBYTES: crypto_box_BEFORENMBYTES, 951 | crypto_box_NONCEBYTES: crypto_box_NONCEBYTES, 952 | crypto_box_ZEROBYTES: crypto_box_ZEROBYTES, 953 | crypto_box_BOXZEROBYTES: crypto_box_BOXZEROBYTES, 954 | crypto_sign_BYTES: crypto_sign_BYTES, 955 | crypto_sign_PUBLICKEYBYTES: crypto_sign_PUBLICKEYBYTES, 956 | crypto_sign_SECRETKEYBYTES: crypto_sign_SECRETKEYBYTES, 957 | crypto_sign_SEEDBYTES: crypto_sign_SEEDBYTES, 958 | crypto_hash_BYTES: crypto_hash_BYTES 959 | }; 960 | 961 | /* High-level API */ 962 | 963 | function checkLengths(k, n) { 964 | if (k.length !== crypto_secretbox_KEYBYTES) throw new Error('bad key size'); 965 | if (n.length !== crypto_secretbox_NONCEBYTES) throw new Error('bad nonce size'); 966 | } 967 | 968 | function checkBoxLengths(pk, sk) { 969 | if (pk.length !== crypto_box_PUBLICKEYBYTES) throw new Error('bad public key size'); 970 | if (sk.length !== crypto_box_SECRETKEYBYTES) throw new Error('bad secret key size'); 971 | } 972 | 973 | function checkArrayTypes() { 974 | for (var i = 0; i < arguments.length; i++) { 975 | if (!(arguments[i] instanceof Uint8Array)) 976 | throw new TypeError('unexpected type, use Uint8Array'); 977 | } 978 | } 979 | 980 | function cleanup(arr) { 981 | for (var i = 0; i < arr.length; i++) arr[i] = 0; 982 | } 983 | 984 | nacl.randomBytes = function(n) { 985 | var b = new Uint8Array(n); 986 | randombytes(b, n); 987 | return b; 988 | }; 989 | 990 | nacl.secretbox = function(msg, nonce, key) { 991 | checkArrayTypes(msg, nonce, key); 992 | checkLengths(key, nonce); 993 | var m = new Uint8Array(crypto_secretbox_ZEROBYTES + msg.length); 994 | var c = new Uint8Array(m.length); 995 | for (var i = 0; i < msg.length; i++) m[i+crypto_secretbox_ZEROBYTES] = msg[i]; 996 | crypto_secretbox(c, m, m.length, nonce, key); 997 | return c.subarray(crypto_secretbox_BOXZEROBYTES); 998 | }; 999 | 1000 | nacl.secretbox.open = function(box, nonce, key) { 1001 | checkArrayTypes(box, nonce, key); 1002 | checkLengths(key, nonce); 1003 | var c = new Uint8Array(crypto_secretbox_BOXZEROBYTES + box.length); 1004 | var m = new Uint8Array(c.length); 1005 | for (var i = 0; i < box.length; i++) c[i+crypto_secretbox_BOXZEROBYTES] = box[i]; 1006 | if (c.length < 32) return null; 1007 | if (crypto_secretbox_open(m, c, c.length, nonce, key) !== 0) return null; 1008 | return m.subarray(crypto_secretbox_ZEROBYTES); 1009 | }; 1010 | 1011 | nacl.secretbox.keyLength = crypto_secretbox_KEYBYTES; 1012 | nacl.secretbox.nonceLength = crypto_secretbox_NONCEBYTES; 1013 | nacl.secretbox.overheadLength = crypto_secretbox_BOXZEROBYTES; 1014 | 1015 | nacl.scalarMult = function(n, p) { 1016 | checkArrayTypes(n, p); 1017 | if (n.length !== crypto_scalarmult_SCALARBYTES) throw new Error('bad n size'); 1018 | if (p.length !== crypto_scalarmult_BYTES) throw new Error('bad p size'); 1019 | var q = new Uint8Array(crypto_scalarmult_BYTES); 1020 | crypto_scalarmult(q, n, p); 1021 | return q; 1022 | }; 1023 | 1024 | nacl.scalarMult.base = function(n) { 1025 | checkArrayTypes(n); 1026 | if (n.length !== crypto_scalarmult_SCALARBYTES) throw new Error('bad n size'); 1027 | var q = new Uint8Array(crypto_scalarmult_BYTES); 1028 | crypto_scalarmult_base(q, n); 1029 | return q; 1030 | }; 1031 | 1032 | nacl.scalarMult.scalarLength = crypto_scalarmult_SCALARBYTES; 1033 | nacl.scalarMult.groupElementLength = crypto_scalarmult_BYTES; 1034 | 1035 | nacl.box = function(msg, nonce, publicKey, secretKey) { 1036 | var k = nacl.box.before(publicKey, secretKey); 1037 | return nacl.secretbox(msg, nonce, k); 1038 | }; 1039 | 1040 | nacl.box.before = function(publicKey, secretKey) { 1041 | checkArrayTypes(publicKey, secretKey); 1042 | checkBoxLengths(publicKey, secretKey); 1043 | var k = new Uint8Array(crypto_box_BEFORENMBYTES); 1044 | crypto_box_beforenm(k, publicKey, secretKey); 1045 | return k; 1046 | }; 1047 | 1048 | nacl.box.after = nacl.secretbox; 1049 | 1050 | nacl.box.open = function(msg, nonce, publicKey, secretKey) { 1051 | var k = nacl.box.before(publicKey, secretKey); 1052 | return nacl.secretbox.open(msg, nonce, k); 1053 | }; 1054 | 1055 | nacl.box.open.after = nacl.secretbox.open; 1056 | 1057 | nacl.box.keyPair = function() { 1058 | var pk = new Uint8Array(crypto_box_PUBLICKEYBYTES); 1059 | var sk = new Uint8Array(crypto_box_SECRETKEYBYTES); 1060 | crypto_box_keypair(pk, sk); 1061 | return {publicKey: pk, secretKey: sk}; 1062 | }; 1063 | 1064 | nacl.box.keyPair.fromSecretKey = function(secretKey) { 1065 | checkArrayTypes(secretKey); 1066 | if (secretKey.length !== crypto_box_SECRETKEYBYTES) 1067 | throw new Error('bad secret key size'); 1068 | var pk = new Uint8Array(crypto_box_PUBLICKEYBYTES); 1069 | crypto_scalarmult_base(pk, secretKey); 1070 | return {publicKey: pk, secretKey: new Uint8Array(secretKey)}; 1071 | }; 1072 | 1073 | nacl.box.publicKeyLength = crypto_box_PUBLICKEYBYTES; 1074 | nacl.box.secretKeyLength = crypto_box_SECRETKEYBYTES; 1075 | nacl.box.sharedKeyLength = crypto_box_BEFORENMBYTES; 1076 | nacl.box.nonceLength = crypto_box_NONCEBYTES; 1077 | nacl.box.overheadLength = nacl.secretbox.overheadLength; 1078 | 1079 | nacl.sign = function(msg, secretKey) { 1080 | checkArrayTypes(msg, secretKey); 1081 | if (secretKey.length !== crypto_sign_SECRETKEYBYTES) 1082 | throw new Error('bad secret key size'); 1083 | var signedMsg = new Uint8Array(crypto_sign_BYTES+msg.length); 1084 | crypto_sign(signedMsg, msg, msg.length, secretKey); 1085 | return signedMsg; 1086 | }; 1087 | 1088 | nacl.sign.open = function(signedMsg, publicKey) { 1089 | checkArrayTypes(signedMsg, publicKey); 1090 | if (publicKey.length !== crypto_sign_PUBLICKEYBYTES) 1091 | throw new Error('bad public key size'); 1092 | var tmp = new Uint8Array(signedMsg.length); 1093 | var mlen = crypto_sign_open(tmp, signedMsg, signedMsg.length, publicKey); 1094 | if (mlen < 0) return null; 1095 | var m = new Uint8Array(mlen); 1096 | for (var i = 0; i < m.length; i++) m[i] = tmp[i]; 1097 | return m; 1098 | }; 1099 | 1100 | nacl.sign.detached = function(msg, secretKey) { 1101 | var signedMsg = nacl.sign(msg, secretKey); 1102 | var sig = new Uint8Array(crypto_sign_BYTES); 1103 | for (var i = 0; i < sig.length; i++) sig[i] = signedMsg[i]; 1104 | return sig; 1105 | }; 1106 | 1107 | nacl.sign.detached.verify = function(msg, sig, publicKey) { 1108 | checkArrayTypes(msg, sig, publicKey); 1109 | if (sig.length !== crypto_sign_BYTES) 1110 | throw new Error('bad signature size'); 1111 | if (publicKey.length !== crypto_sign_PUBLICKEYBYTES) 1112 | throw new Error('bad public key size'); 1113 | var sm = new Uint8Array(crypto_sign_BYTES + msg.length); 1114 | var m = new Uint8Array(crypto_sign_BYTES + msg.length); 1115 | var i; 1116 | for (i = 0; i < crypto_sign_BYTES; i++) sm[i] = sig[i]; 1117 | for (i = 0; i < msg.length; i++) sm[i+crypto_sign_BYTES] = msg[i]; 1118 | return (crypto_sign_open(m, sm, sm.length, publicKey) >= 0); 1119 | }; 1120 | 1121 | nacl.sign.keyPair = function() { 1122 | var pk = new Uint8Array(crypto_sign_PUBLICKEYBYTES); 1123 | var sk = new Uint8Array(crypto_sign_SECRETKEYBYTES); 1124 | crypto_sign_keypair(pk, sk); 1125 | return {publicKey: pk, secretKey: sk}; 1126 | }; 1127 | 1128 | nacl.sign.keyPair.fromSecretKey = function(secretKey) { 1129 | checkArrayTypes(secretKey); 1130 | if (secretKey.length !== crypto_sign_SECRETKEYBYTES) 1131 | throw new Error('bad secret key size'); 1132 | var pk = new Uint8Array(crypto_sign_PUBLICKEYBYTES); 1133 | pk = derivePublicFromSecret(secretKey); 1134 | return {publicKey: pk, secretKey: new Uint8Array(secretKey)}; 1135 | }; 1136 | 1137 | nacl.sign.keyPair.fromSeed = function(seed) { 1138 | checkArrayTypes(seed); 1139 | if (seed.length !== crypto_sign_SEEDBYTES) 1140 | throw new Error('bad seed size'); 1141 | var pk = new Uint8Array(crypto_sign_PUBLICKEYBYTES); 1142 | var sk = new Uint8Array(crypto_sign_SECRETKEYBYTES); 1143 | for (var i = 0; i < 32; i++) sk[i] = seed[i]; 1144 | crypto_sign_keypair(pk, sk, true); 1145 | return {publicKey: pk, secretKey: sk}; 1146 | }; 1147 | 1148 | nacl.sign.publicKeyLength = crypto_sign_PUBLICKEYBYTES; 1149 | nacl.sign.secretKeyLength = crypto_sign_SECRETKEYBYTES; 1150 | nacl.sign.seedLength = crypto_sign_SEEDBYTES; 1151 | nacl.sign.signatureLength = crypto_sign_BYTES; 1152 | 1153 | nacl.hash = function(msg) { 1154 | checkArrayTypes(msg); 1155 | var h = new Uint8Array(crypto_hash_BYTES); 1156 | crypto_hash(h, msg, msg.length); 1157 | return h; 1158 | }; 1159 | 1160 | nacl.hash.hashLength = crypto_hash_BYTES; 1161 | 1162 | nacl.verify = function(x, y) { 1163 | checkArrayTypes(x, y); 1164 | // Zero length arguments are considered not equal. 1165 | if (x.length === 0 || y.length === 0) return false; 1166 | if (x.length !== y.length) return false; 1167 | return (vn(x, 0, y, 0, x.length) === 0) ? true : false; 1168 | }; 1169 | 1170 | nacl.setPRNG = function(fn) { 1171 | randombytes = fn; 1172 | }; 1173 | 1174 | (function() { 1175 | // Initialize PRNG if environment provides CSPRNG. 1176 | // If not, methods calling randombytes will throw. 1177 | var crypto = typeof self !== 'undefined' ? (self.crypto || self.msCrypto) : null; 1178 | if (crypto && crypto.getRandomValues) { 1179 | // Browsers. 1180 | var QUOTA = 65536; 1181 | nacl.setPRNG(function(x, n) { 1182 | var i, v = new Uint8Array(n); 1183 | for (i = 0; i < n; i += QUOTA) { 1184 | crypto.getRandomValues(v.subarray(i, i + Math.min(n - i, QUOTA))); 1185 | } 1186 | for (i = 0; i < n; i++) x[i] = v[i]; 1187 | cleanup(v); 1188 | }); 1189 | } else if (typeof require !== 'undefined') { 1190 | // Node.js. 1191 | crypto = require('crypto'); 1192 | if (crypto && crypto.randomBytes) { 1193 | nacl.setPRNG(function(x, n) { 1194 | var i, v = crypto.randomBytes(n); 1195 | for (i = 0; i < n; i++) x[i] = v[i]; 1196 | cleanup(v); 1197 | }); 1198 | } 1199 | } 1200 | })(); 1201 | 1202 | 1203 | module.exports = nacl; 1204 | --------------------------------------------------------------------------------