ZOOBC / DÉVELOPPEURS
Développeurs
Tout ce dont un programme a besoin pour lire ou écrire sur cette chaîne. Une passerelle est une porte d'entrée HTTPS : elle termine le TLS, limite le débit et relaie l'API de la chaîne vers les nœuds, pour qu'un navigateur puisse dialoguer avec ZooBC sans en exploiter un.
Sur quelle chaîne développez-vous ?
Les exemples ci-dessous s'exécutent sur la passerelle publique du TestNet. Les adresses, les soldes et les hachages de transaction ne se transfèrent pas d'un réseau à l'autre : vérifiez sur lequel vous vous trouvez avant d'agir.
Ouverture du MainNet le 14 février 2027
Une seule URL de base
Tout ce qui concerne la chaîne se trouve sous /api/v1/ sur cette passerelle, à la même origine que cette page : pas de CORS à négocier, pas de clé à obtenir, pas de forfait de débit.
zoobc.network est la passerelle publique du TestNet ZooBC, pas le MainNet. Les exemples ci-dessous s'exécutent sur le réseau de test.
curl https://this-gateway/api/v1/blockchain/status
Les lectures et les écritures ne vont pas au même endroit, ce qui compte plus qu'il n'y paraît :
| GET | relayé vers un nœud d'archive, qui conserve l'historique profond : blocs, transactions, comptes à n'importe quelle hauteur. |
|---|---|
| POST | relayé directement vers un nœud complet, jamais vers l'archive. Soumettre une transaction nécessite une mempool, et un nœud d'archive est un service d'historique en lecture seule : il répondait autrefois aux POST par une 404, ce qui explique pourquoi la diffusion via une passerelle n'a pas fonctionné pendant un temps. |
Les routes que vous utiliserez en premier
| /api/v1/blockchain/status | hauteur, hauteur confirmée, dernier bloc |
| /api/v1/blocks/latest | le bloc le plus récent, en entier |
| /api/v1/accounts/<address> | solde et solde disponible. Accepte une adresse ZBC_… ou l'hexadécimal brut de 36 octets |
| /api/v1/accounts/<address>/tokens | chaque jeton détenu par ce compte |
| /api/v1/transactions?limit=25 | transactions signées récentes |
| /api/v1/movements/latest | les variations de solde opérées par la chaîne elle-même : récompenses, déblocages progressifs, remboursements |
| /api/v1/exchange/markets | marchés ouverts ; …/orderbook?market=<id> pour la profondeur, …/offers pour les paquets d'échange |
| /api/v1/tokens | chaque jeton, avec son offre et ses décimales |
| /api/v1/release/list | les versions publiées et leurs empreintes, la référence que vérifie /verify |
| /api/v1/registry/nodes | le registre des nœuds ; voir aussi
/gateways, /relays, /archivals |
Certaines routes n'existent que sur un nœud d'archive, d'autres uniquement sur un nœud complet. Cette passerelle essaie d'abord l'archive et retente l'API du nœud en cas de 404 au corps vide : vous avez donc rarement à vous en soucier, mais une 404 avec un corps est une vraie réponse, pas une erreur d'aiguillage.
Soumettre une transaction
Construisez-la et signez-la avec zbc-cli, qui prend ses paramètres en JSON sur stdin, afin qu'une clé privée n'atterrisse jamais dans ps ni dans l'historique de votre shell :
echo '{"sender_privkey":"…","recipient":"ZBC_…","amount":100000000}' \
| zbc-cli send --json-input --api https://this-gateway
1 ZBC vaut 108 unités atomiques. Tous les montants de l'API sont atomiques : il n'y a aucune décimale dans le format de transmission.
Ce qui se construit sur ces rails
Les types de transaction que vous appelez ici sont la base des travaux en cours :
| Agents d'IA | transferts programmés, séquestre et multisig utilisés comme plafond de dépenses qu'un agent autonome ne peut pas relever de lui-même. Fonctionne dès aujourd'hui, avec une référence Python sans dépendance sur le testnet public. |
|---|---|
| IA décentralisée | des modèles spécialisés exploités indépendamment, assemblés côté utilisateur et réglés de manière transparente, pour qu'aucune entreprise ne s'interpose entre une personne et l'intelligence qu'elle utilise. Orientation après le mainnet. |
| ProxCell | confirmation optimiste locale pour les paiements en personne, avec des couches de règlement géographiques au-dessus. Document de travail. |
Référence complète
Chaque route, ses paramètres et le format de sa réponse :
Ouvrir la référence de l'API → · vue alternative
Les deux adresses servent le même document.
Téléchargements
Les fichiers qu'un opérateur a publiés sur cette passerelle sont servis à /dl/<name>. Les noms sont un unique segment de [A-Za-z0-9._-], et tout est envoyé en pièce jointe sans jamais être affiché : cette origine sert aussi le portefeuille, et une page affichée ici hériterait de l'origine du portefeuille.
Les paquets de version se trouvent sous /releases/<version>/<arch>/, /releases/latest désignant le paquet actuel. L'installateur du nœud se trouve à
/install.
Cette passerelle est-elle en bonne santé ? vérification…
/gateway/status indique ce que cette passerelle sait d'elle-même et des nœuds qu'elle peut joindre : mode, domaine, nombre de nœuds sur sa liste d'autorisation et ceux qui répondent.
/gateway/system-stats décrit la machine : CPU, mémoire, disque, durée de fonctionnement. C'est de là que proviennent les chiffres de la page de la passerelle.
curl https://this-gateway/gateway/status
curl https://this-gateway/gateway/system-stats
Aucun des deux n'exige de clé. Aucun n'expose quoi que ce soit sur la chaîne ; pour cela, utilisez /api/v1/… ci-dessus.
Joindre un nœud précis
Les routes de la chaîne répondent depuis le nœud d'archive choisi par cette passerelle. Pour interroger un nœud désigné, placez son IP dans le nom d'hôte (les points deviennent des tirets), et un -p<port> facultatif choisit le port de l'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
Seuls les nœuds de la liste d'autorisation de cette passerelle répondent. Tout le reste est refusé plutôt que relayé, de sorte que le nom d'hôte ne peut pas servir à atteindre des hôtes arbitraires via la passerelle.
Relais
La voix, la vidéo et le partage d'écran passent en pair à pair ; quand un pare-feu bloque une liaison directe, un relais achemine le trafic contre crédit. /relay/info indique son adresse, son tarif et le hachage de genèse de la chaîne sur laquelle il effectue ses règlements : un portefeuille le compare à celui de son propre nœud avant de payer, car un relais situé sur une autre chaîne ne peut pas créditer ce qu'il reçoit.
curl https://this-gateway/relay/info
Panneau d'administration
Les commandes propres à la passerelle se trouvent sur zoobc.network/admin. Le panneau est protégé par admin_api_key, issu de /etc/zoobc/gateway.json, que l'installateur génère pour chaque machine : il n'existe aucune clé par défaut.
La liste d'autorisation
Une passerelle ne relaie que vers les nœuds qui lui ont été signalés. C'est cette liste qui empêche la forme de nom d'hôte ip-… de transformer la passerelle en proxy ouvert.
| POST /gateway/allowlist/add | admettre un nœud, {"ip":"…","port":8080} |
| POST /gateway/allowlist/remove | en retirer un |
| POST /gateway/allowlist/refresh | relire le registre et revérifier l'état de santé |
| GET /gateway/allowlist | ce qui est actuellement admis |
Les trois écritures exigent la clé d'administration. En mode public, la passerelle découvre aussi les nœuds à partir du registre de la chaîne elle-même ; en mode privé, elle utilise uniquement static_nodes.
Outils de publication
Le calculateur d'empreinte calcule un SHA-256 dans le navigateur pour tout ce que vous voulez vérifier à la main, et /verify compare un paquet téléchargé aux empreintes publiées par la chaîne.
Les deux documents
Tout ce dont une équipe de portefeuille matériel a besoin est spécifié ici, et intégralement dans les deux PDF ci-dessous. Tous deux ont été vérifiés par rapport au nœud ZooBC v0.4.0 (zoobc-main à 7ad161a4) et à des vecteurs de transaction acceptés par un nœud v0.4.0 en service.
La version 2.1 remplace le brouillon 1 sur un point substantiel : les signatures de transaction sont désormais liées à la chaîne. Deux changements cassent la compatibilité pour quiconque a développé à partir du brouillon 1 : le condensé de signature et le corps ApprovalEscrow. Plus rien n'implémente l'ancienne construction v0.3.2 : développez uniquement à partir du document actuel.
Résumé en une page
| Schéma de signature | Ed25519 (RFC 8032, pur, sans variante prehash, sans chaîne de contexte). Signature de 64 octets, clé publique de 32 octets. |
|---|---|
| Ce qui est signé | Ed25519.sign(sk, SHA3-256("ZBC-TX" ‖ genesis_hash(32) ‖ unsigned_bytes)). Le condensé de 32 octets est le message Ed25519. |
| Liaison à la chaîne | Le condensé couvre le hachage du bloc de genèse de la chaîne, de sorte qu'une signature produite pour une chaîne ne peut pas être rejouée sur une autre. Le format de transmission est inchangé ; le hachage de genèse n'est pas un champ de la transaction. |
| Étiquette de signature | "ZBC-TX", six octets ASCII 5a 42 43 2d 54 58. Pas de terminateur, pas de préfixe de longueur. |
| Fonction de hachage | SHA3-256 (FIPS 202). Ni Keccak-256, ni SHA-256. |
| Dérivation des clés | Phrase mnémonique BIP-39, puis graine (PBKDF2-HMAC-SHA512, 2048 itérations), puis SLIP-0010 Ed25519, chemin m/44'/883'/account'. Trois niveaux renforcés, sans niveaux change ni index. |
| Coin type SLIP-44 | 883 (enregistré : 883 | ZBC | ZooBC). |
| Compte (format de transmission) | 36 octets : u32le(0) ‖ pubkey(32). La forme hexadécimale est celle qu'accepte l'API JSON. |
| Adresse (affichage) | ZBC_ suivi de 56 caractères base32 en 7 groupes de 8. Somme de contrôle de 3 octets = SHA3-256(pubkey ‖ "ZBC")[0..3]. |
| Octet de version | 0x01, inchangé. Il n'a pas été incrémenté pour la liaison à la chaîne et ne sert pas à distinguer les versions. |
| Unité native | 1 ZBC = 100 000 000 unités atomiques (8 décimales). Les montants et les frais sont des unités atomiques u64 en little-endian. |
| Ordre des octets | Tout ce qui transite est en little-endian, sauf les index enfants SLIP-0010 (big-endian, conformément à la norme). |
| Hachage de la transaction | tx_hash = SHA3-256(unsigned_bytes ‖ signature); tx_id = int64le(tx_hash[0..8]). Le hachage de genèse n'entre que dans le condensé de signature. |
| Protection contre le rejeu | Liaison à la chaîne, plus unicité du hachage, plus une fenêtre d'inclusion de 3600 s. Il n'y a pas de nonce. |
| Frais minimum | Base de 0,025 ZBC (2 500 000 unités atomiques) multipliée par l'échelle des frais du réseau, plus un loyer proportionnel à la taille. Demandez au nœud : GET /api/v1/blockchain/estimate-fee. |
| Types de niveau 1 | 1 SendZBC, 11 TransferToken, 4 ApprovalEscrow. |
| Découverte de la chaîne | GET /api/v1/node/info renvoie genesis_hash, signing_version: 2 et signing_tag: "ZBC-TX". Lisez-le depuis le point de terminaison vers lequel vous diffuserez. |
Lisez d'abord ces quatre points
Quatre points qui façonnent une implémentation plus que tout le reste.
| 1 | Il n'y a pas de nonce, ni de point de terminaison pour transaction non signée. ZooBC est fondé sur les comptes, mais ce modèle n'implique ici ni l'un ni l'autre. L'hôte sérialise lui-même la transaction ; la structure ci-dessous est complète et figée par des vecteurs. |
|---|---|
| 2 | La signature couvre un préfixe étiqueté, "ZBC-TX" plus le hachage de genèse de la chaîne, et pas seulement les octets de la transaction. Si vous vous trompez, chaque signature échoue avec une erreur générique. |
| 3 | Le point de terminaison de diffusion n'accepte pas le blob de transaction signée. Il prend les octets du corps de la transaction ainsi que l'enveloppe sous forme de clés JSON nommées, et reconstruit lui-même l'enveloppe. |
| 4 | L'historique est renvoyé du plus récent au plus ancien, borné par limit. Deux points de terminaison réunis donnent une vue complète, et le montant dépend du type au lieu d'être un champ de l'enveloppe. |
Dérivation des clés et adresses
Référence : zoobc-signer/extension/lib/zoobc-crypto.js, identique à l'octet près au portefeuille de référence ; équivalents côté nœud dans 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
Le compte 0 est m/44'/883'/0', le compte 1 est m/44'/883'/1', et ainsi de suite. La dérivation des clés n'est pas affectée par la liaison à la chaîne : un même jeu de clés sert toutes les chaînes ZooBC, seule la signature diffère.
L'adresse affichée est construite à partir de la clé publique, et non du compte de 36 octets :
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
Le décodage accepte les tirets bas, les tirets, les espaces ou l'absence de séparateur, en majuscules comme en minuscules, et la somme de contrôle doit être recalculée et comparée. La somme de contrôle SHA3 de 3 octets ci-dessus est la définition qu'utilisent le code et la chaîne : implémentez à partir d'elle, et de l'implémentation de référence figurant dans la spécification, qui reproduit les adresses des exemples.
Format de transmission de la transaction non signée
Source de référence : src/util/transaction_util.cpp:144-270. Le format de transmission n'a pas changé dans la v0.4.0. Aucune longueur ni aucun décalage ci-dessous n'est affecté par la liaison à la chaîne.
| # | taille | champ | remarques |
|---|---|---|---|
| 1 | 4 | transaction_type | u32le, voir le catalogue des types ci-dessous |
| 2 | 1 | version | toujours 0x01 ; le nœud rejette toute autre valeur |
| 3 | 8 | horodatage | u64le, secondes Unix, supérieur à 0 |
| 4 | 36 | expéditeur | 00000000 ‖ pubkey(32) pour un signataire ZBC |
| 5 | 4 ou 4+n | destinataire | 36 octets pour ZBC ; u32le(type) ‖ charge utile pour les autres ; 02 00 00 00 seul lorsqu'il n'y a pas de destinataire |
| 6 | 8 | frais | u64le, unités atomiques |
| 7 | 4 | body_length | u32le |
| 8 | n | corps | structure propre au type |
| 9 | 4 ou var. | séquestre | sans séquestre : les 4 octets 02 00 00 00. Avec séquestre : approver(36) ‖ commission u64le ‖ timeout u64le ‖ instr_len u32le ‖ instruction ‖ multi_party(1) |
| 10 | 4 | message_length | u32le |
| 11 | m | message | texte brut, 256 octets maximum pour les transactions ordinaires |
L'octet final multi_party du bloc de séquestre est obligatoire. L'omettre, ou écrire le marqueur d'absence de séquestre avec une autre valeur, produit uniquement « Invalid transaction signature » sur le nœud, sans autre diagnostic.
Exemple détaillé, validé sur la chaîne
SendZBC de 1 ZBC vers soi-même, frais de 0,05 ZBC, horodatage 1700000000, sans séquestre, sans message. C'est le vecteur 0 des sept que contient la spécification.
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
Cela fait 113 octets. La préimage de signature en ajoute 38 en tête :
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, signature 9a3b044d...03bb07, tx_hash = f00a6692...ce78b0. Un nœud v0.4.0 en service a renvoyé HTTP 202 et exactement ce hachage.
Procédure de signature, côté appareil
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
Utilisez un SHA3 en flux pour que l'étape 6 n'ait pas besoin de toute la transaction en RAM. Fournissez les 6 octets de l'étiquette, puis les 32 octets de genèse, puis la transaction.
L'étiquette est l'erreur d'implémentation la plus probable de toute la spécification. "ZBC-TX" correspond à six octets ASCII, 5a 42 43 2d 54 58, sans terminateur NUL ni préfixe de longueur. Un firmware qui termine la chaîne, ou qui écrit sept octets, produit une signature d'apparence valide qui échoue sur la chaîne avec une erreur générique et aucun diagnostic. Testez-la avec le vecteur 0 avant toute chose.
Ce que la liaison à la chaîne fait et ne fait pas
L'hôte fournit les 32 octets de genèse ; l'appareil ne les recherche pas. La signature est donc indépendante de la chaîne, et un seul chemin de code sert le testnet, le devnet, le mainnet et toute chaîne ZooBC privée ou parallèle.
| Ce qu'elle fait | Elle empêche le rejeu d'une chaîne à l'autre. Une signature produite pour la chaîne A ne sera pas valide sur la chaîne B. C'est ce pour quoi elle a été conçue, et elle le fait complètement. |
|---|---|
| Ce qu'elle ne fait pas | Empêcher un hôte compromis de désigner la mauvaise chaîne. L'appareil signe pour n'importe quel hachage qui lui est transmis. |
Une table des hachages de genèse connus dans le firmware reste donc souhaitable, mais dans un seul but : afficher à l'écran un nom de réseau digne de confiance, en revenant à « UNKNOWN CHAIN » suivi des 8 premiers caractères hexadécimaux lorsque le hachage ne figure pas dans la table.
Ne refusez pas par défaut les chaînes non reconnues. Le hachage du devnet change à chaque relance, et les chaînes privées ou parallèles sont normales sur ZooBC : un refus strict rendrait l'appareil inutilisable pour l'intégration et pour des déploiements légitimes. Si un fabricant souhaite ce verrou, faites-en un réglage explicite de l'utilisateur, désactivé par défaut.
Types de transaction : ce qu'un firmware standard devrait prendre en charge
Identifiant de type = group + 256 × subtype. Il existe cinquante-six types. Ces trois-là couvrent le paiement en ZBC, le paiement en n'importe quel jeton et la finalisation d'un séquestre, et leurs corps sont petits et de taille fixe.
| id | nom | corps | destinataire |
|---|---|---|---|
| 1 | SendZBC | amount u64le (8) | requis, tout type |
| 11 | TransferToken | token_id i64le (8) ‖ amount i64le (8) ‖ fee_in_token u8 facultatif | requis |
| 4 | ApprovalEscrow | approval u32le ‖ escrowed_transaction_hash (32) = 36 octets | marqueur vide ; l'expéditeur est l'approbateur |
token_id est une valeur signée de 64 bits dérivée d'un hachage : elle peut donc être négative. Les jetons Genesis ont les identifiants 1 à 14 ; le ZBC lui-même a l'identifiant 0 et n'apparaît jamais dans TransferToken. Chaque jeton a son propre decimals (0 à 8), que l'appareil ne peut pas vérifier.
ApprovalEscrow a changé dans la v0.4.0. Le corps était approval plus un identifiant de 8 octets (12 octets) ; il est désormais approval plus le hachage de 32 octets de la transaction placée sous séquestre (36 octets), et l'analyseur refuse toute autre longueur. La raison tient directement aux portefeuilles matériels : un signataire qui ne voyait qu'un identifiant de 8 octets ne pouvait pas vérifier ce qu'il libérait, et un identifiant de 8 octets peut être trouvé par une recherche en 2^64 contre un faux séquestre présenté à l'appareil. Avec le hachage complet, l'appareil peut vérifier ce qu'il approuve.
Ce que l'appareil vérifie
| Structure | Analyser les 11 champs. Chaque préfixe de longueur doit être cohérent et le tampon doit se terminer exactement après le message. Dans tout autre cas : refuser. |
|---|---|
| Version | Doit valoir 1. |
| Expéditeur | Doit être égal au compte que l'appareil dérive lui-même pour le chemin demandé. L'appareil ne fait jamais confiance aux octets d'expéditeur qui lui sont transmis ; il redérive et compare les 36 octets. C'est là toute la garantie « d'où vient-elle ». |
| Type | Doit appartenir à l'ensemble pris en charge, sauf si le mode expert est activé. |
| Longueur du corps | 8 pour SendZBC, 16 ou 17 pour TransferToken, 36 pour ApprovalEscrow. Octets en trop : refuser. Le nœud accepterait un corps SendZBC plus long et en ignorerait la fin, ce qui est précisément le moyen de faire passer une charge utile cachée. |
| Expiration du séquestre | Doit être postérieure à l'horodatage de la transaction. |
| Hachage de genèse | Doit faire exactement 32 octets. L'appareil ne le valide pas par rapport à une liste, mais une longueur incorrecte constitue une requête malformée. |
| Jamais | Accepter un condensé précalculé à signer. L'appareil hache lui-même les octets, étiquette et genèse compris. |
| Plafonds de taille | Recommandé : transaction non signée de 2 Ko au plus, message de 256 o, instruction de séquestre de 512 o, en refusant tout ce qui est plus grand avec TOO_LARGE. Les transactions de niveau 1 font moins de 300 octets, mais un ApprovalEscrow arrive avec une seconde transaction jointe pour vérification : le plafond doit donc couvrir les deux tampons. Les charges utiles plus volumineuses relèvent des portefeuilles logiciels. |
Ce que l'utilisateur approuve à l'écran
Écran par écran, dans cet ordre, adapté du signataire de référence validé sur la chaîne.
| Réseau | Nom issu de la table du firmware pour le hachage de genèse fourni ; « UNKNOWN CHAIN » suivi des 8 premiers caractères hexadécimaux s'il ne figure pas dans la table. |
|---|---|
| Type | « Send ZBC », « Send token », « Approve escrow ». |
| Montant | amount / 1e8 avec le symbole ZBC. |
| Destinataire | Adresse complète, jamais abrégée. Une adresse abrégée au milieu est précisément ce que déjoue une clé sosie générée par recherche de motif (vanity). |
| Frais | unités atomiques / 1e8 ZBC. |
| Total sortant | montant plus frais en un seul nombre, pour les montants en ZBC uniquement. Un montant en jetons et des frais en ZBC sont des unités différentes et ne doivent pas être additionnés. |
| Message | Affiché s'il est imprimable, sinon « N bytes (binary) » suivi des 8 premiers caractères hexadécimaux de son SHA3-256. |
| Séquestre | Approbateur en entier, commission, expiration sous forme de date et d'heure, texte de l'instruction. |
| Jeton (type 11) | Identifiant du jeton (décimal signé) et montant atomique. Si l'hôte fournit decimals ou symbol comme indications d'affichage, affichez le montant mis à l'échelle avec la mention « as reported by the app » (selon l'application), car l'appareil ne peut pas les vérifier. |
| Approbation de séquestre | « APPROVE » ou « REJECT » en gros caractères, puis le détail vérifié du séquestre, puis les frais. |
Il n'est pas nécessaire d'afficher les horodatages. Ils n'ont pas de sens pour l'utilisateur et le nœud impose la fenêtre.
Protocole entre l'hôte et l'appareil
Le transport est la couche USB/HID propre au fabricant, avec son découpage en blocs existant. C'est le contenu qui compte.
| commande | requête | réponse |
|---|---|---|
| GET_APP_VERSION | aucune | version du firmware, ensemble des types pris en charge, taille max. des transactions |
| GET_PUBLIC_KEY | account_index, confirm_on_device | pubkey(32), address (chaîne de 66 caractères). En cas de confirmation, l'appareil affiche l'adresse complète pour que l'utilisateur la compare avec celle du site web. |
| SIGN_TX | account_index, genesis_hash(32), unsigned_tx_bytes | signature(64), tx_hash(32), ou un refus typé : SENDER_MISMATCH, UNSUPPORTED_TYPE, MALFORMED, USER_REJECTED, TOO_LARGE, ESCROW_HASH_MISMATCH |
| SIGN_MESSAGE | account_index, message_bytes | signature(64), Ed25519 brut, voir la règle ci-dessous |
genesis_hash est déterminant, et non une simple indication d'affichage. Dans le brouillon 1, le paramètre réseau pouvait être une énumération, car il ne servait qu'à choisir un libellé. Il fait désormais partie du condensé : il doit donc s'agir des 32 octets réels, et une valeur erronée produit une signature qu'aucune chaîne n'acceptera.
La règle de signature brute. Une commande de signature brute ne doit jamais signer une entrée d'exactement 32 octets, et ne devrait signer que des entrées qu'elle peut afficher sous forme de texte. Une entrée brute de 32 octets pourrait être le condensé SHA3 d'une transaction que l'utilisateur n'a jamais vue, ce qui transformerait la commande de message en signature aveugle de transactions. Refuser la longueur 32 ferme cette faille sans aucune modification de la chaîne.
La séquence complète
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'hôte compare le transaction_hash du nœud avec le tx_hash de l'appareil. Ils doivent être identiques ; sinon, l'hôte a modifié les octets après la signature.
Lisez genesis_hash et signing_version depuis le point de terminaison vers lequel vous diffuserez, puisque c'est cette chaîne qui juge la signature.
Identité de la chaîne
Le seul identifiant de réseau dont dispose ZooBC est le hachage du bloc de genèse. Les nœuds le comparent avant d'établir une connexion entre pairs ; il n'existe pas d'octet d'identifiant réseau. GET /api/v1/node/info le renvoie avec deux champs apparus dans la v0.4.0 :
"genesis_hash": "090ab3c7...", "signing_version": 2, "signing_tag": "ZBC-TX"
signing_version absent signifie que le nœud est antérieur à la v0.4.0 et applique l'ancien condensé non lié. Un hôte qui ne prend en charge que les chaînes actuelles peut interpréter son absence comme « ne pas signer ici ». Un nœud affiche l'hexadécimal en minuscules et l'API d'archive en majuscules : décodez donc sans tenir compte de la casse et retirez un éventuel 0x initial. Les octets sont les mêmes dans les deux cas.
Où le lire. Une passerelle répond à /api/v1/node/info depuis le service d'archive plutôt que depuis un nœud. L'archive transmet les deux champs de signature provenant du nœud qu'elle reflète, en les émettant exactement lorsque le nœud les signale, à partir du build 7ad161a4. Lisez signing_version depuis un point de terminaison de nœud, ou depuis une passerelle sur ce build ou un build ultérieur, et interprétez son absence comme « demander à un nœud » plutôt que comme une réponse.
Diffusion
POST /api/v1/transactions. Inchangé dans la v0.4.0, aucun nouveau champ. transaction_body_bytes correspond au corps uniquement : le nœud reconstruit l'enveloppe à partir des champs nommés, ajoute en tête son propre hachage de genèse et l'étiquette, puis recalcule le condensé ; chaque champ doit donc être identique à ce qui a été signé.
{
"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 }
}
Le succès est un HTTP 202 avec {"status":"success","duplicate":false,"transaction_hash":"..."}. duplicate:true signifie que le nœud la détient déjà : ne la renvoyez pas. Traitez aussi 200 comme un succès. Un échec est un 400 avec un corps d'erreur, ou un 503 en cas de charge.
Un test d'intégration utile : le pool de préparation vérifie d'abord la signature, si bien qu'une transaction qui atteint une erreur ultérieure comme « Sender account does not exist » a déjà passé la vérification de signature. Vous pouvez valider votre procédure de signature avec une clé non approvisionnée.
Quand une signature est rejetée
Une incohérence de chaîne ne peut aujourd'hui pas être distinguée de tout autre échec de signature. Un condensé de l'ancien format et un condensé pour la mauvaise chaîne renvoient tous deux exactement le même corps :
{"success":false,"error":"Transaction validation failed: ValidationError: Invalid transaction signature","code":400}
Examinez donc les causes dans cet ordre, de la plus probable à la moins probable :
| 1 | Le tag omis, terminé par un octet NUL, ou écrit sur sept octets. |
|---|---|
| 2 | Keccak-256 utilisé à la place de SHA3-256. |
| 3 | Un hash de genèse qui ne correspond pas à la chaîne vers laquelle la transaction est diffusée. |
| 4 | L'octet final multi_party du séquestre omis. |
| 5 | Un corps ApprovalEscrow encore de 12 octets au lieu de 36. |
Points de terminaison à cibler
| chaîne | passerelle (HTTPS) | signature | remarque |
|---|---|---|---|
| testnet | https://this-gateway | passe à la v0.4.0 | la cible d'intégration une fois la bascule faite |
| devnet | https://socialconnect.network | v0.4.0 | 11 nœuds, faucet sur /faucet ; le hash de genèse change à chaque relance |
| mainnet | pas encore lancé | v0.4.0 ou ultérieure | lire son hash de genèse sur la chaîne à son ouverture |
Acheminez les lectures vers un nœud d'archive, puis une passerelle, puis un nœud. Diffusez vers une passerelle ou un nœud, jamais vers un point de terminaison d'archive.
Lisez le hash de genèse à l'exécution ; ne le codez pas en dur. Celui du devnet change à chaque relance. Une table de hashes dans le firmware ne sert qu'à afficher un nom à l'écran : une entrée périmée se dégrade donc en « UNKNOWN CHAIN » au lieu de casser la signature. Quand le hash du devnet change, toutes les signatures faites auparavant deviennent nulles, et c'est précisément la fonctionnalité qui fait son travail.