Rand Protocol
Documentazione
Guide

Bridge

Il bridge dei guardian così come è costruito: un lock sulla chain di origine, un'attestazione del guardian con una co-firma Dilithium2, un BridgeAttest che deposita una nota, un BridgeBurn in un solo bundle che effettua il prelievo, con il formato wire, i sette backing di zUSD, le commissioni zUSD su mint e burn, i limiti di mint e la pausa, le rotazioni rules-v2 con la relativa prova di possesso e il ritardo, e ciò che è attivo sulla chain 20.

Usa questa pagina per integrare il bridge: costruire un deposito su una catena di origine, inviare un’attestazione su Rand, bruciare un asset bridged per farlo uscire, e leggere lo stato pubblico del bridge. La chain 20, l’attuale testnet (attiva dal 2026-10-01 06:17 UTC, fullnode v0.6.8), è stata creata con una sezione bridge: otto guardiani a partire dall’indice di guardian set 1, otto guardiani post-quantum, una chiave di pausa, emettitori per Ethereum, BSC, Tron e Solana (i primi tre sono gli endpoint ridistribuiti il 2026-09-30), il gruppo rules-v2, il cui tetto globale di mint è 4.000 zUSD per ogni 24 ore mobili, una soglia di replay in ingresso, e due gruppi nuovi nella v0.6.8: commissioni di 10 bps su ogni mint e ogni burn, prelevate in zUSD, e regole di rotazione che impongono a una rotazione del set post-quantum o della chiave di pausa di provare il possesso delle nuove chiavi e di attendere un giorno. A differenza di quello della chain 14, il suo genesis elenca zUSD con tutti e sette i backing e riporta la custodia che è passata dalla chain 14 attraverso ogni cut successivo (Riportato dalla chain 14). rand_getAssets e /bridge/assets dell’explorer indicano il locked corrente di ciascun backing.

Il flusso

source chain (Ethereum / BSC / Tron / Solana)                 Rand
┌────────────────────────────────┐                           ┌────────────────────────────────┐
│ lock tokens, emit              │  guardians observe,       │ BridgeAttest transaction        │
│ (amount, token, to, fee)       │  sign mu = keccak²(body)  │   admission → one deposit note  │
└────────────────────────────────┘  threshold n·2/3 + 1      │   computed by the chain          │
              ▲                     + Dilithium2 co-signers  │                                  │
   release after guardian         guardian committee         │ BridgeBurn transaction           │
   signatures on the burn  ◄───── secp256k1 keys ◄─────────  │   one bundle: slots 0–1 burn the │
   message                        read the burn log          │   asset, 2–3 pay the RAND fee;   │
                                                             │   emits a sequenced burn message │
                                                             └────────────────────────────────┘

In ingresso: il valore viene bloccato sulla catena di origine, i guardiani attestano, chiunque invia l’attestazione a Rand, e il ledger aggiunge una nota di deposito per il destinatario: una nota la cui parola asset è l’indice di registro del token bridged. In uscita: una transazione Rand brucia note di quell’asset ed emette un messaggio; i guardiani attestano quel messaggio; il contratto sulla catena di origine rilascia i fondi. Rand non verifica mai l’header di una catena di origine né una prova di stato: le firme del comitato di guardiani sono l’intero modello di fiducia.

Modello di fiducia e id di catena

Id di catena locali al bridge, condivisi con i contratti (bridge-codec):

catenaid
Rand1
Ethereum2
BSC3
Tron4
Solana5

I guardiani sono un comitato di chiavi secp256k1; l’identità di un guardiano è costituita dagli ultimi 20 byte di keccak256(uncompressed_pubkey[1..]), la derivazione degli indirizzi di Ethereum, così che una stessa chiave funzioni sui contratti EVM, sul programma Solana e su Rand. Il quorum per un set di dimensione n è n·2/3 + 1. Ogni firma deve essere low-s (s ≤ n/2), il che esclude la malleabilità. Il digest firmato dai guardiani è mu = keccak256(keccak256(body_bytes)). Una rotazione del guardian set è un’attestazione dell’emettitore di governance (chain 1, keccak256("rand-bridge-governance")), deve portare new_index == current + 1 e deve essere firmata dal set corrente, non da uno superato; il set uscente conserva una finestra di tolleranza di 86.400 secondi per i trasferimenti in corso. (Questo è il set ECDSA. Il set post-quantum e la chiave di pausa ruotano con azioni proprie, che sulla chain 20 attendono un ritardo di un giorno: sotto.) Se n·2/3 + 1 guardiani colludono possono coniare valore arbitrario; il rafforzamento del bridge limita quanto e quanto in fretta, e non elimina questo rischio.

Binding della transazione. Una transazione Rand non porta alcuna firma: un burn è autorizzato dalla prova STARK del suo bundle. Dalla chain 14 ogni prova di bundle è prodotta su Transaction::binding, e verificata rispetto a esso: un hash dell’intera transazione (id di catena, l’unico bundle con le sue quattro envelope, l’azione) con i soli byte della prova azzerati. Dalla chain 17 un bundle porta due prove, la prova del bundle e la prova di autorizzazione della spesa: il binding azzera entrambe, mantiene l’auth_commit del bundle, ed entrambe sono verificate rispetto a esso. Una copia di un burn in sospeso con to, relayer_fee o (to_chain, token) modificati, o di un’attestazione con time o envelope modificati, non supera la prova (TxError::InvalidBundleProof). Sulla chain 20 il binding porta anche l’hash del genesis (genesis binding_domain: 1: rand-tx-bind-2 sull’hash del genesis, l’id di catena, il bundle e l’azione), così che neppure una prova prodotta per un’altra catena che condivide lo stesso id di catena risulti valida. Wallet e relayer devono eseguire un binario compilato per la catena a cui inviano: v0.6.8 sulla chain 20 (v0.6.7 sulle chain 18 e 19); un rand più vecchio produce prove e firme che la chain 20 rifiuta.

I daemon dei guardiani e dei relayer non fanno parte del full node; il full node porta il codec condiviso e la metà ricevente.

Formato di trasmissione

Ogni intero è big-endian; la versione del formato è 1.

Envelope di attestazione

offsetlung.campo
01version = 1
14guardian_set_index
51n_sigs
666·n_sigsfirme: index (1), r (32), s (32), v (1)
6 + 66·n_sigs…body

Header del body — 51 byte, poi il payload

offsetlung.campo
04timestamp
44nonce
82emitter_chain
1032emitter_address
428sequence
501consistency_level
51…payload

Payload Transfer — id 1, 133 byte

offsetlung.campo
01id = 1
132amount (u256; Rand usa i 128 bit bassi, e un deposito deve entrare anche in un u64 di nota)
3332token_address
652token_chain
6732to — l’hash del destinatario, vedi sotto
992to_chain
10132fee (u256): la quota di amount spettante al relayer

