use keccak_sha3::{
packed_chip::{PackedChip, PackedConfig, PACKED_ADVICE_COLS, PACKED_FIXED_COLS},
sha3_256_gadget::{Keccak256, Sha3_256},
};
#[cfg(test)]
use midnight_circuits::{
field::decomposition::chip::P2RDecompositionConfig, testing_utils::FromScratch,
};
use midnight_circuits::{
instructions::AssertionInstructions,
types::{AssignedByte, InnerValue},
CircuitField, ComposableChip,
};
#[cfg(test)]
use midnight_proofs::plonk::Instance;
use midnight_proofs::{
circuit::{Chip, Layouter, Value},
plonk::{Advice, Column, ConstraintSystem, Error, Fixed},
};
use crate::external::{convert_to_bytes, unsafe_convert_to_bytes, NG};
#[derive(Clone, Debug)]
pub struct KeccakSha3Wrapper<F: CircuitField> {
keccak_chip: Keccak256<F, PackedChip<F>>,
sha3_chip: Sha3_256<F, PackedChip<F>>,
native_gadget: NG<F>,
}
impl<F: CircuitField> Chip<F> for KeccakSha3Wrapper<F> {
type Config = PackedConfig;
type Loaded = ();
fn config(&self) -> &Self::Config {
self.keccak_chip.config()
}
fn loaded(&self) -> &Self::Loaded {
self.keccak_chip.loaded()
}
}
impl<F: CircuitField> ComposableChip<F> for KeccakSha3Wrapper<F> {
type SharedResources = (
Column<Fixed>,
[Column<Advice>; PACKED_ADVICE_COLS],
[Column<Fixed>; PACKED_FIXED_COLS],
);
type InstructionDeps = NG<F>;
fn new(config: &Self::Config, sub_chips: &Self::InstructionDeps) -> Self {
let packed_chip = PackedChip::new(config);
Self {
keccak_chip: Keccak256::<F, PackedChip<F>>::new(packed_chip.clone()),
sha3_chip: Sha3_256::<F, PackedChip<F>>::new(packed_chip),
native_gadget: sub_chips.clone(),
}
}
fn load(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
self.keccak_chip.load_table(layouter)
}
fn configure(
meta: &mut ConstraintSystem<F>,
shared_resources: &Self::SharedResources,
) -> Self::Config {
keccak_sha3::packed_chip::PackedChip::configure(
meta,
shared_resources.0,
shared_resources.1,
shared_resources.2,
)
}
}
impl<F: CircuitField> KeccakSha3Wrapper<F> {
fn digest(
&self,
keccak: bool,
layouter: &mut impl Layouter<F>,
input: &[AssignedByte<F>],
) -> Result<[AssignedByte<F>; 32], Error> {
let raw_input = input.iter().map(|b| b.value()).collect::<Vec<Value<u8>>>();
let (reassigned_input, digest) = if keccak {
self.keccak_chip.digest(layouter, &raw_input)
} else {
self.sha3_chip.digest(layouter, &raw_input)
}?;
let reassigned_input =
unsafe_convert_to_bytes(layouter, &self.native_gadget, &reassigned_input)?;
for (original_byte, reassigned_byte) in input.iter().zip(reassigned_input.iter()) {
self.native_gadget.assert_equal(layouter, original_byte, reassigned_byte)?
}
assert_eq!(input.len(), reassigned_input.len());
let digest = convert_to_bytes(layouter, &self.native_gadget, &digest)?;
Ok(digest.try_into().unwrap())
}
pub fn keccak_256_digest(
&self,
layouter: &mut impl Layouter<F>,
input: &[AssignedByte<F>],
) -> Result<[AssignedByte<F>; 32], Error> {
self.digest(true, layouter, input)
}
pub fn sha3_256_digest(
&self,
layouter: &mut impl Layouter<F>,
input: &[AssignedByte<F>],
) -> Result<[AssignedByte<F>; 32], Error> {
self.digest(false, layouter, input)
}
}
#[cfg(test)]
impl<F: CircuitField> FromScratch<F> for KeccakSha3Wrapper<F> {
type Config = (PackedConfig, P2RDecompositionConfig);
fn new_from_scratch(config: &Self::Config) -> Self {
let native_gadget = NG::new_from_scratch(&config.1);
KeccakSha3Wrapper::new(&config.0, &native_gadget)
}
fn configure_from_scratch(
meta: &mut ConstraintSystem<F>,
advice_columns: &mut Vec<Column<Advice>>,
fixed_columns: &mut Vec<Column<Fixed>>,
instance_columns: &[Column<Instance>; 2],
) -> Self::Config {
let native_config =
NG::configure_from_scratch(meta, advice_columns, fixed_columns, instance_columns);
while advice_columns.len() < PACKED_ADVICE_COLS {
advice_columns.push(meta.advice_column());
}
let nb_fixed_needed = std::cmp::max(fixed_columns.len(), PACKED_FIXED_COLS + 1);
while fixed_columns.len() < nb_fixed_needed {
fixed_columns.push(meta.fixed_column());
}
let advice_cols: [_; PACKED_ADVICE_COLS] =
advice_columns[..PACKED_ADVICE_COLS].try_into().unwrap();
let fixed_cols: [_; PACKED_FIXED_COLS] =
fixed_columns[..PACKED_FIXED_COLS].try_into().unwrap();
let constant_column = fixed_columns[PACKED_FIXED_COLS];
let sha_config =
KeccakSha3Wrapper::configure(meta, &(constant_column, advice_cols, fixed_cols));
(sha_config, native_config)
}
fn load_from_scratch(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
self.load(layouter)?;
NG::load_from_scratch(&self.native_gadget, layouter)
}
}
#[cfg(test)]
mod test {
use midnight_circuits::{
field::NativeGadget,
instructions::{hash::HashCPU, HashInstructions},
testing_utils::{test_hash, FromScratch},
types::AssignedByte,
CircuitField,
};
use midnight_curves::Fq;
use midnight_proofs::{
circuit::Layouter,
plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Instance},
};
use sha3::{Digest, Keccak256 as KeccakCpu, Sha3_256 as Sha3Cpu};
use crate::external::keccak_sha3::KeccakSha3Wrapper;
#[derive(Debug, Clone)]
struct Keccak256<F: CircuitField>(KeccakSha3Wrapper<F>);
#[derive(Debug, Clone)]
struct Sha3_256<F: CircuitField>(KeccakSha3Wrapper<F>);
impl<F: CircuitField> HashCPU<u8, [u8; 32]> for Keccak256<F> {
fn hash(input: &[u8]) -> [u8; 32] {
let mut hasher = KeccakCpu::new();
hasher.update(input);
hasher.finalize().into()
}
}
impl<F: CircuitField> HashCPU<u8, [u8; 32]> for Sha3_256<F> {
fn hash(inputs: &[u8]) -> [u8; 32] {
let mut hasher = Sha3Cpu::new();
hasher.update(inputs);
hasher.finalize().into()
}
}
impl<F: CircuitField> HashInstructions<F, AssignedByte<F>, [AssignedByte<F>; 32]> for Keccak256<F> {
fn hash(
&self,
layouter: &mut impl Layouter<F>,
inputs: &[AssignedByte<F>],
) -> Result<[AssignedByte<F>; 32], Error> {
self.0.keccak_256_digest(layouter, inputs)
}
}
impl<F: CircuitField> HashInstructions<F, AssignedByte<F>, [AssignedByte<F>; 32]> for Sha3_256<F> {
fn hash(
&self,
layouter: &mut impl Layouter<F>,
inputs: &[AssignedByte<F>],
) -> Result<[AssignedByte<F>; 32], Error> {
self.0.sha3_256_digest(layouter, inputs)
}
}
impl<F: CircuitField> FromScratch<F> for Keccak256<F> {
type Config = <KeccakSha3Wrapper<F> as FromScratch<F>>::Config;
fn new_from_scratch(config: &Self::Config) -> Self {
Keccak256(KeccakSha3Wrapper::new_from_scratch(config))
}
fn configure_from_scratch(
meta: &mut ConstraintSystem<F>,
advice_columns: &mut Vec<Column<Advice>>,
fixed_columns: &mut Vec<Column<Fixed>>,
instance_columns: &[Column<Instance>; 2],
) -> Self::Config {
KeccakSha3Wrapper::configure_from_scratch(
meta,
advice_columns,
fixed_columns,
instance_columns,
)
}
fn load_from_scratch(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
KeccakSha3Wrapper::load_from_scratch(&self.0, layouter)
}
}
impl<F: CircuitField> FromScratch<F> for Sha3_256<F> {
type Config = <KeccakSha3Wrapper<F> as FromScratch<F>>::Config;
fn new_from_scratch(config: &Self::Config) -> Self {
Sha3_256(KeccakSha3Wrapper::new_from_scratch(config))
}
fn configure_from_scratch(
meta: &mut ConstraintSystem<F>,
advice_columns: &mut Vec<Column<Advice>>,
fixed_columns: &mut Vec<Column<Fixed>>,
instance_columns: &[Column<Instance>; 2],
) -> Self::Config {
KeccakSha3Wrapper::configure_from_scratch(
meta,
advice_columns,
fixed_columns,
instance_columns,
)
}
fn load_from_scratch(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
KeccakSha3Wrapper::load_from_scratch(&self.0, layouter)
}
}
#[test]
fn test_keccak_preimage() {
fn test_wrapper(input_size: usize, cost_model: bool) {
test_hash::<
Fq,
AssignedByte<Fq>,
[AssignedByte<Fq>; 32],
Keccak256<Fq>,
NativeGadget<Fq, _, _>,
>(cost_model, "Keccak_256", input_size);
}
const SHA3_256_RATE: usize = 136;
test_wrapper(256, true);
test_wrapper(SHA3_256_RATE - 2, false);
test_wrapper(SHA3_256_RATE - 1, false);
test_wrapper(SHA3_256_RATE, false);
test_wrapper(SHA3_256_RATE + 1, false);
test_wrapper(SHA3_256_RATE + 2, false);
test_wrapper(2 * SHA3_256_RATE - 2, false);
test_wrapper(2 * SHA3_256_RATE - 1, false);
test_wrapper(2 * SHA3_256_RATE, false);
test_wrapper(2 * SHA3_256_RATE + 1, false);
test_wrapper(2 * SHA3_256_RATE + 2, false);
test_wrapper(4 * SHA3_256_RATE, false);
test_wrapper(0, false);
test_wrapper(1, false);
test_wrapper(2, false);
}
#[test]
fn test_sha3_preimage() {
fn test_wrapper(input_size: usize, cost_model: bool) {
test_hash::<
Fq,
AssignedByte<Fq>,
[AssignedByte<Fq>; 32],
Sha3_256<Fq>,
NativeGadget<Fq, _, _>,
>(cost_model, "Sha3_256", input_size);
}
const SHA3_256_RATE: usize = 136;
test_wrapper(256, true);
test_wrapper(SHA3_256_RATE - 2, false);
test_wrapper(SHA3_256_RATE - 1, false);
test_wrapper(SHA3_256_RATE, false);
test_wrapper(SHA3_256_RATE + 1, false);
test_wrapper(SHA3_256_RATE + 2, false);
test_wrapper(2 * SHA3_256_RATE - 2, false);
test_wrapper(2 * SHA3_256_RATE - 1, false);
test_wrapper(2 * SHA3_256_RATE, false);
test_wrapper(2 * SHA3_256_RATE + 1, false);
test_wrapper(2 * SHA3_256_RATE + 2, false);
test_wrapper(4 * SHA3_256_RATE, false);
test_wrapper(0, false);
test_wrapper(1, false);
test_wrapper(2, false);
}
}