import { assert, expect } from 'chai'; import { Multiaddr } from 'multiaddr'; import PeerId from 'peer-id'; import { bufToHex } from '../utils'; import { ERR_INVALID_ID } from './constants'; import { ENR } from './enr'; import { createKeypairFromPeerId } from './keypair'; import { v4 } from './index'; describe('ENR', function () { describe('decodeTxt', () => { it('should encodeTxt and decodeTxt', async () => { const peerId = await PeerId.create({ keyType: 'secp256k1' }); const enr = ENR.createFromPeerId(peerId); const keypair = createKeypairFromPeerId(peerId); enr.setLocationMultiaddr(new Multiaddr('/ip4/18.223.219.100/udp/9000')); enr.multiaddrs = [ new Multiaddr( '/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss' ), new Multiaddr( '/dns6/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/443/wss' ), new Multiaddr( '/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234/wss' ), ]; const txt = enr.encodeTxt(keypair.privateKey); expect(txt.slice(0, 4)).to.be.equal('enr:'); const enr2 = ENR.decodeTxt(txt); expect(bufToHex(enr2.signature as Buffer)).to.be.equal( bufToHex(enr.signature as Buffer) ); const multiaddr = enr2.getLocationMultiaddr('udp')!; expect(multiaddr.toString()).to.be.equal('/ip4/18.223.219.100/udp/9000'); expect(enr2.multiaddrs).to.not.be.undefined; expect(enr2.multiaddrs!.length).to.be.equal(3); const multiaddrsAsStr = enr2.multiaddrs!.map((ma) => ma.toString()); expect(multiaddrsAsStr).to.include( '/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss' ); expect(multiaddrsAsStr).to.include( '/dns6/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/443/wss' ); expect(multiaddrsAsStr).to.include( '/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234/wss' ); }); it('should decode valid enr successfully', () => { const txt = 'enr:-Ku4QMh15cIjmnq-co5S3tYaNXxDzKTgj0ufusA-QfZ66EWHNsULt2kb0eTHoo1Dkjvvf6CAHDS1Di-htjiPFZzaIPcLh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD2d10HAAABE________x8AgmlkgnY0gmlwhHZFkMSJc2VjcDI1NmsxoQIWSDEWdHwdEA3Lw2B_byeFQOINTZ0GdtF9DBjes6JqtIN1ZHCCIyg'; const enr = ENR.decodeTxt(txt); const eth2 = enr.get('eth2') as Buffer; expect(eth2).to.not.be.undefined; expect(bufToHex(eth2)).to.be.equal('f6775d0700000113ffffffffffff1f00'); }); it('should decode valid ENR with multiaddrs successfully [shared test vector]', () => { const txt = 'enr:-QEnuEBEAyErHEfhiQxAVQoWowGTCuEF9fKZtXSd7H_PymHFhGJA3rGAYDVSHKCyJDGRLBGsloNbS8AZF33IVuefjOO6BIJpZIJ2NIJpcIQS39tkim11bHRpYWRkcnO4lgAvNihub2RlLTAxLmRvLWFtczMud2FrdXYyLnRlc3Quc3RhdHVzaW0ubmV0BgG73gMAODcxbm9kZS0wMS5hYy1jbi1ob25na29uZy1jLndha3V2Mi50ZXN0LnN0YXR1c2ltLm5ldAYBu94DACm9A62t7AQL4Ef5ZYZosRpQTzFVAB8jGjf1TER2wH-0zBOe1-MDBNLeA4lzZWNwMjU2azGhAzfsxbxyCkgCqq8WwYsVWH7YkpMLnU2Bw5xJSimxKav-g3VkcIIjKA'; const enr = ENR.decodeTxt(txt); expect(enr.multiaddrs).to.not.be.undefined; expect(enr.multiaddrs!.length).to.be.equal(3); const multiaddrsAsStr = enr.multiaddrs!.map((ma) => ma.toString()); expect(multiaddrsAsStr).to.include( '/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss' ); expect(multiaddrsAsStr).to.include( '/dns6/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/443/wss' ); expect(multiaddrsAsStr).to.include( '/onion3/vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd:1234/wss' ); }); it('should decode valid enr with tcp successfully', async () => { const txt = 'enr:-IS4QAmC_o1PMi5DbR4Bh4oHVyQunZblg4bTaottPtBodAhJZvxVlWW-4rXITPNg4mwJ8cW__D9FBDc9N4mdhyMqB-EBgmlkgnY0gmlwhIbRi9KJc2VjcDI1NmsxoQOevTdO6jvv3fRruxguKR-3Ge4bcFsLeAIWEDjrfaigNoN0Y3CCdl8'; const enr = ENR.decodeTxt(txt); expect(enr.tcp).to.not.be.undefined; expect(enr.tcp).to.be.equal(30303); expect(enr.ip).to.not.be.undefined; expect(enr.ip).to.be.equal('134.209.139.210'); expect(enr.publicKey).to.not.be.undefined; const peerId = await enr.peerId(); expect(peerId.toB58String()).to.be.equal( '16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ' ); }); it('should throw error - no id', () => { try { const txt = Buffer.from( '656e723a2d435972595a62404b574342526c4179357a7a61445a584a42476b636e68344d486342465a6e75584e467264764a6a5830346a527a6a7a', 'hex' ).toString(); ENR.decodeTxt(txt); assert.fail('Expect error here'); } catch (err) { expect(err.message).to.be.equal(ERR_INVALID_ID); } }); it('should throw error - no public key', () => { try { const txt = 'enr:-IS4QJ2d11eu6dC7E7LoXeLMgMP3kom1u3SE8esFSWvaHoo0dP1jg8O3-nx9ht-EO3CmG7L6OkHcMmoIh00IYWB92QABgmlkgnY0gmlwhH8AAAGJc2d11eu6dCsxoQIB_c-jQMOXsbjWkbN-kj99H57gfId5pfb4wa1qxwV4CIN1ZHCCIyk'; ENR.decodeTxt(txt); assert.fail('Expect error here'); } catch (err) { expect(err.message).to.be.equal('Failed to verify ENR: No public key'); } }); }); describe('verify', () => { it('should throw error - no id', () => { try { const enr = new ENR({}, BigInt(0), Buffer.alloc(0)); enr.verify(Buffer.alloc(0), Buffer.alloc(0)); assert.fail('Expect error here'); } catch (err) { expect(err.message).to.be.equal(ERR_INVALID_ID); } }); it('should throw error - invalid id', () => { try { const enr = new ENR( { id: Buffer.from('v3') }, BigInt(0), Buffer.alloc(0) ); enr.verify(Buffer.alloc(0), Buffer.alloc(0)); assert.fail('Expect error here'); } catch (err) { expect(err.message).to.be.equal(ERR_INVALID_ID); } }); it('should throw error - no public key', () => { try { const enr = new ENR( { id: Buffer.from('v4') }, BigInt(0), Buffer.alloc(0) ); enr.verify(Buffer.alloc(0), Buffer.alloc(0)); assert.fail('Expect error here'); } catch (err) { expect(err.message).to.be.equal('Failed to verify ENR: No public key'); } }); it('should return false', () => { const txt = 'enr:-Ku4QMh15cIjmnq-co5S3tYaNXxDzKTgj0ufusA-QfZ66EWHNsULt2kb0eTHoo1Dkjvvf6CAHDS1Di-htjiPFZzaIPcLh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD2d10HAAABE________x8AgmlkgnY0gmlwhHZFkMSJc2VjcDI1NmsxoQIWSDEWdHwdEA3Lw2B_byeFQOINTZ0GdtF9DBjes6JqtIN1ZHCCIyg'; const enr = ENR.decodeTxt(txt); // should have id and public key inside ENR expect(enr.verify(Buffer.alloc(32), Buffer.alloc(64))).to.be.false; }); }); describe('Fuzzing testcases', () => { it('should throw error in invalid signature', () => { const buf = Buffer.from( '656e723a2d4b7634514147774f54385374716d7749354c486149796d494f346f6f464b664e6b456a576130663150384f73456c67426832496a622d4772445f2d623957346b6350466377796e354845516d526371584e716470566f3168656f42683246306447356c64484f494141414141414141414143455a58526f4d704141414141414141414141505f5f5f5f5f5f5f5f5f5f676d6c6b676e5930676d6c7768424c663232534a6332566a634449314e6d73786f514a78436e4536765f7832656b67595f756f45317274777a76477934306d7139654436365866485042576749494e315a48437f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f434436410d0a', 'hex' ).toString(); try { ENR.decodeTxt(buf); } catch (e) { expect(e.message).to.equal( 'Decoded ENR invalid signature: must be a byte array' ); } }); it('should throw error in invalid sequence number', () => { const buf = Buffer.from( '656e723a2d495334514b6b33ff583945717841337838334162436979416e537550444d764b353264433530486d31584744643574457951684d3356634a4c2d5062446b44673541507a5f706f76763022d48dcf992d5379716b306e616e636f4e572d656e7263713042676d6c6b676e5930676d6c77684838414141474a6332566a634449314e6d73786f514d31453579557370397638516a397476335a575843766146427672504e647a384b5049314e68576651577a494e315a4843434239410a', 'hex' ).toString(); try { ENR.decodeTxt(buf); } catch (e) { expect(e.message).to.equal( 'Decoded ENR invalid sequence number: must be a byte array' ); } }); }); describe('Static tests', () => { let privateKey: Buffer; let record: ENR; beforeEach(() => { const seq = 1n; privateKey = Buffer.from( 'b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291', 'hex' ); record = ENR.createV4(v4.publicKey(privateKey)); record.set('ip', Buffer.from('7f000001', 'hex')); record.set('udp', Buffer.from((30303).toString(16), 'hex')); record.seq = seq; }); it('should properly compute the node id', () => { expect(record.nodeId).to.equal( 'a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7' ); }); it('should encode/decode to RLP encoding', () => { const decoded = ENR.decode(record.encode(privateKey)); expect(decoded).to.deep.equal(record); }); it('should encode/decode to text encoding', () => { // spec enr https://eips.ethereum.org/EIPS/eip-778 const testTxt = 'enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8'; const decoded = ENR.decodeTxt(testTxt); expect(decoded.udp).to.be.equal(30303); expect(decoded.ip).to.be.equal('127.0.0.1'); expect(decoded).to.deep.equal(record); expect(record.encodeTxt(privateKey)).to.equal(testTxt); }); }); describe('Multiformats support', () => { let privateKey: Buffer; let record: ENR; beforeEach(() => { privateKey = Buffer.from( 'b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291', 'hex' ); record = ENR.createV4(v4.publicKey(privateKey)); }); it('should get / set UDP multiaddr', () => { const multi0 = new Multiaddr('/ip4/127.0.0.1/udp/30303'); const tuples0 = multi0.tuples(); if (!tuples0[0][1] || !tuples0[1][1]) { throw new Error('invalid multiaddr'); } // set underlying records record.set('ip', tuples0[0][1]); record.set('udp', tuples0[1][1]); // and get the multiaddr expect(record.getLocationMultiaddr('udp')!.toString()).to.equal( multi0.toString() ); // set the multiaddr const multi1 = new Multiaddr('/ip4/0.0.0.0/udp/30300'); record.setLocationMultiaddr(multi1); // and get the multiaddr expect(record.getLocationMultiaddr('udp')!.toString()).to.equal( multi1.toString() ); // and get the underlying records const tuples1 = multi1.tuples(); expect(record.get('ip')).to.deep.equal(tuples1[0][1]); expect(record.get('udp')).to.deep.equal(tuples1[1][1]); }); it('should get / set TCP multiaddr', () => { const multi0 = new Multiaddr('/ip4/127.0.0.1/tcp/30303'); const tuples0 = multi0.tuples(); if (!tuples0[0][1] || !tuples0[1][1]) { throw new Error('invalid multiaddr'); } // set underlying records record.set('ip', tuples0[0][1]); record.set('tcp', tuples0[1][1]); // and get the multiaddr expect(record.getLocationMultiaddr('tcp')!.toString()).to.equal( multi0.toString() ); // set the multiaddr const multi1 = new Multiaddr('/ip4/0.0.0.0/tcp/30300'); record.setLocationMultiaddr(multi1); // and get the multiaddr expect(record.getLocationMultiaddr('tcp')!.toString()).to.equal( multi1.toString() ); // and get the underlying records const tuples1 = multi1.tuples(); expect(record.get('ip')).to.deep.equal(tuples1[0][1]); expect(record.get('tcp')).to.deep.equal(tuples1[1][1]); }); describe('location multiaddr', async () => { const ip4 = '127.0.0.1'; const ip6 = '::1'; const tcp = 8080; const udp = 8080; const peerId = await PeerId.create({ keyType: 'secp256k1' }); const enr = ENR.createFromPeerId(peerId); enr.ip = ip4; enr.ip6 = ip6; enr.tcp = tcp; enr.udp = udp; enr.tcp6 = tcp; enr.udp6 = udp; it('should properly create location multiaddrs - udp4', () => { expect(enr.getLocationMultiaddr('udp4')).to.deep.equal( new Multiaddr(`/ip4/${ip4}/udp/${udp}`) ); }); it('should properly create location multiaddrs - tcp4', () => { expect(enr.getLocationMultiaddr('tcp4')).to.deep.equal( new Multiaddr(`/ip4/${ip4}/tcp/${tcp}`) ); }); it('should properly create location multiaddrs - udp6', () => { expect(enr.getLocationMultiaddr('udp6')).to.deep.equal( new Multiaddr(`/ip6/${ip6}/udp/${udp}`) ); }); it('should properly create location multiaddrs - tcp6', () => { expect(enr.getLocationMultiaddr('tcp6')).to.deep.equal( new Multiaddr(`/ip6/${ip6}/tcp/${tcp}`) ); }); it('should properly create location multiaddrs - udp', () => { // default to ip4 expect(enr.getLocationMultiaddr('udp')).to.deep.equal( new Multiaddr(`/ip4/${ip4}/udp/${udp}`) ); // if ip6 is set, use it enr.ip = undefined; expect(enr.getLocationMultiaddr('udp')).to.deep.equal( new Multiaddr(`/ip6/${ip6}/udp/${udp}`) ); // if ip6 does not exist, use ip4 enr.ip6 = undefined; enr.ip = ip4; expect(enr.getLocationMultiaddr('udp')).to.deep.equal( new Multiaddr(`/ip4/${ip4}/udp/${udp}`) ); enr.ip6 = ip6; }); it('should properly create location multiaddrs - tcp', () => { // default to ip4 expect(enr.getLocationMultiaddr('tcp')).to.deep.equal( new Multiaddr(`/ip4/${ip4}/tcp/${tcp}`) ); // if ip6 is set, use it enr.ip = undefined; expect(enr.getLocationMultiaddr('tcp')).to.deep.equal( new Multiaddr(`/ip6/${ip6}/tcp/${tcp}`) ); // if ip6 does not exist, use ip4 enr.ip6 = undefined; enr.ip = ip4; expect(enr.getLocationMultiaddr('tcp')).to.deep.equal( new Multiaddr(`/ip4/${ip4}/tcp/${tcp}`) ); enr.ip6 = ip6; }); }); }); });