Payload GuardianSetUpgrade — id 2: id (1), new_index (4), n (1), poi n chiavi da 20 byte.

Gli indirizzi Ethereum, BSC e Tron sono di 20 byte, riempiti a sinistra con 12 byte zero negli slot da 32 byte; un indirizzo Solana riempie lo slot. Un Transfer deve provenire dall’emettitore registrato della catena per emitter_chain, e token_chain deve essere uguale alla catena emittente. Un’attestazione pesa al massimo 16 KiB. Nulla del rafforzamento del bridge ha cambiato il formato di trasmissione, il codec o gli endpoint già distribuiti sulle catene di origine: le co-firme post-quantum e i messaggi di governance viaggiano nella transazione Rand, non nell’attestazione.

Il destinatario a 32 byte

Un indirizzo shielded è di 1.216 byte e il formato ne prevede 32. Il campo to porta un hash dell’indirizzo:

to = blake3("rand-shielded-recipient" || pk_bytes || kem_ek)     // ShieldedAddress::recipient_hash

Il depositante indica l’hash sulla catena di origine, i guardiani lo firmano, chi invia mette l’indirizzo completo nell’azione BridgeAttest, e il ledger ricalcola l’hash e rifiuta una discrepanza: altrimenti chi invia potrebbe scegliere chi riceve il deposito di qualcun altro. I front end delle catene di origine devono accettare questa forma; il wallet la stampa. La chain 11 aveva brevemente reso lo slot direttamente l’id del ricevente; quella forma di indirizzo è stata annullata con la chain 12 (Account e indirizzi).

Un token, molti backing: zUSD

Il registro degli asset non è più del bridge. È passato nel registro di token RPL a livello di ledger, condiviso da token nativi e bridged, e la state root della catena vi si impegna come tokens_root. Un token bridged è una voce di registro la cui autorità di mint è Bridge { backings }, e non corrisponde più a una singola coppia (chain, token). Il primo, zUSD, è garantito da USDT e USDC bloccati su quattro catene: Ethereum, Binance Smart Chain e Solana portano entrambe le monete, Tron solo USDT (Circle ha dismesso USDC su Tron):

chain  token (32-byte wire form)                                                  coin  decimals
2      000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7           USDT  6
2      000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48           USDC  6
3      00000000000000000000000055d398326f99059ff775485246999027b3197955           USDT  18
3      0000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d           USDC  18
4      000000000000000000000000a614f803b6fd780986a42c78ec9c7f77e6ded13c           USDT  6
5      ce010e60afedb22717bd63192f54145a3f965a33bb82d2c7029eb2ce1e208264           USDT  6
5      c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61           USDC  6

Sulla chain 20 zUSD ha indice 1, rpl1xtj6k2x8strx8c2d5fjs50ltztck5ykms4ve7nmzmstf6fhn0v0spelqtx, nome “Shielded USD”, 8 decimali, elencato nel file genesis, come sulle chain da 15 a 19, con il nome, il simbolo e il salt che aveva sulla chain 14, così che il suo asset id (32e5ab28c782c663e14da2650a3feb12f16a12db85599f4f62dc169d26f37b1f) e il suo id rpl1… si siano riportati invariati (explorer: GET /tokens/1). Sulla chain 14 era stato registrato all’altezza 256 da una transazione register_bridged_token.

Backing { chain: u16, token: [u8; 32], decimals: u8, locked: u64, minted_today: u64, mint_day: u32 }

Una coppia (chain, token) garantisce al massimo un token nell’intero registro (TokenError::BackingTaken); un token ha da 1 a 32 backing; decimals è il conteggio della moneta di origine, da 0 a 18, mai gli 8 del token bridged. Il backing in pool è un rischio dichiarato: le sette monete sono fungibili in zUSD e da zUSD alla pari, quindi un depeg di una qualsiasi di esse dilui­sce ogni detentore di zUSD, e non esiste alcun tetto lato Rand alla quota di un singolo backing.

zUSD è un paniere. Chi detiene zUSD sopporta il rischio di ogni garanzia elencata, non della moneta che ha depositato: un zUSD è un credito sul pool, su qualunque garanzia con locked sufficiente. Nulla attribuisce un prezzo a una garanzia. Un lock di 1 unità di una qualsiasi moneta elencata conia 1 zUSD, e un burn può indicare qualunque garanzia il cui locked lo copra, così una moneta che ha perso il peg può essere depositata alla pari e lo zUSD bruciato verso una moneta che ha mantenuto il peg, finché le garanzie sane non sono esaurite. La perdita è limitata dalle finestre di conio mobili e dalla custodia effettivamente bloccata: su chain 20 si possono coniare al massimo 4,000 zUSD su tutte le garanzie, e 100,000 per garanzia, in ogni arco di 24 ore (rules v2), e da una garanzia sana non può uscire più di quanto essa contenga. La finestra limita il ritmo, non il totale: dà il tempo di mettere in pausa i conî, il che blocca i nuovi depositi della moneta debole mentre i burn restano aperti. Non esiste un limite di riscatto per singola garanzia né una pausa per singola garanzia. Su chain 14, prima di rules v2, gli unici limiti erano il pauser degli endpoint di origine e i limiti di frequenza per token.

L’invariante: total_supply == Σ backings.locked. TokenRegistry::lock (un BridgeAttest) aggiunge l’importo attestato al locked della garanzia e al total_supply del token in una sola chiamata; TokenRegistry::release (un BridgeBurn) sottrae l’importo rilasciato da entrambi. Nient’altro scrive l’uno o l’altro campo, e la foglia del token impegna il locked di ogni garanzia in tokens_root, quindi due nodi in disaccordo su uno dei due divergono nella radice di stato. Gli endpoint di origine non applicano alcuna commissione sul lock e detraggono l’intero importo attestato al rilascio, quindi il locked di ogni garanzia è pari alla custodia di quell’endpoint, salvo i messaggi in transito. Le commissioni su chain 20 non lo alterano: una commissione è una note zUSD che resta emessa e garantita, quindi il deposito blocca comunque il lordo e il burn rilascia solo amount − fee. Su chain 20 la trattenuta propria degli endpoint, di 10 bps su USDT al rilascio, è stata portata a 0 al momento del cut; la commissione ora viene prelevata su Rand.

Gli importi sono in unità a 8 decimali sul wire, qualunque siano i decimali della moneta di origine: un deposito di 1 USDT da Ethereum (6 decimali) viene bloccato come 100_000_000 unità. Solo l’indice 0, RAND, ha i nove decimali di questa chain.

