Add Pcd::to_sorted_key_values, and hash values with a counter

This commit is contained in:
Sosthene 2024-12-11 23:23:14 +01:00 committed by Nicolas Cantu
parent f03b1594af
commit 60ff6c1aba

View File

@ -6,7 +6,6 @@ use aes_gcm::{
aead::{Aead, Payload},
AeadCore, Aes256Gcm, KeyInit,
};
use log::debug;
use rand::thread_rng;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
@ -103,15 +102,29 @@ pub trait Pcd<'a>: Serialize + Deserialize<'a> {
}
}
fn hash_all_fields(&self, commited_in: OutPoint) -> Result<Map<String, Value>> {
fn to_sorted_key_values(&self) -> Result<Map<String, Value>> {
let map = self.to_value_object()?;
let mut sorted_key_values: Vec<(String, Value)> = map.into_iter().map(|(key, value)| (key, value)).collect();
sorted_key_values.sort_by_key(|(key, _)| key.to_owned());
Ok(sorted_key_values.into_iter().collect())
}
/// Create a hashed commitments for all values in the pcd
/// We need the commited_in outpoint to prevent same data producing the same hashes between different processes or differents states of the same process
fn hash_all_fields(&self, commited_in: OutPoint) -> Result<Map<String, Value>> {
let outpoint = serialize(&commited_in);
let mut field2hash = Map::with_capacity(map.len());
// To prevent fields with identical data having identical commitments, we order the map in alphabetical order and append a counter to the value
let sorted_key_values = self.to_sorted_key_values()?;
let mut field2hash = Map::with_capacity(sorted_key_values.len());
// this could be optimised since there's a midstate we're reusing
for (field, value) in map {
let tagged_hash = AnkPcdHash::from_value_with_outpoint(&value, &outpoint);
for (i, (field, value)) in sorted_key_values.into_iter().enumerate() {
let mut value_bin = serde_json::to_string(&value)?.into_bytes();
value_bin.push(i.try_into()?);
let tagged_hash = AnkPcdHash::from_value_with_outpoint(&value_bin, &outpoint);
field2hash.insert(field, Value::String(tagged_hash.to_string()));
}
@ -204,9 +217,9 @@ pub trait Pcd<'a>: Serialize + Deserialize<'a> {
fields2keys: &Map<String, Value>,
fields2plain: &mut Map<String, Value>,
) -> Result<()> {
let map = self.to_value_object()?;
let sorted_key_values = self.to_sorted_key_values()?;
for (field, encrypted_value) in map.iter() {
for (i, (field, encrypted_value)) in sorted_key_values.iter().enumerate() {
if let Some(aes_key) = fields2keys.get(field) {
let key_buf = Vec::from_hex(&aes_key.to_string().trim_matches('\"'))?;
@ -239,7 +252,9 @@ pub trait Pcd<'a>: Serialize + Deserialize<'a> {
fields2plain.insert(field.to_owned(), serde_json::from_str(&decrypted_value)?);
} else if let Some(commitment) = fields2commit.get(field) { // We should always have a commitment
// We check if the hashed value is the commitment
let hashed_value = AnkPcdHash::from_value_with_outpoint(encrypted_value, &serialize(&commited_in));
let mut value_bin = encrypted_value.to_string().into_bytes();
value_bin.push(i.try_into()?);
let hashed_value = AnkPcdHash::from_value_with_outpoint(&value_bin, &serialize(&commited_in));
if commitment.as_str().unwrap() != &hashed_value.to_string() {
// The value is encrypted, and we don't have the key
// We put the commitment instead of the encrypted value
@ -258,7 +273,7 @@ pub trait Pcd<'a>: Serialize + Deserialize<'a> {
match value {
Value::Object(map) => Ok(map),
_ => Err(Error::msg("self is not a valid json object"))
_ => Err(Error::msg("not a valid json object"))
}
}
@ -544,6 +559,83 @@ mod tests {
.unwrap()
}
#[test]
fn test_sort_map() {
let pcd = json!({
"z": 1,
"b": 2,
"a": 3
});
let expected = json!({
"a": 3,
"b": 2,
"z": 1
});
let sorted_map = pcd.to_sorted_key_values().unwrap();
assert_eq!(Value::Object(sorted_map), expected);
}
#[test]
fn test_sort_empty_map() {
let empty_map = json!({});
let expected = json!({});
let actual_sorted_map = empty_map.to_sorted_key_values().expect("Failed to sort keys");
assert_eq!(
Value::Object(actual_sorted_map),
expected,
"Sorting failed for an empty map"
);
}
#[test]
fn test_sort_already_sorted_map() {
let sorted_map = json!({
"a": 1,
"b": 2,
"c": 3
});
let expected = sorted_map.clone(); // Expected result is the same
let actual_sorted_map = sorted_map.to_sorted_key_values().expect("Failed to sort keys");
assert_eq!(
Value::Object(actual_sorted_map),
expected,
"Sorting failed for an already sorted map"
);
}
#[test]
fn test_sort_mixed_value_map() {
let mixed_map = json!({
"z": [1, 2, 3],
"b": { "nested": true },
"a": 42
});
let expected = json!({
"a": 42,
"b": { "nested": true },
"z": [1, 2, 3]
});
let actual_sorted_map = mixed_map.to_sorted_key_values().expect("Failed to sort keys");
assert_eq!(
Value::Object(actual_sorted_map),
expected,
"Sorting failed for a map with mixed value types"
);
}
#[test]
fn test_validation_rule_new() {
// Valid input