diff --git a/example/index.html b/example/index.html index d8d2fa4..2112b99 100644 --- a/example/index.html +++ b/example/index.html @@ -7,7 +7,23 @@

Press F12 to open the console

- +

Waku Status: connecting...

+

Handshake Status: -

+ + + + + diff --git a/example/index.js b/example/index.js index 3786542..8a64e20 100644 --- a/example/index.js +++ b/example/index.js @@ -1,11 +1,26 @@ import { createLightNode } from "js-waku/lib/create_waku"; +import { utils } from "js-waku"; import { waitForRemotePeer } from "js-waku/lib/wait_for_remote_peer"; +import { Fleet, getPredefinedBootstrapNodes } from "js-waku/lib/predefined_bootstrap_nodes"; +import { PeerDiscoveryStaticPeers } from "js-waku/lib/peer_discovery_static_list"; +import { Protocols } from "js-waku"; import * as noise from "@waku/noise"; +import protobuf from "protobufjs"; import QRCode from "qrcode"; // TODO: Get rid of these import hexToArrayBuffer from "hex-to-array-buffer"; import arrayBufferToHex from "array-buffer-to-hex"; -import { WakuPairing } from "@waku/noise"; + +const messagesDiv = document.getElementById("messages"); +const nicknameInput = document.getElementById("nick-input"); +const textInput = document.getElementById("text-input"); +const sendButton = document.getElementById("send-btn"); +const sendingStatusSpan = document.getElementById("sending-status"); +const chatArea = document.getElementById("chat-area"); +const qrCanvas = document.getElementById("qr-canvas"); +const qrUrl = document.getElementById("qr-url"); +const wakuStatusSpan = document.getElementById("waku-status"); +const handshakeStatusSpan = document.getElementById("handshake-status"); function getPairingInfofromUrl() { const urlParts = window.location.href.split("?"); @@ -19,22 +34,7 @@ function getPairingInfofromUrl() { return new noise.InitiatorParameters(pairingParts.join(":"), qrMessageNameTag); } -async function confirmAuthCodeFlow(pairingObj) { - const authCode = await pairingObj.getAuthCode(); - pairingObj.validateAuthCode(confirm("Confirm that authcode is: " + authCode)); -} - -async function main() { - // Starting the node - const node = await createLightNode(); - await node.start(); - - // Dialing a node and wait until it's available - const ma = - "/dns4/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm"; - await node.dial(ma, ["filter", "lightpush"]); - await waitForRemotePeer(node, ["filter", "lightpush"]); - +function getSenderAndResponder(node) { const sender = { async publish(encoder, msg) { await node.lightPush.push(encoder, msg); @@ -42,12 +42,15 @@ async function main() { }; const msgQueue = new Array(); - const receiver = { + const subscriptions = new Map(); + const intervals = new Map(); + + const responder = { async subscribe(decoder) { - await node.filter.subscribe([decoder], (wakuMessage) => { + const subscription = await node.filter.subscribe([decoder], (wakuMessage) => { msgQueue.push(wakuMessage); - // TODO: remove subscription once handshake ends? }); + subscriptions.set(decoder.contentTopic, subscription); }, async nextMessage(contentTopic) { if (msgQueue.length != 0) { @@ -67,89 +70,219 @@ async function main() { } } }, 100); + intervals.set(contentTopic, interval); }); }, + async stop(contentTopic) { + if (intervals.has(contentTopic)) { + clearInterval(intervals.get(contentTopic)); + intervals.delete(contentTopic); + } + if (subscriptions.has(contentTopic)) { + await subscriptions.get(contentTopic)(); + subscriptions.delete(contentTopic); + } else { + console.log("Subscriptipon doesnt exist") + } + }, }; - const myStaticKey = noise.generateX25519KeyPair(); + return [sender, responder]; +} - const pairingParameters = getPairingInfofromUrl(); - if (pairingParameters) { - console.log("Initiator"); +async function confirmAuthCodeFlow(pairingObj) { + const authCode = await pairingObj.getAuthCode(); + pairingObj.validateAuthCode(confirm("Confirm that authcode is: " + authCode)); +} - const pairingObj = new noise.WakuPairing(sender, receiver, myStaticKey, pairingParameters); - const pExecute = pairingObj.execute(120000); // timeout after 2m +async function hideQR() { + qrCanvas.remove(); + qrUrl.remove(); +} - confirmAuthCodeFlow(pairingObj); - - try { - console.log("executing handshake..."); - const codecs = await pExecute; - alert("Handshake completed!"); - // TODO: enable a form so users can send messages - } catch (err) { - alert(err); +async function disableUI() { + hideQR(); + chatArea.remove(); +} + +// Function to update the fields to guide the user by disabling buttons. +const updateFields = () => { + const readyToSend = nicknameInput.value !== ""; + textInput.disabled = !readyToSend; + sendButton.disabled = !readyToSend; +}; + +// Protobuf +const ProtoChatMessage = new protobuf.Type("ChatMessage") + .add(new protobuf.Field("timestamp", 1, "uint64")) + .add(new protobuf.Field("nick", 2, "string")) + .add(new protobuf.Field("text", 3, "bytes")); + +let messages = []; + +const updateMessages = () => { + messagesDiv.innerHTML = ""; +}; + +const onMessage = (wakuMessage) => { + const { timestamp, nick, text } = ProtoChatMessage.decode(wakuMessage.payload); + const time = new Date(); + time.setTime(Number(timestamp) * 1000); + + messages.push(`(${nick}) ${utils.bytesToUtf8(text)} [${time.toISOString()}]`); + updateMessages(); +}; + +async function main() { + // Starting the node + const node = await createLightNode({ + libp2p: { + peerDiscovery: [new PeerDiscoveryStaticPeers(getPredefinedBootstrapNodes(Fleet.Test))], + }, + }); + + try { + await node.start(); + + await waitForRemotePeer(node, [Protocols.Filter, Protocols.LightPush]); + + wakuStatusSpan.innerHTML = "connected"; + + const [sender, responder] = getSenderAndResponder(node); + + const myStaticKey = noise.generateX25519KeyPair(); + + const pairingParameters = getPairingInfofromUrl(); + + const initiator = pairingParameters ? true : false; + + let encoder; + let decoder; + + if (initiator) { + console.log("Initiator"); + qrCanvas.remove(); // Initiator does not require a QR code + + const pairingObj = new noise.WakuPairing(sender, responder, myStaticKey, pairingParameters); + const pExecute = pairingObj.execute(120000); // timeout after 2m + + confirmAuthCodeFlow(pairingObj); + + try { + handshakeStatusSpan.innerHTML = "executing handshake..."; + + [encoder, decoder] = await pExecute; + + handshakeStatusSpan.innerHTML = "handshake completed!"; + } catch (err) { + handshakeStatusSpan.innerHTML = err.message; + disableUI(); + console.error(err); + } + + /* + // The information needs to be backed up to decrypt messages sent with + // codecs generated with the handshake. The `handshakeResult` variable + // contains private information that needs to be stored safely + const contentTopic = pairingObj.contentTopic; + const handshakeResult = pairingObj.getHandshakeResult(); + + // To restore the codecs for decrypting older messages, or continuing an existing + // session, use this: + [encoder, decoder] = WakuPairing.getSecureCodec(contentTopic, handshakeResult); + */ + } else { + console.log("Responder"); + + const pairingObj = new noise.WakuPairing(sender, responder, myStaticKey, new noise.ResponderParameters()); + const pExecute = pairingObj.execute(120000); // timeout after 2m + + confirmAuthCodeFlow(pairingObj); + + const pInfo = pairingObj.getPairingInfo(); + + // Data to encode in the QR code. The qrMessageNametag too to the QR string (separated by ) + const qrString = arrayBufferToHex(pInfo.qrMessageNameTag) + ":" + pInfo.qrCode; + const qrURLString = window.location.href + "?" + encodeURIComponent(qrString); + + handshakeStatusSpan.innerHTML = "generating QR code..."; + + console.log("Generating QR..."); + + QRCode.toCanvas(qrCanvas, qrURLString, (err) => { + if (err) { + handshakeStatusSpan.innerHTML = err.message; + disableUI(); + console.error(err); + } else { + handshakeStatusSpan.innerHTML = "waiting for handshake to start"; + qrUrl.href = qrURLString; + qrUrl.style.display = "block"; + } + }); + + try { + handshakeStatusSpan.innerHTML = "executing handshake..."; + + [encoder, decoder] = await pExecute; + + handshakeStatusSpan.innerHTML = "handshake completed!"; + + hideQR(); + } catch (err) { + handshakeStatusSpan.innerHTML = err.message; + disableUI(); + console.error(err); + } + + /* + // The information needs to be backed up to decrypt messages sent with + // codecs generated with the handshake. The `handshakeResult` variable + // contains private information that needs to be stored safely + const contentTopic = pairingObj.contentTopic; + const handshakeResult = pairingObj.getHandshakeResult(); + + // To restore the codecs for decrypting older messages, or continuing an existing + // session, use this: + [encoder, decoder] = WakuPairing.getSecureCodec(contentTopic, handshakeResult); + */ } - // The information needs to be backed up to decrypt messages sent with - // codecs generated with the handshake - const contentTopic = pairingObj.contentTopic; - const handshakeResult = pairingObj.getHandshakeResult(); + nicknameInput.onchange = updateFields; + nicknameInput.onblur = updateFields; - // This information should not be printed, it's done - // to see the information in the dev console - console.log("HandshakeResult", handshakeResult); + sendButton.onclick = async () => { + const text = utils.utf8ToBytes(textInput.value); + const timestamp = new Date(); + const msg = ProtoChatMessage.create({ + text, + nick: nicknameInput.value, + timestamp: Math.floor(timestamp.valueOf() / 1000), + }); + const payload = ProtoChatMessage.encode(msg).finish(); + sendingStatusSpan.innerText = "sending..."; + await node.lightPush.push(encoder, { payload, timestamp }); + sendingStatusSpan.innerText = "sent!"; - // To restore the codecs: - const codecs = WakuPairing.getSecureCodec(contentTopic, handshakeResult); - } else { - console.log("Receiver"); + onMessage({ payload }); - const pairingObj = new noise.WakuPairing(sender, receiver, myStaticKey, new noise.ReceiverParameters()); - const pExecute = pairingObj.execute(120000); // timeout after 2m + textInput.value = null; + setTimeout(() => { + sendingStatusSpan.innerText = ""; + }, 5000); + }; - confirmAuthCodeFlow(pairingObj); + await node.filter.subscribe([decoder], onMessage); - const pInfo = pairingObj.getPairingInfo(); - - // Data to encode in the QR code. The qrMessageNametag too to the QR string (separated by ) - const qrString = arrayBufferToHex(pInfo.qrMessageNameTag) + ":" + pInfo.qrCode; - const qrURL = window.location.href + "?" + encodeURIComponent(qrString); - - console.log("Generating QR..."); - QRCode.toCanvas(document.getElementById("qrCanvas"), qrURL, (error) => { - if (error) console.error(error); - }); - - // Auto open page - TODO: remove this - window.setTimeout(() => { - alert("Automatically opening new page to simulate QR code being scanned"); - window.open(qrURL); - }, 1000); - - try { - console.log("executing handshake..."); - const codecs = await pExecute; - alert("Handshake completed!"); - // TODO: enable a form so users can send messages - } catch (err) { - // TODO: hide QR - // TODO: display message indicating pairing is not valid - // TODO: handle timeout - alert(err); - } - - // The information needs to be backed up to decrypt messages sent with - // codecs generated with the handshake - const contentTopic = pairingObj.contentTopic; - const handshakeResult = pairingObj.getHandshakeResult(); - - // This information should not be printed, it's done - // to see the information in the dev console - console.log("HandshakeResult", handshakeResult); - - // To restore the codecs: - const codecs = WakuPairing.getSecureCodec(contentTopic, handshakeResult); + chatArea.style.display = "block"; + } catch (err) { + wakuStatusSpan.innerHTML = err.message; + disableUI(); + return; } } main(); diff --git a/example/package-lock.json b/example/package-lock.json index 9f99445..8abf4db 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -12,6 +12,7 @@ "array-buffer-to-hex": "^1.0.0", "hex-to-array-buffer": "^2.0.0", "js-waku": "^0.29.0-29436ea", + "protobufjs": "^7.1.2", "qrcode": "^1.5.1" }, "devDependencies": { @@ -191,6 +192,36 @@ "npm": ">=8.7.0" } }, + "node_modules/@chainsafe/libp2p-gossipsub/node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/@chainsafe/libp2p-gossipsub/node_modules/protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, "node_modules/@chainsafe/libp2p-noise": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@chainsafe/libp2p-noise/-/libp2p-noise-8.0.2.tgz", @@ -492,11 +523,6 @@ "npm": ">=7.0.0" } }, - "node_modules/@libp2p/crypto/node_modules/long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" - }, "node_modules/@libp2p/crypto/node_modules/multiformats": { "version": "10.0.2", "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-10.0.2.tgz", @@ -506,29 +532,6 @@ "npm": ">=7.0.0" } }, - "node_modules/@libp2p/crypto/node_modules/protobufjs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", - "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/@libp2p/crypto/node_modules/protons-runtime": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-4.0.1.tgz", @@ -1213,11 +1216,6 @@ "npm": ">=7.0.0" } }, - "node_modules/@libp2p/peer-id-factory/node_modules/long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" - }, "node_modules/@libp2p/peer-id-factory/node_modules/multiformats": { "version": "10.0.2", "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-10.0.2.tgz", @@ -1227,29 +1225,6 @@ "npm": ">=7.0.0" } }, - "node_modules/@libp2p/peer-id-factory/node_modules/protobufjs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", - "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/@libp2p/peer-id-factory/node_modules/protons-runtime": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-4.0.1.tgz", @@ -1347,11 +1322,6 @@ "npm": ">=7.0.0" } }, - "node_modules/@libp2p/peer-record/node_modules/long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" - }, "node_modules/@libp2p/peer-record/node_modules/multiformats": { "version": "10.0.2", "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-10.0.2.tgz", @@ -1361,29 +1331,6 @@ "npm": ">=7.0.0" } }, - "node_modules/@libp2p/peer-record/node_modules/protobufjs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", - "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/@libp2p/peer-record/node_modules/protons-runtime": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-4.0.1.tgz", @@ -4770,9 +4717,9 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" }, "node_modules/longbits": { "version": "1.1.0", @@ -5453,52 +5400,6 @@ "dev": true }, "node_modules/protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, - "node_modules/protons-runtime": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-3.1.0.tgz", - "integrity": "sha512-S1iSPQC0McdHKJRi0XcATBkWgwWPx46UDfrnshYDXBvGHSYqkFtn4MQ8Gatf67w7FzFtHivA+Hb0ZPq56upG8w==", - "dependencies": { - "protobufjs": "^7.0.0", - "uint8arraylist": "^2.3.2" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - }, - "peerDependencies": { - "uint8arraylist": "^2.3.2" - } - }, - "node_modules/protons-runtime/node_modules/long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" - }, - "node_modules/protons-runtime/node_modules/protobufjs": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", @@ -5521,6 +5422,22 @@ "node": ">=12.0.0" } }, + "node_modules/protons-runtime": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-3.1.0.tgz", + "integrity": "sha512-S1iSPQC0McdHKJRi0XcATBkWgwWPx46UDfrnshYDXBvGHSYqkFtn4MQ8Gatf67w7FzFtHivA+Hb0ZPq56upG8w==", + "dependencies": { + "protobufjs": "^7.0.0", + "uint8arraylist": "^2.3.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + }, + "peerDependencies": { + "uint8arraylist": "^2.3.2" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -7211,6 +7128,33 @@ "protobufjs": "^6.11.2", "uint8arraylist": "^2.3.2", "uint8arrays": "^3.0.0" + }, + "dependencies": { + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "protobufjs": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", + "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + } + } } }, "@chainsafe/libp2p-noise": { @@ -7436,35 +7380,11 @@ "uint8arrays": "^4.0.2" }, "dependencies": { - "long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" - }, "multiformats": { "version": "10.0.2", "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-10.0.2.tgz", "integrity": "sha512-nJEHLFOYhO4L+aNApHhCnWqa31FyqAHv9Q77AhmwU3KsM2f1j7tuJpCk5ByZ33smzycNCpSG5klNIejIyfFx2A==" }, - "protobufjs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", - "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - } - }, "protons-runtime": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-4.0.1.tgz", @@ -7985,35 +7905,11 @@ "uint8arrays": "^4.0.2" }, "dependencies": { - "long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" - }, "multiformats": { "version": "10.0.2", "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-10.0.2.tgz", "integrity": "sha512-nJEHLFOYhO4L+aNApHhCnWqa31FyqAHv9Q77AhmwU3KsM2f1j7tuJpCk5ByZ33smzycNCpSG5klNIejIyfFx2A==" }, - "protobufjs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", - "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - } - }, "protons-runtime": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-4.0.1.tgz", @@ -8073,35 +7969,11 @@ "varint": "^6.0.0" } }, - "long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" - }, "multiformats": { "version": "10.0.2", "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-10.0.2.tgz", "integrity": "sha512-nJEHLFOYhO4L+aNApHhCnWqa31FyqAHv9Q77AhmwU3KsM2f1j7tuJpCk5ByZ33smzycNCpSG5klNIejIyfFx2A==" }, - "protobufjs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", - "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - } - }, "protons-runtime": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-4.0.1.tgz", @@ -10874,9 +10746,9 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" }, "longbits": { "version": "1.1.0", @@ -11362,9 +11234,9 @@ "dev": true }, "protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -11376,9 +11248,8 @@ "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", "@types/node": ">=13.7.0", - "long": "^4.0.0" + "long": "^5.0.0" } }, "protons-runtime": { @@ -11388,32 +11259,6 @@ "requires": { "protobufjs": "^7.0.0", "uint8arraylist": "^2.3.2" - }, - "dependencies": { - "long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", - "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" - }, - "protobufjs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", - "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - } - } } }, "proxy-addr": { diff --git a/example/package.json b/example/package.json index 54d5d4c..cca9a9c 100644 --- a/example/package.json +++ b/example/package.json @@ -13,6 +13,7 @@ "array-buffer-to-hex": "^1.0.0", "hex-to-array-buffer": "^2.0.0", "js-waku": "^0.29.0-29436ea", + "protobufjs": "^7.1.2", "qrcode": "^1.5.1" }, "devDependencies": { diff --git a/src/codec.ts b/src/codec.ts index aa0482f..bba6998 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -135,10 +135,13 @@ export class NoiseSecureTransferDecoder implements Decoder { return; } - const payloadV2 = PayloadV2.deserialize(proto.payload); - - const decryptedPayload = this.hsResult.readMessage(payloadV2); - - return new NoiseSecureMessage(proto, decryptedPayload); + try { + const payloadV2 = PayloadV2.deserialize(proto.payload); + const decryptedPayload = this.hsResult.readMessage(payloadV2); + return new NoiseSecureMessage(proto, decryptedPayload); + } catch (err) { + log("could not decode message ", err); + return; + } } } diff --git a/src/index.ts b/src/index.ts index 3430f3f..9c8b3d2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,7 @@ import { MessageNametagError, StepHandshakeParameters, } from "./handshake.js"; -import { InitiatorParameters, Receiver, ReceiverParameters, Sender, WakuPairing } from "./pairing.js"; +import { InitiatorParameters, Responder, ResponderParameters, Sender, WakuPairing } from "./pairing.js"; import { EmptyPreMessage, HandshakePattern, @@ -51,4 +51,4 @@ export { ChaChaPolyCipherState, NoisePublicKey }; export { MessageNametagBuffer }; export { NoiseHandshakeDecoder, NoiseHandshakeEncoder, NoiseSecureTransferDecoder, NoiseSecureTransferEncoder }; export { QR }; -export { InitiatorParameters, ReceiverParameters, Sender, Receiver, WakuPairing }; +export { InitiatorParameters, ResponderParameters, Sender, Responder, WakuPairing }; diff --git a/src/pairing.spec.ts b/src/pairing.spec.ts index e1ec8b5..f7f9586 100644 --- a/src/pairing.spec.ts +++ b/src/pairing.spec.ts @@ -8,7 +8,7 @@ import { equals as uint8ArrayEquals } from "uint8arrays/equals"; import { NoiseHandshakeMessage } from "./codec"; import { generateX25519KeyPair } from "./crypto"; -import { ReceiverParameters, WakuPairing } from "./pairing"; +import { ResponderParameters, WakuPairing } from "./pairing"; import { MessageNametagBufferSize } from "./payload"; describe("js-noise: pairing object", () => { @@ -30,7 +30,7 @@ describe("js-noise: pairing object", () => { }, }; const decoderMap: { [key: string]: Decoder } = {}; - const receiver = { + const responder = { subscribe(decoder: Decoder): Promise { return new Promise((resolve) => { decoderMap[decoder.contentTopic] = decoder; @@ -42,6 +42,10 @@ describe("js-noise: pairing object", () => { const decodedMessage = await decoderMap[contentTopic].decode(msg); return decodedMessage!; }, + async stop(contentTopic: string): Promise { + // Do nothing. This is just a simulation + console.debug("stopping subscription to", contentTopic); + }, }; // ================= @@ -49,15 +53,15 @@ describe("js-noise: pairing object", () => { const bobStaticKey = generateX25519KeyPair(); const aliceStaticKey = generateX25519KeyPair(); - const recvParameters = new ReceiverParameters(); - const bobPairingObj = new WakuPairing(sender, receiver, bobStaticKey, recvParameters); + const recvParameters = new ResponderParameters(); + const bobPairingObj = new WakuPairing(sender, responder, bobStaticKey, recvParameters); const bobExecP1 = bobPairingObj.execute(); // Confirmation is done by manually confirmAuthCodeFlow(bobPairingObj, true); const initParameters = bobPairingObj.getPairingInfo(); - const alicePairingObj = new WakuPairing(sender, receiver, aliceStaticKey, initParameters); + const alicePairingObj = new WakuPairing(sender, responder, aliceStaticKey, initParameters); const aliceExecP1 = alicePairingObj.execute(); // Confirmation is done manually @@ -92,8 +96,8 @@ describe("js-noise: pairing object", () => { }); it("should timeout", async function () { - const bobPairingObj = new WakuPairing(sender, receiver, generateX25519KeyPair(), new ReceiverParameters()); - const alicePairingObj = new WakuPairing(sender, receiver, generateX25519KeyPair(), bobPairingObj.getPairingInfo()); + const bobPairingObj = new WakuPairing(sender, responder, generateX25519KeyPair(), new ResponderParameters()); + const alicePairingObj = new WakuPairing(sender, responder, generateX25519KeyPair(), bobPairingObj.getPairingInfo()); const bobExecP1 = bobPairingObj.execute(1000); const aliceExecP1 = alicePairingObj.execute(1000); diff --git a/src/pairing.ts b/src/pairing.ts index 3cb94d9..cd1c529 100644 --- a/src/pairing.ts +++ b/src/pairing.ts @@ -24,7 +24,7 @@ export interface Sender { publish(encoder: Encoder, msg: Message): Promise; } -export interface Receiver { +export interface Responder { subscribe(decoder: Decoder): Promise; // next message should return messages received in a content topic @@ -32,6 +32,9 @@ export interface Receiver { // will call pop in the queue to remove the oldest message received // (it's important to maintain order of received messages) nextMessage(contentTopic: string): Promise; + + // this should stop the subscription + stop(contentTopic: string): Promise; } function delay(ms: number): Promise { @@ -44,7 +47,7 @@ export class InitiatorParameters { constructor(public readonly qrCode: string, public readonly qrMessageNameTag: Uint8Array) {} } -export class ReceiverParameters { +export class ResponderParameters { constructor( public readonly applicationName: string = "waku-noise-sessions", public readonly applicationVersion: string = "0.1", @@ -75,9 +78,9 @@ export class WakuPairing { constructor( private sender: Sender, - private receiver: Receiver, + private responder: Responder, private myStaticKey: KeyPair, - pairingParameters: InitiatorParameters | ReceiverParameters, + pairingParameters: InitiatorParameters | ResponderParameters, private myEphemeralKey: KeyPair = generateX25519KeyPair() ) { this.randomFixLenVal = randomBytes(32, rng); @@ -160,7 +163,7 @@ export class WakuPairing { while (!stopLoop) { try { - const hsMessage = await this.receiver.nextMessage(contentTopic); + const hsMessage = await this.responder.nextMessage(contentTopic); const step = this.handshake.stepHandshake({ readPayloadV2: hsMessage.payloadV2, messageNametag, @@ -181,7 +184,7 @@ export class WakuPairing { private async initiatorHandshake(): Promise<[NoiseSecureTransferEncoder, NoiseSecureTransferDecoder]> { // Subscribe to the contact content topic const decoder = new NoiseHandshakeDecoder(this.contentTopic); - await this.receiver.subscribe(decoder); + await this.responder.subscribe(decoder); // The handshake initiator writes a Waku2 payload v2 containing the handshake message // and the (encrypted) transport message @@ -192,7 +195,7 @@ export class WakuPairing { }); // We prepare a message from initiator's payload2 - // At this point wakuMsg is sent over the Waku network to receiver content topic + // At this point wakuMsg is sent over the Waku network to responder content topic let encoder = new NoiseHandshakeEncoder(this.contentTopic, hsStep); await this.sender.publish(encoder, {}); @@ -211,12 +214,14 @@ export class WakuPairing { // <- sB, eAsB {r} hsStep = await this.executeReadStepWithNextMessage(this.contentTopic, this.handshake.hs.toMessageNametag()); + await this.responder.stop(this.contentTopic); + if (!this.handshake.hs.rs) throw new Error("invalid handshake state"); - // Initiator further checks if receiver's commitment opens to receiver's static key received - const expectedReceiverCommittedStaticKey = commitPublicKey(this.handshake.hs.rs, hsStep.transportMessage); - if (!uint8ArrayEquals(expectedReceiverCommittedStaticKey, this.qr.committedStaticKey)) { - throw new Error("expected committed static key does not match the receiver actual committed static key"); + // Initiator further checks if responder's commitment opens to responder's static key received + const expectedResponderCommittedStaticKey = commitPublicKey(this.handshake.hs.rs, hsStep.transportMessage); + if (!uint8ArrayEquals(expectedResponderCommittedStaticKey, this.qr.committedStaticKey)) { + throw new Error("expected committed static key does not match the responder actual committed static key"); } // 3rd step @@ -238,10 +243,10 @@ export class WakuPairing { return WakuPairing.getSecureCodec(this.contentTopic, this.handshakeResult); } - private async receiverHandshake(): Promise<[NoiseSecureTransferEncoder, NoiseSecureTransferDecoder]> { + private async responderHandshake(): Promise<[NoiseSecureTransferEncoder, NoiseSecureTransferDecoder]> { // Subscribe to the contact content topic const decoder = new NoiseHandshakeDecoder(this.contentTopic); - await this.receiver.subscribe(decoder); + await this.responder.subscribe(decoder); // the received reads the initiator's payloads, and returns the (decrypted) transport message the initiator sent // Note that the received verifies if the received payloadV2 has the expected messageNametag set @@ -259,25 +264,27 @@ export class WakuPairing { } // 2nd step // <- sB, eAsB {r} - // Receiver writes and returns a payload + // Responder writes and returns a payload hsStep = this.handshake.stepHandshake({ transportMessage: this.randomFixLenVal, messageNametag: this.handshake.hs.toMessageNametag(), }); - // We prepare a Waku message from receiver's payload2 + // We prepare a Waku message from responder's payload2 const encoder = new NoiseHandshakeEncoder(this.contentTopic, hsStep); await this.sender.publish(encoder, {}); // 3rd step // -> sA, sAeB, sAsB {s} - // The receiver reads the initiator's payload sent by the initiator + // The responder reads the initiator's payload sent by the initiator hsStep = await this.executeReadStepWithNextMessage(this.contentTopic, this.handshake.hs.toMessageNametag()); + await this.responder.stop(this.contentTopic); + if (!this.handshake.hs.rs) throw new Error("invalid handshake state"); - // The receiver further checks if the initiator's commitment opens to the initiator's static key received + // The responder further checks if the initiator's commitment opens to the initiator's static key received const expectedInitiatorCommittedStaticKey = commitPublicKey(this.handshake.hs.rs, hsStep.transportMessage); if (!uint8ArrayEquals(expectedInitiatorCommittedStaticKey, initiatorCommittedStaticKey)) { throw new Error("expected committed static key does not match the initiator actual committed static key"); @@ -321,12 +328,12 @@ export class WakuPairing { this.eventEmitter.emit("pairingTimeout"); }, timeoutMs); - const handshakeFn = this.initiator ? this.initiatorHandshake : this.receiverHandshake; + const handshakeFn = this.initiator ? this.initiatorHandshake : this.responderHandshake; handshakeFn .bind(this)() .then( (response) => resolve(response), - (err) => reject(new Error(err)) + (err) => reject(err) ) .finally(() => clearTimeout(timer)); }); diff --git a/src/payload.ts b/src/payload.ts index 924cf84..eadf3aa 100644 --- a/src/payload.ts +++ b/src/payload.ts @@ -61,10 +61,10 @@ export class MessageNametagBuffer { const index = this.buffer.findIndex((x) => uint8ArrayEquals(x, messageNametag)); if (index == -1) { - console.error("Message nametag not found in buffer"); + console.debug("Message nametag not found in buffer"); return false; } else if (index > 0) { - console.error( + console.debug( "Message nametag is present in buffer but is not the next expected nametag. One or more messages were probably lost" ); return false;