Это руководство представляет собой описание некоторых простых примеров, которые помогут вам в работе с инструкциями Aleo.
Инструкции это как сборка для программ Aleo.
Программирование более высокого уровня может быть выполнено с помощью Leo, о котором вы можете прочитать здесь.
Прежде чем мы начнем, убедитесь, что у вас установлен и запущен Rust. Вы можете найти несколько примеров в репозитории проекта Aleo
Установите Aleo
- Сборка из исходного кода
Вы можете установить aleo
, собрав его из исходного кода следующим образом (мы рекомендуем устанавливать Aleo именно таким образом):
# Download the source code
git clone https://github.com/AleoHQ/aleo && cd aleo
# Install Aleo
$ cargo install --path .
Сборка из crates.io
(Эта опция устарела, мы рекомендуем собирать из исходников, чтобы использовать последнюю версию)
Также вы можете установить сборник aleo из репозитория crates.io. В терминале запустите:
cargo install aleo
На этом этапе вы можете запустить команду aleo
в терминале:
aleo
Первые шаги
Создание нового проекта Aleo
Чтобы создать новый проект, мы воспользуемся командой new
. Наш проект:
aleo new foo
Это создаст папку foo и файлы с базовой структурой проекта:
- README.md скелет README с инструкциями по сборке
- main.aleo основной файл исходного кода.
- program.json, содержащий идентификационные данные проекта в формате JSON. В частности, адрес разработчика и его закрытый ключ для программы.
Давайте откроем main.aleo и зададим функцию sum:
// 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")
Сначала в вашу систему загружается "universal setup". Подробнее об этом можно прочитать здесь или в документе Marlin.
Как только universal setup будет готова, каждая функция в вашем файле main.aleo собирается, создавая это в папке output:
- sum.prover проверяющая для функции sum.
- sum.verifier верификатор для функции sum.
- main.avm байткод вашей программы aleo для запуска виртуальной машиной.
Как вы уже догадались, у нас всего один .avm-файл для всей программы, но для каждой функции есть свой проверяющий и верификатор.
Запуск программы
Вы можете запустить программу с помощью команды aleo run
, за которой следует имя функции, которую вы хотите выполнить, и ее входные параметры. Давайте запустим наши функции sum:
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")
Как вы можете видеть здесь, выполнение функции sum длилось 1170 мс, а выходному регистру было присвоено значение 5u32
, представляющее sum входов.
Обзор программы
Давайте рассмотрим программу foo внутри файла 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;
Во-первых, нам нужно заявить программу следующим образом:
program foo.aleo;
Затем мы можем начать писать её функции (или другие структуры Aleo, такие как интерфейсы, записи, закрытия, как мы увидим позже). В случае с функциями все очень просто:
function [function_name]:
Функции состоят из трех основных частей:
- Раздел input "ввода". Здесь мы определяем его входные параметры:
input r0 as u32.public;
input r1 as u32.private;
Все в инструкциях Aleo заявляются/хранятся внутри регистра с типом (i8
,field
,bool
, и т.д.) и параметром видимости (public
или private
). Регистры имеют имена r0
, r1
, ..., rn
. Более подробно о регистрах, типах и видимости мы поговорим позже.
Итак, в данном случае мы используем r0
и r1
для хранения входов, передаваемых в последовательном порядке в программу, в виде значений u32
, где мы можем хранить 32-битные беззнаковые целые числа для выполнения нашей функции sum.
- Раздел instructions "инструкций". Следующий раздел, заключающий в себе ядро нашей функции. Здесь мы указываем количество инструкций Aleo, необходимых для того, чтобы наша программа сделала то, что мы хотим. Например, выполнение функции add:
add r0 r1 into r2;
За каждой инструкцией Aleo следуют ее входные параметры с заданными типами, а результат сохраняется в регистре into. Вы можете найти все доступные инструкции Aleo здесь.
- Раздел output "вывода". Подобно разделу ввода, раздел вывода делает то же самое для вывода программы. Это возвращаемое значение функции.
output r2 as u32.private;
Разбираемся в некоторых концепциях
Типы
Aleo является типизированным языком. В настоящее время доступны следующие типы данных:
Boolean
Field
Group
I8
I16
I32
I64
I128
U8
U16
U32
U64
U128
Scalar
Определены пользователем:
Interface
Record
Registers "Регистры"
Регистр - это место, где вы храните данные, чтобы затем иметь возможность их изменять.
Interfaces "Интерфейсы"
Interfaces - это определяемые пользователем структуры данных. Они очень похожи на традиционные структуры в обычных языках программирования. Интерфейсы можно хранить в регистрах, как и любые другие типы данных 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")
Records "Записи"
Record - это фундаментальная структура данных для кодирования пользовательских активов и состояния приложения. Они очень похожи на интерфейсы, но у них есть два необязательных параметра:
record token:
owner as address.private
gates as u64.private
owner
означает адрес 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.
Ваша первая программа Aleo: Выполнение перевода
Рассмотрим эту программу:
// The 'foo.aleo' program.
program foo.aleo;
record token:
owner as address.private;
gates as u64.private;
amount as u64.private;
function transfer_amount:
// sender token record
input r0 as token.record;
// receiver address
input r1 as address.private;
// amount to transfer
input r2 as u64.private;
// final balance of sender
sub r0.amount r2 into r3;
// final balance of receiver
add 0u64 r2 into r4;
// sender token record after the transfer
cast r0.owner r0.gates r3 into r5 as token.record;
// receiver token record after the transfer
cast r1 0u64 r4 into r6 as token.record;
// sender new token record
output r5 as token.record;
// receiver new 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
и r6
. Наконец, обе записи отправляются из функции с помощью инструкции output.
Для запуска этой функции первым параметром является входная запись программы. Формат этого параметра такой же, как и для интерфейсных типов:
{
owner: aleo1x5nz5u4j50w482t5xtqc3jdwly9s8saaxlgjz0wvmuzmxv2l5q9qmypx09.private,
gates: 0u64.private,
amount: 50u64.private
}
где:
- owner: публичный адрес программы, как указано в
development.address
файла build/program.json. - gates: параметры, которые есть у записи.
- другие параметры: зависят от самой программы (в данном примере мы использовали параметр amount со значением 50).
Запустим функцию transfer_amount
(если вы следуете за нами, не забудьте использовать адрес, найденный в program.json для поля owner):
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. Это вводит некоторые ограничения, которых нет в обычных языках программирования. Например, при использовании тернарного оператора для наличия ответвления в потоке программы необходимо заранее иметь оба результата (т.е. результат каждого ответвления должен быть вычислен и известен до выполнения тернарного оператора). Логическое сравнение должно быть между регистрами, а не между возвратными значениями вызываемых функций.
Источник - https://www.entropy1729.com/getting-started-aleo-instructions/
Перевел - egormajj#0340
Top comments (0)