#!/usr/bin/env node
// Generate Phase 3 test vectors: HyperDHT messages, namespaces, Noise IK.
// Usage: node test/test_phase3_vectors.js > test/phase3_vectors.json
const c = require('compact-encoding')
const net = require('compact-encoding-net')
const sodium = require('sodium-universal')
const b4a = require('b4a')
const crypto = require('hypercore-crypto')
const m = require('hyperdht/lib/messages')
const constants = require('hyperdht/lib/constants')
function encode (enc, value) {
const state = { start: 0, end: 0, buffer: null }
enc.preencode(state, value)
state.buffer = b4a.alloc(state.end)
enc.encode(state, value)
return state.buffer
}
function toHex (buf) {
return b4a.toString(buf, 'hex')
}
const vectors = {
namespaces: {},
noisePayload: [],
handshakeMessage: [],
holepunchPayload: [],
peerRecord: [],
announceMessage: [],
securePayload: [],
noiseIK: {}
}
// ---- Namespace vectors ----
// Compute namespaces exactly as constants.js does.
const [NS_ANNOUNCE, NS_UNANNOUNCE, NS_MUTABLE_PUT, NS_PEER_HANDSHAKE, NS_PEER_HOLEPUNCH] =
crypto.namespace('hyperswarm/dht', [
constants.COMMANDS.ANNOUNCE, // 4
constants.COMMANDS.UNANNOUNCE, // 5
constants.COMMANDS.MUTABLE_PUT, // 6
constants.COMMANDS.PEER_HANDSHAKE, // 0
constants.COMMANDS.PEER_HOLEPUNCH // 1
])
vectors.namespaces = {
NS_ANNOUNCE: toHex(NS_ANNOUNCE),
NS_UNANNOUNCE: toHex(NS_UNANNOUNCE),
NS_MUTABLE_PUT: toHex(NS_MUTABLE_PUT),
NS_PEER_HANDSHAKE: toHex(NS_PEER_HANDSHAKE),
NS_PEER_HOLEPUNCH: toHex(NS_PEER_HOLEPUNCH)
}
// ---- NoisePayload: basic (4 bytes) ----
{
const payload = {
version: 1,
error: 0,
firewall: 0,
holepunch: null,
addresses4: [],
addresses6: [],
udx: null,
secretStream: null,
relayThrough: null,
relayAddresses: null
}
const buf = encode(m.noisePayload, payload)
vectors.noisePayload.push({
description: 'basic (no optional fields)',
bytes: buf.length,
hex: toHex(buf),
error: 0,
firewall: 0
})
}
// ---- NoisePayload: with addresses and firewall ----
{
const payload = {
version: 1,
error: 0,
firewall: 2,
holepunch: null,
addresses4: [
{ host: '127.0.0.1', port: 10240 },
{ host: '192.168.1.1', port: 4242 }
],
addresses6: [],
udx: null,
secretStream: null,
relayThrough: null,
relayAddresses: null
}
const buf = encode(m.noisePayload, payload)
vectors.noisePayload.push({
description: 'with addresses4 and firewall=CONSISTENT',
bytes: buf.length,
hex: toHex(buf),
firewall: 2,
addresses4: payload.addresses4
})
}
// ---- NoisePayload: with UDX info ----
{
const payload = {
version: 1,
error: 0,
firewall: 1,
holepunch: null,
addresses4: [{ host: '10.0.0.1', port: 12345 }],
addresses6: [],
udx: { reusableSocket: false, id: 7, seq: 100 },
secretStream: null,
relayThrough: null,
relayAddresses: null
}
const buf = encode(m.noisePayload, payload)
vectors.noisePayload.push({
description: 'with addresses4 + UDX info',
bytes: buf.length,
hex: toHex(buf),
firewall: 1,
udx_id: 7,
udx_seq: 100
})
}
// ---- NoisePayload: with secretStream ----
{
const payload = {
version: 1,
error: 0,
firewall: 1,
holepunch: null,
addresses4: [],
addresses6: [],
udx: { reusableSocket: true, id: 1, seq: 0 },
secretStream: {},
relayThrough: null,
relayAddresses: null
}
const buf = encode(m.noisePayload, payload)
vectors.noisePayload.push({
description: 'with UDX + secretStream',
bytes: buf.length,
hex: toHex(buf)
})
}
// ---- Handshake message ----
{
const noise = b4a.alloc(32, 0xAA)
const msg = {
mode: 1,
noise,
peerAddress: null,
relayAddress: { host: '88.99.3.86', port: 49737 }
}
const buf = encode(m.handshake, msg)
vectors.handshakeMessage.push({
description: 'mode=1 with relay address',
bytes: buf.length,
hex: toHex(buf),
mode: 1,
noise_hex: toHex(noise),
relay: msg.relayAddress
})
}
// ---- Handshake message: with both addresses ----
{
const noise = b4a.alloc(81, 0xBB) // Typical Noise IK msg1 size
const msg = {
mode: 0,
noise,
peerAddress: { host: '10.0.0.1', port: 12345 },
relayAddress: { host: '88.99.3.86', port: 49737 }
}
const buf = encode(m.handshake, msg)
vectors.handshakeMessage.push({
description: 'mode=0 with peer + relay addresses',
bytes: buf.length,
hex: toHex(buf),
mode: 0
})
}
// ---- Holepunch payload: basic ----
{
const payload = {
error: 0,
firewall: 2,
round: 1,
connected: false,
punching: true,
addresses: null,
remoteAddress: null,
token: null,
remoteToken: null
}
const buf = encode(m.holepunchPayload, payload)
vectors.holepunchPayload.push({
description: 'punching, round=1, firewall=CONSISTENT',
bytes: buf.length,
hex: toHex(buf),
firewall: 2,
round: 1,
punching: true
})
}
// ---- Holepunch payload: with addresses and tokens ----
{
const token = b4a.alloc(32, 0xCC)
const remoteToken = b4a.alloc(32, 0xDD)
const payload = {
error: 0,
firewall: 1,
round: 3,
connected: false,
punching: true,
addresses: [{ host: '10.0.0.1', port: 12345 }, { host: '192.168.1.1', port: 4242 }],
remoteAddress: { host: '88.99.3.86', port: 49737 },
token,
remoteToken
}
const buf = encode(m.holepunchPayload, payload)
vectors.holepunchPayload.push({
description: 'with addresses, remoteAddress, tokens',
bytes: buf.length,
hex: toHex(buf)
})
}
// ---- Peer record: no relays ----
{
const pk = b4a.alloc(32, 0)
for (let i = 0; i < 32; i++) pk[i] = i
const record = {
publicKey: pk,
relayAddresses: []
}
const buf = encode(m.peer, record)
vectors.peerRecord.push({
description: 'no relay addresses',
bytes: buf.length,
hex: toHex(buf),
pk_hex: toHex(pk)
})
}
// ---- Peer record: with relays ----
{
const pk = b4a.alloc(32, 0xFF)
const record = {
publicKey: pk,
relayAddresses: [
{ host: '88.99.3.86', port: 49737 },
{ host: '142.93.90.113', port: 49737 }
]
}
const buf = encode(m.peer, record)
vectors.peerRecord.push({
description: 'with 2 relay addresses',
bytes: buf.length,
hex: toHex(buf)
})
}
// ---- Announce message: with peer + signature ----
{
const pk = b4a.alloc(32, 0)
for (let i = 0; i < 32; i++) pk[i] = i
const sig = b4a.alloc(64, 0)
for (let i = 0; i < 64; i++) sig[i] = i
const msg = {
peer: { publicKey: pk, relayAddresses: [] },
refresh: null,
signature: sig
}
const buf = encode(m.announce, msg)
vectors.announceMessage.push({
description: 'peer + signature, no refresh',
bytes: buf.length,
hex: toHex(buf)
})
}
// ---- Announce message: peer + refresh + signature ----
{
const pk = b4a.alloc(32, 0xAA)
const refresh = b4a.alloc(32, 0xBB)
const sig = b4a.alloc(64, 0xCC)
const msg = {
peer: { publicKey: pk, relayAddresses: [{ host: '88.99.3.86', port: 49737 }] },
refresh,
signature: sig
}
const buf = encode(m.announce, msg)
vectors.announceMessage.push({
description: 'peer + refresh + signature with relay',
bytes: buf.length,
hex: toHex(buf)
})
}
// ---- SecurePayload: encrypt/decrypt round-trip with known key ----
// We can't test exact bytes (random nonce), but we test the format: nonce(24) || ciphertext+mac
{
const key = b4a.alloc(32, 0x42)
const plaintext = b4a.from('holepunch-test-payload')
// Manually create a deterministic nonce for testing
const nonce = b4a.alloc(24, 0)
for (let i = 0; i < 24; i++) nonce[i] = i
const ct = b4a.alloc(plaintext.length + sodium.crypto_secretbox_MACBYTES)
sodium.crypto_secretbox_easy(ct, plaintext, nonce, key)
const encrypted = b4a.concat([nonce, ct])
vectors.securePayload.push({
description: 'deterministic nonce encrypt',
key_hex: toHex(key),
plaintext_hex: toHex(plaintext),
nonce_hex: toHex(nonce),
encrypted_hex: toHex(encrypted),
encrypted_bytes: encrypted.length
})
}
// ---- Noise IK handshake cross-validation ----
// Use deterministic keypairs so C++ can reproduce the same handshake.
{
// Generate deterministic keypairs from seeds.
const initiator_seed = b4a.alloc(32, 0)
for (let i = 0; i < 32; i++) initiator_seed[i] = i
const responder_seed = b4a.alloc(32, 0)
for (let i = 0; i < 32; i++) responder_seed[i] = 32 + i
const initiator_kp = { publicKey: b4a.alloc(32), secretKey: b4a.alloc(64) }
sodium.crypto_sign_seed_keypair(initiator_kp.publicKey, initiator_kp.secretKey, initiator_seed)
const responder_kp = { publicKey: b4a.alloc(32), secretKey: b4a.alloc(64) }
sodium.crypto_sign_seed_keypair(responder_kp.publicKey, responder_kp.secretKey, responder_seed)
vectors.noiseIK = {
initiator_pk: toHex(initiator_kp.publicKey),
initiator_sk: toHex(initiator_kp.secretKey),
responder_pk: toHex(responder_kp.publicKey),
responder_sk: toHex(responder_kp.secretKey),
ns_peer_handshake: toHex(NS_PEER_HANDSHAKE),
ns_peer_holepunch: toHex(NS_PEER_HOLEPUNCH)
}
// We can't do a full Noise IK handshake cross-validation here because
// the JS uses ephemeral keys, making each run non-deterministic.
// Instead, we validate:
// 1. Namespace computation (above)
// 2. Keypair generation from seed
// 3. Message codec round-trips (above)
// The C++ test will validate its own internal handshake consistency.
}
console.log(JSON.stringify(vectors, null, 2))