Used by the Kioubit Autopeering System (dn42.g-load.eu)
The Kioubit Verify Services allow you to verify that a user is the holder of an ASN based on the authentication methods the user has provided to the dn42 registry.
Place this button wherever you want users to authenticate.
<script src="kioubit-auth-button.js"></script>
<kioubit-auth-btn return="http://example.org/?t=1234" token=""></kioubit-auth-btn>

https://example.org/ with your website
where the verification code from Step 2 is hosted.token parameter can be used for
user-defined data.
<?php

// Get parameters and signature
$params = filter_var($_GET["params"], FILTER_SANITIZE_URL);
$signature = filter_var($_GET["signature"], FILTER_SANITIZE_URL);

// Signature Verification
$public_key_pem = file_get_contents('public_key.pem');
$r = openssl_verify($params, base64_decode($signature), $public_key_pem, OPENSSL_ALGO_SHA512);
if ($r != 1) {
 die("Could not verify signature");
}

// Decode parameters to json
$params = base64_decode($params);
$info = json_decode($params, true);

// Check that the request is for our own domain
if ("owndomain.com" != $info["domain"]) {
 die("The request is for a different domain");
}

// Check the time of the request
if (abs($info["time"] - time()) > 60) {
 die("The request has expired");
}

// Print json object
echo nl2br(print_r($info, true));

?>
#!/usr/bin/env python3

import base64
import os
import json
import time
# Requires pyca/cryptography (cryptography==43.0.1)
from cryptography import x509
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.exceptions import InvalidSignature


class AuthVerifier():
 PUBKEY_FILE = os.path.dirname(__file__)+"/public_key.pem"
 __domain: str = ""
 __pubKey: ec.EllipticCurvePublicKey | None = None

 def __init__(self, domain: str, pubkey: str = PUBKEY_FILE):
 self.__domain = domain
 with open(pubkey, "br") as pk:
 pk_content = pk.read()
 self.__pubKey = serialization.load_pem_public_key(pk_content)

 def verify(self, params: str, signature: str) -> tuple[bool, dict[str, any] | str]:
 try:
 sig = base64.b64decode(signature)
 except base64.binascii.Error:
 return False, "Invalid base64 encoding for 'signature'"
 try:
 self.__pubKey.verify(sig, params.encode(), ec.ECDSA(hashes.SHA512()))
 except InvalidSignature:
 return False, "Invalid signature"

 try:
 params_decoded = base64.b64decode(params)
 except base64.binascii.Error:
 return False, "Invalid base64 encoding for 'params'"

 try:
 user_data = json.loads(params_decoded)
 if abs(time.time() - user_data["time"]) > 60:
 return False, "Signature has expired"
 if user_data["domain"] != self.__domain.replace("https://",""):
 return False, "Invalid domain"
 except json.decoder.JSONDecodeError:
 return False, "Invalid user data JSON"
 except KeyError:
 return False, "Required value not found in JSON"
 return True, user_data


if __name__ == "__main__":
 test_params="eyJhc24iOiI0MjQyNDIzMDM1IiwidGltZSI6MTY2ODI2NjkyNiwiYWxsb3dlZDQiOiIxNzIuMjIuMTI1LjEyOFwvMjYsMTcyLjIwLjAuODFcLzMyIiwiYWxsb3dlZDYiOiJmZDYzOjVkNDA6NDdlNTo6XC80OCxmZDQyOmQ0MjpkNDI6ODE6OlwvNjQiLCJtbnQiOiJMQVJFLU1OVCIsImF1dGh0eXBlIjoibG9naW5jb2RlIiwiZG9tYWluIjoic3ZjLmJ1cmJsZS5kbjQyIn0="
 test_signature="MIGIAkIBAmwz3sQ1vOkH8+8e0NJ8GsUqKSaazIWmYDp60sshlTo7gCAopZOZ6/+tD6s+oEGM1i5mKGbHgK9ROATQLHxUZecCQgCa2N828uNn76z1Yg63/c7veMVIiK4l1X9TCUepJnJ3mCto+7ogCP+2vQm6GHipSNRF4wnt6tZbir0HZvrqEnRAmA=="
 
 from unittest import mock
 example_verifier = AuthVerifier("svc.burble.dn42")

 @mock.patch('time.time', mock.MagicMock(return_value=1668266926))
 def test_with_correct_time():
 result, user_data = example_verifier.verify(params=test_params, signature=test_signature)
 print(user_data)
 assert result == True
 
 @mock.patch('time.time', mock.MagicMock(return_value=1668266910))
 def test_with_backwards_time():
 result, _ = example_verifier.verify(params=test_params, signature=test_signature)
 assert result == True

 @mock.patch('time.time', mock.MagicMock(return_value=1668266000))
 def test_with_expired_time():
 result, _ = example_verifier.verify(params=test_params, signature=test_signature)
 assert result == False

 test_with_correct_time()
 test_with_backwards_time()
 test_with_expired_time()
