Siguiente
Continúa con Verificación para recorrer una proof completa desde los datos crudos hasta el commitment on-chain, o con Modos, SLA y finality para saber cómo se programan los batches y se hacen finales.
Esta página especifica, byte a byte, qué compromete CORE-M a la blockchain. Tres implementaciones independientes — el servicio de telemetría, el servicio de verificación, y cualquier verificador externo — deben calcular el hash idéntico a partir de las mismas entradas, por lo que el algoritmo es totalmente determinista y se describe aquí con exactitud. Si tu implementación diverge aunque sea en un solo byte, la verificación fallará por diseño.
Cada punto de telemetría se reduce a un único hash de 32 bytes usando doble SHA-256 — SHA-256 aplicado dos veces. Es la misma construcción que se usa en cada nodo interno del Merkle tree (ver más abajo), de modo que todo el árbol es uniforme:
leaf_hash = SHA256( SHA256( preimage ) )El preimage es la concatenación de bytes, en este orden exacto:
| # | Componente | Codificación | Detalles |
|---|---|---|---|
| 1 | version | 1 byte | Constante 0x01 — la versión del esquema de hash. |
| 2 | tenant_id_len | 2 bytes, uint16 big-endian | Longitud en bytes del tenant_id (UTF-8). |
| 3 | tenant_id | bytes UTF-8 | La cadena tenant_id. |
| 4 | device_id_len | 2 bytes, uint16 big-endian | Longitud en bytes del device_id (UTF-8). |
| 5 | device_id | bytes UTF-8 | La cadena device_id, sin terminador nulo. |
| 6 | timestamp | 8 bytes, uint64 big-endian | Época Unix en segundos. Los nanosegundos se truncan. |
| 7 | payload | bytes UTF-8 | El objeto de valores serializado con RFC 8785 JCS (más abajo). Sin prefijo de longitud. |
El tenant_id forma parte del hash: un verificador que lo omita calcula un hash
distinto, y por eso la API verify/raw exige
tenant_id. Los dos prefijos de longitud uint16 hacen que el límite entre
tenant_id y device_id sea inequívoco, de modo que dos pares tenant/device
distintos nunca pueden producir el mismo preimage.
El payload se canonicaliza con el JSON Canonicalization Scheme de RFC 8785 antes de hacerle el hash, de modo que payloads lógicamente idénticos siempre se serialicen a los mismos bytes:
Como las claves se ordenan de forma determinista, el orden en que un dispositivo
emite sus campos no importa: {"temperature":22.5,"humidity":65} y
{"humidity":65,"temperature":22.5} se canonicalizan a exactamente los mismos
bytes y por tanto producen el mismo hash.
Tomemos un punto concreto:
| Campo | Valor |
|---|---|
tenant_id | T1 |
device_id | D1 |
timestamp | 1711000000 (segundos Unix) |
| valores | {"temperature":22.5,"humidity":65} |
| → payload JCS | {"humidity":65,"temperature":22.5} |
| → preimage (hex) | 0100025431000244310000000065fbc9c07b2268756d6964697479223a36352c2274656d7065726174757265223a32322e357d |
| → leaf_hash (hex) | 9774d8af1f5e5a0457275ec82b482e0e1b46f6fc421720a279d0e661f99755d2 |
Leyendo el preimage de izquierda a derecha: 01 (versión) · 0002 (longitud de
tenant_id) · 5431 ("T1") · 0002 (longitud de device_id) · 4431 ("D1") ·
0000000065fbc9c0 (timestamp) · después los bytes UTF-8 del payload JCS. El
leaf_hash de 32 bytes es SHA256(SHA256(preimage)) — el valor que alimenta el
Merkle tree y, en última instancia, el commitment on-chain.
Anclar una transacción por punto sería lento y costoso, así que un batch de hashes de puntos se compromete en conjunto mediante un Merkle tree binario de doble SHA-256. Solo el root de 32 bytes va on-chain; cada punto conserva un Merkle path corto que prueba su pertenencia.
Reglas de construcción:
leaf_hash del batch son las hojas (leaves).parent = SHA256(SHA256(left || right)), donde left y right son los hashes
hijos de 32 bytes concatenados en orden de árbol (orden de bytes interno — sin
inversión de bytes).Todo el árbol, hojas incluidas, es doble SHA-256 uniforme — la misma construcción que el leaf hash de arriba.
flowchart TB R["Root = SHA256(SHA256(H01 || H23))"] H01["H01 = SHA256(SHA256(H0 || H1))"] H23["H23 = SHA256(SHA256(H2 || H3))"] H0["H0 (leaf)"] H1["H1 (leaf)"] H2["H2 (leaf)"] H3["H3 (leaf)"] R --> H01 R --> H23 H01 --> H0 H01 --> H1 H23 --> H2 H23 --> H3
El Merkle path de cada punto es la lista ordenada de hashes hermanos
(siblings) necesarios para ascender desde esa hoja hasta el root, cada uno
etiquetado con una dirección (si el hermano queda a la izquierda o a la
derecha). La hoja es el leaf_hash del punto — el mismo valor de 32 bytes que la
API de verificación y el almacén de proofs llaman data_hash. Un verificador
reproduce el path así:
current = leaf_hashfor each step in merkle_path: if step.is_right: # sibling is on the right current = SHA256(SHA256(current || step.hash)) else: # sibling is on the left current = SHA256(SHA256(step.hash || current))# current must now equal the Merkle rootPara la hoja H0 en el árbol de arriba, el path es [ {hash: H1, right}, {hash: H23, right} ]: combina con H1 a la derecha para obtener H01, luego con H23 a la
derecha para obtener el root. Recomputar el root a partir de un solo punto — sin
ninguno de los demás puntos del batch — es precisamente lo que hace la proof
portable.
Los paths se calculan en el momento del batch y se almacenan junto a cada hash (y
más tarde en la tabla anchor_proofs de PostgreSQL como JSONB). El recorrido
completo se muestra de principio a fin en
Verificación.
La transacción de anchoring tiene una salida: un OP_FALSE OP_RETURN que lleva un
payload de layout fijo. Tras los opcodes OP_FALSE OP_RETURN, los campos de datos
aparecen en este orden exacto:
| Offset | Campo | Tamaño | Tipo / codificación |
|---|---|---|---|
| 0 | Prefijo de protocolo | 6 bytes | ASCII "CORE-M" |
| 6 | merkle_root | 32 bytes | Merkle root SHA-256 del batch |
| 38 | batch_id | 16 bytes | UUID, binario crudo |
| 54 | timestamp | 8 bytes | uint64 big-endian, segundos Unix |
| 62 | data_point_count | 4 bytes | uint32 big-endian |
El payload total tras los opcodes es de 66 bytes.
OP_FALSE OP_RETURN "CORE-M" 6 bytes, ASCII protocol prefix <merkle_root> 32 bytes, SHA-256 <batch_id> 16 bytes, UUID binary <timestamp> 8 bytes, uint64 big-endian, Unix seconds <data_point_count> 4 bytes, uint32 big-endianEl algoritmo de hashing idéntico se comparte entre tres fronteras — telemetría (calculando hashes para el anchoring), verificación (recomputando a partir de datos crudos), y cualquier verificador externo. Ese determinismo compartido es toda la base de la garantía.
Siguiente
Continúa con Verificación para recorrer una proof completa desde los datos crudos hasta el commitment on-chain, o con Modos, SLA y finality para saber cómo se programan los batches y se hacen finales.