Un token deve essere elencato prima del suo primo deposito. Non esiste più la registrazione al primo avvistamento: un’attestazione che indica una (chain, token) che nessuno ha elencato viene rifiutata con BridgeError::UnlistedToken (token … of chain … is not listed on this chain). I fondi restano nella custodia dell’endpoint di origine finché un quorum di guardiani post-quantistico non elenca la moneta (Listing after genesis).

asset_id = blake3("rand-bridge-asset" || token_chain (2 bytes BE) || token_address (32 bytes))

rand_bridgeAssetId(token_chain, token_address) calcola ancora questo valore su qualunque chain, ed è ciò a cui si risolve la (token_chain, token_address) del payload di un Transfer, ma non è più una chiave del registro. Ciò che una note e un wallet portano con sé è l’indice del token, un u32 denso: l’indice 0 è RAND e non compare mai nel registro. rand_getAssets ha una riga per garanzia, con tutte le righe di uno stesso token sotto lo stesso indice:

[{ "index": 1, "chain": 2, "token": "000000000000000000000000dac17f…", "asset_id": "…", "decimals": 6,
   "locked": "0", "mint_cap_per_day": "10000000000000", "minted_today": "0", "mint_day": 20716 }]

locked, mint_cap_per_day e minted_today sono stringhe decimali in unità a 8 decimali, come ogni importo u64 che l’RPC serve da chain 14; index, chain, decimals e mint_day restano numeri. I metodi a livello di token — rand_getTokens, rand_getToken, rand_getTokenSupply — restituiscono un token bridged come una sola riga con tutte le sue garanzie (RPL tokens). Il GET /bridge/tokens dell’explorer è l’allowlist statica delle monete che il bridge accetta.

Inbound: BridgeAttest

Action::BridgeAttest { attestation: Vec<u8>, recipient: ShieldedAddress, r: Word8, time: u32,
                       asset: u32, envelope: Envelope, pq_signatures: Vec<PqSignature> }

PqSignature { index: u8, signature: Vec<u8> }        // Dilithium2, 2,420 bytes

Trasportato da una normale transazione shielded il cui unico bundle ad asset nascosto paga la base di 0.001 RAND e nient’altro: il suo relayer paga per un depositante che non detiene ancora RAND. La chain, non chi sottomette, calcola la note di deposito { pk: recipient.pk, from: 0, amount, asset: <index>, time, r } dall’importo firmato dai guardiani, quindi un relayer non può gonfiare né reindirizzare un conio; il suo commitment è l’unica foglia che una transazione non porta sul wire. Su chain 20 l’amount della note è il lordo meno la commissione di conio, e la chain aggiunge una seconda note, la commissione, alla tesoreria (Fees on mint and burn).

Il blinding è derivato, non scelto. r deve essere

r = blake3("rand-deposit-r-1" || mu)        // read as eight little-endian words; mu = keccak256(keccak256(body))

il digest firmato dal quorum dei guardiani; qualunque altro valore è BridgeError::WrongDepositBlinding, rifiutato prima di qualsiasi lavoro sulle firme. Il campo resta sul wire — la ricostruzione dei campi pubblici da parte del wallet lo legge lì — è soltanto vincolato, e la regola copre anche l’attest di una rotazione. Prima di essa, chiunque vedesse la transazione pendente di un relayer poteva sottomettere la stessa attestazione con un blinding proprio: il destinatario riceveva comunque una note, ma la transazione del relayer moriva dopo che questi aveva pagato una proof. Con r fissato, due sottomissioni della stessa attestazione allo stesso time indicano una sola note e il pool le mette in conflitto sul commitment.

time e asset sono le due previsioni del depositante, fatte perché la busta possa essere sigillata prima che la transazione esista. time è vincolato alla stessa finestra di quello di un bundle (1,024 blocchi su chain 20, dal proof_window_blocks del genesis; 256 prima), e non è derivato di proposito: nessuna regola fornisce un time su cui tutti i sottomettenti concordino, quindi un front-runner può ancora scegliere un altro time nella finestra — una note diversa dello stesso destinatario, importo e asset, che il wallet del destinatario trova allo stesso modo — e le due sottomissioni collidono invece sul digest dell’attestazione. asset deve essere uguale all’indice che il registro ha già assegnato a (chain, token), altrimenti TxError::AttestAssetMismatch.

Il fee del payload Transfer viene trasportato e non pagato a nessuno: non c’è un’identità di sottomettente da pagare, quindi il deposito è l’importo lordo, meno soltanto la commissione di conio propria della chain dove il genesis ne imposta una (chain 20). La protezione dal replay avviene per digest: ogni mu consumato entra nell’insieme spent del bridge, e il mempool rifiuta un secondo relayer in corsa sulla stessa attestazione. Un deposito è inoltre soggetto al limite di conio della garanzia, su chain 20 anche a quello globale, e viene rifiutato mentre i conî sono in pausa (B1, rules v2). Entrambi i limiti contano il lordo. Su una chain il cui genesis imposta una soglia di replay, chain 20 inclusa, un transfer il cui sequence è inferiore alla soglia della sua chain di origine viene rifiutato con BelowReplayFloor prima che venga verificata qualsiasi firma (Carried over from chain 14).

rand bridge-mint <attestation hex or @file> --pq <JSON or @file> [--to rand1…] [--fee <RAND>] [--no-wait]
rand bridge-rotate <rotation hex or @file> --pq <JSON or @file> [--fee <RAND>] [--no-wait]

--pq è obbligatorio: il quorum di co-firme Dilithium2 dei guardiani come [{"index":0,"signature":"<4840 hex>"},…]. Prima di qualsiasi proving il wallet decodifica l’attestazione, verifica il quorum rispetto ai pq_guardians del nodo e all’id della chain, verifica che il destinatario abbia come hash quello attestato e legge l’indice elencato; poi deriva r, sigilla la busta del destinatario (per l’importo netto su una chain con commissioni) e paga con un solo bundle di proprio RAND. bridge-rotate sottomette una rotazione dell’insieme dei guardiani (payload 2), che non deposita nulla, e rifiuta qualunque cosa non faccia avanzare di uno l’insieme di Rand. Poiché ogni campo di una note di deposito è pubblico in quella transazione, il wallet di un destinatario ricostruisce le note di deposito a lui indirizzate scorrendo le transazioni bridge_attest confermate, senza aprire alcuna busta: un relayer ostile può sigillare spazzatura ma non può nascondere la note.

Outbound: BridgeBurn

Transaction {
  chain_id,
  bundle: <hidden-asset bundle: slots 0–1 spend the token, slots 2–3 pay the RAND fee;
           burn_asset = asset, burn_a = amount, burn_r = 0, fee >= BRIDGE_BURN_FEE>,
  action: BridgeBurn { asset, amount, relayer_fee, to_chain, token, to }
}

