Skip to content

Introduce Absorb/Challenge traits #636

@rot256

Description

@rot256

Currently the Sponge type looks like this:

impl<P: SWModelParameters, SC: SpongeConstants>
    FqSponge<P::BaseField, GroupAffine<P>, P::ScalarField> for DefaultFqSponge<P, SC>
where
    P::BaseField: PrimeField,
    <P::BaseField as PrimeField>::BigInt: Into<<P::ScalarField as PrimeField>::BigInt>,
{
    fn new(params: ArithmeticSpongeParams<P::BaseField>) -> DefaultFqSponge<P, SC> {
        ...
    }

    fn absorb_g(&mut self, g: &[GroupAffine<P>]) {
        ...
    }

    fn absorb_fr(&mut self, x: &[P::ScalarField]) {
        ...
    }

    fn digest(mut self) -> P::ScalarField {
        ...
    }

    fn challenge(&mut self) -> P::ScalarField {
        ...
    }

    fn challenge_fq(&mut self) -> P::BaseField {
        ...
    }
}

Meaning there is a method for every type you can absorb and every type you can squeeze. I suggest getting rid of this in favor of using two traits: one for "absorbable" objects and one for "squeezable" objects.

Concretely I suggest adding traits like these:

pub trait Challenge<F: FftField + PrimeField> {
    fn generate(sponge: &mut Sponge<F>) -> Self;
}
pub trait Absorb<F: FftField + PrimeField> {
    fn absorb(&self, sponge: &mut Sponge<F>);
}

And changing the sponge type so it looks more like this:

impl<F: FftField + PrimeField> Sponge<F> {
    pub fn new(constants: Constants<F>) -> Self {
        ...
    }

    // absorb an instance of T using the sponge
    pub fn absorb<T: Absorb<F>>(&mut self, val: &T) {
        val.absorb(cs, self);
    }
    
    // sample an instance of T using the sponge
    pub fn challenge<T: Challenge<F>>(&mut self) ->  T {
        T::generate(self);
    }
}

In addition we provide a few special implementations of Challenge<F> and Absorb<F>:

// can absorb a element from the same field
impl<F: FftField + PrimeField> Absorb<F> for F {
    fn absorb(&self, sponge: &mut Sponge<F>) {
        ...
    }
}
// can generate a variable from the same field (a digest)
impl<F: FftField + PrimeField> Challenge<F> for F {
    fn generate(sponge: &mut Sponge<F>) -> Self {
        ...
    }
}

This enables describing/deriving absorbing/squeezing of more complex types elsewhere in terms of these "base" implementation, e.g.

// can absorb a slice of absorbable elements
impl<F: FftField + PrimeField, T: Absorb<F>> Absorb<F> for [T] {
    fn absorb(&self,  sponge: &mut Sponge<F>) {
        self.iter().for_each(|c| c.absorb(sponge))
    }
}

// define a curve point type
pub struct Point<G>
where
    G: AffineCurve,
    G::BaseField: FftField + PrimeField,
{
    _ph: PhantomData<G>,
    x: G::BaseField,
    y: G::BaseField,
}

// can absorb curve points using a base field sponge
impl<G> Absorb<G::BaseField> for Point<G>
where
    G: AffineCurve,
    G::BaseField: FftField + PrimeField,
{
    fn absorb(&self, sponge: &mut Sponge<G::BaseField>) {
        sponge.absorb(&self.x);
        sponge.absorb(&self.y);
    }
}

Without changing the Sponge type/trait, this also enables implementing the traits for new types outside the module. In particular the above allows:

sponge.absorb(&points);

For a sequence of points (exactly like sponge.absorb_g currently), but also enables sponge.absorb(&field_elements) without changing the sponge type.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions