checking the combined Plonk constraints seems to work

This commit is contained in:
Balazs Komuves 2024-12-15 20:53:14 +01:00
parent 4b34d8df89
commit d19c929c89
No known key found for this signature in database
GPG Key ID: F63B7AEF18435562
13 changed files with 390 additions and 42 deletions

View File

@ -25,8 +25,9 @@ try to focus on simplicity.
- [ ] Parsing from Plonky's custom binary serialization
- [x] Generating verifier challenges
- [ ] Recursive circuit subtle details (like [this](https://github.com/0xPolygonZero/plonky2/blob/356aefb6863ac881fb71f9bf851582c915428458/plonky2/src/fri/challenges.rs#L55-L64]))
- [ ] Constraints check
- [x] Constraints check
- [ ] FRI check
- [ ] Support lookup tables
- [ ] Documenting Plonky2 internals and the verifier algorithm
Supported gates:

View File

@ -147,6 +147,9 @@ div a b = mul a (inv b)
--------------------------------------------------------------------------------
pow_ :: Goldilocks -> Int -> Goldilocks
pow_ x e = pow x (fromIntegral e)
pow :: Goldilocks -> Integer -> Goldilocks
pow x e
| e == 0 = 1

View File

@ -63,6 +63,9 @@ instance Fractional FExt where
--------------------------------------------------------------------------------
scaleExt :: F -> FExt -> FExt
scaleExt s (MkExt a b) = MkExt (s*a) (s*b)
sqrExt :: FExt -> FExt
sqrExt x = x*x
@ -77,6 +80,9 @@ divExt u v = u * invExt v
--------------------------------------------------------------------------------
powExt_ :: GoldilocksExt -> Int -> GoldilocksExt
powExt_ x e = powExt x (fromIntegral e)
powExt :: GoldilocksExt -> Integer -> GoldilocksExt
powExt x e
| e == 0 = 1

View File

@ -5,19 +5,14 @@
-- of constraints.
--
{-# LANGUAGE StrictData, DeriveFunctor, GADTs, RecordWildCards #-}
{-# LANGUAGE StrictData, RecordWildCards #-}
module Gate.Constraints where
--------------------------------------------------------------------------------
import Prelude hiding ( (^) )
import Prelude hiding ( (^) )
import Data.Array hiding (range)
import Data.Char
import Text.Show
import Data.IntMap (IntMap)
import qualified Data.IntMap as IntMap
import Algebra.Goldilocks
import Algebra.GoldilocksExt
@ -28,6 +23,8 @@ import Gate.Vars
import Gate.Computation
import Gate.Poseidon
import Misc.Aux
--------------------------------------------------------------------------------
-- * Gate constraints
@ -87,7 +84,7 @@ gateComputation gate =
-- equality with "hardcoded" hash components
PublicInputGate
-> commitList [ hash i - wire i | i <- range 4 ]
-> commitList [ wire i - hash i | i <- range 4 ]
PoseidonGate hash_width -> case hash_width of
12 -> poseidonGateConstraints

View File

@ -21,6 +21,8 @@ import Gate.Vars
import Gate.Computation
import Hash.Constants
import Misc.Aux
--------------------------------------------------------------------------------
sbox :: Expr Var_ -> Compute (Expr Var_)
@ -49,7 +51,7 @@ poseidonGateConstraints = do
-- merkle swap
let input_lhs i = input i
let input_rhs i = input (i+4)
commit $ (1 - swap_flag) * swap_flag
commit $ swap_flag * (swap_flag - 1)
commitList [ swap_flag * (input_rhs i - input_lhs i) - delta i | i <- range 4 ]
-- swapped inputs

45
src/Gate/Selector.hs Normal file
View File

@ -0,0 +1,45 @@
-- | Selector polynomials
{-# LANGUAGE StrictData, RecordWildCards #-}
module Gate.Selector where
--------------------------------------------------------------------------------
import Data.Array hiding (range)
import Algebra.Goldilocks
import Algebra.GoldilocksExt
import Algebra.Expr
import Gate.Base
import Gate.Vars
import Types
import Misc.Aux
--------------------------------------------------------------------------------
-- | Given an evaluation point @x@ and a gate index @k@, we compute
-- the evaluation of the corresponding selector polynomial
--
-- Note: In the actual protocol, we have @x = S_g(zeta)@
--
evalSelectorPoly :: SelectorsInfo -> FExt -> Int -> FExt
evalSelectorPoly (MkSelectorsInfo{..}) x k = value where
group_idx = selector_indices !! k
range = selector_groups !! group_idx
initial = if length selector_groups > 1 then unused - x else 1
value = initial * product [ fromIntegral j - x | j <- enumerateRange range , j /= k ]
unused = (2^32 - 1) :: FExt
-- | Given the evaluations of the selector column polynomials, we evaluate
-- all the gate selectors
evalSelectors :: SelectorsInfo -> [FExt] -> [FExt]
evalSelectors selInfo@(MkSelectorsInfo{..}) xs = values where
values = [ evalSelectorPoly selInfo (xs!!grp) i | (i,grp) <- zip [0..] selector_indices ]
numSelectorColumns :: SelectorsInfo -> Int
numSelectorColumns selInfo = length (selector_groups selInfo)
--------------------------------------------------------------------------------

View File

@ -44,9 +44,6 @@ instance Pretty v => Pretty (Var v) where
--------------------------------------------------------------------------------
-- * Convenience
range :: Int -> [Int]
range k = [0..k-1]
wire, cnst, hash :: Int -> Expr (Var PlonkyVar)
wire i = VarE $ ProofVar $ WireV i -- witness variable
cnst i = VarE $ ProofVar $ ConstV i -- constant variable

View File

@ -10,6 +10,7 @@ module Hash.Constants where
import Data.Array
import Algebra.Goldilocks
import Misc.Aux
--------------------------------------------------------------------------------
@ -111,13 +112,8 @@ fast_PARTIAL_ROUND_INITIAL_MATRIX = listArray ((0,0),(10,10)) $ concat
partialMdsMatrixCoeff :: Int -> Int -> F
partialMdsMatrixCoeff i j = fast_PARTIAL_ROUND_INITIAL_MATRIX ! (j,i)
partition12 :: [a] -> [[a]]
partition12 = go where
go [] = []
go xs = take 12 xs : go (drop 12 xs)
all_ROUND_CONSTANTS :: Array Int (Array Int F)
all_ROUND_CONSTANTS = listArray (0,29) $ map (listArray (0,11)) $ partition12
all_ROUND_CONSTANTS = listArray (0,29) $ map (listArray (0,11)) $ partition 12
[ 0xb585f766f2144405 , 0x7746a55f43921ad7 , 0xb2fb0d31cee799b4 , 0x0f6760a4803427d7
, 0xe10d666650f4e012 , 0x8cae14cb07d09bf1 , 0xd438539c95f63e9f , 0xef781c7ce35b4c3d
, 0xcdc4a239b0c44426 , 0x277fa208bf337bff , 0xe17653a29da578a1 , 0xc54302f225db2c76

76
src/Misc/Aux.hs Normal file
View File

@ -0,0 +1,76 @@
-- | Misc helper functions
{-# LANGUAGE StrictData, DeriveGeneric, DeriveAnyClass #-}
module Misc.Aux where
--------------------------------------------------------------------------------
import Data.Array
import Data.List
import Data.Aeson hiding ( Array , pairs )
import GHC.Generics
--------------------------------------------------------------------------------
range :: Int -> [Int]
range k = [0..k-1]
range' :: Int -> Int -> [Int]
range' a b = [a..b-1]
--------------------------------------------------------------------------------
-- | Consecutive pairs of a list
pairs :: [a] -> [(a,a)]
pairs [] = []
pairs [_] = []
pairs (x:rest@(y:_)) = (x,y) : pairs rest
safeZip :: [a] -> [b] -> [(a,b)]
safeZip = safeZipWith (,)
safeZipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
safeZipWith f = go where
go (x:xs) (y:ys) = f x y : go xs ys
go [] [] = []
go _ _ = error "safeZipWith: different input lengths"
longZipWith :: a -> b -> (a -> b -> c) -> [a] -> [b] -> [c]
longZipWith x0 y0 f = go where
go [] [] = []
go [] ys = map (x0 `f`) ys
go xs [] = map (`f` y0) xs
go (x:xs) (y:ys) = f x y : go xs ys
-- | Partition into equal sized chunks
partition :: Int -> [a] -> [[a]]
partition k = go where
go [] = []
go xs = take k xs : go (drop k xs)
--------------------------------------------------------------------------------
listToArray :: [a] -> Array Int a
listToArray xs = listArray (0, length xs - 1) xs
arrayLength :: Array Int a -> Int
arrayLength arr = let (a,b) = bounds arr in b-a+1
--------------------------------------------------------------------------------
-- | The interval @[a,b)@ (inclusive on the left, exclusive on the right)
data Range = MkRange
{ range_start :: !Int
, range_end :: !Int
}
deriving (Eq,Show,Generic)
enumerateRange :: Range -> [Int]
enumerateRange (MkRange a b) = [a..b-1]
instance FromJSON Range where parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = drop 6 }
instance ToJSON Range where toJSON = genericToJSON defaultOptions { fieldLabelModifier = drop 6 }
--------------------------------------------------------------------------------

144
src/Plonk/Vanishing.hs Normal file
View File

@ -0,0 +1,144 @@
-- | Evaluate all the Plonk constraints.
--
-- The following constraints are combined via powers of alpha:
--
-- * boundary value constraints for the permutation running products (1 per challenge round)
--
-- * running product consistency constraints (10 per challenge round, where @10 = #routed wires / max_degree@)
--
-- * all lookup terms (many per challenge round)
--
-- * combined gate constraints (123 by default as the Poseidon gate is the largest)
--
-- This is done as many times as there are rounds, with different alphas (chosen from the base field).
--
{-# LANGUAGE RecordWildCards #-}
module Plonk.Vanishing where
--------------------------------------------------------------------------------
import Data.Array
import Data.List ( foldl' , zipWith4 )
import Algebra.Goldilocks
import Algebra.GoldilocksExt
import Challenge.Verifier
import Hash.Digest
import Hash.Sponge
import Gate.Computation
import Gate.Constraints
import Gate.Selector
import Types
import Misc.Aux
--------------------------------------------------------------------------------
-- | Evaluate the Lagrange polynomial @L_0(x)@ of a subgroup of size @N@
evalLagrange0 :: Int -> FExt -> FExt
evalLagrange0 nn zeta
| zeta == 1 = 1
| otherwise = (powExt_ zeta nn - 1) / (fromIntegral nn * (zeta - 1))
--------------------------------------------------------------------------------
-- | We combine the same things with all the challenge round alphas...
-- (Plonky2 is full with very strange design decisions...)
--
evalCombinedPlonkConstraints :: CommonCircuitData -> ProofWithPublicInputs -> ProofChallenges -> [FExt]
evalCombinedPlonkConstraints common proof challenges = final where
constraints = evalAllPlonkConstraints common proof challenges
final = [ combineWithPowersOfAlpha alpha constraints | alpha <- plonk_alphas challenges ]
-- | @sum [ alpha^k * x_k ]@
combineWithPowersOfAlpha :: F -> [FExt] -> FExt
combineWithPowersOfAlpha alpha xs = foldl' f 0 (reverse xs) where
f !acc !x = x + scaleExt alpha acc
--------------------------------------------------------------------------------
evalAllPlonkConstraints :: CommonCircuitData -> ProofWithPublicInputs -> ProofChallenges -> [FExt]
evalAllPlonkConstraints
(MkCommonCircuitData{..})
(MkProofWithPublicInputs{..})
(MkProofChallenges{..}) = finals
where
finals = concat
[ zs1
, concat pp_checks
, lookup_checks
, gates
]
lookup_checks = [] -- TODO
MkProof{..} = the_proof
MkOpeningSet{..} = openings
nselectors = numSelectorColumns circuit_selectors_info
opening_selectors = take nselectors opening_constants
nn = fri_nrows circuit_fri_params
maxdeg = circuit_quotient_degree_factor
pi_hash = sponge public_inputs
-- gate constraints
eval_vars = toEvaluationVars pi_hash circuit_selectors_info openings
gate_prgs = map gateProgram circuit_gates
sel_values = evalSelectors circuit_selectors_info opening_selectors
unfiltered = map (runStraightLine eval_vars) gate_prgs
filtered = zipWith (\s cons -> map (*s) cons) sel_values unfiltered
gates = combineFilteredGateConstraints filtered
-- permutation constraints
zs1 = [ evalLagrange0 nn plonk_zeta * (z-1) | z <- opening_plonk_zs ]
pp_chunks = partition circuit_num_partial_products opening_partial_products
pp_checks = zipWith4 evalPartialProducts
opening_plonk_zs
opening_plonk_zs_next
(zip plonk_betas plonk_gammas)
pp_chunks
evalPartialProducts :: FExt -> FExt -> (F,F) -> [FExt] -> [FExt]
evalPartialProducts z znext (beta,gamma) pp_chunk = zipWith3 f (pairs current) numers denoms where
numers = partition maxdeg $ zipWith (\koset w -> w + scaleExt (beta*koset) plonk_zeta + fromBase gamma) circuit_k_is opening_wires
denoms = partition maxdeg $ zipWith (\sigma w -> w + scaleExt beta sigma + fromBase gamma) opening_plonk_sigmas opening_wires
current = [z] ++ pp_chunk ++ [znext]
f :: (FExt,FExt) -> [FExt] -> [FExt] -> FExt
f (prev,next) numer denom = prev * product numer - next * product denom
--------------------------------------------------------------------------------
-- | Each gate has some number of constrains (depending on which gate it is).
-- These are combined \"verticaly\" by simple addition, so that each term in the
-- resulting list of summed constraints is a sum of constraints from distinct gates,
-- at most 1 per gate.
--
-- This look suspicious at first glance: normally we never just add together constraints!
-- However this looks safe in this case, because the selectors ensure that when evaluating
-- at a particular row of the witness, each such sum can have at most 1 nonzero summand
combineFilteredGateConstraints :: [[FExt]] -> [FExt]
combineFilteredGateConstraints = foldl1 (longZipWith 0 0 (+))
--------------------------------------------------------------------------------
toEvaluationVars :: Digest -> SelectorsInfo -> OpeningSet -> EvaluationVars FExt
toEvaluationVars pi_hash selinfo (MkOpeningSet{..}) =
MkEvaluationVars
{ local_selectors = listToArray (take nsels opening_constants)
, local_constants = listToArray (drop nsels opening_constants)
, local_wires = listToArray opening_wires
, public_inputs_hash = digestToList pi_hash
}
where
nsels = numSelectorColumns selinfo
--------------------------------------------------------------------------------

62
src/Plonk/Verifier.hs Normal file
View File

@ -0,0 +1,62 @@
-- | Check the combined Plonk equation @Q_alpha(zeta) * Z_X(zeta) = C_alpha(zeta)@
--
-- Note: there are @num_challenge_rounds@ number of these (usually 2 or 3);
-- they are the same except with different combining @alpha@ coefficients.
--
{-# LANGUAGE RecordWildCards #-}
module Plonk.Verifier where
--------------------------------------------------------------------------------
import Data.Array
import Data.List ( foldl' , zipWith4 )
import Algebra.Goldilocks
import Algebra.GoldilocksExt
import Challenge.Verifier
import Plonk.Vanishing
import Hash.Digest
import Types
import Misc.Aux
--------------------------------------------------------------------------------
{-
-- | Evaluate the zero polynomial @Z_H(x)@
evalZeroPoly :: Int -> FExt -> FExt
evalZeroPoly nn zeta = powExt_ zeta nn - 1
-}
--------------------------------------------------------------------------------
-- | Assuming valid FRI openings, this function checks the combined Plonk equations
checkCombinedPlonkEquations :: CommonCircuitData -> ProofWithPublicInputs -> ProofChallenges -> Bool
checkCombinedPlonkEquations common proof challenges =
and (checkCombinedPlonkEquations' common proof challenges)
checkCombinedPlonkEquations' :: CommonCircuitData -> ProofWithPublicInputs -> ProofChallenges -> [Bool]
checkCombinedPlonkEquations' common proof_pis challenges = ok_list where
maxdeg = circuit_quotient_degree_factor common :: Int
nn = circuit_nrows common :: Int
zeta = plonk_zeta challenges :: FExt
zeta_n = powExt_ zeta nn :: FExt
combined_evals = evalCombinedPlonkConstraints common proof_pis challenges :: [FExt]
quotient_evals = map (reduceWithPowers zeta_n) quotient_chunks :: [FExt]
quotient_chunks = partition maxdeg (opening_quotient_polys $ openings $ the_proof proof_pis) :: [[FExt]]
ok_list = [ q * (zeta_n - 1) == c | (q,c) <- safeZip quotient_evals combined_evals ]
-- sum [ u^i * q[i] | i<-[0..] ]
reduceWithPowers :: FExt -> [FExt] -> FExt
reduceWithPowers u chunk = foldl' f 0 (reverse chunk) where
f !acc !x = x + u * acc
--------------------------------------------------------------------------------

View File

@ -20,6 +20,7 @@ import Algebra.GoldilocksExt
import Hash.Digest
import Gate.Base
import Gate.Parser
import Misc.Aux
--------------------------------------------------------------------------------
@ -47,13 +48,16 @@ data CommonCircuitData = MkCommonCircuitData
, circuit_num_constants :: Int -- ^ The number of constant wires.
, circuit_num_public_inputs :: Int -- ^ Number of public inputs
, circuit_k_is :: [F] -- ^ The @{k_i}@ values (coset shifts) used in @S_I D_i@ in Plonk's permutation argument.
, circuit_num_partial_products :: Int -- ^ The number of partial products needed to compute the `Z` polynomials.
, circuit_num_partial_products :: Int -- ^ The number of partial products needed to compute the `Z` polynomials; @ = ceil( #routed / max_degree ) - 1@
, circuit_num_lookup_polys :: Int -- ^ The number of lookup polynomials.
, circuit_num_lookup_selectors :: Int -- ^ The number of lookup selectors.
, circuit_luts :: [LookupTable] -- ^ The stored lookup tables.
}
deriving (Eq,Show,Generic)
circuit_nrows :: CommonCircuitData -> Int
circuit_nrows = fri_nrows . circuit_fri_params
instance FromJSON CommonCircuitData where parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = drop 8 }
--instance ToJSON CommonCircuitData where toJSON = genericToJSON defaultOptions { fieldLabelModifier = drop 8 }
@ -74,16 +78,6 @@ data CircuitConfig = MkCircuitConfig
instance FromJSON CircuitConfig where parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = drop 7 }
instance ToJSON CircuitConfig where toJSON = genericToJSON defaultOptions { fieldLabelModifier = drop 7 }
-- | The interval @[a,b)@ (inclusive on the left, exclusive on the right)
data Range = MkRange
{ range_start :: Int
, range_end :: Int
}
deriving (Eq,Show,Generic)
instance FromJSON Range where parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = drop 6 }
instance ToJSON Range where toJSON = genericToJSON defaultOptions { fieldLabelModifier = drop 6 }
data SelectorsInfo = MkSelectorsInfo
{ selector_indices :: [Int] -- ^ which gate is in which selector groups (length = number of gates)
, selector_groups :: [Range] -- ^ the selector groups are continuous intervals
@ -153,6 +147,11 @@ data FriParams = MkFriParams
}
deriving (Eq,Show,Generic)
-- | Number of rows in the circuit
fri_nrows :: FriParams -> Int
fri_nrows params = 2^nbits where
nbits = fri_degree_bits params
instance FromJSON FriParams where parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = drop 4 }
instance ToJSON FriParams where toJSON = genericToJSON defaultOptions { fieldLabelModifier = drop 4 }
@ -243,15 +242,15 @@ data Proof = MkProof
deriving (Eq,Show,Generic,ToJSON,FromJSON)
data OpeningSet = MkOpeningSet
{ opening_constants :: [FExt]
, opening_plonk_sigmas :: [FExt]
, opening_wires :: [FExt]
, opening_plonk_zs :: [FExt]
, opening_plonk_zs_next :: [FExt]
, opening_partial_products :: [FExt]
, opening_quotient_polys :: [FExt]
, opening_lookup_zs :: [FExt]
, opening_lookup_zs_next :: [FExt]
{ opening_constants :: [FExt] -- ^ note: this includes the selector columns!
, opening_plonk_sigmas :: [FExt] -- ^ these correspond to columns encoding the wire permutation (as many as routed columns)
, opening_wires :: [FExt] -- ^ these are evaluations of the witness columns
, opening_plonk_zs :: [FExt] -- ^ first columns of the permutation arguments (there @num_challenges@ number of these)
, opening_plonk_zs_next :: [FExt] -- ^ evaluations of the first columns shifted by \"one row\"
, opening_partial_products :: [FExt] -- ^ remaining columns of the permutation arguments
, opening_quotient_polys :: [FExt] -- ^ the quotient polynomials (@num_challenges * max_degree@)
, opening_lookup_zs :: [FExt] -- ^ first columns of the lookup arguments
, opening_lookup_zs_next :: [FExt] -- ^ rest of the lookup arguments
}
deriving (Eq,Show,Generic)

View File

@ -1,4 +1,5 @@
{-# LANGUAGE RecordWildCards #-}
module Main where
--------------------------------------------------------------------------------
@ -10,6 +11,8 @@ import Hash.Sponge
import Hash.Digest
import Algebra.Goldilocks
import Challenge.Verifier
import Plonk.Vanishing
import Plonk.Verifier
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as L
@ -17,9 +20,9 @@ import qualified Data.ByteString.Lazy.Char8 as L
--------------------------------------------------------------------------------
main = do
-- let prefix = "fibonacci"
let prefix = "fibonacci"
-- let prefix = "recursion_outer"
let prefix = "lookup"
-- let prefix = "lookup"
text_common <- L.readFile ("../json/" ++ prefix ++ "_common.json")
text_vkey <- L.readFile ("../json/" ++ prefix ++ "_vkey.json" )
@ -28,7 +31,24 @@ main = do
let Just common_data = decode text_common :: Maybe CommonCircuitData
let Just verifier_data = decode text_vkey :: Maybe VerifierOnlyCircuitData
let Just proof_data = decode text_proof :: Maybe ProofWithPublicInputs
let pi_hash = sponge (public_inputs proof_data)
putStrLn $ "public inputs hash = " ++ show pi_hash
let MkOpeningSet{..} = openings (the_proof proof_data)
putStrLn $ "# opening_constants = " ++ show (length opening_constants )
putStrLn $ "# opening_plonk_sigmas = " ++ show (length opening_plonk_sigmas )
putStrLn $ "# opening_wires = " ++ show (length opening_wires )
putStrLn $ "# opening_plonk_zs = " ++ show (length opening_plonk_zs )
putStrLn $ "# opening_plonk_zs_next = " ++ show (length opening_plonk_zs_next )
putStrLn $ "# opening_partial_products = " ++ show (length opening_partial_products)
putStrLn $ "# opening_quotient_polys = " ++ show (length opening_quotient_polys )
putStrLn $ "# opening_lookup_zs = " ++ show (length opening_lookup_zs )
putStrLn $ "# opening_lookup_zs_next = " ++ show (length opening_lookup_zs_next )
let challenges = proofChallenges common_data verifier_data proof_data
print challenges
print challenges
print $ evalCombinedPlonkConstraints common_data proof_data challenges
print $ checkCombinedPlonkEquations' common_data proof_data challenges