Użyj tej strony, aby zintegrować się z mostem: zbudować depozyt w łańcuchu źródłowym, przesłać poświadczenie do Rand, spalić aktywo mostowane z powrotem na zewnątrz i odczytać publiczny stan mostu. Chain 20, obecny testnet (działa od 2026-10-01 06:17 UTC, fullnode v0.6.8), został uruchomiony z sekcją bridge: ośmiu strażników zaczynających od indeksu zestawu strażników 1, ośmiu strażników postkwantowych, klucz pauzy, emitenci dla Ethereum, BSC, Tron i Solana (pierwsze trzy to punkty końcowe wdrożone ponownie 2026-09-30), grupa rules-v2, w której globalny limit emisji wynosi 4,000 zUSD na kroczące 24 godziny, dolna granica ochrony przed powtórzeniami dla ruchu przychodzącego oraz dwie grupy nowe w v0.6.8: opłaty w wysokości 10 bps od każdej emisji i każdego spalenia, pobierane w zUSD, oraz reguły rotacji, które wymagają, aby rotacja zestawu postkwantowego lub klucza pauzy udowodniła posiadanie nowych kluczy i odczekała dobę. W odróżnieniu od chain 14, jego genesis wymienia zUSD ze wszystkimi siedmioma zabezpieczeniami i przenosi depozyt przekazywany od chain 14 przez każde kolejne uruchomienie (Przeniesione z chain 14). rand_getAssets i /bridge/assets w eksploratorze podają bieżącą wartość locked każdego zabezpieczenia.
Przepływ
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 │
└────────────────────────────────┘
Ruch przychodzący: wartość jest blokowana w łańcuchu źródłowym, strażnicy ją poświadczają, ktokolwiek przesyła poświadczenie do Rand, a księga dopisuje jedną notatkę depozytową dla odbiorcy — notatkę, której słowo asset jest indeksem zmostowanego tokena w rejestrze. Ruch wychodzący: transakcja Rand spala notatki tego aktywa i emituje komunikat; strażnicy poświadczają ten komunikat; kontrakt w łańcuchu źródłowym wypłaca środki. Rand nigdy nie weryfikuje nagłówka łańcucha źródłowego ani dowodu stanu: podpisy komitetu strażników stanowią cały model zaufania.
Model zaufania i identyfikatory łańcuchów
Lokalne dla mostu identyfikatory łańcuchów, wspólne z kontraktami (bridge-codec):
| łańcuch | id |
|---|---|
| Rand | 1 |
| Ethereum | 2 |
| BSC | 3 |
| Tron | 4 |
| Solana | 5 |
Strażnicy tworzą komitet kluczy secp256k1; tożsamością strażnika jest ostatnie 20 bajtów keccak256(uncompressed_pubkey[1..]), czyli wyprowadzenie adresu znane z Ethereum, więc jeden klucz działa w kontraktach EVM, w programie Solana i w Rand. Kworum dla zestawu o rozmiarze n wynosi n·2/3 + 1. Każdy podpis musi mieć niskie s (s ≤ n/2), co wyklucza podatność na modyfikację. Skrót, który podpisują strażnicy, to mu = keccak256(keccak256(body_bytes)). Rotacja zestawu strażników jest poświadczeniem od emitenta zarządzającego (chain 1, keccak256("rand-bridge-governance")), musi zawierać new_index == current + 1 i musi być podpisana przez zestaw bieżący, nie przez zestaw wycofany; ustępujący zestaw zachowuje 86,400-sekundowe okno karencji dla transferów w toku. (Dotyczy to zestawu ECDSA. Zestaw postkwantowy i klucz pauzy rotują własnymi akcjami, które w chain 20 odczekują jednodniowe opóźnienie: poniżej.) Jeśli n·2/3 + 1 strażników zawrze zmowę, mogą wyemitować dowolną wartość; Wzmocnienie mostu ogranicza, ile i jak szybko, ale tego nie usuwa.
Powiązanie transakcji. Transakcja Rand nie niesie podpisu: spalenie jest autoryzowane dowodem STARK jej pakietu. Od chain 14 każdy dowód pakietu jest tworzony na podstawie Transaction::binding i względem niego weryfikowany — jest to skrót całej transakcji (identyfikator łańcucha, jedyny pakiet z jego czterema kopertami, akcja) z wyzerowanymi wyłącznie bajtami dowodu. Od chain 17 pakiet niesie dwa dowody: dowód pakietu i dowód autoryzacji wydatku; powiązanie zeruje oba, zachowuje auth_commit pakietu, a oba są weryfikowane względem niego. Kopia oczekującego spalenia ze zmienionym to, relayer_fee lub (to_chain, token), albo attest ze zmienionym time lub kopertą, nie przechodzi weryfikacji dowodu (TxError::InvalidBundleProof). W chain 20 powiązanie obejmuje też skrót genesis (genesis binding_domain: 1: rand-tx-bind-2 nad skrótem genesis, identyfikatorem łańcucha, pakietem i akcją), więc dowód wykonany dla innego łańcucha o tym samym identyfikatorze również nie przechodzi weryfikacji. Portfele i relayery muszą uruchamiać binarkę zbudowaną dla łańcucha, do którego wysyłają: v0.6.8 w chain 20 (v0.6.7 w chain 18 i 19); starszy rand tworzy dowody i podpisy, które chain 20 odrzuca.
Demony strażnika i relayera nie są częścią pełnego węzła; pełny węzeł zawiera wspólny kodek i połowę odbierającą.
Format przesyłu
Każda liczba całkowita jest zapisana w porządku big-endian; wersja formatu wynosi 1.
Koperta poświadczenia
| offset | dł. | pole |
|---|---|---|
| 0 | 1 | version = 1 |
| 1 | 4 | guardian_set_index |
| 5 | 1 | n_sigs |
| 6 | 66·n_sigs | podpisy: index (1), r (32), s (32), v (1) |
6 + 66·n_sigs | … | body |
Nagłówek treści — 51 bajtów, potem ładunek
| offset | dł. | pole |
|---|---|---|
| 0 | 4 | timestamp |
| 4 | 4 | nonce |
| 8 | 2 | emitter_chain |
| 10 | 32 | emitter_address |
| 42 | 8 | sequence |
| 50 | 1 | consistency_level |
| 51 | … | payload |
Ładunek Transfer — id 1, 133 bajty
| offset | dł. | pole |
|---|---|---|
| 0 | 1 | id = 1 |
| 1 | 32 | amount (u256; Rand używa niższych 128 bitów, a depozyt musi się też mieścić w u64 notatki) |
| 33 | 32 | token_address |
| 65 | 2 | token_chain |
| 67 | 32 | to — skrót odbiorcy, zob. niżej |
| 99 | 2 | to_chain |
| 101 | 32 | fee (u256): część amount przypadająca relayerowi |
Ładunek GuardianSetUpgrade — id 2: id (1), new_index (4), n (1), potem n 20-bajtowych kluczy.
Adresy Ethereum, BSC i Tron mają 20 bajtów, dopełnione z lewej 12 zerowymi bajtami w 32-bajtowych polach; adres Solana wypełnia całe pole. Transfer musi pochodzić od zarejestrowanego emitenta łańcucha dla emitter_chain, a token_chain musi być równy łańcuchowi emitującemu. Poświadczenie ma najwyżej 16 KiB. Żaden element wzmocnienia mostu nie zmienił formatu przesyłu, kodeka ani wdrożonych punktów końcowych w łańcuchach źródłowych: współpodpisy postkwantowe i komunikaty zarządzające podróżują w transakcji Rand, nie w poświadczeniu.
32-bajtowy odbiorca
Adres chroniony (shielded) ma 1,216 bajtów, a format przesyłu ma miejsce na 32. Pole to niesie jego skrót:
to = blake3("rand-shielded-recipient" || pk_bytes || kem_ek) // ShieldedAddress::recipient_hash
Deponent podaje skrót w łańcuchu źródłowym, strażnicy go podpisują, przesyłający umieszcza pełny adres w akcji BridgeAttest, a księga przelicza skrót i odrzuca niezgodność — w przeciwnym razie przesyłający mógłby wybrać, kto otrzyma cudzy depozyt. Interfejsy łańcuchów źródłowych muszą akceptować tę postać; portfel ją wypisuje. Chain 11 na krótko uczynił z tego pola bezpośrednio identyfikator odbiorcy; ta postać adresu została wycofana wraz z chain 12 (Konta i adresy).
Jeden token, wiele zabezpieczeń: zUSD
Rejestr aktywów nie należy już do mostu. Przeniósł się do rejestru tokenów RPL na poziomie księgi, wspólnego dla tokenów natywnych i zmostowanych, a korzeń stanu łańcucha zobowiązuje się do niego jako tokens_root. Token zmostowany to wpis w rejestrze, którego uprawnienie do emisji to Bridge { backings }, i nie odpowiada już jednej parze (chain, token). Pierwszy z nich, zUSD, jest zabezpieczony USDT i USDC zablokowanymi w czterech łańcuchach — Ethereum, Binance Smart Chain i Solana obsługują obie monety, Tron tylko USDT (Circle zaprzestał USDC na 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
W chain 20 zUSD ma indeks 1, rpl1xtj6k2x8strx8c2d5fjs50ltztck5ykms4ve7nmzmstf6fhn0v0spelqtx, nazwę “Shielded USD”, 8 miejsc dziesiętnych, i jest wymieniony w pliku genesis, tak jak w chain 15–19, z nazwą, symbolem i solą, które miał w chain 14, więc jego identyfikator aktywa (32e5ab28c782c663e14da2650a3feb12f16a12db85599f4f62dc169d26f37b1f) i identyfikator rpl1… przeszły bez zmian (eksplorator: GET /tokens/1). W chain 14 został zarejestrowany na wysokości 256 transakcją register_bridged_token.
Backing { chain: u16, token: [u8; 32], decimals: u8, locked: u64, minted_today: u64, mint_day: u32 }
Para (chain, token) zabezpiecza w całym rejestrze najwyżej jeden token (TokenError::BackingTaken); token ma od 1 do 32 zabezpieczeń; decimals to własna liczba miejsc dziesiętnych monety źródłowej, od 0 do 18, nigdy 8 tokena zmostowanego. Zabezpieczenie zbiorcze jest ryzykiem jawnie wskazanym: siedem monet jest wymienialnych na zUSD i z powrotem po parytecie, więc utrata parytetu którejkolwiek z nich rozwadnia każdego posiadacza zUSD, a po stronie Rand nie ma limitu udziału pojedynczego zabezpieczenia.
zUSD to koszyk. Posiadacz ponosi ryzyko każdego wymienionego zabezpieczenia, a nie tylko monety, którą wpłacił: jeden zUSD to roszczenie wobec puli, realizowalne z dowolnego zabezpieczenia, które ma dość locked. Żadne zabezpieczenie nie jest wyceniane. Zablokowanie 1 jednostki dowolnej wymienionej monety emituje 1 zUSD, a spalenie może wskazać dowolne zabezpieczenie, którego locked je pokrywa. Moneta, która utraciła powiązanie z dolarem, może więc zostać wpłacona po parze, a zUSD spalone na rzecz monety, która to powiązanie utrzymała, aż zdrowe zabezpieczenia zostaną wyczerpane. Strata jest ograniczona przez kroczące okna emisji i przez faktycznie zablokowane depozyty: na chain 20 w ciągu dowolnych 24 godzin można wyemitować co najwyżej 4 000 zUSD łącznie dla wszystkich zabezpieczeń i 100 000 na jedno zabezpieczenie (rules v2), a przez zdrowe zabezpieczenie nie może wyjść więcej, niż ono zawiera. Okno ogranicza tempo, nie sumę: daje czas na wstrzymanie emisji, co zatrzymuje nowe wpłaty słabej monety, podczas gdy spalanie pozostaje otwarte. Nie ma limitu wykupu na zabezpieczenie ani wstrzymania dla pojedynczego zabezpieczenia. Na chain 14, przed rules v2, jedynym ograniczeniem były własny pauser punktów źródłowych i limity tempa dla poszczególnych tokenów.
Niezmiennik: total_supply == Σ backings.locked. TokenRegistry::lock (BridgeAttest) w jednym wywołaniu dodaje poświadczoną kwotę do locked zabezpieczenia i do total_supply tokena; TokenRegistry::release (BridgeBurn) odejmuje wypłacaną kwotę od obu. Nic innego nie zapisuje żadnego z tych pól, a liść tokena zatwierdza locked każdego zabezpieczenia w tokens_root, więc dwa węzły, które różnią się co do jednego z nich, rozchodzą się na korzeniu stanu. Punkty źródłowe nie pobierają opłaty przy blokowaniu i potrącają całą poświadczoną kwotę przy wypłacie, więc locked każdego zabezpieczenia równa się depozytowi w danym punkcie, z dokładnością do wiadomości w drodze. Opłaty na chain 20 tego nie naruszają: opłata jest notą zUSD, która pozostaje wyemitowana i zabezpieczona, więc wpłata nadal blokuje kwotę brutto, a spalenie wypłaca tylko amount − fee. Na chain 20 własna opłata punktów źródłowych w wysokości 10 bps od USDT przy wypłacie została przy starcie ustawiona na 0; opłata jest teraz pobierana na Rand.
Kwoty w protokole mają 8-miejscowe jednostki, niezależnie od własnych miejsc dziesiętnych monety źródłowej: wpłata 1 USDT z Ethereum (6 miejsc dziesiętnych) jest blokowana jako 100_000_000 jednostek. Tylko indeks 0, RAND, ma dziewięć miejsc dziesiętnych tego łańcucha.
Token musi być wymieniony przed pierwszą wpłatą. Nie ma już rejestracji przy pierwszym wystąpieniu: poświadczenie wskazujące (chain, token), którego nikt nie wymienił, jest odrzucane z BridgeError::UnlistedToken (token … of chain … is not listed on this chain). Środki pozostają w depozycie punktu źródłowego, dopóki kworum postkwantowych strażników nie wymieni monety (Wymienianie po genesis).
asset_id = blake3("rand-bridge-asset" || token_chain (2 bytes BE) || token_address (32 bytes))
rand_bridgeAssetId(token_chain, token_address) nadal oblicza ten identyfikator w dowolnym łańcuchu i to do niego rozwiązuje się (token_chain, token_address) z ładunku Transfer, ale nie jest już kluczem rejestru. Nota i portfel noszą indeks tokena, gęsty u32: indeks 0 to RAND i nigdy nie występuje w rejestrze. rand_getAssets zwraca jeden wiersz na zabezpieczenie, a wszystkie wiersze jednego tokena mają ten sam indeks:
[{ "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 i minted_today są dziesiętnymi ciągami znaków w 8-miejscowych jednostkach, jak każda kwota u64 serwowana przez RPC od chain 14; index, chain, decimals i mint_day pozostają liczbami. Metody na poziomie tokena — rand_getTokens, rand_getToken, rand_getTokenSupply — zwracają token mostowany jako jeden wiersz ze wszystkimi jego zabezpieczeniami (tokeny RPL). GET /bridge/tokens eksploratora to statyczna lista dozwolonych monet, które most przyjmuje.
Przychodzące: 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
Przenosi ją zwykła transakcja osłonięta, której jedyny pakiet ukrytego aktywa płaci podstawę 0,001 RAND i nic więcej: jej relayer płaci za deponenta, który nie ma jeszcze RAND. To łańcuch, a nie zgłaszający, oblicza notę wpłaty { pk: recipient.pk, from: 0, amount, asset: <index>, time, r } z kwoty podpisanej przez strażników, więc relayer nie może zawyżyć ani przekierować emisji; jej zobowiązanie jest jedynym liściem, którego transakcja nie niesie w protokole. Na chain 20 amount noty to kwota brutto pomniejszona o opłatę za emisję, a łańcuch dołącza drugą notę, opłatę, na rzecz skarbca (Opłaty za emisję i spalanie).
Zaślepienie jest wyprowadzane, nie wybierane. r musi być równe
r = blake3("rand-deposit-r-1" || mu) // read as eight little-endian words; mu = keccak256(keccak256(body))
skrótowi, który podpisało kworum strażników; wszystko inne to BridgeError::WrongDepositBlinding, odrzucane przed jakąkolwiek weryfikacją podpisów. Pole pozostaje w protokole — odbudowa publicznych pól w portfelu odczytuje je stamtąd — jest tylko ograniczone, a reguła obejmuje także poświadczenie rotacji. Wcześniej każdy, kto zobaczył oczekującą transakcję relayera, mógł złożyć to samo poświadczenie z własnym zaślepieniem: odbiorca nadal dostawał notę, ale transakcja relayera przepadała po tym, jak zapłacił za dowód. Przy ustalonym r dwóch zgłaszających jedno poświadczenie z tym samym time wskazuje jedną notę, a pula uznaje je za konflikt na zobowiązaniu.
time i asset to dwie prognozy deponenta, sporządzone po to, by kopertę dało się zapieczętować, zanim transakcja powstanie. time podlega temu samemu oknu co czas pakietu (1 024 bloków na chain 20, z proof_window_blocks w genesis; wcześniej 256) i celowo nie jest wyprowadzany: żadna reguła nie daje wartości time, na którą zgodziliby się wszyscy zgłaszający, więc front-runner nadal może wybrać inny time w oknie — inną notę tego samego odbiorcy, kwoty i aktywa, którą portfel odbiorcy znajduje w ten sam sposób — a oba zgłoszenia kolidują wtedy na skrócie poświadczenia. asset musi równać się indeksowi, który rejestr już nadał (chain, token), w przeciwnym razie TxError::AttestAssetMismatch.
Pole fee ładunku Transfer jest przenoszone i nikomu niewypłacane: nie ma tożsamości zgłaszającego, której można by zapłacić, więc wpłata to kwota brutto, pomniejszona jedynie o własną opłatę łańcucha za emisję, gdy genesis ją ustawia (chain 20). Ochrona przed powtórzeniem opiera się na skrócie: każde zużyte mu trafia do zbioru spent mostu, a mempool odrzuca drugiego relayera ścigającego się o to samo poświadczenie. Wpłata podlega też limitowi emisji zabezpieczenia, na chain 20 także limitowi globalnemu, i jest odrzucana, gdy emisja jest wstrzymana (B1, rules v2). Oba limity liczą kwotę brutto. W łańcuchu, którego genesis ustawia próg powtórzeń, w tym chain 20, transfer, którego sequence jest poniżej progu jego łańcucha źródłowego, jest odrzucany z BelowReplayFloor przed sprawdzeniem jakiegokolwiek podpisu (Przeniesione z 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 jest wymagane: kworum współpodpisów Dilithium2 strażników jako [{"index":0,"signature":"<4840 hex>"},…]. Przed jakimkolwiek dowodzeniem portfel dekoduje poświadczenie, sprawdza kworum względem pq_guardians węzła i identyfikatora łańcucha, sprawdza, czy odbiorca daje skrót zgodny z poświadczonym, i odczytuje wymieniony indeks; następnie wyprowadza r, zapieczętowuje kopertę odbiorcy (na kwotę netto w łańcuchu z opłatami) i płaci jednym pakietem własnych RAND. bridge-rotate składa rotację zbioru strażników (ładunek 2), która niczego nie wpłaca, i odrzuca wszystko, co nie przesuwa zbioru Rand o jeden. Ponieważ każde pole noty wpłaty jest jawne w tej jednej transakcji, portfel odbiorcy odbudowuje adresowane do niego noty wpłat, przeglądając zatwierdzone transakcje bridge_attest, bez otwierania żadnej koperty — wrogi relayer może zapieczętować śmieci, ale nie może ukryć noty.
Wychodzące: 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 }
}
Od pakietu ukrytego aktywa w chain 14 spalenie to jeden pakiet (wcześniej były dwa): sloty 0–1 dowodzą, że spalający posiadał noty aktywa o sumie co najmniej amount, przy czym burn_a to wartość opuszczająca pulę, a sloty 2–3 płacą opłatę w RAND. Opłata to BRIDGE_BURN_FEE, 0,01 RAND, która pokrywa podstawę pakietu. relayer_fee to część amount (fee ≤ amount) wydzielana w łańcuchu docelowym przez kontrakt wypłaty, a nie dodatkowa opłata, więc burn_a wynosi dokładnie amount. Na chain 20 łańcuch pobiera też opłatę za spalenie w wysokości 10 bps od amount jako notę zUSD na rzecz skarbca, a wypłata to amount − fee; część relayera musi się zmieścić w tej wypłacie (Opłaty za emisję i spalanie).
Spalenie wskazuje swoją monetę. (to_chain, token) musi być zabezpieczeniem spalanego aktywa (w przeciwnym razie TokenError::NotABacking), a dwie reguły są sprawdzane na etapie akcji, przed dowodem pakietu:
- Jednostka wypłaty. Zabezpieczenie z
d < 8miejscami dziesiętnymi źródła wypłaca w jednostkach10^(8−d)(100 dla monety 6-miejscowej; 1 dla 18-miejscowej). Wypłacana kwota irelayer_feemuszą być całkowitymi wielokrotnościami, inaczejTokenError::NotReleasable(… is not a multiple of the release unit …): ułamek poniżej najmniejszej jednostki monety źródłowej utknąłby w depozycie. - Własny depozyt zabezpieczenia. Wypłacana kwota
≤ lockeddla tego zabezpieczenia, inaczejTokenError::InsufficientBacking(only … is locked in that backing, not the … asked for). Całkowita podaż tokena może pokrywać wypłatę; to depozyt tego jednego zabezpieczenia musi.
Wypłacana kwota to amount w łańcuchu bez opłat i amount − fee na chain 20.
to musi mieć właściwy kształt (20 bajtów dopełnionych zerami w łańcuchach 2, 3 i 4; nigdy samych zer). Księga zapisuje BridgeBurnRecord pod ściśle rosnącym burn_sequence, z hashem transakcji w slocie nadawcy, a strażnicy czytają dziennik spaleń:
→ {"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 to komunikat wychodzący dokładnie w takiej postaci, w jakiej strażnicy (guardians) muszą go zahaszować i podpisać; jego kwota Transfer to release_amount. Powyższy rekord to pierwszy burn w chain 20: 0.999 zUSD spalone na rzecz BSC USDT na wysokości 2,899, z czego 0.000999 trafiło do skarbca, a 0.998001 zostało wypłacone.
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
Portfel sprawdza najpierw — na podstawie jednego odczytu stanu mostu i rejestru tokenów, przed jakimkolwiek dowodzeniem — że chain ma most, że ASSET znajduje się w rejestrze, że TOKEN na TO_CHAIN go zabezpiecza i ma zablokowane co najmniej AMOUNT, oraz że AMOUNT i opłata relayera są wielokrotnościami całych jednostek wypłaty. W chainie z opłatami wypisuje następnie opłatę mostu i kwotę, która zostanie wypłacona, i przed dowodzeniem pyta o zgodę (--yes pomija pytanie). Burn dowodzi następnie jednego pakietu: około 100 s na laptopie i około 1.5 MB dowodu, a w chain 20, jak w każdym pakiecie od chain 17, obok niego dowód autoryzacji wydatku (kilka sekund, około 1.37 MB).
Opłaty przy mint i burn
Nowość w fullnode v0.6.8, ustawiana w chain 20 przez grupę genesis
"bridge": { …, "fees": { "mint_bps": 10, "burn_bps": 10, "recipient": "rand1…" } }
chain pobiera opłatę od każdego mintu i każdego burnu przez most, w tokenie przenoszonym przez most, na Rand, jako osłonięta notatka na adres skarbca recipient. Każda stawka wynosi co najwyżej 100 bps (1 %); w chain 20 obie wynoszą 10 bps, 0.1 %. Opłata to ⌊amount · bps / 10⁴⌋ zaokrąglone w dół do całej jednostki wypłaty zabezpieczenia (10^(8 − source decimals): 100 jednostek dla USDT z 6 miejscami dziesiętnymi, 1 dla monety z 18), więc opłata i reszta są całymi jednostkami, a opłata zaokrąglona do zera nie tworzy notatki (ledger/bridge_notes.rs, bridge_fee).
wpłata (BridgeAttest) brutto g | burn (BridgeBurn) kwoty amount | |
|---|---|---|
| opłata | ⌊g · mint_bps / 10⁴⌋, w dół do jednostki wypłaty | ⌊amount · burn_bps / 10⁴⌋, w dół do jednostki wypłaty |
| strona użytkownika | notatka wpłaty na g − fee | spalone amount (burn_a == amount, bez reszty guesta); treść wychodząca poświadcza release_amount = amount − fee, a punkt końcowy wypłaca dokładnie tyle |
| notatka skarbca | fee, blinding blake3("rand-bridge-fee-r-1" ‖ mu), z time i aktywem wpłaty | fee, blinding blake3("rand-bridge-burn-fee-r-1" ‖ txid), z time pakietu |
locked i total_supply | rosną o g | maleją o release_amount |
| sprawdzane względem | limitów mintu na zabezpieczenie i globalnego, dla g | InsufficientBacking i NotReleasable dla release_amount; część relayera musi się w niej mieścić (FeeExceedsAmount) |
Obie notatki są wyemitowane i zabezpieczone, więc nadal zachodzi total_supply == Σ locked == custody: notatka opłaty jest zUSD jak każda inna, a jej wartość pozostaje zablokowana w chainie źródłowym, dopóki ktoś jej nie spali. BRIDGE_BURN_FEE, czyli 0.01 RAND, które burn płaci w swoich slotach RAND, pozostaje bez zmian i jest odrębne.
Pierwsza runda w chain 20, odczytana z węzła: wpłata 1 zUSD z BSC na wysokości 1,664 (4f6e6c54…1a64) dała wpłacającemu 0.999 zUSD, a skarbcowi 0.001; burn 0.999 zUSD z powrotem na BSC USDT na wysokości 2,899 (sekwencja 13) zatrzymał 0.000999 zUSD i wypłacił 0.998001. Po nim (odczyt 2026-10-01) zabezpieczenie BSC USDT wynosiło 22.001999 zUSD, a podaż tokena, 99.001999 zUSD, równała się sumie jego zabezpieczeń.
Jak skarbiec znajduje swoje notatki. Notatka opłaty nie ma koperty: każde jej słowo jest jawne (kwota z transakcji i stawki z genesis, aktywo i time z transakcji, blinding wyprowadzony, właściciel to recipient z genesis). Portfel, którego adresem jest recipient, odtwarza notatki opłat w tym samym jawnym przebiegu, w którym odtwarza notatki wpłat, odczytując rand_getBridgeState.fees i miejsca dziesiętne zabezpieczeń raz na synchronizację. Węzeł, który kłamie o opłatach, może jedynie sprawić, że notatka pozostanie nieznaleziona, nigdy zaksięgować żadnej. Portfel wpłacającego wycenia własną wpłatę tak samo: brutto minus opłata. Robi to tylko portfel v0.6.8; starszy i tak nie może wysyłać w chain 20.
Bez grupy, w chainach od 14 do 19, nie ma opłaty: wpłata księguje kwotę brutto, a burn wypłaca amount. Do chain 20 jedyną opłatą mostu był własny pobór 10 bps USDT przez punkty końcowe źródła przy wypłacie, w chainie hosta; w momencie cięcia chain 20 ustawiono go na 0. Grupa jest związana z hashem genesis i włączona do korzenia mostu pod rand-bridge-fees-1. Kodowanie opisuje §25 w fullnode docs/bridge.md.
Utwardzanie mostu
locked na zabezpieczenie i InsufficientBacking chronią wykup. Nie ograniczają mintu: attestation z ważnym kworum strażników mintuje to, co mówi. Trzy warstwy zamykają tę lukę, a czwarta pozwala wylistować token bez cięcia chaina. Żadna nie zmienia punktów końcowych w chainie źródłowym, formatu przewodowego attestation ani bridge-codec.
B1: limit mintu na zabezpieczenie i pauza mintu
Genesis tokens.mint_cap_per_day obowiązuje na zabezpieczenie: chain 20 ustawia 10000000000000, czyli 100,000 zUSD na zabezpieczenie na dobę, jak każdy chain od 14. Bez rules v2 (chain 14) doba to timestamp_ms / 86_400_000 ze znacznika czasu bloku; w rules v2 (chainy od 15 do 20) ten sam limit mierzy się w oknie kroczącym cap_window_secs czasu bloków, a ciaśniejszą granicą jest globalny limit całego rejestru — w chain 20 4,000 zUSD na 86,400 s (rules v2). Wpłata ponad limit jest odrzucana z TokenError::MintCapExceeded przed pracą nad podpisami strażników i jest dopuszczalna, gdy okno znów ma miejsce.
Genesis bridge.pause_key to jeden klucz Dilithium2, różny od każdego postkwantowego strażnika. Dwie akcje bez pakietu i bez opłaty:
PauseMints { nonce, signature }— podpisana samym kluczem pauzy. Może tylko wstrzymać.UnpauseMints { nonce, pq_signatures }— wymaga postkwantowego kworum strażników, nigdy klucza pauzy.
Obie niosą i podbijają pause_nonce, więc żadna nie może zostać odtworzona. Gdy mint jest wstrzymany, każdy transferowy BridgeAttest jest odrzucany z BridgeError::MintsPaused; burny i rotacje zbioru strażników pozostają otwarte, więc pauza nigdy nie zamyka drogi do wykupu. W rules v2 (chainy od 15 do 20) listowanie jest również odrzucane w czasie pauzy. Klucz pauzy ma być przechowywany na innej maszynie niż klucze strażników.
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]
Co pokrywa pauza: demony strażników oszukane lub przejęte tak, by współpodpisały sfałszowany mint, podczas gdy posiadacze postkwantowego kworum nie są przejęci. Nie jest to obrona przed kradzieżą tego kworum, które może odwiesić mint, wylistować do 32 zabezpieczeń na jednym tokenie, każde z własnym limitem dziennym, a w rules v2 wymienić zbiór postkwantowy i klucz pauzy.
B2: ograniczone znaczniki czasu bloków
Znacznik czasu bloku w chainie z mostem rozstrzyga o wygaśnięciu zbioru strażników i o dobie limitu mintu, więc lider nie może móc go przeskoczyć. W chainie z mostem:
- Reguła ważności, sprawdzana także przy odtwarzaniu:
timestamp_ms ≤ parent.timestamp_ms + 60_000, w przeciwnym razieBlockError::TimestampLeap. Wraz z istniejącą regułą cofnięcia (BlockError::TimestampRewind) czas bloków porusza się tylko do przodu, najwyżej o minutę na blok. - Reguła głosowania, nigdy reguła odtwarzania: walidator wstrzymuje głos na blok wyprzedzający jego własny zegar o więcej niż 15,000 ms.
Każdy walidator w chainie z mostem potrzebuje zatem NTP: ten, którego zegar odbiega o ponad 15 sekund, jest wadliwym liderem i wadliwym głosującym. Gdy chain nadrabia zaległości po zatrzymaniu, czas bloków może rosnąć do 60 s na blok, więc doba limitu mintu może minąć szybciej niż prawdziwa doba — co najwyżej jeden dodatkowy dzienny limit na pełną dobę zgromadzonego opóźnienia.
B3: współpodpis Dilithium2 pod każdym mintem
Każdy BridgeAttest, zarówno wpłata, jak i rotacja, musi nieść obok kworum ECDSA kworum podpisów Dilithium2 pod
M = "rand-bridge-pq-cosign-1" || rand_chain_id (u64 BE) || mu // 63 bytes
mu to digest, który podpisuje kworum ECDSA; identyfikator chaina sprawia, że współpodpis z testnet nie zweryfikuje się w innym chainie Rand. Podpisy jadą w pq_signatures akcji, wewnątrz wiązania transakcji, więc nie można ich usunąć ani podmienić w kopii. Sprawdzane od taniego do drogiego: quorum(n) ≤ len ≤ n (PqNoQuorum), indeksy ściśle rosnące (PqIndexOrder), każdy indeks < n (PqIndexOutOfRange), każdy podpis dokładnie 2,420 bajtów (PqBadSignatureLength), a na końcu że każdy weryfikuje się pod pq_guardians[index] (PqBadSignature). Oba kworum liczy się niezależnie. Genesis bridge.pq_guardians jest wyrównany indeksami ze zbiorem ECDSA z genesis (zbiór 1 w chain 20). Rotacja payload-2 przesuwa tylko zbiór ECDSA; bez rules v2 zbiór postkwantowy jest stały przez życie chaina, a w rules v2 zastępuje go wyłącznie RotatePqGuardians (w chain 20 jego forma V2, z dowodem posiadania i jednodniowym opóźnieniem). Chain 20 ma ośmiu, te same osiem kluczy co chainy od 15 do 19; mint potrzebuje sześciu z nich (chain 14 miał sześciu i potrzebował pięciu).
Ta współsygnatura nadal wiąże wyłącznie identyfikator łańcucha, także w łańcuchu 20: w przeciwieństwie do poniższych komunikatów zarządzania nie została przeniesiona za hash genesis, ponieważ demony strażników, które ją składają, znajdują się w innym repozytorium i nie uległy zmianie. Kopia łańcucha o tym samym identyfikatorze, która skopiowała też zbiór strażników, przyjęłaby współsygnatury depozytu złożone dla oryginału; własny pakiet opłat transakcji depozytu jest w łańcuchu 20 związany z genesis, ale zawarte w nim poświadczenie już nie.
B4: wpisanie tokena mostkowanego po genesis
Zwykły adres nigdy nie może utworzyć tokena z uprawnieniem Bridge. Są dwie akcje, każda w zwykłym pakiecie opłat w RAND i każda pod kworum postkwantowym:
RegisterBridgedToken { name, symbol, salt, chain, token, decimals, nonce, pq_signatures }— nowy token mostkowany pod następnym indeksem, z 8 miejscami dziesiętnymi w Rand, pod limitem emisji z genesis, z jego pierwszym pokryciem. Opłata: podstawa pakietu plusregistration_feerejestru (1 RAND w łańcuchu 20, czyli 1,001 RAND; łańcuch 20 spala opłatę rejestracyjną zamiast wypłacać ją proponentowi bloku).ListBacking { token_index, chain, token, decimals, nonce, pq_signatures }— dodatkowe pokrycie zarejestrowanego tokena mostkowanego, zaczynające odlocked = 0. Opłata: podstawa pakietu.
Obie niosą i podbijają list_nonce (w przeciwnym razie BadListNonce { expected, got }) oraz wykonują te same kontrole: łańcuch ma zarejestrowany emiter (BridgeError::NoEmitter), (chain, token) nie pokrywa jeszcze niczego, jest mniej niż 32 pokryć, 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>
Dwie reguły kolejności. Strażnicy najpierw podpisują rejestrację, czekają na jej zatwierdzenie, odczytują przydzielony indeks i dopiero wtedy podpisują wpisy, które go wskazują — rejestracja tokena natywnego nie wymaga uprawnień, więc wpis podpisany wcześniej mógłby zastać swój indeks już zajęty. Monetę wpisuje się w Rand przed tym, jak setToken włączy ją w punkcie końcowym źródła: w odwrotnej kolejności depozyt mógłby zostać zablokowany, zanim Rand ma pokrycie do zaksięgowania, a jego poświadczenie byłoby odrzucane jako UnlistedToken, dopóki Rand by go nie dogonił.
Komunikaty zarządzania
Stałe bajty, nigdy bincode; wszystkie liczby całkowite w porządku 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 niosą pause_nonce, M_list/M_register niosą list_nonce, a M_rotate_pq/M_rotate_pause niosą rotation_nonce. count to liczba kluczy, nie długość w bajtach; każdy klucz to surowe 1312 bajtów klucza publicznego Dilithium2, w kolejności, jaką będzie miał nowy zbiór.
W łańcuchu 20 każdy komunikat jest związany z hashem genesis (genesis binding_domain: 1): cyfra wersji w znaczniku zmienia się na 2, a 32-bajtowy hash genesis znajduje się między znacznikiem a identyfikatorem łańcucha, reszta pozostaje bez zmian — M'_pause = "rand-bridge-pause-2" || genesis [32] || chain_id u64 || nonce u64, i tak samo rand-bridge-pq-unpause-2, rand-bridge-pq-list-2, rand-bridge-pq-register-2, rand-bridge-pq-rotate-pq-2 oraz rand-bridge-pq-rotate-pause-2 (bridge/gov.rs). Klucz wstrzymania lub kworum, które podpisuje dla jednego łańcucha, nie podpisuje wtedy dla żadnego innego łańcucha o tym samym identyfikatorze i tych samych kluczach. Plik podpisany w formie …-1 jest w łańcuchu 20 odrzucany (BadPauseSignature, PqBadSignature). Łańcuchy od 14 do 19 używają powyższych układów …-1, bajt w bajt.
Reguły v2: rotacje, kroczące limity, brak wpisów w czasie wstrzymania
Opcjonalna grupa wewnątrz sekcji bridge w genesis. Łańcuch 14 jej nie miał; każdy łańcuch od 15 ją ma, łańcuch 20 z tymi samymi wartościami:
"bridge": { …, "rules_v2": { "global_mint_cap_per_window": 400000000000, "cap_window_secs": 86400 } }
- Dwie rotacje pod kworum postkwantowym.
RotatePqGuardians { new_pq_guardians, nonce, pq_signatures }zastępuje cały zbiór postkwantowy;RotatePauseKey { new_pause_key, nonce, pq_signatures }zastępuje klucz wstrzymania. Obie są pozbawione pakietu i opłaty, jakPauseMints, obie są podpisywane przez bieżący zbiór postkwantowy i obie niosą i podbijająrotation_nonce, więc jedno podpisane kworum autoryzuje dokładnie jedną rotację. Nowy zbiór musi mieć długość bieżącego zbioru ECDSA, klucze po 1312 bajtów, bez powtórzeń i bez klucza wstrzymania. Nie ma okna karencji: zastąpiony zbiór lub klucz nie podpisuje już niczego. W łańcuchu 20 te akcje v1 są odrzucane, a rotacja musi dowieść posiadania nowych kluczy i odczekać dobę (niżej). - Kroczące limity okienkowe. Limit
mint_cap_per_daydla pojedynczego pokrycia jest mierzony w kroczącym okniecap_window_secszamiast w dobie kalendarzowej, a drugi, ogólnorejestrowy limit,global_mint_cap_per_window, obejmuje łącznie wszystkie pokrycia wszystkich tokenów (TokenError::GlobalMintCapExceeded). W łańcuchu 20 to 400 000 000 000 jednostek, 4000 zUSD na 86 400 s. Przy regule dobowej limit o 23:59:59 i ponownie o 00:00:01 oznaczał dwukrotność limitu w dwie sekundy; przy oknie druga emisja jest odrzucana. - Brak wpisów w czasie wstrzymania.
RegisterBridgedTokeniListBackingsą odrzucane jakoMintsPaused, gdymint_paused. Spalanie i obie rotacje pozostają otwarte.
Rotacja: dowód posiadania i jednodniowe opóźnienie
Reguły v2 zostawiły otwarte trzy sprawy: nic nie dowodziło, że ktokolwiek posiada nowe klucze (literówka lub klucz, którego nikt nie ma, na stałe zablokowałyby kworum postkwantowe), rotacja wchodziła w życie w bloku, w którym trafiła do łańcucha, i nic nie mogło zatrzymać rotacji wrogiej. Zamyka je druga opcjonalna grupa obok rules_v2 (które wymaga), nowa w v0.6.8 (audyt v6, BRG-14). Łańcuch 20 ją ustawia:
"bridge": { …, "rotation": { "delay_secs": 86400, "needs_possession": true } }
- Dowód posiadania. Rotacja musi mieć postać
RotatePqGuardiansV2 { new_pq_guardians, possession, nonce, pq_signatures }lubRotatePauseKeyV2 { new_pause_key, possession, nonce, pq_signatures }:possession[i]to własny podpis Dilithium2 nowego kluczaizłożony pod dokładnie tym komunikatem, który podpisuje kworum (M_rotate_pqlubM_rotate_pause, w formie…-2w łańcuchu 20), po jednym na klucz, w kolejności kluczy, po 2420 bajtów każdy. Akcje v1 są odrzucane jakoPossessionRequired. Zła liczba lub długość jest odrzucana przed weryfikacją kworum; zły podpis posiadania (BadPossession { index }) — po niej. - Opóźnienie. Przyjęta rotacja jest zapisywana jako oczekująca, z
effective_at_secs = block time + delay_secs, i wchodzi w życie na końcu pierwszego bloku, którego znacznik czasu ją osiąga. W łańcuchu 20 to 86 400 s, jedna doba. Do tego czasu stary zbiór współsygnuje każdą emisję, a stary klucz wstrzymania wstrzymuje. Przyjęcie rotacji zużywa jedenrotation_nonce, a jej aktywacja zużywa kolejny, więc poświadczenie emisji zgromadzone w puli pod starym zbiorem opuszcza pulę, gdy zbiór się zmienia. Jednocześnie może oczekiwać po jednej rotacji każdego rodzaju (RotationPending { effective_at_secs }). - Anulowanie.
CancelRotation { kind, nonce, signature }(bez pakietu i opłaty) odrzuca oczekującą rotację rodzajukind(0 = zbiór postkwantowy, 1 = klucz wstrzymania). Podpisuje ją bieżący klucz wstrzymania, jedyny klucz trzymany osobno od kworum, pod"rand-bridge-cancel-rotation-2" || genesis [32] || chain_id u64 || rotation_nonce u64 || kind u8w łańcuchu 20, i zużywa nonce. Przechwycone kworum nie może więc zmienić zbioru w czasie krótszym niż doba, a posiadacz klucza wstrzymania może to powstrzymać w ciągu tej doby; nadchodzący klucz wstrzymania nie może anulować własnego przybycia.
Narzędziem podpisującym jest rand-node bridge-gov (rotate-pq, rotate-pause, cancel-rotation, każde z krokami message, sign i submit; submit ponownie weryfikuje każdy wiersz posiadania przed wysłaniem). rand_getBridgeState zwraca rotation_rules i pending_rotations; oba mają wartość null w łańcuchu bez tej grupy (łańcuchy od 14 do 19). Rotacja zbioru strażników ECDSA (ładunek 2) nie jest dotknięta: zachowuje własne reguły i okno karencji. W łańcuchu 20 nie przeprowadzono żadnej rotacji.
Przeniesione z łańcucha 14
Łańcuch 15 został wycięty z pozycji mostu łańcucha 14 (te same emitery i punkty końcowe źródła), a łańcuchy 16, 17 i 18 zostały wycięte w ten sam sposób z pozycji poprzednika. Łańcuch 19 został wycięty z pozycji łańcucha 18 z jedną zmianą: jego emitery Ethereum, BSC i Tron to punkty końcowe wdrożone ponownie 2026-09-30 (0x7af6b17047c1db6cb54347fdea45cf9179075bfa dla łańcuchów 2 i 3, 0x6410797df959987a5baf65b5fab97edeb34d5163 dla łańcucha 4). Łańcuch 18 nigdy im nie zaufał, więc nic z nich tam nie wyemitowano. Łańcuch 20 został wycięty z migawki łańcucha 19 na wysokości 7258, po uprzednim zatrzymaniu mostu, i zachowuje bez zmian emitery, strażników, klucze postkwantowe i klucz wstrzymania łańcucha 19; dodaje grupy opłat i rotacji. Emiter Solana i emiter po stronie Rand nie zmieniły się od łańcucha 14. Co niesie genesis łańcucha 20:
- Depozyt. 99 zUSD, jako dwie genesis’owe noty zUSD (10 i 89 zUSD), wobec 22 USDT zablokowanych na Ethereum, 22 USDT na BSC, 30 USDT na Tron i 25 USDT na Solana oraz 0 przy każdym zabezpieczeniu w USDC: tyle wynosiło
lockedłańcucha 19 w chwili migawki, co zgadzało się z depozytem na punktach końcowych.total_supply == Σ lockedrówna się depozytowi na punktach końcowych w bloku 0. Historia: 10 zUSD, które pozostały w łańcuchu 14, stanowiło jedną genesis’ową notę w łańcuchach od 15 do 19; genesis łańcucha 18 miał je jako 9 USDT zablokowanych na Tron i 1 na Solana, a genesis łańcucha 19 jako 10 na Solana po zrównoważeniu zabezpieczeń w łańcuchu 18. Pozostałe 89 to wpłaty netto łańcucha 19, pomniejszone o jego spalenia. - Indeks zestawu strażników 1, na którym znajdują się już źródłowe punkty końcowe; następna rotacja ECDSA musi nieść indeks 2. Osiem kluczy postkwantowych i klucz pauzy pochodzą z łańcucha 15, bez zmian.
burn_sequence13: spalenia łańcucha 14 skończyły się na 6, genesis łańcuchów od 15 do 18 zaczynał od 7, łańcuch 18 wykonał jedno spalenie (7), a łańcuch 19 zaczął od 8. Łańcuch 19 wykonał pięć spaleń, o numerach od 8 do 12, więc łańcuch 20 zaczyna od 13 i żaden punkt końcowy ani demon indeksowany numerem sekwencyjnym nie zobaczy żadnego z nich dwukrotnie. Pierwsze spalenie łańcucha 20 otrzymało numer 13.- Powtórzenia (replay). Emisja z wcześniejszego łańcucha nadal przeszłaby kworum ECDSA, ale jej współpodpis postkwantowy wiąże identyfikator tego łańcucha, więc w łańcuchu 20 zostaje odrzucona. Genesis łańcucha 15 nie miał dolnego progu dla transferów przychodzących (
min_inbound_sequencewynosiłonull), więc tam zabezpieczenie opierało się na współpodpisie i na tym, że strażnicy nigdy nie współpodpisują starej blokady dla nowego identyfikatora łańcucha. Od łańcucha 16 genesis ustala taki próg: dla każdego łańcucha źródłowego najniższysequence, jaki może nieść atestacja transferu od emitenta tego łańcucha. W łańcuchu 20 jest to{ "2": 3, "3": 3, "4": 3, "5": 7 }: następny wychodzący numer sekwencyjny każdego punktu końcowego w chwili migawki łańcucha 19, przy czym każda wcześniejsza blokada zdążyła wyemitować w łańcuchu 19. (W łańcuchu 18 było to{ "2": 2, "3": 2, "4": 2, "5": 2 }; w łańcuchu 19{ "2": 1, "3": 1, "4": 1, "5": 4 }, gdzie sekwencja 0 na każdym ponownie wdrożonym punkcie końcowym była własną blokadą operatora, która nigdy nie może emitować.) Transfer poniżej progu jest odrzucany z błędemBelowReplayFloor, niezależnie od kworum i współpodpisu, które niesie. Próg nie dotyczy rotacji zestawów strażników. Jest ustalany w genesis, zatwierdzony w haszu genesis i wpisany w korzeń mostu.
Kolejność uruchomienia w łańcuchu 14 (historia)
Jak most został uruchomiony w łańcuchu 14, wcześniejszym testnet, we wrześniu 2026:
- Utworzyć łańcuch z sekcją
bridge(wraz zpq_guardiansipause_key) oraz sekcjątokensustawiającąregistration_feeimint_cap_per_day, bez wymieniania żadnego tokena. Wykonano 2026-09-20. - Włączyć faucet, aby portfel wdrażającego mógł pozyskać RAND. Wykonano.
- Wdrażający rejestruje zUSD z pierwszym zabezpieczeniem, pod podpisem kworum na
M_registerprzylist_nonce = 0. Wykonano na wysokości 256. - Strażnicy odczytują indeks i podpisują sześć pozostałych komunikatów
ListBacking, przesyłanych pojedynczo. Wykonano:list_noncewynosi 7. - Dla każdego zabezpieczenia: wylistować w Rand przed
setTokenna źródłowym punkcie końcowym. - Rotacja zestawu strażników (zestaw 0 → zestaw 1, podpisana przez 5 z 6 członków bieżącego zestawu) na czterech źródłowych punktach końcowych, a następnie ta sama atestacja w Rand. Rand odczytywał zestaw 0 przez cały czas życia łańcucha 14; łańcuch 15 startuje od zestawu 1.
- Rzeczywisty obieg w obie strony dla każdego łańcucha źródłowego, na małych kwotach — wpłata przez most, emisja zUSD, transfer, spalenie z powrotem — oraz uzgodnienie
lockedkażdego zabezpieczenia (rand_getTokenSupply) z depozytem na punkcie końcowym.
Stan publiczny
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 to klucze publiczne Dilithium2, każdy po 1312 bajtów zapisanych szesnastkowo. Nie ma już next_index: token przenoszony mostem jest wylistowany, pod indeksem ustalonym przy jego rejestracji, zanim będzie można go wpłacić, więc nie ma indeksu do przewidywania. rotation_nonce to wartość, jaką musi nieść następny M_rotate_pq/M_rotate_pause/M_cancel; rules_v2 ma wartość null w łańcuchu bez tej grupy (łańcuch 14), a min_inbound_sequence ma wartość null w łańcuchu bez dolnego progu powtórzeń (łańcuch 15 i wcześniejsze); w łańcuchu 20 wynosi { "2": 3, "3": 3, "4": 3, "5": 7 }. rotation_rules, pending_rotations i fees mają wartość null w łańcuchu bez tych grup (łańcuchy od 14 do 19). Powyższy przykład pokazuje kształt odpowiedzi, a nie wartości łańcucha 20: łańcuch 20 wystartował z guardian_set_index 1 i burn_sequence 13 oraz zwraca "rotation_rules": { "delay_secs": 86400, "needs_possession": true }, "pending_rotations": [] i "fees": { "mint_bps": 10, "burn_bps": 10, "recipient": "rand1…" }. Łańcuch bez sekcji bridge odpowiada {"enabled": false}.
Brak sald: wartość przeniesiona mostem to noty, a portfel odczytuje to, co posiada, poleceniem rand asset-balance [INDEX] na podstawie własnych not. Korzeń mostu zatwierdza emitentów, zestawy strażników, skróty wydanych transferów, numer sekwencyjny spaleń, postkwantowy zestaw strażników oraz klucz pauzy, flagę pauzy i nonce’y zarządzania (dwa, a przy regułach v2 także rotation_nonce), próg powtórzeń tam, gdzie genesis go ustala, a w łańcuchu 20 także reguły rotacji wraz z każdą oczekującą rotacją oraz grupę opłat; wchodzi do korzenia stanu tylko w łańcuchu, który ma most. Rejestr tokenów, z locked każdego zabezpieczenia, jest zatwierdzany osobno jako tokens_root. Endpointy eksploratora GET /bridge i GET /bridge/assets serwują ten sam stan, drugi z sumami wpłat i spaleń dla każdego zabezpieczenia jako ciągi o 8 miejscach dziesiętnych; GET /tokens i GET /tokens/:id serwują rejestr.
Co jest publiczne, a co ukryte
| co | kto to widzi |
|---|---|
| kwota wpłaty, indeks aktywa i adres odbiorcy | wszyscy, w tej jednej transakcji |
| którą notę odbiorca wydaje później | nikt |
| kwota spalenia, aktywo, moneta, miejsce docelowe i opłata dla relayera | wszyscy — strażnicy tego potrzebują |
| kwota noty opłaty mostu, aktywo i właściciel, skarbiec (łańcuch 20) | wszyscy: jest odtwarzana z pól publicznych |
| które noty sfinansowały spalenie | nikt |
| salda przeniesione mostem | tylko posiadacz lub posiadacz klucza podglądu |
zestawy strażników, emitenci, rejestr z locked każdego zabezpieczenia, dziennik spaleń | wszyscy |
Znane luki
Z własnej listy fullnode’a: relayer nie jest wynagradzany w Rand; koperta wpłaty nie jest z niczym związana, więc wrogi relayer może zapieczętować śmieci, dlatego portfel odtwarza noty wpłat z pól publicznych; wspólne wektory testowe na poziomie księgi nie zostały odbudowane na puli not; bridge-burn nadal dowiaduje się o polityce docelowego punktu końcowego dopiero po zapłaceniu za dowód; znaczniki czasu bloków równe znacznikowi rodzica są dozwolone w łańcuchu z mostem, więc zmowa liderów mogłaby utrzymać zastąpiony zestaw strażników w oknie karencji; dziennik spaleń w pamięci jest nieograniczony; nie istnieje ani nie jest planowany lekki klient ani weryfikacja stanu łańcucha źródłowego w łańcuchu.