-
Notifications
You must be signed in to change notification settings - Fork 302
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
crypto: create new crate, scratch work #66
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,115 @@ | ||
//! 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); | ||
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 | ||
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
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe as a method on this struct (which we can rename, but the struct has one public method implemented following roughly the C2SP variable-length hash so we could add a fixed-length
hash_1
) or just as a standalone functionThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing I'm wondering is whether it would make sense to split the upstream code in
ark-sponge
into two levels, one that just does the permutation, and then another that tracks the sponge state on top of that.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
created https://github.com/arkworks-rs/sponge/issues/29