diff --git a/Cargo.toml b/Cargo.toml index 0144ee64..e4420de6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ plonky2 = { git = "https://github.com/0xPolygonZero/plonky2", optional = true } serde = "1.0.219" serde_json = "1.0.140" base64 = "0.22.1" +bs58 = "0.5.1" schemars = "0.8.22" num = { version = "0.4.3", features = ["num-bigint"] } num-bigint = { version = "0.4.6", features = ["rand"] } diff --git a/examples/signed_pod.rs b/examples/signed_pod.rs index 1081aa66..03db74cd 100644 --- a/examples/signed_pod.rs +++ b/examples/signed_pod.rs @@ -15,7 +15,7 @@ fn main() -> Result<(), Box> { // Create a schnorr key pair to sign the pod let sk = SecretKey::new_rand(); let pk = sk.public_key(); - println!("Public key: {:?}\n", pk); + println!("Public key: {}\n", pk); let mut signer = Signer(sk); diff --git a/src/backends/plonky2/primitives/ec/curve.rs b/src/backends/plonky2/primitives/ec/curve.rs index 90a764c0..af064d52 100644 --- a/src/backends/plonky2/primitives/ec/curve.rs +++ b/src/backends/plonky2/primitives/ec/curve.rs @@ -3,7 +3,7 @@ //! We roughly follow pornin/ecgfp5. use core::ops::{Add, Mul}; use std::{ - array, + array, fmt, ops::{AddAssign, Neg, Sub}, sync::LazyLock, }; @@ -23,7 +23,7 @@ use plonky2::{ util::serialization::{Read, Write}, }; use rand::rngs::OsRng; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::backends::plonky2::{ circuits::common::ValueTarget, @@ -94,12 +94,65 @@ fn ec_field_from_bytes(b: &[u8]) -> Result { Ok(QuinticExtension(array::from_fn(|i| fields[i]))) } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Point { pub x: ECField, pub u: ECField, } +impl fmt::Display for Point { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[allow(clippy::collapsible_else_if)] + if f.alternate() { + write!(f, "({}, {})", self.x, self.u) + } else { + if self.is_in_subgroup() { + // Compressed + let u_bytes = self.as_bytes_from_subgroup().expect("point in subgroup"); + let u_b58 = bs58::encode(u_bytes).into_string(); + write!(f, "{}", u_b58) + } else { + // Non-compressed + let xu_bytes = [ec_field_to_bytes(&self.x), ec_field_to_bytes(&self.u)].concat(); + let xu_b58 = bs58::encode(xu_bytes).into_string(); + write!(f, "{}", xu_b58) + } + } + } +} + +impl Serialize for Point { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let point_b58 = format!("{}", self); + serializer.serialize_str(&point_b58) + } +} + +impl<'de> Deserialize<'de> for Point { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let point_b58 = String::deserialize(deserializer)?; + let point_bytes: Vec = bs58::decode(point_b58) + .into_vec() + .map_err(serde::de::Error::custom)?; + if point_bytes.len() == 80 { + // Non-compressed + Ok(Point { + x: ec_field_from_bytes(&point_bytes[..40]).map_err(serde::de::Error::custom)?, + u: ec_field_from_bytes(&point_bytes[40..]).map_err(serde::de::Error::custom)?, + }) + } else { + // Compressed + Self::from_bytes_into_subgroup(&point_bytes).map_err(serde::de::Error::custom) + } + } +} + impl Point { pub fn new_rand_from_subgroup() -> Self { &OsRng.gen_biguint_below(&GROUP_ORDER) * Self::generator() @@ -111,8 +164,8 @@ impl Point { match self.is_in_subgroup() { true => Ok(self.u), false => Err(Error::custom(format!( - "Point must lie in EC subgroup: ({}, {})", - self.x, self.u + "Point must lie in EC subgroup: {}", + self ))), } } @@ -810,7 +863,7 @@ mod test { match p == q { true => Ok(()), false => Err(Error::custom(format!( - "Roundtrip compression failed: {:?} ≠ {:?}", + "Roundtrip compression failed: {} ≠ {}", p, q ))), } @@ -897,4 +950,28 @@ mod test { assert!(data.prove(pw).is_err()); Ok(()) } + + #[test] + fn test_point_serialize_deserialize() -> Result<(), anyhow::Error> { + // In subgroup + let g = Point::generator(); + + let serialized = serde_json::to_string_pretty(&g)?; + println!("g = {}", serialized); + let deserialized = serde_json::from_str(&serialized)?; + assert_eq!(g, deserialized); + + // Not in subgroup + let not_sub = Point { + x: Point::b() / g.x, + u: g.u, + }; + + let serialized = serde_json::to_string_pretty(¬_sub)?; + println!("not_sub = {}", serialized); + let deserialized = serde_json::from_str(&serialized)?; + assert_eq!(not_sub, deserialized); + + Ok(()) + } } diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index d921dc0d..048c0282 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -180,7 +180,7 @@ impl fmt::Display for TypedValue { TypedValue::Set(s) => write!(f, "set:{}", s.commitment()), TypedValue::Array(a) => write!(f, "arr:{}", a.commitment()), TypedValue::Raw(v) => write!(f, "{}", v), - TypedValue::PublicKey(p) => write!(f, "ecGFp5_pt:({},{})", p.x, p.u), + TypedValue::PublicKey(p) => write!(f, "pk:{}", p), TypedValue::PodId(id) => write!(f, "pod_id:{}", id), } } @@ -849,6 +849,7 @@ pub struct MainPodInputs<'a> { /// Statements that need to be made public (they can come from input pods or input /// statements) pub public_statements: &'a [Statement], + // TODO: REMOVE THIS pub vd_set: VDSet, }