cl: swap jubjub for accel k256

This commit is contained in:
David Rusu 2024-06-27 16:00:42 +00:00
parent 89c70ea0e2
commit a819123bc3
11 changed files with 161 additions and 129 deletions

View File

@ -10,9 +10,17 @@ serde = {version="1.0", features = ["derive"]}
bincode = "1.3.3"
risc0-groth16 = "1.0.1"
blake2 = "0.10.6"
jubjub = "0.10.0"
# jubjub = "0.10.0"
group = "0.13.0"
rand_core = "0.6.0"
rand_chacha = "0.3.1"
lazy_static = "1.4.0"
hex = "0.4.3"
k256 = {version = "0.13.3", features = ["serde", "hash2curve"]}
# [dependencies.k256]
# git = "https://github.com/risc0/RustCrypto-elliptic-curves"
# tag = "k256/v0.13.3-risczero.0"

View File

@ -1,28 +1,32 @@
use group::{ff::Field, GroupEncoding};
use jubjub::{Scalar, SubgroupPoint};
use lazy_static::lazy_static;
use rand_core::RngCore;
use serde::{Deserialize, Serialize};
use k256::{
elliptic_curve::{
group::GroupEncoding, Field
},
ProjectivePoint, Scalar, AffinePoint
};
lazy_static! {
static ref PEDERSON_COMMITMENT_BLINDING_POINT: SubgroupPoint =
static ref PEDERSON_COMMITMENT_BLINDING_POINT: ProjectivePoint =
crate::crypto::hash_to_curve(b"NOMOS_CL_PEDERSON_COMMITMENT_BLINDING");
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct Balance(#[serde(with = "serde_point")] pub SubgroupPoint);
pub struct Balance(pub AffinePoint);
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct BalanceWitness {
pub value: u64,
pub unit: String,
#[serde(with = "serde_scalar")]
pub blinding: Scalar,
}
impl Balance {
pub fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
pub fn to_bytes(&self) -> [u8; 33] {
self.0.to_bytes().into()
}
}
@ -40,117 +44,119 @@ impl BalanceWitness {
}
pub fn commit(&self) -> Balance {
Balance(balance(self.value, &self.unit, self.blinding))
Balance(balance(self.value, &self.unit, self.blinding).into())
}
pub fn unit_point(&self) -> SubgroupPoint {
pub fn unit_point(&self) -> ProjectivePoint {
unit_point(&self.unit)
}
}
pub fn unit_point(unit: &str) -> SubgroupPoint {
pub fn unit_point(unit: &str) -> ProjectivePoint {
crate::crypto::hash_to_curve(unit.as_bytes())
}
pub fn balance(value: u64, unit: &str, blinding: Scalar) -> SubgroupPoint {
pub fn balance(value: u64, unit: &str, blinding: Scalar) -> ProjectivePoint {
let value_scalar = Scalar::from(value);
unit_point(unit) * value_scalar + *PEDERSON_COMMITMENT_BLINDING_POINT * blinding
}
mod serde_scalar {
use super::Scalar;
use serde::de::{self, Visitor};
use serde::{Deserializer, Serializer};
use std::fmt;
// mod serde_scalar {
// use super::Scalar;
// use serde::de::{self, Visitor};
// use serde::{Deserializer, Serializer};
// use std::fmt;
// Serialize a SubgroupPoint by converting it to bytes.
pub fn serialize<S>(scalar: &Scalar, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bytes = scalar.to_bytes();
serializer.serialize_bytes(&bytes)
}
// // Serialize a SubgroupPoint by converting it to bytes.
// pub fn serialize<S>(scalar: &Scalar, serializer: S) -> Result<S::Ok, S::Error>
// where
// S: Serializer,
// {
// let bytes = scalar.to_bytes();
// serializer.serialize_bytes(&bytes)
// }
// Deserialize a SubgroupPoint by converting it from bytes.
pub fn deserialize<'de, D>(deserializer: D) -> Result<Scalar, D::Error>
where
D: Deserializer<'de>,
{
struct BytesVisitor;
// // Deserialize a SubgroupPoint by converting it from bytes.
// pub fn deserialize<'de, D>(deserializer: D) -> Result<Scalar, D::Error>
// where
// D: Deserializer<'de>,
// {
// struct BytesVisitor;
impl<'de> Visitor<'de> for BytesVisitor {
type Value = Scalar;
// impl<'de> Visitor<'de> for BytesVisitor {
// type Value = Scalar;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a valid Scalar in byte representation")
}
// fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
// formatter.write_str("a valid Scalar in byte representation")
// }
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
let mut bytes = <jubjub::SubgroupPoint as group::GroupEncoding>::Repr::default();
assert_eq!(bytes.len(), v.len());
bytes.copy_from_slice(v);
// fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
// where
// E: de::Error,
// {
// let mut bytes = <jubjub::SubgroupPoint as group::GroupEncoding>::Repr::default();
// assert_eq!(bytes.len(), v.len());
// bytes.copy_from_slice(v);
Ok(Scalar::from_bytes(&bytes).unwrap())
}
}
// Ok(Scalar::from_bytes(&bytes).unwrap())
// }
// }
deserializer.deserialize_bytes(BytesVisitor)
}
}
// deserializer.deserialize_bytes(BytesVisitor)
// }
// }
mod serde_point {
use super::SubgroupPoint;
use group::GroupEncoding;
use serde::de::{self, Visitor};
use serde::{Deserializer, Serializer};
use std::fmt;
// mod serde_point {
// use super::SubgroupPoint;
// use group::GroupEncoding;
// use serde::de::{self, Visitor};
// use serde::{Deserializer, Serializer};
// use std::fmt;
// Serialize a SubgroupPoint by converting it to bytes.
pub fn serialize<S>(point: &SubgroupPoint, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bytes = point.to_bytes();
serializer.serialize_bytes(&bytes)
}
// // Serialize a SubgroupPoint by converting it to bytes.
// pub fn serialize<S>(point: &SubgroupPoint, serializer: S) -> Result<S::Ok, S::Error>
// where
// S: Serializer,
// {
// let bytes = point.to_bytes();
// serializer.serialize_bytes(&bytes)
// }
// Deserialize a SubgroupPoint by converting it from bytes.
pub fn deserialize<'de, D>(deserializer: D) -> Result<SubgroupPoint, D::Error>
where
D: Deserializer<'de>,
{
struct BytesVisitor;
// // Deserialize a SubgroupPoint by converting it from bytes.
// pub fn deserialize<'de, D>(deserializer: D) -> Result<SubgroupPoint, D::Error>
// where
// D: Deserializer<'de>,
// {
// struct BytesVisitor;
impl<'de> Visitor<'de> for BytesVisitor {
type Value = SubgroupPoint;
// impl<'de> Visitor<'de> for BytesVisitor {
// type Value = SubgroupPoint;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a valid SubgroupPoint in byte representation")
}
// fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
// formatter.write_str("a valid SubgroupPoint in byte representation")
// }
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
let mut bytes = <jubjub::SubgroupPoint as group::GroupEncoding>::Repr::default();
assert_eq!(bytes.len(), v.len());
bytes.copy_from_slice(v);
// fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
// where
// E: de::Error,
// {
// let mut bytes = <jubjub::SubgroupPoint as group::GroupEncoding>::Repr::default();
// assert_eq!(bytes.len(), v.len());
// bytes.copy_from_slice(v);
Ok(SubgroupPoint::from_bytes(&bytes).unwrap())
}
}
// Ok(SubgroupPoint::from_bytes(&bytes).unwrap())
// }
// }
// deserializer.deserialize_bytes(BytesVisitor)
// }
// }
deserializer.deserialize_bytes(BytesVisitor)
}
}
#[cfg(test)]
mod test {
use crate::test_util::seed_rng;
use k256::elliptic_curve::group::prime::PrimeCurveAffine;
use super::*;
@ -168,20 +174,20 @@ mod test {
#[test]
fn test_balance_blinding() {
// balances are blinded
let r1 = Scalar::from(12);
let r2 = Scalar::from(8);
let r1 = Scalar::from(12u32);
let r2 = Scalar::from(8u32);
let a_w = BalanceWitness::new(10, "NMO", r1);
let b_w = BalanceWitness::new(10, "NMO", r2);
let a = a_w.commit();
let b = b_w.commit();
assert_ne!(a, b);
assert_eq!(a.0 - b.0, BalanceWitness::new(0, "NMO", r1 - r2).commit().0);
assert_eq!(a.0.to_curve() - b.0.to_curve(), BalanceWitness::new(0, "NMO", r1 - r2).commit().0.to_curve());
}
#[test]
fn test_balance_units() {
// Unit's differentiate between values.
let r = Scalar::from(1337);
let r = Scalar::from(1337u32);
let nmo = BalanceWitness::new(10, "NMO", r);
let eth = BalanceWitness::new(10, "ETH", r);
assert_ne!(nmo.commit(), eth.commit());
@ -192,18 +198,18 @@ mod test {
let mut rng = seed_rng(0);
let r1 = Scalar::random(&mut rng);
let r2 = Scalar::random(&mut rng);
let ten = BalanceWitness::new(10, "NMO", 0.into());
let eight = BalanceWitness::new(8, "NMO", 0.into());
let two = BalanceWitness::new(2, "NMO", 0.into());
let ten = BalanceWitness::new(10, "NMO", 0u32.into());
let eight = BalanceWitness::new(8, "NMO", 0u32.into());
let two = BalanceWitness::new(2, "NMO", 0u32.into());
// Values of same unit are homomorphic
assert_eq!(ten.commit().0 - eight.commit().0, two.commit().0);
assert_eq!(ten.commit().0.to_curve() - eight.commit().0.to_curve(), two.commit().0.to_curve());
// Blinding factors are also homomorphic.
assert_eq!(
BalanceWitness::new(10, "NMO", r1).commit().0
- BalanceWitness::new(10, "NMO", r2).commit().0,
BalanceWitness::new(0, "NMO", r1 - r2).commit().0
BalanceWitness::new(10, "NMO", r1).commit().0.to_curve()
- BalanceWitness::new(10, "NMO", r2).commit().0.to_curve(),
BalanceWitness::new(0, "NMO", r1 - r2).commit().0.to_curve()
);
}
}

View File

@ -1,8 +1,9 @@
use std::collections::BTreeSet;
use jubjub::{Scalar, SubgroupPoint};
use serde::{Deserialize, Serialize};
use k256::{Scalar, ProjectivePoint};
use crate::{
error::Error,
note::NoteCommitment,
@ -30,7 +31,7 @@ pub struct BundleProof {
}
impl Bundle {
pub fn balance(&self) -> SubgroupPoint {
pub fn balance(&self) -> ProjectivePoint {
self.partials.iter().map(|ptx| ptx.balance()).sum()
}

View File

@ -1,13 +1,8 @@
use blake2::{Blake2s256, Digest};
use group::Group;
use jubjub::SubgroupPoint;
use rand_chacha::ChaCha20Rng;
use rand_core::SeedableRng;
use k256::{Secp256k1, ProjectivePoint, elliptic_curve::{
hash2curve::{GroupDigest, ExpandMsgXmd},
},sha2::Sha256
};
pub fn hash_to_curve(bytes: &[u8]) -> SubgroupPoint {
let mut hasher = Blake2s256::new();
hasher.update(b"NOMOS_HASH_TO_CURVE");
hasher.update(bytes);
let seed: [u8; 32] = hasher.finalize().into();
SubgroupPoint::random(ChaCha20Rng::from_seed(seed))
pub fn hash_to_curve(bytes: &[u8]) -> ProjectivePoint {
Secp256k1::hash_from_bytes::<ExpandMsgXmd<Sha256>>(&[bytes], &[b"NOMOS_HASH_TO_CURVE"]).unwrap()
}

View File

@ -105,11 +105,11 @@ impl Input {
&& death_constraint_is_satisfied
}
pub fn to_bytes(&self) -> [u8; 96] {
let mut bytes = [0u8; 96];
pub fn to_bytes(&self) -> [u8; 97] {
let mut bytes = [0u8; 97];
bytes[..32].copy_from_slice(self.note_comm.as_bytes());
bytes[32..64].copy_from_slice(self.nullifier.as_bytes());
bytes[64..96].copy_from_slice(&self.balance.to_bytes());
bytes[64..97].copy_from_slice(&self.balance.to_bytes());
bytes
}
}

View File

@ -1,7 +1,7 @@
use blake2::{Blake2s256, Digest};
use group::GroupEncoding;
use rand_core::RngCore;
use serde::{Deserialize, Serialize};
use k256::elliptic_curve::group::GroupEncoding;
use crate::{
balance::{Balance, BalanceWitness},

View File

@ -61,10 +61,10 @@ impl Output {
&& self.balance == witness.note.balance()
}
pub fn to_bytes(&self) -> [u8; 64] {
let mut bytes = [0u8; 64];
pub fn to_bytes(&self) -> [u8; 65] {
let mut bytes = [0u8; 65];
bytes[..32].copy_from_slice(self.note_comm.as_bytes());
bytes[32..64].copy_from_slice(&self.balance.to_bytes());
bytes[32..65].copy_from_slice(&self.balance.to_bytes());
bytes
}
}

View File

@ -1,17 +1,18 @@
use std::collections::BTreeSet;
use jubjub::SubgroupPoint;
use rand_core::RngCore;
use risc0_groth16::ProofJson;
use serde::{Deserialize, Serialize};
use k256::ProjectivePoint;
use k256::elliptic_curve::group::prime::PrimeCurveAffine;
use crate::error::Error;
use crate::input::{Input, InputProof, InputWitness};
use crate::merkle;
use crate::output::{Output, OutputProof, OutputWitness};
const MAX_INPUTS: usize = 32;
const MAX_OUTPUTS: usize = 32;
const MAX_INPUTS: usize = 8;
const MAX_OUTPUTS: usize = 8;
/// The partial transaction commitment couples an input to a partial transaction.
/// Prevents partial tx unbundling.
@ -157,9 +158,9 @@ impl PartialTx {
.all(|(o, p)| o.verify(p))
}
pub fn balance(&self) -> SubgroupPoint {
let in_sum: SubgroupPoint = self.inputs.iter().map(|i| i.balance.0).sum();
let out_sum: SubgroupPoint = self.outputs.iter().map(|o| o.balance.0).sum();
pub fn balance(&self) -> ProjectivePoint {
let in_sum: ProjectivePoint = self.inputs.iter().map(|i| i.balance.0.to_curve()).sum();
let out_sum: ProjectivePoint = self.outputs.iter().map(|o| o.balance.0.to_curve()).sum();
out_sum - in_sum
}

View File

@ -9,3 +9,10 @@ opt-level = 3
[profile.release]
debug = 1
lto = true
[patch.crates-io]
# Placing these patch statement in the workspace Cargo.toml will add RISC Zero SHA-256 and bigint
# multiplication accelerator support for all downstream usages of the following crates.
# sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.6-risczero.0" }
k256 = { git = "https://github.com/risc0/RustCrypto-elliptic-curves", tag = "k256/v0.13.3-risczero.0" }
crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.2-risczero.0" }

View File

@ -1,3 +1,5 @@
[package]
name = "method"
version = "0.1.0"

View File

@ -2,7 +2,6 @@ use blake2::{Blake2s256, Digest};
use risc0_zkvm::guest::env;
use common::*;
use cl::merkle;
use cl::note::NoteWitness;
use cl::input::InputWitness;
use cl::output::OutputWitness;
@ -24,22 +23,29 @@ fn execute(
mut journal: Journal,
) {
// verify ptx/cl preconditions
eprintln!("start exec: {}", env::cycle_count());
assert_eq!(ptx_root, merkle::node(input_root, output_root));
eprintln!("ptx_root: {}", env::cycle_count());
// Glue the zone and the cl together, specifically, it verifies the note requesting
// a transfer is included as part of the same transaction in the cl
assert!(merkle::verify_path(merkle::leaf(&in_note.commit().to_bytes()), &in_ptx_path, input_root));
let in_comm = in_note.commit().to_bytes();
eprintln!("input comm: {}", env::cycle_count());
assert!(merkle::verify_path(merkle::leaf(&in_comm), &in_ptx_path, input_root));
eprintln!("input merkle path: {}", env::cycle_count());
// check the commitments match the actual data
let state_cm = calculate_state_hash(&state);
let journal_cm = calculate_journal_hash(&journal);
let state_root = merkle::node(state_cm, journal_cm);
assert_eq!(state_root, in_note.note.state);
eprintln!("input state root: {}", env::cycle_count());
// then run the state transition function
let state = stf(state, input);
journal.push(input);
eprintln!("stf: {}", env::cycle_count());
// verifying ptx/cl postconditions
@ -48,11 +54,16 @@ fn execute(
let out_state_root = merkle::node(out_state_cm, out_journal_cm);
// TODO: verify death constraints are propagated
assert_eq!(out_state_root, out_note.note.state);
eprintln!("out state root: {}", env::cycle_count());
// Glue the zone and the cl together, specifically, it verifies an output note
// containing the zone state is included as part of the same transaction in the cl
// (this is done in the death condition to disallow burning)
assert!(merkle::verify_path(merkle::leaf(&out_note.commit().to_bytes()), &out_ptx_path, output_root));
let out_comm = out_note.commit().to_bytes();
eprintln!("output comm: {}", env::cycle_count());
assert!(merkle::verify_path(merkle::leaf(&out_comm), &out_ptx_path, output_root));
eprintln!("out merkle proof: {}", env::cycle_count());
}
fn main() {
@ -70,6 +81,7 @@ fn main() {
let state: State = env::read();
let journal: Journal = env::read();
eprintln!("parse input: {}", env::cycle_count());
execute(ptx_root, input_root, output_root, in_ptx_path, out_ptx_path, in_note, out_note, input, state, journal);
}