message.js
1 const ip = require('ip') 2 const rlp = require('rlp-encoding') 3 const secp256k1 = require('secp256k1') 4 const Buffer = require('safe-buffer').Buffer 5 const { keccak256, int2buffer, buffer2int, assertEq } = require('../util') 6 7 function getTimestamp () { 8 return (Date.now() / 1000) | 0 9 } 10 11 const timestamp = { 12 encode: function (value = getTimestamp() + 60) { 13 const buffer = Buffer.allocUnsafe(4) 14 buffer.writeUInt32BE(value) 15 return buffer 16 }, 17 decode: function (buffer) { 18 if (buffer.length !== 4) throw new RangeError(`Invalid timestamp buffer :${buffer.toString('hex')}`) 19 return buffer.readUInt32BE(0) 20 } 21 } 22 23 const address = { 24 encode: function (value) { 25 if (ip.isV4Format(value)) return ip.toBuffer(value) 26 if (ip.isV6Format(value)) return ip.toBuffer(value) 27 throw new Error(`Invalid address: ${value}`) 28 }, 29 decode: function (buffer) { 30 if (buffer.length === 4) return ip.toString(buffer) 31 if (buffer.length === 16) return ip.toString(buffer) 32 33 const str = buffer.toString() 34 if (ip.isV4Format(str) || ip.isV6Format(str)) return str 35 36 // also can be host, but skip it right now (because need async function for resolve) 37 throw new Error(`Invalid address buffer: ${buffer.toString('hex')}`) 38 } 39 } 40 41 const port = { 42 encode: function (value) { 43 if (value === null) return Buffer.allocUnsafe(0) 44 if ((value >>> 16) > 0) throw new RangeError(`Invalid port: ${value}`) 45 return Buffer.from([ (value >>> 8) & 0xff, (value >>> 0) & 0xff ]) 46 }, 47 decode: function (buffer) { 48 if (buffer.length === 0) return null 49 // if (buffer.length !== 2) throw new RangeError(`Invalid port buffer: ${buffer.toString('hex')}`) 50 return buffer2int(buffer) 51 } 52 } 53 54 const endpoint = { 55 encode: function (obj) { 56 return [ 57 address.encode(obj.address), 58 port.encode(obj.udpPort), 59 port.encode(obj.tcpPort) 60 ] 61 }, 62 decode: function (payload) { 63 return { 64 address: address.decode(payload[0]), 65 udpPort: port.decode(payload[1]), 66 tcpPort: port.decode(payload[2]) 67 } 68 } 69 } 70 71 const ping = { 72 encode: function (obj) { 73 return [ 74 int2buffer(obj.version), 75 endpoint.encode(obj.from), 76 endpoint.encode(obj.to), 77 timestamp.encode(obj.timestamp) 78 ] 79 }, 80 decode: function (payload) { 81 return { 82 version: buffer2int(payload[0]), 83 from: endpoint.decode(payload[1]), 84 to: endpoint.decode(payload[2]), 85 timestamp: timestamp.decode(payload[3]) 86 } 87 } 88 } 89 90 const pong = { 91 encode: function (obj) { 92 return [ 93 endpoint.encode(obj.to), 94 obj.hash, 95 timestamp.encode(obj.timestamp) 96 ] 97 }, 98 decode: function (payload) { 99 return { 100 to: endpoint.decode(payload[0]), 101 hash: payload[1], 102 timestamp: timestamp.decode(payload[2]) 103 } 104 } 105 } 106 107 const findneighbours = { 108 encode: function (obj) { 109 return [ 110 obj.id, 111 timestamp.encode(obj.timestamp) 112 ] 113 }, 114 decode: function (payload) { 115 return { 116 id: payload[0], 117 timestamp: timestamp.decode(payload[1]) 118 } 119 } 120 } 121 122 const neighbours = { 123 encode: function (obj) { 124 return [ 125 obj.peers.map((peer) => endpoint.encode(peer).concat(peer.id)), 126 timestamp.encode(obj.timestamp) 127 ] 128 }, 129 decode: function (payload) { 130 return { 131 peers: payload[0].map((data) => { 132 return { endpoint: endpoint.decode(data), id: data[3] } // hack for id 133 }), 134 timestamp: timestamp.decode(payload[1]) 135 } 136 } 137 } 138 139 const messages = { ping, pong, findneighbours, neighbours } 140 141 const types = { 142 byName: { 143 ping: 0x01, 144 pong: 0x02, 145 findneighbours: 0x03, 146 neighbours: 0x04 147 }, 148 byType: { 149 0x01: 'ping', 150 0x02: 'pong', 151 0x03: 'findneighbours', 152 0x04: 'neighbours' 153 } 154 } 155 156 // [0, 32) data hash 157 // [32, 96) signature 158 // 96 recoveryId 159 // 97 type 160 // [98, length) data 161 162 function encode (typename, data, privateKey) { 163 const type = types.byName[typename] 164 if (type === undefined) throw new Error(`Invalid typename: ${typename}`) 165 const encodedMsg = messages[typename].encode(data) 166 const typedata = Buffer.concat([ Buffer.from([ type ]), rlp.encode(encodedMsg) ]) 167 168 const sighash = keccak256(typedata) 169 const sig = secp256k1.sign(sighash, privateKey) 170 const hashdata = Buffer.concat([ sig.signature, Buffer.from([ sig.recovery ]), typedata ]) 171 const hash = keccak256(hashdata) 172 return Buffer.concat([ hash, hashdata ]) 173 } 174 175 function decode (buffer) { 176 const hash = keccak256(buffer.slice(32)) 177 assertEq(buffer.slice(0, 32), hash, 'Hash verification failed') 178 179 const typedata = buffer.slice(97) 180 const type = typedata[0] 181 const typename = types.byType[type] 182 if (typename === undefined) throw new Error(`Invalid type: ${type}`) 183 const data = messages[typename].decode(rlp.decode(typedata.slice(1))) 184 185 const sighash = keccak256(typedata) 186 const signature = buffer.slice(32, 96) 187 const recoverId = buffer[96] 188 const publicKey = secp256k1.recover(sighash, signature, recoverId, false) 189 190 return { typename, data, publicKey } 191 } 192 193 module.exports = { encode, decode }