From e133018d3fc42dbecedc38d13dc845ff2c644fc7 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sun, 3 Oct 2021 18:17:54 -0700 Subject: [PATCH] crypto: create new crate, scratch work Adds asset identifiers and value commitment generator computation. 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. --- .github/workflows/notes.yml | 2 +- Cargo.toml | 5 +++ crypto/Cargo.toml | 16 +++++++ crypto/src/asset.rs | 85 +++++++++++++++++++++++++++++++++++++ crypto/src/lib.rs | 5 +++ crypto/src/poseidon_hash.rs | 37 ++++++++++++++++ penumbra/Cargo.toml | 2 - 7 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 crypto/Cargo.toml create mode 100644 crypto/src/asset.rs create mode 100644 crypto/src/lib.rs create mode 100644 crypto/src/poseidon_hash.rs diff --git a/.github/workflows/notes.yml b/.github/workflows/notes.yml index bf006951c5..1ff3d07c2d 100644 --- a/.github/workflows/notes.yml +++ b/.github/workflows/notes.yml @@ -39,7 +39,7 @@ jobs: # - published crates are excluded # Doing this in one go is useful because the JSON file with search # indexes is overwritten on each cargo doc invocation. - cargo doc --no-deps -p tendermint -p ark-sponge -p decaf377 -p poseidon377 -p penumbra-proto -p penumbra + cargo doc --no-deps -p tendermint -p ark-sponge -p decaf377 -p poseidon377 -p penumbra-crypto -p penumbra-proto -p penumbra - name: Move API docs to subdirectory run: | if [ -d "firebase-tmp" ]; then rm -rf firebase-tmp; fi diff --git a/Cargo.toml b/Cargo.toml index 22bb1814d6..888dc69962 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,9 +2,14 @@ members = [ "proto", + "crypto", "penumbra", ] [patch.crates-io] tracing = { git = "https://github.com/tokio-rs/tracing/", branch = "v0.1.x" } tracing-subscriber = { git = "https://github.com/tokio-rs/tracing/", branch = "v0.1.x" } + +# The "ours" branch is based off of v0.3.0 +ark-ff = { git = "https://github.com/penumbra-zone/algebra", branch = "ours" } +ark-serialize = { git = "https://github.com/penumbra-zone/algebra", branch = "ours" } diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml new file mode 100644 index 0000000000..ac9b4fa413 --- /dev/null +++ b/crypto/Cargo.toml @@ -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" diff --git a/crypto/src/asset.rs b/crypto/src/asset.rs new file mode 100644 index 0000000000..a0cf4e02aa --- /dev/null +++ b/crypto/src/asset.rs @@ -0,0 +1,85 @@ +//! 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 { + f.write_fmt(format_args!("asset::Id({})", self.0.to_string())) + } +} + +// 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 = 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()); + } +} diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs new file mode 100644 index 0000000000..68bd0a413a --- /dev/null +++ b/crypto/src/lib.rs @@ -0,0 +1,5 @@ +pub use decaf377::Fq; + +pub mod asset; + +mod poseidon_hash; diff --git a/crypto/src/poseidon_hash.rs b/crypto/src/poseidon_hash.rs new file mode 100644 index 0000000000..0e47b2c551 --- /dev/null +++ b/crypto/src/poseidon_hash.rs @@ -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() +} diff --git a/penumbra/Cargo.toml b/penumbra/Cargo.toml index 769960b3d5..2dd922dc49 100644 --- a/penumbra/Cargo.toml +++ b/penumbra/Cargo.toml @@ -15,8 +15,6 @@ publish = false # Workspace dependencies penumbra-proto = { path = "../proto" } # Penumbra dependencies -decaf377 = { git = "https://github.com/penumbra-zone/decaf377" } -poseidon377 = { git = "https://github.com/penumbra-zone/poseidon377" } tower-abci = { git = "https://github.com/penumbra-zone/tower-abci/" } tendermint-proto = { git = "https://github.com/penumbra-zone/tendermint-rs.git", branch = "abci-domain-types" } tendermint = { git = "https://github.com/penumbra-zone/tendermint-rs.git", branch = "abci-domain-types" }