Dal bundle ad asset nascosto di chain 14 un burn è un solo bundle (prima erano due): gli slot 0–1 dimostrano che chi brucia possedeva note dell’asset per una somma pari ad almeno amount, con burn_a il valore che lascia il pool, e gli slot 2–3 pagano la commissione in RAND. La commissione è BRIDGE_BURN_FEE, 0.01 RAND, che copre la base del bundle. relayer_fee è una quota di amount (fee ≤ amount) ricavata sulla chain di destinazione dal contratto di rilascio, non un costo aggiuntivo, quindi burn_a è esattamente amount. Su chain 20 la chain trattiene inoltre una commissione di burn di 10 bps di amount come note zUSD alla tesoreria, e il rilascio è amount − fee; la quota del relayer deve rientrare in quel rilascio (Fees on mint and burn).

Un burn indica la sua moneta. (to_chain, token) deve essere una garanzia dell’asset bruciato (altrimenti TokenError::NotABacking), e due regole vengono verificate al passo dell’azione, prima della proof del bundle:

  • L’unità di rilascio. Una garanzia con d < 8 decimali di origine rilascia in unità di 10^(8−d) (100 per una moneta a 6 decimali; 1 per una a 18 decimali). L’importo rilasciato e relayer_fee devono essere ciascuno un multiplo intero, altrimenti TokenError::NotReleasable (… is not a multiple of the release unit …): una frazione inferiore all’unità minima della moneta di origine resterebbe bloccata nella custodia.
  • La custodia della garanzia stessa. L’importo rilasciato ≤ locked per quella garanzia, altrimenti TokenError::InsufficientBacking (only … is locked in that backing, not the … asked for). L’offerta totale del token può coprire il rilascio; ciò che deve coprirlo è la custodia di questa singola garanzia.

L’importo rilasciato è amount su una chain senza commissioni e amount − fee su chain 20.

to deve avere la forma corretta (20 byte con zero a sinistra su chain 2, 3 e 4; mai tutto zero). Il ledger registra un BridgeBurnRecord sotto un burn_sequence strettamente crescente, con l’hash della transazione nello slot del mittente, e i guardiani leggono il log dei burn:

→ {"method":"rand_getBridgeBurn","params":[13]}
← { "sequence": 13, "amount": "99900000", "fee": "99900", "release_amount": "99800100",
    "body_hex": "…", "digest": "a4bc1c7d…63f4", "tx": "ceb9e3c2…0be7", "height": 2899 }

body_hex è il messaggio in uscita esattamente come i guardiani devono calcolarne l’hash e firmarlo; il suo importo Transfer è release_amount. Il record qui sopra è la prima bruciatura della chain 20: 0.999 zUSD bruciati verso USDT su BSC all’altezza 2,899, di cui 0.000999 sono andati alla tesoreria e 0.998001 sono stati rilasciati.

rand bridge-burn <ASSET> <AMOUNT> <TO_CHAIN> <TOKEN> <TO> [--relayer-fee N] [--fee <RAND>] [--no-wait] [--yes]
rand bridge-message <SEQUENCE>       # one outbound message, verbatim, for a guardian to sign

Il wallet verifica prima di tutto, con una sola lettura dello stato del bridge e del registro dei token e prima di qualsiasi generazione di prove, che la chain abbia un bridge, che ASSET sia nel registro, che TOKEN su TO_CHAIN lo garantisca e abbia almeno AMOUNT bloccato, e che AMOUNT e la commissione del relayer siano unità di rilascio intere. Su una chain con commissioni stampa poi la commissione del bridge e l’importo che verrà rilasciato, e chiede conferma prima di generare la prova (--yes salta la domanda). Una bruciatura genera quindi una sola bundle: circa 100 s su un laptop e circa 1.5 MB di prova e, sulla chain 20, come per ogni bundle dalla chain 17, accanto ad essa una prova di autorizzazione di spesa (pochi secondi, circa 1.37 MB).

Commissioni su emissione e bruciatura

Novità del fullnode v0.6.8, impostate sulla chain 20 dal gruppo di genesi

"bridge": { …, "fees": { "mint_bps": 10, "burn_bps": 10, "recipient": "rand1…" } }

la chain applica una commissione a ogni emissione e a ogni bruciatura tramite bridge, nel token del bridge, su Rand, come nota schermata all’indirizzo della tesoreria recipient. Ciascuna aliquota è al massimo 100 bps (1 %); sulla chain 20 entrambe sono 10 bps, 0.1 %. La commissione è ⌊amount · bps / 10⁴⌋ arrotondata per difetto a un’unità di rilascio intera del collaterale (10^(8 − source decimals): 100 unità per un USDT a 6 decimali, 1 per una moneta a 18 decimali), così la commissione e il resto sono entrambi unità intere, e una commissione che si arrotonda a zero non genera alcuna nota (ledger/bridge_notes.rs, bridge_fee).

deposito (BridgeAttest) di un lordo gbruciatura (BridgeBurn) di amount
commissione⌊g · mint_bps / 10⁴⌋, per difetto a un’unità di rilascio⌊amount · burn_bps / 10⁴⌋, per difetto a un’unità di rilascio
lato utenteuna nota di deposito di g − feeamount bruciato (burn_a == amount, nessun resto del guest); il corpo in uscita attesta release_amount = amount − fee, e l’endpoint rilascia esattamente quell’importo
la nota della tesoreriafee, blinding blake3("rand-bridge-fee-r-1" ‖ mu), al time e sull’asset del depositofee, blinding blake3("rand-bridge-burn-fee-r-1" ‖ txid), al time della bundle
locked e total_supplyaumentano di gdiminuiscono di release_amount
verificato rispetto ai limiti di emissione per collaterale e globale, su gInsufficientBacking e NotReleasable su release_amount; la quota del relayer deve rientrarvi (FeeExceedsAmount)

Entrambe le note sono emesse e garantite, quindi total_supply == Σ locked == custody continua a valere: la nota di commissione è zUSD come ogni altra, e il suo valore resta bloccato sulla chain di origine finché qualcuno non la brucia. BRIDGE_BURN_FEE, i 0.01 RAND che una bruciatura paga nei suoi slot RAND, resta invariata e separata.

Il primo viaggio di andata e ritorno della chain 20, letto dal nodo: un deposito di 1 zUSD da BSC all’altezza 1,664 (4f6e6c54…1a64) ha dato al depositante 0.999 zUSD e alla tesoreria 0.001; una bruciatura di 0.999 zUSD verso USDT su BSC all’altezza 2,899 (sequenza 13) ha trattenuto 0.000999 zUSD e ne ha rilasciati 0.998001. Dopo di essa (letto il 2026-10-01) il collaterale USDT su BSC conteneva 22.001999 zUSD, e l’offerta del token, 99.001999 zUSD, era pari alla somma dei suoi collaterali.

