#!/usr/bin/env node /** MISAI public coordination client. This key signs provider identity only, never custody. */ import { generateKeyPairSync, createPrivateKey, createPublicKey, createHash, sign, verify } from 'node:crypto'; import * as fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; export const DOMAIN = Buffer.from('misai-provider-coordination/1\0', 'utf8'); export const SITE = 'misai.io'; export const NETWORK = 'chronx-k0-candidate'; const MAX_U128 = (1n << 128n) - 1n; const MAX_U64 = (1n << 64n) - 1n; const PUBLIC_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../public'); const SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex'); const CAPABILITIES = ['ltc-liquidity', 'btc-liquidity', 'usdc-liquidity', 'strategy-evaluation', 'proof-production', 'execution']; const assert = (condition, message) => { if (!condition) throw new Error(message); }; /** Bounded reader rejects duplicates and floats before JS can round or erase them. */ export function strictJson(raw, maxBytes = 65536) { assert(typeof raw === 'string' && Buffer.byteLength(raw) > 0 && Buffer.byteLength(raw) <= maxBytes, 'JSON byte limit'); let at = 0, nodes = 0; const space = () => { while (' \t\r\n'.includes(raw[at] ?? '\0')) at++; }; const string = () => { const start = at++; let escaped = false; while (at < raw.length) { const character = raw[at++]; if (escaped) { escaped = false; continue; } if (character === '\\') { escaped = true; continue; } if (character === '"') { const result = JSON.parse(raw.slice(start, at)); assert(typeof result === 'string', 'JSON string required'); return result; } } throw new Error('Unterminated JSON string'); }; const value = (depth) => { space(); assert(depth <= 8 && ++nodes <= 4096, 'JSON complexity limit'); const c = raw[at]; if (c === '"') return string(); if (c === '{') { at++; const result = Object.create(null); space(); if (raw[at] === '}') { at++; return result; } while (true) { space(); assert(raw[at] === '"', 'Object key required'); const key = string(); assert(!Object.hasOwn(result, key), 'Duplicate JSON key'); space(); assert(raw[at++] === ':', 'Object colon required'); result[key] = value(depth + 1); space(); const end = raw[at++]; if (end === '}') return result; assert(end === ',', 'Object delimiter required'); } } if (c === '[') { at++; const result = []; space(); if (raw[at] === ']') { at++; return result; } while (true) { result.push(value(depth + 1)); space(); const end = raw[at++]; if (end === ']') return result; assert(end === ',', 'Array delimiter required'); } } for (const [literal, result] of [['true', true], ['false', false], ['null', null]]) { if (raw.startsWith(literal, at)) { at += literal.length; return result; } } const match = /^-?(?:0|[1-9][0-9]*)/.exec(raw.slice(at)); if (match) { at += match[0].length; assert(!['.', 'e', 'E'].includes(raw[at]), 'Floating point refused; financial amounts must be decimal strings'); assert(match[0].length <= 10, 'JSON integer limit; use exact decimal strings'); return Number(match[0]); } throw new Error('Invalid JSON value'); }; const result = value(0); space(); assert(at === raw.length, 'Trailing JSON data'); return result; } export function canonical(value) { if (value === null || typeof value === 'boolean' || typeof value === 'string') return JSON.stringify(value); if (typeof value === 'number') { assert(Number.isSafeInteger(value) && String(value).length <= 10, 'Only bounded exact JSON integers are supported'); return JSON.stringify(value); } if (Array.isArray(value)) return '[' + value.map(canonical).join(',') + ']'; assert(value && typeof value === 'object' && [Object.prototype, null].includes(Object.getPrototypeOf(value)), 'Plain JSON object required'); return '{' + Object.keys(value).sort().map(key => JSON.stringify(key) + ':' + canonical(value[key])).join(',') + '}'; } function fields(object, expected) { assert(object && typeof object === 'object' && !Array.isArray(object), 'Object required'); assert(Object.keys(object).sort().join('\0') === [...expected].sort().join('\0'), 'Missing or unknown fields'); } function hex(value, bytes) { assert(typeof value === 'string' && new RegExp('^[0-9a-f]{' + bytes * 2 + '}$(?![\\s\\S])').test(value) && !/^0+$/.test(value), 'Nonzero lowercase hex required'); } function decimal(value, maximum = MAX_U128) { assert(typeof value === 'string' && /^[1-9][0-9]*$(?![\s\S])/.test(value) && value.length <= maximum.toString().length && BigInt(value) <= maximum, 'Positive bounded decimal string required'); } function id(value) { assert(typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9._:\/-]{0,127}$(?![\s\S])/.test(value), 'Invalid identifier'); } export function validatePayload(payload) { const common = ['version', 'operation', 'site_id', 'network_id', 'provider_id', 'nonce']; assert(['register', 'publish'].includes(payload?.operation), 'Operation must be register or publish'); const extra = payload.operation === 'register' ? ['display_name', 'capabilities'] : ['kernel_target', 'profile_id', 'profile_terms_hash', 'deed_id', 'terms_hash', 'state_digest', 'anchor_digest', 'order_hash', 'input_asset_id', 'total_input_units', 'output_asset_id', 'output_units', 'expiry_height', 'quote_expires_at', 'source_assets', 'proof_hex']; fields(payload, [...common, ...extra]); assert(payload.version === 1 && payload.site_id === SITE && payload.network_id === NETWORK, 'Version/site/network mismatch'); hex(payload.provider_id, 32); decimal(payload.nonce, MAX_U64); if (payload.operation === 'register') { assert(typeof payload.display_name === 'string' && /^[A-Za-z0-9][A-Za-z0-9 ._-]{0,79}$(?![\s\S])/.test(payload.display_name), 'Invalid display name'); assert(Array.isArray(payload.capabilities) && payload.capabilities.length >= 1 && payload.capabilities.length <= CAPABILITIES.length && new Set(payload.capabilities).size === payload.capabilities.length && payload.capabilities.every(cap => CAPABILITIES.includes(cap)), 'Unknown, duplicate or empty capabilities'); return payload; } for (const key of ['kernel_target', 'profile_id', 'input_asset_id', 'output_asset_id']) id(payload[key]); assert(payload.kernel_target === NETWORK, 'Unsupported kernel target'); assert(payload.input_asset_id !== payload.output_asset_id, 'Exchange assets must differ'); for (const key of ['profile_terms_hash', 'deed_id', 'terms_hash', 'state_digest', 'anchor_digest', 'order_hash']) hex(payload[key], 32); for (const key of ['total_input_units', 'output_units']) decimal(payload[key]); for (const key of ['expiry_height', 'quote_expires_at']) decimal(payload[key], MAX_U64); assert(Array.isArray(payload.source_assets) && payload.source_assets.length >= 1 && payload.source_assets.length <= 64, 'Source count'); const seen = new Set(); let sum = 0n; for (const source of payload.source_assets) { fields(source, ['asset_id', 'source_id', 'units']); id(source.asset_id); id(source.source_id); decimal(source.units); assert(source.asset_id === payload.output_asset_id, 'Source asset mismatch'); const reference = source.asset_id + '\0' + source.source_id; assert(!seen.has(reference), 'Duplicate source claim'); seen.add(reference); sum += BigInt(source.units); assert(sum <= MAX_U128, 'Source amount sum overflow'); } assert(sum >= BigInt(payload.output_units), 'Claimed source amounts below output'); assert(typeof payload.proof_hex === 'string' && payload.proof_hex.length <= 8192 && payload.proof_hex.length % 2 === 0 && /^[0-9a-f]*$(?![\s\S])/.test(payload.proof_hex), 'Bounded proof hex required'); return payload; } export function publicIdentity(privateKey) { const key = createPublicKey(privateKey); assert(key.asymmetricKeyType === 'ed25519', 'A dedicated Ed25519 coordination key is required'); const der = key.export({ type: 'spki', format: 'der' }); assert(der.length === SPKI_PREFIX.length + 32 && der.subarray(0, SPKI_PREFIX.length).equals(SPKI_PREFIX), 'Invalid Ed25519 public key'); const bytes = der.subarray(SPKI_PREFIX.length); return { public_key_hex: bytes.toString('hex'), provider_id: createHash('sha256').update(bytes).digest('hex') }; } export function signPayload(payload, privateKey) { validatePayload(payload); const identity = publicIdentity(privateKey); assert(payload.provider_id === identity.provider_id, 'Payload provider identity does not match coordination key'); const bytes = Buffer.from(canonical(payload), 'utf8'); assert(bytes.length <= 32768, 'Signed payload byte limit'); return { payload_b64: bytes.toString('base64'), public_key_hex: identity.public_key_hex, signature_hex: sign(null, Buffer.concat([DOMAIN, bytes]), privateKey).toString('hex') }; } export function verifyEnvelope(envelope) { fields(envelope, ['payload_b64', 'public_key_hex', 'signature_hex']); hex(envelope.public_key_hex, 32); hex(envelope.signature_hex, 64); assert(typeof envelope.payload_b64 === 'string' && envelope.payload_b64.length <= 44000, 'Envelope base64 limit'); const bytes = Buffer.from(envelope.payload_b64, 'base64'); assert(bytes.length <= 32768 && bytes.toString('base64') === envelope.payload_b64, 'Canonical bounded base64 required'); const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes); const payload = strictJson(text, 32768); validatePayload(payload); assert(canonical(payload) === text, 'Canonical signed payload required'); const rawKey = Buffer.from(envelope.public_key_hex, 'hex'); assert(createHash('sha256').update(rawKey).digest('hex') === payload.provider_id, 'Provider identity mismatch'); const key = createPublicKey({ key: Buffer.concat([SPKI_PREFIX, rawKey]), type: 'spki', format: 'der' }); assert(verify(null, Buffer.concat([DOMAIN, bytes]), key, Buffer.from(envelope.signature_hex, 'hex')), 'Signature refused'); return { signature_verified: true, provider_id: payload.provider_id, operation: payload.operation, nonce: payload.nonce, qualified: false, funding_verified: false, execution: 'disabled', payload }; } export function privatePath(filename, publicRoot = PUBLIC_ROOT) { assert(typeof filename === 'string' && filename.length > 0, 'Provide --key with a private file path outside the public site'); const resolved = path.resolve(filename); const parent = fs.realpathSync(path.dirname(resolved)); const target = path.join(parent, path.basename(resolved)); const webRoot = fs.existsSync(publicRoot) ? fs.realpathSync(publicRoot) : path.resolve(publicRoot); assert(target !== webRoot && !target.startsWith(webRoot + path.sep) && !target.split(path.sep).some(segment => /^(public|public_html|htdocs|www)$/i.test(segment)), 'Coordination private key must be outside public web directories'); return target; } export function initKey(filename) { const target = privatePath(filename); const { privateKey } = generateKeyPairSync('ed25519'); const fd = fs.openSync(target, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | (fs.constants.O_NOFOLLOW || 0), 0o600); try { fs.writeFileSync(fd, privateKey.export({ type: 'pkcs8', format: 'pem' })); fs.fsyncSync(fd); } finally { fs.closeSync(fd); } return { ...publicIdentity(privateKey), key_path: target, key_role: 'provider coordination only; never custody', qualified: false }; } export function readKey(filename) { const target = privatePath(filename); const stat = fs.lstatSync(target); const checkStat = current => { assert(current.isFile() && !current.isSymbolicLink() && current.size <= 4096, 'Coordination key must be a bounded regular file, not a symlink'); if (process.platform !== 'win32') { assert((current.mode & 0o077) === 0, 'Coordination key permissions must be 0600 or stricter'); if (typeof process.getuid === 'function') assert(current.uid === process.getuid(), 'Coordination key must be owned by the current user'); } }; checkStat(stat); const fd = fs.openSync(target, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0)); try { const opened = fs.fstatSync(fd); checkStat(opened); assert(opened.ino === stat.ino && opened.dev === stat.dev, 'Coordination key changed during open'); const buffer = Buffer.alloc(4097); let length = 0; while (length < buffer.length) { const read = fs.readSync(fd, buffer, length, buffer.length - length, null); if (read === 0) break; length += read; } assert(length <= 4096, 'Coordination key byte limit'); const key = createPrivateKey(buffer.subarray(0, length)); assert(key.asymmetricKeyType === 'ed25519', 'Dedicated Ed25519 coordination key required'); return key; } finally { fs.closeSync(fd); } } export async function boundedResponseText(response, signal, maximum = 65536) { assert(response.body && typeof response.body.getReader === 'function', 'Readable response body required'); const reader = response.body.getReader(), chunks = []; let length = 0; const read = () => new Promise((resolve, reject) => { if (signal?.aborted) { reject(new Error('Coordination request timed out or was aborted')); return; } const abort = () => reject(new Error('Coordination request timed out or was aborted')); signal?.addEventListener('abort', abort, { once: true }); reader.read().then(value => { signal?.removeEventListener('abort', abort); resolve(value); }, error => { signal?.removeEventListener('abort', abort); reject(error); }); }); try { while (true) { const { done, value } = await read(); if (done) break; assert(value instanceof Uint8Array, 'Byte response stream required'); length += value.byteLength; assert(length <= maximum, 'Coordination response exceeds 64 KiB'); chunks.push(Buffer.from(value)); } return new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks, length)); } catch (error) { // Cancellation must not turn a timed-out request into another unbounded wait. reader.cancel(error).catch(() => {}); throw error; } finally { try { reader.releaseLock(); } catch {} } } export async function submitEnvelope(action, envelope, endpoint = 'https://www.misai.io/api.php', fetcher = fetch) { assert(['register', 'publish'].includes(action), 'Submission action invalid'); const result = verifyEnvelope(envelope); assert(result.operation === action, 'Submission operation mismatch'); const url = new URL(endpoint); assert(!url.username && !url.password && !url.hash, 'Endpoint must not contain credentials or a fragment'); const local = ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname); assert(url.protocol === 'https:' || (url.protocol === 'http:' && local), 'Use HTTPS, or loopback HTTP for testing'); url.searchParams.set('action', action); const controller = new AbortController(), timeout = setTimeout(() => controller.abort(), 15000); try { const response = await fetcher(url, { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json' }, body: canonical(envelope), redirect: 'error', signal: controller.signal }); const text = await boundedResponseText(response, controller.signal); const output = strictJson(text); assert(response.ok, typeof output.error === 'string' ? output.error : 'Service rejected coordination request'); return output; } finally { clearTimeout(timeout); } } function options(args) { const result = Object.create(null); for (let i = 0; i < args.length; i += 2) { assert(args[i]?.startsWith('--') && args[i + 1] && !args[i + 1].startsWith('--'), 'Options require --name value pairs'); const name = args[i].slice(2); assert(!Object.hasOwn(result, name), 'Duplicate option'); result[name] = args[i + 1]; } return result; } function allowOptions(opts, names) { assert(Object.keys(opts).every(name => names.includes(name)), 'Unknown option'); } function input(filename) { assert(filename, 'Provide --input payload.json (or - for stdin)'); return strictJson(fs.readFileSync(filename === '-' ? 0 : filename, 'utf8')); } function output(value, filename) { const text = JSON.stringify(value, null, 2) + '\n'; if (filename) fs.writeFileSync(filename, text, { flag: 'wx', mode: 0o600 }); else process.stdout.write(text); } export async function main(args) { const [command, ...rest] = args; if (!command || command === 'help' || command === '--help') { process.stdout.write(`MISAI provider coordination CLI (Node 18+)\n\n init --key /private/provider.pem\n register --key /private/provider.pem --name "Provider name" --capabilities ltc-liquidity [--nonce 1] [--endpoint https://www.misai.io/api.php]\n sign --key /private/provider.pem --input payload.json [--output envelope.json]\n publish --key /private/provider.pem --input payload.json [--endpoint https://www.misai.io/api.php]\n verify --input envelope.json\n\nOnly register and publish submit to the service. init/sign/verify work offline.\nUse a dedicated coordination key; never a wallet or custody key.\nPayload examples: https://www.misai.io/api.php?action=docs\nAmounts/nonces/expiry are exact decimal strings. Published offers remain unverified.\n`); return; } const opts = options(rest); if (command === 'init') { allowOptions(opts, ['key']); output(initKey(opts.key)); return; } if (command === 'verify') { allowOptions(opts, ['input']); output(verifyEnvelope(input(opts.input))); return; } if (command === 'register') { allowOptions(opts, ['key', 'name', 'capabilities', 'nonce', 'endpoint']); const key = readKey(opts.key), identity = publicIdentity(key); const payload = { version: 1, operation: 'register', site_id: SITE, network_id: NETWORK, provider_id: identity.provider_id, nonce: opts.nonce || '1', display_name: opts.name, capabilities: (opts.capabilities || '').split(',') }; output(await submitEnvelope('register', signPayload(payload, key), opts.endpoint)); return; } if (command === 'sign' || command === 'publish') { allowOptions(opts, command === 'sign' ? ['key', 'input', 'output'] : ['key', 'input', 'endpoint']); const payload = input(opts.input), envelope = signPayload(payload, readKey(opts.key)); if (command === 'sign') output(envelope, opts.output); else { assert(payload.operation === 'publish', 'publish requires a publish payload'); output(await submitEnvelope('publish', envelope, opts.endpoint)); } return; } throw new Error('Unknown command; run provider.mjs help'); } if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) { main(process.argv.slice(2)).catch(error => { process.stderr.write('MISAI: ' + error.message + '\n'); process.exitCode = 1; }); }