Without a signature, one line in a server’s hosts file is enough to answer “licence valid” in our place. Every response carries an Ed25519 signature over the canonical form of the data — keys sorted, compact JSON. Verify it before you trust anything. The library does it for you, including on the cached copy, which is also bound to its host so it cannot be copied from one site to another.
Only needed if you call the API directly. Rebuild the canonical form from the parsed object — never from the raw bytes you received, which may differ — then verify the detached Ed25519 signature against our public key.
Ed25519, raw 32 bytes in base64. It is the same for every author and every product.
/PTkKQpweo9XCLOS0+b0vgrecx7Ly/SsAuwTiA737Tw=
The signature covers the donnees object serialised by these four rules, and nothing else. Get one of them wrong and every signature will look invalid.
For the response above, the exact bytes that were signed:
{"domaine":"exemple.fr","emis":1790687453,"essai":false,"hote":"boutique.exemple.fr","motif":"","ok":true,"quand":"","revalider":1790773853,"sursis":2592000}
function signature_valide(array $donnees, $signature, $publique)
{
ksort($donnees);
$corps = json_encode($donnees, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return sodium_crypto_sign_verify_detached(
base64_decode($signature, true),
$corps,
base64_decode($publique, true)
);
}
import { createPublicKey, verify } from 'node:crypto';
const publique = createPublicKey({
key: Buffer.concat([Buffer.from('302a300506032b6570032100', 'hex'),
Buffer.from('/PTkKQpweo9XCLOS0+b0vgrecx7Ly/SsAuwTiA737Tw=', 'base64')]),
format: 'der',
type: 'spki',
});
const canonique = (d) =>
JSON.stringify(Object.fromEntries(Object.keys(d).sort().map((k) => [k, d[k]])));
const valide = verify(null, Buffer.from(canonique(rep.donnees), 'utf8'),
publique, Buffer.from(rep.signature, 'base64'));
import base64, json
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
publique = Ed25519PublicKey.from_public_bytes(base64.b64decode('/PTkKQpweo9XCLOS0+b0vgrecx7Ly/SsAuwTiA737Tw='))
canonique = json.dumps(rep['donnees'], sort_keys=True,
separators=(',', ':'), ensure_ascii=False)
publique.verify(base64.b64decode(rep['signature']), canonique.encode('utf-8'))
Write to us from your author area. A question that needed asking usually means this page is missing a paragraph.