Format
This commit is contained in:
parent
4809459da5
commit
3c15b41699
@ -1,5 +1,8 @@
|
||||
use anyhow::{Error, Result};
|
||||
use sp_client::silentpayments::{bitcoin_hashes::{sha256t_hash_newtype, Hash, HashEngine}, secp256k1::PublicKey};
|
||||
use sp_client::silentpayments::{
|
||||
bitcoin_hashes::{sha256t_hash_newtype, Hash, HashEngine},
|
||||
secp256k1::PublicKey,
|
||||
};
|
||||
|
||||
use aes_gcm::aead::{Aead, Payload};
|
||||
pub use aes_gcm::{AeadCore, Aes256Gcm, KeyInit};
|
||||
@ -29,7 +32,8 @@ pub fn encrypt_with_key(key: &[u8; 32], plaintext: &[u8]) -> Result<Vec<u8>> {
|
||||
msg: plaintext,
|
||||
aad: AAD,
|
||||
};
|
||||
let ciphertext = encryption_eng.encrypt(&nonce, payload)
|
||||
let ciphertext = encryption_eng
|
||||
.encrypt(&nonce, payload)
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
|
||||
let mut res: Vec<u8> = Vec::with_capacity(nonce.len() + ciphertext.len());
|
||||
@ -46,7 +50,8 @@ pub fn decrypt_with_key(key: &[u8; 32], ciphertext: &[u8]) -> Result<Vec<u8>> {
|
||||
msg: &ciphertext[12..],
|
||||
aad: AAD,
|
||||
};
|
||||
let plaintext = decryption_eng.decrypt(nonce.into(), payload)
|
||||
let plaintext = decryption_eng
|
||||
.decrypt(nonce.into(), payload)
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
|
||||
Ok(plaintext)
|
||||
|
@ -2,7 +2,10 @@ use serde::{Deserialize, Serialize};
|
||||
use tsify::Tsify;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use sp_client::{bitcoin::{hashes::Hash, OutPoint, Txid}, spclient::SpWallet};
|
||||
use sp_client::{
|
||||
bitcoin::{hashes::Hash, OutPoint, Txid},
|
||||
spclient::SpWallet,
|
||||
};
|
||||
|
||||
use crate::pcd::Member;
|
||||
|
||||
@ -65,6 +68,11 @@ impl Device {
|
||||
|
||||
pub fn get_other_addresses(&self) -> Vec<String> {
|
||||
let our_address = self.get_wallet().get_client().get_receiving_address();
|
||||
self.to_member().unwrap().get_addresses().into_iter().filter(|a| *a != our_address).collect()
|
||||
self.to_member()
|
||||
.unwrap()
|
||||
.get_addresses()
|
||||
.into_iter()
|
||||
.filter(|a| *a != our_address)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
use std::fmt::Debug;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
pub use sp_client;
|
||||
pub use log;
|
||||
pub use aes_gcm;
|
||||
pub use log;
|
||||
pub use sp_client;
|
||||
|
||||
pub mod crypto;
|
||||
pub mod device;
|
||||
@ -12,8 +12,8 @@ pub mod network;
|
||||
pub mod pcd;
|
||||
pub mod prd;
|
||||
pub mod process;
|
||||
pub mod silentpayments;
|
||||
pub mod signature;
|
||||
pub mod silentpayments;
|
||||
|
||||
pub const MAX_PRD_PAYLOAD_SIZE: usize = u16::MAX as usize; // 64KiB sounds reasonable for now
|
||||
|
||||
|
@ -58,7 +58,7 @@ impl AnkFlag {
|
||||
match self {
|
||||
Self::NewTx => "NewTx",
|
||||
Self::Faucet => "Faucet",
|
||||
Self::Cipher => "Cipher",
|
||||
Self::Cipher => "Cipher",
|
||||
Self::Commit => "Commit",
|
||||
Self::Unknown => "Unknown",
|
||||
}
|
||||
@ -82,7 +82,11 @@ impl CommitMessage {
|
||||
/// Create a new commitment message for the first transaction of the chain
|
||||
/// init_tx must be the hex string of the transaction
|
||||
/// validation_tokens must be empty
|
||||
pub fn new_first_commitment(transaction: Transaction, encrypted_pcd: Map<String, Value>, keys: Map<String, Value>) -> Self {
|
||||
pub fn new_first_commitment(
|
||||
transaction: Transaction,
|
||||
encrypted_pcd: Map<String, Value>,
|
||||
keys: Map<String, Value>,
|
||||
) -> Self {
|
||||
Self {
|
||||
init_tx: serialize(&transaction).to_lower_hex_string(),
|
||||
encrypted_pcd,
|
||||
@ -95,7 +99,11 @@ impl CommitMessage {
|
||||
/// Create a new commitment message for an update transaction
|
||||
/// init_tx must be the hex string of the txid of the first commitment transaction
|
||||
/// validation_tokens must be empty
|
||||
pub fn new_update_commitment(init_tx: OutPoint, encrypted_pcd: Map<String, Value>, keys: Map<String, Value>) -> Self {
|
||||
pub fn new_update_commitment(
|
||||
init_tx: OutPoint,
|
||||
encrypted_pcd: Map<String, Value>,
|
||||
keys: Map<String, Value>,
|
||||
) -> Self {
|
||||
Self {
|
||||
init_tx: init_tx.to_string(),
|
||||
encrypted_pcd,
|
||||
@ -197,8 +205,8 @@ pub struct CachedMessage {
|
||||
pub id: u32,
|
||||
pub status: CachedMessageStatus,
|
||||
pub transaction: Option<String>,
|
||||
pub commitment: Option<String>, // content of the op_return
|
||||
pub sender: Option<Member>, // Never None when message sent
|
||||
pub commitment: Option<String>, // content of the op_return
|
||||
pub sender: Option<Member>, // Never None when message sent
|
||||
pub shared_secrets: Vec<String>, // Max 2 secrets in case we send to both address of the recipient
|
||||
pub cipher: Vec<String>, // Max 2 ciphers in case we send to both address of the recipient
|
||||
pub timestamp: u64,
|
||||
@ -242,7 +250,7 @@ impl CachedMessage {
|
||||
};
|
||||
match engine.decrypt(&nonce.into(), payload) {
|
||||
Ok(plain) => return Ok(plain),
|
||||
Err(_) => continue
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
|
||||
@ -267,7 +275,7 @@ impl CachedMessage {
|
||||
};
|
||||
match engine.decrypt(&nonce.into(), payload) {
|
||||
Ok(plain) => return Ok(plain),
|
||||
Err(_) => continue
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
|
||||
|
317
src/pcd.rs
317
src/pcd.rs
@ -1,26 +1,37 @@
|
||||
use anyhow::{Error, Result};
|
||||
use std::{collections::HashSet, str::FromStr};
|
||||
use anyhow::{Result, Error};
|
||||
|
||||
use aes_gcm::{aead::{Aead, Payload}, AeadCore, Aes256Gcm, KeyInit};
|
||||
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};
|
||||
use sp_client::{bitcoin::{hashes::{sha256t_hash_newtype, Hash, HashEngine}, hex::{DisplayHex, FromHex}, XOnlyPublicKey}, silentpayments::utils::SilentPaymentAddress};
|
||||
use sp_client::{
|
||||
bitcoin::{
|
||||
hashes::{sha256t_hash_newtype, Hash, HashEngine},
|
||||
hex::{DisplayHex, FromHex},
|
||||
XOnlyPublicKey,
|
||||
},
|
||||
silentpayments::utils::SilentPaymentAddress,
|
||||
};
|
||||
use tsify::Tsify;
|
||||
|
||||
use crate::{crypto::AAD, signature::{AnkValidationNoHash, AnkValidationYesHash, Proof}};
|
||||
use crate::{
|
||||
crypto::AAD,
|
||||
signature::{AnkValidationNoHash, AnkValidationYesHash, Proof},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Tsify)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct Member {
|
||||
sp_addresses: Vec<String>
|
||||
sp_addresses: Vec<String>,
|
||||
}
|
||||
|
||||
impl Member {
|
||||
pub fn new(
|
||||
sp_addresses: Vec<SilentPaymentAddress>,
|
||||
) -> Result<Self> {
|
||||
pub fn new(sp_addresses: Vec<SilentPaymentAddress>) -> Result<Self> {
|
||||
if sp_addresses.is_empty() {
|
||||
return Err(Error::msg("empty address set"));
|
||||
}
|
||||
@ -32,13 +43,12 @@ impl Member {
|
||||
}
|
||||
}
|
||||
|
||||
let res: Vec<String> = sp_addresses.iter()
|
||||
let res: Vec<String> = sp_addresses
|
||||
.iter()
|
||||
.map(|a| Into::<String>::into(*a))
|
||||
.collect();
|
||||
|
||||
Ok(Self {
|
||||
sp_addresses: res
|
||||
})
|
||||
Ok(Self { sp_addresses: res })
|
||||
}
|
||||
|
||||
pub fn get_addresses(&self) -> Vec<String> {
|
||||
@ -80,15 +90,24 @@ pub trait Pcd<'a>: Serialize + Deserialize<'a> {
|
||||
AnkPcdHash::from_value(&self.to_value())
|
||||
}
|
||||
|
||||
fn encrypt_fields(&self, fields2keys: &mut Map<String, Value>, fields2cipher: &mut Map<String, Value>) -> Result<()> {
|
||||
fn encrypt_fields(
|
||||
&self,
|
||||
fields2keys: &mut Map<String, Value>,
|
||||
fields2cipher: &mut Map<String, Value>,
|
||||
) -> Result<()> {
|
||||
let as_value = self.to_value();
|
||||
let as_map = as_value.as_object().ok_or_else(|| Error::msg("Expected object"))?;
|
||||
let as_map = as_value
|
||||
.as_object()
|
||||
.ok_or_else(|| Error::msg("Expected object"))?;
|
||||
let mut rng = thread_rng();
|
||||
|
||||
for (field, value) in as_map {
|
||||
let aes_key = Aes256Gcm::generate_key(&mut rng);
|
||||
let nonce = Aes256Gcm::generate_nonce(&mut rng);
|
||||
fields2keys.insert(field.to_owned(), Value::String(aes_key.to_lower_hex_string()));
|
||||
fields2keys.insert(
|
||||
field.to_owned(),
|
||||
Value::String(aes_key.to_lower_hex_string()),
|
||||
);
|
||||
|
||||
let encrypt_eng = Aes256Gcm::new(&aes_key);
|
||||
let value_string = value.to_string();
|
||||
@ -96,7 +115,8 @@ pub trait Pcd<'a>: Serialize + Deserialize<'a> {
|
||||
msg: value_string.as_bytes(),
|
||||
aad: AAD,
|
||||
};
|
||||
let cipher = encrypt_eng.encrypt(&nonce, payload)
|
||||
let cipher = encrypt_eng
|
||||
.encrypt(&nonce, payload)
|
||||
.map_err(|e| Error::msg(format!("Encryption failed for field {}: {}", field, e)))?;
|
||||
|
||||
let mut res = Vec::with_capacity(nonce.len() + cipher.len());
|
||||
@ -109,21 +129,32 @@ pub trait Pcd<'a>: Serialize + Deserialize<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decrypt_fields(&self, fields2keys: &Map<String, Value>, fields2plain: &mut Map<String, Value>) -> Result<()> {
|
||||
fn decrypt_fields(
|
||||
&self,
|
||||
fields2keys: &Map<String, Value>,
|
||||
fields2plain: &mut Map<String, Value>,
|
||||
) -> Result<()> {
|
||||
let value = self.to_value();
|
||||
let map = value.as_object().unwrap();
|
||||
|
||||
for (field, encrypted_value) in map.iter() {
|
||||
if let Some(aes_key) = fields2keys.get(field) {
|
||||
|
||||
let key_buf = Vec::from_hex(&aes_key.to_string().trim_matches('\"'))?;
|
||||
|
||||
let decrypt_eng = Aes256Gcm::new(key_buf.as_slice().into());
|
||||
|
||||
let raw_cipher = Vec::from_hex(&encrypted_value.as_str().ok_or_else(|| Error::msg("Expected string"))?.trim_matches('\"'))?;
|
||||
|
||||
let raw_cipher = Vec::from_hex(
|
||||
&encrypted_value
|
||||
.as_str()
|
||||
.ok_or_else(|| Error::msg("Expected string"))?
|
||||
.trim_matches('\"'),
|
||||
)?;
|
||||
|
||||
if raw_cipher.len() < 28 {
|
||||
return Err(Error::msg(format!("Invalid ciphertext length for field {}", field)));
|
||||
return Err(Error::msg(format!(
|
||||
"Invalid ciphertext length for field {}",
|
||||
field
|
||||
)));
|
||||
}
|
||||
|
||||
let payload = Payload {
|
||||
@ -131,7 +162,8 @@ pub trait Pcd<'a>: Serialize + Deserialize<'a> {
|
||||
aad: AAD,
|
||||
};
|
||||
|
||||
let plain = decrypt_eng.decrypt(raw_cipher[..12].into(), payload)
|
||||
let plain = decrypt_eng
|
||||
.decrypt(raw_cipher[..12].into(), payload)
|
||||
.map_err(|_| Error::msg(format!("Failed to decrypt field {}", field)))?;
|
||||
let decrypted_value: String = String::from_utf8(plain)?;
|
||||
|
||||
@ -151,10 +183,10 @@ pub trait Pcd<'a>: Serialize + Deserialize<'a> {
|
||||
|
||||
impl Pcd<'_> for Value {}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Tsify)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Tsify)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct ValidationRule {
|
||||
quorum: f32, // Must be >= 0.0, <= 1.0, 0.0 means reading right
|
||||
quorum: f32, // Must be >= 0.0, <= 1.0, 0.0 means reading right
|
||||
pub fields: Vec<String>, // Which fields are concerned by this rule
|
||||
min_sig_member: f32, // Must be >= 0.0, <= 1.0, does each member need to sign with all it's devices?
|
||||
}
|
||||
@ -166,7 +198,9 @@ impl ValidationRule {
|
||||
}
|
||||
|
||||
if min_sig_member < 0.0 || min_sig_member > 1.0 {
|
||||
return Err(Error::msg("min_signatures_member must be 0.0 < min_signatures_member <= 1.0"));
|
||||
return Err(Error::msg(
|
||||
"min_signatures_member must be 0.0 < min_signatures_member <= 1.0",
|
||||
));
|
||||
}
|
||||
|
||||
if fields.is_empty() {
|
||||
@ -182,28 +216,41 @@ impl ValidationRule {
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn is_satisfied(&self, field: &str, new_state_hash: AnkPcdHash, proofs: &[&Proof], members: &[Member]) -> bool {
|
||||
pub fn is_satisfied(
|
||||
&self,
|
||||
field: &str,
|
||||
new_state_hash: AnkPcdHash,
|
||||
proofs: &[Proof],
|
||||
members: &[Member],
|
||||
) -> bool {
|
||||
// Check if this rule applies to the field
|
||||
if !self.fields.contains(&field.to_string()) || members.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let required_members = (members.len() as f32 * self.quorum).ceil() as usize;
|
||||
let validating_members = members.iter()
|
||||
let validating_members = members
|
||||
.iter()
|
||||
.filter(|member| {
|
||||
let member_proofs: Vec<&Proof> = proofs.iter()
|
||||
let member_proofs: Vec<&Proof> = proofs
|
||||
.iter()
|
||||
.filter(|p| member.key_is_part_of_member(&p.get_key()))
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
self.satisfy_min_sig_member(member, new_state_hash, &member_proofs).is_ok()
|
||||
|
||||
self.satisfy_min_sig_member(member, new_state_hash, &member_proofs)
|
||||
.is_ok()
|
||||
})
|
||||
.count();
|
||||
|
||||
validating_members >= required_members
|
||||
}
|
||||
|
||||
pub fn satisfy_min_sig_member(&self, member: &Member, new_state_hash: AnkPcdHash, proofs: &[&Proof]) -> Result<()> {
|
||||
pub fn satisfy_min_sig_member(
|
||||
&self,
|
||||
member: &Member,
|
||||
new_state_hash: AnkPcdHash,
|
||||
proofs: &[&Proof],
|
||||
) -> Result<()> {
|
||||
if proofs.len() == 0 {
|
||||
return Err(Error::msg("Can't validate with 0 proof"));
|
||||
}
|
||||
@ -260,9 +307,16 @@ pub struct RoleDefinition {
|
||||
}
|
||||
|
||||
impl RoleDefinition {
|
||||
pub fn is_satisfied(&self, new_state: &Value, previous_state: &Value, proofs: &[&Proof]) -> bool {
|
||||
pub fn is_satisfied(
|
||||
&self,
|
||||
new_state: &Value,
|
||||
previous_state: &Value,
|
||||
proofs: &[Proof],
|
||||
) -> bool {
|
||||
// compute the modified fields
|
||||
let modified_fields: Vec<String> = new_state.as_object().unwrap()
|
||||
let modified_fields: Vec<String> = new_state
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter_map(|(key, value)| {
|
||||
let previous_value = previous_state.as_object().unwrap().get(key);
|
||||
@ -278,12 +332,15 @@ impl RoleDefinition {
|
||||
|
||||
// check that for each field we can satisfy at least one rule
|
||||
modified_fields.iter().all(|field| {
|
||||
self.validation_rules.iter().any(|rule| rule.is_satisfied(field, new_state_hash, proofs, &self.members))
|
||||
self.validation_rules
|
||||
.iter()
|
||||
.any(|rule| rule.is_satisfied(field, new_state_hash, proofs, &self.members))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_applicable_rules(&self, field: &str) -> Vec<&ValidationRule> {
|
||||
self.validation_rules.iter()
|
||||
self.validation_rules
|
||||
.iter()
|
||||
.filter(|rule| rule.fields.contains(&field.to_string()))
|
||||
.collect()
|
||||
}
|
||||
@ -341,11 +398,14 @@ fn compare_arrays(array1: &Vec<Value>, array2: &Vec<Value>) -> bool {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
use sp_client::{bitcoin::{secp256k1::SecretKey, Network}, spclient::{SpClient, SpWallet, SpendKey}};
|
||||
use sp_client::{
|
||||
bitcoin::{secp256k1::SecretKey, Network},
|
||||
spclient::{SpClient, SpWallet, SpendKey},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
pcd::{Member, AnkPcdHash},
|
||||
pcd::{AnkPcdHash, Member},
|
||||
signature::{AnkHash, Proof},
|
||||
};
|
||||
|
||||
@ -353,28 +413,48 @@ mod tests {
|
||||
SpWallet::new(
|
||||
SpClient::new(
|
||||
"default".to_owned(),
|
||||
SecretKey::from_str("a67fb6bf5639efd0aeb19c1c584dd658bceda87660ef1088d4a29d2e77846973").unwrap(),
|
||||
SpendKey::Secret(SecretKey::from_str("a1e4e7947accf33567e716c9f4d186f26398660e36cf6d2e711af64b3518e65c").unwrap()),
|
||||
SecretKey::from_str(
|
||||
"a67fb6bf5639efd0aeb19c1c584dd658bceda87660ef1088d4a29d2e77846973",
|
||||
)
|
||||
.unwrap(),
|
||||
SpendKey::Secret(
|
||||
SecretKey::from_str(
|
||||
"a1e4e7947accf33567e716c9f4d186f26398660e36cf6d2e711af64b3518e65c",
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
None,
|
||||
Network::Signet
|
||||
).unwrap(),
|
||||
Network::Signet,
|
||||
)
|
||||
.unwrap(),
|
||||
None,
|
||||
vec![]
|
||||
).unwrap()
|
||||
vec![],
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn create_bob_wallet() -> SpWallet {
|
||||
SpWallet::new(
|
||||
SpClient::new(
|
||||
"default".to_owned(),
|
||||
SecretKey::from_str("4d9f62b2340de3f0bafd671b78b19edcfded918c4106baefd34512f12f520e9b").unwrap(),
|
||||
SpendKey::Secret(SecretKey::from_str("dafb99602721577997a6fe3da54f86fd113b1b58f0c9a04783d486f87083a32e").unwrap()),
|
||||
SecretKey::from_str(
|
||||
"4d9f62b2340de3f0bafd671b78b19edcfded918c4106baefd34512f12f520e9b",
|
||||
)
|
||||
.unwrap(),
|
||||
SpendKey::Secret(
|
||||
SecretKey::from_str(
|
||||
"dafb99602721577997a6fe3da54f86fd113b1b58f0c9a04783d486f87083a32e",
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
None,
|
||||
Network::Signet
|
||||
).unwrap(),
|
||||
Network::Signet,
|
||||
)
|
||||
.unwrap(),
|
||||
None,
|
||||
vec![]
|
||||
).unwrap()
|
||||
vec![],
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -423,25 +503,52 @@ mod tests {
|
||||
let validation_hash1 = AnkValidationYesHash::from_commitment(new_state_hash);
|
||||
let validation_hash2 = AnkValidationNoHash::from_commitment(new_state_hash);
|
||||
|
||||
let alice_spend_key: SecretKey = alice_wallet.get_client().get_spend_key().try_into().unwrap();
|
||||
let alice_spend_key: SecretKey = alice_wallet
|
||||
.get_client()
|
||||
.get_spend_key()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let bob_spend_key: SecretKey = bob_wallet.get_client().get_spend_key().try_into().unwrap();
|
||||
|
||||
let alice_proof = Proof::new(AnkHash::ValidationNo(validation_hash2), alice_spend_key);
|
||||
let bob_proof = Proof::new(AnkHash::ValidationYes(validation_hash1), bob_spend_key);
|
||||
|
||||
let members = vec![
|
||||
Member::new(vec![SilentPaymentAddress::try_from(alice_wallet.get_client().get_receiving_address()).unwrap()]).unwrap(),
|
||||
Member::new(vec![SilentPaymentAddress::try_from(bob_wallet.get_client().get_receiving_address()).unwrap()]).unwrap(),
|
||||
Member::new(vec![SilentPaymentAddress::try_from(
|
||||
alice_wallet.get_client().get_receiving_address(),
|
||||
)
|
||||
.unwrap()])
|
||||
.unwrap(),
|
||||
Member::new(vec![SilentPaymentAddress::try_from(
|
||||
bob_wallet.get_client().get_receiving_address(),
|
||||
)
|
||||
.unwrap()])
|
||||
.unwrap(),
|
||||
];
|
||||
|
||||
// We test that the rule is satisfied with only bob proof
|
||||
let result = validation_rule.is_satisfied(fields[0].as_str(), new_state_hash, &vec![&bob_proof], &members);
|
||||
let result = validation_rule.is_satisfied(
|
||||
fields[0].as_str(),
|
||||
new_state_hash,
|
||||
&vec![bob_proof],
|
||||
&members,
|
||||
);
|
||||
assert!(result);
|
||||
// Since Alice voted no, rule shouldn't be satisfied only with her proof
|
||||
let result = validation_rule.is_satisfied(fields[0].as_str(), new_state_hash, &vec![&alice_proof], &members);
|
||||
let result = validation_rule.is_satisfied(
|
||||
fields[0].as_str(),
|
||||
new_state_hash,
|
||||
&vec![alice_proof],
|
||||
&members,
|
||||
);
|
||||
assert!(!result);
|
||||
// Since the quorum is 0.5, Bob yes should be enough to satisfy even with Alice's no
|
||||
let result = validation_rule.is_satisfied(fields[0].as_str(), new_state_hash, &vec![&alice_proof, &bob_proof], &members);
|
||||
let result = validation_rule.is_satisfied(
|
||||
fields[0].as_str(),
|
||||
new_state_hash,
|
||||
&vec![alice_proof, bob_proof],
|
||||
&members,
|
||||
);
|
||||
assert!(result);
|
||||
}
|
||||
|
||||
@ -459,28 +566,39 @@ mod tests {
|
||||
let validation_hash1 = AnkValidationYesHash::from_commitment(new_state_hash);
|
||||
let validation_hash2 = AnkValidationNoHash::from_commitment(new_state_hash);
|
||||
|
||||
let alice_spend_key: SecretKey = alice_wallet.get_client().get_spend_key().try_into().unwrap();
|
||||
let alice_spend_key: SecretKey = alice_wallet
|
||||
.get_client()
|
||||
.get_spend_key()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let bob_spend_key: SecretKey = bob_wallet.get_client().get_spend_key().try_into().unwrap();
|
||||
|
||||
let alice_proof = Proof::new(AnkHash::ValidationNo(validation_hash2), alice_spend_key);
|
||||
let bob_proof = Proof::new(AnkHash::ValidationYes(validation_hash1), bob_spend_key);
|
||||
|
||||
let proofs = vec![
|
||||
&alice_proof,
|
||||
&bob_proof
|
||||
];
|
||||
let proofs = vec![alice_proof, bob_proof];
|
||||
|
||||
let members = vec![
|
||||
Member::new(vec![SilentPaymentAddress::try_from(alice_wallet.get_client().get_receiving_address()).unwrap()]).unwrap(),
|
||||
Member::new(vec![SilentPaymentAddress::try_from(bob_wallet.get_client().get_receiving_address()).unwrap()]).unwrap(),
|
||||
Member::new(vec![SilentPaymentAddress::try_from(
|
||||
alice_wallet.get_client().get_receiving_address(),
|
||||
)
|
||||
.unwrap()])
|
||||
.unwrap(),
|
||||
Member::new(vec![SilentPaymentAddress::try_from(
|
||||
bob_wallet.get_client().get_receiving_address(),
|
||||
)
|
||||
.unwrap()])
|
||||
.unwrap(),
|
||||
];
|
||||
|
||||
// Test with empty members list
|
||||
let result = validation_rule.is_satisfied(fields[0].as_str(), new_state_hash, &proofs, &vec![]);
|
||||
let result =
|
||||
validation_rule.is_satisfied(fields[0].as_str(), new_state_hash, &proofs, &vec![]);
|
||||
assert!(!result);
|
||||
|
||||
// Test with no matching field
|
||||
let result = validation_rule.is_satisfied("nonexistent_field", new_state_hash, &proofs, &members);
|
||||
let result =
|
||||
validation_rule.is_satisfied("nonexistent_field", new_state_hash, &proofs, &members);
|
||||
assert!(!result);
|
||||
}
|
||||
|
||||
@ -497,24 +615,34 @@ mod tests {
|
||||
|
||||
let validation_hash = AnkValidationYesHash::from_commitment(new_state_hash);
|
||||
|
||||
let alice_spend_key: SecretKey = alice_wallet.get_client().get_spend_key().try_into().unwrap();
|
||||
let alice_spend_key: SecretKey = alice_wallet
|
||||
.get_client()
|
||||
.get_spend_key()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
// Both proofs are signed by Alice
|
||||
let alice_proof_1 = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key);
|
||||
let alice_proof_2 = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key);
|
||||
|
||||
let proofs = vec![
|
||||
&alice_proof_1,
|
||||
&alice_proof_2
|
||||
];
|
||||
let proofs = vec![alice_proof_1, alice_proof_2];
|
||||
|
||||
let members = vec![
|
||||
Member::new(vec![SilentPaymentAddress::try_from(alice_wallet.get_client().get_receiving_address()).unwrap()]).unwrap(),
|
||||
Member::new(vec![SilentPaymentAddress::try_from(bob_wallet.get_client().get_receiving_address()).unwrap()]).unwrap(),
|
||||
Member::new(vec![SilentPaymentAddress::try_from(
|
||||
alice_wallet.get_client().get_receiving_address(),
|
||||
)
|
||||
.unwrap()])
|
||||
.unwrap(),
|
||||
Member::new(vec![SilentPaymentAddress::try_from(
|
||||
bob_wallet.get_client().get_receiving_address(),
|
||||
)
|
||||
.unwrap()])
|
||||
.unwrap(),
|
||||
];
|
||||
|
||||
// Test case where both proofs are signed by Alice, but both Alice and Bob are passed as members
|
||||
let result = validation_rule.is_satisfied(fields[0].as_str(), new_state_hash, &proofs, &members);
|
||||
let result =
|
||||
validation_rule.is_satisfied(fields[0].as_str(), new_state_hash, &proofs, &members);
|
||||
assert!(!result);
|
||||
}
|
||||
|
||||
@ -531,24 +659,34 @@ mod tests {
|
||||
|
||||
let validation_hash = AnkValidationYesHash::from_commitment(new_state_hash);
|
||||
|
||||
let alice_spend_key: SecretKey = alice_wallet.get_client().get_spend_key().try_into().unwrap();
|
||||
let alice_spend_key: SecretKey = alice_wallet
|
||||
.get_client()
|
||||
.get_spend_key()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
// Both proofs are signed by Alice
|
||||
let alice_proof_1 = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key);
|
||||
let alice_proof_2 = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key);
|
||||
|
||||
let proofs = vec![
|
||||
&alice_proof_1,
|
||||
&alice_proof_2
|
||||
];
|
||||
let proofs = vec![alice_proof_1, alice_proof_2];
|
||||
|
||||
let members = vec![
|
||||
Member::new(vec![SilentPaymentAddress::try_from(alice_wallet.get_client().get_receiving_address()).unwrap()]).unwrap(),
|
||||
Member::new(vec![SilentPaymentAddress::try_from(bob_wallet.get_client().get_receiving_address()).unwrap()]).unwrap(),
|
||||
Member::new(vec![SilentPaymentAddress::try_from(
|
||||
alice_wallet.get_client().get_receiving_address(),
|
||||
)
|
||||
.unwrap()])
|
||||
.unwrap(),
|
||||
Member::new(vec![SilentPaymentAddress::try_from(
|
||||
bob_wallet.get_client().get_receiving_address(),
|
||||
)
|
||||
.unwrap()])
|
||||
.unwrap(),
|
||||
];
|
||||
|
||||
// Test case where quorum is 0.5, but Alice provides two proofs. This should fail since the quorum requires different members.
|
||||
let result = validation_rule.is_satisfied(fields[0].as_str(), new_state_hash, &proofs, &members);
|
||||
let result =
|
||||
validation_rule.is_satisfied(fields[0].as_str(), new_state_hash, &proofs, &members);
|
||||
assert!(!result);
|
||||
}
|
||||
|
||||
@ -559,12 +697,23 @@ mod tests {
|
||||
|
||||
let alice_wallet = create_alice_wallet();
|
||||
|
||||
let member = Member::new(vec![SilentPaymentAddress::try_from(alice_wallet.get_client().get_receiving_address()).unwrap()]).unwrap();
|
||||
let member = Member::new(vec![SilentPaymentAddress::try_from(
|
||||
alice_wallet.get_client().get_receiving_address(),
|
||||
)
|
||||
.unwrap()])
|
||||
.unwrap();
|
||||
let pcd = json!({"field1": "value1"});
|
||||
let new_state_hash = AnkPcdHash::from_value(&pcd);
|
||||
let alice_spend_key: SecretKey = alice_wallet.get_client().get_spend_key().try_into().unwrap();
|
||||
let alice_spend_key: SecretKey = alice_wallet
|
||||
.get_client()
|
||||
.get_spend_key()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let proof = Proof::new(AnkHash::ValidationYes(AnkValidationYesHash::from_commitment(new_state_hash)), alice_spend_key);
|
||||
let proof = Proof::new(
|
||||
AnkHash::ValidationYes(AnkValidationYesHash::from_commitment(new_state_hash)),
|
||||
alice_spend_key,
|
||||
);
|
||||
let proofs = vec![&proof];
|
||||
|
||||
let result = validation_rule.satisfy_min_sig_member(&member, new_state_hash, &proofs);
|
||||
|
62
src/prd.rs
62
src/prd.rs
@ -1,16 +1,16 @@
|
||||
use std::collections::HashSet;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{Result, Error};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use anyhow::{Error, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use serde_json::{Map, Value};
|
||||
use sp_client::bitcoin::hashes::{sha256t_hash_newtype, Hash, HashEngine};
|
||||
use sp_client::bitcoin::hex::FromHex;
|
||||
use sp_client::bitcoin::secp256k1::SecretKey;
|
||||
use sp_client::bitcoin::{OutPoint, XOnlyPublicKey};
|
||||
use sp_client::bitcoin::{OutPoint, Psbt, XOnlyPublicKey};
|
||||
use sp_client::silentpayments::utils::SilentPaymentAddress;
|
||||
use sp_client::spclient::SpWallet;
|
||||
use sp_client::bitcoin::hashes::{sha256t_hash_newtype, Hash, HashEngine};
|
||||
use tsify::Tsify;
|
||||
|
||||
use crate::pcd::{AnkPcdHash, Member, Pcd};
|
||||
@ -24,8 +24,8 @@ pub enum PrdType {
|
||||
None,
|
||||
Message,
|
||||
Update, // Update an existing process
|
||||
List, // request a list of items
|
||||
Response,
|
||||
List, // request a list of items
|
||||
Response,
|
||||
Confirm,
|
||||
TxProposal, // Send a psbt asking for recipient signature, used for login not sure about other use cases
|
||||
}
|
||||
@ -61,7 +61,7 @@ pub struct Prd {
|
||||
pub sender: String,
|
||||
pub keys: Map<String, Value>, // key is a key in pcd, value is the key to decrypt it
|
||||
pub validation_tokens: Vec<Proof>,
|
||||
pub payload: String, // Payload depends on the actual type
|
||||
pub payload: String, // Payload depends on the actual type
|
||||
pub proof: Option<Proof>, // This must be None up to the creation of the network message
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ impl Prd {
|
||||
root_commitment: OutPoint,
|
||||
sender: String, // Should take Member as argument
|
||||
encrypted_pcd: Map<String, Value>,
|
||||
keys: Map<String, Value>
|
||||
keys: Map<String, Value>,
|
||||
) -> Self {
|
||||
Self {
|
||||
prd_type: PrdType::Update,
|
||||
@ -83,6 +83,18 @@ impl Prd {
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_response(
|
||||
root_commitment: OutPoint,
|
||||
sender: String,
|
||||
@ -116,13 +128,14 @@ impl Prd {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn _extract_from_message(plain: &[u8], commitment: Option<&AnkPrdHash>) -> Result<Self> {
|
||||
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"));
|
||||
return Err(anyhow::Error::msg(
|
||||
"Received prd is not what was commited in the transaction",
|
||||
));
|
||||
}
|
||||
}
|
||||
// check that the proof is consistent
|
||||
@ -132,7 +145,12 @@ impl Prd {
|
||||
let addresses = sender.get_addresses();
|
||||
let mut spend_keys: Vec<XOnlyPublicKey> = vec![];
|
||||
for address in addresses {
|
||||
spend_keys.push(<SilentPaymentAddress>::try_from(address)?.get_spend_key().x_only_public_key().0);
|
||||
spend_keys.push(
|
||||
<SilentPaymentAddress>::try_from(address)?
|
||||
.get_spend_key()
|
||||
.x_only_public_key()
|
||||
.0,
|
||||
);
|
||||
}
|
||||
// The key in proof must be one of the sender keys
|
||||
let proof_key = proof.get_key();
|
||||
@ -146,7 +164,7 @@ impl Prd {
|
||||
if !known_key {
|
||||
return Err(anyhow::Error::msg("Proof signed with an unknown key"));
|
||||
}
|
||||
proof.verify()?;
|
||||
proof.verify()?;
|
||||
}
|
||||
// check that the commitment outpoint is valid, just in case
|
||||
OutPoint::from_str(&prd.root_commitment)?;
|
||||
@ -157,13 +175,17 @@ impl Prd {
|
||||
Self::_extract_from_message(plain, None)
|
||||
}
|
||||
|
||||
pub fn extract_from_message_with_commitment(plain: &[u8], commitment: &AnkPrdHash) -> Result<Self> {
|
||||
pub fn extract_from_message_with_commitment(
|
||||
plain: &[u8],
|
||||
commitment: &AnkPrdHash,
|
||||
) -> Result<Self> {
|
||||
Self::_extract_from_message(plain, Some(commitment))
|
||||
}
|
||||
|
||||
pub fn filter_keys(&mut self, to_keep: HashSet<String>) {
|
||||
let current_keys = self.keys.clone();
|
||||
let filtered_keys: Map<String, Value> = current_keys.into_iter()
|
||||
let filtered_keys: Map<String, Value> = current_keys
|
||||
.into_iter()
|
||||
.filter(|(field, _)| to_keep.contains(field))
|
||||
.collect();
|
||||
self.keys = filtered_keys;
|
||||
@ -178,9 +200,12 @@ impl Prd {
|
||||
to_commit.proof = None;
|
||||
|
||||
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();
|
||||
to_commit.payload = Value::from_str(&to_commit.payload)
|
||||
.unwrap()
|
||||
.tagged_hash()
|
||||
.to_string();
|
||||
}
|
||||
|
||||
|
||||
AnkPrdHash::from_value(&to_commit.to_value())
|
||||
}
|
||||
|
||||
@ -189,10 +214,11 @@ impl Prd {
|
||||
let spend_sk: SecretKey = sp_wallet.get_client().get_spend_key().try_into()?;
|
||||
let to_sign = self.clone(); // we sign the whole prd, incl the keys, for each recipient
|
||||
|
||||
let message_hash = AnkHash::Message(AnkMessageHash::from_message(to_sign.to_string().as_bytes()));
|
||||
let message_hash =
|
||||
AnkHash::Message(AnkMessageHash::from_message(to_sign.to_string().as_bytes()));
|
||||
|
||||
let proof = Proof::new(message_hash, spend_sk);
|
||||
|
||||
|
||||
let mut res = self.clone();
|
||||
res.proof = Some(proof);
|
||||
|
||||
|
108
src/process.rs
108
src/process.rs
@ -1,10 +1,19 @@
|
||||
use std::{collections::HashMap, sync::{Mutex, MutexGuard, OnceLock}};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Mutex, MutexGuard, OnceLock},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
use sp_client::{bitcoin::OutPoint, silentpayments::utils::SilentPaymentAddress};
|
||||
|
||||
use crate::{crypto::AnkSharedSecretHash, pcd::{AnkPcdHash, Pcd, RoleDefinition, ValidationRule}, prd::Prd, signature::Proof, MutexExt};
|
||||
use crate::{
|
||||
crypto::AnkSharedSecretHash,
|
||||
pcd::{AnkPcdHash, Pcd, RoleDefinition, ValidationRule},
|
||||
prd::Prd,
|
||||
signature::Proof,
|
||||
MutexExt,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ProcessState {
|
||||
@ -18,19 +27,21 @@ impl ProcessState {
|
||||
/// A state is valid if the attached validation_tokens satisfy the updated conditions defined in the encrypted_pcd
|
||||
pub fn is_valid(&self, previous_state: Option<&ProcessState>) -> Result<bool, anyhow::Error> {
|
||||
// Determine modified fields
|
||||
let modified_fields: Vec<String> = if let Some(previous_state) = previous_state
|
||||
{
|
||||
let res: Vec<String> = self.encrypted_pcd.as_object().unwrap()
|
||||
.iter()
|
||||
.filter_map(|(key, value)| {
|
||||
let previous_value = previous_state.encrypted_pcd.get(key);
|
||||
if previous_value.is_none() || value != previous_value.unwrap() {
|
||||
Some(key.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let modified_fields: Vec<String> = if let Some(previous_state) = previous_state {
|
||||
let res: Vec<String> = self
|
||||
.encrypted_pcd
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter_map(|(key, value)| {
|
||||
let previous_value = previous_state.encrypted_pcd.get(key);
|
||||
if previous_value.is_none() || value != previous_value.unwrap() {
|
||||
Some(key.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if res.is_empty() {
|
||||
return Err(anyhow::anyhow!("State is identical to the previous state"));
|
||||
@ -38,7 +49,9 @@ impl ProcessState {
|
||||
|
||||
res
|
||||
} else {
|
||||
self.encrypted_pcd.as_object().unwrap()
|
||||
self.encrypted_pcd
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect()
|
||||
@ -46,7 +59,8 @@ impl ProcessState {
|
||||
|
||||
// Extract roles and their definitions
|
||||
let mut fields2plains = Map::new();
|
||||
self.encrypted_pcd.decrypt_fields(&self.keys, &mut fields2plains)?;
|
||||
self.encrypted_pcd
|
||||
.decrypt_fields(&self.keys, &mut fields2plains)?;
|
||||
|
||||
let mut roles2rules: HashMap<String, RoleDefinition> = HashMap::new();
|
||||
if let Some(roles) = fields2plains.get("roles") {
|
||||
@ -67,9 +81,11 @@ impl ProcessState {
|
||||
|
||||
// Check if each modified field satisfies at least one applicable rule across all roles
|
||||
let all_fields_validated = modified_fields.iter().all(|field| {
|
||||
let applicable_rules: Vec<(&RoleDefinition, &ValidationRule)> = roles2rules.values()
|
||||
let applicable_rules: Vec<(&RoleDefinition, &ValidationRule)> = roles2rules
|
||||
.values()
|
||||
.flat_map(|role_def| {
|
||||
role_def.get_applicable_rules(field)
|
||||
role_def
|
||||
.get_applicable_rules(field)
|
||||
.into_iter()
|
||||
.map(move |rule| (role_def, rule))
|
||||
})
|
||||
@ -79,10 +95,13 @@ impl ProcessState {
|
||||
return false; // No rules apply to this field, consider it invalid
|
||||
}
|
||||
|
||||
let validation_tokens: Vec<&Proof> = self.validation_tokens.iter().collect();
|
||||
|
||||
applicable_rules.into_iter().any(|(role_def, rule)| {
|
||||
rule.is_satisfied(field, AnkPcdHash::from_value(&self.encrypted_pcd), &validation_tokens, &role_def.members)
|
||||
rule.is_satisfied(
|
||||
field,
|
||||
AnkPcdHash::from_value(&self.encrypted_pcd),
|
||||
&self.validation_tokens,
|
||||
&role_def.members,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
@ -100,24 +119,42 @@ pub struct Process {
|
||||
}
|
||||
|
||||
impl Process {
|
||||
pub fn new(states: Vec<ProcessState>, shared_secrets: HashMap<SilentPaymentAddress, AnkSharedSecretHash>, impending_requests: Vec<Prd>) -> Self {
|
||||
pub fn new(
|
||||
states: Vec<ProcessState>,
|
||||
shared_secrets: HashMap<SilentPaymentAddress, AnkSharedSecretHash>,
|
||||
impending_requests: Vec<Prd>,
|
||||
) -> Self {
|
||||
Self {
|
||||
states,
|
||||
shared_secrets: shared_secrets.into_iter().map(|(k, v)| (k.to_string(), v)).collect(),
|
||||
shared_secrets: shared_secrets
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.to_string(), v))
|
||||
.collect(),
|
||||
impending_requests,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_shared_secret(&mut self, address: SilentPaymentAddress, secret: AnkSharedSecretHash) {
|
||||
pub fn insert_shared_secret(
|
||||
&mut self,
|
||||
address: SilentPaymentAddress,
|
||||
secret: AnkSharedSecretHash,
|
||||
) {
|
||||
self.shared_secrets.insert(address.to_string(), secret);
|
||||
}
|
||||
|
||||
pub fn get_shared_secret_for_address(&self, address: &SilentPaymentAddress) -> Option<AnkSharedSecretHash> {
|
||||
pub fn get_shared_secret_for_address(
|
||||
&self,
|
||||
address: &SilentPaymentAddress,
|
||||
) -> Option<AnkSharedSecretHash> {
|
||||
self.shared_secrets.get(&address.to_string()).cloned()
|
||||
}
|
||||
|
||||
pub fn get_all_secrets(&self) -> HashMap<SilentPaymentAddress, AnkSharedSecretHash> {
|
||||
self.shared_secrets.clone().into_iter().map(|(k, v)| (SilentPaymentAddress::try_from(k.as_str()).unwrap(), v)).collect()
|
||||
self.shared_secrets
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|(k, v)| (SilentPaymentAddress::try_from(k.as_str()).unwrap(), v))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn insert_state(&mut self, state: ProcessState) {
|
||||
@ -142,12 +179,15 @@ impl Process {
|
||||
|
||||
pub fn get_previous_state(&self, current_state: &ProcessState) -> Option<&ProcessState> {
|
||||
// Find the index of the current state
|
||||
let current_index = self.states.iter().position(|state| state == current_state)?;
|
||||
let current_index = self
|
||||
.states
|
||||
.iter()
|
||||
.position(|state| state == current_state)?;
|
||||
|
||||
// Check if there is a previous state
|
||||
if current_index > 0 {
|
||||
// Create a new Process with the previous state
|
||||
let previous_state = self.get_state_at(current_index-1).unwrap();
|
||||
let previous_state = self.get_state_at(current_index - 1).unwrap();
|
||||
Some(&previous_state)
|
||||
} else {
|
||||
None // No previous state exists
|
||||
@ -207,7 +247,11 @@ impl Process {
|
||||
let latest_state = self.get_latest_state().unwrap();
|
||||
let latest_outpoint = latest_state.commited_in;
|
||||
|
||||
let pos = self.states.iter().position(|s| s.commited_in == latest_outpoint).unwrap();
|
||||
let pos = self
|
||||
.states
|
||||
.iter()
|
||||
.position(|s| s.commited_in == latest_outpoint)
|
||||
.unwrap();
|
||||
|
||||
self.states.split_off(pos)
|
||||
}
|
||||
@ -229,7 +273,9 @@ impl Process {
|
||||
let target_commited_in = state.commited_in;
|
||||
|
||||
// Find the index of the first state with the same commited_in value
|
||||
self.states.iter().position(|s| s.commited_in == target_commited_in)
|
||||
self.states
|
||||
.iter()
|
||||
.position(|s| s.commited_in == target_commited_in)
|
||||
}
|
||||
|
||||
pub fn get_number_of_states(&self) -> usize {
|
||||
|
@ -1,10 +1,10 @@
|
||||
use anyhow::Result;
|
||||
use rand::{thread_rng, RngCore};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_client::bitcoin::hashes::{sha256t_hash_newtype, Hash, HashEngine};
|
||||
use sp_client::bitcoin::key::Secp256k1;
|
||||
use sp_client::bitcoin::secp256k1::schnorr::Signature;
|
||||
use sp_client::bitcoin::secp256k1::{Keypair, Message, SecretKey, XOnlyPublicKey};
|
||||
use sp_client::bitcoin::hashes::{sha256t_hash_newtype, Hash, HashEngine};
|
||||
|
||||
use crate::pcd::AnkPcdHash;
|
||||
|
||||
@ -68,9 +68,9 @@ impl AnkHash {
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Proof {
|
||||
signature: Signature,
|
||||
signature: Signature,
|
||||
message: AnkHash,
|
||||
key: XOnlyPublicKey
|
||||
key: XOnlyPublicKey,
|
||||
}
|
||||
|
||||
impl Proof {
|
||||
@ -83,12 +83,16 @@ impl Proof {
|
||||
|
||||
thread_rng().fill_bytes(&mut aux_rand);
|
||||
|
||||
let sig = secp.sign_schnorr_with_aux_rand(&Message::from_digest(message_hash.to_byte_array()), &keypair, &aux_rand);
|
||||
let sig = secp.sign_schnorr_with_aux_rand(
|
||||
&Message::from_digest(message_hash.to_byte_array()),
|
||||
&keypair,
|
||||
&aux_rand,
|
||||
);
|
||||
|
||||
Self {
|
||||
signature: sig,
|
||||
message: message_hash,
|
||||
key: keypair.x_only_public_key().0
|
||||
key: keypair.x_only_public_key().0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,7 +106,11 @@ impl Proof {
|
||||
|
||||
pub fn verify(&self) -> Result<()> {
|
||||
let secp = Secp256k1::verification_only();
|
||||
secp.verify_schnorr(&self.signature, &Message::from_digest(self.message.to_byte_array()), &self.key)?;
|
||||
secp.verify_schnorr(
|
||||
&self.signature,
|
||||
&Message::from_digest(self.message.to_byte_array()),
|
||||
&self.key,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -110,6 +118,4 @@ impl Proof {
|
||||
pub fn to_string(&self) -> String {
|
||||
serde_json::to_string(self).unwrap()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user