From f84e22433a9dbee4d34a22a266e028e545bdd8fd Mon Sep 17 00:00:00 2001 From: Gab Virebent Date: Sat, 22 Aug 2026 20:36:56 +0200 Subject: Initial identicons-web release --- index.php | 1290 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1290 insertions(+) create mode 100644 index.php (limited to 'index.php') diff --git a/index.php b/index.php new file mode 100644 index 0000000..ef26ce3 --- /dev/null +++ b/index.php @@ -0,0 +1,1290 @@ + $max) $v = mb_substr($v, 0, $max); + return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $v); +} + +/** + * Clean a public key from any format to raw key content. + * Handles: PEM (any type), SSH format, hex, base64, raw paste. + */ +function cleanPublicKey(string $raw): string { + $key = trim($raw); + if (empty($key)) return ''; + + // Strip PEM headers/footers (-----BEGIN ... -----, -----END ... -----) + $key = preg_replace('/-----BEGIN [A-Z\s]+-----/', '', $key); + $key = preg_replace('/-----END [A-Z\s]+-----/', '', $key); + + // Strip SSH prefix (ssh-ed25519, ssh-rsa, ecdsa-sha2-nistp256, etc.) + $key = preg_replace('/^(ssh-\S+|ecdsa-\S+)\s+/', '', trim($key)); + + // Strip SSH trailing comment (user@host) + $key = preg_replace('/\s+\S+@\S+\s*$/', '', $key); + $key = preg_replace('/\s+[^\s=+\/]+\s*$/', '', $key); + + // Remove all whitespace and newlines + $key = preg_replace('/\s+/', '', $key); + + return $key; +} + +// ============================================ +// IDENTICON GENERATION VIA CLI +// ============================================ + +function generateIdenticonBase64(string $input, int $size): ?string { + if (!is_executable(IDENTICONS_CLI)) return null; + $cmd = IDENTICONS_CLI . ' -input ' . escapeshellarg($input) . + ' -size ' . intval($size) . ' -transparent -format base64 2>&1'; + $out = trim(shell_exec($cmd) ?? ''); + if (empty($out) || !preg_match('/^[A-Za-z0-9+\/=]+$/', $out)) return null; + return $out; +} + +// ============================================ +// FACE HEADER FOLDING (RFC 2822) +// ============================================ + +function foldFaceHeader(string $base64): string { + $maxFirst = 72 - strlen('Face: '); + $maxCont = 72 - 1; + $lines = ['Face: ' . substr($base64, 0, $maxFirst)]; + $offset = $maxFirst; + while ($offset < strlen($base64)) { + $lines[] = ' ' . substr($base64, $offset, $maxCont); + $offset += $maxCont; + } + return implode("\n", $lines); +} + +// ============================================ +// ED25519 KEYPAIR GENERATION +// ============================================ + +function generateKeypair(): array { + $kp = sodium_crypto_sign_keypair(); + $sk = sodium_crypto_sign_secretkey($kp); + $pk = sodium_crypto_sign_publickey($kp); + $result = [ + 'publicKey' => base64_encode($pk), + 'secretKey' => base64_encode($sk), + 'created' => gmdate('c'), + ]; + // Wipe sensitive material from memory + sodium_memzero($sk); + sodium_memzero($kp); + return $result; +} + +// ============================================ +// ED25519 SIGNATURE VERIFICATION +// ============================================ + +function verifySignature(string $message, string $signatureB64, string $pubkeyB64): bool { + $sig = base64_decode($signatureB64, true); + $pk = base64_decode($pubkeyB64, true); + if ($sig === false || $pk === false) return false; + if (strlen($sig) !== 64 || strlen($pk) !== 32) return false; + return sodium_crypto_sign_verify_detached($sig, $message, $pk); +} + +// ============================================ +// STATIC IDENTITY PAGE GENERATOR +// ============================================ + +function buildIdentityPageHTML( + string $username, string $email, string $pubkey, + string $hash, string $identicon256, string $faceBase64 +): string { + $u = htmlspecialchars($username, ENT_QUOTES, 'UTF-8'); + $e = htmlspecialchars($email, ENT_QUOTES, 'UTF-8'); + $p = htmlspecialchars($pubkey, ENT_QUOTES, 'UTF-8'); + $h = htmlspecialchars($hash, ENT_QUOTES, 'UTF-8'); + $faceHeader = htmlspecialchars(foldFaceHeader($faceBase64), ENT_QUOTES, 'UTF-8'); + $vfaceHeaders = htmlspecialchars( + "From: {$username} <{$email}>\n" . + "Ed25519-Pub: {$pubkey}\n" . + "Ed25519-Sig: [sign message body with your private key]\n" . + "Identity-Hash: {$hash}\n" . + foldFaceHeader($faceBase64), + ENT_QUOTES, 'UTF-8' + ); + + return << + + + + +VFACE Identity — {$u} + + + +

VFACE Identity

+
+
Identicon
+
Username
{$u}
+
Email
{$e}
+
Public Key
{$p}
+
Identity Hash
{$h}
+
+
+VFACE Headers for Usenet / Email:
+Copy these headers into your client. Replace the signature line after signing your message body. +
{$vfaceHeaders}
+
+
+How to verify this identity:

+1. Concatenate this string:
+ +Click to select all — then copy
+2. Calculate SHA256 of that string — it should match the Identity Hash above.

+3. Go to identicons.virebent.art and use the Analyze tab to verify the identicon, or the Create tab to regenerate it from the same input.

+4. For signed messages: verify the Ed25519 signature with the public key above.

+5. Check the .ots file alongside this page for Bitcoin timestamp proof (opentimestamps.org). +
+ + +HTML; +} + +// ============================================ +// CH1FFR3PUNK PALETTES (for analysis) +// ============================================ + +const PRIMARY_PALETTE = [ + [0x00, 0xbf, 0x93], [0x2d, 0xcc, 0x70], [0x42, 0xe4, 0x53], [0xf1, 0xc4, 0x0f], + [0xe6, 0x7f, 0x22], [0xff, 0x94, 0x4e], [0xe8, 0x4c, 0x3d], [0x35, 0x98, 0xdb], + [0x9a, 0x59, 0xb5], [0xef, 0x3e, 0x96], [0xdf, 0x21, 0xb9], [0x7d, 0xc2, 0xd2], + [0x16, 0xa0, 0x86], [0x27, 0xae, 0x61], [0x24, 0xc3, 0x33], [0x1c, 0xab, 0xbb], +]; + +const SECONDARY_PALETTE = [ + [0x34, 0x49, 0x5e], [0x95, 0xa5, 0xa5], [0xd2, 0x54, 0x00], [0xc1, 0x39, 0x2b], + [0x29, 0x7f, 0xb8], [0x8d, 0x44, 0xad], [0xbe, 0x12, 0x7e], [0xe5, 0x23, 0x83], + [0x27, 0xae, 0x61], [0x24, 0xc3, 0x33], [0xd9, 0xd9, 0x21], [0xf3, 0x9c, 0x11], + [0xff, 0x55, 0x00], [0x1c, 0xab, 0xbb], [0x23, 0x23, 0x23], [0x7e, 0x8c, 0x8d], +]; + +const BACKGROUNDS = [ + [255, 255, 255], [243, 245, 247], [236, 240, 241], +]; + +function colorsMatch(array $a, array $b, int $tolerance = 5): bool { + return abs($a['r'] - $b['r']) <= $tolerance && + abs($a['g'] - $b['g']) <= $tolerance && + abs($a['b'] - $b['b']) <= $tolerance; +} + +function colorDistance(array $a, array $b): float { + return sqrt(pow($a[0] - $b[0], 2) + pow($a[1] - $b[1], 2) + pow($a[2] - $b[2], 2)); +} + +function analyzeIdenticon(string $filePath): array { + $result = [ + 'valid' => false, 'dimensions' => null, 'cell_size' => null, + 'symmetric' => false, 'colors_found' => [], + 'primary_color' => null, 'secondary_color' => null, + 'background' => null, 'bg_match' => false, + 'primary_match' => false, 'secondary_match' => false, + 'palette_score' => 0, 'errors' => [], 'dataurl' => null, + ]; + + $info = @getimagesize($filePath); + if ($info === false) { $result['errors'][] = 'Not a valid image file.'; return $result; } + if ($info[2] !== IMAGETYPE_PNG) { $result['errors'][] = 'Image must be PNG format.'; return $result; } + + $width = $info[0]; $height = $info[1]; + $result['dimensions'] = "{$width}x{$height}"; + + if ($width !== $height) { $result['errors'][] = 'Image is not square.'; return $result; } + if ($width < 10) { $result['errors'][] = 'Image too small.'; return $result; } + + $spriteSize = 5; + $cellSize = intdiv($width, $spriteSize); + $margin = intdiv($width - $spriteSize * $cellSize, 2); + $result['cell_size'] = $cellSize; + + $img = @imagecreatefrompng($filePath); + if (!$img) { $result['errors'][] = 'Failed to load PNG.'; return $result; } + + if (!imageistruecolor($img)) { imagepalettetotruecolor($img); } + imagealphablending($img, false); + imagesavealpha($img, true); + + ob_start(); imagepng($img); $pngData = ob_get_clean(); + $result['dataurl'] = 'data:image/png;base64,' . base64_encode($pngData); + + $grid = []; $colorMap = []; + for ($row = 0; $row < $spriteSize; $row++) { + $grid[$row] = []; + for ($col = 0; $col < $spriteSize; $col++) { + $px = min($col * $cellSize + $margin + intdiv($cellSize, 2), $width - 1); + $py = min($row * $cellSize + $margin + intdiv($cellSize, 2), $width - 1); + $rgba = imagecolorat($img, $px, $py); + $r = ($rgba >> 16) & 0xFF; $g = ($rgba >> 8) & 0xFF; + $b = $rgba & 0xFF; $a = ($rgba >> 24) & 0x7F; + $grid[$row][$col] = ['r' => $r, 'g' => $g, 'b' => $b, 'a' => $a]; + if ($a < 64) { $colorMap["{$r},{$g},{$b}"] = [$r, $g, $b]; } + } + } + + $symmetryOk = true; + for ($row = 0; $row < 5; $row++) { + for ($pair = 0; $pair < 2; $pair++) { + if (!colorsMatch($grid[$row][$pair], $grid[$row][4 - $pair], 5)) { + $symmetryOk = false; break 2; + } + } + } + $result['symmetric'] = $symmetryOk; + $result['colors_found'] = array_values($colorMap); + + $bgColor = null; $fgColors = []; + $cornerColor = $grid[0][0]; + + foreach (BACKGROUNDS as $bg) { + if (colorsMatch(['r'=>$bg[0],'g'=>$bg[1],'b'=>$bg[2],'a'=>0], $cornerColor, 5)) { + $result['bg_match'] = true; $bgColor = $bg; break; + } + } + + $hasTransparent = false; + for ($row = 0; $row < 5; $row++) { + for ($col = 0; $col < 5; $col++) { + if ($grid[$row][$col]['a'] >= 64) { $hasTransparent = true; break 2; } + } + } + if ($hasTransparent) { $result['bg_match'] = true; $result['background'] = 'transparent'; } + elseif ($bgColor) { $result['background'] = sprintf('#%02x%02x%02x', $bgColor[0], $bgColor[1], $bgColor[2]); } + + foreach ($colorMap as $key => $rgb) { + if ($bgColor && colorsMatch(['r'=>$rgb[0],'g'=>$rgb[1],'b'=>$rgb[2],'a'=>0], + ['r'=>$bgColor[0],'g'=>$bgColor[1],'b'=>$bgColor[2],'a'=>0], 5)) continue; + $fgColors[] = $rgb; + } + + foreach ($fgColors as $fg) { + foreach (PRIMARY_PALETTE as $idx => $pal) { + if (colorDistance($fg, $pal) < 10) { + $result['primary_match'] = true; + $result['primary_color'] = ['rgb'=>$fg, 'hex'=>sprintf('#%02x%02x%02x',$fg[0],$fg[1],$fg[2]), 'index'=>$idx]; + break 2; + } + } + } + + foreach ($fgColors as $fg) { + if ($result['primary_color'] && colorDistance($fg, $result['primary_color']['rgb']) < 10) continue; + foreach (SECONDARY_PALETTE as $idx => $pal) { + if (colorDistance($fg, $pal) < 10) { + $result['secondary_match'] = true; + $result['secondary_color'] = ['rgb'=>$fg, 'hex'=>sprintf('#%02x%02x%02x',$fg[0],$fg[1],$fg[2]), 'index'=>$idx]; + break 2; + } + } + } + + $score = 0; + if ($result['symmetric']) $score += 30; + if ($result['bg_match']) $score += 20; + if ($result['primary_match']) $score += 25; + if ($result['secondary_match']) $score += 25; + $result['palette_score'] = $score; + + $result['valid'] = ($result['symmetric'] && $result['bg_match'] && + $result['primary_match'] && $result['secondary_match'] && + count($colorMap) >= 2 && count($colorMap) <= 4); + + imagedestroy($img); + return $result; +} + +// ============================================ +// PROCESS REQUESTS +// ============================================ + +$tab = $_POST['tab'] ?? 'create'; +$result = null; +$verifyRes = null; +$anaResult = null; +$error = null; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $tab = $_POST['tab'] ?? 'create'; + + // ---- TAB: CREATE ---- + if ($tab === 'create') { + $username = sanitize($_POST['username'] ?? '', 64); + $email = sanitize($_POST['email'] ?? '', 254); + $keyMode = $_POST['key_mode'] ?? 'generate'; + $rawPubkey = sanitize($_POST['pubkey'] ?? '', 8192); + + if (empty($username)) { $error = 'Pick a username.'; } + elseif (empty($email)) { $error = 'Enter an email address.'; } + elseif ($keyMode === 'existing' && empty($rawPubkey)) { + $error = 'Paste your public key.'; + } + + if (!$error) { + $keypair = null; + if ($keyMode === 'generate') { + if (!function_exists('sodium_crypto_sign_keypair')) { + $error = 'Server does not support Ed25519 (sodium extension missing).'; + } else { + $keypair = generateKeypair(); + $pubkey = $keypair['publicKey']; + } + } else { + $pubkey = cleanPublicKey($rawPubkey); + if (empty($pubkey)) { + $error = 'Could not extract a valid key from your input.'; + } + } + $keyCleaned = isset($pubkey) && $rawPubkey !== $pubkey; + } + + if (!$error) { + $input = $username . '|' . $email . '|' . $pubkey; + $hash = hash('sha256', $input); + $ico48 = generateIdenticonBase64($input, 48); + $ico256 = generateIdenticonBase64($input, 256); + + if (!$ico48 || !$ico256) { + $error = 'Failed to generate identicon (binary not found).'; + } else { + $result = [ + 'username' => $username, + 'email' => $email, + 'pubkey' => $pubkey, + 'keypair' => $keypair, + 'keyCleaned' => $keyCleaned ?? false, + 'input' => $input, + 'hash' => $hash, + 'ico48' => $ico48, + 'ico256' => $ico256, + 'face' => foldFaceHeader($ico48), + 'headers' => "From: {$username} <{$email}>\n" . + "Ed25519-Pub: {$pubkey}\n" . + "Ed25519-Sig: [sign message body with your private key]\n" . + "Identity-Hash: {$hash}\n" . + foldFaceHeader($ico48), + 'identityPage' => buildIdentityPageHTML( + $username, $email, $pubkey, $hash, $ico256, $ico48 + ), + ]; + } + } + + // ---- TAB: VERIFY ---- + } elseif ($tab === 'verify') { + $vRawPubkey = sanitize($_POST['v_pubkey'] ?? '', 8192); + $vSig = sanitize($_POST['v_signature'] ?? '', 1024); + $vBody = $_POST['v_body'] ?? ''; + $vUser = sanitize($_POST['v_username'] ?? '', 64); + $vEmail = sanitize($_POST['v_email'] ?? '', 254); + $vFace = sanitize($_POST['v_face'] ?? '', 8192); + $vPubkey = cleanPublicKey($vRawPubkey); + + if (empty($vPubkey) || empty($vSig) || empty($vBody)) { + $error = 'Public key, signature, and message body are required.'; + } + + if (!$error) { + $sigValid = verifySignature($vBody, $vSig, $vPubkey); + + $hashCheck = null; + $icoCheck = null; + $icoGenerated = null; + + if (!empty($vUser) && !empty($vEmail)) { + $expectedInput = $vUser . '|' . $vEmail . '|' . $vPubkey; + $expectedHash = hash('sha256', $expectedInput); + $hashCheck = $expectedHash; + + $icoGenerated = generateIdenticonBase64($expectedInput, 48); + + if (!empty($vFace) && $icoGenerated) { + $cleanFace = trim(preg_replace('/^Face:\s*/m', '', $vFace)); + $cleanFace = preg_replace('/\s+/', '', $cleanFace); + $icoCheck = ($cleanFace === $icoGenerated); + } + } + + $verifyRes = [ + 'sig_valid' => $sigValid, + 'hash_expected' => $hashCheck, + 'ico_match' => $icoCheck, + 'ico_generated' => $icoGenerated, + ]; + } + + // ---- TAB: ANALYZE ---- + } elseif ($tab === 'analyze') { + if (!isset($_FILES['identicon_file']) || $_FILES['identicon_file']['error'] !== UPLOAD_ERR_OK) { + $uploadErr = $_FILES['identicon_file']['error'] ?? UPLOAD_ERR_NO_FILE; + $error = match ($uploadErr) { + UPLOAD_ERR_NO_FILE => 'No file uploaded.', + UPLOAD_ERR_INI_SIZE, + UPLOAD_ERR_FORM_SIZE => 'File too large.', + default => 'Upload failed (error code: ' . $uploadErr . ').', + }; + } else { + $file = $_FILES['identicon_file']; + if ($file['size'] > MAX_UPLOAD_SIZE) { + $error = 'File exceeds maximum size of ' . (MAX_UPLOAD_SIZE / 1024) . ' KB.'; + } elseif ($file['size'] === 0) { + $error = 'Uploaded file is empty.'; + } else { + $finfo = new finfo(FILEINFO_MIME_TYPE); + $mime = $finfo->file($file['tmp_name']); + if ($mime !== 'image/png') { + $error = 'Only PNG files are accepted (detected: ' . htmlspecialchars($mime) . ').'; + } else { + $anaResult = analyzeIdenticon($file['tmp_name']); + if (!empty($anaResult['errors'])) { + $error = implode(' ', $anaResult['errors']); + } + + // Optional: compare with identity + $anaUser = sanitize($_POST['ana_username'] ?? '', 64); + $anaEmail = sanitize($_POST['ana_email'] ?? '', 254); + $anaRawKey = sanitize($_POST['ana_pubkey'] ?? '', 8192); + $anaKey = cleanPublicKey($anaRawKey); + + if (!empty($anaUser) && !empty($anaEmail) && !empty($anaKey) && $anaResult && !$error) { + $compareInput = $anaUser . '|' . $anaEmail . '|' . $anaKey; + $compareHash = hash('sha256', $compareInput); + + // Read raw uploaded file and base64 it + $uploadedB64 = base64_encode(file_get_contents($file['tmp_name'])); + + // Generate at both sizes and try matching + $compareIco48 = generateIdenticonBase64($compareInput, 48); + $compareIco256 = generateIdenticonBase64($compareInput, 256); + + $match = ($compareIco48 && $uploadedB64 === $compareIco48) || + ($compareIco256 && $uploadedB64 === $compareIco256); + + $dims = explode('x', $anaResult['dimensions'] ?? '48x48'); + $showSize = (intval($dims[0]) > 48) ? 256 : 48; + $showIco = ($showSize === 256) ? $compareIco256 : $compareIco48; + + $anaResult['comparison'] = [ + 'hash' => $compareHash, + 'generated' => $showIco, + 'match' => $match, + ]; + } + } + } + } + } +} + +// Preserve form values +$fUser = htmlspecialchars($_POST['username'] ?? '', ENT_QUOTES, 'UTF-8'); +$fEmail = htmlspecialchars($_POST['email'] ?? '', ENT_QUOTES, 'UTF-8'); +$fPubkey = htmlspecialchars($_POST['pubkey'] ?? '', ENT_QUOTES, 'UTF-8'); +$fKeyMode = $_POST['key_mode'] ?? 'generate'; +?> + + + + + + +VFACE — Your Identity, Cryptographically Yours + + + +
+ +
+

