DEV Community

Cover image for Build, Sign, Verify: A Hands-On Tour of VeritasChain Protocol v1.2 PRD

Build, Sign, Verify: A Hands-On Tour of VeritasChain Protocol v1.2 PRD

A practical walkthrough of the v1.2 Public Review Draft, written from the VSO standards team. We open the bundle, install dependencies, generate signed events, verify them, package a Regulatory Evidence Bundle, and run the full conformance test suite. Everything in this article is reproducible on a stock Ubuntu 24.04 box with Python 3.12.

VeritasChain Standards Organization (VSO) has opened the Public Review Draft of VeritasChain Protocol v1.2. This article is the hands-on companion to the implementer's preview on our blog and the longer-form Medium piece. Where those articles explain what v1.2 changes and why, this one runs the code.

If you have read the announcements and want to know whether the schemas, the verifier, and the CI flow actually work on your machine before you commit a roadmap decision — this is the article for you.

A few preliminaries:

  • This is a Public Review Draft, not a GA release. The protocol surface is feature-frozen except for corrections required by material interoperability, security, or regulatory-readability issues raised during the 45-day review window.
  • v1.2 is a vocabulary extension of v1.1, not a new protocol. The cryptographic substrate — COSE_Sign1, RFC 6962 Merkle trees, RFC 8785 JCS canonicalisation, RFC 3161 / eIDAS-qualified TSA / SCITT external anchoring — is unchanged. Every v1.0 and v1.1 anchor remains valid under v1.2 verification tools.
  • Honest scoping. VCP is a private-industry specification. It is not a harmonised standard under Regulation (EU) 1025/2012 and confers no presumption of conformity with the EU AI Act or any other regulation. It supports evidence generation for AI Act Articles 12, 19, 26, and 72; it does not by itself make any system compliant.

With that out of the way, let's open the bundle.

Table of contents

  1. Setting up the workspace
  2. The bundle layout
  3. Signing a minimal v1.2 event
  4. Adding regulatory_profile and clock_evidence
  5. Emitting a gov.change_event (and why ML retraining lives here now)
  6. Pre-trade controls: trade.ptc_snapshot and trade.ptc_breach
  7. Crypto-shredding with Erasure Certificate v2
  8. Assembling a Regulatory Evidence Bundle
  9. Hybrid post-quantum signature vectors
  10. Running the full conformance suite
  11. Writing your own verifier (in any language)
  12. Wiring it into CI
  13. Migrating from a v1.1 emitter
  14. Where to send your Public Review comments

1. Setting up the workspace

We start with a stock Ubuntu 24.04 box (any modern Linux works). Install Python 3.12 if you don't have it, then fetch the PRD bundle:

# Either clone the repo
$ git clone https://github.com/veritaschain/vcp-spec
$ cd vcp-spec

# Or download the release archive
$ curl -L -O https://github.com/veritaschain/vcp-spec/releases/download/v1.2-prd/vcp-v1.2-prd.zip
$ unzip vcp-v1.2-prd.zip
$ cd vcp-v1.2-prd
Enter fullscreen mode Exit fullscreen mode

Install the Python dependencies declared at the repository root:

$ pip install -r requirements.txt
Successfully installed PyNaCl-1.5.0 jsonschema-4.21.1
Enter fullscreen mode Exit fullscreen mode

PyNaCl gives us Ed25519 signing and verification. jsonschema gives us Draft 2020-12 validation. No web framework, no database, no service — the reference tools are deliberately minimal so you can read them in an afternoon and reimplement them in any language you like.

Verify the install is sane:

$ python3 -c "from nacl.signing import SigningKey; print('PyNaCl OK')"
PyNaCl OK
$ python3 -c "from jsonschema import Draft202012Validator; print('jsonschema OK')"
jsonschema OK
Enter fullscreen mode Exit fullscreen mode

2. The bundle layout

Take a quick look at what shipped:

$ ls -F
.github/
DISPOSITION_LOG.md
GA_CUTOVER_PATCH.md
README.md
VCP-Specification-v1_2-PRD.md
pqc-vectors/
requirements.txt
schemas/
tests/
tools/
Enter fullscreen mode Exit fullscreen mode

Three directories matter for this tutorial:

  • schemas/ — the JSON Schemas for CORE, TRADE, GOV, RISK, PRIVACY, RECOVERY, and the Regulatory Evidence Bundle (REB). Their $id values resolve to https://standards.veritaschain.org/vcp/v1.2/… at GA. During the PRD period, treat them as placeholders that point to the canonical content in this repo.
  • tests/silver/ and tests/gold/ — the conformance test suite. Pre-built signed sample bundles plus expected results.
  • tools/ — Python scripts you can run against the bundle. The five scripts you'll meet in this article:
    • tools/vcp_verify.py — the reference verifier
    • tools/validate_schemas.py — JSON Schema validator
    • tools/generate_test_bundles.py — deterministic test-bundle generator
    • tools/generate_pqc_vectors.py — PQC reference vector generator
    • tools/pqc_verify_check.py — hybrid vector pass/fail check

And one file you'll grow to appreciate: .github/workflows/conformance.yml, which runs all the checks above on every push. We'll come back to that in section 11.

3. Signing a minimal v1.2 event

The simplest possible conformant v1.2.0 event is one that does nothing new beyond bumping the version constant from "1.1" to "1.2". Let's build one.

Create tutorial/01_minimal.py:

import json, hashlib, base64, uuid, datetime
from nacl.signing import SigningKey


def jcs(value):
    """RFC 8785 canonicalisation — production code should use a vetted library.
    This is the same minimal implementation used by tools/vcp_verify.py."""
    return _j(value).encode("utf-8")


def _j(v):
    if v is None: return "null"
    if v is True: return "true"
    if v is False: return "false"
    if isinstance(v, str):
        return json.dumps(v, ensure_ascii=False, separators=(",", ":"))
    if isinstance(v, (int, float)):
        return json.dumps(v, allow_nan=False)
    if isinstance(v, list):
        return "[" + ",".join(_j(x) for x in v) + "]"
    if isinstance(v, dict):
        items = sorted(v.items(), key=lambda kv: kv[0].encode("utf-16-be"))
        return "{" + ",".join(_j(k) + ":" + _j(val) for k, val in items) + "}"
    raise TypeError(type(v))


def utc_now_iso():
    return datetime.datetime.utcnow().isoformat(timespec="milliseconds") + "Z"