Come la tesoreria trova le sue note. Una nota di commissione non ha busta: ogni sua parola è pubblica (l’importo dalla transazione e dall’aliquota di genesi, l’asset e il time dalla transazione, il blinding derivato, il proprietario il recipient di genesi). Il wallet il cui indirizzo è recipient ricostruisce le note di commissione nello stesso passaggio pubblico che ricostruisce le note di deposito, leggendo rand_getBridgeState.fees e i decimali dei collaterali una volta per sincronizzazione. Un nodo che mente sulle commissioni può solo far sì che una nota non venga trovata, mai accreditarne una. Il wallet di un depositante valuta il proprio deposito allo stesso modo, lordo meno commissione. Solo un wallet v0.6.8 lo fa; uno più vecchio, in ogni caso, non può inviare sulla chain 20.

Senza il gruppo, chain da 14 a 19, non c’è commissione: un deposito accredita il lordo e una bruciatura rilascia amount. Fino alla chain 20 l’unica commissione del bridge era il prelievo del 10 bps di USDT applicato dagli endpoint di origine al rilascio, sulla chain ospite; è stato portato a 0 al taglio della chain 20. Il gruppo è vincolato all’hash di genesi e incorporato nella radice del bridge sotto rand-bridge-fees-1. La codifica è nel §25 di docs/bridge.md del fullnode.

Rafforzamento del bridge

I locked per collaterale e la protezione InsufficientBacking presidiano il riscatto. Non limitano l’emissione: un’attestazione con un quorum di guardiani valido emette ciò che dichiara. Tre livelli colmano questa lacuna e un quarto permette di elencare un token senza un taglio della chain. Nessuno modifica gli endpoint sulla chain di origine, il formato di trasmissione dell’attestazione o bridge-codec.

B1: un limite di emissione per collaterale e una pausa delle emissioni

Il tokens.mint_cap_per_day di genesi si applica per collaterale: la chain 20 imposta 10000000000000, 100,000 zUSD per collaterale al giorno, come ogni chain dalla 14. Senza le regole v2 (chain 14) un giorno è timestamp_ms / 86_400_000 del timestamp del blocco; con le regole v2 (chain da 15 a 20) lo stesso limite è misurato su una finestra mobile di cap_window_secs di tempo di blocco, e il limite globale dell’intero registro — 4,000 zUSD per 86,400 s sulla chain 20 — è il vincolo più stretto (regole v2). Un deposito oltre il limite viene rifiutato con TokenError::MintCapExceeded prima del lavoro sulle firme dei guardiani, ed è ammissibile quando la finestra torna ad avere spazio.

Il bridge.pause_key di genesi è una sola chiave Dilithium2, distinta da ogni guardiano post-quantistico. Due azioni senza bundle e senza commissione:

  • PauseMints { nonce, signature } — firmata dalla sola chiave di pausa. Può solo mettere in pausa.
  • UnpauseMints { nonce, pq_signatures } — richiede un quorum di guardiani post-quantistici, mai la chiave di pausa.

Entrambe portano e incrementano pause_nonce, quindi nessuna delle due può essere riprodotta. Mentre è in pausa, ogni BridgeAttest di trasferimento viene rifiutato con BridgeError::MintsPaused; le bruciature e le rotazioni dell’insieme dei guardiani restano aperte, così una pausa non intrappola mai il riscatto. Con le regole v2 (chain da 15 a 20) anche l’elencazione è rifiutata durante la pausa. La chiave di pausa va tenuta su una macchina diversa da quelle delle chiavi dei guardiani.

rand bridge-pause --sig <HEX or @file> [--no-wait]        # no key file and no RAND needed
rand bridge-unpause --pq <JSON or @file> [--no-wait]

Ciò che la pausa copre: demoni dei guardiani ingannati o compromessi che cofirmano un’emissione falsificata mentre i detentori del quorum post-quantistico non lo sono. Non è una difesa contro il furto di quel quorum, che può togliere la pausa, elencare fino a 32 collaterali su un token, ciascuno con il proprio limite giornaliero, e con le regole v2 sostituire l’insieme post-quantistico e la chiave di pausa.

B2: timestamp dei blocchi limitati

Il timestamp del blocco di una chain con bridge decide la scadenza dell’insieme dei guardiani e il giorno del limite di emissione, quindi un leader non deve poterlo far saltare in avanti. Su una chain con un bridge:

  • Una regola di validità, verificata anche nella riproduzione: timestamp_ms ≤ parent.timestamp_ms + 60_000, altrimenti BlockError::TimestampLeap. Con la regola di retrocessione esistente (BlockError::TimestampRewind), il tempo di blocco avanza solo, di al massimo un minuto per blocco.
  • Una regola di voto, mai una regola di riproduzione: un validator trattiene il proprio voto da un blocco più avanti di 15,000 ms rispetto al proprio orologio.

Ogni validator di una chain con bridge ha quindi bisogno di NTP: chi è sfasato di oltre 15 secondi è un leader difettoso e un votante difettoso. Mentre una chain si rimette in pari dopo un arresto, il tempo di blocco può avanzare fino a 60 s per blocco, quindi un giorno di limite di emissione può scadere più in fretta di un giorno reale — al massimo il limite di un giorno in più per ogni giorno intero di ritardo accumulato.

B3: una cofirma Dilithium2 su ogni emissione

Ogni BridgeAttest, sia un deposito sia una rotazione, deve portare accanto al proprio quorum ECDSA un quorum di firme Dilithium2 su

M = "rand-bridge-pq-cosign-1" || rand_chain_id (u64 BE) || mu        // 63 bytes

mu è il digest firmato dal quorum ECDSA; l’id della chain impedisce a una cofirma di testnet di risultare valida su un’altra chain Rand. Le firme viaggiano nelle pq_signatures dell’azione, dentro il vincolo della transazione, così non possono essere rimosse o sostituite su una copia. Verificate dal più economico al più costoso: quorum(n) ≤ len ≤ n (PqNoQuorum), indici strettamente crescenti (PqIndexOrder), ogni indice < n (PqIndexOutOfRange), ogni firma di esattamente 2,420 byte (PqBadSignatureLength), e infine che ciascuna risulti valida sotto pq_guardians[index] (PqBadSignature). I due quorum sono contati in modo indipendente. Il bridge.pq_guardians di genesi è allineato per indice con l’insieme ECDSA di genesi (insieme 1 sulla chain 20). Una rotazione di payload 2 sposta solo l’insieme ECDSA; senza le regole v2 l’insieme post-quantistico è fisso per tutta la vita della chain, e con le regole v2 solo RotatePqGuardians lo sostituisce (sulla chain 20 nella sua forma V2, con una prova di possesso e un ritardo di un giorno). La chain 20 ne ha otto, le stesse otto chiavi delle chain da 15 a 19; un’emissione ne richiede sei (la chain 14 ne aveva sei e ne richiedeva cinque).

