KDPika Licensing Sign inStart free
API v1
Show all languages
Browse documentation

Choose a page in the navigation drawer.

KDPika Licensing · API v1 Advanced

C signature verification

Run the complete C Ed25519 verifier against KDPika's real signed JSON and positive, tampered, malformed and expired test cases.

On this page
Updated 2026-10-17 UTCDownload Markdown

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.

Prerequisites

  • Read signed responses.
  • Install C11 compiler; yyjson 0.13.0; libsodium 1.0.20.
  • Download the complete verification source archive 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

bashbash
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

bashbash
./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.

Complete source

canonical.hc
/* 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
verify.cc
/* 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.

After signature success, check product, environment, installation hash, active state, start/expiry, application-version policy and signed validation/grace deadlines. See 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

SymptomCauseAction
Positive case failsWrong trusted key, changed numeric tokens or missing dependencyRestore the exact archive/vector files and rerun the build
Tampered case returns falseExpected rejectionKeep the refusal; do not bypass verification
Expired case returns trueAuthenticity and expiry are separateReject access in the license policy layer
Cannot open a fileTerminal is outside the extracted security directoryChange to that directory and retry using quoted paths

Next steps

Run all test vectors and apply the signed-response policy checks.

Was this page helpful?

Choose an answer

Do not include personal data, license keys or credentials. Retained for up to 30 days. This is not a support inbox.

Search documentation

Arrow keys to move, Enter to open, Escape to close. Search stays in your browser.

Cookie settings