def main():
    # Deterministic seed so anyone reading this article gets the same output
    sk = SigningKey(bytes.fromhex("11" * 32))
    issuer_id = "did:web:tutorial.example"

    payload = {"order_id": "demo-001", "side": "BUY", "stub": True}

    event = {
        "vcp_version": "1.2",
        "event_id":    str(uuid.uuid4()),  # production code uses UUIDv7
        "event_type":  "trade.order",
        "issuer_id":   issuer_id,
        "policy_id":   "tutorial-policy-2026-06",
        "issued_at":   utc_now_iso(),
        "payload_hash": hashlib.sha256(jcs(payload)).hexdigest(),
        "hash_alg":    "sha-256",
        "sig_alg":     "ed25519",
        "external_anchor": {
            "anchor_type":  "RFC3161",
            "anchor_value": "PLACEHOLDER_TSA_TOKEN",
            "anchor_time":  utc_now_iso(),
        },
    }

    # Sign over the JCS form with the signature field absent
    event["signature"] = base64.b64encode(sk.sign(jcs(event)).signature).decode()
    print(json.dumps(event, indent=2))


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Run it:

$ python3 tutorial/01_minimal.py
{
  "vcp_version": "1.2",
  "event_id": "5dac…",
  "event_type": "trade.order",
  "issuer_id": "did:web:tutorial.example",
  "policy_id": "tutorial-policy-2026-06",
  "issued_at": "2026-06-06T05:42:00.123Z",
  "payload_hash": "f3a1…",
  "hash_alg": "sha-256",
  "sig_alg": "ed25519",
  "external_anchor": { "anchor_type": "RFC3161", "anchor_value": "PLACEHOLDER_TSA_TOKEN", … },
  "signature": "PsK1…"
}
Enter fullscreen mode Exit fullscreen mode

This event is conformant v1.2.0 at the regulatory_profile = NONE profile (omission defaults to NONE for v1.1 compatibility). It will validate against schemas/core.schema.json and verify against tools/vcp_verify.py's Ed25519 check.

If you are migrating from v1.1, this is your one-line change: "vcp_version": "1.1""vcp_version": "1.2". Everything else continues to work.

4. Adding regulatory_profile and clock_evidence

