Разработка схемы Halo2: технический обзор Sin7Y (20)

Разработка схемы Halo2: технический обзор Sin7Y (20)

8 марта 2022 г.

В предыдущей статье мы обсуждали, как использовать halo2 для разработки схем. В этой статье мы проиллюстрируем, на что нужно обратить внимание при разработке схем. При написании этой статьи мы ссылались на код halo2, версия f9b3ff2aef09a5a3cb5489d0e7e747e9523d2e6e. Прежде чем мы начнем, давайте рассмотрим наиболее важное содержание, а именно определение схемы.


```javascript


// источник/plunk/схема.rs


черта паба Circuit {


тип Конфигурация: Клон;


тип FloorPlanner: FloorPlanner;


fn без_свидетелей(&self) -> Self;


fn configure(meta: &mut ConstraintSystem) -> Self::Config;


fn синтез(&self, config: Self::Config, layouter: impl Layouter) -> Result<(), Error>;


Как обычно, при разработке схем нам нужно реализовать этот трейт:


  • Конфиг

  • Он определяет ограничения схемы, в основном определяемые функцией create_gate().

  • Планировщик этажей

  • Это стратегия планирования схемы, реализующая функцию synthesize(), которая синтезирует схему, используя предоставленную конфигурацию, константы и присвоение.

  • без_свидетелей

  • Это схема без свидетелей, обычно использующая функцию Self::default().

  • настроить

  • Это процесс разработки описания схемы ворот и применимых к ней ограничений.

  • синтезировать

  • Он присваивает значение Layouter на основе предоставленной конфигурации, а ядро ​​использует свою функцию Assin_region(), которая использует замыкание, а параметром замыкания является Region.

В результате предыдущего определения разработка схемы halo2 состоит из двух важнейших функций: настройки и синтеза. Первый устанавливает ворота и определяет ограничения, а второй назначает ограничениям данные-свидетели и общедоступные данные.
Давайте подробно рассмотрим, что происходит во время разработки схемы в этой статье. В качестве отправной точки возьмем официальный простой пример.


Настроить


В соответствии с объявлением функции configure, при определении схемы ConstraintSystem` будет изменен и возвращен вConfig` для последующего использования.


```javascript


// примеры/simple-example.rs


