UNPKG

@ethereumjs/devp2p

Version:
187 lines 6.51 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.encode = encode; exports.decode = decode; const rlp_1 = require("@ethereumjs/rlp"); const util_1 = require("@ethereumjs/util"); const debug_1 = require("debug"); const keccak_js_1 = require("ethereum-cryptography/keccak.js"); const secp256k1_compat_js_1 = require("ethereum-cryptography/secp256k1-compat.js"); const util_ts_1 = require("../util.js"); const secp256k1_1 = require("ethereum-cryptography/secp256k1"); const debug = (0, debug_1.default)('devp2p:dpt:server'); function getTimestamp() { return (Date.now() / 1000) | 0; } const timestamp = { encode(value = getTimestamp() + 60) { const bytes = new Uint8Array(4); new DataView(bytes.buffer).setUint32(0, value); return bytes; }, decode(bytes) { if (bytes.length !== 4) throw new RangeError(`Invalid timestamp bytes :${(0, util_1.bytesToHex)(bytes)}`); return new DataView(bytes.buffer).getUint32(0); }, }; const address = { encode(value) { if ((0, util_ts_1.isV4Format)(value)) return (0, util_ts_1.ipToBytes)(value); if ((0, util_ts_1.isV6Format)(value)) return (0, util_ts_1.ipToBytes)(value); throw (0, util_1.EthereumJSErrorWithoutCode)(`Invalid address: ${value}`); }, decode(bytes) { if (bytes.length === 4) return (0, util_ts_1.ipToString)(bytes); if (bytes.length === 16) return (0, util_ts_1.ipToString)(bytes); const str = (0, util_1.bytesToUtf8)(bytes); if ((0, util_ts_1.isV4Format)(str) || (0, util_ts_1.isV6Format)(str)) return str; // also can be host, but skip it right now (because need async function for resolve) throw (0, util_1.EthereumJSErrorWithoutCode)(`Invalid address bytes: ${(0, util_1.bytesToHex)(bytes)}`); }, }; const port = { encode(value) { if (value === null) return new Uint8Array(); if (value >>> 16 > 0) throw new RangeError(`Invalid port: ${value}`); return Uint8Array.from([(value >>> 8) & 0xff, (value >>> 0) & 0xff]); }, decode(bytes) { if (bytes.length === 0) return null; return (0, util_1.bytesToInt)(bytes); }, }; const endpoint = { encode(obj) { return [ address.encode(obj.address), port.encode(obj.udpPort ?? null), port.encode(obj.tcpPort ?? null), ]; }, decode(payload) { return { address: address.decode(payload[0]), udpPort: port.decode(payload[1]), tcpPort: port.decode(payload[2]), }; }, }; const ping = { encode(obj) { return [ (0, util_1.intToBytes)(obj.version), endpoint.encode(obj.from), endpoint.encode(obj.to), timestamp.encode(obj.timestamp), ]; }, decode(payload) { return { version: (0, util_1.bytesToInt)(payload[0]), from: endpoint.decode(payload[1]), to: endpoint.decode(payload[2]), timestamp: timestamp.decode(payload[3]), }; }, }; const pong = { encode(obj) { return [endpoint.encode(obj.to), obj.hash, timestamp.encode(obj.timestamp)]; }, decode(payload) { return { to: endpoint.decode(payload[0]), hash: payload[1], timestamp: timestamp.decode(payload[2]), }; }, }; const findneighbours = { encode(obj) { return [obj.id, timestamp.encode(obj.timestamp)]; }, decode(payload) { return { id: payload[0], timestamp: timestamp.decode(payload[1]), }; }, }; const neighbours = { encode(obj) { return [ obj.peers.map((peer) => endpoint.encode(peer).concat(peer.id)), timestamp.encode(obj.timestamp), ]; }, decode(payload) { return { peers: payload[0].map((data) => { return { endpoint: endpoint.decode(data), id: data[3] }; // hack for id }), timestamp: timestamp.decode(payload[1]), }; }, }; const messages = { ping, pong, findneighbours, neighbours, }; const types = { byName: { ping: 0x01, pong: 0x02, findneighbours: 0x03, neighbours: 0x04, }, byType: { 0x01: 'ping', 0x02: 'pong', 0x03: 'findneighbours', 0x04: 'neighbours', }, }; // [0, 32) data hash // [32, 96) signature // 96 recoveryId // 97 type // [98, length) data function encode(typename, data, privateKey, common) { const type = types.byName[typename]; if (type === undefined) throw (0, util_1.EthereumJSErrorWithoutCode)(`Invalid typename: ${typename}`); const encodedMsg = messages[typename].encode(data); const typedata = (0, util_1.concatBytes)(Uint8Array.from([type]), rlp_1.RLP.encode(encodedMsg)); const sighash = (common?.customCrypto.keccak256 ?? keccak_js_1.keccak256)(typedata); const sig = (common?.customCrypto.ecsign ?? secp256k1_1.secp256k1.sign)(sighash, privateKey); const hashdata = (0, util_1.concatBytes)((0, util_1.setLengthLeft)((0, util_1.bigIntToBytes)(sig.r), 32), (0, util_1.setLengthLeft)((0, util_1.bigIntToBytes)(sig.s), 32), Uint8Array.from([sig.recovery]), typedata); const hash = (common?.customCrypto.keccak256 ?? keccak_js_1.keccak256)(hashdata); return (0, util_1.concatBytes)(hash, hashdata); } function decode(bytes, common) { const hash = (common?.customCrypto.keccak256 ?? keccak_js_1.keccak256)(bytes.subarray(32)); (0, util_ts_1.assertEq)(bytes.subarray(0, 32), hash, 'Hash verification failed', debug); const typedata = bytes.subarray(97); const type = typedata[0]; const typename = types.byType[type]; if (typename === undefined) throw (0, util_1.EthereumJSErrorWithoutCode)(`Invalid type: ${type}`); const data = messages[typename].decode((0, util_ts_1.unstrictDecode)(typedata.subarray(1))); const sighash = (common?.customCrypto.keccak256 ?? keccak_js_1.keccak256)(typedata); const signature = bytes.subarray(32, 96); const recoverId = bytes[96]; const publicKey = (common?.customCrypto.ecdsaRecover ?? secp256k1_compat_js_1.ecdsaRecover)(signature, recoverId, sighash, false); return { typename, data, publicKey }; } //# sourceMappingURL=message.js.map