VFACE

+
Your pseudonym, cryptographically yours.
+
+ VFACE gives you a persistent identity that nobody can steal or fake. + Pick a name, get a unique visual fingerprint tied to your key. + Anyone can verify it's really you — without knowing who you are. +
+ How does it work? Read the full explanation → +
+ Reset +
+ +
+ + + +
+ + +
+ + + +
+
+ + +
+ + +
+ + +
+ +
+ + +
Use something@example.invalid if you want a fictional address.
+
+ + +
+ + +
+ + +
+ + +
+ Works with any key type or format: Ed25519, RSA, PEM, SSH, hex strings. + Headers like -----BEGIN PUBLIC KEY----- and SSH prefixes + are stripped automatically.
+ YubiKey users: paste your public key — the private key + stays in the hardware. This is the recommended setup. + Compatible with yubicrypt + and yubisigner. +
+
+ +
+
+ A fresh Ed25519 key pair will be generated for you. + You'll download it as a JSON file — keep it safe, it's your identity. +
+
+ + +
+ + + +
+

Your VFACE Identity

+ +
+
+ Your identicon +
This is your face.
+
+
+ +
+ Name + +
+
+ Email + +
+
+ Public Key + +
+ + +
+ Your key was normalized — headers, prefixes, and formatting were removed. + The cleaned version above is what defines your identity. + Always use this exact value when verifying. +
+ +
+ Identity Hash + +
+ + +
+ Save your key pair now. This is the only time you'll see your private key. + Download the file below and store it somewhere safe. If you lose it, this identity is gone forever. + Nobody — including this server — has a copy. +
+ +
+
 'Ed25519',
+                'publicKey' => $result['keypair']['publicKey'],
+                'secretKey' => $result['keypair']['secretKey'],
+                'created'   => $result['keypair']['created'],
+                'username'  => $result['username'],
+                'email'     => $result['email'],
+                'identityHash' => $result['hash'],
+            ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)) ?>
+ +
+ + +

VFACE Headers

+
+ Copy these into your Usenet/email client. + Replace the signature line after signing your message body. +
+
+
+ +
+ +

What's next?

+
+
+
1
+
+ Save your key pair (if generated above). +
Store the JSON file on an encrypted drive. Consider a YubiKey for maximum security.
+
+
+
+
2
+
+ Publish your identity page. +
Host the static HTML page below on your website or .onion service. It's your public identity card.
+
+
+
+
3
+
+ Timestamp it on Bitcoin. +
Run ots stamp identity-page.html to anchor your identity on the blockchain. + This proves when you first claimed this name — nobody can backdate a fake.
+
+
+
+
4
+
+ Start signing messages. +
Use your private key to sign every message you send. Recipients verify with your public key.
+
+
+
+ +

Download Identity Page

+
+ A ready-to-publish static HTML page. Upload it to your web server + and timestamp it with OpenTimestamps for first-claim proof. +
+
+
+ +
+ + +
+ +
+
+ + +
+
+
+ Upload an identicon image to check if it's a valid + Ch1ffr3punk + identicon. Optionally enter identity details to check if the image belongs + to a specific identity. +
+ +
+ + + +
+ +
+ +
+
Any square PNG: 48×48, 256×256, etc. Max KB.
+
+ +
+
+ Optional — enter identity details to check if this identicon belongs to a specific person. +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + +
+

