You've reached the end of the Anchoring section
Back to the Anchoring Overview, or review the exact commitment in Hashing & Merkle Trees.
The point of anchoring is that a third party can prove a data point existed and is unaltered without trusting CORE-M. This page shows the two verification APIs, the manual procedure a verifier follows, the self-contained portable proof bundle, and what BEEF is. It assumes you know the canonical hash and Merkle path from Hashing & Merkle Trees.
To verify a point you need only:
device_id, timestamp (Unix seconds), and the
telemetry payload.txid, and BEEF transaction, from CORE-M’s
verification API, the PostgreSQL proof store, or a portable bundle shared
out-of-band.No access to CORE-M’s running services is required for the verification itself.
CORE-M exposes two endpoints that automate the lookup. See API Overview for auth and base URLs.
Use this when you already hold the 32-byte data_hash.
GET /api/v1/verify/hash/<data_hash_hex>Looks up the proof store and returns the proof for that hash. If no proof exists,
the response is NOT_FOUND with "no blockchain proof found for this data hash — it may be pending or nonexistent". If the same hash was anchored in more than one
batch (re-anchoring), the most recent proof — the one with the highest
block_height — is returned.
Use this when you have the original fields and want CORE-M to compute the hash for
you. It recomputes SHA256(device_id_utf8 || timestamp_uint64_be || jcs_payload_utf8) — the same algorithm as the telemetry service — looks up the
result, and returns the same response shape as verify-by-hash.
curl -X POST https://<host>/api/v1/verify/raw \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "device_id": "D1", "timestamp": "2024-03-21T05:46:40Z", "numeric_values": {"temperature": 22.5, "humidity": 65}, "string_values": {} }'If the supplied data differs even slightly from what was anchored, the recomputed
hash will not match any stored proof and the response is NOT_FOUND.
Both endpoints return the same proof:
{ "txid": "c3d4e5f6a1b2...", "block_height": 850000, "block_hash": "00000000000000000abc...", "confirmed_at": "2024-03-21T08:31:12Z", "batch_id": "8f14e45f-ceea-467a-9f3b-2c1d4e5f6a7b", "merkle_path": [ { "hash": "9a0c...", "is_right": true }, { "hash": "4f7b...", "is_right": true } ], "beef_hex": "0100beef..."}merkle_path is an array of {hash, direction} entries (the direction is
expressed as is_right), and beef_hex is the hex-encoded BEEF transaction —
explained below.
A verifier does not have to trust the API’s verified flag — they can redo the
proof end to end. Four steps:
Recompute the data hash. From the original device_id, timestamp (Unix
seconds), and payload, compute
SHA256(device_id_utf8 || timestamp_uint64_be || jcs_payload_utf8) exactly as
in Hashing & Merkle Trees. Confirm it equals the
data_hash in the proof. This proves the data has not been altered.
Walk the Merkle path. Starting from data_hash, fold in each sibling using
its direction until you reach the root:
current = data_hashfor step in merkle_path: if step.is_right: current = SHA256(current || step.hash) else: current = SHA256(step.hash || current)# current is the reconstructed Merkle rootCheck the OP_RETURN in the BEEF transaction. Decode the BEEF blob, find the
OP_FALSE OP_RETURN output, and read its payload:
"CORE-M" || merkle_root(32) || batch_id(16) || timestamp(8) || count(4).
Confirm the embedded merkle_root equals the root you reconstructed in step 2.
This proves your point is committed by this on-chain transaction.
Confirm block inclusion. The BEEF blob carries the transaction’s Merkle
proof within its block, so you can verify inclusion at the claimed
block_height offline. Optionally cross-check against any BSV node or block
explorer that the block hash at that height matches and contains the txid.
The block’s position in the chain is your lower bound on when the data existed.
Take the point from Hashing & Merkle Trees:
device_id="D1", timestamp=1711000000, payload
{"temperature":22.5,"humidity":65}.
{"humidity":65,"temperature":22.5}, encode
"D1" as 0x4431 and the timestamp as 0x0000000065FBC9C0, concatenate
device_id || timestamp || payload, and SHA-256 the result. Confirm it equals
the data_hash returned by the API.merkle_path entries above — both is_right — folding
the hash to the right at each step to reach the root.OP_RETURN payload from the BEEF transaction, read
the 32-byte merkle_root at offset 6, and check it matches your reconstructed
root.850000, and (optionally) that a block explorer agrees the block at height
850000 has hash 00000000000000000abc... and contains the txid.If all four steps pass, the point provably existed at the time of that block and has not changed since.
For any final data point, the API can return a self-contained portable proof
bundle (request include_bundle=true on verify-by-hash). It carries everything
an external verifier needs to validate the commitment without calling CORE-M
again after download.
The bundle contains:
| Field | Purpose |
|---|---|
canonical_hash_inputs | The exact device_id, Unix-seconds timestamp, and JCS payload used to compute the hash. |
merkle_path | The sibling hashes with directions, leaf to root. |
merkle_root | The reconstructed/committed root. |
txid | The anchoring transaction ID. |
raw_tx or beef_hex | The transaction, as BEEF or raw, for offline checking. |
block_header_chain | Block headers establishing inclusion and chain context. |
confirmation_depth | How many confirmations deep the transaction is. |
anchor_mode | merkle_batch, per_event, or hash_chain. |
verification_steps | Human-readable steps mirroring the procedure above. |
schema_version | Bundle schema version. |
The bundle validates against the published JSON schema, so a verifier can machine-check its structure before trusting its contents.
BEEF — Background Evaluation Extended Format — is a compact binary format for SPV (Simplified Payment Verification) proofs. Instead of requiring a full node and the whole block-header chain, BEEF bundles into one blob everything needed to verify a transaction:
CORE-M builds the BEEF blob when the anchoring transaction is confirmed — combining
the anchoring transaction, its ancestor (funding) transaction, and the Merkle
proofs for both — and stores the hex with the proof record. A
verifier decodes BEEF to extract the OP_RETURN, verify block inclusion via the
embedded Merkle proof, and confirm the transaction belongs to the longest chain —
all of which is what makes step 3 and step 4 above possible offline.
Confirmed proofs are persisted in PostgreSQL (anchor_proofs): data_hash,
batch_id, merkle_path (JSONB), txid, beef_hex, block_height,
block_hash, confirmed_at, tenant_id, and device_id.
You've reached the end of the Anchoring section
Back to the Anchoring Overview, or review the exact commitment in Hashing & Merkle Trees.