The minimal event above is fine for regulatory_profile = NONE. The moment you declare MIFID_ALGO or MIFID_HFT, clock_evidence becomes MUST (per the spec's tier matrix in §7.5).

Why? Because once you're claiming to be readable as algorithmic-trading evidence under MiFID II / RTS 6, an auditor reading your trail needs to know how trustworthy the timestamps actually are — not just that you claimed "PTP-locked".

Let's add both. Create tutorial/02_mifid_algo.py:

import json, hashlib, base64, uuid, datetime, subprocess, os
from nacl.signing import SigningKey
# ...jcs(), _j(), utc_now_iso() as in 01_minimal.py


def collect_clock_evidence():
    """Reference clock-evidence collector. In production, read from your
    PTP daemon (phc2sys) or NTP daemon (chronyd). Here we return a static
    representative example."""
    return {
        "utc_source":     "PTP-grandmaster:dc1",
        "offset_us":      12.4,
        "max_error_us":   38.0,
        "holdover_state": "LOCKED",
        "sync_method":    "PTPv2_LOCKED",
        "last_sync_at":   "2026-06-06T05:41:55Z",
        "clock_state":    "NOMINAL",
    }


def main():
    sk = SigningKey(bytes.fromhex("11" * 32))

    payload = {"order_id": "demo-002", "side": "BUY", "venue_mic": "XEUR"}

    event = {
        "vcp_version":        "1.2",
        "event_id":           str(uuid.uuid4()),
        "event_type":         "trade.order",
        "issuer_id":          "did:web:tutorial.example",
        "policy_id":          "tutorial-policy-2026-06",
        "regulatory_profile": "MIFID_ALGO",        # NEW
        "agent_id":           "agent:mr-strategy-v3.4.1",  # NEW
        "issued_at":          utc_now_iso(),
        "clock_evidence":     collect_clock_evidence(),    # NEW
        "payload_hash":       hashlib.sha256(jcs(payload)).hexdigest(),
        "hash_alg":           "sha-256",
        "sig_alg":            "ed25519",
        "external_anchor": {
            "anchor_type":  "RFC3161",
            "anchor_value": "PLACEHOLDER_TSA_TOKEN",
            "anchor_time":  utc_now_iso(),
        },
    }

    event["signature"] = base64.b64encode(sk.sign(jcs(event)).signature).decode()
    print(json.dumps(event, indent=2))


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

A note on agent_id. It is a VCP technical identifier — purely for tracing an action back to the AI component that produced it. It does not imply that the system is legally classified as an autonomous agent under any regulatory regime. We make this point in the schema's description field too, because the legal connotation is real and we want to keep the technical identifier free of it.

One important rule: an implementation that cannot collect real clock evidence MUST NOT fabricate it. If your emitter runs in a container without access to the host PTP daemon, emit:

{
  "clock_evidence": {
    "utc_source":     "container-unknown",
    "max_error_us":   1000000,
    "holdover_state": "UNKNOWN",
    "sync_method":    "BEST_EFFORT",
    "last_sync_at":   "1970-01-01T00:00:00Z",
    "clock_state":    "UNRELIABLE"
  }
}
Enter fullscreen mode Exit fullscreen mode

This is a true statement and an auditor can read it. A fabricated "clock_state": "NOMINAL" would not be.

5. Emitting a gov.change_event (and why ML retraining lives here now)

ESMA's Supervisory Briefing of 26 February 2026 introduces a six-category material-change taxonomy and states that retraining of ML components used in algorithmic trading constitutes a material change. v1.2 makes this recordable as gov.change_event.

The rule: emit the ChangeEvent before the change takes effect in production. Every CORE event issued after the change references the change_id until a superseding ChangeEvent supersedes it.

# tutorial/03_change_event.py
def emit_change_event(sk, issuer_id, policy_id):
    ev = {
        "vcp_version":        "1.2",
        "event_id":           str(uuid.uuid4()),
        "event_type":         "gov.change_event",
        "issuer_id":          issuer_id,
        "policy_id":          policy_id,
        "regulatory_profile": "MIFID_ALGO",
        "issued_at":          utc_now_iso(),
        "clock_evidence":     collect_clock_evidence(),
        # gov.change_event-specific fields
        "change_id":      str(uuid.uuid4()),
        "supersedes_id":  None,
        "categories":     ["adaptive_retraining", "logic_change"],
        "scope": {
            "strategy_ids":  ["alpha-mr-001"],
            "venues":        ["XEUR", "XPAR"],
            "asset_classes": ["EQUITY"],
        },
        "model_version":  { "from": "v3.4.1", "to": "v3.4.2" },
        "approval": {
            "approver_role":     "Head of Algo Trading",
            "approval_doc_hash": hashlib.sha256(b"approval-doc-2026-06-06").hexdigest(),
        },
        "go_live_at":         "2026-06-15T08:00:00Z",
        "test_evidence_refs": ["did:web:tutorial.example/tests/run-2026-06-05"],
        "rollback_plan_ref":  "did:web:tutorial.example/runbooks/rollback-mr",
        # Required CORE envelope tail
        "payload_hash": hashlib.sha256(jcs({"stub": True})).hexdigest(),
        "hash_alg":     "sha-256",
        "sig_alg":      "ed25519",
        "external_anchor": {
            "anchor_type":  "RFC3161",
            "anchor_value": "PLACEHOLDER_TSA_TOKEN",
            "anchor_time":  utc_now_iso(),
        },
    }
    ev["signature"] = base64.b64encode(sk.sign(jcs(ev)).signature).decode()
    return ev
Enter fullscreen mode Exit fullscreen mode

What this lets an auditor reconstruct: "On 2026-06-06 at 05:42 UTC, the Head of Algo Trading approved a v3.4.1→v3.4.2 model update on strategy alpha-mr-001 for XEUR/XPAR equity trading, scheduled to go live 2026-06-15, with test evidence at the named reference and a documented rollback plan."

For ML retraining, the conventional sequence emits three events in this order:

  1. gov.change_event with categories: ["adaptive_retraining"] recording the approval and the go-live time.
  2. recovery.model_retrained recording the actual training run with the dataset hash and the training-run ID.
  3. The first CORE event after go-live carrying a reference to the new change_id.

The cross-references mean a supervisor reviewing an incident at time T can ask "what was changing in the system at time T-1?" and get a structured answer that ties strategy, model version, approval chain, and post-change activity into one chain of evidence.

The second event in the sequence — recovery.model_retrained — looks like this:

# tutorial/03_change_event.py — continued
def emit_model_retrained(sk, issuer_id, policy_id, change_event_id):
    ev = base_event(sk, issuer_id, policy_id,
                       event_type="recovery.model_retrained")
    ev.update({
        "model_id":        "alpha-mr-001",
        "dataset_hash":    hashlib.sha256(b"training-dataset-2026-Q2").hexdigest(),
        "training_run_id": "trn-2026-06-06-001",
        "change_event_id": change_event_id,  # links back to the gov.change_event
    })
    ev["signature"] = base64.b64encode(sk.sign(jcs({k:v for k,v in ev.items() if k != "signature"})).signature).decode()
    return ev


# Wiring it all together
change_ev    = emit_change_event(sk, issuer_id, policy_id)
ledger.append(change_ev)

# ... operator runs the training pipeline ...

retrained_ev = emit_model_retrained(sk, issuer_id, policy_id,
                                    change_ev["change_id"])
ledger.append(retrained_ev)

# ... at go_live_at, the first CORE event references change_ev["change_id"] ...
Enter fullscreen mode Exit fullscreen mode

The change_event_id field on the recovery.model_retrained event is the cross-reference that makes the chain navigable. A supervisor with one of the three event_ids can pull the other two and reconstruct the entire approve→train→deploy chain.

6. Pre-trade controls: trade.ptc_snapshot and trade.ptc_breach

Article 13 of RTS 6 requires pre-trade controls. v1.1 had no first-class structure for them. v1.2 introduces trade.ptc_snapshot (the state of controls at a moment in time) and trade.ptc_breach (what happened when a control fired).

The implementation pattern is straightforward:

# tutorial/04_pre_trade_controls.py
def emit_ptc_snapshot(sk, issuer_id, policy_id):
    ev = base_event(sk, issuer_id, policy_id, event_type="trade.ptc_snapshot")
    ev.update({
        "scope":    "ACCOUNT",
        "scope_id": "live-account-7",
        "controls": {
            "price_collar":          { "method": "absolute", "value": "100.00" },
            "max_order_value":       { "currency": "EUR", "amount": 250000 },
            "max_order_volume":      { "shares": 10000 },
            "max_message_rate":      { "messages_per_second": 200 },
            "repeated_exec_throttle":{ "window_ms": 100, "max_executions": 10 },
        },
        "active_kill_switches": ["primary", "escalation"],
        "approving_role":       "Head of Algo Trading",
    })
    ev["signature"] = base64.b64encode(sk.sign(jcs({k:v for k,v in ev.items() if k != "signature"})).signature).decode()
    return ev


def check_order_against_snapshot(order, snapshot):
    controls = snapshot["controls"]
    if order["price"] > controls["price_collar"]["value"]:
        return ("PRICE_COLLAR", order["price"], controls["price_collar"]["value"])
    if order["quantity"] * order["price"] > controls["max_order_value"]["amount"]:
        return ("MAX_ORDER_VALUE",
                order["quantity"] * order["price"],
                controls["max_order_value"]["amount"])
    # ... similar checks for max_order_volume, max_message_rate, repeated_exec_throttle
    return None


def emit_ptc_breach(sk, issuer_id, policy_id, snapshot_id, breach, order_id):
    breach_type, observed, limit = breach
    ev = base_event(sk, issuer_id, policy_id, event_type="trade.ptc_breach")
    ev.update({
        "snapshot_id": snapshot_id,
        "breach_type": breach_type,
        "order_id":    order_id,
        "observed":    { "value": observed, "limit": limit },
        "action_taken": "BLOCK",
        "operator":     "AUTO",
    })
    ev["signature"] = base64.b64encode(sk.sign(jcs({k:v for k,v in ev.items() if k != "signature"})).signature).decode()
    return ev
Enter fullscreen mode Exit fullscreen mode

The supervisory question PTC events answer is: given these controls, why did this order get through? With v1.2, the answer is recorded in evidence at the moment of the order, not reconstructed from system logs months later.

When to emit a snapshot: at system start-up; at every change to PTC parameters; at minimum once per business day; and at every breach. Orders carry a ptc_snapshot_ref field indicating which snapshot was in force at order time. This creates the bidirectional link an auditor needs: from order to controls, and from controls to all orders they touched.

Here is what a rejection flow looks like end-to-end. Suppose an account-7 trader sends an order at price 305,000 EUR against a max_order_value of 250,000 EUR. The emitter sequence is:

# tutorial/04_pre_trade_controls.py — continued
snapshot   = emit_ptc_snapshot(sk, issuer_id, policy_id)   # at boot or change
ledger.append(snapshot)

order      = {"order_id":"ord-2026-06-06-12345","price":305000,
              "quantity":1,"venue_mic":"XEUR"}
breach     = check_order_against_snapshot(order, snapshot)

if breach:
    breach_event = emit_ptc_breach(sk, issuer_id, policy_id,
                                   snapshot["event_id"], breach,
                                   order["order_id"])
    ledger.append(breach_event)
    reject_order(order, reason=breach[0])
else:
    order_event = emit_order_event(sk, issuer_id, policy_id,
                                   order, snapshot["event_id"])
    ledger.append(order_event)
    route_order(order)
Enter fullscreen mode Exit fullscreen mode

What an auditor sees in the ledger, in chronological order:

event_id   event_type             scope              detail
0196…ab    trade.ptc_snapshot     ACCOUNT live-acct-7  max_order_value EUR 250,000
0196…cd    trade.ptc_breach       (refs ab)            MAX_ORDER_VALUE 305000 vs 250000 → BLOCK
Enter fullscreen mode Exit fullscreen mode

The supervisor can run the verifier against this slice of the ledger and confirm — without trusting the operator's narrative — that the breach was recorded, the action was taken, and no order event was issued. That's the structural difference between v1.1 (which would have left this to system logs) and v1.2 (which captures it in cryptographically anchored evidence).

7. Crypto-shredding with Erasure Certificate v2

The PRIVACY module addresses GDPR Article 17 (right to erasure) through crypto-shredding: the ciphertext stays in the ledger, but its decryption key is destroyed in an HSM. The legal argumentation is in Annex J of the specification (explicitly labelled as not legal advice).

What changed from Erasure Certificate v1: structured GDPR Article 17(1) ground reference, KMS key URI, HSM attestation block, explicit backup_treatment statement, anonymisation-claim basis, and optional co-signers (DPO + CISO).

# tutorial/05_erasure.py
def emit_erasure_certificate(sk, subject_pseudonym):
    ev = base_event(sk, "did:web:tutorial.example",
                       "tutorial-policy-2026-06",
                       event_type="privacy.erasure")
    ev.update({
        "subject_pseudonym":  subject_pseudonym,
        "erasure_basis":      "GDPR-17(1)(b)",
        "kms_key_uri":        "kms://eu-west-1/keys/01HX-tutorial-key",
        "hsm_attestation": {
            "vendor":               "VendorName",
            "fips_140_3_level":     3,
            "attestation_evidence": "attestation-token-2026-06-06",
        },
        "backup_treatment":         "scheduled_overwrite_within_90d",
        "anonymisation_claim_basis":"k=20, l=3 over the residual graph",
        "co_signers": ["did:web:dpo.tutorial.example", "did:web:ciso.tutorial.example"],
    })
    ev["signature"] = base64.b64encode(sk.sign(jcs({k:v for k,v in ev.items() if k != "signature"})).signature).decode()
    return ev
Enter fullscreen mode Exit fullscreen mode

Three operational rules to remember:

  1. subject_pseudonym MUST be a stable pseudonymous identifier. Plaintext PII MUST NOT appear here.
  2. kms_key_uri MUST point to the destroyed key (KMIP locator, cloud KMS ARN, or HSM key reference).
  3. co_signers is OPTIONAL but RECOMMENDED at Gold and Platinum, with DPO + CISO being the typical configuration.

The crypto-shredding pillars (from Annex J) — Article 17(1) is satisfied where data are made inaccessible given the state of the art; HSM-attested key destruction is a state-of-the-art mechanism; the EDPB-flagged backup gap is addressed by the documented backup_treatment timeline — are how the operator argues that destruction of the key is, in substance, erasure. Your local DPA remains the final word.

8. Assembling a Regulatory Evidence Bundle

The Regulatory Evidence Bundle (REB) is the structural innovation of v1.2. It is not a new event; it is a packaging contract that lets you deliver, on demand, a self-contained, independently verifiable evidence set to a supervisor or auditor.

Three normative rules:

  • Reproducibility. Two independent assemblers, given the same period, scope, and source ledger, MUST produce the same content hash.
  • Profile-specific minima. Each Regulatory Profile carries a required minimum component set.
  • Signing. The REB MUST be signed by the operator and SHOULD be co-signed by the operator's compliance function.

A reference assembler:

# tutorial/06_reb.py
def assemble_reb(period, scope, profile, ledger):
    components = {
        "core_events": {
            "merkle_root": ledger.merkle_root_for(period, scope).hex(),
            "count":       ledger.count_for(period, scope),
            "path_index":  ledger.path_index_for(period, scope),
        },
        "policy_id_history":         sorted(ledger.distinct_values("policy_id", period, scope)),
        "regulatory_profile_history": sorted(ledger.distinct_values("regulatory_profile", period, scope)),
    }

    if profile in {"MIFID_ALGO", "MIFID_HFT", "MIXED_FINANCIAL_AI"}:
        components["clock_evidence_summary"]   = ledger.summarise_clock(period, scope)
        components["change_events"]            = ledger.event_ids("gov.change_event", period, scope)
        components["ptc_snapshots"]            = ledger.event_ids("trade.ptc_snapshot", period, scope)
        components["ptc_breaches"]             = ledger.event_ids("trade.ptc_breach", period, scope)
        components["provider_dependencies"]    = ledger.event_ids("gov.provider_dependency", period, scope)
        components["outage_notices"]           = ledger.event_ids_prefix("recovery.outage_", period, scope)

    if profile in {"AI_ACT_HRAI", "MIXED_FINANCIAL_AI"}:
        components["pmm_manifests"]            = ledger.event_ids("gov.pmm_manifest", period, scope)
        components["recovery_integrity_tests"] = ledger.event_ids("recovery.integrity_test", period, scope)
        components["erasure_certificates"]     = ledger.event_ids("privacy.erasure", period, scope)

    reb = {
        "reb_version":        "1.0",
        "vcp_version":        "1.2",
        "regulatory_profile": profile,
        "period":             period,
        "scope":              scope,
        "components":         components,
        "external_anchors":   ledger.anchors_for(period, scope),
        "verifier_recipe":    "https://standards.veritaschain.org/verify/reb-1.0",
    }
    # Sign over the JCS form with the signature field absent
    sig_bytes = sk.sign(jcs(reb)).signature
    reb["issuer_signature"] = base64.b64encode(sig_bytes).decode()
    return reb
Enter fullscreen mode Exit fullscreen mode

Reproducibility, in practice, means that two independent teams writing assemble_reb against the same ledger and the same period/scope MUST produce REBs whose content hashes are identical. This is the property that makes a REB independently verifiable. It is not what the operator says happened — it is what anyone with access to the source ledger can reproduce.

The REB SHOULD be served via a SCRAPI-compatible endpoint at /v1/reb/{reb_id}. When an authority asks "show us your Q2 algorithmic-trading evidence in EUR-denominated venues," the operational answer is one URL that returns one self-contained bundle.

8.1 Serving a REB over HTTP

The SCRAPI compatibility point matters because it lets an auditor's tooling — or a regulatory technologist's script — fetch your REB without negotiating a custom protocol. A minimal Flask-based serving endpoint:

# tutorial/07_reb_server.py
from flask import Flask, request, jsonify, abort
import json, hashlib

app = Flask(__name__)
REB_STORE = {}  # in production, this is your durable storage


def reb_content_hash(reb: dict) -> str:
    """Reproducibility check: derive deterministic content hash."""
    reb_without_sig = {k: v for k, v in reb.items() if k != "issuer_signature"}
    return hashlib.sha256(jcs(reb_without_sig)).hexdigest()


@app.route("/v1/reb/<reb_id>", methods=["GET"])
def get_reb(reb_id: str):
    reb = REB_STORE.get(reb_id)
    if reb is None:
        abort(404)
    # Match the Accept header — SCITT convention is application/cose or application/json
    if "application/json" in request.headers.get("Accept", "application/json"):
        return jsonify(reb)
    abort(406)


@app.route("/v1/reb/<reb_id>/verify", methods=["GET"])
def verify_reb(reb_id: str):
    """Independent verifier convenience endpoint: recompute content hash."""
    reb = REB_STORE.get(reb_id)
    if reb is None:
        abort(404)
    return jsonify({
        "reb_id":              reb_id,
        "regulatory_profile":  reb["regulatory_profile"],
        "period":              reb["period"],
        "computed_content_hash": reb_content_hash(reb),
        "merkle_root":         reb["components"]["core_events"]["merkle_root"],
        "issuer_signature_present": "issuer_signature" in reb,
    })


@app.route("/v1/reb", methods=["POST"])
def publish_reb():
    reb = request.get_json()
    # Validate against schema before accepting
    from jsonschema import Draft202012Validator
    schema = json.loads(open("schemas/reb.schema.json").read())
    errs = list(Draft202012Validator(schema).iter_errors(reb))
    if errs:
        return jsonify({"errors": [e.message for e in errs]}), 400
    reb_id = reb_content_hash(reb)[:16]
    REB_STORE[reb_id] = reb
    return jsonify({"reb_id": reb_id}), 201
Enter fullscreen mode Exit fullscreen mode

What a regulator's verifier sees when they call this:

$ curl https://your-firm.example/v1/reb/a1b2c3d4e5f67890
{
  "reb_version":        "1.0",
  "vcp_version":        "1.2",
  "regulatory_profile": "MIFID_ALGO",
  "period":             { "from": "2026-04-01", "to": "2026-06-30" },
  "components": {},
  "external_anchors": [],
  "issuer_signature":  "..."
}

$ curl https://your-firm.example/v1/reb/a1b2c3d4e5f67890/verify
{
  "reb_id":               "a1b2c3d4e5f67890",
  "regulatory_profile":   "MIFID_ALGO",
  "computed_content_hash":"a1b2c3d4e5f6789012345...",
  "merkle_root":          "...",
  "issuer_signature_present": true
}
Enter fullscreen mode Exit fullscreen mode

The /verify convenience endpoint is optional but valuable: it tells the requester what content hash this server computed, which they can compare against the content hash their independent assembler would compute from the same source ledger. If those two hashes match, the REB is reproducible, which is the v1.2 normative requirement.

In production you would also:

  • Pin TLS to mutual authentication for supervisor access
  • Add rate limiting and audit logging on the REB endpoint itself (which is, ironically, a VCP event in its own right)
  • Cache REBs by content hash, since a reproducible REB is by definition idempotent
  • Serve application/cose content type alongside JSON for SCITT verifier compatibility

9. Hybrid post-quantum signature vectors

v1.2 reserves ed25519+mldsa65 as the canonical hybrid SignAlgo, following the LAMPS composite-signatures combiner (draft-ietf-lamps-pq-composite-sigs-06). The verify-then-emit ratchet:

Version Verify hybrid Emit hybrid
v1.2.0 (now) MAY at all tiers MAY at all tiers; registry reserves ed25519+mldsa65
v1.2.1 (Q1 2027 target) MUST at Platinum, SHOULD at Gold SHOULD at Platinum, MAY at Gold/Silver
v1.3.0 MUST at Platinum, MUST at Gold MUST at Platinum, SHOULD at Gold (subject to maturity)

The PRD ships with two vectors in pqc-vectors/. The classical half uses real Ed25519. The PQC half is a clearly-labelled PRD_PQC_PLACEHOLDER until vetted ML-DSA-65 vectors replace it before v1.3.0's emit-MUST cut-over.

Run the vector check:

$ python3 tools/pqc_verify_check.py
PQC hybrid vector check (classical-half only — PQC half is PRD placeholder)
  [PASS] verify-pass.json: expected=VERIFY_PASS observed=VERIFY_PASS
  [PASS] verify-fail-tampered.json: expected=VERIFY_FAIL observed=VERIFY_FAIL
Enter fullscreen mode Exit fullscreen mode

The verify-fail-tampered.json vector has a single bit flipped in byte 0 of the Ed25519 signature. Any conformant verifier MUST reject it. The verify-pass.json vector is the same message with an intact signature; any conformant verifier MUST accept it.

If you are writing your own verifier — and we encourage you to, since independent implementations are part of the GA-readiness checklist — your implementation MUST produce identical pass/fail outcomes on these two vectors. That is the operational meaning of conformance for the PQC vectors.

For implementers planning the v1.2.1 → v1.3.0 transition: ML-DSA-65 signatures are about 3.3 KB; the composite is about 3.4 KB. At HFT order-flow rates, the realistic deployment pattern is batch-signing Merkle leaves and emitting one composite signature per batch, anchoring at the batch root.

10. Running the full conformance suite

Time to run everything together:

$ python3 tools/validate_schemas.py
Silver bundle — schema validation
  [PASS] silver event #0 validates against core.schema.json
  [PASS] silver REB validates against reb.schema.json

Gold bundle — schema validation
  [PASS] gold event #0 validates against core.schema.json
  [PASS] gold event #1 validates against core.schema.json
  [PASS] gold event #2 validates against core.schema.json
  [PASS] gold REB validates against reb.schema.json

Summary: 0 schema-validation failures
Enter fullscreen mode Exit fullscreen mode
$ python3 tools/vcp_verify.py --tier silver --bundle tests/silver/
VCP v1.2-prd reference verifier — tier=silver bundle=tests/silver

Event #0 0196a98b-4980-7abc-8123-4567890def012 type=trade.order
  [PASS] policy_id present
  [PASS] event_id is UUIDv7
  [PASS] event canonicalisation deterministic  — jcs_sha256=fb720ff9bc11539b…
  [PASS] ed25519 signature verify
  [PASS] external_anchor present

Tier-specific checks
  [PASS] ClockEvidence present (Silver SHOULD)

Regulatory Evidence Bundle
  [PASS] REB reproducibility (same input → same content hash)

Summary: 7 pass, 0 fail
Enter fullscreen mode Exit fullscreen mode
$ python3 tools/vcp_verify.py --tier gold --bundle tests/gold/
… (3 events × 4-5 checks each, plus tier-specific PTC and ChangeEvent checks)
Summary: 19 pass, 0 fail
Enter fullscreen mode Exit fullscreen mode
$ python3 tools/pqc_verify_check.py
  [PASS] verify-pass.json: expected=VERIFY_PASS observed=VERIFY_PASS
  [PASS] verify-fail-tampered.json: expected=VERIFY_FAIL observed=VERIFY_FAIL
Enter fullscreen mode Exit fullscreen mode

Four categories, all green. The conformance properties the verifier exercises:

  1. RFC 8785 JCS canonicalisation (deterministic event-hash recomputation)
  2. Event-hash verification against the recomputed canonical form
  3. Ed25519 signature verification (RFC 8032)
  4. Merkle proof verification (RFC 6962-style)
  5. External-anchor presence
  6. PolicyID presence
  7. ClockEvidence presence (Silver: SHOULD; Gold: MUST when MIFID_ALGO)
  8. REB reproducibility (deterministic content hash)
  9. PTCSnapshot presence (Gold: SHOULD)
  10. ChangeEvent presence (Gold: SHOULD)

If you write your own verifier in Go, Rust, TypeScript, or whatever else, your implementation MUST produce identical pass/fail outcomes on these test bundles. That is what makes you a conformant implementation. We will list independent implementations in the GA acknowledgements — so if you build one, let us know.

11. Writing your own verifier (in any language)

The GA-readiness checklist requires "at least one independent implementation passes the conformance test suite at Silver and Gold." If you build that implementation, you become part of the GA story. Here is the recipe in language-agnostic pseudocode, drawn from tools/vcp_verify.py.

The minimum your verifier needs to do, in order:

function verify_event(event, public_key_hex):
    # 1. Canonicalisation
    event_for_sig = event without "signature"
    canon_bytes   = jcs_canonicalise(event_for_sig)  # RFC 8785

    # 2. Hash and signature
    computed_hash = sha256(canon_bytes)
    sig_bytes     = base64_decode(event["signature"])

    # 3. Verify Ed25519
    if not ed25519_verify(public_key_hex, canon_bytes, sig_bytes):
        return FAIL("signature invalid")

    # 4. Mandatory fields
    if not event["policy_id"]:
        return FAIL("policy_id missing")
    if not is_uuidv7(event["event_id"]):
        return FAIL("event_id is not UUIDv7")
    if not event["external_anchor"]["anchor_value"]:
        return FAIL("external_anchor missing")

    # 5. Profile-conditional fields
    profile = event.get("regulatory_profile", "NONE")
    if profile in {"MIFID_ALGO", "MIFID_HFT", "MIXED_FINANCIAL_AI"}:
        if not event.get("clock_evidence"):
            return FAIL("clock_evidence MUST under MIFID_* profiles")

    return PASS
Enter fullscreen mode Exit fullscreen mode

The four properties that are unambiguous and language-portable:

Property What you need RFC / spec reference
JCS canonicalisation UTF-16 code-unit ordered keys, ECMA 262 number serialisation, no insignificant whitespace RFC 8785
Ed25519 verification Pure Ed25519, no pre-hash, no domain separation RFC 8032
Merkle proof verification Leaf hash = SHA-256(0x00 ‖ leaf); node hash = SHA-256(0x01 ‖ left ‖ right) RFC 6962 §2.1.1
UUIDv7 detection Version nibble == 7; variant bits == 10 RFC 9562 §5.7

A Rust implementer would lean on ed25519-dalek for signatures, sha2 for hashing, and write the JCS serialiser from scratch (it's about 60 lines). A Go implementer would use crypto/ed25519 from the standard library and either bring their own JCS or use one of the community implementations. A TypeScript implementer would use @noble/ed25519 (no native dependencies, runs in Node and the browser) and again write JCS by hand.

The fact that JCS is the easiest part to get subtly wrong is worth flagging. The two most common failure modes:

  • Object key ordering by UTF-8 byte instead of UTF-16 code unit. These agree for ASCII keys but diverge for keys containing characters in the supplementary planes. RFC 8785 specifies UTF-16, so use UTF-16.
  • Number serialisation that emits 1.0 instead of 1. ECMA 262 says integers serialise without a decimal point. If you serialise via your language's default JSON number formatter, check this case explicitly.

If your verifier ingests tests/silver/sample-events.jsonl and tests/silver/sample-reb.json, runs the verification steps above, and emits 7 pass, 0 fail, it is conformant at Silver. Same exercise on tests/gold/, with additional checks for PTC snapshot presence and ChangeEvent presence, gives you Gold. Send us a link and we will add your implementation to the GA acknowledgements section.

A note for the truly motivated: a fully featured independent verifier would also re-derive Merkle roots from the leaf hashes, verify the external anchor (TSA token chain, or SCITT Receipt inclusion proof), and run the REB reproducibility check by reassembling the REB from your own copy of the ledger. None of that is required to claim Silver/Gold conformance, but all of it is welcome and is the path to becoming the kind of independent supervisory-side verifier that the GA-readiness checklist is hoping for.

11.1 Common pitfalls (with actual verifier output)

What follows are four ways your verifier — or your emitter — can be subtly wrong, with the exact output the reference verifier produces when each one occurs. Use these as integration tests against your own implementation.

Pitfall 1: JCS key ordering using UTF-8 instead of UTF-16.

If your JCS serialiser orders object keys by UTF-8 byte sequence, your event hash will diverge from the spec's for any event containing non-ASCII keys. The reference verifier catches this because the recomputed canonical form does not match the signed payload:

Event #0 0196a98b-...
  [FAIL] ed25519 signature verify  — signature invalid
Enter fullscreen mode Exit fullscreen mode

The signature is mathematically intact; the verifier just hashed different bytes than the emitter signed. If you see this failure mode on events that contain CJK keys, fields with combining characters, or supplementary-plane symbols, check your key sort.

Pitfall 2: Number serialisation with trailing .0.

If your JSON encoder emits 1.0 instead of 1 for integer-valued floats, the canonical form diverges from the spec. Same failure shape as Pitfall 1:

  [FAIL] ed25519 signature verify  — signature invalid
Enter fullscreen mode Exit fullscreen mode

ECMA 262 says integers serialise without a decimal point. Most modern JSON encoders get this right, but some Python libraries (and some Java BigDecimal paths) emit .0 for integer floats. If you see verify failures correlated with events containing whole-number quantities like "quantity": 1000.0, this is the cause.

Pitfall 3: Missing policy_id (v1.1 hard deadline in force).

If you migrate from a pre-v1.1 emitter and forget to add policy_id, the verifier rejects the event regardless of signature validity:

Event #0 0196a98b-...
  [PASS] event canonicalisation deterministic
  [FAIL] policy_id present  — policy_id missing
  [PASS] ed25519 signature verify
Enter fullscreen mode Exit fullscreen mode

The signature is valid; the schema-level requirement is not. The Policy Identification deadline of 25 March 2026 is in force and v1.2 does not relax it.

Pitfall 4: clock_evidence claimed at MIFID_ALGO but absent.

If you set regulatory_profile: "MIFID_ALGO" but forget to emit clock_evidence, the Gold-tier verifier flags the inconsistency:

Tier-specific checks
  [FAIL] ClockEvidence MUST for MIFID_ALGO/HFT/MIXED  — MUST violation per §7.5
Enter fullscreen mode Exit fullscreen mode

This is the one your emitter-side defensive check (the RuntimeError in V12Emitter._needs_clock) should prevent at issuance time. If a v1.2.0 event slips through with MIFID_ALGO and no clock_evidence, it is not conformant; the verifier is correct to reject it.

The pattern across all four pitfalls is the same: the v1.2 verifier surfaces the inconsistency at the earliest possible point — at signature verification for canonicalisation errors, at field-presence checks for missing required fields, at tier-policy checks for profile-conditional fields. If your own verifier reproduces these four behaviours, you are well on the way to Silver/Gold conformance.

12. Wiring it into CI

The PRD includes a GitHub Actions workflow you can use as-is or adapt:

# .github/workflows/conformance.yml
name: VCP v1.2-prd Conformance
on:
  push:
    branches: [main, "v1.2-prd"]
  pull_request:
    branches: [main, "v1.2-prd"]
  workflow_dispatch:

jobs:
  conformance:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -r requirements.txt

      # Reproducibility check: regenerate then diff against checked-in
      - run: python3 tools/generate_test_bundles.py
      - run: git diff --exit-code tests/silver/ tests/gold/
      - run: python3 tools/generate_pqc_vectors.py
      - run: git diff --exit-code pqc-vectors/

      # Schema and tier conformance
      - run: python3 tools/validate_schemas.py
      - run: python3 tools/vcp_verify.py --tier silver --bundle tests/silver/
      - run: python3 tools/vcp_verify.py --tier gold   --bundle tests/gold/
      - run: python3 tools/pqc_verify_check.py
Enter fullscreen mode Exit fullscreen mode

The pattern worth stealing is the reproducibility check at the top: a deterministic generator plus checked-in output plus git diff --exit-code. If anyone (including the maintainer) changes either side without keeping them in sync, the build fails. We use this for tests/silver/, tests/gold/, and pqc-vectors/. It eliminates an entire class of bug where checked-in test data and the generator drift silently.

If you adopt this pattern in your own VCP-emitter project, you get a free guarantee that your sample bundles continue to match your generator, and your CI surfaces any drift on the next push.

13. Migrating from a v1.1 emitter

For most v1.1 deployments, migration to v1.2.0 is a single-line change.

A v1.1 emitter:

class V11Emitter:
    def emit(self, event_type, payload, anchor):
        ev = {
            "vcp_version": "1.1",                      # changes to "1.2"
            "event_id":    str(uuid.uuid4()),
            "event_type":  event_type,
            "issuer_id":   self.issuer_id,
            "policy_id":   self.policy_id,
            "issued_at":   utc_now_iso(),
            "payload_hash": hashlib.sha256(jcs(payload)).hexdigest(),
            "hash_alg":    "sha-256",
            "sig_alg":     "ed25519",
            "external_anchor": anchor,
        }
        ev["signature"] = base64.b64encode(self.sk.sign(jcs(ev)).signature).decode()
        return ev
Enter fullscreen mode Exit fullscreen mode

The same emitter, minimally migrated to v1.2.0 and conformant at regulatory_profile = MIFID_ALGO:

class V12Emitter:
    def __init__(self, sk, issuer_id, policy_id,
                 regulatory_profile="NONE",
                 clock_evidence_provider=None,
                 agent_id=None):
        self.sk = sk
        self.issuer_id = issuer_id
        self.policy_id = policy_id
        self.regulatory_profile = regulatory_profile
        self.clock_evidence_provider = clock_evidence_provider
        self.agent_id = agent_id
        self._needs_clock = regulatory_profile in (
            "MIFID_ALGO", "MIFID_HFT", "MIXED_FINANCIAL_AI"
        )

    def emit(self, event_type, payload, anchor):
        ev = {
            "vcp_version": "1.2",                          # CHANGED
            "event_id":    str(uuid.uuid4()),
            "event_type":  event_type,
            "issuer_id":   self.issuer_id,
            "policy_id":   self.policy_id,
            "issued_at":   utc_now_iso(),
            "payload_hash": hashlib.sha256(jcs(payload)).hexdigest(),
            "hash_alg":    "sha-256",
            "sig_alg":     "ed25519",
            "external_anchor": anchor,
            "regulatory_profile": self.regulatory_profile, # ADDED
        }
        if self.agent_id is not None:                      # ADDED
            ev["agent_id"] = self.agent_id
        if self._needs_clock:                              # ADDED
            if self.clock_evidence_provider is None:
                raise RuntimeError(
                    "ClockEvidence MUST be present under MIFID_* profiles"
                )
            ev["clock_evidence"] = self.clock_evidence_provider()
        ev["signature"] = base64.b64encode(self.sk.sign(jcs(ev)).signature).decode()
        return ev
Enter fullscreen mode Exit fullscreen mode

Three things change, none of them break v1.1 semantics:

  • "vcp_version" becomes "1.2".
  • regulatory_profile and (optionally) agent_id are populated.
  • A clock_evidence callback is invoked when the profile requires it.

Nothing about how events are signed, hashed, anchored, or canonicalised changes. A relying party verifying a v1.2 event uses the same JCS canonicalisation, the same Ed25519 signature check, the same external-anchor verification path. This is what we mean by "vocabulary extension, not protocol revision."

What you are not required to do:

  • Re-issue, re-sign, or re-anchor v1.0 or v1.1 events. They remain valid under v1.2 verification tools.
  • Migrate to hybrid signatures in v1.2.0. The reservation of ed25519+mldsa65 is forward-positioning, not a v1.2.0 requirement.
  • Adopt every Provisional Regulatory Profile. They are reserved for community comment and may change.

14. Where to send your Public Review comments

Public Review runs for 45 days from the PRD opening. Comments go through GitHub Issues using the Public Review Comment template at github.com/veritaschain/vcp-spec. Every substantive comment receives one of six dispositions — Accepted, Accepted with modification, Deferred, Declined, Out of scope, Editorial — recorded openly in DISPOSITION_LOG.md and updated weekly.

The categories of comment we especially welcome from dev.to readers:

  • Does the clock_evidence structure cover what your time infrastructure actually produces? In particular, can your PTP or NTP stack produce offset_us and max_error_us, or do you have to fabricate them?
  • Does trade.ptc_snapshot capture every pre-trade control your venue or jurisdiction requires? Are there controls that wouldn't fit one of the five categories shown?
  • Does the gov.change_event taxonomy cover every change you'd actually emit one for? In particular, where do adaptive_retraining and external_dependency_change overlap, and what should the rule be?
  • Does the REB minimum-content matrix, by Regulatory Profile, match what your supervisors actually request?
  • Does the requirements.txt install cleanly on your CI image? Does the reproducibility check pass on first run?
  • Did this tutorial work end-to-end on your machine? If not, where did it break? That information is gold for us.

Security findings — cryptographic vulnerabilities, schema injection paths, signature-bypass conditions — go privately to security@veritaschain.org under a 90-day responsible-disclosure timeline.

The GA-readiness checklist is six items: every $id URI resolves to its canonical artefact, the next IETF individual-draft iteration reflects the PRD's terminology, at least one independent implementation passes the conformance suite at Silver and Gold, the PQC verify vectors are reproducible across implementations, comments are addressed in a published disposition log, and the v1.1 hard-deadline counters (PolicyIdentification 2026-03-25, Silver external anchoring 2026-06-25) are confirmed unaffected. Until those six are cleared, this is a PRD. After they are cleared, v1.2.0 ships.

Closing notes

VCP v1.2 PRD is now open. The repository is at github.com/veritaschain/vcp-spec. The full specification is VCP-Specification-v1_2-PRD.md. Independent implementations are welcome and will be acknowledged.

Three things to take away if you read this far:

  1. v1.2 is the vocabulary regulators are about to start asking for in algorithmic-trading and AI audit trails. It is not a new protocol. It is not a rewrite.
  2. The reference tools work. We have shown the commands above; you can run them yourself. If they don't work on your machine, that is exactly the kind of finding we need before GA.
  3. Honest scoping holds. VCP supports evidence generation for AI Act Articles 12, 19, 26, and 72. It does not by itself make any system compliant.

Eighty-seven days until 2 August 2026. Forty-five for review. The rest is the work.

If you write a verifier in your favourite language and want it linked from our acknowledgements, let us know. If you find a bug, file an issue. If you find a security issue, email security@veritaschain.org. If you build something on VCP v1.2 we don't know about yet, we would genuinely like to hear about it.


About VSO. VeritasChain Standards Organization is a Tokyo-based standards body developing open cryptographic specifications for AI and algorithmic systems under the principle "Verify, Don't Trust." We maintain the VeritasChain Protocol (VCP) and the Verifiable AI Provenance (VAP) Framework. We are registered in Japan (D-U-N-S 698368529). VeritasChain株式会社, a separate commercial entity, operates in the same jurisdiction. This article is published from the VSO official dev.to account.

Companion publications

Contact

Published under Creative Commons Attribution 4.0 International. The VCP specification itself is governed by the licence stated in its repository.

GitHub logo veritaschain / vcp-spec

Official specification for the VeritasChain Protocol (VCP) v1.0 – global audit standard for algorithmic trading.

VCP Version License: CC BY 4.0

VeritasChain Protocol (VCP)

VeritasChain Protocol (VCP) is an open, vendor-neutral standard for
cryptographically verifiable audit trails in algorithmic and AI-driven trading systems.

VCP enables regulators, auditors, and market participants to
verify — not merely trust — the integrity, completeness, and ordering of trading decisions, orders, executions, and risk controls.

This repository is maintained by the
VeritasChain Standards Organization (VSO).


📌 Canonical Specification Location (IMPORTANT)

The canonical (normative) specification of VCP is located under:

/spec/
├─ v1.0/
├─ v1.1/
└─ v1.2/
  • Each version directory contains the authoritative specification (SPEC.md)
  • Files outside /spec/ are non-normative
  • HTML, PDF, or translated documents (if any) are provided for convenience only

If there is any conflict, the content under /spec/ always prevails.


📘 Available Versions

▶ Current Stable

  • v1.2 — latest specification with strengthened integrity guarantees → /spec/v1.2/

▶ Legacy

  • v1.0 — initial released version → /spec/v1.0/

Migration notes and compatibility considerations are…

Top comments (0)