Questa co-firma vincola ancora solo il chain id, anche sulla chain 20: a differenza dei messaggi di governance riportati più avanti, non è stata spostata dietro il genesis hash, perché i daemon dei guardian che la producono si trovano in un altro repository e non sono cambiati. Una copia con lo stesso id di una chain che abbia copiato anche il guardian set accetterebbe le co-firme di un deposito prodotte per l’originale; il fee bundle della transazione di deposito è vincolato al genesis sulla chain 20, l’attestazione al suo interno no.

B4: elencare un token bridged dopo il genesis

Un indirizzo ordinario non può mai creare un token con autorità Bridge. Due azioni, ciascuna su un normale fee bundle in RAND e ciascuna sotto il quorum post-quantistico:

  • RegisterBridgedToken { name, symbol, salt, chain, token, decimals, nonce, pq_signatures } — un nuovo token bridged al successivo indice, con 8 decimali su Rand, entro il mint cap del genesis, con il suo primo backing. Commissione: la base del bundle più la registration_fee del registro (1 RAND sulla chain 20, quindi 1,001 RAND; la chain 20 brucia la commissione di registrazione invece di versarla al proposer).
  • ListBacking { token_index, chain, token, decimals, nonce, pq_signatures } — un ulteriore backing su un token bridged registrato, con locked = 0 iniziale. Commissione: la base del bundle.

Entrambe portano e incrementano list_nonce (altrimenti BadListNonce { expected, got }) ed eseguono gli stessi controlli: la chain ha un emitter registrato (BridgeError::NoEmitter), (chain, token) non fa ancora da backing a nulla, meno di 32 backing, decimals ≤ 18.

rand token register-bridged --name … --symbol … --salt <hex32> --chain N --token <hex32> --decimals N --pq <JSON or @file>
rand token list-backing --asset <index> --chain N --token <hex32> --decimals N --pq <JSON or @file>

Due regole di ordinamento. I guardian firmano prima la registrazione, attendono che sia confermata, rileggono l’indice assegnato e solo allora firmano gli elenchi che lo nominano: registrare un token nativo non richiede permessi, quindi un elenco firmato in anticipo potrebbe trovare il proprio indice già occupato. E una moneta viene elencata su Rand prima che setToken la abiliti sull’endpoint di origine: nell’ordine inverso, un deposito potrebbe bloccare fondi prima che Rand abbia un backing da accreditare, e la sua attestazione verrebbe rifiutata con UnlistedToken finché Rand non si allinea.

Messaggi di governance

Byte fissi, mai bincode; tutti gli interi in big-endian:

M_pause    = "rand-bridge-pause-1"        || chain_id u64 || nonce u64
M_unpause  = "rand-bridge-pq-unpause-1"   || chain_id u64 || nonce u64
M_list     = "rand-bridge-pq-list-1"      || chain_id u64 || nonce u64 || token_index u32 || chain u16 || token [32] || decimals u8
M_register = "rand-bridge-pq-register-1"  || chain_id u64 || nonce u64 || u8 len || name || u8 len || symbol || salt [32] || chain u16 || token [32] || decimals u8
M_rotate_pq    = "rand-bridge-pq-rotate-pq-1"    || chain_id u64 || rotation_nonce u64 || count u32 || key [1312] …   // rules v2
M_rotate_pause = "rand-bridge-pq-rotate-pause-1" || chain_id u64 || rotation_nonce u64 || key [1312]            // rules v2

M_pause/M_unpause portano pause_nonce, M_list/M_register portano list_nonce, e M_rotate_pq/M_rotate_pause portano rotation_nonce. count è il numero di chiavi, non una lunghezza in byte; ogni chiave è la chiave pubblica Dilithium2 di 1.312 byte grezzi, nell’ordine che avrà il nuovo set.

Sulla chain 20 ogni messaggio è vincolato al genesis hash (genesis binding_domain: 1): la cifra di versione del tag passa a 2 e il genesis hash di 32 byte si colloca tra il tag e il chain id, con la parte finale invariata — M'_pause = "rand-bridge-pause-2" || genesis [32] || chain_id u64 || nonce u64, e allo stesso modo rand-bridge-pq-unpause-2, rand-bridge-pq-list-2, rand-bridge-pq-register-2, rand-bridge-pq-rotate-pq-2 e rand-bridge-pq-rotate-pause-2 (bridge/gov.rs). Una chiave di pausa o un quorum che firma per una chain non firma quindi per nessun’altra chain che condivida il suo chain id e le sue chiavi. Un file firmato nella forma …-1 viene rifiutato sulla chain 20 (BadPauseSignature, PqBadSignature). Le chain da 14 a 19 usano i layout …-1 sopra riportati, byte per byte.

Rules v2: rotazioni, cap a finestra mobile, nessun elenco durante la pausa

Un gruppo opzionale all’interno della sezione bridge del genesis. La chain 14 non lo aveva; ogni chain dalla 15 in poi lo ha, la chain 20 con gli stessi valori:

"bridge": { …, "rules_v2": { "global_mint_cap_per_window": 400000000000, "cap_window_secs": 86400 } }
  • Due rotazioni sotto il quorum post-quantistico. RotatePqGuardians { new_pq_guardians, nonce, pq_signatures } sostituisce l’intero set post-quantistico; RotatePauseKey { new_pause_key, nonce, pq_signatures } sostituisce la chiave di pausa. Entrambe sono prive di bundle e di commissione come PauseMints, entrambe sono firmate dal set post-quantistico corrente, ed entrambe portano e incrementano rotation_nonce, così un quorum firmato autorizza esattamente una rotazione. Il nuovo set deve avere la stessa lunghezza del set ECDSA corrente, chiavi da 1.312 byte, nessuna ripetizione e non la chiave di pausa. Non c’è alcuna finestra di tolleranza: il set o la chiave sostituiti non firmano più nulla. Sulla chain 20 queste azioni v1 sono rifiutate, e una rotazione deve dimostrare il possesso delle nuove chiavi e attendere un giorno (sotto).
  • Cap a finestra mobile. Il mint_cap_per_day per backing viene misurato su una finestra mobile di cap_window_secs anziché sul giorno di calendario, e un secondo cap, valido per l’intero registro, global_mint_cap_per_window, copre insieme tutti i backing di tutti i token (TokenError::GlobalMintCapExceeded). Sulla chain 20 è pari a 400.000.000.000 unità, 4.000 zUSD per 86.400 s. Con la regola del giorno, il cap alle 23:59:59 e di nuovo alle 00:00:01 era il doppio del cap in due secondi; con la finestra il secondo mint viene rifiutato.
  • Nessun elenco durante la pausa. RegisterBridgedToken e ListBacking sono rifiutati con MintsPaused finché mint_paused. I burn e entrambe le rotazioni restano aperti.

