All docs

Verifier SDK

Verify Sanad artifacts offline

Reference implementations in JavaScript, Python, and Go. Each verifies a Sanad-signed artifact with no network calls, no external dependencies beyond standard libraries (Go) or one well-known crypto library (Python).

For one-off verifications without setting up an SDK, use the browser verifier — paste the artifact JSON, click verify. Same canonical-JSON algorithm.

Artifact shape

Every signed Sanad artifact ships exactly three top-level fields:

  • signed_payload — the JSON object that was signed
  • signature — 64-byte Ed25519 signature, hex-encoded
  • public_key — 32-byte raw Ed25519 public key, hex-encoded

The signature is over canonical-JSON: keys sorted lexicographically, no whitespace, standard escaping. Both signer and verifier must produce the same byte string for the same logical object — that's what canonical-JSON guarantees.

JavaScript / Node 18+ (Web Crypto)

// JavaScript / Node — verify a Sanad artifact offline.
import { webcrypto } from "node:crypto"; // Node 18+

function canonical(value) {
  if (value === null || typeof value !== "object") return JSON.stringify(value);
  if (Array.isArray(value)) return "[" + value.map(canonical).join(",") + "]";
  return "{" + Object.keys(value).sort()
    .map(k => JSON.stringify(k) + ":" + canonical(value[k])).join(",") + "}";
}

function hexToBytes(hex) {
  const clean = hex.replace(/\s+/g, "");
  const out = new Uint8Array(clean.length / 2);
  for (let i = 0; i < clean.length; i += 2) out[i / 2] = parseInt(clean.slice(i, i + 2), 16);
  return out;
}

const SPKI_PREFIX = new Uint8Array([
  0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00,
]);

export async function verifySanadArtifact(artifact) {
  const sig = hexToBytes(artifact.signature);
  const raw = hexToBytes(artifact.public_key);
  const spki = new Uint8Array(SPKI_PREFIX.length + raw.length);
  spki.set(SPKI_PREFIX, 0);
  spki.set(raw, SPKI_PREFIX.length);
  const key = await webcrypto.subtle.importKey(
    "spki", spki, { name: "Ed25519" }, true, ["verify"],
  );
  const payload = new TextEncoder().encode(canonical(artifact.signed_payload));
  return webcrypto.subtle.verify("Ed25519", key, sig, payload);
}

// Usage:
const valid = await verifySanadArtifact(JSON.parse(artifactJson));
console.log(valid ? "verifies" : "does NOT verify");

Python 3.10+ (cryptography)

# Python — verify a Sanad artifact offline.
# pip install cryptography
import json
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.exceptions import InvalidSignature

def canonical(value) -> str:
    if value is None or not isinstance(value, (dict, list)):
        return json.dumps(value, separators=(",", ":"))
    if isinstance(value, list):
        return "[" + ",".join(canonical(x) for x in value) + "]"
    keys = sorted(value.keys())
    return "{" + ",".join(json.dumps(k) + ":" + canonical(value[k]) for k in keys) + "}"

def verify_sanad_artifact(artifact: dict) -> bool:
    pub = Ed25519PublicKey.from_public_bytes(bytes.fromhex(artifact["public_key"]))
    sig = bytes.fromhex(artifact["signature"])
    payload = canonical(artifact["signed_payload"]).encode("utf-8")
    try:
        pub.verify(sig, payload)
        return True
    except InvalidSignature:
        return False

# Usage:
with open("artifact.json") as f:
    artifact = json.load(f)
print("verifies" if verify_sanad_artifact(artifact) else "does NOT verify")

Go 1.20+ (standard library only)

// Go — verify a Sanad artifact offline. Standard library only.
package sanadverify

import (
	"crypto/ed25519"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"sort"
	"strings"
)

func canonical(v any) string {
	switch x := v.(type) {
	case nil:
		return "null"
	case bool, float64:
		b, _ := json.Marshal(x)
		return string(b)
	case string:
		b, _ := json.Marshal(x)
		return string(b)
	case []any:
		parts := make([]string, len(x))
		for i, item := range x { parts[i] = canonical(item) }
		return "[" + strings.Join(parts, ",") + "]"
	case map[string]any:
		keys := make([]string, 0, len(x))
		for k := range x { keys = append(keys, k) }
		sort.Strings(keys)
		parts := make([]string, len(keys))
		for i, k := range keys {
			kb, _ := json.Marshal(k)
			parts[i] = string(kb) + ":" + canonical(x[k])
		}
		return "{" + strings.Join(parts, ",") + "}"
	}
	return "null"
}

type Artifact struct {
	SignedPayload any    `json:"signed_payload"`
	Signature     string `json:"signature"`
	PublicKey     string `json:"public_key"`
}

func Verify(a *Artifact) (bool, error) {
	pub, err := hex.DecodeString(a.PublicKey)
	if err != nil { return false, fmt.Errorf("public_key hex: %w", err) }
	if len(pub) != ed25519.PublicKeySize {
		return false, fmt.Errorf("public_key wrong size: %d", len(pub))
	}
	sig, err := hex.DecodeString(a.Signature)
	if err != nil { return false, fmt.Errorf("signature hex: %w", err) }
	payload := []byte(canonical(a.SignedPayload))
	return ed25519.Verify(pub, payload, sig), nil
}

Why offline

  • Regulator audits. An auditor brings their own laptop, copies the artifact, verifies offline. Sanad's servers cannot retroactively change a signature.
  • Air-gapped environments. Government secure labs, defence networks, classified-data clinics — none of these have outbound internet. The Go binary runs with zero dependencies.
  • Cross-jurisdiction verification. An EU auditor, a US insurer, an Indian DPB inspector — all can verify with the same algorithm without any party trusting another.
  • Time-shifted audits. An artifact signed in 2026 can be verified in 2030 even if Sanad no longer exists. The maths doesn't expire.

Open source

The reference implementations above are MIT-licensed. Copy them into your audit toolchain. We'll publish packaged libraries under @sanad/verifier (npm), sanad-verifier (PyPI), and github.com/anupam9091/sanad-verifier-go as the open-source repos mature post first-pilot.

Found a bug? Email security@cognoshift.in — covered by our disclosure programme.