구조체(struct)를 사용하여 데이터와 메서드를 묶는다.
열거형(enum)을 사용해 여러 상태 정의
trait = 인터페이스와 비슷한 개념, 행동(메소드)정의, 동작을 추상화하는 역할 "이 타입이 이 기능할 수 있다"
pub enum Operator {
Add,
Sub,
Mul,
}
연산자정의
T와 E의 의미T: 성공 시 반환할 값의 타입. 성공적인 연산이 완료되면 이 타입의 값을 Ok(T)로 감싸서 반환함E: 에러 발생 시 반환할 값의 타입. 에러가 발생하면 이 타입의 값을 Err(E)로 감싸서 반환함.trait을 사용하면 여러 타입에 대해 동일한 메소드 정의 가능
-> 코드 중복 줄이고, 여러 타입에서 공통적으로 사용하는 행동 정의 가능, 같은 메소드를 타입에 따라 다르게 구현 가능(polymorphism)
왜 이게 중요한가?
-> rust는 컴파일 시점에 타입을 체크하는데, trait을 사용하면 각 타입이 특정 행동을 구현해야 한다는 강제성을 부여한다.
-> 런타임 에러가 줄어든다
match token {
"+" => Ok(Operator::Add),
"-" => Ok(Operator::Sub),
"*" => Ok(Operator::Mul),
_ => Err(ParserError::InvalidOperator),
}
match : rust의 제어 흐름 구문 중 하나
주어진 값을 여러 패턴과 비교하여 해당하는 경우에 맞는 코드를 실행한다
https://rinthel.github.io/rust-lang-book-ko/ch06-02-match.html
_ : 와일드카드
클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법
struct vs object
구조체(struct)는 객체의 ==데이터 속성== 같은 것으로 보면 된다.
struct는 일반적으로 정적 타입 언어에서 사용되며, 컴파일 타임에 타입이 고정된다. 반면 object는 동적 타입 언어에서도 사용되며, 런타임에 생성될 수 있다.struct는 기본적으로 데이터 구조로 사용되지만, object는 상태(데이터)와 행동(메서드)을 모두 포함하는 개념입니다.struct가 주로 데이터 구조로 사용되며, OOP를 지원하지 않는 구조적 프로그래밍 방식입니다. 반면 Java나 Python 같은 언어에서는 OOP의 주요 구성 요소로서 object를 사용합니다struct vs tuple
튜플과 유사하게, 구조체의 구성요소들은 각자 다른 타입을 지닐 수 있습니다. 그러나 튜플과는 다르게 각 구성요소들은 명명할 수 있어 값이 의미하는 바를 명확하게 인지할 수 있습니다. 구조체는 각 구성요소들에 명명을 할 수 있다는 점 덕분에 튜플보다 유연하게 다룰 수 있습니다. 구조체 내의 특정 요소 데이터 명세를 기술하거나, 접근할 때 순서에 의존할 필요가 없기 때문입니다.
https://rinthel.github.io/rust-lang-book-ko/ch05-00-structs.html
rust에서는 기본적으로 변수를 선언하면 불변임(const), 그래서 mut를 선언해줘야 나중에 바꿀 수 있음(쓰기 가능)
https://rinthel.github.io/rust-lang-book-ko/ch03-01-variables-and-mutability.html
let a = match tokens.next() {
Some(a) => Operand::from_token(a),
None => Err(ParserError::NotEnoughInputs),
}?;
some(a) : a가 있으면
=> Operand::from_token(a) from_token(a)를 호출하여 오퍼랜드로 변환
?는 Result 타입을 자동으로 처리
Some :
정의 : Option 열거형의 변형 중 하나, 값이 존재할 때 사용
형태 : Option<T>의 두가지 변형
Ok :
정의 : Result 열거형의 변형 중 하나
Ok(T), Err(E)
계산의 중간 결과 저장하는 열
-> 회로의 각 단계에서 어떤 값이 계산되는지 보여줌, witness가 저장됨
prover가 이 값을 사용하여 계산을 진행함
최종 출력 값을 포함하는 열. 회로에서 검증할 데이터 또는 공개 입력 저장
[[Halo2]]
main.rs -> zk_calculator.rs -> calculator_circuit.rs ( run-> parse -> run_circuit -> a, b, c assign , circuit(a,b,operator)-> c (=operator) is assigned to public input -> prover (MockProver의 run 함수 실행 parameter(k, circuit, public_inputs))) -> MockProver함수 내부 config -> CalculatorCircuit 인스턴스 실행 -> configure 메서드 호출 -> advice 및 instance column 설정 (MockProver 내부) -> synthesize함수 call (calculator.rs) -> arithmetic_chip 인스턴스 설정 및 실행 -> Operator::Add에서 add함수 실행하며 add.rs로 이동 -> AddInstructions에서 함수를 implementation해놓음 -> arithmetic_chip.expose_public -> FloorPlanner에서 synthesize -> Prover ok -> Prover.verify -> run(zkCalculator) -> main(main.rs)
끝
ex ) 콘솔에 1 + 2 입력했다고 가정
zk_calculator.rs
io::stdin().read_line(&mut input).expect("io failed");
입력을 기다림
self.parse(input).expect("parse failed");
parse함수 호출
fn parse(&mut self, input: String) -> Result<(), ParserError> {
// split input by whitespace
let mut tokens = input.split_whitespace();
// parse into operand a or bubble up error
let a = match tokens.next() {
Some(a) => Operand::from_token(a),
None => Err(ParserError::NotEnoughInputs),
}?;
// parse into operator or bubble up error
let operator = match tokens.next() {
Some(op) => Operator::from_token(op),
None => Err(ParserError::NotEnoughInputs),
}?;
// parse into operand or bubble up error
let b = match tokens.next() {
Some(b) => Operand::from_token(b),
None => Err(ParserError::NotEnoughInputs),
}?;
// if there are more tokens remaining, something went wrong, so we
// bubble up an error about it
if tokens.next().is_some() {
return Err(ParserError::TooManyInputs);
}
// mutate the ZkCalculator
self.operation = Some(Operation { a, operator, b });
// return ok
Ok(())
}
입력 값에 대해 whitespace 기준으로 값들을 나눔
첫 값은 a로 할당
some(a) : a가 있으면
=> Operand::from_token(a) from_token(a)를 호출하여 오퍼랜드로 변환
그 다음 값은 operator로 할당
그 다음 값은 b로 할당
다음 값이 있다면 TooManyInputs 에러 반환
마지막으로 모든 값을 Operation에 저장
pub struct ZkCalculator {
/// Optionally stores the Operation to execute.
operation: Option<Operation>,
}

