From dbce356818fbd4dd6b86dbf5978dd11104e18b0f Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 16 Sep 2022 15:18:44 -0700 Subject: [PATCH] Validate the shape of each proof --- evm/src/stark.rs | 74 +++++++++++++++++----------- plonky2/src/fri/mod.rs | 1 + plonky2/src/fri/structure.rs | 1 + plonky2/src/fri/validate_shape.rs | 66 +++++++++++++++++++++++++ plonky2/src/fri/verifier.rs | 6 +-- plonky2/src/hash/merkle_proofs.rs | 6 +++ plonky2/src/hash/merkle_tree.rs | 4 ++ plonky2/src/plonk/circuit_data.rs | 30 ++++++++++-- plonky2/src/plonk/mod.rs | 1 + plonky2/src/plonk/plonk_common.rs | 14 ------ plonky2/src/plonk/validate_shape.rs | 67 +++++++++++++++++++++++++ plonky2/src/plonk/verifier.rs | 7 ++- starky/src/stark.rs | 76 +++++++++++++++++------------ 13 files changed, 267 insertions(+), 86 deletions(-) create mode 100644 plonky2/src/fri/validate_shape.rs create mode 100644 plonky2/src/plonk/validate_shape.rs diff --git a/evm/src/stark.rs b/evm/src/stark.rs index a205547a..1af8a5e2 100644 --- a/evm/src/stark.rs +++ b/evm/src/stark.rs @@ -16,6 +16,10 @@ use crate::permutation::PermutationPair; use crate::vars::StarkEvaluationTargets; use crate::vars::StarkEvaluationVars; +const TRACE_ORACLE_INDEX: usize = 0; +const PERMUTATION_CTL_ORACLE_INDEX: usize = 1; +const QUOTIENT_ORACLE_INDEX: usize = 2; + /// Represents a STARK system. pub trait Stark, const D: usize>: Sync { /// The total number of columns in the trace. @@ -81,28 +85,35 @@ pub trait Stark, const D: usize>: Sync { num_ctl_zs: usize, config: &StarkConfig, ) -> FriInstanceInfo { - let no_blinding_oracle = FriOracleInfo { blinding: false }; - let mut oracle_indices = 0..; - - let trace_info = - FriPolynomialInfo::from_range(oracle_indices.next().unwrap(), 0..Self::COLUMNS); + let trace_oracle = FriOracleInfo { + num_polys: Self::COLUMNS, + blinding: false, + }; + let trace_info = FriPolynomialInfo::from_range(TRACE_ORACLE_INDEX, 0..Self::COLUMNS); let num_permutation_batches = self.num_permutation_batches(config); - let permutation_ctl_index = oracle_indices.next().unwrap(); + let num_perutation_ctl_polys = num_permutation_batches + num_ctl_zs; + let permutation_ctl_oracle = FriOracleInfo { + num_polys: num_perutation_ctl_polys, + blinding: false, + }; let permutation_ctl_zs_info = FriPolynomialInfo::from_range( - permutation_ctl_index, - 0..num_permutation_batches + num_ctl_zs, + PERMUTATION_CTL_ORACLE_INDEX, + 0..num_perutation_ctl_polys, ); let ctl_zs_info = FriPolynomialInfo::from_range( - permutation_ctl_index, + PERMUTATION_CTL_ORACLE_INDEX, num_permutation_batches..num_permutation_batches + num_ctl_zs, ); - let quotient_info = FriPolynomialInfo::from_range( - oracle_indices.next().unwrap(), - 0..self.quotient_degree_factor() * config.num_challenges, - ); + let num_quotient_polys = self.quotient_degree_factor() * config.num_challenges; + let quotient_oracle = FriOracleInfo { + num_polys: num_quotient_polys, + blinding: false, + }; + let quotient_info = + FriPolynomialInfo::from_range(QUOTIENT_ORACLE_INDEX, 0..num_quotient_polys); let zeta_batch = FriBatchInfo { point: zeta, @@ -122,7 +133,7 @@ pub trait Stark, const D: usize>: Sync { polynomials: ctl_zs_info, }; FriInstanceInfo { - oracles: vec![no_blinding_oracle; oracle_indices.next().unwrap()], + oracles: vec![trace_oracle, permutation_ctl_oracle, quotient_oracle], batches: vec![zeta_batch, zeta_next_batch, ctl_last_batch], } } @@ -137,28 +148,35 @@ pub trait Stark, const D: usize>: Sync { num_ctl_zs: usize, inner_config: &StarkConfig, ) -> FriInstanceInfoTarget { - let no_blinding_oracle = FriOracleInfo { blinding: false }; - let mut oracle_indices = 0..; - - let trace_info = - FriPolynomialInfo::from_range(oracle_indices.next().unwrap(), 0..Self::COLUMNS); + let trace_oracle = FriOracleInfo { + num_polys: Self::COLUMNS, + blinding: false, + }; + let trace_info = FriPolynomialInfo::from_range(TRACE_ORACLE_INDEX, 0..Self::COLUMNS); let num_permutation_batches = self.num_permutation_batches(inner_config); - let permutation_ctl_index = oracle_indices.next().unwrap(); + let num_perutation_ctl_polys = num_permutation_batches + num_ctl_zs; + let permutation_ctl_oracle = FriOracleInfo { + num_polys: num_perutation_ctl_polys, + blinding: false, + }; let permutation_ctl_zs_info = FriPolynomialInfo::from_range( - permutation_ctl_index, - 0..num_permutation_batches + num_ctl_zs, + PERMUTATION_CTL_ORACLE_INDEX, + 0..num_perutation_ctl_polys, ); let ctl_zs_info = FriPolynomialInfo::from_range( - permutation_ctl_index, + PERMUTATION_CTL_ORACLE_INDEX, num_permutation_batches..num_permutation_batches + num_ctl_zs, ); - let quotient_info = FriPolynomialInfo::from_range( - oracle_indices.next().unwrap(), - 0..self.quotient_degree_factor() * inner_config.num_challenges, - ); + let num_quotient_polys = self.quotient_degree_factor() * inner_config.num_challenges; + let quotient_oracle = FriOracleInfo { + num_polys: num_quotient_polys, + blinding: false, + }; + let quotient_info = + FriPolynomialInfo::from_range(QUOTIENT_ORACLE_INDEX, 0..num_quotient_polys); let zeta_batch = FriBatchInfoTarget { point: zeta, @@ -180,7 +198,7 @@ pub trait Stark, const D: usize>: Sync { polynomials: ctl_zs_info, }; FriInstanceInfoTarget { - oracles: vec![no_blinding_oracle; oracle_indices.next().unwrap()], + oracles: vec![trace_oracle, permutation_ctl_oracle, quotient_oracle], batches: vec![zeta_batch, zeta_next_batch, ctl_last_batch], } } diff --git a/plonky2/src/fri/mod.rs b/plonky2/src/fri/mod.rs index 4ed2ea3b..5eaf012e 100644 --- a/plonky2/src/fri/mod.rs +++ b/plonky2/src/fri/mod.rs @@ -7,6 +7,7 @@ pub mod prover; pub mod recursive_verifier; pub mod reduction_strategies; pub mod structure; +mod validate_shape; pub mod verifier; pub mod witness_util; diff --git a/plonky2/src/fri/structure.rs b/plonky2/src/fri/structure.rs index d5c2c81c..0d64ae20 100644 --- a/plonky2/src/fri/structure.rs +++ b/plonky2/src/fri/structure.rs @@ -25,6 +25,7 @@ pub struct FriInstanceInfoTarget { #[derive(Copy, Clone)] pub struct FriOracleInfo { + pub num_polys: usize, pub blinding: bool, } diff --git a/plonky2/src/fri/validate_shape.rs b/plonky2/src/fri/validate_shape.rs new file mode 100644 index 00000000..e8a70a08 --- /dev/null +++ b/plonky2/src/fri/validate_shape.rs @@ -0,0 +1,66 @@ +use anyhow::ensure; +use plonky2_field::extension::Extendable; + +use crate::fri::proof::{FriProof, FriQueryRound, FriQueryStep}; +use crate::fri::structure::FriInstanceInfo; +use crate::fri::FriParams; +use crate::hash::hash_types::RichField; +use crate::plonk::config::GenericConfig; + +pub(crate) fn validate_fri_proof_shape( + proof: &FriProof, + instance: &FriInstanceInfo, + params: &FriParams, +) -> anyhow::Result<()> +where + F: RichField + Extendable, + C: GenericConfig, +{ + let FriProof { + commit_phase_merkle_caps, + query_round_proofs, + final_poly, + pow_witness: _pow_witness, + } = proof; + + let cap_height = params.config.cap_height; + for cap in commit_phase_merkle_caps { + ensure!(cap.height() == cap_height); + } + + for query_round in query_round_proofs { + let FriQueryRound { + initial_trees_proof, + steps, + } = query_round; + + ensure!(initial_trees_proof.evals_proofs.len() == instance.oracles.len()); + for ((leaf, merkle_proof), oracle) in initial_trees_proof + .evals_proofs + .iter() + .zip(&instance.oracles) + { + ensure!(leaf.len() == oracle.num_polys); // TODO: Account for blinding if ZK? + ensure!(merkle_proof.len() + cap_height == params.lde_bits()); + } + + ensure!(steps.len() == params.reduction_arity_bits.len()); + let mut codeword_len_bits = params.lde_bits(); + for (step, arity_bits) in steps.iter().zip(¶ms.reduction_arity_bits) { + let FriQueryStep { + evals, + merkle_proof, + } = step; + + let arity = 1 << arity_bits; + codeword_len_bits -= arity_bits; + + ensure!(evals.len() == arity); + ensure!(merkle_proof.len() + cap_height == codeword_len_bits); + } + } + + ensure!(final_poly.len() == params.final_poly_len()); + + Ok(()) +} diff --git a/plonky2/src/fri/verifier.rs b/plonky2/src/fri/verifier.rs index ed44f0c4..02816000 100644 --- a/plonky2/src/fri/verifier.rs +++ b/plonky2/src/fri/verifier.rs @@ -6,6 +6,7 @@ use plonky2_util::{log2_strict, reverse_index_bits_in_place}; use crate::fri::proof::{FriChallenges, FriInitialTreeProof, FriProof, FriQueryRound}; use crate::fri::structure::{FriBatchInfo, FriInstanceInfo, FriOpenings}; +use crate::fri::validate_shape::validate_fri_proof_shape; use crate::fri::{FriConfig, FriParams}; use crate::hash::hash_types::RichField; use crate::hash::merkle_proofs::verify_merkle_proof_to_cap; @@ -67,10 +68,7 @@ pub fn verify_fri_proof, C: GenericConfig where [(); C::Hasher::HASH_SIZE]:, { - ensure!( - params.final_poly_len() == proof.final_poly.len(), - "Final polynomial has wrong degree." - ); + validate_fri_proof_shape::(proof, instance, params)?; // Size of the LDE domain. let n = params.lde_size(); diff --git a/plonky2/src/hash/merkle_proofs.rs b/plonky2/src/hash/merkle_proofs.rs index 90d55ce1..f54793d9 100644 --- a/plonky2/src/hash/merkle_proofs.rs +++ b/plonky2/src/hash/merkle_proofs.rs @@ -17,6 +17,12 @@ pub struct MerkleProof> { pub siblings: Vec, } +impl> MerkleProof { + pub fn len(&self) -> usize { + self.siblings.len() + } +} + #[derive(Clone, Debug)] pub struct MerkleProofTarget { /// The Merkle digest of each sibling subtree, staying from the bottommost layer. diff --git a/plonky2/src/hash/merkle_tree.rs b/plonky2/src/hash/merkle_tree.rs index 1da66bff..703a353e 100644 --- a/plonky2/src/hash/merkle_tree.rs +++ b/plonky2/src/hash/merkle_tree.rs @@ -21,6 +21,10 @@ impl> MerkleCap { self.0.len() } + pub fn height(&self) -> usize { + log2_strict(self.len()) + } + pub fn flatten(&self) -> Vec { self.0.iter().flat_map(|&h| h.to_vec()).collect() } diff --git a/plonky2/src/plonk/circuit_data.rs b/plonky2/src/plonk/circuit_data.rs index 20697d36..f2363f41 100644 --- a/plonky2/src/plonk/circuit_data.rs +++ b/plonky2/src/plonk/circuit_data.rs @@ -9,7 +9,8 @@ use crate::field::types::Field; use crate::fri::oracle::PolynomialBatch; use crate::fri::reduction_strategies::FriReductionStrategy; use crate::fri::structure::{ - FriBatchInfo, FriBatchInfoTarget, FriInstanceInfo, FriInstanceInfoTarget, FriPolynomialInfo, + FriBatchInfo, FriBatchInfoTarget, FriInstanceInfo, FriInstanceInfoTarget, FriOracleInfo, + FriPolynomialInfo, }; use crate::fri::{FriConfig, FriParams}; use crate::gates::gate::GateRef; @@ -22,7 +23,7 @@ use crate::iop::target::Target; use crate::iop::witness::PartialWitness; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::config::{GenericConfig, Hasher}; -use crate::plonk::plonk_common::{PlonkOracle, FRI_ORACLES}; +use crate::plonk::plonk_common::PlonkOracle; use crate::plonk::proof::{CompressedProofWithPublicInputs, ProofWithPublicInputs}; use crate::plonk::prover::prove; use crate::plonk::verifier::verify; @@ -342,7 +343,7 @@ impl, C: GenericConfig, const D: usize> let openings = vec![zeta_batch, zeta_next_batch]; FriInstanceInfo { - oracles: FRI_ORACLES.to_vec(), + oracles: self.fri_oracles(), batches: openings, } } @@ -368,11 +369,32 @@ impl, C: GenericConfig, const D: usize> let openings = vec![zeta_batch, zeta_next_batch]; FriInstanceInfoTarget { - oracles: FRI_ORACLES.to_vec(), + oracles: self.fri_oracles(), batches: openings, } } + fn fri_oracles(&self) -> Vec { + vec![ + FriOracleInfo { + num_polys: self.num_preprocessed_polys(), + blinding: PlonkOracle::CONSTANTS_SIGMAS.blinding, + }, + FriOracleInfo { + num_polys: self.config.num_wires, + blinding: PlonkOracle::WIRES.blinding, + }, + FriOracleInfo { + num_polys: self.num_zs_partial_products_polys(), + blinding: PlonkOracle::ZS_PARTIAL_PRODUCTS.blinding, + }, + FriOracleInfo { + num_polys: self.num_quotient_polys(), + blinding: PlonkOracle::QUOTIENT.blinding, + }, + ] + } + fn fri_preprocessed_polys(&self) -> Vec { FriPolynomialInfo::from_range( PlonkOracle::CONSTANTS_SIGMAS.index, diff --git a/plonky2/src/plonk/mod.rs b/plonky2/src/plonk/mod.rs index 4f2fa4e1..73e6c96e 100644 --- a/plonky2/src/plonk/mod.rs +++ b/plonky2/src/plonk/mod.rs @@ -8,6 +8,7 @@ pub mod plonk_common; pub mod proof; pub mod prover; pub mod recursive_verifier; +mod validate_shape; pub(crate) mod vanishing_poly; pub mod vars; pub mod verifier; diff --git a/plonky2/src/plonk/plonk_common.rs b/plonky2/src/plonk/plonk_common.rs index e947353b..24a94bb3 100644 --- a/plonky2/src/plonk/plonk_common.rs +++ b/plonky2/src/plonk/plonk_common.rs @@ -3,7 +3,6 @@ use plonky2_field::packed::PackedField; use plonky2_field::types::Field; use crate::fri::oracle::SALT_SIZE; -use crate::fri::structure::FriOracleInfo; use crate::gates::arithmetic_base::ArithmeticGate; use crate::hash::hash_types::RichField; use crate::iop::ext_target::ExtensionTarget; @@ -11,13 +10,6 @@ use crate::iop::target::Target; use crate::plonk::circuit_builder::CircuitBuilder; use crate::util::reducing::ReducingFactorTarget; -pub(crate) const FRI_ORACLES: [FriOracleInfo; 4] = [ - PlonkOracle::CONSTANTS_SIGMAS.as_fri_oracle(), - PlonkOracle::WIRES.as_fri_oracle(), - PlonkOracle::ZS_PARTIAL_PRODUCTS.as_fri_oracle(), - PlonkOracle::QUOTIENT.as_fri_oracle(), -]; - /// Holds the Merkle tree index and blinding flag of a set of polynomials used in FRI. #[derive(Debug, Copy, Clone)] pub struct PlonkOracle { @@ -42,12 +34,6 @@ impl PlonkOracle { index: 3, blinding: true, }; - - pub(crate) const fn as_fri_oracle(&self) -> FriOracleInfo { - FriOracleInfo { - blinding: self.blinding, - } - } } pub fn salt_size(salted: bool) -> usize { diff --git a/plonky2/src/plonk/validate_shape.rs b/plonky2/src/plonk/validate_shape.rs new file mode 100644 index 00000000..9d36110b --- /dev/null +++ b/plonky2/src/plonk/validate_shape.rs @@ -0,0 +1,67 @@ +use anyhow::ensure; +use plonky2_field::extension::Extendable; + +use crate::hash::hash_types::RichField; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::plonk::config::{GenericConfig, Hasher}; +use crate::plonk::proof::{Proof, ProofWithPublicInputs}; + +pub(crate) fn validate_proof_with_pis_shape( + proof_with_pis: &ProofWithPublicInputs, + common_data: &CommonCircuitData, +) -> anyhow::Result<()> +where + F: RichField + Extendable, + C: GenericConfig, + [(); C::Hasher::HASH_SIZE]:, +{ + let ProofWithPublicInputs { + proof, + public_inputs, + } = proof_with_pis; + + validate_proof_shape(proof, common_data)?; + + ensure!( + public_inputs.len() == common_data.num_public_inputs, + "Number of public inputs doesn't match circuit data." + ); + + Ok(()) +} + +fn validate_proof_shape( + proof: &Proof, + common_data: &CommonCircuitData, +) -> anyhow::Result<()> +where + F: RichField + Extendable, + C: GenericConfig, + [(); C::Hasher::HASH_SIZE]:, +{ + let config = &common_data.config; + let Proof { + wires_cap, + plonk_zs_partial_products_cap, + quotient_polys_cap, + openings, + // The shape of the opening proof will be checked in the FRI verifier (see + // validate_fri_proof_shape), so we ignore it here. + opening_proof: _, + } = proof; + + let cap_height = common_data.fri_params.config.cap_height; + ensure!(wires_cap.height() == cap_height); + ensure!(plonk_zs_partial_products_cap.height() == cap_height); + ensure!(quotient_polys_cap.height() == cap_height); + + ensure!(openings.constants.len() == common_data.num_constants); + ensure!(openings.plonk_sigmas.len() == config.num_routed_wires); + ensure!(openings.wires.len() == config.num_wires); + ensure!(openings.plonk_zs.len() == config.num_challenges); + ensure!(openings.plonk_zs_next.len() == config.num_challenges); + ensure!(openings.partial_products.len() == common_data.num_partial_products); + ensure!(openings.quotient_polys.len() == common_data.num_quotient_polys()); + + Ok(()) +} diff --git a/plonky2/src/plonk/verifier.rs b/plonky2/src/plonk/verifier.rs index 13821ff3..6a4f3790 100644 --- a/plonky2/src/plonk/verifier.rs +++ b/plonky2/src/plonk/verifier.rs @@ -8,6 +8,7 @@ use crate::plonk::circuit_data::{CommonCircuitData, VerifierOnlyCircuitData}; use crate::plonk::config::{GenericConfig, Hasher}; use crate::plonk::plonk_common::reduce_with_powers; use crate::plonk::proof::{Proof, ProofChallenges, ProofWithPublicInputs}; +use crate::plonk::validate_shape::validate_proof_with_pis_shape; use crate::plonk::vanishing_poly::eval_vanishing_poly; use crate::plonk::vars::EvaluationVars; @@ -19,10 +20,8 @@ pub(crate) fn verify, C: GenericConfig, c where [(); C::Hasher::HASH_SIZE]:, { - ensure!( - proof_with_pis.public_inputs.len() == common_data.num_public_inputs, - "Number of public inputs doesn't match circuit data." - ); + validate_proof_with_pis_shape(&proof_with_pis, common_data)?; + let public_inputs_hash = proof_with_pis.get_public_inputs_hash(); let challenges = proof_with_pis.get_challenges(public_inputs_hash, common_data)?; diff --git a/starky/src/stark.rs b/starky/src/stark.rs index df549572..7f0df197 100644 --- a/starky/src/stark.rs +++ b/starky/src/stark.rs @@ -85,25 +85,32 @@ pub trait Stark, const D: usize>: Sync { g: F, config: &StarkConfig, ) -> FriInstanceInfo { - let no_blinding_oracle = FriOracleInfo { blinding: false }; - let mut oracle_indices = 0..; + let mut oracles = vec![]; - let trace_info = - FriPolynomialInfo::from_range(oracle_indices.next().unwrap(), 0..Self::COLUMNS); + let trace_info = FriPolynomialInfo::from_range(oracles.len(), 0..Self::COLUMNS); + oracles.push(FriOracleInfo { + num_polys: Self::COLUMNS, + blinding: false, + }); let permutation_zs_info = if self.uses_permutation_args() { - FriPolynomialInfo::from_range( - oracle_indices.next().unwrap(), - 0..self.num_permutation_batches(config), - ) + let num_z_polys = self.num_permutation_batches(config); + let polys = FriPolynomialInfo::from_range(oracles.len(), 0..num_z_polys); + oracles.push(FriOracleInfo { + num_polys: num_z_polys, + blinding: false, + }); + polys } else { vec![] }; - let quotient_info = FriPolynomialInfo::from_range( - oracle_indices.next().unwrap(), - 0..self.quotient_degree_factor() * config.num_challenges, - ); + let num_quotient_polys = self.quotient_degree_factor() * config.num_challenges; + let quotient_info = FriPolynomialInfo::from_range(oracles.len(), 0..num_quotient_polys); + oracles.push(FriOracleInfo { + num_polys: num_quotient_polys, + blinding: false, + }); let zeta_batch = FriBatchInfo { point: zeta, @@ -118,10 +125,9 @@ pub trait Stark, const D: usize>: Sync { point: zeta.scalar_mul(g), polynomials: [trace_info, permutation_zs_info].concat(), }; - FriInstanceInfo { - oracles: vec![no_blinding_oracle; oracle_indices.next().unwrap()], - batches: vec![zeta_batch, zeta_next_batch], - } + let batches = vec![zeta_batch, zeta_next_batch]; + + FriInstanceInfo { oracles, batches } } /// Computes the FRI instance used to prove this Stark. @@ -132,25 +138,32 @@ pub trait Stark, const D: usize>: Sync { g: F, config: &StarkConfig, ) -> FriInstanceInfoTarget { - let no_blinding_oracle = FriOracleInfo { blinding: false }; - let mut oracle_indices = 0..; + let mut oracles = vec![]; - let trace_info = - FriPolynomialInfo::from_range(oracle_indices.next().unwrap(), 0..Self::COLUMNS); + let trace_info = FriPolynomialInfo::from_range(oracles.len(), 0..Self::COLUMNS); + oracles.push(FriOracleInfo { + num_polys: Self::COLUMNS, + blinding: false, + }); let permutation_zs_info = if self.uses_permutation_args() { - FriPolynomialInfo::from_range( - oracle_indices.next().unwrap(), - 0..self.num_permutation_batches(config), - ) + let num_z_polys = self.num_permutation_batches(config); + let polys = FriPolynomialInfo::from_range(oracles.len(), 0..num_z_polys); + oracles.push(FriOracleInfo { + num_polys: num_z_polys, + blinding: false, + }); + polys } else { vec![] }; - let quotient_info = FriPolynomialInfo::from_range( - oracle_indices.next().unwrap(), - 0..self.quotient_degree_factor() * config.num_challenges, - ); + let num_quotient_polys = self.quotient_degree_factor() * config.num_challenges; + let quotient_info = FriPolynomialInfo::from_range(oracles.len(), 0..num_quotient_polys); + oracles.push(FriOracleInfo { + num_polys: num_quotient_polys, + blinding: false, + }); let zeta_batch = FriBatchInfoTarget { point: zeta, @@ -166,10 +179,9 @@ pub trait Stark, const D: usize>: Sync { point: zeta_next, polynomials: [trace_info, permutation_zs_info].concat(), }; - FriInstanceInfoTarget { - oracles: vec![no_blinding_oracle; oracle_indices.next().unwrap()], - batches: vec![zeta_batch, zeta_next_batch], - } + let batches = vec![zeta_batch, zeta_next_batch]; + + FriInstanceInfoTarget { oracles, batches } } /// Pairs of lists of columns that should be permutations of one another. A permutation argument