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 C++17 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
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 -c dependencies/yyjson.c -o build/yyjson.o
c++ -std=c++17 -Idependencies $(pkg-config --cflags libsodium) cpp/verify.cpp build/yyjson.o $(pkg-config --libs libsodium) -o build/cpp-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
./build/cpp-verify vectors/positive.key.json vectors/positive.envelope.json
./build/cpp-verify vectors/tampered.key.json vectors/tampered.envelope.json
./build/cpp-verify vectors/expired.key.json vectors/expired.envelope.json
python3 selftest.py --command ./build/cpp-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
/* 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
// KDPika signature authenticity only. C++17+, yyjson 0.13.0, libsodium 1.0.20.
// Uses complete adjacent C canonical.h. Run verifier key.json envelope.json.
// Output signature_valid=true/false. Trust the key separately; enforce policy next.
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
#include "../c/canonical.h"
static std::string read_file(const char *path) {
std::ifstream file(path,std::ios::binary);if(!file) throw std::runtime_error("Cannot read file");
std::string data;char buffer[8192];
while(file.read(buffer,sizeof buffer) || file.gcount()) {
data.append(buffer,static_cast<size_t>(file.gcount()));if(data.size()>1048576) throw std::runtime_error("JSON exceeds 1 MiB");
}
if(file.bad()) throw std::runtime_error("Cannot read file");return data;
}
int main(int argc,char **argv) {
bool valid=false;
try {
if(argc==3) {
const auto key=read_file(argv[1]),env=read_file(argv[2]);
valid=verify_envelope(key.data(),key.size(),env.data(),env.size());
}
} catch(const std::exception&) { valid=false; }
std::cout<<"signature_valid="<<(valid?"true":"false")<<'\n';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
| 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 and apply the signed-response policy checks.