Начало работы с инструкциями Aleo
Текст оригинальной статьи: https://www.entropy1729.com/getting-started-aleo-instructions/
Мой Дискорд: useless_dorozhkina#1394
Данный гайд является пошаговым разбором нескольких легких примеров, чтобы помочь вам с инструкциями Aleo.
Инструкции - это нечто вроде assembly для программ Aleo. Программирование более высокого уровня может быть осуществлено с помощью Leo, с которым вы можете ознакомиться здесь.
Прежде, чем мы начнем, убедитесь, что у вас установлен и запущен Rust. Вы можете найти несколько примеров в репозитории проекта Aleo.
Установите Aleo
Вы можете установить aleo, создав из исходного кода, как показано ниже (мы рекомендуем устанавливать Aleo этим путем):
# Установите исходный код git clone https://github.com/AleoHQ/aleo && cd aleo # Установите Aleo $ cargo install --path .
- Сборка из crates.io
(Эта опция устарела, мы рекомендуем собирать из исходного кода, чтобы получить последнюю версию)
В качестве альтернативы, вы можете установить компилятор Aleo из репозитория crates.io. В вашем терминале введите:
cargo install aleo
На этом шаге вы можете ввести команду new. Наш проект:
aleo new foo
Первые шаги
Создание нового проекта Aleo
Чтобы создать новый проект, мы используем команду new. Наш проект:
Это создаст директорию foo и файлы с базовой структурой проекта:
- README.md со скелетом README с инструкциями о том, как компилировать.
- main.aleo - главный файл исходного кода.
- program.json, содержащий в себе идентификатор проекта в формате JSON. В частности, адрес разработчика и его приватный ключ для программы.
Давайте откроем main.aleo и зададим функцию сложения:
// The 'foo.aleo' program.
program foo.aleo;
function sum:
input r0 as u32.public;
input r1 as u32.private;
add r0 r1 into r2;
output r2 as u32.private;Мы рассмотрим, что значит данный код через секунду. Для начала, мы создадим нашу программу foo.
Компилирование проекта Aleo
Чтобы скомпилировать проект, в главной директории введите:
aleo build
⏳ Compiling 'foo.aleo'... • Loaded universal setup (in 1478 ms) • Built 'sum' (in 6323 ms) ✅ Built 'foo.aleo' (in "[...]/foo")
Сначала "универсальная настрйка" загружается в вашу среду. Вы можете прочитать больше об этом здесь или в докладе о Marlin.
Как только завершена универсальная установка, все функции в вашем файле main.aleo будут созданы, сгенерировав в выходной папке это:
- sum.prover - доказывающий для функции суммирования.
- sum.verifier - проверяющий для функции суммирования.
- main.avm - байт-код вашей программы aleo, который будет запущен VM.
Как вы уже можете догадаться, у нас есть только один файл .avm для всей программы, но доказывающий и проверяющий для каждой функции.
Запуск программы
Вы можете запустить всю программу с помощью команды aleo run с последующим именем функции, которую вы хотите исполнить и ее входными данными. Давайте запустим нашу функцию сложения:
aleo run sum 2u32 3u32
когда выполнение будет завершено, вы увидите подобный вывод:
🚀 Executing 'foo.aleo/sum'... • Calling 'foo.aleo/sum'... • Executed 'sum' (in 1170 ms) ➡️ Output • 5u32 ✅ Executed 'foo.aleo/sum' (in "[...]/foo")
Как вы можете увидеть, исполнение функции сложения длилось 1170 мс, и выходному реестру присвоилось значение 5u32, представляющее сумму входных данных.
Обзор программы
Давайте исследуем программу foo внутри файла main.aleo:
// Программа 'foo.aleo'.
program foo.aleo;
function sum:
input r0 as u32.public;
input r1 as u32.private;
add r0 r1 into r2;
output r2 as u32.private;Для начала нам нужно объявить программу, как показано ниже:
program foo.aleo;
Затем мы начинаем писать ее функции (или другие структуры Aleo, такие, как интерфейсы, записи, замыкания, как мы увидим позже). В случае функций все очень просто:
function [function_name]:
Функции состоят из трех основных частей:
- Раздел ввода. Здесь мы объявляем его входные параметры:
input r0 as u32.public;
input r1 as u32.private;Все в инструкциях Aleo объявляется/хранится внутри реестра с типом (i8,field,bool и так далее) и опцией видимости (public or private). Реестры называются, как r0, r1, ..., rn. Позже мы более подробно рассмотрим реестры, типы и видимость.
Итак, в данном случае мы используем r0 и r1, чтобы хранить входные данные, переданные последовательно в программу как значения u32, где мы можем хранить 32-разрядные целые числа без знака для выполнения нашей операции сложения.
- Раздел инструкций. Следующий раздел содержится в ядре нашей функции. Здесь мы вызываем количество инструкций Aleo, которое нам нужно для того, чтобы наша программа выполняла то, что мы хотим. Например, выполнение операции сложения:
add r0 r1 into r2;
За каждой инструкцией Aleo следуют входные параметры с их определенными типами, а результат хранится в реестре into. Вы можете увидеть больше инструкций Aleo здесь.
- Раздел вывода. Также, как и разделе ввода, раздел вывода делает то же самое для вывода программы. Это возвращаемое значение функции.
output r2 as u32.private;
Углубление в некоторые концепции
Типы
Aleo - это типизированный язык. На данный момент доступны следующие типы данных:
Boolean Field Group I8 I16 I32 I64 I128 U8 U16 U32 U64 U128 Scalar
Interface Record
Реестры
Реестры - это места, в которых вы храните ваши данные, чтобы затем иметь возможность изменять их.
Интерфейсы
Интерфейсы - это структуры данных, определяемых пользователем. Они очень похожи на традиционные структуры в обычных языках программирования. Вы можете хранить Интерфейсы в реестрах, как и в случае с любыми другими типами данных Aleo.
Например, давайте создадим интерфейс, представляющий массив фиксированного размера из 3 элементов. Добавьте его в нижнюю часть файла main.aleo:
interface array3:
a0 as u32;
a1 as u32;
a2 as u32;Теперь, просто для примера, давайте создадим функцию, которая добавляет единицу к каждому элементу реестра с хранящимся в нем типом данных array3.
function sum_one_to_array3:
input r0 as array3.private;
add r0.a0 1u32 into r1;
add r0.a1 1u32 into r2;
add r0.a2 1u32 into r3;
cast r1 r2 r3 into r4 as array3;
output r4 as array3.private;Как вы видите, мы можем ввести интерфейс в реестр r0 и получить доступ к элементам с синтаксисом .. Мы выполняем инструкцию add для каждого элемента, сохраняя результат в реестры r1, r2 и r3, и, наконец, мы используем команду cast для создания нового интерфейса array3 в r4.
Теперь, давайте запустим программу. В данном случае, единственная новая вещь, которую вам нужно знать, это то, что интерфейсы передаются в cli следующим образом:
"{a0: 1u32, a1: 2u32, a2: 3u32}"Теперь мы можем выполнить команду aleo run. Мы очистим проект, чтобы подобрать новый код:
aleo clean && aleo run sum_one_to_array3 "{a0: 0u32, a1: 1u32, a2: 2u32}"И мы получим новые элемента array3 как выходные данные:
🚀 Executing 'foo.aleo/sum_one_to_array3'...
• Calling 'foo.aleo/sum_one_to_array3'...
• Executed 'sum_one_to_array3' (in 1331 ms)
➡️ Output
• {
a0: 1u32,
a1: 2u32,
a2: 3u32
}
✅ Executed 'foo.aleo/sum_one_to_array3' (in "[...]/foo")Записи
Запись - это фундаментальная структура данных для шифрования активов пользователя и состояния приложения. Они очень похожи на интерфейсы, но у них есть два обязательных параметра.
record token:
owner as address.private
gates as u64.privateowner относится к владельцу адреса Aleo, которому принадлежит запись. gates - это количество кредитов, которые записи придется потратить.
Записи важны, потому что они представляют базовую структуру Aleo для обработки состояния в вашем приложении.
При выполнении функции Aleo в качестве входных реестров могут передаваться только реестры, принадлежащие адресу приложения. В противном случае будет выдана ошибка, и приложение не будет запущено.
Вы можете найти адрес своего разрабатыемого приложения внутри файла program.json:
{
"program": "foo.aleo",
"version": "0.0.0",
"description": "",
"development": {
"private_key": "APrivateKey1zkpFsQNXJwdvjKs9bRsM91KcwJW1gW4CDtF3FJbgVBAvPds",
"address": "aleo1x5nz5u4j50w482t5xtqc3jdwly9s8saaxlgjz0wvmuzmxv2l5q9qmypx09"
},
"license": "MIT"
}Состояние Алео в двух словах
В Aleo управление состоянием приложения осуществляется с помощью записей. Аккаунт Aleo может создать транзакцию для использования записи и создания на ее месте новой. Записи на Aleo шифруются по адресу владельца записи, это гарантирует, что все записи в Aleo являются полностью приватными.
Ваша первая программа Aleo: Осуществление перевода
// Программа 'foo.aleo'.
program foo.aleo;
record token:
owner as address.private;
gates as u64.private;
amount as u64.private;
function transfer_amount:
// запись токена отправителя
input r0 as token.record;
// адрес получателя
input r1 as address.private;
// сумма для перевода
input r2 as u64.private;
// конечный баланс отправителя
sub r0.amount r2 into r3;
// конечный баланс получателя
add 0u64 r2 into r4;
// запись токена отправителя после перевода
cast r0.owner r0.gates r3 into r5 as token.record;
// запись токена получателя после перевода
cast r1 0u64 r4 into r6 as token.record;
// новая запись токена отправителя
output r5 as token.record;
// новая запись токена получателя
output r6 as token.record;Сначала мы определили наш собственный тип данных, token, с двумя обязательными параметрами, owner и gates и определяемым пользователем параметром amount, который показывает количество наших токенов.
Функция transfer_amount принимает 3 входных значения (sender, receiver и amount) и сохраняет их в 3 реестра (r0, r1 и r2). После этого она вычисляет окончательные балансы для обоих, отправителя и получателя, и сохраняет их в r3 и r4 (используя инструкции sub и add для вычитания и сложения). С помощью этих окончательных значений, она создает выходные записи для отправителя и получателя, сохраняя их в r5 and r6 . Наконец, обе записи отправляются из функции с помощью инструкции output.
Для запуска этой функции первым параметром является входная запись программы. Формат этого параметра такой же, как и для типов интерфейсов:
{
owner: aleo1x5nz5u4j50w482t5xtqc3jdwly9s8saaxlgjz0wvmuzmxv2l5q9qmypx09.private,
gates: 0u64.private,
amount: 50u64.private
}- owner: публичный адрес программы, который указан в
development.addressфайла build/program.json. - gates: gates, которые имеются у записи.
- другие параметры: зависят от самой программы (в данном примере мы использовали параметр amount со значением 50).
Давайте запустим функцию transfer_amount (если вы следуете этим шагам, помните что в поле owner нужно использовать адрес из program.json):
aleo clean && aleo run transfer_amount "{
owner: aleo1x5nz5u4j50w482t5xtqc3jdwly9s8saaxlgjz0wvmuzmxv2l5q9qmypx09.private,
gates: 0u64.private,
amount: 50u64.private
}" aleo1h3gu7fky36y8r7v2x9phc434fgf20g8qd7c7u45v269jfw6vmugqjegcvp 10u64Мы получим следующие выходные записи:
🚀 Executing 'foo.aleo/transfer_amount'...
• Calling 'foo.aleo/transfer_amount'...
• Executed 'transfer_amount' (in 3520 ms)
➡️ Outputs
• {
owner: aleo1x5nz5u4j50w482t5xtqc3jdwly9s8saaxlgjz0wvmuzmxv2l5q9qmypx09.private,
gates: 0u64.private,
amount: 40u64.private
_nonce: 2293253577170800572742339369209137467208538700597121244293392265726446806023group.public
}
• {
owner: aleo1h3gu7fky36y8r7v2x9phc434fgf20g8qd7c7u45v269jfw6vmugqjegcvp.private,
gates: 0u64.private,
amount: 10u64.private
_nonce: 2323253577170856894742339369235137467208538700597121244293392765726742543235group.public
}
✅ Executed 'foo.aleo/transfer_amount' (in "[...]/foo")И все! Вы перевели свои первые собственные токены в Aleo!
Заметьте: _nonce не записан в инструкциях Aleo. Компилятор выводит _nonce в выходных данных записи. Пользователю нужно предоставить его в качестве входной переменной при использовании записи.
Заключительные замечания
Программы Aleo могут компилировать и выполнять криптографические схемы, написанные в инструкциях Aleo. Это вводит некоторые ограничения, которых нет в обычных языках программирования. Например, когда вы используете троичный оператор для создания ветви в потоке программы, вы должны иметь оба результата заранее (т.е. результат каждой ветви должен быть вычислен и известен перед выполнением троичного оператора). Логическое сравнение должно выполняться между регистрами, а не между возвращаемыми значениями вызовов функций.