package main

import (
	"crypto/ecdsa"
	"crypto/sha512"
	"crypto/x509"
	_ "embed"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"math"
	"time"
)

//go:embed public_key.pem
var pemPubKey []byte

const myDomain = "svc.burble.dn42"

func VerifyAuthToken(signature, params string) (user_data map[string]any, err error) {
	// Read public key
	blockPub, _ := pem.Decode(pemPubKey)
	if blockPub == nil {
		return nil, errors.New("failed to decode PEM block")
	}
	x509EncodedPub := blockPub.Bytes
	genericPublicKey, err := x509.ParsePKIXPublicKey(x509EncodedPub)
	if err != nil {
		return nil, errors.New("internal server error")
	}
	publicKey := genericPublicKey.(*ecdsa.PublicKey)

	// Hash parameters
	hash := sha512.Sum512([]byte(params))

	// Decode base64 signature
	signatureBytes, err := base64.StdEncoding.DecodeString(signature)
	if err != nil {
		return nil, errors.New("failed to decode signature")
	}

	// Verify signature
	if !ecdsa.VerifyASN1(publicKey, hash[:], signatureBytes) {
		return nil, errors.New("invalid signature")
	}

	// Decode parameters
	parameterBytes, err := base64.StdEncoding.DecodeString(params)
	if err != nil {
		return nil, fmt.Errorf("failed decoding verified parameters: %w", err)
	}

	err = json.Unmarshal(parameterBytes, &user_data)
	if err != nil {
		return nil, fmt.Errorf("failed unmarshaling verified parameters: %w", err)
	}

	if math.Abs(user_data["time"].(float64)-float64(time.Now().Unix())) > 60 {
		return nil, errors.New("the request has expired")
	}

	if user_data["domain"].(string) != myDomain {
		return nil, errors.New("this request is for a different domain")
	}

	return
}

func main() {
	var test_params = "eyJhc24iOiI0MjQyNDIzMDM1IiwidGltZSI6MTY2ODI2NjkyNiwiYWxsb3dlZDQiOiIxNzIuMjIuMTI1LjEyOFwvMjYsMTcyLjIwLjAuODFcLzMyIiwiYWxsb3dlZDYiOiJmZDYzOjVkNDA6NDdlNTo6XC80OCxmZDQyOmQ0MjpkNDI6ODE6OlwvNjQiLCJtbnQiOiJMQVJFLU1OVCIsImF1dGh0eXBlIjoibG9naW5jb2RlIiwiZG9tYWluIjoic3ZjLmJ1cmJsZS5kbjQyIn0="
	var test_signature = "MIGIAkIBAmwz3sQ1vOkH8+8e0NJ8GsUqKSaazIWmYDp60sshlTo7gCAopZOZ6/+tD6s+oEGM1i5mKGbHgK9ROATQLHxUZecCQgCa2N828uNn76z1Yg63/c7veMVIiK4l1X9TCUepJnJ3mCto+7ogCP+2vQm6GHipSNRF4wnt6tZbir0HZvrqEnRAmA=="
	fmt.Println(VerifyAuthToken(test_signature, test_params))
}

import { readFileSync } from 'fs';
import { createPublicKey, createVerify } from 'crypto';

