Update prd/pcd to keep commitments of each field
This commit is contained in:
parent
4885066bd2
commit
c2fde13e95
@ -1,3 +1,5 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use aes_gcm::aead::{Aead, Payload};
|
use aes_gcm::aead::{Aead, Payload};
|
||||||
use aes_gcm::{Aes256Gcm, KeyInit};
|
use aes_gcm::{Aes256Gcm, KeyInit};
|
||||||
use anyhow::{Error, Result};
|
use anyhow::{Error, Result};
|
||||||
@ -12,7 +14,7 @@ use tsify::Tsify;
|
|||||||
|
|
||||||
use crate::crypto::AAD;
|
use crate::crypto::AAD;
|
||||||
use crate::error::AnkError;
|
use crate::error::AnkError;
|
||||||
use crate::pcd::Member;
|
use crate::pcd::{Member, RoleDefinition};
|
||||||
use crate::signature::Proof;
|
use crate::signature::Proof;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Tsify)]
|
#[derive(Debug, Serialize, Deserialize, Tsify)]
|
||||||
@ -72,8 +74,8 @@ impl AnkFlag {
|
|||||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||||
pub struct CommitMessage {
|
pub struct CommitMessage {
|
||||||
pub init_tx: String, // Can be tx or txid of the first transaction of the chain, which is maybe not ideal
|
pub init_tx: String, // Can be tx or txid of the first transaction of the chain, which is maybe not ideal
|
||||||
pub encrypted_pcd: Map<String, Value>,
|
pub pcd_commitment: Value, // map of field <=> hash of the clear value
|
||||||
pub keys: Map<String, Value>,
|
pub roles: HashMap<String, RoleDefinition>, // Can be hashed and compared with the value above
|
||||||
pub validation_tokens: Vec<Proof>,
|
pub validation_tokens: Vec<Proof>,
|
||||||
pub error: Option<AnkError>,
|
pub error: Option<AnkError>,
|
||||||
}
|
}
|
||||||
@ -84,13 +86,13 @@ impl CommitMessage {
|
|||||||
/// validation_tokens must be empty
|
/// validation_tokens must be empty
|
||||||
pub fn new_first_commitment(
|
pub fn new_first_commitment(
|
||||||
transaction: Transaction,
|
transaction: Transaction,
|
||||||
encrypted_pcd: Map<String, Value>,
|
pcd_commitment: Value,
|
||||||
keys: Map<String, Value>,
|
roles: HashMap<String, RoleDefinition>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
init_tx: serialize(&transaction).to_lower_hex_string(),
|
init_tx: serialize(&transaction).to_lower_hex_string(),
|
||||||
encrypted_pcd,
|
pcd_commitment,
|
||||||
keys,
|
roles,
|
||||||
validation_tokens: vec![],
|
validation_tokens: vec![],
|
||||||
error: None,
|
error: None,
|
||||||
}
|
}
|
||||||
@ -101,13 +103,13 @@ impl CommitMessage {
|
|||||||
/// validation_tokens must be empty
|
/// validation_tokens must be empty
|
||||||
pub fn new_update_commitment(
|
pub fn new_update_commitment(
|
||||||
init_tx: OutPoint,
|
init_tx: OutPoint,
|
||||||
encrypted_pcd: Map<String, Value>,
|
pcd_commitment: Value,
|
||||||
keys: Map<String, Value>,
|
roles: HashMap<String, RoleDefinition>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
init_tx: init_tx.to_string(),
|
init_tx: init_tx.to_string(),
|
||||||
encrypted_pcd,
|
pcd_commitment,
|
||||||
keys,
|
roles,
|
||||||
validation_tokens: vec![],
|
validation_tokens: vec![],
|
||||||
error: None,
|
error: None,
|
||||||
}
|
}
|
||||||
|
112
src/pcd.rs
112
src/pcd.rs
@ -1,5 +1,6 @@
|
|||||||
use anyhow::{Error, Result};
|
use anyhow::{Error, Result};
|
||||||
use std::{collections::HashSet, str::FromStr};
|
use rs_merkle::{algorithms::Sha256, Hasher, MerkleTree};
|
||||||
|
use std::{collections::{HashMap, HashSet}, str::FromStr};
|
||||||
|
|
||||||
use aes_gcm::{
|
use aes_gcm::{
|
||||||
aead::{Aead, Payload},
|
aead::{Aead, Payload},
|
||||||
@ -11,9 +12,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
use sp_client::{
|
use sp_client::{
|
||||||
bitcoin::{
|
bitcoin::{
|
||||||
hashes::{sha256t_hash_newtype, Hash, HashEngine},
|
consensus::serialize, hashes::{sha256t_hash_newtype, Hash, HashEngine}, hex::{DisplayHex, FromHex}, secp256k1::PublicKey, OutPoint, XOnlyPublicKey
|
||||||
hex::{DisplayHex, FromHex},
|
|
||||||
XOnlyPublicKey,
|
|
||||||
},
|
},
|
||||||
silentpayments::utils::SilentPaymentAddress,
|
silentpayments::utils::SilentPaymentAddress,
|
||||||
};
|
};
|
||||||
@ -85,17 +84,57 @@ impl AnkPcdHash {
|
|||||||
AnkPcdHash::from_engine(eng)
|
AnkPcdHash::from_engine(eng)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_map(map: &Map<String, Value>) -> Self {
|
/// Adding the root_commitment guarantee that the same clear value across different processes wont' produce the same hash
|
||||||
let value = Value::Object(map.clone());
|
pub fn from_value_with_outpoint(value: &Value, outpoint: &[u8]) -> Self {
|
||||||
let mut eng = AnkPcdHash::engine();
|
let mut eng = AnkPcdHash::engine();
|
||||||
|
eng.input(outpoint);
|
||||||
eng.input(value.to_string().as_bytes());
|
eng.input(value.to_string().as_bytes());
|
||||||
AnkPcdHash::from_engine(eng)
|
AnkPcdHash::from_engine(eng)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Pcd<'a>: Serialize + Deserialize<'a> {
|
pub trait Pcd<'a>: Serialize + Deserialize<'a> {
|
||||||
fn tagged_hash(&self) -> AnkPcdHash {
|
fn from_string(str: &str) -> Result<Value> {
|
||||||
AnkPcdHash::from_value(&self.to_value())
|
let value: Value = serde_json::from_str(str)?;
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Value::Object(_) => Ok(value),
|
||||||
|
_ => Err(Error::msg("Not a Pcd: not a valid JSON object"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_fields(&self, root_commitment: OutPoint) -> Result<Map<String, Value>> {
|
||||||
|
let map = self.to_value_object()?;
|
||||||
|
|
||||||
|
let outpoint = serialize(&root_commitment);
|
||||||
|
|
||||||
|
let mut field2hash = Map::with_capacity(map.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);
|
||||||
|
field2hash.insert(field, Value::String(tagged_hash.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(field2hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// We need to run `hash_fields` before
|
||||||
|
/// This will just take all the hash value and produces a merkle tree
|
||||||
|
fn create_merkle_tree(fields2hash: &Value) -> Result<MerkleTree<Sha256>> {
|
||||||
|
let leaves: Vec<[u8; 32]> = fields2hash
|
||||||
|
.as_object()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|(_, value)| {
|
||||||
|
let mut res = [0u8; 32];
|
||||||
|
res.copy_from_slice(&Vec::from_hex(value.as_str().unwrap()).unwrap());
|
||||||
|
res
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let merkle_tree = MerkleTree::<Sha256>::from_leaves(&leaves);
|
||||||
|
|
||||||
|
Ok(merkle_tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encrypt_fields(
|
fn encrypt_fields(
|
||||||
@ -104,14 +143,11 @@ pub trait Pcd<'a>: Serialize + Deserialize<'a> {
|
|||||||
fields2keys: &mut Map<String, Value>,
|
fields2keys: &mut Map<String, Value>,
|
||||||
fields2cipher: &mut Map<String, Value>,
|
fields2cipher: &mut Map<String, Value>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let as_value = self.to_value();
|
let map = self.to_value_object()?;
|
||||||
let as_map = as_value
|
|
||||||
.as_object()
|
|
||||||
.ok_or_else(|| Error::msg("Expected object"))?;
|
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
for (field, value) in as_map {
|
for (field, value) in map {
|
||||||
if fields_to_encrypt.contains(field) {
|
if fields_to_encrypt.contains(&field) {
|
||||||
let aes_key = Aes256Gcm::generate_key(&mut rng);
|
let aes_key = Aes256Gcm::generate_key(&mut rng);
|
||||||
let nonce = Aes256Gcm::generate_nonce(&mut rng);
|
let nonce = Aes256Gcm::generate_nonce(&mut rng);
|
||||||
fields2keys.insert(
|
fields2keys.insert(
|
||||||
@ -125,9 +161,9 @@ pub trait Pcd<'a>: Serialize + Deserialize<'a> {
|
|||||||
msg: value_string.as_bytes(),
|
msg: value_string.as_bytes(),
|
||||||
aad: AAD,
|
aad: AAD,
|
||||||
};
|
};
|
||||||
let cipher = encrypt_eng
|
let cipher = encrypt_eng.encrypt(&nonce, payload).map_err(|e| {
|
||||||
.encrypt(&nonce, payload)
|
Error::msg(format!("Encryption failed for field {}: {}", field, e))
|
||||||
.map_err(|e| Error::msg(format!("Encryption failed for field {}: {}", field, e)))?;
|
})?;
|
||||||
|
|
||||||
let mut res = Vec::with_capacity(nonce.len() + cipher.len());
|
let mut res = Vec::with_capacity(nonce.len() + cipher.len());
|
||||||
res.extend_from_slice(&nonce);
|
res.extend_from_slice(&nonce);
|
||||||
@ -147,8 +183,7 @@ pub trait Pcd<'a>: Serialize + Deserialize<'a> {
|
|||||||
fields2keys: &Map<String, Value>,
|
fields2keys: &Map<String, Value>,
|
||||||
fields2plain: &mut Map<String, Value>,
|
fields2plain: &mut Map<String, Value>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let value = self.to_value();
|
let map = self.to_value_object()?;
|
||||||
let map = value.as_object().unwrap();
|
|
||||||
|
|
||||||
for (field, encrypted_value) in map.iter() {
|
for (field, encrypted_value) in map.iter() {
|
||||||
if let Some(aes_key) = fields2keys.get(field) {
|
if let Some(aes_key) = fields2keys.get(field) {
|
||||||
@ -190,8 +225,41 @@ pub trait Pcd<'a>: Serialize + Deserialize<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_value(&self) -> Value {
|
fn to_value_object(&self) -> Result<Map<String, Value>> {
|
||||||
Value::from_str(&serde_json::to_string(&self).unwrap()).unwrap()
|
let value = serde_json::to_value(self)?;
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Value::Object(map) => Ok(map),
|
||||||
|
_ => Err(Error::msg("self is not a valid json object"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_roles(&self) -> Result<HashMap<String, RoleDefinition>> {
|
||||||
|
let obj = self.to_value_object()?;
|
||||||
|
|
||||||
|
let parse_roles_map = |m: &Map<String, Value>| {
|
||||||
|
let mut res: HashMap<String, RoleDefinition> = HashMap::new();
|
||||||
|
for (name, role_def) in m {
|
||||||
|
res.insert(name.clone(), serde_json::from_value(role_def.clone())?);
|
||||||
|
}
|
||||||
|
<Result<HashMap<String, RoleDefinition>, Error>>::Ok(res)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(roles) = obj.get("roles") {
|
||||||
|
match roles {
|
||||||
|
Value::Object(m) => {
|
||||||
|
parse_roles_map(m)
|
||||||
|
},
|
||||||
|
Value::String(s) => {
|
||||||
|
let m: Map<String, Value> = serde_json::from_str(&s)?;
|
||||||
|
|
||||||
|
parse_roles_map(&m)
|
||||||
|
}
|
||||||
|
_ => Err(Error::msg("\"roles\" is not an object"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(Error::msg("No \"roles\" key in this pcd"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,7 +381,7 @@ impl ValidationRule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Tsify)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Tsify)]
|
||||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||||
pub struct RoleDefinition {
|
pub struct RoleDefinition {
|
||||||
pub members: Vec<Member>,
|
pub members: Vec<Member>,
|
||||||
|
87
src/prd.rs
87
src/prd.rs
@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
use sp_client::bitcoin::hashes::{sha256t_hash_newtype, Hash, HashEngine};
|
use sp_client::bitcoin::hashes::{sha256t_hash_newtype, Hash, HashEngine};
|
||||||
use sp_client::bitcoin::hex::FromHex;
|
use sp_client::bitcoin::hex::FromHex;
|
||||||
use sp_client::bitcoin::secp256k1::SecretKey;
|
use sp_client::bitcoin::secp256k1::{PublicKey, SecretKey};
|
||||||
use sp_client::bitcoin::{OutPoint, Psbt, XOnlyPublicKey};
|
use sp_client::bitcoin::{OutPoint, Psbt};
|
||||||
use sp_client::silentpayments::utils::SilentPaymentAddress;
|
use sp_client::silentpayments::utils::SilentPaymentAddress;
|
||||||
use sp_client::spclient::SpWallet;
|
use sp_client::spclient::SpWallet;
|
||||||
use tsify::Tsify;
|
use tsify::Tsify;
|
||||||
@ -26,9 +26,9 @@ pub enum PrdType {
|
|||||||
Message,
|
Message,
|
||||||
Update, // Update an existing process
|
Update, // Update an existing process
|
||||||
List, // request a list of items
|
List, // request a list of items
|
||||||
Response,
|
Response, // Validate (or disagree) with a prd update
|
||||||
Confirm,
|
Confirm, // Confirm we received an update
|
||||||
TxProposal, // Send a psbt asking for recipient signature, used for login not sure about other use cases
|
TxProposal, // Send a psbt asking for recipient signature
|
||||||
}
|
}
|
||||||
|
|
||||||
sha256t_hash_newtype! {
|
sha256t_hash_newtype! {
|
||||||
@ -61,8 +61,9 @@ pub struct Prd {
|
|||||||
pub root_commitment: String,
|
pub root_commitment: String,
|
||||||
pub sender: String,
|
pub sender: String,
|
||||||
pub keys: Map<String, Value>, // key is a key in pcd, value is the key to decrypt it
|
pub keys: Map<String, Value>, // key is a key in pcd, value is the key to decrypt it
|
||||||
|
pub pcd_commitments: Value,
|
||||||
pub validation_tokens: Vec<Proof>,
|
pub validation_tokens: Vec<Proof>,
|
||||||
pub payload: String, // Payload depends on the actual type
|
pub payload: String, // Additional information depending on the type
|
||||||
pub proof: Option<Proof>, // This must be None up to the creation of the network message
|
pub proof: Option<Proof>, // This must be None up to the creation of the network message
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +77,7 @@ impl Prd {
|
|||||||
Self {
|
Self {
|
||||||
prd_type: PrdType::Connect,
|
prd_type: PrdType::Connect,
|
||||||
root_commitment: String::default(),
|
root_commitment: String::default(),
|
||||||
|
pcd_commitments: Value::Null,
|
||||||
sender: serde_json::to_string(&sender).unwrap(),
|
sender: serde_json::to_string(&sender).unwrap(),
|
||||||
validation_tokens,
|
validation_tokens,
|
||||||
keys: Map::new(),
|
keys: Map::new(),
|
||||||
@ -87,8 +89,9 @@ impl Prd {
|
|||||||
pub fn new_update(
|
pub fn new_update(
|
||||||
root_commitment: OutPoint,
|
root_commitment: OutPoint,
|
||||||
sender: String, // Should take Member as argument
|
sender: String, // Should take Member as argument
|
||||||
encrypted_pcd: Map<String, Value>,
|
encrypted_values_merkle_root: String,
|
||||||
keys: Map<String, Value>,
|
keys: Map<String, Value>,
|
||||||
|
pcd_commitments: Value,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
prd_type: PrdType::Update,
|
prd_type: PrdType::Update,
|
||||||
@ -96,19 +99,8 @@ impl Prd {
|
|||||||
sender,
|
sender,
|
||||||
validation_tokens: vec![],
|
validation_tokens: vec![],
|
||||||
keys,
|
keys,
|
||||||
payload: Value::Object(encrypted_pcd).to_string(),
|
pcd_commitments,
|
||||||
proof: None,
|
payload: encrypted_values_merkle_root,
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_tx_proposal(root_commitment: OutPoint, sender: Member, psbt: Psbt) -> Self {
|
|
||||||
Self {
|
|
||||||
prd_type: PrdType::TxProposal,
|
|
||||||
root_commitment: root_commitment.to_string(),
|
|
||||||
sender: serde_json::to_string(&sender).unwrap(),
|
|
||||||
validation_tokens: vec![],
|
|
||||||
keys: Map::new(),
|
|
||||||
payload: serde_json::to_string(&psbt).unwrap(),
|
|
||||||
proof: None,
|
proof: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,7 +109,7 @@ impl Prd {
|
|||||||
root_commitment: OutPoint,
|
root_commitment: OutPoint,
|
||||||
sender: String,
|
sender: String,
|
||||||
validation_tokens: Vec<Proof>,
|
validation_tokens: Vec<Proof>,
|
||||||
pcd_commitment: AnkPcdHash,
|
pcd_commitments: Value,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
prd_type: PrdType::Response,
|
prd_type: PrdType::Response,
|
||||||
@ -125,7 +117,8 @@ impl Prd {
|
|||||||
sender,
|
sender,
|
||||||
validation_tokens: validation_tokens,
|
validation_tokens: validation_tokens,
|
||||||
keys: Map::new(),
|
keys: Map::new(),
|
||||||
payload: pcd_commitment.to_string(),
|
pcd_commitments,
|
||||||
|
payload: String::default(),
|
||||||
proof: None,
|
proof: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,29 +126,22 @@ impl Prd {
|
|||||||
pub fn new_confirm(
|
pub fn new_confirm(
|
||||||
root_commitment: OutPoint,
|
root_commitment: OutPoint,
|
||||||
sender: Member,
|
sender: Member,
|
||||||
pcd_commitment: AnkPcdHash,
|
pcd_commitments: Value,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
prd_type: PrdType::Confirm,
|
prd_type: PrdType::Confirm,
|
||||||
root_commitment: root_commitment.to_string(),
|
root_commitment: root_commitment.to_string(),
|
||||||
|
pcd_commitments,
|
||||||
sender: serde_json::to_string(&sender).unwrap(),
|
sender: serde_json::to_string(&sender).unwrap(),
|
||||||
validation_tokens: vec![],
|
validation_tokens: vec![],
|
||||||
keys: Map::new(),
|
keys: Map::new(),
|
||||||
payload: pcd_commitment.to_string(),
|
payload: String::default(),
|
||||||
proof: None,
|
proof: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _extract_from_message(plain: &[u8], local_address: SilentPaymentAddress, commitment: Option<&AnkPrdHash>) -> Result<Self> {
|
pub fn extract_from_message(plain: &[u8], local_address: SilentPaymentAddress) -> Result<Self> {
|
||||||
let prd: Prd = serde_json::from_slice(plain)?;
|
let prd: Prd = serde_json::from_slice(plain)?;
|
||||||
if let Some(commitment) = commitment {
|
|
||||||
// check that the hash of the prd is consistent with what's commited in the op_return
|
|
||||||
if prd.create_commitment() != *commitment {
|
|
||||||
return Err(anyhow::Error::msg(
|
|
||||||
"Received prd is not what was commited in the transaction",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that the proof is consistent
|
// check that the proof is consistent
|
||||||
if let Some(proof) = prd.proof {
|
if let Some(proof) = prd.proof {
|
||||||
@ -190,23 +176,9 @@ impl Prd {
|
|||||||
} else {
|
} else {
|
||||||
log::warn!("No proof for prd with root_commitment {}", prd.root_commitment);
|
log::warn!("No proof for prd with root_commitment {}", prd.root_commitment);
|
||||||
}
|
}
|
||||||
// check that the commitment outpoint is valid, just in case
|
|
||||||
OutPoint::from_str(&prd.root_commitment)?;
|
|
||||||
Ok(prd)
|
Ok(prd)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_from_message(plain: &[u8], local_address: SilentPaymentAddress) -> Result<Self> {
|
|
||||||
Self::_extract_from_message(plain, local_address, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_from_message_with_commitment(
|
|
||||||
plain: &[u8],
|
|
||||||
local_address: SilentPaymentAddress,
|
|
||||||
commitment: &AnkPrdHash,
|
|
||||||
) -> Result<Self> {
|
|
||||||
Self::_extract_from_message(plain, local_address, Some(commitment))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn filter_keys(&mut self, to_keep: HashSet<String>) {
|
pub fn filter_keys(&mut self, to_keep: HashSet<String>) {
|
||||||
let current_keys = self.keys.clone();
|
let current_keys = self.keys.clone();
|
||||||
let filtered_keys: Map<String, Value> = current_keys
|
let filtered_keys: Map<String, Value> = current_keys
|
||||||
@ -216,25 +188,6 @@ impl Prd {
|
|||||||
self.keys = filtered_keys;
|
self.keys = filtered_keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We commit to everything except the keys and the proof
|
|
||||||
/// Because 1) we need one commitment to common data for all recipients of the transaction
|
|
||||||
/// 2) we already commit to the keys in the sender proof anyway
|
|
||||||
pub fn create_commitment(&self) -> AnkPrdHash {
|
|
||||||
let mut to_commit = self.clone();
|
|
||||||
to_commit.keys = Map::new();
|
|
||||||
to_commit.proof = None;
|
|
||||||
to_commit.validation_tokens = vec![];
|
|
||||||
|
|
||||||
if to_commit.payload.len() != 64 && Vec::from_hex(&to_commit.payload).is_err() {
|
|
||||||
to_commit.payload = Value::from_str(&to_commit.payload)
|
|
||||||
.unwrap()
|
|
||||||
.tagged_hash()
|
|
||||||
.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
AnkPrdHash::from_value(&to_commit.to_value())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate the signed proof and serialize to send over the network
|
/// Generate the signed proof and serialize to send over the network
|
||||||
pub fn to_network_msg(&self, sp_wallet: &SpWallet) -> Result<String> {
|
pub fn to_network_msg(&self, sp_wallet: &SpWallet) -> Result<String> {
|
||||||
let spend_sk: SecretKey = sp_wallet.get_client().get_spend_key().try_into()?;
|
let spend_sk: SecretKey = sp_wallet.get_client().get_spend_key().try_into()?;
|
||||||
@ -255,6 +208,6 @@ impl Prd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_value(&self) -> Value {
|
pub fn to_value(&self) -> Value {
|
||||||
Value::from_str(&self.to_string()).unwrap()
|
serde_json::to_value(self).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
170
src/process.rs
170
src/process.rs
@ -5,12 +5,11 @@ use std::{
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
use sp_client::{bitcoin::OutPoint, silentpayments::utils::SilentPaymentAddress};
|
use sp_client::bitcoin::OutPoint;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
crypto::AnkSharedSecretHash,
|
|
||||||
pcd::{AnkPcdHash, Pcd, RoleDefinition},
|
pcd::{AnkPcdHash, Pcd, RoleDefinition},
|
||||||
prd::Prd,
|
prd::{Prd, PrdType},
|
||||||
signature::Proof,
|
signature::Proof,
|
||||||
MutexExt,
|
MutexExt,
|
||||||
};
|
};
|
||||||
@ -18,12 +17,20 @@ use crate::{
|
|||||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct ProcessState {
|
pub struct ProcessState {
|
||||||
pub commited_in: OutPoint,
|
pub commited_in: OutPoint,
|
||||||
|
pub pcd_commitment: Value, // If we can't modify a field, we just copy the previous value
|
||||||
pub encrypted_pcd: Value, // Some fields may be clear, if the owner of the process decides so
|
pub encrypted_pcd: Value, // Some fields may be clear, if the owner of the process decides so
|
||||||
pub keys: Map<String, Value>, // We may not always have all the keys
|
pub keys: Map<String, Value>, // We may not always have all the keys
|
||||||
pub validation_tokens: Vec<Proof>, // Signature of the hash of the encrypted pcd tagged with some decision like "yes" or "no"
|
pub validation_tokens: Vec<Proof>, // Signature of the hash of the encrypted pcd tagged with some decision like "yes" or "no"
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProcessState {
|
impl ProcessState {
|
||||||
|
pub fn decrypt_pcd(&self) -> Value {
|
||||||
|
// TODO add real error management
|
||||||
|
let mut fields2plain = Map::new();
|
||||||
|
let _ = self.encrypted_pcd.decrypt_fields(&self.keys, &mut fields2plain);
|
||||||
|
Value::Object(fields2plain)
|
||||||
|
}
|
||||||
|
|
||||||
fn compute_modified_fields(&self, previous_state: Option<&ProcessState>) -> Vec<String> {
|
fn compute_modified_fields(&self, previous_state: Option<&ProcessState>) -> Vec<String> {
|
||||||
let new_state = &self.encrypted_pcd;
|
let new_state = &self.encrypted_pcd;
|
||||||
|
|
||||||
@ -134,10 +141,16 @@ impl ProcessState {
|
|||||||
Err(anyhow::anyhow!("Not enough valid proofs"))
|
Err(anyhow::anyhow!("Not enough valid proofs"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.encrypted_pcd == Value::Null ||
|
||||||
|
self.pcd_commitment == Value::Null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A process is basically a succession of states
|
/// A process is basically a succession of states
|
||||||
/// If a process has nothing to do with us, impending_requests will be empty
|
/// If a process has nothing to do with us, impending_requests will be empty
|
||||||
|
/// The latest state MUST be an empty state with only the commited_in field set at the last unspent outpoint
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Process {
|
pub struct Process {
|
||||||
states: Vec<ProcessState>,
|
states: Vec<ProcessState>,
|
||||||
@ -155,8 +168,38 @@ impl Process {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_state(&mut self, state: ProcessState) {
|
pub fn get_last_unspent_outpoint(&self) -> anyhow::Result<OutPoint> {
|
||||||
self.states.push(state);
|
if self.states.is_empty() { return Err(anyhow::Error::msg("Empty Process")); }
|
||||||
|
let last_state = self.states.last().unwrap();
|
||||||
|
Ok(last_state.commited_in)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// We want to always keep an empty state with only the latest unspent commited_in value at the last position
|
||||||
|
pub fn insert_state(&mut self, prd_update: &Prd) -> anyhow::Result<()> {
|
||||||
|
if prd_update.prd_type != PrdType::Update { return Err(anyhow::Error::msg("Only update prd allowed")) }
|
||||||
|
if self.states.is_empty() { return Err(anyhow::Error::msg("Empty Process")); }
|
||||||
|
let new_encrypted_pcd: Value = serde_json::from_str(&prd_update.payload).unwrap();
|
||||||
|
let last_index = self.states.len() - 1;
|
||||||
|
let last_value = self.states.get(last_index).unwrap();
|
||||||
|
if last_value.encrypted_pcd != Value::Null ||
|
||||||
|
last_value.keys != Map::new() ||
|
||||||
|
last_value.pcd_commitment != Value::Null ||
|
||||||
|
last_value.validation_tokens != vec![]
|
||||||
|
{
|
||||||
|
return Err(anyhow::Error::msg("Last state is not empty"));
|
||||||
|
}
|
||||||
|
let empty_state = self.states.remove(last_index);
|
||||||
|
let new_state = ProcessState {
|
||||||
|
commited_in: empty_state.commited_in,
|
||||||
|
pcd_commitment: prd_update.pcd_commitments.clone(),
|
||||||
|
encrypted_pcd: new_encrypted_pcd,
|
||||||
|
keys: prd_update.keys.clone(),
|
||||||
|
validation_tokens: vec![]
|
||||||
|
};
|
||||||
|
self.states.push(new_state);
|
||||||
|
// We always keep an empty state at the end
|
||||||
|
self.states.push(empty_state);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_state_at(&self, index: usize) -> Option<&ProcessState> {
|
pub fn get_state_at(&self, index: usize) -> Option<&ProcessState> {
|
||||||
@ -195,63 +238,79 @@ impl Process {
|
|||||||
/// This is useful when multiple unvalidated states are pending waiting for enough validations
|
/// This is useful when multiple unvalidated states are pending waiting for enough validations
|
||||||
/// It returns the latest state and all the previous states that have the same commited_in
|
/// It returns the latest state and all the previous states that have the same commited_in
|
||||||
/// It means that all the returned states are unvalidated and except for the one that get validated they will be pruned
|
/// It means that all the returned states are unvalidated and except for the one that get validated they will be pruned
|
||||||
pub fn get_latest_concurrent_states(&self) -> Vec<&ProcessState> {
|
pub fn get_latest_concurrent_states(&self) -> anyhow::Result<Vec<&ProcessState>> {
|
||||||
let mut states = vec![];
|
if self.get_number_of_states() == 0 {
|
||||||
let latest_state = self.get_latest_state();
|
// This should never happen, but we better get rid of it now
|
||||||
|
return Err(anyhow::Error::msg("process is empty".to_owned()));
|
||||||
if latest_state.is_none() {
|
|
||||||
return states;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let latest_state_outpoint = latest_state.unwrap().commited_in;
|
let mut states = vec![];
|
||||||
|
let mut previous_commited_in = OutPoint::null();
|
||||||
// We iterate backwards until we find a state that has a different commited_in
|
// We iterate backwards until we find a state that has a different commited_in
|
||||||
for state in self.states.iter().rev() {
|
for state in self.states.iter().rev() {
|
||||||
if state.commited_in != latest_state_outpoint {
|
log::debug!("state: {:#?}", state);
|
||||||
|
if previous_commited_in == OutPoint::null() {
|
||||||
|
previous_commited_in = state.commited_in;
|
||||||
|
} else if previous_commited_in != state.commited_in {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
states.push(state);
|
states.push(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
states
|
Ok(states)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_latest_concurrent_states_mut(&mut self) -> Vec<&mut ProcessState> {
|
pub fn get_latest_concurrent_states_mut(&mut self) -> anyhow::Result<Vec<&mut ProcessState>> {
|
||||||
let mut states = vec![];
|
if self.get_number_of_states() == 0 {
|
||||||
let latest_state = self.get_latest_state();
|
// This should never happen, but we better get rid of it now
|
||||||
|
return Err(anyhow::Error::msg("process is empty".to_owned()));
|
||||||
if latest_state.is_none() {
|
|
||||||
return states;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let latest_state_outpoint = latest_state.unwrap().commited_in;
|
let mut states = vec![];
|
||||||
|
let mut previous_commited_in = OutPoint::null();
|
||||||
// We iterate backwards until we find a state that has a different commited_in
|
// We iterate backwards until we find a state that has a different commited_in
|
||||||
for state in self.states.iter_mut().rev() {
|
for state in self.states.iter_mut().rev() {
|
||||||
if state.commited_in != latest_state_outpoint {
|
if previous_commited_in == OutPoint::null() {
|
||||||
|
previous_commited_in = state.commited_in;
|
||||||
|
} else if previous_commited_in != state.commited_in {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
states.push(state);
|
states.push(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
states
|
Ok(states)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_latest_concurrent_states(&mut self) -> Vec<ProcessState> {
|
pub fn remove_latest_concurrent_states(&mut self) -> anyhow::Result<Vec<ProcessState>> {
|
||||||
if self.states.is_empty() {
|
if self.get_number_of_states() == 0 {
|
||||||
return vec![];
|
// This should never happen, but we better get rid of it now
|
||||||
|
return Err(anyhow::Error::msg("process is empty".to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let latest_state = self.get_latest_state().unwrap();
|
let mut previous_commited_in = OutPoint::null();
|
||||||
let latest_outpoint = latest_state.commited_in;
|
// Iterate backwards, find the reverse position, and adjust to forward position
|
||||||
|
let reverse_position = self.states.iter().rev().position(|state| {
|
||||||
|
if previous_commited_in == OutPoint::null() {
|
||||||
|
previous_commited_in = state.commited_in;
|
||||||
|
false // Continue iterating
|
||||||
|
} else if previous_commited_in != state.commited_in {
|
||||||
|
true // Stop when a different commited_in is found
|
||||||
|
} else {
|
||||||
|
false // Continue iterating
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let pos = self
|
let forward_position = reverse_position.map(|pos| self.states.len() - pos - 1);
|
||||||
.states
|
|
||||||
.iter()
|
|
||||||
.position(|s| s.commited_in == latest_outpoint)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
self.states.split_off(pos)
|
match forward_position {
|
||||||
|
Some(pos) => Ok(self.states.split_off(pos)),
|
||||||
|
None => {
|
||||||
|
// We take everything out
|
||||||
|
let res = self.states.to_vec();
|
||||||
|
self.states = vec![];
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_latest_commited_state(&self) -> Option<&ProcessState> {
|
pub fn get_latest_commited_state(&self) -> Option<&ProcessState> {
|
||||||
@ -259,30 +318,18 @@ impl Process {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let latest_state = self.get_latest_state().unwrap();
|
let last_state = self.states.last().unwrap();
|
||||||
|
|
||||||
// a commited outpoint with an index of u32::MAX is a pending state
|
debug_assert!(last_state.is_empty()); // Last state must always be empty
|
||||||
if latest_state.commited_in.vout != u32::MAX {
|
|
||||||
// This state is commited, there's no pending state
|
|
||||||
return Some(latest_state);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We look for the last state before all the pending states
|
// We look for the last commited in before all the pending states
|
||||||
let latest_outpoint = latest_state.commited_in;
|
let latest_outpoint = last_state.commited_in;
|
||||||
|
|
||||||
let pos = self
|
return self
|
||||||
.states
|
.states
|
||||||
.iter()
|
.iter()
|
||||||
.position(|s| s.commited_in == latest_outpoint)
|
.rev()
|
||||||
.unwrap();
|
.find(|s| s.commited_in != latest_outpoint);
|
||||||
|
|
||||||
if pos == 0 {
|
|
||||||
// There's no commited states, we just return None
|
|
||||||
return None;
|
|
||||||
} else {
|
|
||||||
// The state just before is last commited state
|
|
||||||
return self.get_state_at(pos-1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_impending_request(&mut self, request: Prd) {
|
pub fn insert_impending_request(&mut self, request: Prd) {
|
||||||
@ -297,6 +344,12 @@ impl Process {
|
|||||||
self.impending_requests.iter_mut().collect()
|
self.impending_requests.iter_mut().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prune_impending_requests(&mut self) {
|
||||||
|
self.impending_requests = self.impending_requests.clone().into_iter()
|
||||||
|
.filter(|r| r.prd_type != PrdType::None)
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_state_index(&self, state: &ProcessState) -> Option<usize> {
|
pub fn get_state_index(&self, state: &ProcessState) -> Option<usize> {
|
||||||
// Get the commited_in value of the provided state
|
// Get the commited_in value of the provided state
|
||||||
let target_commited_in = state.commited_in;
|
let target_commited_in = state.commited_in;
|
||||||
@ -444,6 +497,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let outpoint = OutPoint::null();
|
||||||
|
|
||||||
|
let pcd_commitment = encrypted_pcd.hash_fields(outpoint).unwrap();
|
||||||
|
|
||||||
let mut fields2keys = Map::new();
|
let mut fields2keys = Map::new();
|
||||||
let mut fields2cipher = Map::new();
|
let mut fields2cipher = Map::new();
|
||||||
// let field_to_encrypt: Vec<String> = encrypted_pcd.as_object().unwrap().keys().map(|k| k.clone()).collect();
|
// let field_to_encrypt: Vec<String> = encrypted_pcd.as_object().unwrap().keys().map(|k| k.clone()).collect();
|
||||||
@ -454,7 +511,8 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
ProcessState {
|
ProcessState {
|
||||||
commited_in: OutPoint::null(),
|
commited_in: outpoint,
|
||||||
|
pcd_commitment: Value::Object(pcd_commitment),
|
||||||
encrypted_pcd,
|
encrypted_pcd,
|
||||||
keys: Map::new(),
|
keys: Map::new(),
|
||||||
validation_tokens: vec![],
|
validation_tokens: vec![],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user