Skip to content

Verification

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:

  1. The original datadevice_id, timestamp (Unix seconds), and the telemetry payload.
  2. The proof — the Merkle path, txid, and BEEF transaction, from CORE-M’s verification API, the PostgreSQL proof store, or a portable bundle shared out-of-band.
  3. Access to the BSV blockchain — any node or block explorer.

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.

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:

  1. 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.

  2. Walk the Merkle path. Starting from data_hash, fold in each sibling using its direction until you reach the root:

    current = data_hash
    for step in merkle_path:
    if step.is_right:
    current = SHA256(current || step.hash)
    else:
    current = SHA256(step.hash || current)
    # current is the reconstructed Merkle root
  3. Check 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.

  4. 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}.

  1. Recompute. Canonicalize to {"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.
  2. Walk. Apply the two merkle_path entries above — both is_right — folding the hash to the right at each step to reach the root.
  3. Decode BEEF. Pull the OP_RETURN payload from the BEEF transaction, read the 32-byte merkle_root at offset 6, and check it matches your reconstructed root.
  4. Confirm. Verify the transaction’s Merkle proof places it in block 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:

FieldPurpose
canonical_hash_inputsThe exact device_id, Unix-seconds timestamp, and JCS payload used to compute the hash.
merkle_pathThe sibling hashes with directions, leaf to root.
merkle_rootThe reconstructed/committed root.
txidThe anchoring transaction ID.
raw_tx or beef_hexThe transaction, as BEEF or raw, for offline checking.
block_header_chainBlock headers establishing inclusion and chain context.
confirmation_depthHow many confirmations deep the transaction is.
anchor_modemerkle_batch, per_event, or hash_chain.
verification_stepsHuman-readable steps mirroring the procedure above.
schema_versionBundle 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:

  1. The transaction itself.
  2. Its ancestor transactions (proving the inputs are valid).
  3. Merkle proofs linking each transaction to its block header.
  4. The relevant block headers.

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.