Signatures

The implemented lattice-based signature schemes can be found in the module qfall_crypto::constructions::signature. All of them implement the trait SignatureScheme and thus the functions gen, sign, and vfy for the key generation, signing and verification according to some public parameters, which are stored in the struct of each implemented scheme.

With the provided functionality, it is easy to setup a scheme, and sign and verify messages:

use qfall_crypto::construction::signature::{SignatureScheme, FDH};

fn signing_and_verifying() {
    // setup public parameters and generate key-pair
    let mut fdh = FDH::init_gpv(10, 512, 42);
    let (pk, sk) = fdh.gen();

    // sign and verify a message
    let sigma = fdh.sign("Hello World!".to_owned(), &sk, &pk);
    assert!(fdh.vfy("Hello World!".to_owned(), &sigma, &pk))
}

The signature schemes we have implemented are FDH and PFDH signature schemes that build upon any kind of PSF. We have implemented a general implementation of FDH and PFDH, such that instantiation with any valid PSF and correspondingly chosen parameters directly yields a valid signature scheme.

As the FDH signature scheme is stateful and requires storage, the signature scheme must also be serializable. As the combination of

  • an implementation that is as general as possible
  • an implementation that is serializable

is quite hard to do in code, you might see some types called PhantomData. This is only used for type-binding of the correspondingly used PSF and does not take any memory or appears in the serialization of the signature scheme.

A serialization looks as follows:

use qfall_crypto::construction::{
    hash::sha256::HashMatZq,
    signature::{SignatureScheme, FDH},
};
use qfall_crypto::primitive::psf::PSFGPV;
use qfall_math::{integer::MatZ, integer_mod_q::MatZq, rational::MatQ};

fn serialize_and_deserialize() {
    // setup public parameters and generate key-pair
    let mut fdh = FDH::init_gpv(10, 1024, 42);
    let (pk, sk) = fdh.gen();

    // sign one message
    let _ = fdh.sign("Hello World!".to_owned(), &sk, &pk);

    // serialize the signature scheme
    let fdh_string = serde_json::to_string(&fdh).unwrap();

    // deserialize the signature scheme together with the storage
    let fdh_deserialized: FDH<MatZq, (MatZ, MatQ), MatZ, MatZq, PSFGPV, HashMatZq> =
        serde_json::from_str(&fdh_string).unwrap();
}

The part FDH<MatZq, (MatZ, MatQ), MatZ, MatZq, PSFGPV, HashMatZq> might look a little irritating at first. Our implementation for a signature scheme is as general as possible, hence each implementation of an FDH signature scheme uses the same FDH struct, only with different parameters. The parameters <MatZq, (MatZ, MatQ), MatZ, MatZq, PSFGPV, HashMatZq> define the corresponding PSF and hash function used for the signature scheme. Looking at the documentation in the crypto crate this might become clearer FDH<A, Trapdoor, Domain, Range, T: PSF<A, Trapdoor, Domain, Range>, Hash: HashInto<Range>>. Each parameter determines the kind of signature scheme that is implemented. For the ring-based FDH signature scheme, we have FDH<MatPolynomialRingZq, (MatPolyOverZ, MatPolyOverZ), MatPolyOverZ, MatPolynomialRingZq, PSFGPVRing, HashMatPolynomialRingZq> which follows the same principle.

This idea was also applied to the PFDH signature scheme where the classical implementation follows the same pattern PFDH<MatZq, (MatZ, MatQ), MatZ, MatZq, PSFGPV, HashMatZq>.