pub trait AddInstructions<F: FieldExt>: Chip<F> {
/// Numeric variable.
type Num;
/// Addition instruction.
/// Takes two inputs and returns the sum.
fn add(
&self,
layouter: &mut impl Layouter<F>,
a: Self::Num,
b: Self::Num,
) -> Result<Self::Num, Error>;
}
Addchip이 가져야할 기능 정의 (여기서는 add함수)
pub struct AddConfig {
/// Advice column for `input_a` and `output`.
a: Column<Advice>,
/// Advice column for `input_b`.
b: Column<Advice>,
/// Addition Selector.
sel_add: Selector,
}
칩의 설정
pub struct AddChip<F: FieldExt> {
/// Addition configuration.
config: AddConfig,
/// Placeholder data.
_marker: PhantomData<F>,
}
덧셈 칩 정의
덧셈 칩 구현
/// Addition chip implementation.
impl<F: FieldExt> AddChip<F> {
/// Construct AddChip and return.
pub fn construct(
config: <Self as Chip<F>>::Config,
_loaded: <Self as Chip<F>>::Loaded,
) -> Self {
Self {
config,
_marker: PhantomData,
}
}
/// Configure AddChip and return the Config.
pub fn configure(
meta: &mut ConstraintSystem<F>,
a: Column<Advice>,
b: Column<Advice>,
) -> <Self as Chip<F>>::Config {
// enable equality on columns
meta.enable_equality(a);
meta.enable_equality(b);
// get selector
let sel_add = meta.selector();
// define the addition gate
meta.create_gate(
// gate name
"add",
// gate logic
|meta| {
// query advice value from a on the current rotation
let lhs = meta.query_advice(a, Rotation::cur());
// query advice value from b on the current rotation
let rhs = meta.query_advice(b, Rotation::cur());
// query advice value from c on the next rotation
let out = meta.query_advice(a, Rotation::next());
// query selector
let sel_add = meta.query_selector(sel_add);
// return an iterable of `selector * (a + b - c)`
// if `sel_add == 0`, then lhs, rhs and out are not constrained.
// if `sel_add != 0`, then `lhs + rhs = out` is contrained.
vec![sel_add * (lhs + rhs - out)]
},
);
// return config
AddConfig { a, b, sel_add }
}
}
impl<F: FieldExt> AddChip<F>:
impl<F: FieldExt> Chip<F> for AddChip<F>:
트레이트 구현.
chip 트레이트를 AddChip 타입에 맞게 구현한 것.
Addchip이 chip트레이트를 만족하도록 만들고,
트레이트가 요구하는 loaded 매서드를 AddChip에 맞게 구현한다.
AddChip 선언:
AddChip은 덧셈 기능을 가진 칩을 만들기 위해 새로 정의된 구조체입니다.AddConfig라는 설정(config)을 사용하여 덧셈에 필요한 정보를 저장합니다.Chip 트레이트 구현:
impl<F: FieldExt> Chip<F> for AddChip<F>: 이 구문은 ==AddChip이 Halo2에서 제공하는 Chip 트레이트를 따르도록 구현합니다.AddChip이 Chip 트레이트의 요구사항인 config와 loaded 메서드를 구현합니다.AddChip에 추가 기능 구현:
impl<F: FieldExt> AddChip<F>: 이 구문은 AddChip에 덧셈 연산을 위한 메서드들을 추가로 정의하는 부분입니다.construct, configure 같은 메서드를 통해 칩을 설정하고 사용할 수 있도록 준비합니다.AddChip은 Halo2의 Chip 트레이트를 구현하여, Halo2에서 요구하는 방식으로 덧셈을 수행하는 칩을 만들었습니다. Halo2의 Chip 트레이트는 칩이 어떻게 동작해야 하는지 정의하고 있고, AddChip은 이 트레이트를 구현하여 덧셈 기능을 가진 칩으로 동작하게 만든 것입니다.
Chip 트레이트: Halo2 라이브러리가 제공하는 기본적인 칩의 인터페이스(규격)입니다. 모든 칩은 이 트레이트를 구현해야 하며, 칩의 설정(Config)과 로드된 데이터(Loaded)를 다루는 규칙을 제공합니다.
AddChip 같은 사용자 정의 칩이 Halo2에서 정상적으로 동작하려면, Halo2가 요구하는 Chip 인터페이스에 맞춰서 칩을 구현해야 합니다.AddChip: 이것은 사용자가 정의한 칩이에요. 이 칩은 특정 연산, 즉 덧셈을 수행하는 역할을 합니다. 이를 Halo2에서 요구하는 Chip 트레이트에 맞게 맞춤화한 것이고요.
AddChip이 Chip의 규격을 따르도록 설정(Config)과 초기화(Loaded) 부분을 구현하는 것.AddInstructions 트레이트: 이 트레이트는 실제로 칩이 제공할 기능(덧셈 연산)을 정의하는 인터페이스입니다. AddChip이 이 트레이트를 구현함으로써, 덧셈 연산을 수행하는 논리를 칩에 맞게 정의합니다.
AddChip에 맞게 덧셈 연산이 어떤 방식으로 이루어질지 규정하는 것.결국, 두 가지 단계를 거치면 됩니다:
Chip 트레이트에 맞춰서 AddChip을 구현해, 회로에서 사용할 수 있는 칩으로 만든다.AddChip)에 맞춰서 구체적인 덧셈 연산을 수행하는 로직을 AddInstructions 트레이트로 구현한다.회로의 구성 요소(변수, 게이트)를 어떻게 배치하고, 연결할지 정의하는 역할