'error', 'message' => 'Public key is required.']); killme(); } // Ğ1 SS58 addresses: base58-encoded, 36 bytes decoded, begin with "g1". // Encoded length is 46–47 characters. Reject anything outside that range. $len = strlen($pubkey); if ($len < 46 || $len > 48) { header('Content-Type: application/json'); echo json_encode(['status' => 'error', 'message' => 'Invalid public key format.']); killme(); } // Must begin with "g1" (SS58 with Ğ1 network prefix). if (strncmp($pubkey, 'g1', 2) !== 0) { header('Content-Type: application/json'); echo json_encode(['status' => 'error', 'message' => 'Invalid public key prefix.']); killme(); } // Only base58 characters (Bitcoin alphabet, no 0/O/I/l). if (!preg_match('/^[1-9A-HJ-NP-Za-km-z]+$/', $pubkey)) { header('Content-Type: application/json'); echo json_encode(['status' => 'error', 'message' => 'Invalid public key characters.']); killme(); } // Determine channel_id to write against. // Operators use their own channel_id. Participants also use local_channel(). // We do not write other channels' keys — only the caller's own. $channel_id = local_channel(); if (!$channel_id) { header('Content-Type: application/json'); echo json_encode(['status' => 'error', 'message' => 'Not authenticated.']); killme(); } // Deduplicate: if the stored key matches, return ok without writing. $existing = get_pconfig($channel_id, 'g1wallet', 'g1_pubkey'); if ($existing === $pubkey) { header('Content-Type: application/json'); echo json_encode(['status' => 'ok', 'note' => 'Key unchanged.']); killme(); } // Store the public key. set_pconfig($channel_id, 'g1wallet', 'g1_pubkey', $pubkey); header('Content-Type: application/json'); echo json_encode(['status' => 'ok']); killme(); } function g1wallet_handle_broadcast_post() { // Receives a signed Duniter transaction document (base64-encoded) from the browser. // Validates the node token, relays to the orchestrator, returns the transaction hash. // // The browser signs the document with the participant's private key (WebCrypto). // Only the signed bytes arrive here — never the private key. $signed_doc = trim($_POST['signed_doc'] ?? ''); $doc_type = trim($_POST['doc_type'] ?? ''); // e.g. 'transfer', 'certification' if (!$signed_doc || !$doc_type) { header('Content-Type: application/json'); echo json_encode(['status' => 'error', 'message' => 'signed_doc and doc_type are required.']); killme(); } // Validate doc_type is a known type. $allowed_types = ['transfer', 'certification']; if (!in_array($doc_type, $allowed_types, true)) { header('Content-Type: application/json'); echo json_encode(['status' => 'error', 'message' => 'Unknown doc_type.']); killme(); } // Load config for orchestrator endpoint. $config = g1wallet_load_config(); $orchestrator_url = rtrim($config['orchestrator_url'] ?? '', '/'); $node_token = $config['node_token'] ?? ''; if (!$orchestrator_url || !$node_token) { header('Content-Type: application/json'); echo json_encode(['status' => 'error', 'message' => 'Orchestrator not configured.']); killme(); } // Relay to orchestrator POST /g1wallet/broadcast. $payload = json_encode([ 'signed_doc' => $signed_doc, 'doc_type' => $doc_type, ]); $ch = curl_init($orchestrator_url . '/g1wallet/broadcast'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'X-Node-Token: ' . $node_token, ]); curl_setopt($ch, CURLOPT_TIMEOUT, 15); $raw = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); $err = curl_error($ch); curl_close($ch); if ($err) { header('Content-Type: application/json'); echo json_encode(['status' => 'error', 'message' => 'Orchestrator unreachable.']); killme(); } if ($http !== 200) { header('Content-Type: application/json'); echo json_encode(['status' => 'error', 'message' => 'Orchestrator returned HTTP ' . intval($http) . '.']); killme(); } $result = json_decode($raw, true); if (json_last_error() !== JSON_ERROR_NONE || !isset($result['status'])) { header('Content-Type: application/json'); echo json_encode(['status' => 'error', 'message' => 'Invalid orchestrator response.']); killme(); } header('Content-Type: application/json'); echo json_encode($result); killme(); }