From baf560ae60f24c2cac6aa10a51919a8c17cf8cbe Mon Sep 17 00:00:00 2001 From: victorkstarkware Date: Thu, 30 May 2024 13:54:29 +0300 Subject: [PATCH 1/2] added coset_vanishing() to constraints and add() to circlepointsecure --- README.md | 12 ++ src/circle_secure/bitcoin_script.rs | 130 ++++++++++++++++++- src/constraints/bitcoin_script.rs | 188 +++++++++++++++++++--------- 3 files changed, 264 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 337ec6c..f119aba 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,9 @@ This repository includes Bitcoin script implementations of various cryptographic - **CirclePoint over QM31** * implementation of double of a circle point over QM31. * implementation of drawing a random point on the circle over QM31, which is useful for OODS. + * implementation of addition of a circle point over QM31. +- **Constraints on the circle curve over QM31** + * implementation of `coset_vanishing()` and `pair_vanishing()`. - **Fiat-Shamir Transcript** * aka "channel", which is the name used in Starkware's [stwo](https://github.com/starkware-libs/stwo) library. * absorbing commitments and QM31 elements through `OP_CAT + OP_SHA256`. @@ -51,6 +54,15 @@ These performance numbers are obtained from `cargo test -- --nocapture` over com - **CirclePoint over QM31** * CirclePointSecure.double_x() = 13505 bytes * CirclePointSecure.get_random_point() = 40546 bytes + * CirclePointSecure.add() = 40542 bytes + * CirclePointSecure.add_x_only() = 26791 bytes +- **Constraints on the circle curve over QM31** + * Constraints.pair_vanishing() = 26932 bytes + * Constraints.coset_vanishing(log_size=5) = 80827 bytes + * Constraints.coset_vanishing(log_size=6) = 94332 bytes + * Constraints.coset_vanishing(log_size=7) = 107837 bytes + * Constraints.coset_vanishing(log_size=8) = 121342 bytes + * Constraints.coset_vanishing(log_size=9) = 134847 bytes - **Fiat-Shamir Transcript** * Channel.absorb_commitment = 2 bytes * Channel.absorb_qm31() = 9 bytes diff --git a/src/circle_secure/bitcoin_script.rs b/src/circle_secure/bitcoin_script.rs index 703cd8e..c6fb53a 100644 --- a/src/circle_secure/bitcoin_script.rs +++ b/src/circle_secure/bitcoin_script.rs @@ -1,9 +1,7 @@ use crate::treepp::*; use num_traits::One; use rust_bitcoin_m31::{ - m31_add_n31, m31_sub, push_m31_one, push_n31_one, push_qm31_one, qm31_double, qm31_dup, - qm31_equalverify, qm31_from_bottom, qm31_mul, qm31_neg, qm31_roll, qm31_rot, qm31_square, - qm31_sub, qm31_swap, + m31_add_n31, m31_sub, push_m31_one, push_n31_one, push_qm31_one, qm31_add, qm31_copy, qm31_double, qm31_dup, qm31_equalverify, qm31_from_bottom, qm31_fromaltstack, qm31_mul, qm31_neg, qm31_roll, qm31_rot, qm31_square, qm31_sub, qm31_swap, qm31_toaltstack }; use std::ops::{Add, Mul, Neg}; use stwo_prover::core::fields::qm31::QM31; @@ -15,6 +13,58 @@ use crate::channel::ChannelGadget; pub struct CirclePointSecureGadget; impl CirclePointSecureGadget { + /// Only computes the x component of addition between points + pub fn add_x_only() -> Script { + script! { + { qm31_roll(3) } + { qm31_roll(2) } + qm31_mul + { qm31_roll(1) } + { qm31_roll(2) } + qm31_mul + qm31_sub + } + } + + + /// Add two points. + pub fn add() -> Script { + script! { + { qm31_copy(3) } + { qm31_copy(2) } + qm31_mul + { qm31_copy(3) } + { qm31_copy(2) } + qm31_mul + { qm31_roll(5)} + { qm31_roll(5)} + qm31_add + { qm31_roll(4)} + { qm31_roll(4)} + qm31_add + qm31_mul + qm31_toaltstack + { qm31_copy(1) } + { qm31_copy(1) } + qm31_add + qm31_fromaltstack + qm31_swap + qm31_sub + qm31_toaltstack + qm31_sub + qm31_fromaltstack + } + } + + /// Fail the execution if the two points are not equal. + pub fn equalverify() -> Script { + script! { + { qm31_roll(2) } + qm31_equalverify + qm31_equalverify + } + } + /// Double a point. /// Rationale: cos(2*theta) = 2*cos(theta)^2-1 /// @@ -105,12 +155,13 @@ impl CirclePointSecureGadget { #[cfg(test)] mod test { use num_traits::One; + use stwo_prover::core::circle::CirclePoint; use std::ops::{Add, Mul, Neg}; use crate::treepp::*; use rand::{Rng, RngCore, SeedableRng}; use rand_chacha::ChaCha20Rng; - use rust_bitcoin_m31::qm31_equalverify; + use rust_bitcoin_m31::{qm31_equalverify, qm31_roll}; use stwo_prover::core::fields::m31::M31; use stwo_prover::core::fields::qm31::QM31; use stwo_prover::core::fields::{Field, FieldExpOps}; @@ -120,6 +171,77 @@ mod test { circle_secure::bitcoin_script::CirclePointSecureGadget, }; + #[test] + fn test_add() { + let mut prng = ChaCha20Rng::seed_from_u64(0); + + let add_script = CirclePointSecureGadget::add(); + println!("CirclePointSecure.add() = {} bytes", add_script.len()); + + let add_x_script = CirclePointSecureGadget::add_x_only(); + println!("CirclePointSecure.add_x_only() = {} bytes", add_x_script.len()); + + for _ in 0..100 { + let a = CirclePoint { + x: QM31::from_m31( + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + ), + y: QM31::from_m31( + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + ) + }; + + let b = CirclePoint { + x: QM31::from_m31( + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + ), + y: QM31::from_m31( + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + ) + }; + let c = a+b; + + let script = script! { + { a.x } + { a.y } + { b.x } + { b.y } + { add_script.clone() } + { c.x } + { c.y } + { CirclePointSecureGadget::equalverify() } + OP_TRUE + }; + let exec_result = execute_script(script); + assert!(exec_result.success); + + let script = script! { + { a.x } + { a.y } + { b.x } + { b.y } + { add_x_script.clone() } + { c.x } + qm31_equalverify + OP_TRUE + }; + let exec_result = execute_script(script); + assert!(exec_result.success); + } + } + #[test] fn test_double_x() { let double_x_script = CirclePointSecureGadget::double_x(); diff --git a/src/constraints/bitcoin_script.rs b/src/constraints/bitcoin_script.rs index a8b0f99..aa40f26 100644 --- a/src/constraints/bitcoin_script.rs +++ b/src/constraints/bitcoin_script.rs @@ -1,12 +1,35 @@ -use crate::treepp::*; +use crate::{circle_secure::CirclePointSecureGadget, treepp::*}; use rust_bitcoin_m31::{qm31_add, qm31_mul, qm31_swap}; use std::ops::{Add, Mul, Neg}; -use stwo_prover::core::fields::qm31::QM31; +use stwo_prover::core::{circle::{CirclePoint, Coset}, fields::qm31::QM31}; /// Gadget for constraints over the circle curve pub struct ConstraintsGadget; impl ConstraintsGadget { + //TODO: point_vanishing_fraction(). Depends on what format we'll end up needing its output in FRI + + /// Evaluates a vanishing polynomial P : CirclePoint -> QM31 of the given coset + /// + /// input: + /// z.x (QM31) + /// z.y (QM31) + /// + /// output: + /// P(z) + pub fn coset_vanishing(coset: Coset) -> Script { + let shift = - coset.initial.into_ef::() + coset.step_size.half().to_point().into_ef::(); + + script! { + { shift.x } + { shift.y } + { CirclePointSecureGadget::add_x_only() } + for _ in 1..coset.log_size { + { CirclePointSecureGadget::double_x() } + } + } + } + /// Evaluates a polynomial P : CirclePointSecure -> QM31 that vanishes at excluded0 and excluded1 /// /// input: @@ -16,21 +39,19 @@ impl ConstraintsGadget { /// output: /// P(z) pub fn pair_vanishing( - excluded0x: QM31, - excluded0y: QM31, - excluded1x: QM31, - excluded1y: QM31, + excluded0: CirclePoint, + excluded1: CirclePoint ) -> Script { script! { - { excluded1x.add(excluded0x.neg()) } + { excluded1.x - excluded0.x } qm31_mul //(excluded1.x - excluded0.x) * z.y qm31_swap - { excluded0y.add(excluded1y.neg()) } + { excluded0.y - excluded1.y } qm31_mul //(excluded0.y - excluded1.y) * z.x qm31_add - { excluded0x.mul(excluded1y).add(excluded0y.mul(excluded1x).neg())} + { excluded0.x * excluded1.y - excluded0.y * excluded1.x } qm31_add //(excluded0.y - excluded1.y) * z.x // + (excluded1.x - excluded0.x) * z.y @@ -41,75 +62,118 @@ impl ConstraintsGadget { #[cfg(test)] mod test { - use std::ops::{Add, Mul, Neg}; use crate::{constraints::ConstraintsGadget, treepp::*}; use rand::{RngCore, SeedableRng}; use rand_chacha::ChaCha20Rng; use rust_bitcoin_m31::qm31_equalverify; + use stwo_prover::core::circle::{CirclePoint, Coset}; + use stwo_prover::core::constraints::{pair_vanishing,coset_vanishing}; use stwo_prover::core::fields::m31::M31; use stwo_prover::core::fields::qm31::QM31; + #[test] + fn test_coset_vanishing() { + let mut prng = ChaCha20Rng::seed_from_u64(0); + + for log_size in 5..10 { + let coset = Coset::subgroup(log_size); + let coset_vanishing_script = ConstraintsGadget::coset_vanishing(coset); + println!("Constraints.coset_vanishing(log_size={}) = {} bytes", log_size, coset_vanishing_script.len()); + + let z = CirclePoint { + x: QM31::from_m31( + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + ), + y: QM31::from_m31( + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + ) + }; + + let res = coset_vanishing(coset,z); + + let script = script! { + { z.x } + { z.y } + { coset_vanishing_script.clone() } + { res } + qm31_equalverify + OP_TRUE + }; + let exec_result = execute_script(script); + assert!(exec_result.success); + + } + } + + #[test] fn test_pair_vanishing() { for seed in 0..20 { let mut prng = ChaCha20Rng::seed_from_u64(seed); - let zx = QM31::from_m31( - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - ); - - let zy = QM31::from_m31( - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - ); - - let e0x = QM31::from_m31( - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - ); - - let e0y = QM31::from_m31( - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - ); - - let e1x = QM31::from_m31( - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - ); - - let e1y = QM31::from_m31( - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - M31::reduce(prng.next_u64()), - ); + let z = CirclePoint { + x: QM31::from_m31( + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + ), + y: QM31::from_m31( + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + ) + }; - //(excluded0.y - excluded1.y) * z.x - // + (excluded1.x - excluded0.x) * z.y - // + (excluded0.x * excluded1.y - excluded0.y * excluded1.x) - let res = e0y - .add(e1y.neg()) - .mul(zx) - .add(e1x.add(e0x.neg()).mul(zy)) - .add(e0x.mul(e1y).add(e0y.mul(e1x).neg())); + let excluded0 = CirclePoint { + x: QM31::from_m31( + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + ), + y: QM31::from_m31( + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + ) + }; + + let excluded1 = CirclePoint { + x: QM31::from_m31( + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + ), + y: QM31::from_m31( + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + M31::reduce(prng.next_u64()), + ) + }; + + let res = pair_vanishing(excluded0,excluded1,z); + + let pair_vanishing_script = ConstraintsGadget::pair_vanishing(excluded0,excluded1); + if seed == 0 { + println!("Constraints.pair_vanishing() = {} bytes", pair_vanishing_script.len()); + } let script = script! { - { zx } - { zy } - { ConstraintsGadget::pair_vanishing(e0x,e0y,e1x,e1y) } + { z.x } + { z.y } + { pair_vanishing_script.clone() } { res } qm31_equalverify OP_TRUE From 79a6a2117fd68cd59fff71b0411182a33c43b8a2 Mon Sep 17 00:00:00 2001 From: victorkstarkware Date: Thu, 30 May 2024 14:42:37 +0300 Subject: [PATCH 2/2] fmt --- src/circle_secure/bitcoin_script.rs | 108 ++++++++++++++-------------- src/constraints/bitcoin_script.rs | 42 ++++++----- 2 files changed, 80 insertions(+), 70 deletions(-) diff --git a/src/circle_secure/bitcoin_script.rs b/src/circle_secure/bitcoin_script.rs index 335b435..a60f52d 100644 --- a/src/circle_secure/bitcoin_script.rs +++ b/src/circle_secure/bitcoin_script.rs @@ -1,7 +1,9 @@ use crate::treepp::*; use num_traits::One; use rust_bitcoin_m31::{ - m31_add_n31, m31_sub, push_m31_one, push_n31_one, push_qm31_one, qm31_add, qm31_copy, qm31_double, qm31_dup, qm31_equalverify, qm31_from_bottom, qm31_fromaltstack, qm31_mul, qm31_neg, qm31_roll, qm31_rot, qm31_square, qm31_sub, qm31_swap, qm31_toaltstack + m31_add_n31, m31_sub, push_m31_one, push_n31_one, push_qm31_one, qm31_add, qm31_copy, + qm31_double, qm31_dup, qm31_equalverify, qm31_from_bottom, qm31_fromaltstack, qm31_mul, + qm31_neg, qm31_roll, qm31_rot, qm31_square, qm31_sub, qm31_swap, qm31_toaltstack, }; use std::ops::{Add, Mul, Neg}; use stwo_prover::core::fields::qm31::QM31; @@ -13,57 +15,56 @@ use crate::channel::ChannelGadget; pub struct CirclePointSecureGadget; impl CirclePointSecureGadget { - /// Only computes the x component of addition between points - pub fn add_x_only() -> Script { - script! { - { qm31_roll(3) } - { qm31_roll(2) } - qm31_mul - { qm31_roll(1) } - { qm31_roll(2) } - qm31_mul - qm31_sub - } + /// Only computes the x component of addition between points + pub fn add_x_only() -> Script { + script! { + { qm31_roll(3) } + { qm31_roll(2) } + qm31_mul + { qm31_roll(1) } + { qm31_roll(2) } + qm31_mul + qm31_sub } + } - - /// Add two points. - pub fn add() -> Script { - script! { - { qm31_copy(3) } - { qm31_copy(2) } - qm31_mul - { qm31_copy(3) } - { qm31_copy(2) } - qm31_mul - { qm31_roll(5)} - { qm31_roll(5)} - qm31_add - { qm31_roll(4)} - { qm31_roll(4)} - qm31_add - qm31_mul - qm31_toaltstack - { qm31_copy(1) } - { qm31_copy(1) } - qm31_add - qm31_fromaltstack - qm31_swap - qm31_sub - qm31_toaltstack - qm31_sub - qm31_fromaltstack - } + /// Add two points. + pub fn add() -> Script { + script! { + { qm31_copy(3) } + { qm31_copy(2) } + qm31_mul + { qm31_copy(3) } + { qm31_copy(2) } + qm31_mul + { qm31_roll(5)} + { qm31_roll(5)} + qm31_add + { qm31_roll(4)} + { qm31_roll(4)} + qm31_add + qm31_mul + qm31_toaltstack + { qm31_copy(1) } + { qm31_copy(1) } + qm31_add + qm31_fromaltstack + qm31_swap + qm31_sub + qm31_toaltstack + qm31_sub + qm31_fromaltstack } + } - /// Fail the execution if the two points are not equal. - pub fn equalverify() -> Script { - script! { - { qm31_roll(2) } - qm31_equalverify - qm31_equalverify - } + /// Fail the execution if the two points are not equal. + pub fn equalverify() -> Script { + script! { + { qm31_roll(2) } + qm31_equalverify + qm31_equalverify } + } /// Double a point. /// Rationale: cos(2*theta) = 2*cos(theta)^2-1 @@ -155,8 +156,8 @@ impl CirclePointSecureGadget { #[cfg(test)] mod test { use num_traits::One; - use stwo_prover::core::circle::CirclePoint; use std::ops::{Add, Mul, Neg}; + use stwo_prover::core::circle::CirclePoint; use crate::treepp::*; use rand::{Rng, RngCore, SeedableRng}; @@ -179,7 +180,10 @@ mod test { println!("CirclePointSecure.add() = {} bytes", add_script.len()); let add_x_script = CirclePointSecureGadget::add_x_only(); - println!("CirclePointSecure.add_x_only() = {} bytes", add_x_script.len()); + println!( + "CirclePointSecure.add_x_only() = {} bytes", + add_x_script.len() + ); for _ in 0..100 { let a = CirclePoint { @@ -194,7 +198,7 @@ mod test { M31::reduce(prng.next_u64()), M31::reduce(prng.next_u64()), M31::reduce(prng.next_u64()), - ) + ), }; let b = CirclePoint { @@ -209,9 +213,9 @@ mod test { M31::reduce(prng.next_u64()), M31::reduce(prng.next_u64()), M31::reduce(prng.next_u64()), - ) + ), }; - let c = a+b; + let c = a + b; let script = script! { { a.x } diff --git a/src/constraints/bitcoin_script.rs b/src/constraints/bitcoin_script.rs index be64b33..0abacf1 100644 --- a/src/constraints/bitcoin_script.rs +++ b/src/constraints/bitcoin_script.rs @@ -1,6 +1,9 @@ use crate::{circle_secure::CirclePointSecureGadget, treepp::*}; use rust_bitcoin_m31::{qm31_add, qm31_mul, qm31_swap}; -use stwo_prover::core::{circle::{CirclePoint, Coset}, fields::qm31::QM31}; +use stwo_prover::core::{ + circle::{CirclePoint, Coset}, + fields::qm31::QM31, +}; /// Gadget for constraints over the circle curve pub struct ConstraintsGadget; @@ -17,7 +20,8 @@ impl ConstraintsGadget { /// output: /// P(z) pub fn coset_vanishing(coset: Coset) -> Script { - let shift = - coset.initial.into_ef::() + coset.step_size.half().to_point().into_ef::(); + let shift = + -coset.initial.into_ef::() + coset.step_size.half().to_point().into_ef::(); script! { { shift.x } @@ -37,10 +41,7 @@ impl ConstraintsGadget { /// /// output: /// P(z) - pub fn pair_vanishing( - excluded0: CirclePoint, - excluded1: CirclePoint - ) -> Script { + pub fn pair_vanishing(excluded0: CirclePoint, excluded1: CirclePoint) -> Script { script! { { excluded1.x - excluded0.x } qm31_mul //(excluded1.x - excluded0.x) * z.y @@ -67,7 +68,7 @@ mod test { use rand_chacha::ChaCha20Rng; use rust_bitcoin_m31::qm31_equalverify; use stwo_prover::core::circle::{CirclePoint, Coset}; - use stwo_prover::core::constraints::{pair_vanishing,coset_vanishing}; + use stwo_prover::core::constraints::{coset_vanishing, pair_vanishing}; use stwo_prover::core::fields::m31::M31; use stwo_prover::core::fields::qm31::QM31; @@ -78,7 +79,11 @@ mod test { for log_size in 5..10 { let coset = Coset::subgroup(log_size); let coset_vanishing_script = ConstraintsGadget::coset_vanishing(coset); - println!("Constraints.coset_vanishing(log_size={}) = {} bytes", log_size, coset_vanishing_script.len()); + println!( + "Constraints.coset_vanishing(log_size={}) = {} bytes", + log_size, + coset_vanishing_script.len() + ); let z = CirclePoint { x: QM31::from_m31( @@ -92,10 +97,10 @@ mod test { M31::reduce(prng.next_u64()), M31::reduce(prng.next_u64()), M31::reduce(prng.next_u64()), - ) + ), }; - let res = coset_vanishing(coset,z); + let res = coset_vanishing(coset, z); let script = script! { { z.x } @@ -107,11 +112,9 @@ mod test { }; let exec_result = execute_script(script); assert!(exec_result.success); - } } - #[test] fn test_pair_vanishing() { for seed in 0..20 { @@ -129,7 +132,7 @@ mod test { M31::reduce(prng.next_u64()), M31::reduce(prng.next_u64()), M31::reduce(prng.next_u64()), - ) + ), }; let excluded0 = CirclePoint { @@ -144,7 +147,7 @@ mod test { M31::reduce(prng.next_u64()), M31::reduce(prng.next_u64()), M31::reduce(prng.next_u64()), - ) + ), }; let excluded1 = CirclePoint { @@ -159,14 +162,17 @@ mod test { M31::reduce(prng.next_u64()), M31::reduce(prng.next_u64()), M31::reduce(prng.next_u64()), - ) + ), }; - let res = pair_vanishing(excluded0,excluded1,z); + let res = pair_vanishing(excluded0, excluded1, z); - let pair_vanishing_script = ConstraintsGadget::pair_vanishing(excluded0,excluded1); + let pair_vanishing_script = ConstraintsGadget::pair_vanishing(excluded0, excluded1); if seed == 0 { - println!("Constraints.pair_vanishing() = {} bytes", pair_vanishing_script.len()); + println!( + "Constraints.pair_vanishing() = {} bytes", + pair_vanishing_script.len() + ); } let script = script! {