Skip to content

Commit

Permalink
fix: speed up sqrt and inversion for FP, FP2, using unconstrained
Browse files Browse the repository at this point in the history
- Added new optional 'sphinx-zkvm' dependency and 'zkvm' feature in Cargo.toml
- Modified `sqrt` method in `Fp2` file, adding differentiated execution logic based on the `target_os`
- Adjusted `Fp2` operations to handle quadratic nonresidue during the square root computation effectively and to conform to environment specifications.
  • Loading branch information
huitseeker committed Aug 16, 2024
1 parent 9334926 commit 22045d0
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 65 deletions.
10 changes: 8 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,19 @@ version = "0.6"
default-features = false

[dependencies.subtle]
version = "2.2.1"
version = "2.6.1"
default-features = false

[dependencies.zeroize]
version = "1.4"
version = "1.8"
default-features = false
optional = true

[dependencies.sphinx-zkvm]
git = "https://github.com/argumentcomputer/sphinx"
branch = "dev"
optional = true

[features]
default = ["groups", "pairings", "alloc", "bits"]
bits = ["ff/bits"]
Expand All @@ -74,3 +79,4 @@ pairings = ["groups", "pairing"]
alloc = ["group/alloc"]
experimental = ["digest"]
nightly = ["subtle/nightly"]
zkvm = ["dep:sphinx-zkvm"]
71 changes: 52 additions & 19 deletions src/fp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,14 +347,29 @@ impl Fp {
// works for elements that are actually quadratic residue,
// so we check that we got the correct result at the end.

let sqrt = self.pow_vartime(&[
0xee7f_bfff_ffff_eaab,
0x07aa_ffff_ac54_ffff,
0xd9cc_34a8_3dac_3d89,
0xd91d_d2e1_3ce1_44af,
0x92c6_e9ed_90d2_eb35,
0x0680_447a_8e5f_f9a6,
]);
cfg_if::cfg_if! {
// NOTE: the branches are meant to be identical except for the use of the unconstrained! macro
if #[cfg(target_os = "zkvm")] {
let sqrt = sphinx_zkvm::precompiles::unconstrained!(self.pow_vartime(&[
0xee7f_bfff_ffff_eaab,
0x07aa_ffff_ac54_ffff,
0xd9cc_34a8_3dac_3d89,
0xd91d_d2e1_3ce1_44af,
0x92c6_e9ed_90d2_eb35,
0x0680_447a_8e5f_f9a6,
]));
}
else {
let sqrt = self.pow_vartime(&[
0xee7f_bfff_ffff_eaab,
0x07aa_ffff_ac54_ffff,
0xd9cc_34a8_3dac_3d89,
0xd91d_d2e1_3ce1_44af,
0x92c6_e9ed_90d2_eb35,
0x0680_447a_8e5f_f9a6,
]);
}
}

CtOption::new(sqrt, sqrt.square().ct_eq(self))
}
Expand All @@ -364,17 +379,35 @@ impl Fp {
/// element, returning None in the case that this element
/// is zero.
pub fn invert(&self) -> CtOption<Self> {
// Exponentiate by p - 2
let t = self.pow_vartime(&[
0xb9fe_ffff_ffff_aaa9,
0x1eab_fffe_b153_ffff,
0x6730_d2a0_f6b0_f624,
0x6477_4b84_f385_12bf,
0x4b1b_a7b6_434b_acd7,
0x1a01_11ea_397f_e69a,
]);

CtOption::new(t, !self.is_zero())
cfg_if::cfg_if! {
// NOTE: the branches are meant to be identical except for the use of the unconstrained! macro
if #[cfg(target_os = "zkvm")] {
// Exponentiate by p - 2
let t = sphinx_zkvm::precompiles::unconstrained!(self.pow_vartime(&[
0xb9fe_ffff_ffff_aaa9,
0x1eab_fffe_b153_ffff,
0x6730_d2a0_f6b0_f624,
0x6477_4b84_f385_12bf,
0x4b1b_a7b6_434b_acd7,
0x1a01_11ea_397f_e69a,
]));

CtOption::new(t, !self.is_zero() && (self.mul(&t)).ct_eq(&Fp::one()))
}
else {
// Exponentiate by p - 2
let t = self.pow_vartime(&[
0xb9fe_ffff_ffff_aaa9,
0x1eab_fffe_b153_ffff,
0x6730_d2a0_f6b0_f624,
0x6477_4b84_f385_12bf,
0x4b1b_a7b6_434b_acd7,
0x1a01_11ea_397f_e69a,
]);

CtOption::new(t, !self.is_zero())
}
}
}

