Construct and verify your first memory receipt
In this tutorial you will construct a typed memory claim, encode the exact nine-field Form-B signing preimage, sign it with a local exercise key, verify it offline, and prove that a well-formed tampered claim is rejected.
This exercise demonstrates:
- byte integrity: the signed bytes did not change; and
- key possession: the signer held the private key paired with the supplied public key.
It does not establish a production issuer's identity. The exercise creates its own keypair, so that key was not trusted before the receipt existed. Production authenticity requires a real workspace public key published through an independently trusted channel.
Time: about 10 minutes. You need: Python 3.11+, an authorized SDK source checkout, your real IAM workspace UUID, and non-secret subject/issuer DIDs for the local exercise.
Step 1 — Create a project and install the SDK
mkdir osyra-first-memory
cd osyra-first-memory
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e "$OSYRA_OME_SDK_CHECKOUT"Set OSYRA_OME_SDK_CHECKOUT to the real
AP00013-osyra-ome-sdk-python checkout. The package is pre-release and has no
anonymous public install path today.
Verify the install:
python -c "import osyra_ome; print(osyra_ome.__version__)"Expected output:
1.0.0Set these environment variables to authoritative values before continuing:
OSYRA_OME_WORKSPACE: your real IAM workspace UUID.OSYRA_OME_SUBJECT_DID: the subject identifier for this exercise.OSYRA_OME_ISSUER_DID: the issuer identifier for this exercise.OSYRA_OME_BODY: non-secret tutorial content.
Step 2 — Construct a typed memory claim
Create verify_demo.py:
import hashlib
import os
import uuid
from osyra_ome import Claim, ClaimKind
workspace_id = str(uuid.UUID(os.environ["OSYRA_OME_WORKSPACE"]))
# RFC 9562 Appendix A.6 UUIDv7 test vector. KAT-only: never submit this fixed
# identifier to a service or reuse it for a persisted claim.
claim_uuid = uuid.UUID("017f22e2-79b0-7cc3-98c4-dc0c0c07398f")
assert claim_uuid.version == 7
assert claim_uuid.variant == uuid.RFC_4122
memory_body = os.environ["OSYRA_OME_BODY"].encode("utf-8")
claim = Claim(
id=claim_uuid.bytes,
workspace_id=workspace_id,
subject_id=os.environ["OSYRA_OME_SUBJECT_DID"],
parents=(),
issuer_did=os.environ["OSYRA_OME_ISSUER_DID"],
content_hash=hashlib.sha256(memory_body).digest(),
embedding_hash=b"\x00" * 32,
kind=ClaimKind.ASSERTION,
ts=1735689600000,
body=memory_body,
)
print(f"claim id={uuid.UUID(bytes=claim.id)} kind={claim.kind.name}")Run it:
python verify_demo.pyYou now have a validated local value object. No server has signed or persisted it. The fixed ID is a standards test vector, not a generated production ID. Real writes require a unique caller-generated UUIDv7; UUIDv4 is not a fallback.
The Python model validates byte widths. It does not validate UUID version bits
or parse workspace_id, which is why the code does both explicitly.
Step 3 — Build and sign the canonical preimage
Append the following imports and code to verify_demo.py:
import cbor2
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
# The authoritative signed Claim map has exactly these nine keys:
# ws, id, sub, par, iss, ch, eh, kn, ts.
claim_map = {
"ws": claim.workspace_id,
"id": claim.id,
"sub": claim.subject_id,
"par": list(claim.parents),
"iss": claim.issuer_did,
"ch": claim.content_hash,
"eh": claim.embedding_hash,
"kn": int(claim.kind),
"ts": claim.ts,
}
# Form B: canonical_cbor([tstr(domain-separation tag), claim map]).
preimage = cbor2.dumps(
["osyra-ome-claim-v1", claim_map],
canonical=True,
)
exercise_private_key = Ed25519PrivateKey.generate()
exercise_public_key = exercise_private_key.public_key().public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw,
)
signature = exercise_private_key.sign(preimage)
# Preimage size varies with your real workspace and DID string lengths.
print(
f"preimage={len(preimage)} bytes "
f"signature={len(signature)} bytes "
f"public_key={len(exercise_public_key)} bytes"
)This uses the public cbor2 dependency, not the SDK's private
osyra_ome.verify._kernel implementation. Private _kernel APIs are internal
and suitable only for SDK-maintainer KATs; they are not a supported customer
surface.
The body itself is not in the signed preimage. Its SHA-256 digest is the ch
value, so the signature binds the body transitively.
Run the file again. The preimage count is computed from your actual values; the signature and public key must be 64 and 32 bytes respectively.
Step 4 — Verify offline
Append:
from osyra_ome import verify_receipt
ok = verify_receipt(preimage, signature, exercise_public_key)
print(f"exercise receipt verifies: {ok}")
assert ok is TrueRun:
python verify_demo.pyThe final line is:
exercise receipt verifies: TrueThe verifier used no network. It verified the Ed25519 signature over the exact Form-B bytes under the supplied key.
Remember the trust boundary: exercise_public_key proves possession only
because it was generated in the same local exercise. It does not establish
that Osyra, your workspace, or the DID named in iss signed anything.
Tamper with a well-formed claim
Changing an arbitrary CBOR byte can make the encoding malformed, which should
raise rather than return False. To test the clean signature-mismatch path,
make a different but still canonical claim preimage:
tampered_map = {
**claim_map,
"ch": hashlib.sha256(memory_body + b"!").digest(),
}
tampered_preimage = cbor2.dumps(
["osyra-ome-claim-v1", tampered_map],
canonical=True,
)
tampered_ok = verify_receipt(
tampered_preimage,
signature,
exercise_public_key,
)
print(f"tampered receipt verifies: {tampered_ok}")
assert tampered_ok is FalseRun once more:
python verify_demo.pyThe final two verdicts are:
exercise receipt verifies: True
tampered receipt verifies: FalseFor malformed/non-canonical CBOR or the wrong domain tag, the verifier raises
OmeWireFormatError. Wrong-length or invalid key/signature material can raise
OmeVerifyError. It never turns malformed evidence into a successful verdict.
Step 5 — Replace the exercise key with a real trust anchor
In a production verifier, you do not mint a key beside the receipt. You receive three separate evidence inputs:
- the detached Form-B preimage;
- the 64-byte signature; and
- the real 32-byte workspace public key from a trust registry, DID resolution process, or pinned configuration you trusted before receiving the receipt.
The following recipe consumes those caller-controlled inputs. It does not use a key embedded in the receipt or a repository test fixture:
import base64
import os
from osyra_ome import verify_receipt
def required_b64(name: str, expected_len: int | None = None) -> bytes:
value = base64.b64decode(os.environ[name], validate=True)
if expected_len is not None and len(value) != expected_len:
raise ValueError(f"{name} must decode to {expected_len} bytes")
return value
published_trust_anchor = required_b64(
"OSYRA_OME_TRUSTED_PUB_B64",
32,
)
evidence_preimage = required_b64("OSYRA_OME_PREIMAGE_B64")
evidence_signature = required_b64("OSYRA_OME_SIGNATURE_B64", 64)
if not verify_receipt(
evidence_preimage,
evidence_signature,
published_trust_anchor,
):
raise RuntimeError("receipt signature does not match the trusted issuer")The current live Python write result cannot supply evidence_preimage in this
required detached shape:
- AP00011 returns a full SignedClaim envelope in
SignAndInsertResponse.encoded_cbor. - The SDK projects that field into
SignedClaim.encoded_cboras though it were the detached Form-B preimage. verify_receiptand signature-boundget_claim_bodyexpect the detached preimage and reject the full envelope.
Treat live write → offline verify and live verify-on-read as blocked until that contract is repaired and covered by a real SDK → Edge → AP00011 acceptance test.
What this proof does—and does not—cover
The receipt signature authenticates one exact claim preimage under a trusted key. It does not by itself prove that every workspace claim is present or that events occurred in a particular order.
OME's JMT root is an insertion-order-independent commitment to a key/value set. Relative to a trusted root, a valid proof can expose substitution or omission from the committed state. Reordering history while ending at the same set does not change that set commitment. Temporal order and completeness are separate claims and require independently verified witness evidence.
What you built
You:
- installed the pre-release OME Python SDK from an authorized checkout;
- parsed a real workspace UUID;
- constructed a local
Claimusing a valid fixed RFC UUIDv7 KAT value; - encoded the canonical nine-key Form-B preimage;
- signed and verified it with a local exercise key;
- rejected a well-formed tampered preimage; and
- separated a self-signed integrity exercise from production authenticity under a pretrusted workspace key.
No fake server, stub data, or unsupported network route was used.
Where to go next
- OME Python SDK how-to — exact current API and blocked network-path boundaries.
- Verified Memory concept — trust anchors, receipts, state commitments, and completeness.
Verified against:
osyra-ome1.0.0 pre-release, the AP00011 canonical claim encoder, and the AP00011SignAndInsertimplementation on 2026-07-23.