This commit is contained in:
ajvpot
2025-07-06 00:00:00 +00:00
parent 3e6126e108
commit b6b667d5b3
3 changed files with 93 additions and 16 deletions
+47 -11
View File
@@ -226,15 +226,31 @@ function ConfigPopover({ config, setConfig, onClose, anchorRef, onOpenKeyModal }
);
}
function validateMeshcoreKey(base64Key: string): string | null {
if (!base64Key) return null;
// Add a helper to decode base64 or hex
function decodeKeyString(key: string): Buffer | null {
if (!key) return null;
// Try base64 first
try {
const bytes = Buffer.from(base64Key, 'base64');
if (bytes.length !== 16) {
return 'Key must decode to exactly 16 bytes.';
}
} catch {
return 'Invalid base64 encoding.';
const b = Buffer.from(key, 'base64');
if (b.length === 16) return b;
} catch {}
// Try hex (with or without 0x)
let hex = key.trim();
if (hex.startsWith('0x')) hex = hex.slice(2);
if (/^[0-9a-fA-F]{32}$/.test(hex)) {
try {
const b = Buffer.from(hex, 'hex');
if (b.length === 16) return b;
} catch {}
}
return null;
}
function validateMeshcoreKey(key: string): string | null {
if (!key) return null;
const decoded = decodeKeyString(key);
if (!decoded) {
return 'Key must be 16 bytes, in base64 or hex (32 hex digits)';
}
return null;
}
@@ -263,7 +279,17 @@ function MeshcoreKeyModal({ config, setConfig, onClose }: { config: Config, setC
disabled
readOnly
/>
<span className="text-xs text-gray-500">ID: <span className="font-mono">{getChannelIdFromKey(PUBLIC_MESHCORE_KEY.privateKey)}</span></span>
{/* Only show ID if valid */}
{(() => {
try {
const id = getChannelIdFromKey(PUBLIC_MESHCORE_KEY.privateKey);
return (
<span className="text-xs text-gray-500">ID: <span className="font-mono">{id}</span></span>
);
} catch {
return null;
}
})()}
</div>
<div className="flex items-center gap-2 mt-1">
<input
@@ -298,13 +324,23 @@ function MeshcoreKeyModal({ config, setConfig, onClose }: { config: Config, setC
setConfig({ ...config, meshcoreKeys: updated });
}}
/>
<span className="text-xs text-gray-500">ID: <span className="font-mono">{getChannelIdFromKey(key.privateKey)}</span></span>
{/* Only show ID if valid */}
{(() => {
try {
const id = getChannelIdFromKey(key.privateKey);
return (
<span className="text-xs text-gray-500">ID: <span className="font-mono">{id}</span></span>
);
} catch {
return null;
}
})()}
</div>
<div className="flex items-center gap-2 mt-1">
<input
className={`flex-1 p-1 border rounded font-mono ${keyError ? 'border-red-500' : ''}`}
type="text"
placeholder="Base64 Private Key"
placeholder="Base64 or Hex Private Key"
value={key.privateKey}
onChange={e => {
const updated = [...(config.meshcoreKeys || [])];
+23 -4
View File
@@ -3,16 +3,35 @@ import { createHash } from "crypto";
// Module-level cache for channel IDs
const channelIdCache: Record<string, string> = {};
// Add a helper to decode base64 or hex
function decodeKeyString(key: string): Buffer {
// Try base64 first
try {
const b = Buffer.from(key, 'base64');
if (b.length === 16) return b;
} catch {}
// Try hex (with or without 0x)
let hex = key.trim();
if (hex.startsWith('0x')) hex = hex.slice(2);
if (/^[0-9a-fA-F]{32}$/.test(hex)) {
try {
const b = Buffer.from(hex, 'hex');
if (b.length === 16) return b;
} catch {}
}
throw new Error('Invalid key format: must be 16 bytes, base64 or hex');
}
/**
* Returns the channel id for a given base64-encoded key.
* Decodes the key, hashes it with SHA-256, and returns the first byte as hex.
* Results are cached for performance.
*/
export function getChannelIdFromKey(base64Key: string): string {
if (channelIdCache[base64Key]) return channelIdCache[base64Key];
const keyBytes = Buffer.from(base64Key, 'base64');
export function getChannelIdFromKey(key: string): string {
if (channelIdCache[key]) return channelIdCache[key];
const keyBytes = decodeKeyString(key);
const hash = createHash('sha256').update(keyBytes).digest();
const id = hash[0].toString(16).padStart(2, '0');
channelIdCache[base64Key] = id;
channelIdCache[key] = id;
return id;
}
+23 -1
View File
@@ -17,6 +17,28 @@ function base64ToBytes(b64: string): Uint8Array {
return Uint8Array.from(atob(b64), c => c.charCodeAt(0));
}
// Add a helper to decode base64 or hex
function decodeKeyString(key: string): Uint8Array {
// Try base64 first
try {
const b = Uint8Array.from(atob(key), c => c.charCodeAt(0));
if (b.length === 16) return b;
} catch {}
// Try hex (with or without 0x)
let hex = key.trim();
if (hex.startsWith('0x')) hex = hex.slice(2);
if (/^[0-9a-fA-F]{32}$/.test(hex)) {
try {
const b = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
b[i / 2] = parseInt(hex.slice(i, i + 2), 16);
}
if (b.length === 16) return b;
} catch {}
}
throw new Error('Invalid key format: must be 16 bytes, base64 or hex');
}
// Helper: HMAC-SHA256, returns Uint8Array
async function hmacSha256(key: Uint8Array, data: Uint8Array): Promise<Uint8Array> {
if (window.crypto?.subtle) {
@@ -89,7 +111,7 @@ export async function decryptMeshcoreGroupMessage({
for (const base64Key of knownKeys) {
let keyBytes: Uint8Array;
try {
keyBytes = base64ToBytes(base64Key);
keyBytes = decodeKeyString(base64Key);
} catch (e) {
console.warn("Skipping invalid base64 meshcore key:", base64Key, e);
failures.push({ key: base64Key, reason: `base64 decode error: ${e}` });