Skip to content
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 2 commits into from
Oct 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/notes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
16 changes: 16 additions & 0 deletions crypto/Cargo.toml
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"
87 changes: 87 additions & 0 deletions crypto/src/asset.rs
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());
}
}
6 changes: 6 additions & 0 deletions crypto/src/lib.rs
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;
37 changes: 37 additions & 0 deletions crypto/src/poseidon_hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// XXX move into poseidon377 crate?
Copy link
Member

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 function

Copy link
Member Author

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


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()
}
115 changes: 115 additions & 0 deletions crypto/src/value.rs
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());
}
}
2 changes: 0 additions & 2 deletions penumbra/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down