Analysis

+ + = 50) { + $verdictClass = 'unknown'; + $verdictText = 'Partial match — some identicon properties detected (score: ' . $score . '/100)'; + } else { + $verdictClass = 'fail'; + $verdictText = 'Not a valid Ch1ffr3punk identicon (score: ' . $score . '/100)'; + } + ?> + +
+ + +
+ Uploaded image +
+ + +
    +
  • + Horizontal symmetry (5×5 grid, mirrored columns) +
  • +
  • + Background: +
  • +
  • + Primary color: + + + (palette index ) + + no match in primary palette + +
  • +
  • + Secondary color: + + + (palette index ) + + no match in secondary palette + +
  • +
+ +
+ Dimensions + +
+
+ Cell Size + px +
+
+ Unique Colors + +
+ + +
+

Identity Comparison

+ +
This identicon matches the identity provided.
+ +
This identicon does NOT match the identity provided.
+ +
+ Identity Hash + +
+ +
+
+ Uploaded +
Uploaded
+
+
+ Expected +
Expected
+
+
+ +
+ +
+ +
+
+ + +
+
+
+ Advanced — verify an Ed25519 signed message. + Paste the details from the message headers to check if the signature is genuine. +
+ +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ Optional — add identity details for full VFACE verification. +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ + +
+

Verification Result

+ + +
Signature is valid — this message is authentic.
+ +
Signature is INVALID — this message may be forged.
+ + + +
+ Identity Hash + +
+ + + + +
Face header matches the expected identicon.
+ +
Face header does NOT match — possible impersonation.
+ + + + +
+ Expected identicon +
Expected identicon for this identity
+
+ +
+ +
+
+ + + +
+ + + + -- cgit v1.2.3