Rotazione: possesso e ritardo di un giorno

Rules v2 lasciava aperte tre questioni: nulla dimostrava che le nuove chiavi fossero detenute da qualcuno (un errore di battitura, o una chiave che nessuno possiede, avrebbe bloccato per sempre il quorum post-quantistico), una rotazione aveva effetto nel blocco in cui veniva inclusa, e nulla poteva fermarne una ostile. Un secondo gruppo opzionale accanto a rules_v2 (che lo richiede), nuovo nella v0.6.8 (audit v6, BRG-14), le risolve. La chain 20 lo imposta:

"bridge": { …, "rotation": { "delay_secs": 86400, "needs_possession": true } }
  • Possesso. Una rotazione deve essere RotatePqGuardiansV2 { new_pq_guardians, possession, nonce, pq_signatures } oppure RotatePauseKeyV2 { new_pause_key, possession, nonce, pq_signatures }: possession[i] è la firma Dilithium2 della nuova chiave i sullo stesso messaggio firmato dal quorum (M_rotate_pq o M_rotate_pause, nella loro forma …-2 sulla chain 20), una per chiave nell’ordine delle chiavi, di 2.420 byte ciascuna. Le azioni v1 sono rifiutate con PossessionRequired. Un conteggio o una lunghezza errati vengono rifiutati prima che il quorum sia verificato; una firma di possesso errata (BadPossession { index }) dopo.
  • Un ritardo. Una rotazione accettata viene registrata come pending, con effective_at_secs = block time + delay_secs, ed entra in vigore al termine del primo blocco il cui timestamp la raggiunge. Sulla chain 20 sono 86.400 s, un giorno. Fino ad allora il vecchio set co-firma ogni mint e la vecchia chiave di pausa mette in pausa. Accettare una rotazione consuma un rotation_nonce e attivarla ne consuma un altro, quindi un’attestazione di mint accumulata nel pool sotto il vecchio set esce dal pool quando il set cambia. Può essere in sospeso una sola rotazione per tipo (RotationPending { effective_at_secs }).
  • L’annullamento. CancelRotation { kind, nonce, signature } (senza bundle e senza commissione) elimina la rotazione pending di kind (0 = il set post-quantistico, 1 = la chiave di pausa). È firmata dalla chiave di pausa corrente, l’unica chiave tenuta separata dal quorum, su "rand-bridge-cancel-rotation-2" || genesis [32] || chain_id u64 || rotation_nonce u64 || kind u8 sulla chain 20, e consuma il nonce. Un quorum compromesso non può quindi ruotare il set in meno di un giorno, e il titolare della chiave di pausa può fermarlo in quel giorno; la chiave di pausa entrante non può annullare il proprio arrivo.

Il firmatario è rand-node bridge-gov (rotate-pq, rotate-pause, cancel-rotation, ciascuno con i passaggi message, sign e submit; submit riverifica ogni riga di possesso prima dell’invio). rand_getBridgeState restituisce rotation_rules e pending_rotations; entrambi sono null su una chain senza il gruppo (chain da 14 a 19). La rotazione del set di guardian ECDSA (payload 2) non è interessata: mantiene le proprie regole e la propria finestra di tolleranza. Nessuna rotazione è stata eseguita sulla chain 20.

Riportato dalla chain 14

La chain 15 è stata ricavata dalla posizione bridge della chain 14 (gli stessi emitter e gli stessi endpoint di origine), e le chain 16, 17 e 18 sono state ricavate ciascuna da quella della precedente allo stesso modo. La chain 19 è stata ricavata dalla posizione della chain 18 con una modifica: i suoi emitter Ethereum, BSC e Tron sono gli endpoint ridistribuiti il 2026-09-30 (0x7af6b17047c1db6cb54347fdea45cf9179075bfa per le chain 2 e 3, 0x6410797df959987a5baf65b5fab97edeb34d5163 per la chain 4). La chain 18 non si è mai fidata di essi, quindi nulla da essi è stato mintato lì. La chain 20 è stata ricavata da uno snapshot della chain 19 all’altezza 7.258, con il bridge fermato prima, e mantiene invariati gli emitter, i guardian, le chiavi post-quantistiche e la chiave di pausa della chain 19; ciò che aggiunge sono i gruppi commissioni e rotazione. L’emitter di Solana e l’emitter lato Rand non sono cambiati dalla chain 14. Che cosa contiene il genesis della chain 20:

  • Custodia. 99 zUSD, come due note zUSD di genesi (10 e 89 zUSD), a fronte di 22 USDT bloccati su Ethereum, 22 USDT su BSC, 30 USDT su Tron e 25 USDT su Solana, e 0 su ogni backing USDC: il locked della chain 19 allo snapshot, che coincideva con la custodia degli endpoint. total_supply == Σ locked coincide con la custodia degli endpoint al blocco 0. Storia: i 10 zUSD rimasti sulla chain 14 erano una sola nota di genesi sulle chain da 15 a 19; la genesi della chain 18 li teneva come 9 USDT bloccati su Tron e 1 su Solana, e quella della chain 19 come 10 su Solana dopo un ribilanciamento sulla chain 18. Gli altri 89 sono i depositi netti della chain 19, al netto dei suoi burn.
  • Indice del guardian set 1, dove si trovano già gli endpoint di origine; la prossima rotazione ECDSA deve riportare l’indice 2. Le otto chiavi post-quantum e la pause key sono quelle della chain 15, invariate.
  • burn_sequence 13: i burn della chain 14 si sono fermati a 6, la genesi delle chain da 15 a 18 è partita da 7, la chain 18 ha fatto un burn (7) e la chain 19 è partita da 8. La chain 19 ha fatto cinque burn, con sequenze da 8 a 12, quindi la chain 20 parte da 13, e nessun endpoint o daemon indicizzato per sequenza ne vede una due volte. Il primo burn della chain 20 ha preso la sequenza 13.
  • Replay. Il mint di una chain precedente supererebbe ancora il quorum ECDSA, ma la sua co-firma post-quantum vincola l’id di quella chain, quindi sulla chain 20 viene rifiutato. La genesi della chain 15 non aveva alcuna soglia di replay in ingresso (min_inbound_sequence era null), quindi lì la protezione poggiava sulla co-firma e sul fatto che i guardian non co-firmassero mai un vecchio lock per il nuovo id di chain. Dalla chain 16 la genesi ne fissa una: per ogni chain di origine, la sequence più bassa che un’attestazione di trasferimento dall’emitter di quella chain può riportare. Quella della chain 20 è { "2": 3, "3": 3, "4": 3, "5": 7 }: la prossima sequenza in uscita di ciascun endpoint quando è stato fatto lo snapshot della chain 19, dato che ogni lock precedente aveva già coniato sulla chain 19. (Quella della chain 18 era { "2": 2, "3": 2, "4": 2, "5": 2 }; quella della chain 19 era { "2": 1, "3": 1, "4": 1, "5": 4 }, dove la sequenza 0 su ogni endpoint ridistribuito era il lock dell’operatore stesso, che non deve mai coniare.) Un trasferimento sotto la soglia viene rifiutato con BelowReplayFloor, qualunque quorum e co-firma porti con sé. Le rotazioni del guardian set non sono giudicate da essa. La soglia è fissata alla genesi, vincolata all’hash di genesi e incorporata nella bridge root.

