Opening soon — authors keep 100 % of their price Become an author Sign in Create account
Hello, sign in Account & Lists YourAuthor area

Verifying the signature yourself

1.Why the response is signed

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.

2.Verifying the signature yourself

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.

Our public key

Ed25519, raw 32 bytes in base64. It is the same for every author and every product.

/PTkKQpweo9XCLOS0+b0vgrecx7Ly/SsAuwTiA737Tw=

The canonical form

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.

  1. Keys sorted in ascending order.
  2. No whitespace: no space after the colons or the commas.
  3. Forward slashes are not escaped.
  4. Non-ASCII characters stay as UTF-8, with no backslash-u escaping.

For the response above, the exact bytes that were signed:

utf-8
{"domaine":"exemple.fr","emis":1790687453,"essai":false,"hote":"boutique.exemple.fr","motif":"","ok":true,"quand":"","revalider":1790773853,"sursis":2592000}
PHP PHP
sodium
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)
    );
}
JS Node.js
node:crypto
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'));
PY Python
cryptography
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'))

Something unclear?

Write to us from your author area. A question that needed asking usually means this page is missing a paragraph.

Author area