-
Notifications
You must be signed in to change notification settings - Fork 295
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
crypto: create new crate, scratch work
Adds asset identifiers and value commitment generator computation. Adds a basic test of value commitments with different bases. This commit changes the workspace to use a Penumbra fork of some of the arkworks crates. The current change is in the Display impl for Fp* instances. The fork's `ours` branch is based off of `v0.3.0`, in order to not pick up any unreleased breaking changes.
- Loading branch information
1 parent
9f6ac72
commit 4c32464
Showing
8 changed files
with
274 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
[package] | ||
name = "penumbra-crypto" | ||
version = "0.1.0" | ||
edition = "2018" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
decaf377 = { git = "https://github.com/penumbra-zone/decaf377" } | ||
poseidon377 = { git = "https://github.com/penumbra-zone/poseidon377" } | ||
hex = "0.4" | ||
blake2b_simd = "0.5" | ||
ark-ff = "0.3" | ||
once_cell = "1.8" | ||
# only needed because ark-ff doesn't display correctly | ||
num-bigint = "0.4" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
//! Asset types and identifiers. | ||
|
||
use ark_ff::fields::PrimeField; | ||
use once_cell::sync::Lazy; | ||
|
||
use crate::Fq; | ||
|
||
/// An identifier for an IBC asset type. | ||
/// | ||
/// This is similar to, but different from, the design in [ADR001]. As in | ||
/// ADR001, a denomination trace is hashed to a fixed-size identifier, but | ||
/// unlike ADR001, we hash to a field element rather than a byte string. | ||
/// | ||
/// A denomination trace looks like | ||
/// | ||
/// - `denom` (native chain A asset) | ||
/// - `transfer/channelToA/denom` (chain B representation of chain A asset) | ||
/// - `transfer/channelToB/transfer/channelToA/denom` (chain C representation of chain B representation of chain A asset) | ||
/// | ||
/// ADR001 defines the IBC asset ID as the SHA-256 hash of the denomination | ||
/// trace. Instead, Penumbra hashes to a field element, so that asset IDs can | ||
/// be more easily used inside of a circuit. | ||
/// | ||
/// [ADR001]: | ||
/// https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr-001-coin-source-tracing.md | ||
#[derive(Copy, Clone, PartialEq, Eq)] | ||
pub struct Id(pub Fq); | ||
|
||
impl std::fmt::Debug for Id { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
use ark_ff::BigInteger; | ||
let bytes = self.0.into_repr().to_bytes_le(); | ||
f.write_fmt(format_args!("asset::Id({})", hex::encode(&bytes))) | ||
} | ||
} | ||
|
||
// XXX define a DenomTrace structure ? | ||
|
||
impl From<&[u8]> for Id { | ||
fn from(slice: &[u8]) -> Id { | ||
// Convert an asset name to an asset ID by hashing to a scalar | ||
Id(Fq::from_le_bytes_mod_order( | ||
// XXX choice of hash function? | ||
blake2b_simd::Params::default() | ||
.personal(b"penumbra.asset") | ||
.hash(slice) | ||
.as_bytes(), | ||
)) | ||
} | ||
} | ||
|
||
/// The domain separator used to hash asset ids to value generators. | ||
static VALUE_GENERATOR_DOMAIN_SEP: Lazy<Fq> = Lazy::new(|| { | ||
Fq::from_le_bytes_mod_order(blake2b_simd::blake2b(b"penumbra.value.generator").as_bytes()) | ||
}); | ||
|
||
impl Id { | ||
/// Compute the value commitment generator for this asset. | ||
pub fn value_generator(&self) -> decaf377::Element { | ||
use crate::poseidon_hash::hash_1; | ||
let hash = hash_1(&VALUE_GENERATOR_DOMAIN_SEP, self.0); | ||
decaf377::Element::map_to_group_cdh(&hash) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn make_up_some_fake_asset_ids() { | ||
// marked for future deletion | ||
// not really a test, just a way to exercise the code | ||
|
||
let pen_trace = b"pen"; | ||
let atom_trace = b"HubPort/HubChannel/atom"; | ||
|
||
let pen_id = Id::from(&pen_trace[..]); | ||
let atom_id = Id::from(&atom_trace[..]); | ||
|
||
dbg!(pen_id); | ||
dbg!(atom_id); | ||
|
||
dbg!(pen_id.value_generator()); | ||
dbg!(atom_id.value_generator()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
pub use decaf377::{Fq, Fr}; | ||
|
||
pub mod asset; | ||
pub mod value; | ||
|
||
mod poseidon_hash; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// XXX move into poseidon377 crate? | ||
|
||
use crate::Fq; | ||
|
||
use poseidon377::ark_sponge::{ | ||
poseidon::PoseidonSponge, CryptographicSponge, FieldBasedCryptographicSponge, | ||
}; | ||
|
||
pub fn hash_1(domain_separator: &Fq, value: Fq) -> Fq { | ||
// we want to set the capacity to domain_separator and the rate to value, | ||
// then run the sponge and extract the rate. it's a bit hard to do this | ||
// using the ark-sponge api, which is trying to do a higher-level duplex | ||
// construction and doesn't allow access to the underlying sponge | ||
|
||
let mut sponge = PoseidonSponge::new(&poseidon377::params::rate_1()); | ||
|
||
// arkworks sponge api doesn't let us call permute | ||
// | ||
// best we can do now is to look in the source to see how the rate and | ||
// capacity are arranged and try to plumb the functionality we want through | ||
// the higher-level API | ||
// | ||
// arkworks uses (rate || capacity) instead of (capacity || rate) | ||
// | ||
// this also gives incompatible outputs, but let's deal with that later | ||
|
||
// set the capacity | ||
assert_eq!(sponge.state.len(), 2); | ||
sponge.state[1] = *domain_separator; | ||
|
||
// now use absorb to set the rate (hopefully) | ||
sponge.absorb(&value); | ||
// and squeeze an element | ||
let out_vec = sponge.squeeze_native_field_elements(1); | ||
|
||
out_vec.into_iter().next().unwrap() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
//! Values (?) | ||
|
||
use std::ops::Deref; | ||
|
||
use ark_ff::PrimeField; | ||
use once_cell::sync::Lazy; | ||
|
||
use crate::{asset, Fq, Fr}; | ||
|
||
#[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
pub struct Value { | ||
pub amount: u64, | ||
pub asset_id: asset::Id, | ||
} | ||
|
||
#[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
pub struct Commitment(pub decaf377::Element); | ||
|
||
static VALUE_BLINDING_GENERATOR: Lazy<decaf377::Element> = Lazy::new(|| { | ||
let s = Fq::from_le_bytes_mod_order(blake2b_simd::blake2b(b"penumbra.val.blinding").as_bytes()); | ||
decaf377::Element::map_to_group_cdh(&s) | ||
}); | ||
|
||
impl Value { | ||
#[allow(non_snake_case)] | ||
pub fn commit(&self, blinding: Fr) -> Commitment { | ||
let G_v = self.asset_id.value_generator(); | ||
let H = VALUE_BLINDING_GENERATOR.deref(); | ||
|
||
let v = Fr::from(self.amount); | ||
|
||
// XXX would be more convenient for decaf377 not to | ||
// have a custom scalar type ? | ||
let v = decaf377::Scalar::from(v); | ||
let blinding = decaf377::Scalar::from(blinding); | ||
|
||
let C = v * G_v + blinding * H; | ||
|
||
Commitment(C) | ||
} | ||
} | ||
|
||
impl std::ops::Add<Commitment> for Commitment { | ||
type Output = Commitment; | ||
fn add(self, rhs: Commitment) -> Self::Output { | ||
Commitment(self.0 + rhs.0) | ||
} | ||
} | ||
|
||
impl std::ops::Sub<Commitment> for Commitment { | ||
type Output = Commitment; | ||
fn sub(self, rhs: Commitment) -> Self::Output { | ||
Commitment(self.0 - rhs.0) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn sum_value_commitments() { | ||
use ark_ff::Field; | ||
|
||
let pen_trace = b"pen"; | ||
let atom_trace = b"HubPort/HubChannel/atom"; | ||
|
||
let pen_id = asset::Id::from(&pen_trace[..]); | ||
let atom_id = asset::Id::from(&atom_trace[..]); | ||
|
||
// some values of different types | ||
let v1 = Value { | ||
amount: 10, | ||
asset_id: pen_id, | ||
}; | ||
let v2 = Value { | ||
amount: 8, | ||
asset_id: pen_id, | ||
}; | ||
let v3 = Value { | ||
amount: 2, | ||
asset_id: pen_id, | ||
}; | ||
let v4 = Value { | ||
amount: 13, | ||
asset_id: atom_id, | ||
}; | ||
let v5 = Value { | ||
amount: 17, | ||
asset_id: atom_id, | ||
}; | ||
let v6 = Value { | ||
amount: 30, | ||
asset_id: atom_id, | ||
}; | ||
|
||
// some random-looking blinding factors | ||
let b1 = Fr::from(-129).inverse().unwrap(); | ||
let b2 = Fr::from(-199).inverse().unwrap(); | ||
let b3 = Fr::from(-121).inverse().unwrap(); | ||
let b4 = Fr::from(-179).inverse().unwrap(); | ||
let b5 = Fr::from(-379).inverse().unwrap(); | ||
let b6 = Fr::from(-879).inverse().unwrap(); | ||
|
||
// form commitments | ||
let c1 = v1.commit(b1); | ||
let c2 = v2.commit(b2); | ||
let c3 = v3.commit(b3); | ||
let c4 = v4.commit(b4); | ||
let c5 = v5.commit(b5); | ||
let c6 = v6.commit(b6); | ||
|
||
// values sum to 0, so this is a commitment to 0... | ||
let c0 = c1 - c2 - c3 + c4 + c5 - c6; | ||
// with the following synthetic blinding factor: | ||
let b0 = b1 - b2 - b3 + b4 + b5 - b6; | ||
|
||
// so c0 = 0 * G_v1 + 0 * G_v2 + b0 * H | ||
let b0 = decaf377::Scalar::from(b0); | ||
assert_eq!(c0.0, b0 * VALUE_BLINDING_GENERATOR.deref()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters