New Native Operation PublicKeyOf (#355)
* wrote some initial code * added way to input private key into circuit * TypedValue::SecretKey hashed as 10 32-bit limbs * Check PublicKeyOf in Frontend and Middleware * Diff review * PR review * Finish utest * Fix bounds check * added giving secret key witness to circuit * Test & doc improvements * added private key comparison to circuit and added test cases * cargo fmt * Add frontend tests for PublicKeyOf * Add public_key_of and hash_of to op! macro * Add ownership check to ticket example * Group order checking in tests * More negative test cases at circuit level * Cleanups after self review * clippy fixes * Fixes after merge. Temporarily remove plonky2 commit hash * Add a nullifier to the ticket test example * Test PublicKeyOf with a real prover (not mock) * plonky-u32 dependency * feat: optimize operation checks Skip the circuits that verify operation checks other than None, Copy or NewEntry for the public statements. This works because public statements are created by copying private statements, so we never use the other operation checks in those slots. --------- Co-authored-by: Andrew Twyman <artwyman@gmail.com> Co-authored-by: Eduard S. <eduardsanou@posteo.net>
This commit is contained in:
parent
9f8335756c
commit
5b04b2a360
15 changed files with 958 additions and 76 deletions
|
|
@ -552,6 +552,7 @@ pub trait CircuitBuilderElliptic {
|
|||
|
||||
fn add_point(&mut self, p1: &PointTarget, p2: &PointTarget) -> PointTarget;
|
||||
fn double_point(&mut self, p: &PointTarget) -> PointTarget;
|
||||
fn multiply_point(&mut self, p1_scalar: &[BoolTarget; 320], p1: &PointTarget) -> PointTarget;
|
||||
fn linear_combination_points(
|
||||
&mut self,
|
||||
p1_scalar: &[BoolTarget; 320],
|
||||
|
|
@ -654,6 +655,17 @@ impl CircuitBuilderElliptic for CircuitBuilder<GoldilocksField, 2> {
|
|||
*/
|
||||
}
|
||||
|
||||
fn multiply_point(&mut self, p1_scalar: &[BoolTarget; 320], p1: &PointTarget) -> PointTarget {
|
||||
let zero = self.identity_point();
|
||||
let mut ans = zero.clone();
|
||||
for i in (0..320).rev() {
|
||||
ans = self.double_point(&ans);
|
||||
let maybe_p1 = self.if_point(p1_scalar[i], p1, &zero);
|
||||
ans = self.add_point(&ans, &maybe_p1);
|
||||
}
|
||||
ans
|
||||
}
|
||||
|
||||
fn linear_combination_points(
|
||||
&mut self,
|
||||
p1_scalar: &[BoolTarget; 320],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::array;
|
||||
use std::{array, fmt, str::FromStr};
|
||||
|
||||
use num::BigUint;
|
||||
use num_bigint::RandBigInt;
|
||||
|
|
@ -6,7 +6,7 @@ use plonky2::{
|
|||
field::{
|
||||
extension::FieldExtension,
|
||||
goldilocks_field::GoldilocksField,
|
||||
types::{Field, PrimeField},
|
||||
types::{Field, PrimeField, PrimeField64},
|
||||
},
|
||||
hash::{
|
||||
hash_types::HashOutTarget,
|
||||
|
|
@ -173,6 +173,94 @@ impl SecretKey {
|
|||
let s = (nonce + &self.0 * &e) % &*GROUP_ORDER;
|
||||
Signature { s, e }
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> Vec<u8> {
|
||||
let bytes = self.0.to_bytes_le();
|
||||
assert!(bytes.len() <= 40);
|
||||
bytes
|
||||
.into_iter()
|
||||
.chain(std::iter::repeat(0u8))
|
||||
.take(40)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn from_bytes(sk_bytes: &[u8]) -> Result<Self, Error> {
|
||||
if sk_bytes.len() != 40 {
|
||||
return Err(Error::custom(
|
||||
"Invalid byte encoding of Schnorr secret key.".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let big_uint = BigUint::from_bytes_le(sk_bytes);
|
||||
|
||||
if big_uint >= *GROUP_ORDER {
|
||||
return Err(Error::custom(
|
||||
"Invalid Schnorr secret key - not less than group order.".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Self(big_uint))
|
||||
}
|
||||
|
||||
pub fn to_limbs(&self) -> [GoldilocksField; 10] {
|
||||
assert!(self.0.bits() <= 320);
|
||||
let digits = self.0.to_u32_digits();
|
||||
array::from_fn(|i| {
|
||||
let d = digits.get(i).copied().unwrap_or(0);
|
||||
GoldilocksField::from_canonical_u32(d)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_limbs(limbs: [GoldilocksField; 10]) -> Result<Self, Error> {
|
||||
let mut limb_vec = vec![];
|
||||
for gl in limbs.iter() {
|
||||
let g64 = gl.to_canonical_u64();
|
||||
if g64 >= (1 << 32) {
|
||||
return Err(Error::custom(
|
||||
"Invalid limb value in Schnorr secret key.".to_string(),
|
||||
));
|
||||
}
|
||||
limb_vec.push(g64 as u32);
|
||||
}
|
||||
|
||||
Ok(Self(BigUint::from_slice(&limb_vec)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for SecretKey {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let sk_b64 = serialize_bytes(&self.as_bytes());
|
||||
serializer.serialize_str(&sk_b64)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SecretKey {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let sk_b64 = String::deserialize(deserializer)?;
|
||||
let sk_bytes = deserialize_bytes(&sk_b64).map_err(serde::de::Error::custom)?;
|
||||
SecretKey::from_bytes(&sk_bytes).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SecretKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", serialize_bytes(self.as_bytes().as_slice()))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SecretKey {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let sk_bytes = deserialize_bytes(s)?;
|
||||
SecretKey::from_bytes(&sk_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl SignatureTarget {
|
||||
|
|
@ -218,9 +306,14 @@ fn hash_array_circuit(
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use itertools::Itertools;
|
||||
use num::BigUint;
|
||||
use num_bigint::RandBigInt;
|
||||
use plonky2::{
|
||||
field::{goldilocks_field::GoldilocksField, types::Sample},
|
||||
field::{
|
||||
goldilocks_field::GoldilocksField,
|
||||
types::{Field, Sample},
|
||||
},
|
||||
iop::{
|
||||
target::Target,
|
||||
witness::{PartialWitness, WitnessWrite},
|
||||
|
|
@ -253,6 +346,29 @@ mod test {
|
|||
(public_key, msg, sig)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_pairs() -> Result<(), anyhow::Error> {
|
||||
let fixed_sk = SecretKey(BigUint::from(0x1234567890abcdefu64));
|
||||
let fixed_sk_expected_str = "782rkHhWNBIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
|
||||
assert_eq!(fixed_sk.to_string(), fixed_sk_expected_str);
|
||||
let parsed_sk = fixed_sk_expected_str.parse::<SecretKey>()?;
|
||||
assert_eq!(fixed_sk, parsed_sk);
|
||||
|
||||
let fixed_pk = fixed_sk.public_key();
|
||||
let fixed_pk_expected_str = "8sctoHc2aduKbYtZwo7Z3v7YugSuimLUMAhE95V18CWU55tWMvZxEqA";
|
||||
assert_eq!(fixed_pk.to_string(), fixed_pk_expected_str);
|
||||
let parsed_pk = fixed_pk_expected_str.parse::<Point>()?;
|
||||
assert_eq!(fixed_pk, parsed_pk);
|
||||
|
||||
let rand_sk = SecretKey::new_rand();
|
||||
assert_ne!(fixed_sk, rand_sk);
|
||||
assert_eq!(rand_sk, rand_sk.to_string().parse::<SecretKey>()?);
|
||||
let rand_pk = rand_sk.public_key();
|
||||
assert_ne!(fixed_pk, rand_pk);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_signature() {
|
||||
let (public_key, msg, sig) = gen_signed_message();
|
||||
|
|
@ -352,4 +468,67 @@ mod test {
|
|||
data.verify(proof)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_secret_key_serialization() -> Result<(), anyhow::Error> {
|
||||
let sk_123 = SecretKey(BigUint::from(123u32));
|
||||
let str_123 = serde_json::to_string(&sk_123).unwrap();
|
||||
let deser_123 = serde_json::from_str(&str_123).unwrap();
|
||||
assert_eq!(sk_123, deser_123);
|
||||
|
||||
let sk_rand = SecretKey::new_rand();
|
||||
assert_ne!(sk_123, sk_rand);
|
||||
let str_rand = serde_json::to_string(&sk_rand).unwrap();
|
||||
let deser_rand = serde_json::from_str(&str_rand).unwrap();
|
||||
assert_eq!(sk_rand, deser_rand);
|
||||
|
||||
let sk_max = SecretKey(&*GROUP_ORDER - 1u32);
|
||||
assert_ne!(sk_123, sk_max);
|
||||
let str_max = serde_json::to_string(&sk_max).unwrap();
|
||||
let deser_max = serde_json::from_str(&str_max).unwrap();
|
||||
assert_eq!(sk_max, deser_max);
|
||||
|
||||
// Value is too big for group but fits within 320 bits. Thus it
|
||||
// survives the assert in as_bytes(), but gets caught during deserialization.
|
||||
let sk_toobig = SecretKey(GROUP_ORDER.clone());
|
||||
assert_ne!(sk_toobig, sk_123);
|
||||
let str_toobig = serde_json::to_string(&sk_toobig).unwrap();
|
||||
assert!(serde_json::from_str::<SecretKey>(&str_toobig).is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_secret_key_limbs() -> Result<(), anyhow::Error> {
|
||||
let sk_0 = SecretKey(BigUint::from(0u32));
|
||||
let limbs_0 = sk_0.to_limbs();
|
||||
assert_eq!(limbs_0, [GoldilocksField::from_canonical_u32(0); 10]);
|
||||
let rsk_0 = SecretKey::from_limbs(limbs_0).unwrap();
|
||||
assert_eq!(sk_0, rsk_0);
|
||||
|
||||
let sk_n: SecretKey = SecretKey(BigUint::from_slice((0..10).collect_vec().as_slice()));
|
||||
let limbs_n = sk_n.to_limbs();
|
||||
assert_eq!(
|
||||
limbs_n.as_slice(),
|
||||
(0..10)
|
||||
.map(GoldilocksField::from_canonical_u32)
|
||||
.collect_vec()
|
||||
.as_slice()
|
||||
);
|
||||
let rsk_n = SecretKey::from_limbs(limbs_n).unwrap();
|
||||
assert_eq!(sk_n, rsk_n);
|
||||
|
||||
assert!(SecretKey::from_limbs(
|
||||
(0..9)
|
||||
.chain([9u64 << 32])
|
||||
.map(GoldilocksField::from_canonical_u64)
|
||||
.collect_vec()
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue