mirror of
https://github.com/recrof/map.meshcore.dev-uploader.git
synced 2026-03-28 17:42:45 +01:00
bugfix: signing is made off radio
This commit is contained in:
13
index.mjs
13
index.mjs
@@ -4,6 +4,7 @@ import {
|
||||
Advert
|
||||
} from '@liamcottle/meshcore.js';
|
||||
|
||||
import { KeyPair } from './supercop/index.mjs';
|
||||
import crypto from 'crypto';
|
||||
|
||||
const device = process.argv[2] ?? '/dev/ttyACM0';
|
||||
@@ -11,13 +12,13 @@ const apiURL = 'https://map.meshcore.dev/api/v1/uploader/node';
|
||||
const seenAdverts = {};
|
||||
let clientInfo = {};
|
||||
|
||||
const signData = async (connection, data) => {
|
||||
const signData = async (kp, data) => {
|
||||
const json = JSON.stringify(data);
|
||||
const dataHash = new Uint8Array(
|
||||
await crypto.subtle.digest('SHA-256', new TextEncoder().encode(json))
|
||||
);
|
||||
|
||||
return { data: json, signature: BufferUtils.bytesToHex(await connection.sign(dataHash)) }
|
||||
return { data: json, signature: BufferUtils.bytesToHex(await kp.sign(dataHash)) }
|
||||
}
|
||||
|
||||
const processPacket = async (connection, rawPacket) => {
|
||||
@@ -26,7 +27,7 @@ const processPacket = async (connection, rawPacket) => {
|
||||
if(packet.payload_type_string !== 'ADVERT') return;
|
||||
|
||||
const advert = Advert.fromBytes(packet.payload);
|
||||
// console.log(advert);
|
||||
// console.debug('DEBUG: got advert', advert);
|
||||
if(advert.parsed.type === 'CHAT') return;
|
||||
|
||||
const pubKey = BufferUtils.bytesToHex(advert.publicKey);
|
||||
@@ -59,7 +60,7 @@ const processPacket = async (connection, rawPacket) => {
|
||||
links: [`meshcore://${BufferUtils.bytesToHex(rawPacket)}`]
|
||||
};
|
||||
|
||||
const requestData = await signData(connection, data);
|
||||
const requestData = await signData(clientInfo.kp, data);
|
||||
requestData.publicKey = BufferUtils.bytesToHex(clientInfo.publicKey);
|
||||
|
||||
const req = await fetch(apiURL, {
|
||||
@@ -67,6 +68,8 @@ const processPacket = async (connection, rawPacket) => {
|
||||
body: JSON.stringify(requestData)
|
||||
});
|
||||
|
||||
// console.debug('DEBUG: sent request', req);
|
||||
|
||||
console.log('sending', requestData)
|
||||
console.log(await req.json());
|
||||
|
||||
@@ -89,7 +92,7 @@ connection.on('connected', async () => {
|
||||
connection.setManualAddContacts();
|
||||
|
||||
clientInfo = await connection.getSelfInfo();
|
||||
// console.log('info', clientInfo);
|
||||
clientInfo.kp = KeyPair.from({ publicKey: clientInfo.publicKey, secretKey: (await connection.exportPrivateKey()).privateKey });
|
||||
|
||||
console.log('Map uploader waiting for adverts...');
|
||||
});
|
||||
|
||||
229
supercop/index.mjs
Normal file
229
supercop/index.mjs
Normal file
@@ -0,0 +1,229 @@
|
||||
import fs from 'node:fs';
|
||||
|
||||
const Module = (async () => {
|
||||
const memory = new WebAssembly.Memory({ initial: 4 });
|
||||
const imports = { env: { memory } };
|
||||
const program = await WebAssembly.instantiate(fs.readFileSync('./supercop/supercop.wasm'), imports);
|
||||
|
||||
return {
|
||||
memory: memory,
|
||||
instance: program.instance,
|
||||
exports: program.instance.exports,
|
||||
};
|
||||
})();
|
||||
|
||||
function toBuffer(data) {
|
||||
return data instanceof Buffer ? data : Buffer.from(data);
|
||||
}
|
||||
|
||||
function randomBytes(length) {
|
||||
return Buffer.from(new Array(length).fill(0).map(() => Math.floor(Math.random() * 256)));
|
||||
}
|
||||
|
||||
export function createSeed() {
|
||||
return randomBytes(32);
|
||||
}
|
||||
|
||||
export function isSeed(data) {
|
||||
return data.length === 32;
|
||||
}
|
||||
|
||||
export function isPublicKey(data) {
|
||||
return data.length === 32;
|
||||
}
|
||||
|
||||
export function isSignature(data) {
|
||||
return data.length === 64;
|
||||
}
|
||||
|
||||
export function isSecretKey(data) {
|
||||
return data.length === 64;
|
||||
}
|
||||
|
||||
export class KeyPair {
|
||||
publicKey;
|
||||
secretKey;
|
||||
constructor() {
|
||||
// Intentionally empty
|
||||
}
|
||||
// Passes signing on to the exported stand-alone method
|
||||
// Async, so the error = promise rejection
|
||||
async sign(message) {
|
||||
if (!isSecretKey(this.secretKey))
|
||||
throw new Error('No secret key on this keypair, only verification is possible');
|
||||
if (!isPublicKey(this.publicKey))
|
||||
throw new Error('Invalid public key');
|
||||
return sign(message, this.publicKey, this.secretKey);
|
||||
}
|
||||
// Passes verification on to the exported stand-alone method
|
||||
verify(signature, message) {
|
||||
if (!isPublicKey(this.publicKey))
|
||||
throw new Error('Invalid public key');
|
||||
return verify(signature, message, this.publicKey);
|
||||
}
|
||||
keyExchange(theirPublicKey) {
|
||||
if (!isSecretKey(this.secretKey))
|
||||
throw new Error('Invalid secret key');
|
||||
return keyExchange(theirPublicKey, this.secretKey);
|
||||
}
|
||||
toJSON() {
|
||||
return {
|
||||
publicKey: this.publicKey ? [...this.publicKey] : undefined,
|
||||
secretKey: this.secretKey ? [...this.secretKey] : undefined,
|
||||
};
|
||||
}
|
||||
static create(seed) {
|
||||
return createKeyPair(seed);
|
||||
}
|
||||
static from(data) {
|
||||
return keyPairFrom(data);
|
||||
}
|
||||
}
|
||||
|
||||
export function keyPairFrom(data) {
|
||||
if (typeof data !== 'object')
|
||||
throw new Error('Invalid input data');
|
||||
// Sanitization and sanity checking
|
||||
data = {
|
||||
publicKey: toBuffer(data.publicKey),
|
||||
secretKey: toBuffer(data.secretKey)
|
||||
};
|
||||
|
||||
if (!isPublicKey(data.publicKey))
|
||||
throw new Error('Invalid public key');
|
||||
// Not checking the secretKey, allowed to be missing
|
||||
const keypair = new KeyPair();
|
||||
Object.assign(keypair, data);
|
||||
|
||||
return keypair;
|
||||
}
|
||||
|
||||
export async function createKeyPair(seed) {
|
||||
// Pre-fetch module components
|
||||
const fn = (await Module).exports;
|
||||
const mem = (await Module).memory;
|
||||
// Ensure we have a valid seed
|
||||
if (Array.isArray(seed))
|
||||
seed = Buffer.from(seed);
|
||||
if (!isSeed(seed))
|
||||
throw new Error('Invalid seed');
|
||||
// Reserve wasm-side memory
|
||||
const seedPtr = fn._malloc(32);
|
||||
const publicKeyPtr = fn._malloc(32);
|
||||
const secretKeyPtr = fn._malloc(64);
|
||||
const seedBuf = new Uint8Array(mem.buffer, seedPtr, 32);
|
||||
const publicKey = new Uint8Array(mem.buffer, publicKeyPtr, 32);
|
||||
const secretKey = new Uint8Array(mem.buffer, secretKeyPtr, 64);
|
||||
seedBuf.set(seed);
|
||||
fn.create_keypair(publicKeyPtr, secretKeyPtr, seedPtr);
|
||||
fn._free(seedPtr);
|
||||
fn._free(publicKeyPtr);
|
||||
fn._free(secretKeyPtr);
|
||||
|
||||
return keyPairFrom({
|
||||
publicKey: Buffer.from(publicKey),
|
||||
secretKey: Buffer.from(secretKey),
|
||||
});
|
||||
}
|
||||
|
||||
export async function sign(message, publicKey, secretKey) {
|
||||
// Pre-fetch module components
|
||||
const fn = (await Module).exports;
|
||||
const mem = (await Module).memory;
|
||||
|
||||
// Sanitization and sanity checking
|
||||
if (Array.isArray(publicKey))
|
||||
publicKey = Buffer.from(publicKey);
|
||||
if (Array.isArray(secretKey))
|
||||
secretKey = Buffer.from(secretKey);
|
||||
if (!isPublicKey(publicKey))
|
||||
throw new Error('Invalid public key');
|
||||
if (!isSecretKey(secretKey))
|
||||
throw new Error('Invalid secret key');
|
||||
if ('string' === typeof message)
|
||||
message = Buffer.from(message);
|
||||
// Allocate memory on the wasm side to transfer variables
|
||||
const messageLen = message.length;
|
||||
const messageArrPtr = fn._malloc(messageLen);
|
||||
const messageArr = new Uint8Array(mem.buffer, messageArrPtr, messageLen);
|
||||
const publicKeyArrPtr = fn._malloc(32);
|
||||
const publicKeyArr = new Uint8Array(mem.buffer, publicKeyArrPtr, 32);
|
||||
const secretKeyArrPtr = fn._malloc(64);
|
||||
const secretKeyArr = new Uint8Array(mem.buffer, secretKeyArrPtr, 64);
|
||||
const sigPtr = fn._malloc(64);
|
||||
const sig = new Uint8Array(mem.buffer, sigPtr, 64);
|
||||
messageArr.set(message);
|
||||
publicKeyArr.set(publicKey);
|
||||
secretKeyArr.set(secretKey);
|
||||
await fn.sign(sigPtr, messageArrPtr, messageLen, publicKeyArrPtr, secretKeyArrPtr);
|
||||
// Free used memory on wasm side
|
||||
fn._free(messageArrPtr);
|
||||
fn._free(publicKeyArrPtr);
|
||||
fn._free(secretKeyArrPtr);
|
||||
fn._free(sigPtr);
|
||||
|
||||
return Buffer.from(sig);
|
||||
}
|
||||
export async function verify(signature, message, publicKey) {
|
||||
const fn = (await Module).exports;
|
||||
const mem = (await Module).memory;
|
||||
|
||||
// Sanitization and sanity checking
|
||||
if (Array.isArray(signature))
|
||||
signature = Buffer.from(signature);
|
||||
if (Array.isArray(publicKey))
|
||||
publicKey = Buffer.from(publicKey);
|
||||
if (!isPublicKey(publicKey))
|
||||
throw new Error('Invalid public key');
|
||||
if (!isSignature(signature))
|
||||
throw new Error('Invalid signature');
|
||||
if ('string' === typeof message)
|
||||
message = Buffer.from(message);
|
||||
// Allocate memory on the wasm side to transfer variables
|
||||
const messageLen = message.length;
|
||||
const messageArrPtr = fn._malloc(messageLen);
|
||||
const messageArr = new Uint8Array(mem.buffer, messageArrPtr, messageLen);
|
||||
const signatureArrPtr = fn._malloc(64);
|
||||
const signatureArr = new Uint8Array(mem.buffer, signatureArrPtr, 64);
|
||||
const publicKeyArrPtr = fn._malloc(32);
|
||||
const publicKeyArr = new Uint8Array(mem.buffer, publicKeyArrPtr, 32);
|
||||
messageArr.set(message);
|
||||
signatureArr.set(signature);
|
||||
publicKeyArr.set(publicKey);
|
||||
const res = fn.verify(signatureArrPtr, messageArrPtr, messageLen, publicKeyArrPtr) === 1;
|
||||
// Free used memory on wasm side
|
||||
fn._free(messageArrPtr);
|
||||
fn._free(signatureArrPtr);
|
||||
fn._free(publicKeyArrPtr);
|
||||
|
||||
return res;
|
||||
}
|
||||
export async function keyExchange(theirPublicKey, ourSecretKey) {
|
||||
const fn = (await Module).exports;
|
||||
const mem = (await Module).memory;
|
||||
|
||||
// Sanitization and sanity checking
|
||||
if (Array.isArray(theirPublicKey))
|
||||
theirPublicKey = Buffer.from(theirPublicKey);
|
||||
if (Array.isArray(ourSecretKey))
|
||||
ourSecretKey = Buffer.from(ourSecretKey);
|
||||
if (!isPublicKey(theirPublicKey))
|
||||
throw new Error('Invalid public key');
|
||||
if (!isSecretKey(ourSecretKey))
|
||||
throw new Error('Invalid secret key');
|
||||
// Allocate memory on the wasm side to transfer variables
|
||||
const sharedSecretArrPtr = fn._malloc(32);
|
||||
const sharedSecretArr = new Uint8Array(mem.buffer, sharedSecretArrPtr, 32);
|
||||
const publicKeyArrPtr = fn._malloc(32);
|
||||
const publicKeyArr = new Uint8Array(mem.buffer, publicKeyArrPtr, 32);
|
||||
const secretKeyArrPtr = fn._malloc(64);
|
||||
const secretKeyArr = new Uint8Array(mem.buffer, secretKeyArrPtr, 64);
|
||||
publicKeyArr.set(theirPublicKey);
|
||||
secretKeyArr.set(ourSecretKey);
|
||||
fn.key_exchange(sharedSecretArrPtr, publicKeyArrPtr, secretKeyArrPtr);
|
||||
// Free used memory on wasm side
|
||||
fn._free(sharedSecretArrPtr);
|
||||
fn._free(publicKeyArrPtr);
|
||||
fn._free(secretKeyArrPtr);
|
||||
return Buffer.from(sharedSecretArr);
|
||||
}
|
||||
BIN
supercop/supercop.wasm
Normal file
BIN
supercop/supercop.wasm
Normal file
Binary file not shown.
Reference in New Issue
Block a user