ZOOBC / SVILUPPATORI
Sviluppatori
Tutto ciò che serve a un programma per leggere o scrivere su questa catena. Un gateway è una porta d'ingresso HTTPS: termina il TLS, applica limiti di frequenza e inoltra l'API della catena ai nodi, così un browser può comunicare con ZooBC senza dover gestire un nodo.
Su quale catena stai sviluppando?
Gli esempi seguenti vengono eseguiti sul gateway pubblico della TestNet. Indirizzi, saldi e hash delle transazioni non valgono da una rete all'altra, quindi verifica su quale rete ti trovi prima di agire.
La MainNet apre il 14 febbraio 2027
Un solo URL di base
Tutto ciò che riguarda la catena si trova sotto /api/v1/ su questo gateway, con la stessa origine di questa pagina: nessun CORS da negoziare, nessuna chiave da ottenere, nessun piano tariffario.
zoobc.network è il gateway pubblico della TestNet di ZooBC, non della MainNet. Gli esempi qui sotto vengono eseguiti sulla rete di test.
curl https://this-gateway/api/v1/blockchain/status
Letture e scritture vanno in posti diversi, e questo conta più di quanto sembri:
| GET | inoltrata tramite proxy a un nodo di archivio, che conserva la cronologia completa: blocchi, transazioni e account a qualsiasi altezza. |
|---|---|
| POST | inoltrata direttamente a un nodo completo, mai a un nodo di archivio. L'invio di una transazione richiede una mempool, e un nodo di archivio è un servizio di cronologia in sola lettura: rispondeva alle POST con un 404, ecco perché un tempo la trasmissione tramite un gateway non funzionava. |
Le route che userai per prime
| /api/v1/blockchain/status | altezza, altezza confermata, ultimo blocco |
| /api/v1/blocks/latest | il blocco più recente per intero |
| /api/v1/accounts/<address> | saldo e saldo spendibile. Accetta un indirizzo ZBC_… o i 36 byte grezzi in esadecimale |
| /api/v1/accounts/<address>/tokens | tutti i token posseduti da quell'account |
| /api/v1/transactions?limit=25 | transazioni firmate recenti |
| /api/v1/movements/latest | le variazioni di saldo generate dalla catena stessa: ricompense, vesting, rimborsi |
| /api/v1/exchange/markets | mercati aperti; …/orderbook?market=<id> per la profondità, …/offers per i pacchetti di swap |
| /api/v1/tokens | tutti i token, con offerta e decimali |
| /api/v1/release/list | i rilasci pubblicati e i loro hash, ciò con cui /verify effettua il confronto |
| /api/v1/registry/nodes | il registro dei nodi; inoltre
/gateways, /relays, /archivals |
Alcune route esistono solo su un nodo di archivio e alcune solo su un nodo completo. Questo gateway prova prima il nodo di archivio e ritenta con l'API del nodo in caso di 404 con corpo vuoto, quindi raramente devi preoccuparti di quale sia quale; ma un 404 con un corpo è una risposta vera, non una strada sbagliata.
Inviare una transazione
Creala e firmala con zbc-cli, che riceve i parametri come JSON su stdin, così una chiave privata non finisce mai in ps né nella cronologia della shell:
echo '{"sender_privkey":"…","recipient":"ZBC_…","amount":100000000}' \
| zbc-cli send --json-input --api https://this-gateway
1 ZBC equivale a 108 unità atomiche. Ogni importo nell'API è espresso in unità atomiche: nel formato di trasmissione non ci sono decimali da nessuna parte.
Cosa si sta costruendo su queste infrastrutture
Gli stessi tipi di transazione che chiami qui sono la base del lavoro in corso:
| Agenti AI | trasferimenti programmati, escrow e multisig usati come tetto di spesa che un agente autonomo non può alzare da solo. Funziona già oggi, con un esempio Python di riferimento senza dipendenze sulla TestNet pubblica. |
|---|---|
| AI decentralizzata | specialisti di modelli gestiti in modo indipendente, assemblati lato utente e regolati apertamente, così che nessuna singola azienda si frapponga tra una persona e l'intelligenza che usa. Direzione per il dopo MainNet. |
| ProxCell | conferma ottimistica locale per i pagamenti di persona, con livelli di regolamento geografici al di sopra. Documento di lavoro. |
Riferimento completo
Ogni route, i suoi parametri e la struttura della risposta:
Apri il riferimento API → · vista alternativa
Entrambi gli indirizzi servono lo stesso documento.
Download
I file che un operatore ha pubblicato su questo gateway sono serviti su /dl/<name>. I nomi sono un singolo segmento di [A-Za-z0-9._-], e tutto viene inviato come allegato e mai visualizzato: questa origine serve anche il wallet, e una pagina visualizzata qui erediterebbe l'origine del wallet.
I pacchetti di rilascio si trovano sotto /releases/<version>/<arch>/, con /releases/latest che indica quello attuale. L'installer del nodo si trova su
/install.
Questo gateway è in buona salute? verifica in corso…
/gateway/status riporta ciò che questo gateway sa di sé e dei nodi che può raggiungere: modalità, dominio, quanti nodi sono nella sua allowlist, quali rispondono.
/gateway/system-stats riporta i dati della macchina: CPU, memoria, disco, uptime. È da qui che provengono i valori nella pagina del gateway.
curl https://this-gateway/gateway/status
curl https://this-gateway/gateway/system-stats
Nessuno dei due richiede una chiave. Nessuno dei due espone dati della catena: per quelli usa /api/v1/… qui sopra.
Raggiungere un nodo specifico
Le route della catena rispondono da qualsiasi nodo di archivio scelto da questo gateway. Per interrogare un nodo specifico, inserisci il suo IP nel nome host, sostituendo i punti con trattini; un -p<port> facoltativo sceglie la porta dell'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
Rispondono solo i nodi presenti nell'allowlist di questo gateway. Qualsiasi altra richiesta viene rifiutata anziché inoltrata, quindi il nome host non può essere usato per raggiungere host arbitrari tramite il gateway.
Relay
Voce, video e condivisione dello schermo viaggiano peer-to-peer; quando un firewall blocca un collegamento diretto, un relay trasporta il traffico in cambio di credito. /relay/info riporta il suo indirizzo, la sua tariffa e l'hash di genesi della catena su cui regola i pagamenti: un wallet lo confronta con quello del proprio nodo prima di pagare, perché un relay su un'altra catena non può accreditare ciò che riceve.
curl https://this-gateway/relay/info
Pannello di amministrazione
I controlli del gateway si trovano su zoobc.network/admin. Il pannello è protetto dalla chiave admin_api_key definita in /etc/zoobc/gateway.json, che l'installer genera per ogni macchina: non esiste una chiave predefinita.
L'allowlist
Un gateway inoltra le richieste solo ai nodi che gli sono stati indicati. È questo elenco a impedire che la forma di nome host ip-… trasformi il gateway in un proxy aperto.
| POST /gateway/allowlist/add | ammette un nodo, {"ip":"…","port":8080} |
| POST /gateway/allowlist/remove | ne rimuove uno |
| POST /gateway/allowlist/refresh | rilegge il registro e ricontrolla lo stato |
| GET /gateway/allowlist | ciò che è attualmente ammesso |
Tutte e tre le scritture richiedono la chiave di amministrazione. In modalità pubblica il gateway scopre i nodi anche dal registro della catena stessa; in modalità privata usa solo static_nodes.
Strumenti per i rilasci
Il calcolatore di hash dei file calcola uno SHA-256 nel browser per qualsiasi cosa tu voglia controllare a mano, e /verify confronta un pacchetto scaricato con gli hash pubblicati dalla catena.
I due documenti
Tutto ciò di cui un team di hardware wallet ha bisogno è specificato qui, e per intero nei due PDF qui sotto. Entrambi sono stati verificati sul nodo ZooBC v0.4.0 (zoobc-main al commit 7ad161a4) e su vettori di transazione accettati da un nodo v0.4.0 attivo.
La versione 2.1 sostituisce la bozza 1 in un aspetto sostanziale: le firme delle transazioni sono ora vincolate alla catena. Due modifiche rompono la compatibilità per chi ha sviluppato sulla bozza 1: il digest di firma e il corpo di ApprovalEscrow. Nulla implementa la vecchia costruzione v0.3.2, quindi sviluppa solo sul documento attuale.
Riepilogo in una pagina
| Schema di firma | Ed25519 (RFC 8032, puro, senza variante prehash, senza stringa di contesto). Firma da 64 byte, chiave pubblica da 32 byte. |
|---|---|
| Cosa viene firmato | Ed25519.sign(sk, SHA3-256("ZBC-TX" ‖ genesis_hash(32) ‖ unsigned_bytes)). Il digest da 32 byte è il messaggio Ed25519. |
| Vincolo alla catena | Il digest copre l'hash del blocco genesi della catena, quindi una firma creata per una catena non può essere riutilizzata su un'altra. Il formato di trasmissione non cambia; l'hash di genesi non è un campo della transazione. |
| Tag di firma | "ZBC-TX", sei byte ASCII 5a 42 43 2d 54 58. Nessun terminatore, nessun prefisso di lunghezza. |
| Funzione di hash | SHA3-256 (FIPS 202). Non Keccak-256, non SHA-256. |
| Derivazione delle chiavi | Mnemonica BIP-39, poi seed (PBKDF2-HMAC-SHA512, 2048 iterazioni), poi SLIP-0010 Ed25519, percorso m/44'/883'/account'. Tre livelli hardened, nessun livello di change o di indice. |
| Coin type SLIP-44 | 883 (registrato: 883 | ZBC | ZooBC). |
| Account (formato di trasmissione) | 36 byte: u32le(0) ‖ pubkey(32). La forma esadecimale è quella accettata dall'API JSON. |
| Indirizzo (visualizzato) | ZBC_ più 56 caratteri base32 in 7 gruppi da 8. Checksum di 3 byte = SHA3-256(pubkey ‖ "ZBC")[0..3]. |
| Byte di versione | 0x01, invariato. Non è stato incrementato per il vincolo alla catena e non è un discriminatore di versione. |
| Unità nativa | 1 ZBC = 100 000 000 unità atomiche (8 decimali). Importi e commissioni sono unità atomiche u64 little-endian. |
| Ordine dei byte | Tutto ciò che viene trasmesso è little-endian, tranne gli indici figli SLIP-0010 (big-endian, come da standard). |
| Hash della transazione | tx_hash = SHA3-256(unsigned_bytes ‖ signature); tx_id = int64le(tx_hash[0..8]). L'hash di genesi entra solo nel digest di firma. |
| Protezione dal replay | Vincolo alla catena, più l'unicità dell'hash, più una finestra di inclusione di 3600 s. Non esiste un nonce. |
| Commissione minima | 0.025 ZBC di base (2 500 000 unità atomiche) moltiplicati per la scala delle commissioni di rete, più l'affitto per dimensione. Chiedilo al nodo: GET /api/v1/blockchain/estimate-fee. |
| Tipi di livello 1 | 1 SendZBC, 11 TransferToken, 4 ApprovalEscrow. |
| Individuazione della catena | GET /api/v1/node/info restituisce genesis_hash, signing_version: 2 e signing_tag: "ZBC-TX". Leggilo dall'endpoint a cui trasmetterai. |
Leggi prima questi quattro
Quattro punti che influenzano un'implementazione più di tutto il resto.
| 1 | Non esiste un nonce, e non esiste un endpoint per transazioni non firmate. ZooBC è basato su account, ma qui essere basato su account non implica né l'uno né l'altro. L'host serializza da sé la transazione; la struttura qui sotto è completa e fissata dai vettori di test. |
|---|---|
| 2 | La firma copre un prefisso con tag, "ZBC-TX" più l'hash di genesi della catena, non solo i byte della transazione. Se sbagli questo, ogni firma fallisce con un errore generico. |
| 3 | L'endpoint di trasmissione non accetta il blob della transazione firmata. Accetta i byte del corpo della transazione più la busta come chiavi JSON con nome, e ricostruisce da sé la busta. |
| 4 | La cronologia viene restituita dalla voce più recente, limitata da limit. Due endpoint insieme danno un quadro completo, e l'importo dipende dal tipo anziché essere un campo della busta. |
Derivazione delle chiavi e indirizzi
Riferimento: zoobc-signer/extension/lib/zoobc-crypto.js, identico byte per byte al wallet di riferimento; equivalenti lato nodo in 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
L'account 0 è m/44'/883'/0', l'account 1 è m/44'/883'/1' e così via. La derivazione delle chiavi non è influenzata dal vincolo alla catena: un unico insieme di chiavi serve ogni catena ZooBC, cambia solo la firma.
L'indirizzo visualizzato è costruito a partire dalla chiave pubblica, non dall'account da 36 byte:
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 decodifica accetta trattini bassi, trattini, spazi o nessun separatore, in maiuscolo o minuscolo, e il checksum deve essere ricalcolato e confrontato. Il checksum SHA3 da 3 byte qui sopra è la definizione usata dal codice e dalla catena, quindi implementa a partire da quella e dall'implementazione di riferimento nella specifica, che riproduce gli indirizzi di esempio.
Formato di trasmissione della transazione non firmata
Fonte di verità: src/util/transaction_util.cpp:144-270. Il formato di trasmissione non è cambiato nella v0.4.0. Nessuna lunghezza o offset qui sotto è influenzato dal vincolo alla catena.
| # | dimensione | campo | note |
|---|---|---|---|
| 1 | 4 | transaction_type | u32le, vedi il catalogo dei tipi qui sotto |
| 2 | 1 | version | sempre 0x01; il nodo rifiuta qualsiasi altro valore |
| 3 | 8 | timestamp | u64le, secondi Unix, maggiore di 0 |
| 4 | 36 | sender | 00000000 ‖ pubkey(32) per un firmatario ZBC |
| 5 | 4 o 4+n | recipient | 36 byte per ZBC; u32le(type) ‖ payload per gli altri; solo 02 00 00 00 quando non c'è destinatario |
| 6 | 8 | fee | u64le, unità atomiche |
| 7 | 4 | body_length | u32le |
| 8 | n | body | struttura specifica per tipo |
| 9 | 4 o var | escrow | senza escrow: i 4 byte 02 00 00 00. Con escrow: approver(36) ‖ commission u64le ‖ timeout u64le ‖ instr_len u32le ‖ instruction ‖ multi_party(1) |
| 10 | 4 | message_length | u32le |
| 11 | m | message | testo semplice, 256 byte o meno per le transazioni ordinarie |
Il byte finale multi_party del blocco escrow è obbligatorio. Ometterlo, o scrivere il marcatore di assenza di escrow con un valore diverso, produce sul nodo soltanto «Invalid transaction signature», senza ulteriori diagnostiche.
Esempio svolto, convalidato sulla catena
SendZBC di 1 ZBC a se stessi, commissione 0.05 ZBC, timestamp 1700000000, senza escrow, senza messaggio. È il vettore 0 dei sette presenti nella specifica.
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
Sono 113 byte. La preimmagine di firma ne antepone altri 38:
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 attivo ha restituito HTTP 202 ed esattamente quell'hash.
Procedura di firma, lato 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 in streaming, così il passaggio 6 non richiede l'intera transazione in RAM. Fornisci i 6 byte del tag, poi i 32 byte di genesi, poi la transazione.
Il tag è in assoluto l'errore di implementazione più probabile dell'intera specifica. "ZBC-TX" è composto da sei byte ASCII, 5a 42 43 2d 54 58, senza terminatore NUL e senza prefisso di lunghezza. Un firmware che termina la stringa, o che scrive sette byte, produce una firma dall'aspetto valido che fallisce sulla catena con un errore generico e senza diagnostica. Testalo sul vettore 0 prima di ogni altra cosa.
Cosa fa e cosa non fa il vincolo alla catena
L'host fornisce i 32 byte di genesi; il dispositivo non li cerca. La firma è quindi indipendente dalla catena, e un unico percorso di codice serve TestNet, devnet, MainNet e qualsiasi catena ZooBC privata o parallela.
| Cosa fa | Impedisce il replay tra catene. Una firma creata per la catena A non verrà verificata sulla catena B. È lo scopo per cui è stato creato, e lo assolve completamente. |
|---|---|
| Cosa non fa | Impedire a un host compromesso di indicare la catena sbagliata. Il dispositivo firma per qualunque hash gli venga fornito. |
Una tabella nel firmware con gli hash di genesi noti serve quindi ancora, ma per un solo scopo: mostrare sullo schermo un nome di rete affidabile, ripiegando su «UNKNOWN CHAIN» più i primi 8 caratteri esadecimali quando l'hash non è nella tabella.
Non rifiutare per impostazione predefinita le catene non riconosciute. L'hash della devnet cambia a ogni riavvio, e le catene private o parallele sono normali su ZooBC, quindi un rifiuto rigido rende il dispositivo inutilizzabile per l'integrazione e per le installazioni legittime. Se un produttore vuole quel blocco, ne faccia un'impostazione esplicita dell'utente, disattivata per impostazione predefinita.
Tipi di transazione: cosa dovrebbe supportare un firmware standard
Id del tipo = group + 256 × subtype. Esistono cinquantasei tipi. Questi tre coprono il pagamento in ZBC, il pagamento con qualsiasi token e il completamento di un escrow, e i loro corpi sono piccoli e fissi.
| id | nome | body | recipient |
|---|---|---|---|
| 1 | SendZBC | amount u64le (8) | obbligatorio, qualsiasi tipo |
| 11 | TransferToken | token_id i64le (8) ‖ amount i64le (8) ‖ fee_in_token u8 facoltativo | obbligatorio |
| 4 | ApprovalEscrow | approval u32le ‖ escrowed_transaction_hash (32) = 36 byte | marcatore vuoto; il mittente è l'approvatore |
token_id è un valore con segno a 64 bit derivato da un hash, quindi può essere negativo. Le monete della Genesis hanno id da 1 a 14; ZBC stesso ha id 0 e non compare mai in TransferToken. Ogni token ha il proprio decimals (da 0 a 8), che il dispositivo non può verificare.
ApprovalEscrow è cambiato nella v0.4.0. Il corpo era approval più un id da 8 byte (12 byte); ora è approval più l'hash da 32 byte della transazione in escrow (36 byte), e il parser rifiuta qualsiasi altra lunghezza. Il motivo riguarda direttamente gli hardware wallet: un firmatario che vedeva solo un id da 8 byte non poteva controllare cosa stava sbloccando, e un id da 8 byte è individuabile con una ricerca su 2^64 per un falso escrow mostrato al dispositivo. Con l'hash completo il dispositivo può verificare ciò che approva.
Cosa verifica il dispositivo
| Struttura | Analizza gli 11 campi. Ogni prefisso di lunghezza deve essere coerente e il buffer deve terminare esattamente dopo il messaggio. In qualsiasi altro caso: rifiuta. |
|---|---|
| Versione | Deve essere 1. |
| Mittente | Deve coincidere con l'account derivato dal dispositivo stesso per il percorso richiesto. Il dispositivo non si fida mai dei byte del mittente che gli vengono forniti: li deriva di nuovo e confronta tutti i 36 byte. È questa l'intera garanzia sul «da dove proviene». |
| Tipo | Deve far parte dell'insieme supportato, oppure deve essere attiva la modalità esperto. |
| Lunghezza del corpo | 8 per SendZBC, 16 o 17 per TransferToken, 36 per ApprovalEscrow. Byte in più: rifiuta. Il nodo accetterebbe un corpo SendZBC più lungo ignorandone la coda, ed è proprio così che un payload nascosto potrebbe viaggiare insieme alla transazione. |
| Timeout dell'escrow | Deve essere maggiore del timestamp della transazione. |
| Hash di genesi | Deve essere esattamente di 32 byte. Il dispositivo non lo convalida rispetto a un elenco, ma una lunghezza errata indica una richiesta malformata. |
| Mai | Accettare un digest precalcolato da firmare. Il dispositivo calcola da sé l'hash dei byte, tag e genesi inclusi. |
| Limiti di dimensione | Consigliati: transazione non firmata di 2 KB o meno, messaggio di 256 B, istruzione di escrow di 512 B, rifiutando qualsiasi cosa più grande con TOO_LARGE. Le transazioni di livello 1 sono sotto i 300 byte, ma un ApprovalEscrow arriva con una seconda transazione allegata per la verifica, quindi il limite deve coprire entrambi i buffer. I payload più grandi spettano ai wallet software. |
Cosa approva l'utente sullo schermo
Schermata per schermata, in quest'ordine, adattato dal firmatario di riferimento convalidato sulla catena.
| Rete | Nome preso dalla tabella del firmware per l'hash di genesi fornito; «UNKNOWN CHAIN» più i primi 8 caratteri esadecimali quando non è nella tabella. |
|---|---|
| Tipo | «Send ZBC», «Send token», «Approve escrow». |
| Importo | amount / 1e8 con il simbolo ZBC. |
| Destinatario | Indirizzo completo, mai abbreviato. Un indirizzo abbreviato al centro è esattamente ciò che una chiave sosia, generata come vanity address, riesce ad aggirare. |
| Commissione | unità atomiche / 1e8 ZBC. |
| Totale in uscita | importo più commissione in un unico numero, solo per importi in ZBC. Un importo in token e una commissione in ZBC sono unità diverse e non vanno sommati. |
| Messaggio | Mostrato se stampabile, altrimenti «N bytes (binary)» più i primi 8 caratteri esadecimali del suo SHA3-256. |
| Escrow | Approvatore per intero, commissione, timeout come data e ora, testo dell'istruzione. |
| Token (tipo 11) | Id del token (decimale con segno) e l'importo in unità atomiche. Se l'host fornisce decimals o symbol come suggerimenti di visualizzazione, mostra l'importo scalato con l'etichetta «as reported by the app», perché il dispositivo non può verificarli. |
| Approvazione dell'escrow | «APPROVE» o «REJECT» in caratteri grandi, poi i dettagli verificati dell'escrow, poi la commissione. |
Non è necessario mostrare i timestamp. Non hanno significato per l'utente e il nodo impone la finestra.
Protocollo tra host e dispositivo
Il trasporto è il livello USB/HID del produttore, con la sua suddivisione in blocchi già esistente. Ciò che conta è il contenuto.
| comando | richiesta | risposta |
|---|---|---|
| GET_APP_VERSION | nessuno | versione del firmware, insieme dei tipi supportati, dimensione massima della tx |
| GET_PUBLIC_KEY | account_index, confirm_on_device | pubkey(32), address (stringa di 66 caratteri). In fase di conferma, il dispositivo mostra l'indirizzo completo perché l'utente lo confronti con quello del sito web. |
| SIGN_TX | account_index, genesis_hash(32), unsigned_tx_bytes | signature(64), tx_hash(32), oppure un rifiuto tipizzato: SENDER_MISMATCH, UNSUPPORTED_TYPE, MALFORMED, USER_REJECTED, TOO_LARGE, ESCROW_HASH_MISMATCH |
| SIGN_MESSAGE | account_index, message_bytes | signature(64), Ed25519 grezzo, vedi la regola qui sotto |
genesis_hash è essenziale, non un suggerimento di visualizzazione. Nella bozza 1 il parametro di rete poteva essere un enum, perché sceglieva solo un'etichetta. Ora è dentro il digest, quindi deve contenere i 32 byte effettivi, e un valore errato produce una firma che nessuna catena accetterà.
La regola della firma grezza. Un comando di firma grezza non deve mai firmare un input di esattamente 32 byte, e dovrebbe firmare solo input che può mostrare come testo. Un input grezzo di 32 byte potrebbe essere il digest SHA3 di una transazione che l'utente non ha mai visto, trasformando il comando dei messaggi in una firma di transazioni alla cieca. Rifiutare la lunghezza 32 chiude quella falla senza modifiche alla catena.
La sequenza 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) --->|
L'host confronta il transaction_hash del nodo con il tx_hash del dispositivo. Devono essere identici, altrimenti l'host ha alterato i byte dopo la firma.
Leggi genesis_hash e signing_version dall'endpoint a cui trasmetterai, perché è quella la catena che giudica la firma.
Identità della catena
L'unico identificatore di rete di ZooBC è l'hash del blocco genesi. I nodi lo confrontano prima di stabilire il peering; non esiste un byte di network-id. GET /api/v1/node/info lo restituisce insieme a due campi nuovi nella v0.4.0:
"genesis_hash": "090ab3c7...", "signing_version": 2, "signing_tag": "ZBC-TX"
signing_version assente significa che il nodo è precedente alla v0.4.0 e applica il vecchio digest non vincolato. Un host che supporta solo le catene attuali può interpretarne l'assenza come «non firmare qui». Un nodo stampa l'esadecimale in minuscolo e l'API di archivio in maiuscolo, quindi decodifica senza distinguere maiuscole e minuscole e rimuovi un eventuale 0x iniziale. I byte sono gli stessi in entrambi i casi.
Da dove leggerlo. Un gateway risponde a /api/v1/node/info dal servizio di archivio anziché da un nodo. L'archivio inoltra i due campi di firma dal nodo di cui è copia, emettendoli esattamente quando il nodo li riporta, a partire dalla build 7ad161a4. Leggi signing_version da un endpoint di nodo, o da un gateway con quella build o successiva, e interpretane l'assenza come «chiedi a un nodo» anziché come una risposta.
Trasmissione
POST /api/v1/transactions. Invariato nella v0.4.0, nessun nuovo campo. transaction_body_bytes è solo il corpo: il nodo ricostruisce la busta dai campi con nome, antepone il proprio hash di genesi e il tag, e ricalcola il digest, quindi ogni campo deve essere uguale a quanto è stato firmato.
{
"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 }
}
Il successo è HTTP 202 con {"status":"success","duplicate":false,"transaction_hash":"..."}. duplicate:true significa che il nodo la possiede già, quindi non inviarla di nuovo. Considera un successo anche 200. Il fallimento è 400 con un corpo di errore, o 503 sotto carico.
Un test di integrazione utile: il pool di staging controlla prima la firma, quindi una transazione che arriva a un errore successivo come «Sender account does not exist» ha già superato la verifica della firma. Puoi convalidare il tuo percorso di firma con una chiave senza fondi.
Quando una firma viene rifiutata
Oggi una mancata corrispondenza di catena non è distinguibile da qualsiasi altro errore di firma. Un digest legacy e un digest della catena sbagliata restituiscono entrambi lo stesso identico corpo:
{"success":false,"error":"Transaction validation failed: ValidationError: Invalid transaction signature","code":400}
Quindi verifica le cause in quest'ordine, partendo dalla più probabile:
| 1 | Il tag omesso, terminato con NUL o scritto su sette byte. |
|---|---|
| 2 | Keccak-256 usato al posto di SHA3-256. |
| 3 | Un hash genesis che non corrisponde alla catena su cui si trasmette. |
| 4 | Il byte multi_party finale dell'escrow omesso. |
| 5 | Un corpo ApprovalEscrow ancora di 12 byte anziché 36. |
Endpoint su cui sviluppare
| catena | gateway (HTTPS) | firma | nota |
|---|---|---|---|
| testnet | https://this-gateway | in migrazione a v0.4.0 | il riferimento per l'integrazione dopo il passaggio |
| devnet | https://socialconnect.network | v0.4.0 | 11 nodi, faucet su /faucet; l'hash genesis cambia a ogni rilancio |
| mainnet | non ancora lanciata | v0.4.0 o successiva | all'avvio, leggi l'hash genesis dalla catena |
Instrada le letture prima al nodo di archivio, poi al gateway, poi al nodo. Trasmetti a un gateway o a un nodo, mai a un endpoint di archivio.
Leggi l'hash genesis a runtime, non scriverlo nel codice. Quello della devnet cambia a ogni rilancio. Una tabella di hash nel firmware serve solo a mostrare un nome sullo schermo, quindi una voce obsoleta degrada a «UNKNOWN CHAIN» invece di bloccare la firma. Quando l'hash della devnet cambia, ogni firma fatta in precedenza perde validità: è la funzionalità che fa il suo lavoro.