ZOOBC / DESARROLLADORES
Desarrolladores
Todo lo que un programa necesita para leer o escribir en esta cadena. Una pasarela es una puerta de entrada HTTPS: termina el TLS, limita la tasa de solicitudes y reenvía la API de la cadena a los nodos, para que un navegador pueda comunicarse con ZooBC sin ejecutar uno.
¿Sobre qué cadena estás construyendo?
Los ejemplos de abajo se ejecutan contra la pasarela pública de la TestNet. Las direcciones, los saldos y los hashes de transacción no se trasladan de una red a otra, así que confirma en cuál estás antes de actuar.
La MainNet abre el 14 de febrero de 2027
Una sola URL base
Todo lo relativo a la cadena está bajo /api/v1/ en esta pasarela, en el mismo origen que esta página: sin CORS que negociar, sin clave que obtener, sin plan de tarifas.
zoobc.network es la pasarela pública de la TestNet de ZooBC, no de la MainNet. Los ejemplos de abajo se ejecutan contra la red de pruebas.
curl https://this-gateway/api/v1/blockchain/status
Las lecturas y las escrituras van a lugares distintos, y eso importa más de lo que parece:
| GET | se reenvía a un nodo de archivo, que guarda el historial profundo: bloques, transacciones y cuentas a cualquier altura. |
|---|---|
| POST | se reenvía directamente a un nodo completo, nunca a uno de archivo. Enviar una transacción necesita un mempool, y un nodo de archivo es un servicio de historial de solo lectura: antes respondía a los POST con un 404, por eso en su momento la difusión a través de una pasarela no funcionaba. |
Las rutas que usarás primero
| /api/v1/blockchain/status | altura, altura confirmada, último bloque |
| /api/v1/blocks/latest | el bloque más reciente, completo |
| /api/v1/accounts/<address> | saldo y saldo disponible. Acepta una dirección ZBC_… o los 36 bytes en hexadecimal en bruto |
| /api/v1/accounts/<address>/tokens | todos los tokens que tiene esa cuenta |
| /api/v1/transactions?limit=25 | transacciones firmadas recientes |
| /api/v1/movements/latest | cambios de saldo que hace la propia cadena: recompensas, liberación gradual, reembolsos |
| /api/v1/exchange/markets | mercados abiertos; …/orderbook?market=<id> para la profundidad, …/offers para los paquetes de intercambio |
| /api/v1/tokens | todos los tokens, con su suministro y sus decimales |
| /api/v1/release/list | versiones publicadas y sus hashes, contra los que comprueba /verify |
| /api/v1/registry/nodes | el registro de nodos; también
/gateways, /relays, /archivals |
Algunas rutas solo existen en un nodo de archivo y otras solo en un nodo completo. Esta pasarela prueba primero con el de archivo y reintenta con la API del nodo ante un 404 con cuerpo vacío, así que rara vez tendrás que preocuparte por cuál es cuál; pero un 404 con cuerpo es una respuesta real, no un desvío equivocado.
Enviar una transacción
Constrúyela y fírmala con zbc-cli, que recibe sus parámetros como JSON por stdin, de modo que una clave privada nunca acaba en ps ni en el historial de tu shell:
echo '{"sender_privkey":"…","recipient":"ZBC_…","amount":100000000}' \
| zbc-cli send --json-input --api https://this-gateway
1 ZBC son 108 unidades atómicas. Todos los importes de la API son atómicos: no hay decimales en ninguna parte del formato de transmisión.
Lo que se está construyendo sobre estas vías
Los mismos tipos de transacción a los que llamas aquí son la base del trabajo en curso:
| Agentes de IA | transferencias programadas, depósito en garantía y multisig usados como un tope de gasto que un agente autónomo no puede elevar por su cuenta. Funciona hoy, con una referencia en Python sin dependencias contra la testnet pública. |
|---|---|
| IA descentralizada | especialistas en modelos operados de forma independiente, ensamblados del lado del usuario y liquidados de forma abierta, para que ninguna empresa se interponga entre una persona y la inteligencia que usa. Rumbo tras la mainnet. |
| ProxCell | confirmación optimista local para pagos en persona, con capas de liquidación geográfica por encima. Documento de trabajo. |
Referencia completa
Cada ruta, sus parámetros y la estructura de su respuesta:
Abrir la referencia de la API → · vista alternativa
Ambas direcciones sirven el mismo documento.
Descargas
Los archivos que un operador ha publicado en esta pasarela se sirven en /dl/<name>. Los nombres son un único segmento de [A-Za-z0-9._-], y todo se envía como adjunto y nunca se renderiza: este origen también sirve la billetera, y una página renderizada aquí heredaría el origen de la billetera.
Los paquetes de versión están en /releases/<version>/<arch>/, y /releases/latest indica el actual. El instalador del nodo está en
/install.
¿Está sana esta pasarela? comprobando…
/gateway/status informa de lo que esta pasarela sabe de sí misma y de los nodos a los que puede llegar: modo, dominio, cuántos nodos hay en su lista de permitidos y cuáles responden.
/gateway/system-stats informa sobre la máquina: CPU, memoria, disco, tiempo de actividad. De ahí salen las cifras de la página de la pasarela.
curl https://this-gateway/gateway/status
curl https://this-gateway/gateway/system-stats
Ninguno necesita clave. Ninguno expone nada sobre la cadena; para eso, usa /api/v1/…, más arriba.
Consultar un nodo específico
Las rutas de la cadena responden desde el nodo de archivo que elija esta pasarela. Para consultar un nodo concreto, pon su IP en el nombre de host, con guiones en lugar de puntos; un -p<port> opcional elige el puerto de la API:
curl https://ip-192-168-1-100.this-gateway/api/v1/node/info
curl https://ip-192-168-1-100-p8080.this-gateway/api/v1/node/info
Solo responden los nodos que están en la lista de permitidos de esta pasarela. Cualquier otro se rechaza en lugar de reenviarse, así que el nombre de host no puede usarse para llegar a hosts arbitrarios a través de la pasarela.
Retransmisor
La voz, el video y la pantalla compartida van de igual a igual; cuando un cortafuegos bloquea un enlace directo, un retransmisor transporta el tráfico a cambio de crédito. /relay/info informa de su dirección, su tarifa y el hash génesis de la cadena en la que liquida; una billetera lo compara con el de su propio nodo antes de pagar, porque un retransmisor de otra cadena no puede acreditar lo que se le paga.
curl https://this-gateway/relay/info
Panel de administración
Los controles propios de la pasarela están en zoobc.network/admin. El panel está protegido por admin_api_key de /etc/zoobc/gateway.json, que el instalador genera para cada máquina: no hay ninguna clave predeterminada.
La lista de permitidos
Una pasarela solo reenvía a los nodos que se le han indicado. Esa lista es lo que impide que la forma de nombre de host ip-… convierta la pasarela en un proxy abierto.
| POST /gateway/allowlist/add | admite un nodo, {"ip":"…","port":8080} |
| POST /gateway/allowlist/remove | quita uno |
| POST /gateway/allowlist/refresh | vuelve a leer el registro y a comprobar el estado |
| GET /gateway/allowlist | lo que está admitido actualmente |
Las tres escrituras necesitan la clave de administración. En modo público, la pasarela también descubre nodos a partir del propio registro de la cadena; en modo privado, solo usa static_nodes.
Herramientas de versiones
La calculadora de hash de archivos calcula un SHA-256 en el navegador para cualquier cosa que quieras comprobar a mano, y /verify comprueba un paquete descargado contra los hashes que ha publicado la cadena.
Los dos documentos
Todo lo que necesita un equipo de billeteras de hardware está especificado aquí, y de forma completa en los dos PDF de abajo. Ambos se verificaron contra el nodo ZooBC v0.4.0 (zoobc-main en 7ad161a4) y contra vectores de transacción que aceptó un nodo v0.4.0 en vivo.
La versión 2.1 sustituye al borrador 1 en un aspecto sustancial: las firmas de las transacciones ahora están vinculadas a la cadena. Dos cambios rompen la compatibilidad para quien haya construido sobre el borrador 1: el digest de firma y el cuerpo de ApprovalEscrow. Nada implementa la antigua construcción v0.3.2, así que construye solo con el documento actual.
Resumen en una página
| Esquema de firma | Ed25519 (RFC 8032, puro, sin variante con prehash, sin cadena de contexto). Firma de 64 bytes, clave pública de 32 bytes. |
|---|---|
| Qué se firma | Ed25519.sign(sk, SHA3-256("ZBC-TX" ‖ genesis_hash(32) ‖ unsigned_bytes)). El digest de 32 bytes es el mensaje Ed25519. |
| Vinculación a la cadena | El digest abarca el hash del bloque génesis de la cadena, así que una firma hecha para una cadena no puede reutilizarse en otra. El formato de transmisión no cambia; el hash génesis no es un campo de la transacción. |
| Etiqueta de firma | "ZBC-TX", seis bytes ASCII 5a 42 43 2d 54 58. Sin terminador ni prefijo de longitud. |
| Función hash | SHA3-256 (FIPS 202). Ni Keccak-256 ni SHA-256. |
| Derivación de claves | Mnemónico BIP-39, luego semilla (PBKDF2-HMAC-SHA512, 2048 rondas), luego SLIP-0010 Ed25519, ruta m/44'/883'/account'. Tres niveles reforzados, sin niveles de cambio ni de índice. |
| Tipo de moneda SLIP-44 | 883 (registrado: 883 | ZBC | ZooBC). |
| Cuenta (en transmisión) | 36 bytes: u32le(0) ‖ pubkey(32). La forma hexadecimal es la que acepta la API JSON. |
| Dirección (visualización) | ZBC_ más 56 caracteres base32 en 7 grupos de 8. Suma de verificación de 3 bytes = SHA3-256(pubkey ‖ "ZBC")[0..3]. |
| Byte de versión | 0x01, sin cambios. No se incrementó con la vinculación a la cadena y no es un discriminador de versión. |
| Unidad nativa | 1 ZBC = 100 000 000 unidades atómicas (8 decimales). Los importes y las comisiones son unidades atómicas u64 en little-endian. |
| Orden de bytes | Todo lo que se transmite está en little-endian, excepto los índices hijos de SLIP-0010 (big-endian, según el estándar). |
| Hash de la transacción | tx_hash = SHA3-256(unsigned_bytes ‖ signature); tx_id = int64le(tx_hash[0..8]). El hash génesis solo entra en el digest de firma. |
| Protección contra la repetición | Vinculación a la cadena, más unicidad del hash, más una ventana de inclusión de 3600 s. No hay nonce. |
| Comisión mínima | 0.025 ZBC de base (2 500 000 unidades atómicas) multiplicado por la escala de comisiones de la red, más el alquiler por tamaño. Pregúntale al nodo: GET /api/v1/blockchain/estimate-fee. |
| Tipos de nivel 1 | 1 SendZBC, 11 TransferToken, 4 ApprovalEscrow. |
| Descubrimiento de la cadena | GET /api/v1/node/info devuelve genesis_hash, signing_version: 2 y signing_tag: "ZBC-TX". Léelo del endpoint al que vayas a difundir. |
Lee primero estos cuatro
Cuatro puntos que condicionan una implementación más que todo lo demás.
| 1 | No hay nonce ni endpoint de transacciones sin firmar. ZooBC se basa en cuentas, pero aquí eso no implica ninguna de las dos cosas. El host serializa la transacción por sí mismo; la estructura de abajo está completa y fijada por vectores. |
|---|---|
| 2 | La firma abarca un prefijo etiquetado, "ZBC-TX" más el hash génesis de la cadena, no solo los bytes de la transacción. Si te equivocas aquí, todas las firmas fallan con un error genérico. |
| 3 | El endpoint de difusión no acepta el blob de la transacción firmada. Acepta los bytes del cuerpo de la transacción más el sobre como claves JSON con nombre, y reconstruye el sobre por sí mismo. |
| 4 | El historial se devuelve del más reciente al más antiguo, limitado por limit. Dos endpoints juntos dan una imagen completa, y el importe depende del tipo en lugar de ser un campo del sobre. |
Derivación de claves y direcciones
Referencia: zoobc-signer/extension/lib/zoobc-crypto.js, idéntica byte a byte a la billetera de referencia; equivalentes del lado del nodo en src/crypto/slip10.cpp.
seed = PBKDF2-HMAC-SHA512(NFKD(mnemonic), "mnemonic" + passphrase, 2048, 64)
I = HMAC-SHA512("ed25519 seed", seed); k = I[0..32], c = I[32..64]
for index in [44, 883, account]: # all hardened
data = 0x00 || k || u32be(index + 0x80000000)
I = HMAC-SHA512(c, data); k = I[0..32], c = I[32..64]
pubkey = Ed25519.publicKeyFromSeed(k) # k is the 32-byte seed
La cuenta 0 es m/44'/883'/0', la cuenta 1 es m/44'/883'/1', y así sucesivamente. La derivación de claves no se ve afectada por la vinculación a la cadena: un mismo conjunto de claves sirve para todas las cadenas ZooBC, solo cambia la firma.
La dirección de visualización se construye a partir de la clave pública, no de la cuenta de 36 bytes:
buf[35] = pubkey(32) || "ZBC"
h = SHA3-256(buf); buf[32..35] = h[0..3] # 3-byte checksum
s = base32(buf) # RFC 4648 A-Z2-7, no padding, 56 chars
address = "ZBC_" + s[0..8] + "_" + s[8..16] + ... # 7 groups, canonical length 66
La decodificación acepta guiones bajos, guiones, espacios o ningún separador, en mayúsculas o minúsculas, y la suma de verificación debe recalcularse y compararse. La suma de verificación SHA3 de 3 bytes de arriba es la definición que usan el código y la cadena, así que implementa a partir de ella y de la implementación de referencia de la especificación, que reproduce las direcciones de ejemplo.
Formato de transmisión de la transacción sin firmar
Fuente de verdad: src/util/transaction_util.cpp:144-270. El formato de transmisión no cambió en v0.4.0. Ninguna longitud ni desplazamiento de abajo se ve afectado por la vinculación a la cadena.
| # | tamaño | campo | notas |
|---|---|---|---|
| 1 | 4 | transaction_type | u32le, consulta el catálogo de tipos más abajo |
| 2 | 1 | versión | siempre 0x01; el nodo rechaza cualquier otro valor |
| 3 | 8 | marca de tiempo | u64le, segundos Unix, mayor que 0 |
| 4 | 36 | remitente | 00000000 ‖ pubkey(32) para un firmante ZBC |
| 5 | 4 o 4+n | destinatario | 36 bytes para ZBC; u32le(type) ‖ carga útil para los demás; 02 00 00 00 solo cuando no hay destinatario |
| 6 | 8 | comisión | u64le, unidades atómicas |
| 7 | 4 | body_length | u32le |
| 8 | n | cuerpo | estructura según el tipo |
| 9 | 4 o variable | depósito en garantía | sin depósito en garantía: los 4 bytes 02 00 00 00. Con depósito en garantía: approver(36) ‖ commission u64le ‖ timeout u64le ‖ instr_len u32le ‖ instruction ‖ multi_party(1) |
| 10 | 4 | message_length | u32le |
| 11 | m | mensaje | texto plano, 256 bytes o menos en las transacciones ordinarias |
El byte final multi_party del bloque de depósito en garantía es obligatorio. Omitirlo, o escribir el marcador de ausencia de depósito en garantía con otro valor, solo produce "Invalid transaction signature" en el nodo, sin más diagnóstico.
Ejemplo resuelto, validado en la cadena
SendZBC de 1 ZBC a uno mismo, comisión de 0.05 ZBC, marca de tiempo 1700000000, sin depósito en garantía, sin mensaje. Es el vector 0 de los siete de la especificación.
01000000 type = 1 (SendZBC)
01 version
00f1536500000000 timestamp 1700000000
00000000 5e8eb28d...2152 sender (type 0 + pubkey)
00000000 5e8eb28d...2152 recipient (type 0 + pubkey)
404b4c0000000000 fee 5 000 000
08000000 body length 8
00e1f50500000000 body: amount 100 000 000
02000000 no-escrow marker
00000000 message length 0
Son 113 bytes. La preimagen de firma antepone 38 más:
5a42432d5458 tag "ZBC-TX" 6 bytes
090ab3c7...a61878 genesis hash 32 bytes
0100000001...00000000 the 113 bytes above 113 bytes
---------
151 bytes
digest = SHA3-256(preimage) = f37e5340...4644fc, firma 9a3b044d...03bb07, tx_hash = f00a6692...ce78b0. Un nodo v0.4.0 en vivo devolvió HTTP 202 y exactamente ese hash.
Procedimiento de firma, lado del dispositivo
input : account_index, genesis_hash(32), unsigned_bytes
1. k = SLIP10(m/44'/883'/account_index')
2. pub = Ed25519.pub(k); self = 00000000 || pub
3. parse unsigned_bytes; every length must land exactly at the end
4. require version == 1 and sender == self
5. decode body per type; render the approval screen; wait for the user
6. digest = SHA3-256("ZBC-TX" || genesis_hash || unsigned_bytes)
7. sig = Ed25519.sign(k, digest) # the digest IS the message
8. return sig(64), and tx_hash = SHA3-256(unsigned_bytes || sig) for the host to cross-check
Usa SHA3 en streaming para que el paso 6 no necesite toda la transacción en RAM. Introduce los 6 bytes de la etiqueta, luego los 32 bytes del génesis y después la transacción.
La etiqueta es el error de implementación más probable de toda la especificación. "ZBC-TX" son seis bytes ASCII, 5a 42 43 2d 54 58, sin terminador NUL y sin prefijo de longitud. Un firmware que añade un terminador a la cadena de texto, o que escribe siete bytes, produce una firma de apariencia válida que falla en la cadena con un error genérico y sin diagnóstico. Pruébalo contra el vector 0 antes que nada.
Lo que hace y lo que no hace la vinculación a la cadena
El host proporciona los 32 bytes del génesis; el dispositivo no los busca. Por tanto, la firma es independiente de la cadena, y una sola ruta de código sirve para testnet, devnet, mainnet y cualquier cadena ZooBC privada o paralela.
| Hace | Detiene la repetición entre cadenas. Una firma hecha para la cadena A no se verificará en la cadena B. Para eso se creó, y lo cumple por completo. |
|---|---|
| No hace | Impedir que un host comprometido indique la cadena equivocada. El dispositivo firma para cualquier hash que se le entregue. |
Así que sigue siendo conveniente una tabla de hashes génesis conocidos en el firmware, pero con un único fin: mostrar en pantalla un nombre de red fiable, recurriendo a "UNKNOWN CHAIN" más los primeros 8 caracteres hex cuando el hash no está en la tabla.
No rechaces por defecto las cadenas no reconocidas. El hash de devnet cambia con cada relanzamiento, y las cadenas privadas o paralelas son normales en ZooBC, así que un rechazo estricto deja el dispositivo inservible para la integración y para despliegues legítimos. Si un fabricante quiere ese bloqueo, que sea un ajuste explícito del usuario, desactivado por defecto.
Tipos de transacción: lo que debería incluir un firmware estándar
Id de tipo = group + 256 × subtype. Existen cincuenta y seis tipos. Estos tres cubren pagar en ZBC, pagar con cualquier token y completar un depósito en garantía, y sus cuerpos son pequeños y fijos.
| id | nombre | cuerpo | destinatario |
|---|---|---|---|
| 1 | SendZBC | amount u64le (8) | obligatorio, cualquier tipo |
| 11 | TransferToken | token_id i64le (8) ‖ amount i64le (8) ‖ fee_in_token u8 opcional | obligatorio |
| 4 | ApprovalEscrow | approval u32le ‖ escrowed_transaction_hash (32) = 36 bytes | marcador vacío; el remitente es el aprobador |
token_id es un valor de 64 bits con signo derivado de un hash, así que puede ser negativo. Las monedas génesis son los ids 1 a 14; el propio ZBC es el id 0 y nunca aparece en TransferToken. Cada token tiene su propio decimals (0 a 8), que el dispositivo no puede verificar.
ApprovalEscrow cambió en v0.4.0. El cuerpo era approval más un id de 8 bytes (12 bytes); ahora es approval más el hash de 32 bytes de la transacción en depósito en garantía (36 bytes), y el analizador rechaza cualquier otra longitud. El motivo afecta directamente a las billeteras de hardware: un firmante que solo veía un id de 8 bytes no podía comprobar qué estaba liberando, y un id de 8 bytes se puede encontrar con una búsqueda de 2^64 contra un depósito en garantía falso mostrado al dispositivo. Con el hash completo, el dispositivo puede verificar lo que aprueba.
Lo que verifica el dispositivo
| Estructura | Analiza los 11 campos. Cada prefijo de longitud debe ser coherente y el búfer debe terminar exactamente después del mensaje. Cualquier otra cosa: rechazar. |
|---|---|
| Versión | Debe ser 1. |
| Remitente | Debe coincidir con la cuenta derivada del propio dispositivo para la ruta solicitada. El dispositivo nunca confía en los bytes del remitente que recibe; vuelve a derivar la cuenta y compara los 36 bytes. Esta es toda la garantía de "desde dónde sale". |
| Tipo | Debe estar en el conjunto admitido, o el modo experto debe estar activado. |
| Longitud del cuerpo | 8 para SendZBC, 16 o 17 para TransferToken, 36 para ApprovalEscrow. Bytes adicionales: rechazar. El nodo aceptaría un cuerpo de SendZBC más largo e ignoraría el resto, y así es exactamente como viajaría una carga oculta. |
| Plazo del depósito en garantía | Debe ser mayor que la marca de tiempo de la transacción. |
| Hash génesis | Debe tener exactamente 32 bytes. El dispositivo no lo valida contra una lista, pero una longitud incorrecta es una solicitud mal formada. |
| Nunca | Aceptar un digest precalculado para firmarlo. El dispositivo calcula el hash de los bytes por sí mismo, incluidos la etiqueta y el génesis. |
| Límites de tamaño | Recomendado: transacción sin firmar de 2 KB o menos, mensaje de 256 B, instrucción de depósito en garantía de 512 B, rechazando cualquier cosa mayor con TOO_LARGE. Las transacciones de nivel 1 ocupan menos de 300 bytes, pero un ApprovalEscrow llega con una segunda transacción adjunta para su verificación, así que el límite debe cubrir ambos búferes. Las cargas más grandes corresponden a las billeteras de software. |
Lo que el usuario aprueba en pantalla
Por pantalla, en este orden, adaptado del firmante de referencia validado en la cadena.
| Red | Nombre de la tabla del firmware para el hash génesis proporcionado; "UNKNOWN CHAIN" más los primeros 8 caracteres hex cuando no está en la tabla. |
|---|---|
| Tipo | "Send ZBC", "Send token" o "Approve escrow". |
| Importe | amount / 1e8 con el símbolo ZBC. |
| Destinatario | Dirección completa, nunca abreviada. Una dirección recortada por el medio es justo lo que burla una clave parecida generada a medida (vanity). |
| Comisión | unidades atómicas / 1e8 ZBC. |
| Total que sale | importe más comisión como un solo número, solo para importes en ZBC. Un importe en tokens y una comisión en ZBC son unidades distintas y no deben sumarse. |
| Mensaje | Se muestra si es imprimible; si no, "N bytes (binary)" más los primeros 8 caracteres hex de su SHA3-256. |
| Depósito en garantía | Aprobador completo, comisión, plazo como fecha y hora, texto de la instrucción. |
| Token (tipo 11) | Id del token (decimal con signo) y el importe atómico. Si el host proporciona decimals o symbol como indicaciones de visualización, muestra el importe escalado con la etiqueta "as reported by the app", porque el dispositivo no puede verificarlos. |
| Aprobación del depósito en garantía | "APPROVE" o "REJECT" en letra grande, luego el detalle verificado del depósito en garantía y después la comisión. |
No es necesario mostrar las marcas de tiempo. No son significativas para el usuario y el nodo impone la ventana.
Protocolo entre host y dispositivo
El transporte es la propia capa USB/HID del fabricante, con su fragmentación existente. Lo que importa es el contenido.
| comando | solicitud | respuesta |
|---|---|---|
| GET_APP_VERSION | ninguna | versión del firmware, conjunto de tipos admitidos, tamaño máximo de tx |
| GET_PUBLIC_KEY | account_index, confirm_on_device | pubkey(32), address (cadena de 66 caracteres). Al confirmar, el dispositivo muestra la dirección completa para que el usuario la compare con la del sitio web. |
| SIGN_TX | account_index, genesis_hash(32), unsigned_tx_bytes | signature(64), tx_hash(32), o un rechazo tipificado: SENDER_MISMATCH, UNSUPPORTED_TYPE, MALFORMED, USER_REJECTED, TOO_LARGE, ESCROW_HASH_MISMATCH |
| SIGN_MESSAGE | account_index, message_bytes | signature(64), Ed25519 en bruto, consulta la regla de abajo |
genesis_hash es esencial, no una indicación de visualización. En el borrador 1, el parámetro de red podía ser un enum, porque solo elegía una etiqueta. Ahora está dentro del digest, así que deben ser los 32 bytes reales, y un valor incorrecto produce una firma que ninguna cadena aceptará.
La regla de la firma en bruto. Un comando de firma en bruto nunca debe firmar una entrada de exactamente 32 bytes, y solo debería firmar entradas que pueda mostrar como texto. Una entrada en bruto de 32 bytes podría ser el digest SHA3 de una transacción que el usuario nunca vio, lo que convierte el comando de mensajes en una firma de transacciones a ciegas. Rechazar la longitud 32 cierra ese agujero sin ningún cambio en la cadena.
La secuencia completa
Website (page) Companion / host device Gateway / node
|-- connect ---------->| | |
| |-- GET_PUBLIC_KEY(0) ----->| (shows ZBC_... address) |
|<-- address ----------|<-- pubkey, address -------| |
|-- GET /api/v1/node/info (genesis_hash, signing_version, height) ---------->|
|-- GET /api/v1/accounts/<addr> (balances) --------------------------------->|
|-- GET /api/v1/accounts/<addr>/tokens, /api/v1/tokens/<id> (decimals) ------>|
| user fills the form | |
|-- GET /api/v1/blockchain/estimate-fee?type=1&body_length=8 ---------------->|
|<-- {minimum_fee, timestamp} ------------------------------------------------|
|-- fields {type, recipient, body, fee, message} -->| |
| | serialize the 11 fields | |
| |-- SIGN_TX(0, genesis, -->| parse, verify, display, |
| | bytes) | user approves |
| |<-- signature, tx_hash ---| |
|<-- signature --------| | |
|-- POST /api/v1/transactions {JSON} ---------------------------------------->|
|<-- 202 {status:"success", transaction_hash} --------------------------------|
|-- GET /api/v1/transactions/<hash>/status (staging, mempool, confirmed) --->|
El host compara el transaction_hash del nodo con el tx_hash del dispositivo. Deben ser idénticos; de lo contrario, el host alteró los bytes después de la firma.
Lee genesis_hash y signing_version del endpoint al que vayas a difundir, ya que esa es la cadena que juzga la firma.
Identidad de la cadena
El único identificador de red que tiene ZooBC es el hash del bloque génesis. Los nodos lo comparan antes de conectarse como pares; no hay ningún byte de id de red. GET /api/v1/node/info lo devuelve junto con dos campos nuevos en v0.4.0:
"genesis_hash": "090ab3c7...", "signing_version": 2, "signing_tag": "ZBC-TX"
signing_version ausente significa que el nodo es anterior a v0.4.0 y aplica el digest heredado no vinculado. Un host que solo admite cadenas actuales puede tratar su ausencia como "no firmes aquí". Un nodo imprime el hex en minúsculas y la API de archivo en mayúsculas, así que decodifica sin distinguir mayúsculas de minúsculas y elimina un 0x inicial si lo hay. Los bytes son los mismos en ambos casos.
De dónde leerlo. Una pasarela responde a /api/v1/node/info desde el servicio de archivo y no desde un nodo. El servicio de archivo transmite los dos campos de firma del nodo que replica, emitiéndolos exactamente cuando el nodo los informa, a partir de la compilación 7ad161a4. Lee signing_version de un endpoint de nodo, o de una pasarela con esa compilación o una posterior, y trata su ausencia como "pregunta a un nodo" y no como una respuesta.
Difusión
POST /api/v1/transactions. Sin cambios en v0.4.0, ningún campo nuevo. transaction_body_bytes es solo el cuerpo: el nodo reconstruye el sobre a partir de los campos con nombre, antepone su propio hash génesis y la etiqueta, y recalcula el digest, así que cada campo debe ser igual a lo que se firmó.
{
"version": 1,
"timestamp": 1700000000,
"transaction_type": 1,
"fee": 5000000,
"sender_account_address": "00000000 5e8eb28d ... 2152",
"recipient_account_address": "00000000 5e8eb28d ... 2152",
"transaction_body_bytes": "00e1f50500000000",
"signature": "9a3b044d ... 03bb07",
"message_hex": "...optional...",
"escrow": { "approver_address": "...", "commission": 0, "timeout": 1700003600 }
}
El éxito es HTTP 202 con {"status":"success","duplicate":false,"transaction_hash":"..."}. duplicate:true significa que el nodo ya la tiene, así que no la reenvíes. Trata también 200 como éxito. El fallo es 400 con un cuerpo de error, o 503 bajo carga.
Una prueba de integración útil: el pool de preparación comprueba primero la firma, así que una transacción que llega a un error posterior como "Sender account does not exist" ya ha superado la verificación de firma. Puedes validar tu ruta de firma con una clave sin fondos.
Cuando se rechaza una firma
Hoy, una discrepancia de cadena no se distingue de ningún otro fallo de firma. Un digest heredado y un digest de la cadena equivocada devuelven exactamente el mismo cuerpo:
{"success":false,"error":"Transaction validation failed: ValidationError: Invalid transaction signature","code":400}
Así que revisa las causas en este orden, de la más probable a la menos probable:
| 1 | Etiqueta omitida, terminada en NUL o escrita con siete bytes. |
|---|---|
| 2 | Uso de Keccak-256 en lugar de SHA3-256. |
| 3 | Un hash de génesis que no coincide con la cadena a la que se difunde. |
| 4 | Byte multi_party final del depósito en garantía omitido. |
| 5 | Un cuerpo de ApprovalEscrow que sigue teniendo 12 bytes en lugar de 36. |
Endpoints para tu integración
| cadena | pasarela (HTTPS) | firma | nota |
|---|---|---|---|
| testnet | https://this-gateway | migrando a v0.4.0 | el objetivo de integración cuando se haga el cambio |
| devnet | https://socialconnect.network | v0.4.0 | 11 nodos, faucet en /faucet; el hash de génesis cambia en cada relanzamiento |
| mainnet | aún no lanzada | v0.4.0 o posterior | leer su hash de génesis desde la cadena cuando se abra |
Dirige las lecturas primero al nodo de archivo, luego a la pasarela y luego al nodo. Difunde a una pasarela o a un nodo, nunca a un endpoint de archivo.
Lee el hash de génesis en tiempo de ejecución; no lo escribas fijo en el código. El de la devnet cambia en cada relanzamiento. La tabla de hashes del firmware existe solo para mostrar un nombre en la pantalla, así que una entrada obsoleta se degrada a "UNKNOWN CHAIN" en lugar de romper la firma. Cuando el hash de la devnet cambia, todas las firmas hechas antes quedan anuladas, y eso es la función haciendo su trabajo.