#[inline]
Expand Down
140 changes: 96 additions & 44 deletions src/fp2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,52 +400,104 @@ impl Fp2 {
// Algorithm 9, https://eprint.iacr.org/2012/685.pdf
// with constant time modifications.

CtOption::new(Fp2::zero(), self.is_zero()).or_else(|| {
// a1 = self^((p - 3) / 4)
let a1 = self.pow_vartime(&[
0xee7f_bfff_ffff_eaaa,
0x07aa_ffff_ac54_ffff,
0xd9cc_34a8_3dac_3d89,
0xd91d_d2e1_3ce1_44af,
0x92c6_e9ed_90d2_eb35,
0x0680_447a_8e5f_f9a6,
]);

// alpha = a1^2 * self = self^((p - 3) / 2 + 1) = self^((p - 1) / 2)
let alpha = a1.square() * self;

// x0 = self^((p + 1) / 4)
let x0 = a1 * self;

// In the event that alpha = -1, the element is order p - 1 and so
// we're just trying to get the square of an element of the subfield
// Fp. This is given by x0 * u, since u = sqrt(-1). Since the element
// x0 = a + bu has b = 0, the solution is therefore au.
CtOption::new(
Fp2 {
c0: -x0.c1,
c1: x0.c0,
},
alpha.ct_eq(&(&Fp2::one()).neg()),
)
// Otherwise, the correct solution is (1 + alpha)^((q - 1) // 2) * x0
.or_else(|| {
cfg_if::cfg_if! {
// NOTE: the branches are meant to be identical except for the use of the unconstrained! macro
if #[cfg(target_os = "zkvm")] {
let sqrt_option = unconstrained!(
CtOption::new(Fp2::zero(), self.is_zero()).or_else(|| {
// a1 = self^((p - 3) / 4)
let a1 = self.pow_vartime(&[
0xee7f_bfff_ffff_eaaa,
0x07aa_ffff_ac54_ffff,
0xd9cc_34a8_3dac_3d89,
0xd91d_d2e1_3ce1_44af,
0x92c6_e9ed_90d2_eb35,
0x0680_447a_8e5f_f9a6,
]);

// alpha = a1^2 * self = self^((p - 3) / 2 + 1) = self^((p - 1) / 2)
let alpha = a1.square() * self;

// x0 = self^((p + 1) / 4)
let x0 = a1 * self;

// In the event that alpha = -1, the element is order p - 1 and so
// we're just trying to get the square of an element of the subfield
// Fp. This is given by x0 * u, since u = sqrt(-1). Since the element
// x0 = a + bu has b = 0, the solution is therefore au.
CtOption::new(
Fp2 {
c0: -x0.c1,
c1: x0.c0,
},
alpha.ct_eq(&(&Fp2::one()).neg()),
)
// Otherwise, the correct solution is (1 + alpha)^((q - 1) // 2) * x0
.or_else(|| {
CtOption::new(
(alpha + Fp2::one()).pow_vartime(&[
0xdcff_7fff_ffff_d555,
0x0f55_ffff_58a9_ffff,
0xb398_6950_7b58_7b12,
0xb23b_a5c2_79c2_895f,
0x258d_d3db_21a5_d66b,
0x0d00_88f5_1cbf_f34d,
]) * x0,
Choice::from(1),
)
})
})
);
} else {
let sqrt_option = CtOption::new(Fp2::zero(), self.is_zero()).or_else(|| {
// a1 = self^((p - 3) / 4)
let a1 = self.pow_vartime(&[
0xee7f_bfff_ffff_eaaa,
0x07aa_ffff_ac54_ffff,
0xd9cc_34a8_3dac_3d89,
0xd91d_d2e1_3ce1_44af,
0x92c6_e9ed_90d2_eb35,
0x0680_447a_8e5f_f9a6,
]);

// alpha = a1^2 * self = self^((p - 3) / 2 + 1) = self^((p - 1) / 2)
let alpha = a1.square() * self;

// x0 = self^((p + 1) / 4)
let x0 = a1 * self;

// In the event that alpha = -1, the element is order p - 1 and so
// we're just trying to get the square of an element of the subfield
// Fp. This is given by x0 * u, since u = sqrt(-1). Since the element
// x0 = a + bu has b = 0, the solution is therefore au.
CtOption::new(
(alpha + Fp2::one()).pow_vartime(&[
0xdcff_7fff_ffff_d555,
0x0f55_ffff_58a9_ffff,
0xb398_6950_7b58_7b12,
0xb23b_a5c2_79c2_895f,
0x258d_d3db_21a5_d66b,
0x0d00_88f5_1cbf_f34d,
]) * x0,
Choice::from(1),
Fp2 {
c0: -x0.c1,
c1: x0.c0,
},
alpha.ct_eq(&(&Fp2::one()).neg()),
)
})
// Only return the result if it's really the square root (and so
// self is actually quadratic nonresidue)
.and_then(|sqrt| CtOption::new(sqrt, sqrt.square().ct_eq(self)))
})
// Otherwise, the correct solution is (1 + alpha)^((q - 1) // 2) * x0
.or_else(|| {
CtOption::new(
(alpha + Fp2::one()).pow_vartime(&[
0xdcff_7fff_ffff_d555,
0x0f55_ffff_58a9_ffff,
0xb398_6950_7b58_7b12,
0xb23b_a5c2_79c2_895f,
0x258d_d3db_21a5_d66b,
0x0d00_88f5_1cbf_f34d,
]) * x0,
Choice::from(1),
)
})
});
}
}

// Only return the result if it's really the square root (and so
// self is actually quadratic nonresidue)
sqrt_option.and_then(|sqrt| CtOption::new(sqrt, sqrt.square().ct_eq(self)))
}

/// Computes the multiplicative inverse of this field
Expand Down

0 comments on commit 22045d0

Please sign in to comment.