fn MyCircuit::configure(meta: &mut ConstraintSystem) -> Self::Config {


// Мы создаем два столбца рекомендаций, которые FieldChip использует для ввода-вывода.


пусть совет = [meta.advice_column(), meta.advice_column()];


// Нам также нужен столбец экземпляра для хранения общедоступных входных данных.


пусть экземпляр = meta.instance_column();


// Создаем фиксированный столбец для загрузки констант.


пусть константа = meta.fixed_column();


meta.enable_equality (экземпляр);


meta.enable_constant (константа);


для столбца в &advice {


meta.enable_equality(*столбец);


пусть s_mul = meta.selector();


meta.create_gate("mul", |meta| {


let lhs = meta.query_advice(advice[0], Rotation::cur());


пусть rhs = meta.query_advice(advice[1], Rotation::cur());


let out = meta.query_advice(advice[0], Rotation::next());


пусть s_mul = meta.query_selector(s_mul);


vec![s_mul * (lhs * rhs - выход)]


Конфигурация поля {


совет,


пример,


с_мул,


Хорошо, давайте углубимся в это и проанализируем это. Конфигурация выполнила следующие действия:


  1. Создаются столбцы «Совет», «Экземпляр» и «Фиксированный» (об их назначении и значении см. в предыдущих статьях).

  • advice_column(), instance_column(), fixed_column() имеют аналогичную функцию, которая заключается в создании соответствующего типа столбца совета/экземпляра/фиксированного, добавление счетчика соответствующего столбца в `ConstraintSystem на 1, а затем вернуть новый столбец.

  1. Затем вызывается функция enable_equality() из ConstraintSystem, чтобы поместить столбцы instance и advice, а затем вызывается функция enable_constant(), чтобы поместить constant.

  • Эти две функции используются для включения возможности обеспечения равенства ячеек в этом столбце.

  1. После этого вызывается функция selector для генерации селектора.

  1. Самое главное, вызывается функция create_gate из ConstraintSystem и замыкание с &mut VirtualCells в качестве параметра для создания шлюза.

  • В этом закрытии вызывается функция query_advice VirtualCells. Столбец совета и селектор, сгенерированные выше, вводятся, а столбец и поворот используются для построения ячейки. В то же время генерируется выражение со столбцом и поворотом в качестве параметров. Наконец, возвращается ограничение, в котором преобладает выражение. Следует отметить, что функция query_advice() не только создает ячейку, но также помещает столбец и поворот в ConstraintSystem. Таким образом, ячейка и cs связаны столбцом и вращением.

  • В функции create_gate ограничения и ячейки, сгенерированные в замыкании, используются для построения Gate и сохраняются в массиве gates cs.

  1. Наконец, вернитесь к конфигурации для дальнейшего использования.

Подводя итог, сначала создайте соответствующий столбец. Затем создайте селектор и используйте функцию create_gate для создания ячеек и ограничений из столбцов и селекторов. Наконец, ограничения и ячейки используются для создания ворот, которые в конце сохраняются.


Одним словом, configure генерирует ограничения.


Синтезировать


Схема инициализируется с помощью свидетеля и общедоступных данных. В функцию синтеза вводятся данные. Поскольку в официальных примерах много структурированных кодов, мы расширим эти коды в функции синтеза в этой статье следующим образом:


```javascript


// примеры/simple-example.rs


fn синтез(&self, config: Self::Config, layouter: impl Layouter) -> Result<(), Error> {


пусть a = layouter.assign_region(


|| "загрузить",


|мут регион| {


область, край


.assign_advice(


|| "частный ввод",


конфиг.совет[0],


0,


|| self.a.ok_or(Ошибка::Синтез),


.карта (число)


пусть b = layouter.assign_region(


|| "нагрузка б",


|мут регион| {


область, край


.assign_advice(


|| "частный ввод",


конфиг.совет[0],


0,


|| self.b.ok_or(Ошибка::Синтез),


.карта (число)


пусть константа = layouter.assign_region(


|| "постоянная нагрузка",


|мут регион| {


область, край


.assign_advice_from_constant(|| "постоянное значение", config.advice[0], 0, self.constant)


.карта (число)


пусть ab = layouter.assign_region(


|| "а*б",


|mut регион: Region<'_, F>| {


config.s_mul.enable(&mut region, 0)?;


a.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?;


b.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?;


пусть значение = a.0.value().and_then(|a| b.0.value().map(|b| a * b));


область, край


.assign_advice(


|| "левый * правый",


конфиг.совет[0],


1,


|| value.ok_or(Ошибка::Синтез),


.карта (число)


пусть ab2 = ab.clone();


пусть absq = layouter.assign_region(


|| "аб * аб",


|mut регион: Region<'_, F>| {


config.s_mul.enable(&mut region, 0)?;


ab.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?;


ab2.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?;


пусть значение = ab.0.value().and_then(|a| ab2.0.value().map(|b| a * b));


область, край


.assign_advice(


|| "левый * правый",


конфиг.совет[0],


1,


|| value.ok_or(Ошибка::Синтез),


.карта (число)


пусть c = layouter.assign_region(


|| "константа * абск",


|mut регион: Region<'_, F>| {


config.s_mul.enable(&mut region, 0)?;


константа.0.copy_advice(|| "lhs", &mut region, config.advice[0], 0)?;


absq.0.copy_advice(|| "rhs", &mut region, config.advice[1], 0)?;


пусть значение = константа.0.значение().and_then(|a|absq.0.value().map(|b| a * b));


область, край


.assign_advice(


|| "левый * правый",


конфиг.совет[0],


1,


|| value.ok_or(Ошибка::Синтез),


.карта (число)


layouter.constrain_instance(c.0.cell(), config.instance, 0)


Давайте начнем с изучения кода, который использует a, b и константы для указания параметров. assign_region – это функция черты Layouter. Прежде чем углубляться в эту функцию, давайте рассмотрим черту Layouter .


Макетировщик


Layouter чип-агностик. Как упоминалось ранее в нашем руководстве по разработке Halo 2, чип является сердцем любой схемы. В результате компоновщику все равно, для чего используется чип или как он определяется. То есть лейаутер и чип не связаны. Компоновка — это абстрактная характеристика стратегии, которой схемы присваивают значение. Что это означает? То есть компоновщик используется для назначения цепей, таких как индексы строк.


Он определяется следующим образом:


```javascript


// источник/схема.rs


pub Layouter {


тип Root: Layouter;


fn assign_region(&mut self, имя: N, назначение: A) -> Результат


куда


A: FnMut(Region<'_, F>) -> Result,


N: Fn() -> NR,


NR: в ;


fn assign_table(&mut self, имя: N, назначение: A) -> Результат<(), Ошибка>


куда


A: FnMut(Table<'_, F>) -> Результат<(), Ошибка>,


N: Fn() -> NR,


NR: в ;


fn constrain_instance(


&мутировать себя,


ячейка: ячейка,


столбец: Столбец<Экземпляр>,


ряд: использование,


) -> Результат<(), Ошибка>;


fn get_root(&mut self) -> &mut Self::Root;


fn push_namespace(&mut self, name_fn: N)


куда


NR: в ,


N: FnOnce() -> NR;


fn pop_namespace(&mut self, gadget_name: Option);


fn namespace(&mut self, name_fn: N) -> NamespacedLayouter<'_, F, Self::Root>


куда


NR: в ,


N: FnOnce() -> NR,


self.get_root().push_namespace(name_fn);


NamespacedLayouter(self.get_root(), PhantomData)


Давайте рассмотрим каждую функцию по очереди:


  • Начнем с самых простых функций, связанных с корнем/пространством имен. Эти функции, связанные с пространством имен, используются для идентификации текущего макета.

  • Функция `constrain_instance используется для ограничения значения строки экземпляра столбца в ячейке и абсолютной позиции.

  • Основными функциями являются assign_region и assign_table, которые, как следует из их названий, используются для назначения регионов и таблиц соответственно. Это также основная функция черты. Черта компоновщика используется для связывания значений с областями и таблицами. Мы обсудим их позже.

Давайте исследуем функцию assign_region. Эта функция получает замыкание string и FnMut(Region<'_, F>) -> Result<AR, Error> и содержит &mut self. Давайте рассмотрим определение этого важного региона:


```javascript


pub struct Region<'r, F: Field> {


регион: &'r mut dyn layouter::RegionLayouter,


Видно, что Region — это инкапсуляция для RegionLayouter, который является Layouter для Region. Прежде чем представить RegionLayouter, мы не должны вдаваться в подробности. Давайте рассмотрим конкретный случай Layouter. В простом примере SingleChipLayouter реализует черту Layouter.


```javascript


// src/схема/floor_planner/single_pass.rs


pub struct SingleChipLayouter<'a, F: Field, CS: Assignment + 'a> {


cs: &'a mut CS,


константы: Vec>,


/// Сохраняет начальную строку для каждого региона.


регионы: Vec,


/// Сохраняет первую пустую строку для каждого столбца.


столбцы: HashMap,


/// Сохраняет фиксированные столбцы таблицы.


table_columns: Vec,


_marker: PhantomData,


fn SingleChipLayouter::assign_region(&mut self, имя: N, mut присваивание: A) -> Результат


куда


A: FnMut(Region<'_, F>) -> Result,


N: Fn() -> NR,


NR: в ,


пусть region_index = self.regions.len();


// 1. Получить форму области.


let mut shape = RegionShape::new(region_index.into());


пусть регион: &mut dyn RegionLayouter = &mut shape;


присвоение(регион.в())?;


// 2. Разметить этот регион. Здесь реализуем самый простой подход: позиционируем


// область, начинающаяся с самой ранней строки, для которой ни один из столбцов не используется.


пусть mut region_start = 0;


для столбца в &shape.columns {


region_start = cmp::max(region_start, self.columns.get(column).cloned().unwrap_or(0));


self.regions.push(region_start.into());


// Обновить информацию об использовании столбца.


для столбца в shape.columns {


self.columns.insert (столбец, region_start + shape.row_count);


// 3. Назначить ячейки региона.


self.cs.enter_region(имя);


let mut region = SingleChipLayouterRegion::new(self, region_index.into());


пусть результат = {


пусть регион: &mut dyn RegionLayouter = &mut region;


присвоение (регион.в())


пусть const_to_assign = region.constants;


self.cs.exit_region();


// 4. Назначить константы. Для простого планировщика этажей мы назначаем константы по порядку в


// первый столбец константы.


если self.constants.is_empty() {


если !constants_to_assign.is_empty() {


вернуть Err(Error::NotEnoughColumnsForConstants);


} еще {


пусть const_column = self.constants[0];


пусть next_constant_row = я


.столбцы


.entry(Столбец::::from(constants_column).into())


.или_по умолчанию();


for (константа, совет) в константах_to_assign {


self.cs.assign_fixed(


|| формат!("Константа({:?})", константа.оценить()),


константы_столбец,


  • следующая_константа_строка,

|| Хорошо (постоянно),


self.cs.copy(


константы_столбца.в(),


  • следующая_константа_строка,

колонка советов,


self.regions[advice.region_index] + совет.row_offset,


*next_constant_row += 1;


Хорошо(результат)


Как описано в предыдущем коде и комментариях, функции assign_region в SingleChipLayouter выполнили следующее:


  1. Возьмите количество регионов текущего SingleChipLayouter, чтобы построить RegionShape, и введите этот RegionShape в замыкание. На этом этапе выполните закрытие, и RegionShape будет изменен.

  • Взгляните на это закрытие. Он вызывает функцию assign_advice региона с вводом мутируемого региона:

```javascript


// источник/схема.rs


pub fn Region::assign_advice<'v, V, VR, A, AR>(


&'v мут себя,


аннотация: А,


столбец: Столбец<Совет>,


смещение: использование,


мут на: В,


) -> Результат, Ошибка>


куда


V: FnMut() -> Результат + 'v,


для <'vr> Assigned: From<&'vr VR>,


А: Fn() -> АР,


AR: в ,


пусть значение mut = None;


пусть ячейка =


селф.регион


.assign_advice(&|| annotation().into(), столбец, смещение, &mut || {


Пусть V = к () ?;


пусть value_f = (&v).into();


значение = некоторые (v);


Хорошо (значение_f)


Хорошо (AssignedCell {


стоимость,


клетка,


_marker: фантомные данные,


Эта функция будет вызывать assign_advice из RegionLayouter. Вышеупомянутый RegionShape реализует черту RegionLayouter, поэтому в конце используется функция assign_adviceRegionShape`.


```javascript


// src/схема/layouter.rs


fn RegionShape::assign_advice<'v>(


&'v мут себя,


_: &'v (dyn Fn() -> String + 'v),


столбец: Столбец<Совет>,


смещение: использование,


_to: &'v mut (dyn FnMut() -> Result, Error> + 'v),


) -> Результат<Ячейка, Ошибка> {


self.columns.insert(Column::::from(column).into());


self.row_count = cmp::max(self.row_count, смещение + 1);


Хорошо (ячейка {


регион_индекс: self.region_index,


row_offset: смещение,


столбец: столбец.в(),


На этом этапе RegionShape запишет столбец, рекомендация по настройке [0] и сравнит смещение + 1 и количество строк, обновив количество строк.


  1. Сравните все столбцы в RegionShape, найдите и обновите начальные значения столбцов и областей в SingleChipLayouter.

  1. Назначьте значения ячейкам региона, чтобы создать SingleChipLayouterRegion, который будет преобразован в RegionLayouter, а затем вызовите функцию замыкания. Единственным изменением шага 1 является реализация функции assign_advice в SingleChipLayouterRegion.

```javascript


// src/схема/floor_planner/single_pass.rs


fn SingleChipLayouterRegion::assign_advice<'v>(


&'v мут себя,


аннотация: &'v (dyn Fn() -> String + 'v),


столбец: Столбец<Совет>,


смещение: использование,


to: &'v mut (dyn FnMut() -> Result, Error> + 'v),


) -> Результат<Ячейка, Ошибка> {


self.layouter.cs.assign_advice(


аннотация,


столбец,


self.layouter.regions[self.region_index] + смещение,


к,


Хорошо (ячейка {


регион_индекс: self.region_index,


row_offset: смещение,


столбец: столбец.в(),


В этой функции можно увидеть, что принята функция assign_advice из Assignment в SingleChipLayouter. В этом примере черта Assignment реализована с помощью MockProver.


```javascript


// источник/dev.rs


fn MockProver::assign_advice(


&мутировать себя,


_: А,


столбец: Столбец<Совет>,


ряд: использование,


к: В,


) -> Результат<(), Ошибка>


куда


V: FnOnce() -> Результат,


VR: В <Назначено>,


A: FnOnce() -> AR,


AR: в ,


если !self.usable_rows.contains(&row) {


вернуть Err (Ошибка :: not_enough_rows_available (self.k));


если позволить Some(region) = self.current_region.as_mut() {


region.update_extent (column.into (), строка);


region.cells.push((column.into(), row));


*себя


.совет


.get_mut(столбец.index())


.and_then(|v| v.get_mut(строка))


.ok_or(Ошибка::BoundsFailure)? = CellValue::Assigned(to()?.into().evaluate());


Ok(())


Он определит, превышает ли строка в этом регионе количество доступных строк. Затем current_region будет изменен. Используемые столбцы и строки записываются в регион. И значение соответствующего столбца/строки в совете изменяется на to. Таким образом, именно MockProver хранит данные рекомендаций. Система ограничений, ранее использовавшаяся в configure, также является полем MockProver. Короче говоря, ссылки MockProver настроены на синтез.


Для разработчиков схем важно, как создавать и разрабатывать эффективные и безопасные схемы, мало обращая внимания на фундаментальную архитектуру схемы. Мы хотим знать, как строить столбцы и строки, а также как создавать вентили на этапе настройки. Кроме того, следует ли последовательно присваивать значения столбцам и строкам на этапе синтеза.


В пример кода внесены следующие изменения.


```javascript


// простой пример.rs


главная функция () {


let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap();


println!("{:?}", прувер);


// assert_eq!(prover.verify(), Ok(()));


Таким образом, MockProver можно распечатать, и результат совета следующий.


```javascript


совет: [


[Установленное (0x0000000000000000000000000000000000000000000000000000000000000002), Assigned (0x0000000000000000000000000000000000000000000000000000000000000003), Assigned (0x0000000000000000000000000000000000000000000000000000000000000007), Assigned (0x0000000000000000000000000000000000000000000000000000000000000002), Assigned (0x0000000000000000000000000000000000000000000000000000000000000006), Assigned (0x0000000000000000000000000000000000000000000000000000000000000006), Assigned (0x0000000000000000000000000000000000000000000000000000000000000024), Assigned (0x0000000000000000000000000000000000000000000000000000000000000007), Assigned (0x00000000000000000000000000000000000000000000000000000000000000fc), Неназначенный, Яд ( 10), Яд(11), Яд(12), Яд(13), Яд(14), Яд(15)


[Неназначенный, Неназначенный, Неназначенный, Assigned (0x0000000000000000000000000000000000000000000000000000000000000003), Неназначенный, Assigned (0x0000000000000000000000000000000000000000000000000000000000000006), Неназначенный, Assigned (0x0000000000000000000000000000000000000000000000000000000000000024), Неназначенный, Неназначенный, Яд (10), Яд (11), Яд (12), Яд (13), Яд(14), Яд(15)


Как видите, в совете две колонки, которые можно визуализировать следующим образом.



Строки с 1 по 3 — все данные загрузки частных/константных данных загрузки, которые помещаются в первый столбец. В строке 4 вызовите mul, получите данные из первого столбца и второго столбца строки, и сохраните результат расчета в первом столбце следующей строки, то есть в строке 5. Ниже приведены аналогичные вычисления mul.


Наконец, `constrain_instance используется для ограничения результата вычисления столбцом экземпляра, чтобы проверить, равен ли рассчитанный 0xfc общедоступному вводу.


Мы видим, что результат instance в MockProver:


```javascript


пример: [


0x0000000000000000000000000000000000000000000000000000000000000000fc,


0x0000000000000000000000000000000000000000000000000000000000000000000,


0x0000000000000000000000000000000000000000000000000000000000000000000,


0x0000000000000000000000000000000000000000000000000000000000000000000,


0x0000000000000000000000000000000000000000000000000000000000000000000,


0x0000000000000000000000000000000000000000000000000000000000000000000,


0x0000000000000000000000000000000000000000000000000000000000000000000,


0x0000000000000000000000000000000000000000000000000000000000000000000,


0x0000000000000000000000000000000000000000000000000000000000000000000,


0x0000000000000000000000000000000000000000000000000000000000000000000,


0x0000000000000000000000000000000000000000000000000000000000000000000,


0x0000000000000000000000000000000000000000000000000000000000000000000,


0x0000000000000000000000000000000000000000000000000000000000000000000,


0x0000000000000000000000000000000000000000000000000000000000000000000,


0x0000000000000000000000000000000000000000000000000000000000000000000,


0x0000000000000000000000000000000000000000000000000000000000000000000


Использованная литература


Репозиторий Halo2 – https://github.com/zcash/halo2
Книга Halo2 – https://zcash.github.io/halo2
Книга Halo2 на китайском языке – https://trapdoor-tech .github.io/halo2-book-chinese



Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE