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

Move app-data-hash into app-data crate #3105

Merged
merged 3 commits into from
Nov 5, 2024
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
13 changes: 1 addition & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 0 additions & 13 deletions crates/app-data-hash/Cargo.toml

This file was deleted.

1 change: 0 additions & 1 deletion crates/app-data-hash/LICENSE-APACHE

This file was deleted.

1 change: 0 additions & 1 deletion crates/app-data-hash/LICENSE-MIT

This file was deleted.

76 changes: 0 additions & 76 deletions crates/app-data-hash/src/lib.rs

This file was deleted.

3 changes: 2 additions & 1 deletion crates/app-data/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ edition = "2021"
license = "MIT OR Apache-2.0"

[dependencies]
app-data-hash = { path = "../app-data-hash" }
bytes-hex = { path = "../bytes-hex" }
anyhow = { workspace = true }
tiny-keccak = { version = "2.0.2", features = ["keccak"] }
serde = { workspace = true }
serde_json = { workspace = true }
serde_with = { workspace = true }
primitive-types = { workspace = true }
hex = { workspace = true }
hex-literal = { workspace = true }

[dev-dependencies]
ethcontract = { workspace = true }
Expand Down
4 changes: 2 additions & 2 deletions crates/app-data/src/app_data.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use {
crate::{AppDataHash, Hooks},
crate::{app_data_hash::hash_full_app_data, AppDataHash, Hooks},
anyhow::{anyhow, Context, Result},
primitive_types::H160,
serde::{de, Deserialize, Deserializer, Serialize, Serializer},
Expand Down Expand Up @@ -79,7 +79,7 @@ impl Validator {
.unwrap_or_default();

Ok(ValidatedAppData {
hash: AppDataHash(app_data_hash::hash_full_app_data(full_app_data)),
hash: AppDataHash(hash_full_app_data(full_app_data)),
document,
protocol,
})
Expand Down
71 changes: 71 additions & 0 deletions crates/app-data/src/app_data_hash.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
//! App data refers to extra information that is associated with orders. This
//! information is not validated by the contract but it is used by other parts
//! of the system. For example, a user could specify that they want their order
//! to be COW only, which is something only the backend understands. Or what
//! their intended slippage when creating the order with the frontend was, which
//! adjusts the signed prices.
//!
//! On the smart contract level app data is freely choosable 32 bytes of signed
//! order data. This isn't enough space for some purposes so we interpret those
//! bytes as a hash of the full app data of arbitrary length. The full app data
//! is thus signed by the user when they sign the order.
//!
//! This crate specifies how the hash is calculated. It takes the keccak256 hash
//! of the input bytes. Additionally, it provides a canonical way to calculate
//! an IPFS CID from the hash. This allows full app data to be uploaded to IPFS.
//!
//! Note that not all app data hashes were created this way. As of 2023-05-25 we
//! are planning to move to the scheme implemented by this crate but orders have
//! been created with arbitrary app data hashes until now. See [this issue][0]
//! for more information.
//!
//! [0]: https://github.com/cowprotocol/services/issues/1465

use {
serde::{de, Deserializer, Serializer},
serde_with::serde::{Deserialize, Serialize},
Expand All @@ -6,6 +29,7 @@ use {
fmt::{self, Debug, Formatter},
str::FromStr,
},
tiny_keccak::{Hasher, Keccak},
};

/// A JSON object used to represent app data documents for uploading and
Expand Down Expand Up @@ -81,6 +105,29 @@ impl PartialEq<[u8; 32]> for AppDataHash {
}
}

/// Hash full app data to get the bytes expected to be set as the contract level
/// app data.
pub fn hash_full_app_data(app_data: &[u8]) -> [u8; 32] {
let mut hasher = Keccak::v256();
hasher.update(app_data);
let mut hash = [0u8; 32];
hasher.finalize(&mut hash);
hash
}

/// Create an IPFS CIDv1 from a hash created by `hash_full_app_data`.
///
/// The return value is the raw bytes of the CID. It is not multibase encoded.
pub fn create_ipfs_cid(app_data_hash: &[u8; 32]) -> [u8; 36] {
let mut cid = [0u8; 4 + 32];
cid[0] = 1; // cid version
cid[1] = 0x55; // raw codec
cid[2] = 0x1b; // keccak hash algorithm
cid[3] = 32; // keccak hash length
cid[4..].copy_from_slice(app_data_hash);
cid
}

#[cfg(test)]
mod tests {
use {super::*, serde_json::json};
Expand Down Expand Up @@ -125,4 +172,28 @@ mod tests {
assert!(AppDataHash::deserialize(json!("asdf")).is_err());
assert!(AppDataHash::deserialize(json!("0x00")).is_err());
}

// Alternative way of calculating the expected values:
// cat appdata | ipfs block put --mhtype keccak-256
// -> bafkrwiek6tumtfzvo6yivqq5c7jtdkw6q3ar5pgfcjdujvrbzkbwl3eueq
// ipfs cid format -b base16
// bafkrwiek6tumtfzvo6yivqq5c7jtdkw6q3ar5pgfcjdujvrbzkbwl3eueq
// -> f01551b208af4e8c9973577b08ac21d17d331aade86c11ebcc5124744d621ca8365ec9424
// Remove the f prefix and you have the same CID.
// Or check out the cid explorer:
// - https://cid.ipfs.tech/#f01551b208af4e8c9973577b08ac21d17d331aade86c11ebcc5124744d621ca8365ec9424
// - https://cid.ipfs.tech/#bafkrwiek6tumtfzvo6yivqq5c7jtdkw6q3ar5pgfcjdujvrbzkbwl3eueq
#[test]
fn known_good() {
let full_app_data = r#"{"appCode":"CoW Swap","environment":"production","metadata":{"quote":{"slippageBips":"50","version":"0.2.0"},"orderClass":{"orderClass":"market","version":"0.1.0"}},"version":"0.6.0"}"#;
let expected_hash =
hex_literal::hex!("8af4e8c9973577b08ac21d17d331aade86c11ebcc5124744d621ca8365ec9424");
let expected_cid = hex_literal::hex!(
"01551b208af4e8c9973577b08ac21d17d331aade86c11ebcc5124744d621ca8365ec9424"
);
let hash = hash_full_app_data(full_app_data.as_bytes());
let cid = create_ipfs_cid(&hash);
assert_eq!(hash, expected_hash);
assert_eq!(cid, expected_cid);
}
}
1 change: 0 additions & 1 deletion crates/e2e/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ web3 = { workspace = true, features = ["http"] }
uuid = { version = "1.8.0", features = ["v4"] }

[dev-dependencies]
app-data-hash = { path = "../app-data-hash" }
futures = { workspace = true }
rand = { workspace = true }
refunder = { path = "../refunder" }
Expand Down
10 changes: 4 additions & 6 deletions crates/e2e/tests/e2e/app_data.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use {
app_data::AppDataHash,
app_data::{hash_full_app_data, AppDataHash},
e2e::{setup::*, tx},
ethcontract::prelude::U256,
model::{
Expand Down Expand Up @@ -74,7 +74,7 @@ async fn app_data(web3: Web3) {

// hash matches
let app_data = "{}";
let app_data_hash = AppDataHash(app_data_hash::hash_full_app_data(app_data.as_bytes()));
let app_data_hash = AppDataHash(hash_full_app_data(app_data.as_bytes()));
let order1 = create_order(OrderCreationAppData::Both {
full: app_data.to_string(),
expected: app_data_hash,
Expand Down Expand Up @@ -133,7 +133,7 @@ async fn app_data(web3: Web3) {

// pre-register some app-data with the API.
let pre_app_data = r#"{"pre":"registered"}"#;
let pre_app_data_hash = AppDataHash(app_data_hash::hash_full_app_data(pre_app_data.as_bytes()));
let pre_app_data_hash = AppDataHash(hash_full_app_data(pre_app_data.as_bytes()));
let err = services.get_app_data(pre_app_data_hash).await.unwrap_err();
dbg!(err);

Expand Down Expand Up @@ -170,9 +170,7 @@ async fn app_data(web3: Web3) {
// pre-registering invalid app-data fails.
let err = services
.put_app_data(
Some(AppDataHash(app_data_hash::hash_full_app_data(
invalid_app_data.as_bytes(),
))),
Some(AppDataHash(hash_full_app_data(invalid_app_data.as_bytes()))),
invalid_app_data,
)
.await
Expand Down
1 change: 0 additions & 1 deletion crates/model/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ doctest = false
[dependencies]
anyhow = { workspace = true }
app-data = { path = "../app-data" }
app-data-hash = { path = "../app-data-hash" }
bytes-hex = { path = "../bytes-hex" }
bigdecimal = { workspace = true }
chrono = { workspace = true, features = ["serde", "clock"] }
Expand Down
4 changes: 2 additions & 2 deletions crates/model/src/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use {
TokenPair,
},
anyhow::{anyhow, Result},
app_data::AppDataHash,
app_data::{hash_full_app_data, AppDataHash},
chrono::{offset::Utc, DateTime},
derivative::Derivative,
hex_literal::hex,
Expand Down Expand Up @@ -465,7 +465,7 @@ impl OrderCreationAppData {
match self {
Self::Hash { hash } => *hash,
Self::Full { full } | Self::Both { full, .. } => {
AppDataHash(app_data_hash::hash_full_app_data(full.as_bytes()))
AppDataHash(hash_full_app_data(full.as_bytes()))
}
}
}
Expand Down
1 change: 0 additions & 1 deletion crates/orderbook/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ path = "src/main.rs"
[dependencies]
anyhow = { workspace = true }
app-data = { path = "../app-data" }
app-data-hash = { path = "../app-data-hash" }
async-trait = { workspace = true }
bigdecimal = { workspace = true }
cached = { workspace = true }
Expand Down
4 changes: 2 additions & 2 deletions crates/orderbook/src/ipfs_app_data.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use {
crate::ipfs::Ipfs,
anyhow::Result,
app_data::AppDataHash,
app_data::{create_ipfs_cid, AppDataHash},
cached::{Cached, TimedSizedCache},
std::sync::Mutex,
};
Expand Down Expand Up @@ -121,7 +121,7 @@ impl IpfsAppData {
}

fn new_app_data_cid(contract_app_data: &AppDataHash) -> String {
let raw_cid = app_data_hash::create_ipfs_cid(&contract_app_data.0);
let raw_cid = create_ipfs_cid(&contract_app_data.0);
multibase::encode(multibase::Base::Base32Lower, raw_cid)
}

Expand Down
1 change: 0 additions & 1 deletion crates/shared/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ doctest = false
[dependencies]
anyhow = { workspace = true }
app-data = { path = "../app-data" }
app-data-hash = { path = "../app-data-hash" }
bytes-hex = { path = "../bytes-hex" }
async-trait = { workspace = true }
bigdecimal = { workspace = true }
Expand Down
Loading
Loading