Initial Inspection
On the surface, this puzzle looks like a discrete logarithm problem: we must recover some secret value given etc. Actually, this variant where additional points ... are given is sometimes called the -discrete logarithm (or -strong discrete logarithm) problem. In some cases, this auxiliary information enables to speed up the computation of using Cheon's algorithm [Che10]. Note also that the assumption that the -discrete log problem is hard implies the soundness of the Groth16 proof system in a restricted model of computation called the algebraic group model [FKL18].
Let's take a look at the code. The package directory is organized as follows:
zkhack-trusted-setup
├── Cargo.toml
└── src
├── bin
│ └── verify-trusted-setup.rs
├── data.rs
└── lib.rs
As with the previous puzzle, the package has two crates: a library crate with root file src/lib.rs which simply declares a module data
and a string slice with the puzzle description and a binary crate with root file src/bin/verify-trusted-setup.rs which contains the following code:
use ark_bls12_381::Fr;
use ark_ec::AffineCurve;
use prompt::{puzzle, welcome};
use std::str::FromStr;
use trusted_setup::data::puzzle_data;
use trusted_setup::PUZZLE_DESCRIPTION;
fn main() {
welcome();
puzzle(PUZZLE_DESCRIPTION);
let (_ts1, _ts2) = puzzle_data();
/* Your solution here! (s in decimal)*/
let s = Fr::from_str("0").unwrap();
assert_eq!(_ts1[0].mul(s), _ts1[1]);
assert_eq!(_ts2[0].mul(s), _ts2[1]);
}
We can see that the main
function simply loads the puzzle data and expects us to replace "0" with the correct value for so that the two final assertions evaluate to true and the program does not panic anymore.
Note that ark_bls12_381::Fr
, the scalar field of the BLS12-381 pairing-friendly elliptic curve, is brought into scope.
The code for the data
module in src/data.rs, where the puzzle_data
function is defined, looks like this:
use ark_bls12_381::{G1Affine, G2Affine};
use ark_serialize::CanonicalDeserialize;
use std::io::Cursor;
pub fn puzzle_data() -> ([G1Affine; 2 * 32 - 1 + 32 + 32], [G2Affine; 32]) {
// ...
}
The puzzle_data
function returns two arrays: _ts1
which holds 127 elements of type G1Affine
(the type representing elements of group of BLS12-381 in affine representation) and _ts2
which holds 32 elements of type G2Affine
(the type representing elements of group of BLS12-381 in affine representation).
From this and the puzzle description we can infer that _ts1
holds
while _ts2
holds
Importantly, and above do not refer to the commonly agreed subgroup generators of BLS12-381 but to the points _ts1[0]
and _ts2[0]
provided by Alice.
Taking a closer look at how these values are defined, we can see that puzzle_data
calls an associated function named deserialize_unchecked
.
What is it that this function does not check?
We head up towards the ark-ec-0.3.0 crate documentation and search for deserialize_unchecked
, which leads us here.
This function is part of the ark_serialize::CanonicalDeserialize
trait.
The documentation simply says:
fn deserialize_unchecked<R: Read>(reader: R) -> Result<Self, SerializationError>
Reads
self
fromreader
without compression, and without performing validity checks. Should be used only when the input is trusted.
Not very informative, so we jump to the source code:
fn deserialize_unchecked<R: Read>(mut reader: R) -> Result<Self, SerializationError> {
let x: P::BaseField = CanonicalDeserialize::deserialize(&mut reader)?;
let (y, flags): (P::BaseField, SWFlags) =
CanonicalDeserializeWithFlags::deserialize_with_flags(&mut reader)?;
let p = GroupAffine::<P>::new(x, y, flags.is_infinity());
Ok(p)
}
This function simply reads two base field elements and from the buffer and creates a new point in affine coordinates.
What could go wrong?
If there's a deserialize_unchecked
function, maybe there's a sibling which actually checks something? Indeed, the function just above in the source code reads:
fn deserialize_uncompressed<R: Read>(
reader: R,
) -> Result<Self, ark_serialize::SerializationError> {
let p = Self::deserialize_unchecked(reader)?;
if !p.is_in_correct_subgroup_assuming_on_curve() {
return Err(SerializationError::InvalidData);
}
Ok(p)
}
(There is also a deserialize
function which does something similar for points in compressed form, meaning they are encoded with their -coordinate and the sign of )
Here we are: the crucial property which is not checked by deserialize_unchecked
is whether the curve point it returns is in the correct subgroup. What does that mean exactly?