feat: add form to send/receive messages after handshake

This commit is contained in:
Richard Ramos 2022-12-16 15:58:21 -04:00 committed by RichΛrd
parent c10e2f1a88
commit 7ba8e92b18
9 changed files with 373 additions and 364 deletions

View File

@ -7,7 +7,23 @@
</head>
<body>
<p>Press F12 to open the console</p>
<canvas id="qrCanvas"></canvas>
<p><b>Waku Status:</b> <span id="waku-status">connecting...</span></p>
<p id="handshake-span"><b>Handshake Status:</b> <span id="handshake-status">-</span></p>
<canvas id="qr-canvas"></canvas>
<a href="#" id="qr-url" style="display: none" target="_blank">Click here to simulate QR code being scanned</a>
<div id="chat-area" style="display: none">
<label for="nick-input">Your nickname</label>
<input id="nick-input" placeholder="Choose a nickname" type="text" />
<label for="text-input">Message text</label>
<input id="text-input" placeholder="Type your message here" type="text" />
<button id="send-btn" type="button" disabled>Send message using Light Push</button>
<span id="sending-status"></span>
<h4 class="mu1">Messages</h4>
<div id="messages"></div>
</div>
<script src="./index.js"></script>
</body>
</html>

View File

@ -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 = "<ul>";
messages.forEach((msg) => {
messagesDiv.innerHTML += `<li>${msg}</li>`;
});
messagesDiv.innerHTML += "</ul>";
};
const onMessage = (wakuMessage) => {
const { timestamp, nick, text } = ProtoChatMessage.decode(wakuMessage.payload);
const time = new Date();
time.setTime(Number(timestamp) * 1000);
messages.push(`(${nick}) <strong>${utils.bytesToUtf8(text)}</strong> <i>[${time.toISOString()}]</i>`);
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();

View File

@ -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": {

View File

@ -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": {

View File

@ -135,10 +135,13 @@ export class NoiseSecureTransferDecoder implements Decoder<NoiseSecureMessage> {
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;
}
}
}

View File

@ -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 };

View File

@ -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<NoiseHandshakeMessage> } = {};
const receiver = {
const responder = {
subscribe(decoder: Decoder<NoiseHandshakeMessage>): Promise<void> {
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<void> {
// 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);

View File

@ -24,7 +24,7 @@ export interface Sender {
publish(encoder: Encoder, msg: Message): Promise<void>;
}
export interface Receiver {
export interface Responder {
subscribe(decoder: Decoder<NoiseHandshakeMessage>): Promise<void>;
// 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<NoiseHandshakeMessage>;
// this should stop the subscription
stop(contentTopic: string): Promise<void>;
}
function delay(ms: number): Promise<void> {
@ -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));
});

View File

@ -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;