Ordine di avvio sulla chain 14 (storico)

Come il bridge è entrato in funzione sulla chain 14, un testnet precedente, a settembre 2026:

  1. Creare la chain con la sezione bridge (pq_guardians e pause_key inclusi) e una sezione tokens che imposta registration_fee e mint_cap_per_day, senza elencare alcun token. Fatto il 2026-09-20.
  2. Abilitare il faucet in modo che un wallet deployer possa ottenere RAND. Fatto.
  3. Il deployer registra zUSD con il suo primo backing, con la firma del quorum su M_register a list_nonce = 0. Fatto all’altezza 256.
  4. I guardian rileggono l’indice e firmano i restanti sei messaggi ListBacking, inviati uno alla volta. Fatto: list_nonce legge 7.
  5. Per ogni backing: elencarlo su Rand prima di setToken sull’endpoint di origine.
  6. La rotazione del guardian set (set 0 → set 1, firmata 5 su 6 dal set corrente) sui quattro endpoint di origine, poi la stessa attestazione su Rand. Rand ha letto il set 0 per tutta la vita della chain 14; la chain 15 parte dal set 1.
  7. Un round trip dal vivo per ogni chain di origine con importi piccoli — bridge in ingresso, mint di zUSD, trasferimento, burn di ritorno — e una riconciliazione del locked di ogni backing (rand_getTokenSupply) con la custodia dell’endpoint.

Stato pubblico

rand_getBridgeState:

{
  "enabled": true,
  "emitter": "01…",  "emitters": { "2": "02…" },
  "guardian_set_index": 0, "guardians": ["aabb…"],
  "pq_guardians": ["…"],
  "mint_paused": false, "pause_nonce": 0, "list_nonce": 0, "pause_key": "…",
  "registration_fee": "1000000000",
  "burn_sequence": 1,
  "rotation_nonce": 0,
  "rules_v2": { "global_mint_cap_per_window": "400000000000", "cap_window_secs": 86400,
                "global_minted_in_window": "…", "global_mint_headroom": "…" },
  "min_inbound_sequence": null,
  "rotation_rules": null, "pending_rotations": null,
  "fees": null,
  "assets": [ … ]
}

pq_guardians sono chiavi pubbliche Dilithium2, di 1.312 byte ciascuna in esadecimale. Non esiste più alcun next_index: un token con bridge viene elencato, con un indice fissato dalla sua registrazione, prima di poter essere depositato, quindi non resta alcun indice da prevedere. rotation_nonce è ciò che deve riportare il prossimo M_rotate_pq/M_rotate_pause/M_cancel; rules_v2 è null su una chain senza il gruppo (chain 14), e min_inbound_sequence è null su una chain senza soglia di replay (chain 15 e precedenti); sulla chain 20 è { "2": 3, "3": 3, "4": 3, "5": 7 }. rotation_rules, pending_rotations e fees sono null su una chain senza quei gruppi (chain da 14 a 19). L’esempio qui sopra mostra la forma, non i valori della chain 20: la chain 20 è partita da guardian_set_index 1 e burn_sequence 13, e serve "rotation_rules": { "delay_secs": 86400, "needs_possession": true }, "pending_rotations": [] e "fees": { "mint_bps": 10, "burn_bps": 10, "recipient": "rand1…" }. Una chain senza sezione bridge risponde {"enabled": false}.

Nessun saldo: il valore trasferito via bridge è fatto di note, e un wallet legge ciò che possiede con rand asset-balance [INDEX] dalle proprie note. La bridge root vincola gli emitter, i guardian set, i digest spesi, la burn sequence, il guardian set post-quantum, e la pause key, il pause flag e i nonce di governance (due, e rotation_nonce con le rules v2), la soglia di replay dove la genesi ne fissa una, e sulla chain 20 le regole di rotazione con l’eventuale rotazione in sospeso e il gruppo delle commissioni; entra nella state root solo su una chain che ha un bridge. Il registro dei token, con il locked di ogni backing, è vincolato separatamente come tokens_root. Gli endpoint GET /bridge e GET /bridge/assets dell’explorer servono lo stesso stato, il secondo con i totali di deposito e burn per backing come stringhe a 8 decimali; GET /tokens e GET /tokens/:id servono il registro.

Cosa è pubblico e cosa è nascosto

cosachi lo vede
importo, indice dell’asset e indirizzo del destinatario di un depositotutti, in quella singola transazione
quale nota un destinatario spende in seguitonessuno
importo, asset, coin, destinazione e commissione del relayer di un burntutti — i guardian ne hanno bisogno
importo, asset e proprietario di una nota di commissione del bridge, la treasury (chain 20)tutti: viene ricostruita dai campi pubblici
quali note hanno finanziato un burnnessuno
saldi trasferiti via bridgesolo il titolare, o chi possiede una viewing key
guardian set, emitter, il registro con il locked di ogni backing, il burn logtutti

Lacune note

Dall’elenco del fullnode stesso: nessun relayer viene pagato su Rand; l’envelope di un deposito non è vincolato a nulla, quindi un relayer ostile può sigillarvi dati spazzatura, ed è per questo che il wallet ricostruisce le note di deposito dai campi pubblici; i test vector condivisi a livello di ledger non sono stati ricostruiti sul pool di note; bridge-burn apprende la policy propria dell’endpoint di destinazione solo dopo aver pagato per una proof; su una chain con bridge i timestamp di blocco uguali a quello del padre sono ammessi, quindi dei leader in collusione potrebbero mantenere un guardian set superato dentro la sua finestra di grazia; il burn log in memoria è illimitato; non esiste, né è previsto, alcun light client né verifica on-chain dello stato della chain di origine.

Ultima verifica rispetto al commit c0878c3f del fullnode (chain 21) il 2026-10-12. Dove il codice e questa pagina non concordano, ha ragione il codice; segnalacelo.