Эти четыре программы — по одному законченному приложению конфиденциального вызова: задача, интерфейс, гостевая программа на Rust, инструментарий на стороне хоста, а также развёртывание и вызов в действующей сети testnet. Они длиннее учебных руководств, и каждая опирается на свою часть машины. Написание программы на Rust или C — руководство по модели, которую используют все четыре. Прочитайте его первым, если read_input, публичный сегмент или восемь выходных слотов вам незнакомы.
Всё здесь впервые было запущено в цепочке 18 с fullnode v0.6.7 30 сентября 2026 года, и каждый вывод на этих страницах вставлен из тех запусков. Программы не переносятся при переходе на новую цепочку, поэтому 1 октября 2026 года все четыре были развёрнуты заново, без изменений, в цепочке 19, а затем в цепочке 20 (fullnode v0.6.8), и каждый раз вызовы повторялись. Идентификатор программы — это хеш кода и публичного сегмента, а не цепочки, поэтому все идентификаторы одинаковы; хеши транзакций в протоколах запусков принадлежат цепочке 18, а таблица ниже ссылается на транзакции цепочки 20. В цепочке 20 программа может также хранить состояние между вызовами (RPL-2); эти четыре программы его не используют, а Состояние программы и репозиторий example-programs охватывают программы, которые используют.
Четыре программы
| программа | что доказывает вызов | на чём держится | слов | уровень |
|---|---|---|---|---|
| Анонимное голосование | вызывающий входит в опубликованный список, не раскрывая, кто именно, и ещё не голосовал | Keccak-256, публичный сегмент | 427 | 12 |
| Доказательство обязательств | приватные балансы клиентов в сумме не превышают опубликованный резерв, при корне, который каждый клиент может проверить | SHA-256, дерево, построенное внутри программы | 994 | 14 |
| Внецепочечный реестр | пакет переводов переводит реестр из одного состояния в следующее, причём каждый отправитель авторизован и ничего не создано | Poseidon2, состояние, переносимое между вызовами | 773 | 14 |
| Приватное кредитное решение | опубликованная модель одобряет или отклоняет приватные данные заявителя, за которые поручился аттестатор | целочисленная арифметика, модель в публичном сегменте | 462 | 12 |
Все четыре работали в цепочке 20 до её вывода из эксплуатации 12 октября 2026 года. Цепочка 21 (fullnode v0.8.0) стартовала без программ, как и после каждого перехода, и эти четыре в ней пока не развёрнуты заново; приведённые ниже идентификаторы при этом не изменятся, поскольку идентификатор хеширует код и публичный сегмент, а не цепочку. Каждый вызов в таблице повторяет с теми же входными данными вызов в цепочке 18, показанный на странице соответствующей программы:
| программа | идентификатор программы | развёрнута в цепочке 20 в | первый вызов в цепочке 20 |
|---|---|---|---|
| анонимное голосование | ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970 | f6ae5528…0f82 | 3a304e7a…427b |
| доказательство обязательств | fce8474dfdf80b9733e04fb72d4d8d19c40ba5ab646b3d867322daf4b9b599c0 | a8bc281b…17d9 | 6116c15f…d62a |
| внецепочечный реестр | c4ddd388f2d94299610c7c3409dfdfb4dcb4ae3e0bc9ec78d0c45f51d8231cab | 72cc95f2…3d51 | 9c9998bb…66eb |
| приватное кредитное решение | 7bbec14289f4446231b3a9a5ac18cb0613a373ca8a14fe4f046f3fde7ae6c73c | 413e6658…e42b | d3ceb306…124a |
Обозреватель показывает все программы и их вызовы по адресу randscan.org/programs. Цепочка testnet время от времени заменяется, и программы при этом не переносятся: после следующего перехода этих идентификаторов не будет, а программы придётся развёртывать заново.
Подготовка
Инструментарий. Гостевые программы собираются с помощью rand-guest из репозитория circuits на коммите, который vendor-ят и fullnode v0.6.7, и v0.6.8:
git clone https://github.com/randprotocol/zkp-circuits circuits
cd circuits
git checkout aeacf31 # branch feat/v067-circuits
rustup toolchain install 1.98.1
rustup target add riscv32im-unknown-none-elf --toolchain 1.98.1
(cd rand-guest && cargo +1.98.1 build --release)
alias rand-guest="$PWD/rand-guest/target/release/rand-guest"
Коммит важен по двум причинам. hc — функция исходного кода и инструментария, поэтому гостевая программа, пересобранная на другом коммите guest-sdk, может получить другой hc. И сигнатуры SDK менялись: на aeacf31 keccak и sha256_compress принимают ссылки на массивы, тогда как на более старых коммитах принимали сырые указатели.
Исходные тексты. Все четыре гостевые программы, их хост-скрипты и демонстрационные данные — это одна загрузка. Распакуйте её внутри checkout, потому что rand-guest build находит guest-sdk, поднимаясь вверх от каталога гостевой программы:
mkdir -p my-guests
curl -sSf https://randprotocol.org/examples/rand-example-programs.tar.gz | tar -xz --strip-components=1 -C my-guests
ls my-guests
# allowlist ledger ledger-host score solvency
Файлы также доступны для просмотра по одному в https://randprotocol.org/examples/programs/, например allowlist/src/main.rs.
Кошелёк. Для развёртывания и вызовов нужен кошелёк rand из fullnode v0.6.8 (cargo build --release -p randprotocol-client в checkout fullnode на теге v0.6.8 либо rand, приложенный к релизу v0.6.8), ключ и RAND. В цепочке 21 кошелёк — это rand из fullnode v0.8.0 (тег v0.8.0): набор ограничений 9 изменил все ключи верификатора, поэтому ни кошелёк v0.6.8, ни кошелёк v0.7.x не могут там ни развёртывать, ни вызывать. Более старый кошелёк не мог проводить транзакции и в цепочке 20: цепочка 20 привязывает каждое доказательство и каждое подписанное сообщение к своему genesis, а кошелёк v0.6.7 этого не делает.
RPC. Публичной конечной точки https://rpc.randprotocol.org достаточно для всего, что описано на этих страницах: чтения программ и квитанций, синхронизации кошелька, развёртывания и вызова. Первая синхронизация кошелька читает каждый заголовок блока и каждый блок, содержащий ноту, — 645 запросов на высоте цепочки 18 на 30 сентября 2026 года — и заняла около трёх минут через эту конечную точку. Конечная точка допускает для каждого адреса 1 200 чтений и 6 отправок в минуту. У собственного узла (Запуск узла) таких ограничений нет.
export RAND_RPC=https://rpc.randprotocol.org
export RAND_KEY=$HOME/wallet.key.json
В цепочке 18 один из голосов, учтённых на странице анонимного голосования, fdbcc960…fc2b, был доказан на ноутбуке и отправлен только через публичную конечную точку. В цепочках 19 и 20 все тринадцать транзакций каждый раз, то есть четыре развёртывания и девять вызовов, прошли именно так.
Что цепочка 18 изменила для программы
Вызов объявляет лимит газа и платит за него. Набор ограничений 8 измеряет каждый запуск. Обычный цикл стоит 1 газ. Исключение составляют системные вызовы хеширования:
| инструкция | газ |
|---|---|
любая инструкция RV32IM, HALT, WRITE_OUTPUT, READ_INPUT, READ_PUBLIC | 1 |
POSEIDON2 над n словами | 3 + 3 × ⌈n / 4⌉ |
SHA256, одно сжатие | 64 |
KECCAK, одна перестановка | 192 |
Доказательство несёт одно новое публичное значение — объявленный лимит, и схема обеспечивает, чтобы газ запуска не превышал его. Точный газ остаётся приватным. rand call сначала выполняет программу в эмуляторе и печатает оба значения:
gas: 5636 (declaring 6144, tier 12)
По умолчанию кошелёк округляет точный газ вверх до кратного 2^(tier − 2), то есть 1 024 на уровне 12 и 4 096 на уровне 14, так что объявленный лимит мало что говорит о запуске. --gas-limit max объявляет потолок уровня и не сообщает ничего сверх уровня. --gas-limit N объявляет точное значение.
Комиссия зависит от объявленного лимита и от байтов.
floor = 0.001 RAND + gas price × declared limit + byte price × ⌈bytes / 1024⌉
Цены начинались со 100 единиц за газ и 800 единиц за KiB (единица — 10⁻⁹ RAND) и понемногу смещаются с каждым блоком к целевому значению. rand_getLimits возвращает текущие. Кошелёк добавляет запас в два ценовых шага, множитель 1,125², поскольку вызов попадает в блок через два-три блока после назначения цены.
Одна деталь определяет большую часть комиссии. Начиная с цепочки 18 доказательство вызова привязано к собственной транзакции, поэтому кошелёк должен зафиксировать комиссию до появления доказательства и оценивает доказательство по предельному размеру цепочки в 4 MiB, а не по реальному. Для анонимного голосования это (1,000,000 + 100 × 6,144 + 800 × 4,097) × 1.125² = 6 191 437 единиц, 0,0062 RAND, из которых газ составляет около восьмой части. Четыре вызова на этих страницах стоили от 0,0057 до 0,0075 RAND каждый. --fee переопределяет значение по умолчанию.
Каждый пакет — это два доказательства. Цепочка 18 использует раздельную авторизацию: пакет, оплачивающий комиссию, несёт небольшое доказательство авторизации уровня 10 рядом с доказательством пакета уровня 14. Кошелёк строит оба и печатает по строке для каждого. Таким образом, вызов — это три доказательства: вызова, авторизации и пакета. На ноутбуке Apple Silicon четыре вызова здесь заняли от трёх до пяти с половиной минут каждый, с пиком памяти от 6,1 до 6,6 GB. Доказательство пакета каждый раз занимает около двух минут из этого времени. Доказательство вызова уровня 12 добавляет полминуты, а уровня 14 — две минуты.
Пределы, в которые должен укладываться вызов.
| предел | цепочки с 18 по 20 |
|---|---|
| уровень | не выше 14: 16 383 цикла, включая строки дайджестов |
| перестановки Poseidon2 | 2 048 на уровне 14, включая дайджесты программы, входа и публичного сегмента |
| перестановки Keccak | 128 |
| сжатия SHA-256 | 128 |
| доказательство вызова | 4 194 304 байта (4 MiB) |
| программа | 8 180 слов без публичного сегмента, несколько меньше с ним: цепочка 21, как и цепочки с 18 по 20, отклоняет развёртывание, которое не смог бы вместить ни один вызов уровня 14 (ProgramUncallable), что бы ни разрешал max_program_words (65 535) |
| публичный сегмент | 32 768 слов, фиксируется при развёртывании |
| приватный вход с запечатанным транскриптом | 16 071 слово |
rand-guest run --tier 14 проверяет первые два предела до развёртывания. Хеш-таблица увеличивает доказательство. Доказательство реестра уровня 14, без хеш-таблицы, занимает 1,50 MB. Таблица SHA-256 доводит доказательство обязательств до 1,91 MB, а таблица Keccak доводит доказательство голосования до 3,35 MB, что составляет большую часть предела в 4 MiB.
Общие соглашения всех четырёх программ
- Слот 0 — это статус, и 0 никогда не является допустимым статусом. У каждого исхода, включая сбои, есть номер от 1 и выше, а сбой завершается штатной остановкой, чтобы его можно было доказать. Квитанцию из одних нулей нельзя принять за результат.
- Слот 7 не используется. Rust-гость, который завершается паникой, записывает туда
0xdeadbeef(3 735 928 559). Читатель сначала проверяет слот 7. - Слоты со 2-го по 6-й несут 160-битное значение: нуллификатор, корень, ссылку, обязательство. Полный 256-битный хеш занимает восемь слов и не оставил бы места для статуса. Усечение до пяти слов сохраняет 80 бит стойкости к коллизиям, и на каждой странице сказано, что именно это защищает.
- Порядок байтов следует хешу. Значения Keccak — слова в порядке little-endian, значения SHA-256 — слова в порядке big-endian, потому что именно так каждый системный вызов читает свой блок. Если скрипт хоста и гость расходятся в этом, получаются дайджесты, которые просто никогда не совпадают.
- Всё, что меняется от экземпляра к экземпляру, находится в публичном сегменте; всё, что меняется от вызова к вызову, — приватный вход. Корень голосования, значение резервов и веса модели фиксируются при развёртывании и привязываются к идентификатору программы. Вызов не несёт собственных публичных слов.
- Входные слова в командной строке записываются в десятичном виде. Страницы превращают файл свидетельства во флаги командой
$(sed 's/[0-9][0-9]*/--input &/g' witness.txt).
Вызовите программу сами
Имея распакованные исходники, собранный инструментарий и пополненный кошелёк (v0.6.8 в chain 20, где выполнялись эти запуски; v0.8.0 в chain 21), голосование от имени одного из демонстрационных участников выполняется четырьмя командами:
cd my-guests/allowlist
python3 allowlist.py witness 10 1 demo-secrets.txt 6 1 > witness.txt # member 6, choice 1
rand-guest build . --max-words 65535 # only needed to run it in the emulator first
rand call ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970 \
$(sed 's/[0-9][0-9]*/--input &/g' witness.txt)
Для вызова образ не нужен: rand call получает код и публичный сегмент программы от узла, проверяет, что их хеш совпадает с идентификатором, и строит доказательство по ним.
Чтобы прочитать результат, не нужны ни кошелёк, ни узел. Любая квитанция запрашивается одним запросом к публичной конечной точке:
$ curl -s https://rpc.randprotocol.org -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"rand_getReceipt","params":["8aa13d1f76d28f63a46c30bb09ef44b1ba35083ca80ba7301997f98e5f783666"]}'
{"id":1,"jsonrpc":"2.0","result":{"h_in":"ea71e7701e62d49cac37b239bef132178f481835027d62b34da45fd6cf2667c6","h_pub":"2296cfca8db9d38bd0b7579056c9dea3c8d8005df7b42e2b1e954a75fed67510","height":98335,"index":0,"outputs":[1,2,392974773,873526743,1921224601,3950868247,2248722827,0],"program":"ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970","tier":12,"tx":"8aa13d1f76d28f63a46c30bb09ef44b1ba35083ca80ba7301997f98e5f783666"}}
API обозревателя возвращает вызовы программы одним запросом: https://randscan.org/api/v1/programs/<program id> возвращает call_count и recent_calls (Explorer API).
Чего они не делают
Программа в Rand — это доказательство о вычислении, и сеть не выполняет никаких действий по её выходным данным. Ни одна из этих программ не переводит RAND или токен, не хранит баланс и не помнит предыдущий вызов. Голоса подсчитывает тот, кто читает квитанции. Обязательства сопоставляются с резервами, которым нужны собственные подтверждения. Балансы реестра — это числа во внецепочечном дереве. Кредитное решение — это бит, который кредитор вправе принять или не принять. Каждая страница завершается списком того, что её доказательство оставляет на усмотрение пользователей.