class KioubitAuthVerifier {
 static #PUBKEY_FILE = "public_key.pem";
 #pubKey;
 #domain;

 constructor(domain, pubkey = this.constructor.#PUBKEY_FILE) {
 this.#domain = domain;

 const pubKeyContent = readFileSync(pubkey);
 this.#pubKey = createPublicKey(pubKeyContent);
 }

 verify(params, signature) {
 let sig;
 try {
 sig = Buffer.from(signature, 'base64');
 } catch (e) {
 return [false, "Invalid base64 encoding for 'signature'"];
 }

 // Verify the signature
 try {
 const sigVerify = createVerify('SHA512');
 sigVerify.update(Buffer.from(params, 'utf8'));
 sigVerify.end();

 const isValid = sigVerify.verify(this.#pubKey, sig);
 if (!isValid) {
 return [false, "Invalid signature"];
 }
 } catch (e) {
 return [false, "Error verifying the signature"];
 }

 // Decode the params
 let user_data;
 try {
 const paramsDecoded = Buffer.from(params, 'base64');
 user_data = JSON.parse(paramsDecoded.toString('utf8'));
 } catch (e) {
 return [false, "Invalid base64 encoding for 'params'"];
 }

 // Check user data validity
 if (Math.abs(Date.now() / 1000 - user_data.time) > 60) {
 return [false, "Signature has expired"];
 }

 if (user_data.domain !== this.#domain) {
 return [false, "Request is for a different domain"];
 }

 return [true, user_data];
 }
}

const testParams = "eyJhc24iOiI0MjQyNDIzMDM1IiwidGltZSI6MTY2ODI2NjkyNiwiYWxsb3dlZDQiOiIxNzIuMjIuMTI1LjEyOFwvMjYsMTcyLjIwLjAuODFcLzMyIiwiYWxsb3dlZDYiOiJmZDYzOjVkNDA6NDdlNTo6XC80OCxmZDQyOmQ0MjpkNDI6ODE6OlwvNjQiLCJtbnQiOiJMQVJFLU1OVCIsImF1dGh0eXBlIjoibG9naW5jb2RlIiwiZG9tYWluIjoic3ZjLmJ1cmJsZS5kbjQyIn0=";
const testSignature = "MIGIAkIBAmwz3sQ1vOkH8+8e0NJ8GsUqKSaazIWmYDp60sshlTo7gCAopZOZ6/+tD6s+oEGM1i5mKGbHgK9ROATQLHxUZecCQgCa2N828uNn76z1Yg63/c7veMVIiK4l1X9TCUepJnJ3mCto+7ogCP+2vQm6GHipSNRF4wnt6tZbir0HZvrqEnRAmA==";
const exampleVerifier = new KioubitAuthVerifier("svc.burble.dn42");

function testWithCorrectTime() {
 // Mock current time for testing
 const originalDateNow = Date.now;
 Date.now = () => 1668266926 * 1000;

 const [result, userData] = exampleVerifier.verify(
 testParams,
 testSignature
 );
 console.assert(result === true, "Should be valid with correct time");
 console.log(userData);

 // Restore original Date.now
 Date.now = originalDateNow;
}

testWithCorrectTime();
Step 1 — Add this HTML page to your website:
<!DOCTYPE html>
<html>
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Authenticate with Kioubit.dn42</title>
 <script src="https://telegram.org/js/telegram-web-app.js"></script>
 <script src="kioubit-auth-button.js"></script>
 <style>
 body { font-family: sans-serif; text-align: center; padding-top: 50px; }
 .loading { display: none; color: #666; }
 kioubit-auth-btn { display: block; width: fit-content; margin: 0 auto; }
 </style>
</head>
<body>
 <div id="ui">
 <kioubit-auth-btn return="" token=""></kioubit-auth-btn>
 </div>

 <div id="loader" class="loading">
 <p>Sending data back to bot...</p>
 </div>

 <script>
 const tg = window.Telegram.WebApp;
 tg.ready();

 const urlParams = new URLSearchParams(window.location.search);

 if (urlParams.has('params')) {
 document.getElementById('ui').style.display = 'none';
 document.getElementById('loader').style.display = 'block';

 // Prepare data object
 const result = {};
 urlParams.forEach((value, key) => result[key] = value);

 // Send data back to the telegram bot
 // This closes the Mini App and triggers the 'web_app_data' event in the bot
 tg.sendData(JSON.stringify(result));
 }
 </script>
</body>
</html>

Step 2 — Add this logic to your bot:
const { Telegraf, Markup } = require('telegraf');

// 1. Paste your token from @BotFather here
const bot = new Telegraf('');

// 2. Paste the URL where you hosted your index
const APP_URL = 'https://example.org/temp/telegram.html';

bot.command('verify', (ctx) => {
 console.log(ctx);
 ctx.reply('Open the app to verify:',
 Markup.keyboard([
 Markup.button.webApp('🚀 Start Verification', APP_URL)
 ]).resize()
 );
});

bot.on('web_app_data', (ctx) => {
 try {
 // Get the raw string from the Telegram message
 const rawJsonString = ctx.message.web_app_data.data;

 // Parse the outer JSON (contains "params" and "signature")
 const envelope = JSON.parse(rawJsonString);

 // Decode the "params" field from Base64 to a UTF-8 string
 const decodedString = Buffer.from(envelope.params, 'base64').toString('utf-8');

 // Parse the actual Kioubit data
 const data = JSON.parse(decodedString);

 // Important: Verify the data here, using the Node.JS method

 // Send the message to the chat
 const report = `
✅ **ASN Verification Received**
• **ASN:** ${data.asn}
• **Maintainer:** ${data.effective_mnt}
• **Method:** ${data.authtype}
• **Domain:** ${data.domain}
 `;

 console.log(report);
 ctx.replyWithMarkdown(report);

 } catch (e) {
 console.error("Failed to process data:", e);
 ctx.reply("Error: Data was received but could not be decoded. Check console.");
 }
});

bot.launch();

// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'));
process.once('SIGTERM', () => bot.stop('SIGTERM'));
console.log('Bot is running...');

Download the public_key.pem and place it in the same directory as your verification code.
$ openssl pkey -pubin -in public_key.pem -text -noout
Public-Key: (521 bit)
pub:
 04:00:e0:30:8e:95:1e:20:f7:8b:9d:2a:fb:bd:fb:
 32:9b:c2:ac:55:fe:b8:e0:ee:0f:c8:8f:dd:53:9c:
 4a:94:84:80:62:dc:b0:63:06:8c:a4:71:21:67:58:
 1b:57:82:79:ab:8f:45:b1:06:de:2f:02:3e:d8:3b:
 98:c2:41:92:4c:55:e2:00:2c:27:12:16:49:f8:e3:
 b6:ec:a7:a3:5d:43:3c:53:31:0f:14:61:33:d7:fe:
 0e:c0:cf:11:03:39:5b:57:7d:20:68:fc:18:99:17:
 d2:d8:3e:69:b4:cf:09:3e:cb:b6:66:47:c2:9f:d3:
 68:44:b4:96:db:8c:df:c6:a0:3e:8c:ab:57
ASN1 OID: secp521r1
NIST CURVE: P-521
{
 "asn": "4242423914",
 "time": 1726845156,
 "allowed4": [
 "172.20.14.32/27",
 "172.20.53.96/27"
 ],
 "allowed6": [
 "fdcf:8538:9ad5::/48",
 "fdfc:e23f:fb45:3234::/64"
 ],
 "mnt": [
 "KIOUBIT-MNT"
 ],
 "effective_mnt": "KIOUBIT-MNT",
 "domain": "dn42.g-load.eu",
 "authtype": "email",
 "user_token": ""
}
asn
The autonomous system number
time
Unix timestamp of the authentication
allowed4 / allowed6
Owned prefixes for all maintainers of an
ASN
mnt
Array of all maintainers of an ASN
effective_mnt
The maintainer that authenticated
domain
The domain the user intends to authenticate
for
authtype
The authentication method used
user_token
Optional user-provided token (max 256
characters)