---
title: "C signature verification"
description: "Run the complete C Ed25519 verifier against KDPika's real signed JSON and positive, tampered, malformed and expired test cases."
slug: "/docs/v1/security/verify-ed25519/c"
section: "Signature examples"
order: 213
audience: "all"
level: "advanced"
last_updated: "2026-10-17"
version: "v1"
related: ["/docs/v1/security/signed-responses","/docs/v1/security/test-vectors"]
draft: false
---
Verify KDPika payload authenticity in C without losing the server's signed numeric tokens.

## What you will learn
- Build and run the complete C verifier.
- Reject altered, ambiguous and untrusted envelopes.
- Keep signature checking separate from permission to use a [license](/docs/v1/start/glossary#license).

## Prerequisites
- Read [signed responses](/docs/v1/security/signed-responses).
- Install C11 compiler; yyjson 0.13.0; libsodium 1.0.20.
- Download the [complete verification source archive](/docs/downloads/security-verifiers.zip) and extract it.
- Open a terminal in the extracted security directory; all paths below are relative to it.
- Use Python 3.9+ only for the optional shared test runner, not as a dependency of this verifier.

## Install and build
```bash
mkdir -p dependencies build
curl -fSL https://raw.githubusercontent.com/ibireme/yyjson/0.13.0/src/yyjson.h -o dependencies/yyjson.h
curl -fSL https://raw.githubusercontent.com/ibireme/yyjson/0.13.0/src/yyjson.c -o dependencies/yyjson.c
cc -std=c11 -O2 -Idependencies $(pkg-config --cflags libsodium) c/verify.c dependencies/yyjson.c $(pkg-config --libs libsodium) -o build/c-verify
```
Expected result: dependencies resolve and the build exits successfully. Version commands print the installed runtime. Use a maintained runtime compatible with the minimum; these are tested compatibility floors, not claims of ongoing vendor support.

Install libsodium through the operating system's package tooling; it is a client development dependency, not a requirement on KDPika shared hosting. Windows needs a matching libsodium library and compiler include/link paths. Linux uses its own library/package paths. macOS Homebrew may require PKG_CONFIG_PATH pointing at libsodium's lib/pkgconfig directory. Native builds were executed on macOS only.

## Run the positive and negative cases
```bash
./build/c-verify vectors/positive.key.json vectors/positive.envelope.json
./build/c-verify vectors/tampered.key.json vectors/tampered.envelope.json
./build/c-verify vectors/expired.key.json vectors/expired.envelope.json
python3 selftest.py --command ./build/c-verify
```
Expected output for the first three commands is signature_valid=true, signature_valid=false and signature_valid=true. The tampered command exits 1 intentionally; run commands separately rather than stopping a batch at that expected refusal. The shared runner prints PASS23 and exits 0 when every expected acceptance/refusal matches.

Expired remains cryptographically authentic but must not unlock access after its expiry. This program returns signature validity only. It does not replace a complete license client, network transport, offline policy or the [official SDK](/docs/v1/start/glossary#official-sdk).

## Complete source
```c
/* Lossless KDPika JSON canonicalizer. C11+, yyjson 0.13.0, libsodium 1.0.20.
 * Shared by C and C++ verifiers. No SDK or server behavior changes.
 * All failures reject; input <=1 MiB, depth <=64, output <=4 MiB. */
#ifndef KDPIKA_CANONICAL_H
#define KDPIKA_CANONICAL_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sodium.h>
#include "yyjson.h"
typedef struct { char *data; size_t len,cap; int ok; } Output;
typedef struct { yyjson_val *key,*value; } Pair;
static void append(Output *out,const char *text,size_t len) {
    if(!out->ok || len>4194304-out->len) { out->ok=0; return; }
    if(out->len+len+1>out->cap) {
        size_t cap=(out->len+len+1)*2; char *next=(char*)realloc(out->data,cap);
        if(!next) { out->ok=0; return; } out->data=next;out->cap=cap;
    }
    memcpy(out->data+out->len,text,len);out->len+=len;out->data[out->len]=0;
}
static void literal(Output *out,const char *text) { append(out,text,strlen(text)); }
static void quote(Output *out,const char *text,size_t len) {
    literal(out,"\"");
    for(size_t i=0;i<len;i++) {
        unsigned char c=(unsigned char)text[i];
        switch(c) {
        case '"':literal(out,"\\\"");break;case '\\':literal(out,"\\\\");break;
        case 8:literal(out,"\\b");break;case 9:literal(out,"\\t");break;
        case 10:literal(out,"\\n");break;case 12:literal(out,"\\f");break;case 13:literal(out,"\\r");break;
        default:
            if(c<32) { char escaped[7];snprintf(escaped,sizeof escaped,"\\u%04x",c);literal(out,escaped); }
            else if(c==0xe2 && i+2<len && (unsigned char)text[i+1]==0x80 && ((unsigned char)text[i+2]==0xa8 || (unsigned char)text[i+2]==0xa9)) {
                literal(out,(unsigned char)text[i+2]==0xa8?"\\u2028":"\\u2029");i+=2;
            } else append(out,text+i,1);
        }
    }
    literal(out,"\"");
}
static int pair_compare(const void *a,const void *b) {
    const Pair *x=(const Pair*)a,*y=(const Pair*)b;
    size_t nx=yyjson_get_len(x->key),ny=yyjson_get_len(y->key),n=nx<ny?nx:ny;
    int cmp=memcmp(yyjson_get_str(x->key),yyjson_get_str(y->key),n);
    return cmp?cmp:(nx>ny)-(nx<ny);
}
static void canonical(Output *out,yyjson_val *value,unsigned depth) {
    if(depth>64 || !value) { out->ok=0;return; }
    if(yyjson_is_obj(value)) {
        size_t count=yyjson_obj_size(value),i=0;Pair *pairs=(Pair*)calloc(count?count:1,sizeof(Pair));
        if(!pairs) { out->ok=0;return; }
        yyjson_obj_iter iter=yyjson_obj_iter_with(value);yyjson_val *key;
        while((key=yyjson_obj_iter_next(&iter))) { pairs[i].key=key;pairs[i++].value=yyjson_obj_iter_get_val(key); }
        qsort(pairs,count,sizeof(Pair),pair_compare);literal(out,"{");
        for(i=0;i<count;i++) {
            if(i) { if(pair_compare(&pairs[i-1],&pairs[i])==0) { out->ok=0;break; } literal(out,","); }
            quote(out,yyjson_get_str(pairs[i].key),yyjson_get_len(pairs[i].key));literal(out,":");canonical(out,pairs[i].value,depth+1);
        }
        literal(out,"}");free(pairs);
    } else if(yyjson_is_arr(value)) {
        size_t i,max;yyjson_val *child;literal(out,"[");
        yyjson_arr_foreach(value,i,max,child) { if(i) literal(out,",");canonical(out,child,depth+1); }literal(out,"]");
    } else if(yyjson_is_str(value)) quote(out,yyjson_get_str(value),yyjson_get_len(value));
    else if(yyjson_is_raw(value)) append(out,yyjson_get_raw(value),yyjson_get_len(value));
    else if(yyjson_is_null(value)) literal(out,"null");
    else if(yyjson_is_bool(value)) literal(out,yyjson_get_bool(value)?"true":"false");
    else out->ok=0;
}
static yyjson_doc *parse(const char *raw,size_t size) {
    if(size>1048576) return NULL;
    yyjson_doc *doc=yyjson_read(raw,size,YYJSON_READ_NUMBER_AS_RAW);
    if(!doc) return NULL;Output test={NULL,0,0,1};canonical(&test,yyjson_doc_get_root(doc),0);free(test.data);
    if(!test.ok) { yyjson_doc_free(doc);return NULL; }return doc;
}
static int equals(yyjson_val *object,const char *name,const char *expected) {
    yyjson_val *value=yyjson_obj_get(object,name);
    return yyjson_is_str(value) && yyjson_get_len(value)==strlen(expected) && memcmp(yyjson_get_str(value),expected,strlen(expected))==0;
}
static int decode(yyjson_val *value,unsigned char *out,size_t size) {
    if(!yyjson_is_str(value)) return 0;size_t written=0;const char *end=NULL;char encoded[128];
    if(sodium_base642bin(out,size,yyjson_get_str(value),yyjson_get_len(value),NULL,&written,&end,sodium_base64_VARIANT_URLSAFE_NO_PADDING)!=0 || written!=size || end!=yyjson_get_str(value)+yyjson_get_len(value)) return 0;
    sodium_bin2base64(encoded,sizeof encoded,out,size,sodium_base64_VARIANT_URLSAFE_NO_PADDING);
    return yyjson_get_len(value)==strlen(encoded) && memcmp(encoded,yyjson_get_str(value),strlen(encoded))==0;
}
static int verify_envelope(const char *key_raw,size_t key_size,const char *env_raw,size_t env_size) {
    if(sodium_init()<0) return 0;
    yyjson_doc *kd=parse(key_raw,key_size),*ed=parse(env_raw,env_size);int valid=0;
    if(kd && ed) {
        yyjson_val *key=yyjson_doc_get_root(kd),*env=yyjson_doc_get_root(ed);unsigned char pub[32],sig[64],hash[32];char hex[65],id[25];
        if(decode(yyjson_obj_get(key,"public_key"),pub,32)) {
            crypto_hash_sha256(hash,pub,32);sodium_bin2hex(hex,sizeof hex,hash,32);snprintf(id,sizeof id,"ed25519_%.16s",hex);
            yyjson_val *payload=yyjson_obj_get(env,"payload");
            if(equals(key,"algorithm","Ed25519") && equals(key,"encoding","base64url") && equals(key,"key_id",id) && equals(env,"algorithm","Ed25519") && equals(env,"key_id",id) && yyjson_is_obj(payload) && decode(yyjson_obj_get(env,"signature"),sig,64)) {
                Output message={NULL,0,0,1};canonical(&message,payload,0);
                valid=message.ok && crypto_sign_verify_detached(sig,(const unsigned char*)message.data,message.len,pub)==0;free(message.data);
            }
        }
    }
    yyjson_doc_free(kd);yyjson_doc_free(ed);return valid;
}
#endif
```

```c
/* KDPika signature authenticity only. C11+, yyjson 0.13.0, libsodium 1.0.20.
 * Run compiled verifier with key.json envelope.json. signature_valid=true/false.
 * Trust the key independently; enforce license policy before allowing access. */
#include "canonical.h"
static char *read_file(const char *path,size_t *size) {
    FILE *file=fopen(path,"rb");if(!file) return NULL;
    char *data=(char*)malloc(1048577);if(!data) { fclose(file);return NULL; }
    *size=fread(data,1,1048577,file);int error=ferror(file);fclose(file);
    if(error || *size>1048576) { free(data);return NULL; }return data;
}
int main(int argc,char **argv) {
    int valid=0;
    if(argc==3) {
        size_t ks=0,es=0;char *key=read_file(argv[1],&ks),*env=read_file(argv[2],&es);
        if(key && env) valid=verify_envelope(key,ks,env,es);free(key);free(env);
    }
    printf("signature_valid=%s\n",valid?"true":"false");return valid?0:1;
}
```

## Expected result
All 23 cases pass in the shared runner, including integral floats, non-BMP key order, Unicode separators, duplicate JSON names, invalid signatures and excessive depth. The original response's numeric tokens are retained, not recalculated from binary floating-point values.

## Common mistakes
- Taking the verification key from the same untrusted message being checked.
- Treating a successful signature as proof of active access, correct installation or recent payment.
- Parsing and reserializing numbers before passing the envelope into this sample.

## Security notes
The key input must come from your trusted pin or deliberate fixed-origin HTTPS trust policy. Public vector keys are test anchors, never production configuration. This sample fails closed on malformed data; never fall back to unverified [features](/docs/v1/start/glossary#feature).

After signature success, check [product](/docs/v1/start/glossary#product), environment, installation hash, active state, start/expiry, application-version policy and signed validation/grace deadlines. See [offline mode](/docs/v1/security/offline-mode). The current official SDKs have documented canonicalization limitations; this independent verifier does not silently repair their caches or alter their public APIs.

## Troubleshooting
| Symptom | Cause | Action |
| --- | --- | --- |
| Positive case fails | Wrong trusted key, changed numeric tokens or missing dependency | Restore the exact archive/vector files and rerun the build |
| Tampered case returns false | Expected rejection | Keep the refusal; do not bypass verification |
| Expired case returns true | Authenticity and expiry are separate | Reject access in the license policy layer |
| Cannot open a file | Terminal is outside the extracted security directory | Change to that directory and retry using quoted paths |

## Next steps
Run [all test vectors](/docs/v1/security/test-vectors) and apply the [signed-response policy checks](/docs/v1/security/signed-responses).

## Related
- [Verification language index](/docs/v1/security/verify-ed25519)
- [Secure token storage](/docs/v1/security/secure-token-storage)
