randomp2p/crypto.js
Ole 09c36ac6c1
All checks were successful
Pin to IPFS / pin (push) Successful in 10s
SDP-Minify: TCP/IPv6/Non-Host-Candidates vor pack() entfernt
2026-06-14 17:22:31 +02:00

129 lines
3.5 KiB
JavaScript

function generatePeerId() {
const bytes = new Uint8Array(8);
crypto.getRandomValues(bytes);
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
}
async function generateSecret() {
const secret = new Uint8Array(32);
crypto.getRandomValues(secret);
return secret;
}
async function commit(secret) {
const hash = await crypto.subtle.digest('SHA-256', secret);
return new Uint8Array(hash);
}
function arraysEqual(a, b) {
if (a.length !== b.length) return false;
let diff = 0;
for (let i = 0; i < a.length; i++) {
diff |= a[i] ^ b[i];
}
return diff === 0;
}
function combineAll(secrets) {
const result = new Uint8Array(32);
for (const secret of secrets) {
for (let i = 0; i < 32; i++) {
result[i] ^= secret[i];
}
}
return result;
}
async function computePeerResult(sharedSeed, peerId) {
const data = new TextEncoder().encode(peerId);
const combined = new Uint8Array(sharedSeed.length + data.length);
combined.set(sharedSeed);
combined.set(data, sharedSeed.length);
const hash = await crypto.subtle.digest('SHA-256', combined);
return new Uint8Array(hash)[0] & 1;
}
function bufToBase64(buf) {
const bytes = new Uint8Array(buf);
let binary = '';
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
function base64ToBuf(base64) {
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes;
}
function hexToBuf(hex) {
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
}
return bytes;
}
/* ─── QR compression: deflate-raw + base45 ─── */
function stripSDP(sdp) {
return sdp.split('\r\n').map(line => {
if (!line.startsWith('a=candidate:')) return line;
const parts = line.split(' ');
if (parts[2] === 'tcp') return null;
if (parts[7] !== 'host') return null;
if (parts[4] && parts[4].includes(':')) return null;
return line;
}).filter(Boolean).join('\r\n');
}
async function pack(obj) {
const json = JSON.stringify(obj);
const blob = new Blob([json]);
const cs = new CompressionStream('deflate-raw');
const ab = await new Response(blob.stream().pipeThrough(cs)).arrayBuffer();
return new Uint8Array(ab);
}
async function unpack(bytes) {
const blob = new Blob([bytes]);
const ds = new DecompressionStream('deflate-raw');
const ab = await new Response(blob.stream().pipeThrough(ds)).arrayBuffer();
return JSON.parse(new TextDecoder().decode(ab));
}
const B45 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
function encode45(buf) {
const bytes = new Uint8Array(buf);
let s = '';
for (let i = 0; i < bytes.length; i += 2) {
if (i + 1 < bytes.length) {
const v = bytes[i] * 256 + bytes[i + 1];
s += B45[v % 45] + B45[Math.floor(v / 45) % 45] + B45[Math.floor(v / 2025)];
} else {
const v = bytes[i];
s += B45[v % 45] + B45[Math.floor(v / 45)];
}
}
return s;
}
function decode45(str) {
const bytes = [];
for (let i = 0; i < str.length; i += 3) {
if (i + 2 < str.length) {
const v = B45.indexOf(str[i]) + B45.indexOf(str[i + 1]) * 45 + B45.indexOf(str[i + 2]) * 2025;
bytes.push(Math.floor(v / 256), v % 256);
} else {
const v = B45.indexOf(str[i]) + B45.indexOf(str[i + 1]) * 45;
bytes.push(v);
}
}
return Uint8Array.from(bytes);
}