Compare commits

..

No commits in common. "e2369eaf6f67986debb0b0284b3023fabd2a2b1b" and "684b71e00cd1ecfd1e27963699391fa61a1160a9" have entirely different histories.

11 changed files with 681 additions and 1224 deletions

View File

@ -5,12 +5,11 @@ use tsify::Tsify;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use sp_client::{ use sp_client::{
bitcoin::{ bitcoin::{absolute::Height, hashes::Hash, secp256k1::PublicKey, Amount, OutPoint, Transaction, XOnlyPublicKey},
absolute::Height, hashes::Hash, secp256k1::PublicKey, Amount, OutPoint, Transaction, silentpayments::{
XOnlyPublicKey, utils::receiving::calculate_ecdh_shared_secret, SilentPaymentAddress
}, },
silentpayments::{utils::receiving::calculate_ecdh_shared_secret, SilentPaymentAddress}, OutputSpendStatus, OwnedOutput, SpClient
OutputSpendStatus, OwnedOutput, SpClient,
}; };
use crate::{pcd::Member, silentpayments::SpWallet}; use crate::{pcd::Member, silentpayments::SpWallet};
@ -34,10 +33,6 @@ impl Device {
} }
} }
pub fn get_sp_wallet(&self) -> &SpWallet {
&self.sp_wallet
}
pub fn get_sp_client(&self) -> &SpClient { pub fn get_sp_client(&self) -> &SpClient {
self.sp_wallet.get_sp_client() self.sp_wallet.get_sp_client()
} }
@ -51,27 +46,24 @@ impl Device {
} }
pub fn get_balance(&self) -> Amount { pub fn get_balance(&self) -> Amount {
self.sp_wallet self.sp_wallet.get_outputs().values()
.get_outputs()
.values()
.filter(|output| output.spend_status == OutputSpendStatus::Unspent) .filter(|output| output.spend_status == OutputSpendStatus::Unspent)
.fold(Amount::ZERO, |acc, x| acc + x.amount) .fold(Amount::ZERO, |acc, x| acc + x.amount)
} }
pub fn update_outputs_with_transaction( pub fn update_outputs_with_transaction(&mut self, tx: &Transaction, blockheight: u32, partial_tweak: PublicKey) -> anyhow::Result<HashMap<OutPoint, OwnedOutput>> {
&mut self,
tx: &Transaction,
blockheight: u32,
partial_tweak: PublicKey,
) -> anyhow::Result<HashMap<OutPoint, OwnedOutput>> {
// First check that we haven't already scanned this transaction // First check that we haven't already scanned this transaction
let txid = tx.txid(); let txid = tx.txid();
for i in 0..tx.output.len() { for i in 0..tx.output.len() {
if self.sp_wallet.get_outputs().contains_key(&OutPoint { if self
txid, .sp_wallet
vout: i as u32, .get_outputs()
}) { .contains_key(&OutPoint {
txid,
vout: i as u32,
})
{
return Err(anyhow::Error::msg("Transaction already scanned")); return Err(anyhow::Error::msg("Transaction already scanned"));
} }
} }
@ -132,11 +124,7 @@ impl Device {
let txid = tx.txid(); let txid = tx.txid();
// update outputs that we own and that are spent // update outputs that we own and that are spent
for input in tx.input.iter() { for input in tx.input.iter() {
if let Some(prevout) = self if let Some(prevout) = self.sp_wallet.get_mut_outputs().get_mut(&input.previous_output) {
.sp_wallet
.get_mut_outputs()
.get_mut(&input.previous_output)
{
// This is spent by this tx // This is spent by this tx
prevout.spend_status = OutputSpendStatus::Spent(*txid.as_byte_array()); prevout.spend_status = OutputSpendStatus::Spent(*txid.as_byte_array());
res.insert(input.previous_output, prevout.clone()); res.insert(input.previous_output, prevout.clone());

View File

@ -1,8 +1,6 @@
use sp_client::bitcoin::{ use sp_client::bitcoin::{
consensus::{serialize, Encodable}, consensus::{serialize, Encodable}, hashes::{sha256t_hash_newtype, Hash, HashEngine}, OutPoint
hashes::{sha256t_hash_newtype, Hash, HashEngine}, };
OutPoint,
};
sha256t_hash_newtype! { sha256t_hash_newtype! {
pub struct AnkPcdTag = hash_str("4nk/Pcd"); pub struct AnkPcdTag = hash_str("4nk/Pcd");
@ -16,9 +14,7 @@ impl AnkPcdHash {
let mut eng = AnkPcdHash::engine(); let mut eng = AnkPcdHash::engine();
eng.input(value); eng.input(value);
eng.input(label); eng.input(label);
serialize(outpoint) serialize(outpoint).consensus_encode(&mut eng).expect("hash engine don't return errors");
.consensus_encode(&mut eng)
.expect("hash engine don't return errors");
AnkPcdHash::from_engine(eng) AnkPcdHash::from_engine(eng)
} }
} }

View File

@ -4,21 +4,21 @@ use std::sync::{Mutex, MutexGuard};
pub use aes_gcm; pub use aes_gcm;
pub use env_logger; pub use env_logger;
pub use js_sys;
pub use log; pub use log;
pub use rand; pub use rand;
pub use sp_client;
pub use serde; pub use serde;
pub use serde_json; pub use serde_json;
pub use serde_wasm_bindgen; pub use serde_wasm_bindgen;
pub use sp_client;
pub use tsify; pub use tsify;
pub use wasm_bindgen; pub use wasm_bindgen;
pub use js_sys;
pub use zstd; pub use zstd;
pub mod crypto; pub mod crypto;
pub mod device; pub mod device;
pub mod error;
pub mod hash; pub mod hash;
pub mod error;
pub mod network; pub mod network;
pub mod pcd; pub mod pcd;
pub mod prd; pub mod prd;
@ -27,7 +27,6 @@ pub mod secrets;
pub mod serialization; pub mod serialization;
pub mod signature; pub mod signature;
pub mod silentpayments; pub mod silentpayments;
pub mod updates;
pub const MAX_PRD_PAYLOAD_SIZE: usize = u16::MAX as usize; // 64KiB sounds reasonable for now pub const MAX_PRD_PAYLOAD_SIZE: usize = u16::MAX as usize; // 64KiB sounds reasonable for now
@ -43,8 +42,8 @@ const ROLESLABEL: &str = "roles";
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum SpecialRoles { pub enum SpecialRoles {
Demiurge, // Only valid for the first state of a process Demiurge, // Only valid for the first state of a process
Pairing, // Special validation rules for pairing process Pairing, // Special validation rules for pairing process
Apophis, // Users in this role have the power to destroy the process Apophis, // Users in this role have the power to destroy the process
} }
impl std::fmt::Display for SpecialRoles { impl std::fmt::Display for SpecialRoles {
@ -54,7 +53,7 @@ impl std::fmt::Display for SpecialRoles {
} }
impl From<&SpecialRoles> for &str { impl From<&SpecialRoles> for &str {
fn from(value: &SpecialRoles) -> Self { fn from(value: &SpecialRoles) -> Self {
match value { match value {
SpecialRoles::Demiurge => DEMIURGE, SpecialRoles::Demiurge => DEMIURGE,
SpecialRoles::Pairing => PAIRING, SpecialRoles::Pairing => PAIRING,
@ -114,7 +113,10 @@ impl<T: Debug> MutexExt<T> for Mutex<T> {
Err(poison_error) => { Err(poison_error) => {
let data = poison_error.into_inner(); let data = poison_error.into_inner();
log::debug!("Failed to lock Mutex (poisoned). Data was: {:?}", data); log::debug!(
"Failed to lock Mutex (poisoned). Data was: {:?}",
data
);
Err(anyhow::anyhow!("Failed to lock Mutex (poisoned)")) Err(anyhow::anyhow!("Failed to lock Mutex (poisoned)"))
} }

View File

@ -18,7 +18,6 @@ pub enum AnkFlag {
Cipher, Cipher,
Commit, Commit,
Handshake, Handshake,
Sync,
Unknown, Unknown,
} }
@ -30,7 +29,6 @@ impl From<&str> for AnkFlag {
"Cipher" => Self::Cipher, "Cipher" => Self::Cipher,
"Commit" => Self::Commit, "Commit" => Self::Commit,
"Handshake" => Self::Handshake, "Handshake" => Self::Handshake,
"Sync" => Self::Sync,
_ => Self::Unknown, _ => Self::Unknown,
} }
} }
@ -50,7 +48,6 @@ impl AnkFlag {
2 => Self::Cipher, 2 => Self::Cipher,
3 => Self::Commit, 3 => Self::Commit,
4 => Self::Handshake, 4 => Self::Handshake,
5 => Self::Sync,
_ => Self::Unknown, _ => Self::Unknown,
} }
} }
@ -62,7 +59,6 @@ impl AnkFlag {
Self::Cipher => "Cipher", Self::Cipher => "Cipher",
Self::Commit => "Commit", Self::Commit => "Commit",
Self::Handshake => "Handshake", Self::Handshake => "Handshake",
Self::Sync => "Sync",
Self::Unknown => "Unknown", Self::Unknown => "Unknown",
} }
} }
@ -74,7 +70,7 @@ impl AnkFlag {
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Tsify)] #[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Tsify)]
#[tsify(into_wasm_abi, from_wasm_abi)] #[tsify(into_wasm_abi, from_wasm_abi)]
pub struct CommitMessage { pub struct CommitMessage {
pub process_id: OutPoint, pub process_id: OutPoint,
pub pcd_commitment: PcdCommitments, // map of field <=> hash of the clear value pub pcd_commitment: PcdCommitments, // map of field <=> hash of the clear value
pub roles: Roles, pub roles: Roles,
pub public_data: Pcd, pub public_data: Pcd,
@ -89,7 +85,7 @@ impl CommitMessage {
pcd_commitment: PcdCommitments, pcd_commitment: PcdCommitments,
roles: Roles, roles: Roles,
public_data: Pcd, public_data: Pcd,
validation_tokens: Vec<Proof>, validation_tokens: Vec<Proof>
) -> Self { ) -> Self {
Self { Self {
process_id, process_id,
@ -168,21 +164,14 @@ pub struct HandshakeMessage {
pub sp_address: String, pub sp_address: String,
pub peers_list: OutPointMemberMap, pub peers_list: OutPointMemberMap,
pub processes_list: OutPointProcessMap, pub processes_list: OutPointProcessMap,
pub chain_tip: u32,
} }
impl HandshakeMessage { impl HandshakeMessage {
pub fn new( pub fn new(sp_address: String, peers_list: OutPointMemberMap, processes_list: OutPointProcessMap) -> Self {
sp_address: String,
peers_list: OutPointMemberMap,
processes_list: OutPointProcessMap,
chain_tip: u32,
) -> Self {
Self { Self {
sp_address, sp_address,
peers_list, peers_list,
processes_list, processes_list,
chain_tip,
} }
} }

View File

@ -3,14 +3,16 @@ use rs_merkle::{algorithms::Sha256, MerkleTree};
use serde::ser::SerializeStruct; use serde::ser::SerializeStruct;
use std::collections::btree_map::Keys; use std::collections::btree_map::Keys;
use std::collections::{BTreeMap, HashSet}; use std::collections::{BTreeMap, HashSet};
use std::fmt;
use std::hash::{Hash as StdHash, Hasher}; use std::hash::{Hash as StdHash, Hasher};
use std::fmt;
use std::io::Write; use std::io::Write;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use sp_client::{ use sp_client::{
bitcoin::{hashes::Hash, secp256k1::PublicKey, OutPoint}, bitcoin::{
hashes::Hash, secp256k1::PublicKey, OutPoint
},
silentpayments::SilentPaymentAddress, silentpayments::SilentPaymentAddress,
}; };
use tsify::Tsify; use tsify::Tsify;
@ -19,8 +21,8 @@ use crate::hash::AnkPcdHash;
use crate::serialization::OutPointMemberMap; use crate::serialization::OutPointMemberMap;
use crate::ROLESLABEL; use crate::ROLESLABEL;
use crate::{ use crate::{
serialization::hex_array_btree,
signature::{AnkHash, AnkValidationNoHash, AnkValidationYesHash, Proof}, signature::{AnkHash, AnkValidationNoHash, AnkValidationYesHash, Proof},
serialization::hex_array_btree
}; };
pub const PCD_VERSION: u8 = 1; pub const PCD_VERSION: u8 = 1;
@ -28,42 +30,40 @@ pub(crate) const ZSTD_COMPRESSION_LEVEL: i32 = zstd::DEFAULT_COMPRESSION_LEVEL;
pub trait PcdSerializable { pub trait PcdSerializable {
fn serialize_to_pcd(&self) -> Result<Vec<u8>>; fn serialize_to_pcd(&self) -> Result<Vec<u8>>;
fn deserialize_from_pcd(data: &[u8]) -> Result<Self> fn deserialize_from_pcd(data: &[u8]) -> Result<Self> where Self: Sized;
where
Self: Sized;
} }
impl PcdSerializable for serde_json::Value { impl PcdSerializable for serde_json::Value {
fn serialize_to_pcd(&self) -> Result<Vec<u8>> { fn serialize_to_pcd(&self) -> Result<Vec<u8>> {
let mut compressed = Vec::new(); let mut compressed = Vec::new();
let mut encoder = zstd::stream::Encoder::new(&mut compressed, ZSTD_COMPRESSION_LEVEL)?; let mut encoder = zstd::stream::Encoder::new(&mut compressed, ZSTD_COMPRESSION_LEVEL)?;
encoder.write_all(&[PCD_VERSION])?; encoder.write_all(&[PCD_VERSION])?;
encoder.write_all(&[DataType::Json as u8])?; encoder.write_all(&[DataType::Json as u8])?;
serde_json::to_writer(&mut encoder, self)?; serde_json::to_writer(&mut encoder, self)?;
encoder.finish()?; encoder.finish()?;
Ok(compressed) Ok(compressed)
} }
fn deserialize_from_pcd(data: &[u8]) -> Result<Self> { fn deserialize_from_pcd(data: &[u8]) -> Result<Self> {
let mut decompressed = Vec::new(); let mut decompressed = Vec::new();
zstd::stream::copy_decode(data, &mut decompressed)?; zstd::stream::copy_decode(data, &mut decompressed)?;
if decompressed.len() < 3 { if decompressed.len() < 3 {
return Err(Error::msg("Invalid data: too short")); return Err(Error::msg("Invalid data: too short"));
} }
let version = decompressed[0]; let version = decompressed[0];
let data_type = DataType::try_from(decompressed[1])?; let data_type = DataType::try_from(decompressed[1])?;
match (version, data_type) { match (version, data_type) {
(PCD_VERSION, DataType::Json) => { (PCD_VERSION, DataType::Json) => {
let json_bytes = &decompressed[2..]; let json_bytes = &decompressed[2..];
let json_string = String::from_utf8(json_bytes.to_vec())?; let json_string = String::from_utf8(json_bytes.to_vec())?;
Ok(serde_json::from_str(&json_string)?) Ok(serde_json::from_str(&json_string)?)
} },
_ => Err(Error::msg("Invalid version or data type")), _ => Err(Error::msg("Invalid version or data type"))
} }
} }
} }
@ -72,15 +72,15 @@ impl PcdSerializable for FileBlob {
fn serialize_to_pcd(&self) -> Result<Vec<u8>> { fn serialize_to_pcd(&self) -> Result<Vec<u8>> {
let mut compressed = Vec::new(); let mut compressed = Vec::new();
let mut encoder = zstd::stream::Encoder::new(&mut compressed, ZSTD_COMPRESSION_LEVEL)?; let mut encoder = zstd::stream::Encoder::new(&mut compressed, ZSTD_COMPRESSION_LEVEL)?;
encoder.write_all(&[PCD_VERSION])?; encoder.write_all(&[PCD_VERSION])?;
encoder.write_all(&[DataType::FileBlob as u8])?; encoder.write_all(&[DataType::FileBlob as u8])?;
let type_len = self.r#type.as_bytes().len() as u8; let type_len = self.r#type.as_bytes().len() as u8;
encoder.write_all(&[type_len])?; encoder.write_all(&[type_len])?;
encoder.write_all(self.r#type.as_bytes())?; encoder.write_all(self.r#type.as_bytes())?;
encoder.write_all(&self.data)?; encoder.write_all(&self.data)?;
encoder.finish()?; encoder.finish()?;
Ok(compressed) Ok(compressed)
} }
@ -88,26 +88,23 @@ impl PcdSerializable for FileBlob {
fn deserialize_from_pcd(data: &[u8]) -> Result<Self> { fn deserialize_from_pcd(data: &[u8]) -> Result<Self> {
let mut decompressed = Vec::new(); let mut decompressed = Vec::new();
zstd::stream::copy_decode(data, &mut decompressed)?; zstd::stream::copy_decode(data, &mut decompressed)?;
if decompressed.len() < 4 { if decompressed.len() < 4 {
return Err(Error::msg("Invalid data: too short")); return Err(Error::msg("Invalid data: too short"));
} }
let version = decompressed[0]; let version = decompressed[0];
let data_type = DataType::try_from(decompressed[1])?; let data_type = DataType::try_from(decompressed[1])?;
match (version, data_type) { match (version, data_type) {
(PCD_VERSION, DataType::FileBlob) => { (PCD_VERSION, DataType::FileBlob) => {
let type_len = decompressed[2] as usize; let type_len = decompressed[2] as usize;
let type_str = String::from_utf8(decompressed[3..3 + type_len].to_vec())?; let type_str = String::from_utf8(decompressed[3..3+type_len].to_vec())?;
let data = decompressed[3 + type_len..].to_vec(); let data = decompressed[3+type_len..].to_vec();
Ok(FileBlob { Ok(FileBlob { r#type: type_str, data })
r#type: type_str, },
data, _ => Err(Error::msg("Invalid version or data type"))
})
}
_ => Err(Error::msg("Invalid version or data type")),
} }
} }
} }
@ -189,7 +186,7 @@ impl StdHash for Member {
} }
impl Member { impl Member {
pub fn new(sp_addresses: Vec<SilentPaymentAddress>) -> Self { pub fn new(sp_addresses: Vec<SilentPaymentAddress>) -> Self{
let unique_addresses: HashSet<_> = sp_addresses.into_iter().collect(); let unique_addresses: HashSet<_> = sp_addresses.into_iter().collect();
let res: Vec<String> = unique_addresses let res: Vec<String> = unique_addresses
@ -212,13 +209,11 @@ impl Member {
} }
pub fn get_address_for_key(&self, key: &PublicKey) -> Option<String> { pub fn get_address_for_key(&self, key: &PublicKey) -> Option<String> {
self.sp_addresses self.sp_addresses.iter().find(|a| {
.iter() let addr = SilentPaymentAddress::try_from(a.as_str()).unwrap();
.find(|a| { addr.get_spend_key() == *key
let addr = SilentPaymentAddress::try_from(a.as_str()).unwrap(); })
addr.get_spend_key() == *key .cloned()
})
.cloned()
} }
} }
@ -238,18 +233,13 @@ impl IntoIterator for Pcd {
impl TryFrom<Value> for Pcd { impl TryFrom<Value> for Pcd {
type Error = Error; type Error = Error;
fn try_from(value: Value) -> Result<Self> { fn try_from(value: Value) -> Result<Self> {
let as_object = value let as_object = value.as_object().ok_or_else(|| Error::msg("Pcd must be an object"))?;
.as_object() let map: Result<BTreeMap<String, Vec<u8>>> = as_object.into_iter().map(|(key, value)| {
.ok_or_else(|| Error::msg("Pcd must be an object"))?; // Use the trait method instead of manual serialization
let map: Result<BTreeMap<String, Vec<u8>>> = as_object let compressed = value.serialize_to_pcd()?;
.into_iter() Ok((key.clone(), compressed))
.map(|(key, value)| { }).collect();
// Use the trait method instead of manual serialization
let compressed = value.serialize_to_pcd()?;
Ok((key.clone(), compressed))
})
.collect();
Ok(Pcd(map?)) Ok(Pcd(map?))
} }
@ -258,15 +248,12 @@ impl TryFrom<Value> for Pcd {
impl TryFrom<BTreeMap<String, FileBlob>> for Pcd { impl TryFrom<BTreeMap<String, FileBlob>> for Pcd {
type Error = Error; type Error = Error;
fn try_from(file_blob_map: BTreeMap<String, FileBlob>) -> Result<Self> { fn try_from(file_blob_map: BTreeMap<String, FileBlob>) -> Result<Self> {
let map: Result<BTreeMap<String, Vec<u8>>> = file_blob_map let map: Result<BTreeMap<String, Vec<u8>>> = file_blob_map.into_iter().map(|(key, value)| {
.into_iter() // Use the trait method instead of manual serialization
.map(|(key, value)| { let compressed = value.serialize_to_pcd()?;
// Use the trait method instead of manual serialization Ok((key, compressed))
let compressed = value.serialize_to_pcd()?; }).collect();
Ok((key, compressed))
})
.collect();
Ok(Pcd(map?)) Ok(Pcd(map?))
} }
@ -322,11 +309,7 @@ impl Pcd {
} }
} }
pub fn insert_serializable<T: PcdSerializable>( pub fn insert_serializable<T: PcdSerializable>(&mut self, key: String, value: &T) -> Result<Option<Vec<u8>>> {
&mut self,
key: String,
value: &T,
) -> Result<Option<Vec<u8>>> {
let compressed = value.serialize_to_pcd()?; let compressed = value.serialize_to_pcd()?;
Ok(self.insert(key, compressed)) Ok(self.insert(key, compressed))
} }
@ -334,29 +317,20 @@ impl Pcd {
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, Tsify)] #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, Tsify)]
#[tsify(into_wasm_abi, from_wasm_abi)] #[tsify(into_wasm_abi, from_wasm_abi)]
pub struct PcdCommitments( pub struct PcdCommitments(#[serde(with = "hex_array_btree")] #[tsify(type = "Record<string, string>")] BTreeMap<String, [u8; 32]>);
#[serde(with = "hex_array_btree")]
#[tsify(type = "Record<string, string>")]
BTreeMap<String, [u8; 32]>,
);
impl PcdCommitments { impl PcdCommitments {
/// Creates a new commitments map with both permissioned and public data, + roles /// Creates a new commitments map with both permissioned and public data, + roles
pub fn new(commited_in: &OutPoint, attributes: &Pcd, roles: &Roles) -> Result<Self> { pub fn new(commited_in: &OutPoint, attributes: &Pcd, roles: &Roles) -> Result<Self> {
let mut field2hash: BTreeMap<String, [u8; 32]> = BTreeMap::new(); let mut field2hash: BTreeMap<String, [u8; 32]> = BTreeMap::new();
for (field, value) in attributes.iter() { for (field, value) in attributes.iter() {
let tagged_hash = let tagged_hash = AnkPcdHash::from_pcd_value(value.as_slice(), field.as_bytes(), commited_in);
AnkPcdHash::from_pcd_value(value.as_slice(), field.as_bytes(), commited_in);
field2hash.insert(field.to_owned(), tagged_hash.to_byte_array()); field2hash.insert(field.to_owned(), tagged_hash.to_byte_array());
} }
if roles.len() > 0 { if roles.len() > 0 {
let roles_label = String::from(ROLESLABEL); let roles_label = String::from(ROLESLABEL);
let roles_hash = AnkPcdHash::from_pcd_value( let roles_hash = AnkPcdHash::from_pcd_value(roles.to_bytes()?.as_slice(), roles_label.as_bytes(), commited_in);
roles.to_bytes()?.as_slice(),
roles_label.as_bytes(),
commited_in,
);
field2hash.insert(roles_label, roles_hash.to_byte_array()); field2hash.insert(roles_label, roles_hash.to_byte_array());
} // We should probably return an error if roles are empty } // We should probably return an error if roles are empty
@ -372,12 +346,7 @@ impl PcdCommitments {
self.0.is_empty() self.0.is_empty()
} }
pub fn update_with_value( pub fn update_with_value(&mut self, outpoint: &OutPoint, field: &str, new_value: &[u8]) -> Result<()> {
&mut self,
outpoint: &OutPoint,
field: &str,
new_value: &[u8],
) -> Result<()> {
if let Some(old_hash) = self.get_mut(field) { if let Some(old_hash) = self.get_mut(field) {
// We hash the new_value // We hash the new_value
let tagged_hash = AnkPcdHash::from_pcd_value(new_value, field.as_bytes(), outpoint); let tagged_hash = AnkPcdHash::from_pcd_value(new_value, field.as_bytes(), outpoint);
@ -394,11 +363,11 @@ impl PcdCommitments {
pub fn get(&self, field: &str) -> Option<&[u8; 32]> { pub fn get(&self, field: &str) -> Option<&[u8; 32]> {
self.0.get(field) self.0.get(field)
} }
pub fn get_mut(&mut self, field: &str) -> Option<&mut [u8; 32]> { pub fn get_mut(&mut self, field: &str) -> Option<&mut [u8; 32]> {
self.0.get_mut(field) self.0.get_mut(field)
} }
pub fn iter(&self) -> std::collections::btree_map::Iter<'_, String, [u8; 32]> { pub fn iter(&self) -> std::collections::btree_map::Iter<'_, String, [u8; 32]> {
self.0.iter() self.0.iter()
@ -414,7 +383,10 @@ impl PcdCommitments {
/// Since BTreeMap keys order is deterministic, we can guarantee a consistent merkle tree /// Since BTreeMap keys order is deterministic, we can guarantee a consistent merkle tree
pub fn create_merkle_tree(&self) -> Result<MerkleTree<Sha256>> { pub fn create_merkle_tree(&self) -> Result<MerkleTree<Sha256>> {
let leaves: Vec<[u8; 32]> = self.0.values().map(|hash| *hash).collect(); let leaves: Vec<[u8; 32]> = self.0
.values()
.map(|hash| *hash)
.collect();
let merkle_tree = MerkleTree::<Sha256>::from_leaves(leaves.as_slice()); let merkle_tree = MerkleTree::<Sha256>::from_leaves(leaves.as_slice());
@ -475,12 +447,7 @@ impl ValidationRule {
return Err(Error::msg("Field isn't part of this rule")); return Err(Error::msg("Field isn't part of this rule"));
} else if members.is_empty() { } else if members.is_empty() {
return Err(Error::msg("Members list is empty")); return Err(Error::msg("Members list is empty"));
} else if self.quorum <= 0.0 } else if self.quorum <= 0.0 || self.quorum > 1.0 || self.quorum.is_sign_negative() || self.quorum.is_nan() { // Just to be sure
|| self.quorum > 1.0
|| self.quorum.is_sign_negative()
|| self.quorum.is_nan()
{
// Just to be sure
return Err(Error::msg("This rule is read only")); return Err(Error::msg("This rule is read only"));
} }
@ -488,9 +455,7 @@ impl ValidationRule {
let validating_members = members let validating_members = members
.iter() .iter()
.filter(|member| { .filter(|member| {
if member.sp_addresses.is_empty() { if member.sp_addresses.is_empty() { return false }; // This can happen when a member in the rule wasn't found in the network
return false;
}; // This can happen when a member in the rule wasn't found in the network
let member_proofs: Vec<&Proof> = proofs let member_proofs: Vec<&Proof> = proofs
.iter() .iter()
.filter(|p| member.key_is_part_of_member(&p.get_key())) .filter(|p| member.key_is_part_of_member(&p.get_key()))
@ -501,11 +466,7 @@ impl ValidationRule {
}) })
.count(); .count();
if validating_members >= required_members { if validating_members >= required_members { Ok(()) } else { Err(Error::msg("Not enough members to validate"))}
Ok(())
} else {
Err(Error::msg("Not enough members to validate"))
}
} }
pub fn satisfy_min_sig_member( pub fn satisfy_min_sig_member(
@ -579,22 +540,22 @@ impl RoleDefinition {
) -> Result<()> { ) -> Result<()> {
let empty_member = Member::new(vec![]); let empty_member = Member::new(vec![]);
if diff.iter().all(|field| { if diff.iter().all(|field| {
self.validation_rules.iter().any(|rule| { self.validation_rules
let members: Vec<&Member> = self .iter()
.members .any(|rule| {
.iter() let members: Vec<&Member> = self.members.iter()
.map(|outpoint| { .map(|outpoint| {
if let Some(member) = members_list.0.get(outpoint) { if let Some(member) = members_list.0.get(outpoint) {
member member
} else { } else {
&empty_member &empty_member
} }
}) })
.collect(); .collect();
rule.is_satisfied(field, new_state_merkle_root, proofs, &members) rule.is_satisfied(field, new_state_merkle_root, proofs, &members).is_ok()
.is_ok() })
}) })
}) { {
Ok(()) Ok(())
} else { } else {
Err(Error::msg("Failed to validate all rules")) Err(Error::msg("Failed to validate all rules"))
@ -622,6 +583,7 @@ impl IntoIterator for Roles {
} }
} }
impl Roles { impl Roles {
pub fn new(roles: BTreeMap<String, RoleDefinition>) -> Self { pub fn new(roles: BTreeMap<String, RoleDefinition>) -> Self {
Roles(roles) Roles(roles)
@ -630,7 +592,7 @@ impl Roles {
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.0.is_empty() self.0.is_empty()
} }
pub fn to_bytes(&self) -> Result<Vec<u8>> { pub fn to_bytes(&self) -> Result<Vec<u8>> {
Ok(serde_json::to_vec(self)?) Ok(serde_json::to_vec(self)?)
} }
@ -641,11 +603,11 @@ impl Roles {
pub fn get(&self, key: &str) -> Option<&RoleDefinition> { pub fn get(&self, key: &str) -> Option<&RoleDefinition> {
self.0.get(key) self.0.get(key)
} }
pub fn get_mut(&mut self, key: &str) -> Option<&mut RoleDefinition> { pub fn get_mut(&mut self, key: &str) -> Option<&mut RoleDefinition> {
self.0.get_mut(key) self.0.get_mut(key)
} }
pub fn iter(&self) -> std::collections::btree_map::Iter<'_, String, RoleDefinition> { pub fn iter(&self) -> std::collections::btree_map::Iter<'_, String, RoleDefinition> {
self.0.iter() self.0.iter()
@ -658,12 +620,12 @@ impl Roles {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{collections::HashMap, str::FromStr};
use serde_json::json; use serde_json::json;
use sp_client::{ use sp_client::{
bitcoin::{secp256k1::SecretKey, Network}, bitcoin::{secp256k1::SecretKey, Network},
SpClient, SpendKey, SpClient, SpendKey,
}; };
use std::{collections::HashMap, str::FromStr};
use super::*; use super::*;
use crate::{ use crate::{
@ -673,8 +635,10 @@ mod tests {
fn create_alice_wallet() -> SpClient { fn create_alice_wallet() -> SpClient {
SpClient::new( SpClient::new(
SecretKey::from_str("a67fb6bf5639efd0aeb19c1c584dd658bceda87660ef1088d4a29d2e77846973") SecretKey::from_str(
.unwrap(), "a67fb6bf5639efd0aeb19c1c584dd658bceda87660ef1088d4a29d2e77846973",
)
.unwrap(),
SpendKey::Secret( SpendKey::Secret(
SecretKey::from_str( SecretKey::from_str(
"a1e4e7947accf33567e716c9f4d186f26398660e36cf6d2e711af64b3518e65c", "a1e4e7947accf33567e716c9f4d186f26398660e36cf6d2e711af64b3518e65c",
@ -688,8 +652,10 @@ mod tests {
fn create_bob_wallet() -> SpClient { fn create_bob_wallet() -> SpClient {
SpClient::new( SpClient::new(
SecretKey::from_str("4d9f62b2340de3f0bafd671b78b19edcfded918c4106baefd34512f12f520e9b") SecretKey::from_str(
.unwrap(), "4d9f62b2340de3f0bafd671b78b19edcfded918c4106baefd34512f12f520e9b",
)
.unwrap(),
SpendKey::Secret( SpendKey::Secret(
SecretKey::from_str( SecretKey::from_str(
"dafb99602721577997a6fe3da54f86fd113b1b58f0c9a04783d486f87083a32e", "dafb99602721577997a6fe3da54f86fd113b1b58f0c9a04783d486f87083a32e",
@ -706,23 +672,13 @@ mod tests {
let bob_address = &addresses[1]; let bob_address = &addresses[1];
HashMap::from([ HashMap::from([
( (
OutPoint::from_str( OutPoint::from_str("b2f105a9df436d16b99e46453b15a0ffc584d136ceda35c0baea28e7e3ade8be:0").unwrap(),
"b2f105a9df436d16b99e46453b15a0ffc584d136ceda35c0baea28e7e3ade8be:0", Member::new(vec![SilentPaymentAddress::try_from(alice_address.as_str()).unwrap()])
)
.unwrap(),
Member::new(vec![
SilentPaymentAddress::try_from(alice_address.as_str()).unwrap()
]),
), ),
( (
OutPoint::from_str( OutPoint::from_str("3cb9e3bf8ec72625c0347a665ab383fda9213d4544ff114ac800a9837b585897:0").unwrap(),
"3cb9e3bf8ec72625c0347a665ab383fda9213d4544ff114ac800a9837b585897:0", Member::new(vec![SilentPaymentAddress::try_from(bob_address.as_str()).unwrap()])
) )
.unwrap(),
Member::new(vec![
SilentPaymentAddress::try_from(bob_address.as_str()).unwrap()
]),
),
]) ])
} }
@ -771,15 +727,16 @@ mod tests {
let public_data = BTreeMap::new(); let public_data = BTreeMap::new();
let roles = BTreeMap::new(); // roles are not necessary here, we can leave it empty let roles = BTreeMap::new(); // roles are not necessary here, we can leave it empty
let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data));
let commitments = let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles))
.unwrap();
let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap();
let validation_hash1 = AnkValidationYesHash::from_merkle_root(new_state_merkle_root); let validation_hash1 = AnkValidationYesHash::from_merkle_root(new_state_merkle_root);
let validation_hash2 = AnkValidationNoHash::from_merkle_root(new_state_merkle_root); let validation_hash2 = AnkValidationNoHash::from_merkle_root(new_state_merkle_root);
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap(); let alice_spend_key: SecretKey = alice_wallet
.get_spend_key()
.try_into()
.unwrap();
let bob_spend_key: SecretKey = bob_wallet.get_spend_key().try_into().unwrap(); let bob_spend_key: SecretKey = bob_wallet.get_spend_key().try_into().unwrap();
let alice_proof = Proof::new(AnkHash::ValidationNo(validation_hash2), alice_spend_key); let alice_proof = Proof::new(AnkHash::ValidationNo(validation_hash2), alice_spend_key);
@ -787,7 +744,7 @@ mod tests {
let members_list = get_members_map([ let members_list = get_members_map([
alice_wallet.get_receiving_address().to_string(), alice_wallet.get_receiving_address().to_string(),
bob_wallet.get_receiving_address().to_string(), bob_wallet.get_receiving_address().to_string()
]); ]);
let members: Vec<&Member> = members_list.values().collect(); let members: Vec<&Member> = members_list.values().collect();
@ -831,15 +788,16 @@ mod tests {
let public_data = BTreeMap::new(); let public_data = BTreeMap::new();
let roles = BTreeMap::new(); let roles = BTreeMap::new();
let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data));
let commitments = let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles))
.unwrap();
let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap();
let validation_hash_yes = AnkValidationYesHash::from_merkle_root(new_state_merkle_root); let validation_hash_yes = AnkValidationYesHash::from_merkle_root(new_state_merkle_root);
let validation_hash_no = AnkValidationNoHash::from_merkle_root(new_state_merkle_root); let validation_hash_no = AnkValidationNoHash::from_merkle_root(new_state_merkle_root);
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap(); let alice_spend_key: SecretKey = alice_wallet
.get_spend_key()
.try_into()
.unwrap();
let bob_spend_key: SecretKey = bob_wallet.get_spend_key().try_into().unwrap(); let bob_spend_key: SecretKey = bob_wallet.get_spend_key().try_into().unwrap();
let alice_proof = Proof::new(AnkHash::ValidationNo(validation_hash_no), alice_spend_key); let alice_proof = Proof::new(AnkHash::ValidationNo(validation_hash_no), alice_spend_key);
@ -849,27 +807,19 @@ mod tests {
let members_list = get_members_map([ let members_list = get_members_map([
alice_wallet.get_receiving_address().to_string(), alice_wallet.get_receiving_address().to_string(),
bob_wallet.get_receiving_address().to_string(), bob_wallet.get_receiving_address().to_string()
]); ]);
let members: Vec<&Member> = members_list.values().collect(); let members: Vec<&Member> = members_list.values().collect();
// Test with empty members list // Test with empty members list
let result = validation_rule.is_satisfied( let result =
fields[0].as_str(), validation_rule.is_satisfied(fields[0].as_str(), new_state_merkle_root, &proofs, &vec![]);
new_state_merkle_root,
&proofs,
&vec![],
);
assert!(result.is_err()); assert!(result.is_err());
// Test with no matching field // Test with no matching field
let result = validation_rule.is_satisfied( let result =
"nonexistent_field", validation_rule.is_satisfied("nonexistent_field", new_state_merkle_root, &proofs, &members);
new_state_merkle_root,
&proofs,
&members,
);
assert!(result.is_err()); assert!(result.is_err());
} }
@ -886,14 +836,15 @@ mod tests {
let public_data = BTreeMap::new(); let public_data = BTreeMap::new();
let roles = BTreeMap::new(); let roles = BTreeMap::new();
let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data));
let commitments = let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles))
.unwrap();
let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap();
let validation_hash = AnkValidationYesHash::from_merkle_root(new_state_merkle_root); let validation_hash = AnkValidationYesHash::from_merkle_root(new_state_merkle_root);
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap(); let alice_spend_key: SecretKey = alice_wallet
.get_spend_key()
.try_into()
.unwrap();
// Both proofs are signed by Alice // Both proofs are signed by Alice
let alice_proof_1 = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key); let alice_proof_1 = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key);
@ -903,18 +854,14 @@ mod tests {
let members_list = get_members_map([ let members_list = get_members_map([
alice_wallet.get_receiving_address().to_string(), alice_wallet.get_receiving_address().to_string(),
bob_wallet.get_receiving_address().to_string(), bob_wallet.get_receiving_address().to_string()
]); ]);
let members: Vec<&Member> = members_list.values().collect(); let members: Vec<&Member> = members_list.values().collect();
// Test case where both proofs are signed by Alice, but both Alice and Bob are passed as members // Test case where both proofs are signed by Alice, but both Alice and Bob are passed as members
let result = validation_rule.is_satisfied( let result =
fields[0].as_str(), validation_rule.is_satisfied(fields[0].as_str(), new_state_merkle_root, &proofs, &members);
new_state_merkle_root,
&proofs,
&members,
);
assert!(result.is_err()); assert!(result.is_err());
} }
@ -931,14 +878,15 @@ mod tests {
let public_data = BTreeMap::new(); let public_data = BTreeMap::new();
let roles = BTreeMap::new(); let roles = BTreeMap::new();
let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data));
let commitments = let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles))
.unwrap();
let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap();
let validation_hash = AnkValidationYesHash::from_merkle_root(new_state_merkle_root); let validation_hash = AnkValidationYesHash::from_merkle_root(new_state_merkle_root);
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap(); let alice_spend_key: SecretKey = alice_wallet
.get_spend_key()
.try_into()
.unwrap();
// Both proofs are signed by Alice // Both proofs are signed by Alice
let alice_proof_1 = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key); let alice_proof_1 = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key);
@ -948,18 +896,14 @@ mod tests {
let members_list = get_members_map([ let members_list = get_members_map([
alice_wallet.get_receiving_address().to_string(), alice_wallet.get_receiving_address().to_string(),
bob_wallet.get_receiving_address().to_string(), bob_wallet.get_receiving_address().to_string()
]); ]);
let members: Vec<&Member> = members_list.values().collect(); let members: Vec<&Member> = members_list.values().collect();
// Test case where quorum is 0.5, but Alice provides two proofs. This should fail since the quorum requires different members. // 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( let result =
fields[0].as_str(), validation_rule.is_satisfied(fields[0].as_str(), new_state_merkle_root, &proofs, &members);
new_state_merkle_root,
&proofs,
&members,
);
assert!(result.is_err()); assert!(result.is_err());
} }
@ -979,23 +923,21 @@ mod tests {
let public_data = BTreeMap::new(); let public_data = BTreeMap::new();
let roles = BTreeMap::new(); let roles = BTreeMap::new();
let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data));
let commitments = let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles))
.unwrap();
let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap();
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap(); let alice_spend_key: SecretKey = alice_wallet
.get_spend_key()
.try_into()
.unwrap();
let proof = Proof::new( let proof = Proof::new(
AnkHash::ValidationYes(AnkValidationYesHash::from_merkle_root( AnkHash::ValidationYes(AnkValidationYesHash::from_merkle_root(new_state_merkle_root)),
new_state_merkle_root,
)),
alice_spend_key, alice_spend_key,
); );
let proofs = vec![&proof]; let proofs = vec![&proof];
let result = let result = validation_rule.satisfy_min_sig_member(&member, new_state_merkle_root, &proofs);
validation_rule.satisfy_min_sig_member(&member, new_state_merkle_root, &proofs);
assert!(result.is_ok()); // Example check - make more meaningful assertions based on real Proof and Member implementations assert!(result.is_ok()); // Example check - make more meaningful assertions based on real Proof and Member implementations
} }
@ -1029,14 +971,15 @@ mod tests {
let public_data = BTreeMap::new(); let public_data = BTreeMap::new();
let roles = BTreeMap::new(); let roles = BTreeMap::new();
let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data));
let commitments = let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles))
.unwrap();
let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap();
let validation_hash = AnkValidationYesHash::from_merkle_root(new_state_merkle_root); let validation_hash = AnkValidationYesHash::from_merkle_root(new_state_merkle_root);
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap(); let alice_spend_key: SecretKey = alice_wallet
.get_spend_key()
.try_into()
.unwrap();
let bob_spend_key: SecretKey = bob_wallet.get_spend_key().try_into().unwrap(); let bob_spend_key: SecretKey = bob_wallet.get_spend_key().try_into().unwrap();
let alice_proof = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key); let alice_proof = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key);
@ -1044,31 +987,19 @@ mod tests {
let proofs = vec![alice_proof, bob_proof]; let proofs = vec![alice_proof, bob_proof];
let modified_fields: Vec<String> = new_state let modified_fields: Vec<String> = new_state.as_object().unwrap().iter().map(|(key, _)| key.clone()).collect();
.as_object()
.unwrap()
.iter()
.map(|(key, _)| key.clone())
.collect();
assert!(role_def assert!(role_def.is_satisfied(modified_fields, new_state_merkle_root, &proofs, &OutPointMemberMap(members)).is_ok());
.is_satisfied(
modified_fields,
new_state_merkle_root,
&proofs,
&OutPointMemberMap(members)
)
.is_ok());
} }
#[test] #[test]
fn test_no_rule_satisfied() { fn test_no_rule_satisfied() {
let alice_wallet = create_alice_wallet(); let alice_wallet = create_alice_wallet();
let bob_wallet = create_bob_wallet(); let bob_wallet = create_bob_wallet();
let members_list = get_members_map([ let members_list = get_members_map([
alice_wallet.get_receiving_address().to_string(), alice_wallet.get_receiving_address().to_string(),
bob_wallet.get_receiving_address().to_string(), bob_wallet.get_receiving_address().to_string()
]); ]);
let fields = vec!["field1".to_string(), "field2".to_string()]; let fields = vec!["field1".to_string(), "field2".to_string()];
@ -1092,15 +1023,16 @@ mod tests {
let public_data = BTreeMap::new(); let public_data = BTreeMap::new();
let roles = BTreeMap::new(); let roles = BTreeMap::new();
let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data));
let commitments = let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles))
.unwrap();
let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap();
// let validation_hash1 = AnkValidationYesHash::from_commitment(new_state_hash); // let validation_hash1 = AnkValidationYesHash::from_commitment(new_state_hash);
let validation_hash = AnkValidationNoHash::from_merkle_root(new_state_merkle_root); let validation_hash = AnkValidationNoHash::from_merkle_root(new_state_merkle_root);
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap(); let alice_spend_key: SecretKey = alice_wallet
.get_spend_key()
.try_into()
.unwrap();
let bob_spend_key: SecretKey = bob_wallet.get_spend_key().try_into().unwrap(); let bob_spend_key: SecretKey = bob_wallet.get_spend_key().try_into().unwrap();
let alice_proof = Proof::new(AnkHash::ValidationNo(validation_hash), alice_spend_key); let alice_proof = Proof::new(AnkHash::ValidationNo(validation_hash), alice_spend_key);
@ -1108,21 +1040,9 @@ mod tests {
let proofs = vec![alice_proof, bob_proof]; let proofs = vec![alice_proof, bob_proof];
let modified_fields: Vec<String> = new_state let modified_fields: Vec<String> = new_state.as_object().unwrap().iter().map(|(key, _)| key.clone()).collect();
.as_object()
.unwrap()
.iter()
.map(|(key, _)| key.clone())
.collect();
assert!(role_def assert!(role_def.is_satisfied(modified_fields, new_state_merkle_root, &proofs, &OutPointMemberMap(members_list)).is_err());
.is_satisfied(
modified_fields,
new_state_merkle_root,
&proofs,
&OutPointMemberMap(members_list)
)
.is_err());
} }
#[test] #[test]
@ -1156,15 +1076,16 @@ mod tests {
let public_data = BTreeMap::new(); let public_data = BTreeMap::new();
let roles = BTreeMap::new(); let roles = BTreeMap::new();
let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data));
let commitments = let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles))
.unwrap();
let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap();
let validation_hash = AnkValidationYesHash::from_merkle_root(new_state_merkle_root); let validation_hash = AnkValidationYesHash::from_merkle_root(new_state_merkle_root);
// let validation_hash = AnkValidationNoHash::from_merkle_root(new_state_merkle_root); // let validation_hash = AnkValidationNoHash::from_merkle_root(new_state_merkle_root);
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap(); let alice_spend_key: SecretKey = alice_wallet
.get_spend_key()
.try_into()
.unwrap();
let bob_spend_key: SecretKey = bob_wallet.get_spend_key().try_into().unwrap(); let bob_spend_key: SecretKey = bob_wallet.get_spend_key().try_into().unwrap();
let alice_proof = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key); let alice_proof = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key);
@ -1172,21 +1093,9 @@ mod tests {
let proofs = vec![alice_proof, bob_proof]; let proofs = vec![alice_proof, bob_proof];
let modified_fields: Vec<String> = new_state let modified_fields: Vec<String> = new_state.as_object().unwrap().iter().map(|(key, _)| key.clone()).collect();
.as_object()
.unwrap()
.iter()
.map(|(key, _)| key.clone())
.collect();
assert!(role_def assert!(role_def.is_satisfied(modified_fields, new_state_merkle_root, &proofs, &OutPointMemberMap(members)).is_ok());
.is_satisfied(
modified_fields,
new_state_merkle_root,
&proofs,
&OutPointMemberMap(members)
)
.is_ok());
} }
#[test] #[test]
@ -1220,15 +1129,16 @@ mod tests {
let public_data = BTreeMap::new(); let public_data = BTreeMap::new();
let roles = BTreeMap::new(); let roles = BTreeMap::new();
let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data)); let attributes = BTreeMap::from_iter(pcd.into_iter().chain(public_data));
let commitments = let commitments = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles))
.unwrap();
let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap(); let new_state_merkle_root = commitments.create_merkle_tree().unwrap().root().unwrap();
let validation_hash = AnkValidationYesHash::from_merkle_root(new_state_merkle_root); let validation_hash = AnkValidationYesHash::from_merkle_root(new_state_merkle_root);
// let validation_hash = AnkValidationNoHash::from_merkle_root(new_state_merkle_root); // let validation_hash = AnkValidationNoHash::from_merkle_root(new_state_merkle_root);
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap(); let alice_spend_key: SecretKey = alice_wallet
.get_spend_key()
.try_into()
.unwrap();
// let bob_spend_key: SecretKey = bob_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::ValidationYes(validation_hash), alice_spend_key); let alice_proof = Proof::new(AnkHash::ValidationYes(validation_hash), alice_spend_key);
@ -1236,21 +1146,9 @@ mod tests {
let proofs = vec![alice_proof]; let proofs = vec![alice_proof];
let modified_fields: Vec<String> = new_state let modified_fields: Vec<String> = new_state.as_object().unwrap().iter().map(|(key, _)| key.clone()).collect();
.as_object()
.unwrap()
.iter()
.map(|(key, _)| key.clone())
.collect();
assert!(role_def assert!(role_def.is_satisfied(modified_fields, new_state_merkle_root, &proofs, &OutPointMemberMap(members)).is_err());
.is_satisfied(
modified_fields,
new_state_merkle_root,
&proofs,
&OutPointMemberMap(members)
)
.is_err());
} }
#[test] #[test]

View File

@ -22,12 +22,12 @@ pub enum PrdType {
None, None,
Connect, Connect,
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, // Validate (or disagree) with a prd update Response, // Validate (or disagree) with a prd update
Confirm, // Confirm we received an update Confirm, // Confirm we received an update
TxProposal, // Send a psbt asking for recipient signature TxProposal, // Send a psbt asking for recipient signature
Request, // asks for the prd update for some state, Request // asks for the prd update for some state,
} }
sha256t_hash_newtype! { sha256t_hash_newtype! {
@ -71,18 +71,10 @@ pub struct Prd {
impl Prd { impl Prd {
/// We answer to ack we received a transaction and got the shared_secret /// We answer to ack we received a transaction and got the shared_secret
/// If validation_tokens is empty we put the proof into it and return it /// If validation_tokens is empty we put the proof into it and return it
/// If validation_tokens contains a valid proof signed by ourselves of empty prd, /// If validation_tokens contains a valid proof signed by ourselves of empty prd,
/// we confirm the secret if necessary and don't return anything /// we confirm the secret if necessary and don't return anything
pub fn new_connect( pub fn new_connect(sender: Member, secret_hash: AnkMessageHash, previous_proof: Option<Proof>) -> Self {
sender: Member, let validation_tokens = if let Some(proof) = previous_proof { vec![proof] } else { vec![] };
secret_hash: AnkMessageHash,
previous_proof: Option<Proof>,
) -> Self {
let validation_tokens = if let Some(proof) = previous_proof {
vec![proof]
} else {
vec![]
};
Self { Self {
prd_type: PrdType::Connect, prd_type: PrdType::Connect,
process_id: OutPoint::null(), process_id: OutPoint::null(),
@ -167,15 +159,16 @@ impl Prd {
let local_spend_key = local_address.get_spend_key(); let local_spend_key = local_address.get_spend_key();
// If it's our own device key we abort // If it's our own device key we abort
if proof_key == local_spend_key { if proof_key == local_spend_key {
return Err(anyhow::Error::msg( return Err(anyhow::Error::msg("Proof signed by ourselves, we are parsing our own message"));
"Proof signed by ourselves, we are parsing our own message",
));
} }
// take the spending keys in sender // take the spending keys in sender
let addresses = prd.sender.get_addresses(); let addresses = prd.sender.get_addresses();
let mut spend_keys: Vec<PublicKey> = vec![]; let mut spend_keys: Vec<PublicKey> = vec![];
for address in addresses { for address in addresses {
spend_keys.push(<SilentPaymentAddress>::try_from(address)?.get_spend_key()); spend_keys.push(
<SilentPaymentAddress>::try_from(address)?
.get_spend_key()
);
} }
// The key in proof must be one of the sender keys // The key in proof must be one of the sender keys
let mut known_key = false; let mut known_key = false;

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,20 @@
use anyhow::{Result, Error};
use tsify::Tsify;
use crate::aes_gcm::aead::{Aead, Payload}; use crate::aes_gcm::aead::{Aead, Payload};
use crate::aes_gcm::Nonce; use crate::aes_gcm::Nonce;
use crate::crypto::{Aes256Gcm, AnkSharedSecretHash, KeyInit, AAD};
use crate::sp_client::bitcoin::hashes::Hash; use crate::sp_client::bitcoin::hashes::Hash;
use crate::sp_client::silentpayments::SilentPaymentAddress; use crate::sp_client::silentpayments::SilentPaymentAddress;
use anyhow::{Error, Result}; use crate::crypto::{Aes256Gcm, AnkSharedSecretHash, KeyInit, AAD};
use serde::ser::SerializeStruct; use serde::ser::SerializeStruct;
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap; use std::collections::HashMap;
use std::str::FromStr; use std::str::FromStr;
use tsify::Tsify;
#[derive(Debug, Clone, Default, PartialEq, Tsify)] #[derive(Debug, Clone, Default, PartialEq, Tsify)]
#[tsify(into_wasm_abi)] #[tsify(into_wasm_abi)]
pub struct SecretsStore { pub struct SecretsStore{
shared_secrets: HashMap<SilentPaymentAddress, AnkSharedSecretHash>, shared_secrets: HashMap<SilentPaymentAddress, AnkSharedSecretHash>,
unconfirmed_secrets: Vec<AnkSharedSecretHash>, unconfirmed_secrets: Vec<AnkSharedSecretHash>
} }
impl Serialize for SecretsStore { impl Serialize for SecretsStore {
@ -24,7 +24,7 @@ impl Serialize for SecretsStore {
{ {
let mut temp_map = HashMap::with_capacity(self.shared_secrets.len()); let mut temp_map = HashMap::with_capacity(self.shared_secrets.len());
for (key, value) in &self.shared_secrets { for (key, value) in &self.shared_secrets {
let key_str = key.to_string(); let key_str = key.to_string();
let value_str = value.to_string(); let value_str = value.to_string();
temp_map.insert(key_str, value_str); temp_map.insert(key_str, value_str);
@ -61,16 +61,14 @@ impl<'de> Deserialize<'de> for SecretsStore {
let mut shared_secrets = HashMap::with_capacity(helper.shared_secrets.len()); let mut shared_secrets = HashMap::with_capacity(helper.shared_secrets.len());
for (key_str, value_str) in helper.shared_secrets { for (key_str, value_str) in helper.shared_secrets {
let key = SilentPaymentAddress::try_from(key_str).map_err(serde::de::Error::custom)?; // Convert String to SilentPaymentAddress let key = SilentPaymentAddress::try_from(key_str).map_err(serde::de::Error::custom)?; // Convert String to SilentPaymentAddress
let value = let value = AnkSharedSecretHash::from_str(&value_str).map_err(serde::de::Error::custom)?; // Convert hex string back to Vec<u8>
AnkSharedSecretHash::from_str(&value_str).map_err(serde::de::Error::custom)?; // Convert hex string back to Vec<u8>
shared_secrets.insert(key, value); shared_secrets.insert(key, value);
} }
let mut unconfirmed_secrets = Vec::with_capacity(helper.unconfirmed_secrets.len()); let mut unconfirmed_secrets = Vec::with_capacity(helper.unconfirmed_secrets.len());
for secret_str in helper.unconfirmed_secrets { for secret_str in helper.unconfirmed_secrets {
let secret_bytes = let secret_bytes = AnkSharedSecretHash::from_str(&secret_str).map_err(serde::de::Error::custom)?;
AnkSharedSecretHash::from_str(&secret_str).map_err(serde::de::Error::custom)?;
unconfirmed_secrets.push(secret_bytes); unconfirmed_secrets.push(secret_bytes);
} }
@ -83,9 +81,9 @@ impl<'de> Deserialize<'de> for SecretsStore {
impl SecretsStore { impl SecretsStore {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
shared_secrets: HashMap::new(), shared_secrets: HashMap::new(),
unconfirmed_secrets: Vec::new(), unconfirmed_secrets: Vec::new()
} }
} }
@ -94,14 +92,11 @@ impl SecretsStore {
} }
/// Returns the previous secret for this address, if any /// Returns the previous secret for this address, if any
pub fn confirm_secret_for_address( pub fn confirm_secret_for_address(&mut self, secret: AnkSharedSecretHash, address: SilentPaymentAddress) -> Option<AnkSharedSecretHash> {
&mut self, if let Some(pos) = self.unconfirmed_secrets.iter()
secret: AnkSharedSecretHash, .position(|s| *s == secret)
address: SilentPaymentAddress, {
) -> Option<AnkSharedSecretHash> { self.shared_secrets.insert(address, self.unconfirmed_secrets.swap_remove(pos))
if let Some(pos) = self.unconfirmed_secrets.iter().position(|s| *s == secret) {
self.shared_secrets
.insert(address, self.unconfirmed_secrets.swap_remove(pos))
} else { } else {
// We didn't know about that secret, just add it // We didn't know about that secret, just add it
// TODO if we already had a secret for this address we just replace it for now // TODO if we already had a secret for this address we just replace it for now
@ -109,10 +104,7 @@ impl SecretsStore {
} }
} }
pub fn get_secret_for_address( pub fn get_secret_for_address(&self, address: SilentPaymentAddress) -> Option<&AnkSharedSecretHash> {
&self,
address: SilentPaymentAddress,
) -> Option<&AnkSharedSecretHash> {
self.shared_secrets.get(&address) self.shared_secrets.get(&address)
} }
@ -124,10 +116,7 @@ impl SecretsStore {
self.unconfirmed_secrets.clone() self.unconfirmed_secrets.clone()
} }
pub fn remove_secret_for_address( pub fn remove_secret_for_address(&mut self, address: SilentPaymentAddress) -> Result<(SilentPaymentAddress, AnkSharedSecretHash)> {
&mut self,
address: SilentPaymentAddress,
) -> Result<(SilentPaymentAddress, AnkSharedSecretHash)> {
if let Some(removed_secret) = self.shared_secrets.remove(&address) { if let Some(removed_secret) = self.shared_secrets.remove(&address) {
return Ok((address, removed_secret)); return Ok((address, removed_secret));
} else { } else {

View File

@ -1,10 +1,10 @@
use crate::{pcd::Member, process::Process};
use serde::de::Error;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serde::de::Error;
use sp_client::bitcoin::hex::{DisplayHex, FromHex}; use sp_client::bitcoin::hex::{DisplayHex, FromHex};
use sp_client::bitcoin::OutPoint; use sp_client::bitcoin::OutPoint;
use std::collections::{BTreeMap, HashMap};
use tsify::Tsify; use tsify::Tsify;
use std::collections::{BTreeMap, HashMap};
use crate::{pcd::Member, process::Process};
#[derive(Debug, Serialize, Deserialize, Tsify)] #[derive(Debug, Serialize, Deserialize, Tsify)]
#[tsify(from_wasm_abi)] #[tsify(from_wasm_abi)]
@ -41,15 +41,23 @@ pub mod members_map {
use super::*; use super::*;
use crate::pcd::Member; use crate::pcd::Member;
pub fn serialize<S>(map: &HashMap<OutPoint, Member>, serializer: S) -> Result<S::Ok, S::Error> pub fn serialize<S>(
map: &HashMap<OutPoint, Member>,
serializer: S,
) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {
let map: HashMap<OutPoint, Member> = map.iter().map(|(k, v)| (*k, v.to_owned())).collect(); let map: HashMap<OutPoint, Member> = map
.iter()
.map(|(k, v)| (*k, v.to_owned()))
.collect();
map.serialize(serializer) map.serialize(serializer)
} }
pub fn deserialize<'de, D>(deserializer: D) -> Result<HashMap<OutPoint, Member>, D::Error> pub fn deserialize<'de, D>(
deserializer: D,
) -> Result<HashMap<OutPoint, Member>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
@ -63,7 +71,10 @@ pub mod outpoint_map {
use super::*; use super::*;
use crate::process::Process; use crate::process::Process;
pub fn serialize<S>(map: &HashMap<OutPoint, Process>, serializer: S) -> Result<S::Ok, S::Error> pub fn serialize<S>(
map: &HashMap<OutPoint, Process>,
serializer: S,
) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {
@ -77,7 +88,9 @@ pub mod outpoint_map {
map.serialize(serializer) map.serialize(serializer)
} }
pub fn deserialize<'de, D>(deserializer: D) -> Result<HashMap<OutPoint, Process>, D::Error> pub fn deserialize<'de, D>(
deserializer: D,
) -> Result<HashMap<OutPoint, Process>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
@ -88,7 +101,9 @@ pub mod outpoint_map {
let result: Result<HashMap<OutPoint, Process>, D::Error> = map let result: Result<HashMap<OutPoint, Process>, D::Error> = map
.into_iter() .into_iter()
.map(|(k, v)| { .map(|(k, v)| {
let outpoint = k.parse().map_err(serde::de::Error::custom)?; let outpoint = k
.parse()
.map_err(serde::de::Error::custom)?;
Ok((outpoint, v)) Ok((outpoint, v))
}) })
.collect(); .collect();
@ -103,7 +118,10 @@ pub mod hex_array_btree {
// Serializes a BTreeMap<String, [u8; 32]> as a BTreeMap<String, String> // Serializes a BTreeMap<String, [u8; 32]> as a BTreeMap<String, String>
// where the value is a hex-encoded string. // where the value is a hex-encoded string.
pub fn serialize<S>(map: &BTreeMap<String, [u8; 32]>, serializer: S) -> Result<S::Ok, S::Error> pub fn serialize<S>(
map: &BTreeMap<String, [u8; 32]>,
serializer: S,
) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {
@ -117,7 +135,9 @@ pub mod hex_array_btree {
// Deserializes a BTreeMap<String, [u8; 32]> from a BTreeMap<String, String> // Deserializes a BTreeMap<String, [u8; 32]> from a BTreeMap<String, String>
// where the value is expected to be a hex-encoded string. // where the value is expected to be a hex-encoded string.
pub fn deserialize<'de, D>(deserializer: D) -> Result<BTreeMap<String, [u8; 32]>, D::Error> pub fn deserialize<'de, D>(
deserializer: D,
) -> Result<BTreeMap<String, [u8; 32]>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {

View File

@ -3,16 +3,13 @@ use std::collections::HashMap;
use anyhow::{Error, Result}; use anyhow::{Error, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sp_client::bitcoin::absolute::Height; use sp_client::bitcoin::absolute::Height;
use sp_client::bitcoin::hashes::Hash;
use sp_client::bitcoin::secp256k1::{PublicKey, SecretKey}; use sp_client::bitcoin::secp256k1::{PublicKey, SecretKey};
use tsify::Tsify; use tsify::Tsify;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use sp_client::bitcoin::{Amount, BlockHash, OutPoint, Transaction, TxOut, Txid, XOnlyPublicKey}; use sp_client::bitcoin::{Amount, OutPoint, Transaction, TxOut, XOnlyPublicKey};
use sp_client::{FeeRate, OutputSpendStatus, OwnedOutput, Recipient, SilentPaymentUnsignedTransaction, SpClient};
use sp_client::silentpayments::utils::receiving::calculate_ecdh_shared_secret; use sp_client::silentpayments::utils::receiving::calculate_ecdh_shared_secret;
use sp_client::{
FeeRate, OutputSpendStatus, OwnedOutput, Recipient, SilentPaymentUnsignedTransaction, SpClient,
};
#[derive(Debug, Default, Deserialize, Serialize, Clone)] #[derive(Debug, Default, Deserialize, Serialize, Clone)]
pub struct SpWallet { pub struct SpWallet {
@ -43,16 +40,14 @@ impl SpWallet {
} }
pub fn get_unspent_outputs(&self) -> HashMap<OutPoint, OwnedOutput> { pub fn get_unspent_outputs(&self) -> HashMap<OutPoint, OwnedOutput> {
self.outputs self.outputs.iter()
.iter()
.filter(|(_, output)| output.spend_status == OutputSpendStatus::Unspent) .filter(|(_, output)| output.spend_status == OutputSpendStatus::Unspent)
.map(|(outpoint, output)| (*outpoint, output.clone())) .map(|(outpoint, output)| (*outpoint, output.clone()))
.collect() .collect()
} }
pub fn get_balance(&self) -> Amount { pub fn get_balance(&self) -> Amount {
self.outputs self.outputs.values()
.values()
.filter(|output| output.spend_status == OutputSpendStatus::Unspent) .filter(|output| output.spend_status == OutputSpendStatus::Unspent)
.fold(Amount::ZERO, |acc, x| acc + x.amount) .fold(Amount::ZERO, |acc, x| acc + x.amount)
} }
@ -73,39 +68,7 @@ impl SpWallet {
self.last_scan = last_scan; self.last_scan = last_scan;
} }
pub fn mark_output_spent(&mut self, outpoint: &OutPoint, txid: &Txid) { pub fn update_with_transaction(&mut self, tx: &Transaction, public_tweak: &PublicKey, height: u32) -> Result<HashMap<OutPoint, OwnedOutput>> {
if let Some(output) = self.outputs.get_mut(outpoint) {
output.spend_status = OutputSpendStatus::Spent(txid.to_byte_array());
}
}
pub fn mark_output_mined(&mut self, outpoint: &OutPoint, blk_hash: BlockHash) {
if let Some(output) = self.outputs.get_mut(outpoint) {
output.spend_status = OutputSpendStatus::Mined(blk_hash.to_byte_array());
}
}
fn check_inputs(&mut self, tx: &Transaction) {
for input in &tx.input {
if let Some(output) = self.outputs.get(&input.previous_output) {
if output.spend_status != OutputSpendStatus::Unspent {
log::debug!("Input is already spent: {:?}", input.previous_output);
continue;
} else {
self.mark_output_spent(&input.previous_output, &tx.txid());
}
}
}
}
pub fn update_with_transaction(
&mut self,
tx: &Transaction,
public_tweak: &PublicKey,
height: u32,
) -> Result<HashMap<OutPoint, OwnedOutput>> {
// Check if we have outputs that are spent by this transaction
self.check_inputs(tx);
let receiver = &self.get_sp_client().sp_receiver; let receiver = &self.get_sp_client().sp_receiver;
let p2tr_outs: Vec<(usize, &TxOut)> = tx let p2tr_outs: Vec<(usize, &TxOut)> = tx
.output .output
@ -115,7 +78,7 @@ impl SpWallet {
.collect(); .collect();
if p2tr_outs.is_empty() { if p2tr_outs.is_empty() {
return Err(Error::msg("No taproot outputs")); return Err(Error::msg("No taproot outputs"))
}; // That should never happen since we have a tweak_data, but anyway }; // That should never happen since we have a tweak_data, but anyway
// Now we can just run sp_receiver on all the p2tr outputs // Now we can just run sp_receiver on all the p2tr outputs
@ -173,7 +136,7 @@ impl SpWallet {
#[derive(Debug, Serialize, Deserialize, PartialEq, Tsify)] #[derive(Debug, Serialize, Deserialize, PartialEq, Tsify)]
#[tsify(from_wasm_abi)] #[tsify(from_wasm_abi)]
pub struct TsUnsignedTransaction(SilentPaymentUnsignedTransaction); pub struct TsUnsignedTransaction(SilentPaymentUnsignedTransaction);
impl TsUnsignedTransaction { impl TsUnsignedTransaction {
pub fn new(unsigned_tx: SilentPaymentUnsignedTransaction) -> Self { pub fn new(unsigned_tx: SilentPaymentUnsignedTransaction) -> Self {
@ -203,16 +166,16 @@ pub fn create_transaction(
thread_rng().fill(&mut commitment); thread_rng().fill(&mut commitment);
} }
recipients.push(Recipient { recipients.push(Recipient {
address: sp_client::RecipientAddress::Data(commitment.to_vec()), address: sp_client::RecipientAddress::Data(commitment.to_vec()),
amount: Amount::ZERO, amount: Amount::ZERO
}); });
let new_transaction = sp_client.create_new_transaction( let new_transaction = sp_client.create_new_transaction(
available_outpoints, available_outpoints,
recipients, recipients,
fee_rate, fee_rate,
sp_client.get_network(), sp_client.get_network()
)?; )?;
let finalized_transaction = SpClient::finalize_transaction(new_transaction)?; let finalized_transaction = SpClient::finalize_transaction(new_transaction)?;
@ -220,10 +183,7 @@ pub fn create_transaction(
Ok(finalized_transaction) Ok(finalized_transaction)
} }
pub fn sign_transaction( pub fn sign_transaction(sp_client: &SpClient, unsigned_transaction: SilentPaymentUnsignedTransaction) -> Result<Transaction> {
sp_client: &SpClient,
unsigned_transaction: SilentPaymentUnsignedTransaction,
) -> Result<Transaction> {
let mut aux_rand = [0u8; 32]; let mut aux_rand = [0u8; 32];
thread_rng().fill(&mut aux_rand); thread_rng().fill(&mut aux_rand);
sp_client.sign_transaction(unsigned_transaction, &aux_rand) sp_client.sign_transaction(unsigned_transaction, &aux_rand)

View File

@ -1,212 +0,0 @@
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::{
mem,
sync::{
mpsc::{self, Receiver, Sender},
Arc, RwLock,
},
};
// use wasm_bindgen::prelude::*;
// use sp_client::bitcoin::absolute::Height;
// use sp_client::bitcoin::BlockHash;
use sp_client::{
bitcoin::{absolute::Height, BlockHash, OutPoint},
OwnedOutput, Updater,
};
use std::collections::{HashMap, HashSet};
#[derive(Debug, Serialize, Deserialize)]
pub struct ScanProgress {
pub start: u32,
pub current: u32,
pub end: u32,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum StateUpdate {
NoUpdate {
blkheight: Height,
},
Update {
blkheight: Height,
blkhash: BlockHash,
found_outputs: HashMap<OutPoint, OwnedOutput>,
found_inputs: HashSet<OutPoint>,
},
}
// #[cfg(target_arch = "wasm32")]
// #[wasm_bindgen]
// extern "C" {
// #[wasm_bindgen(js_namespace = window)]
// fn sendScanProgress(progress: JsValue);
// #[wasm_bindgen(js_namespace = window)]
// fn sendStateUpdate(update: JsValue);
// }
pub trait UpdateSink: Send + Sync {
fn send_scan_progress(&self, progress: ScanProgress) -> Result<()>;
fn send_state_update(&self, update: StateUpdate) -> Result<()>;
}
#[cfg(not(target_arch = "wasm32"))]
pub struct NativeUpdateSink {
scan_tx: Sender<ScanProgress>,
state_tx: Sender<StateUpdate>,
}
#[cfg(not(target_arch = "wasm32"))]
impl NativeUpdateSink {
pub fn new() -> (Self, Receiver<ScanProgress>, Receiver<StateUpdate>) {
let (scan_tx, scan_rx) = mpsc::channel();
let (state_tx, state_rx) = mpsc::channel();
(Self { scan_tx, state_tx }, scan_rx, state_rx)
}
}
#[cfg(not(target_arch = "wasm32"))]
impl UpdateSink for NativeUpdateSink {
fn send_scan_progress(&self, progress: ScanProgress) -> Result<()> {
self.scan_tx.send(progress)?;
Ok(())
}
fn send_state_update(&self, update: StateUpdate) -> Result<()> {
self.state_tx.send(update)?;
Ok(())
}
}
// #[cfg(target_arch = "wasm32")]
// pub struct WasmUpdateSink;
// #[cfg(target_arch = "wasm32")]
// impl UpdateSink for WasmUpdateSink {
// fn send_scan_progress(&self, progress: ScanProgress) -> Result<()> {
// let js_value = serde_wasm_bindgen::to_value(&progress)?;
// sendScanProgress(js_value);
// Ok(())
// }
// fn send_state_update(&self, update: StateUpdate) -> Result<()> {
// let js_value = serde_wasm_bindgen::to_value(&update)?;
// sendStateUpdate(js_value);
// Ok(())
// }
// }
// Global sink instance
static UPDATE_SINK: RwLock<Option<Arc<dyn UpdateSink>>> = RwLock::new(None);
pub fn init_update_sink(sink: Arc<dyn UpdateSink>) {
let mut sink_guard = UPDATE_SINK.write().unwrap();
*sink_guard = Some(sink);
}
pub fn get_update_sink() -> Option<Arc<dyn UpdateSink>> {
UPDATE_SINK.read().unwrap().clone()
}
#[derive(Debug)]
pub struct StateUpdater {
update: bool,
blkhash: Option<BlockHash>,
blkheight: Option<Height>,
found_outputs: HashMap<OutPoint, OwnedOutput>,
found_inputs: HashSet<OutPoint>,
}
impl StateUpdater {
pub fn new() -> Self {
Self {
update: false,
blkheight: None,
blkhash: None,
found_outputs: HashMap::new(),
found_inputs: HashSet::new(),
}
}
pub fn to_update(&mut self) -> Result<StateUpdate> {
let blkheight = self
.blkheight
.ok_or(anyhow::Error::msg("blkheight not filled"))?;
if self.update {
self.update = false;
let blkhash = self.blkhash.ok_or(anyhow::Error::msg("blkhash not set"))?;
self.blkheight = None;
self.blkhash = None;
// take results, and insert new empty values
let found_inputs = mem::take(&mut self.found_inputs);
let found_outputs = mem::take(&mut self.found_outputs);
Ok(StateUpdate::Update {
blkheight,
blkhash,
found_outputs,
found_inputs,
})
} else {
Ok(StateUpdate::NoUpdate { blkheight })
}
}
}
impl Updater for StateUpdater {
fn record_scan_progress(&mut self, start: Height, current: Height, end: Height) -> Result<()> {
self.blkheight = Some(current);
if let Some(sink) = get_update_sink() {
sink.send_scan_progress(ScanProgress {
start: start.to_consensus_u32(),
current: current.to_consensus_u32(),
end: end.to_consensus_u32(),
})?;
}
Ok(())
}
fn record_block_outputs(
&mut self,
height: Height,
blkhash: BlockHash,
found_outputs: HashMap<OutPoint, OwnedOutput>,
) -> Result<()> {
// may have already been written by record_block_inputs
self.update = true;
self.found_outputs = found_outputs;
self.blkhash = Some(blkhash);
self.blkheight = Some(height);
Ok(())
}
fn record_block_inputs(
&mut self,
blkheight: Height,
blkhash: BlockHash,
found_inputs: HashSet<OutPoint>,
) -> Result<()> {
self.update = true;
self.blkheight = Some(blkheight);
self.blkhash = Some(blkhash);
self.found_inputs = found_inputs;
Ok(())
}
fn save_to_persistent_storage(&mut self) -> Result<()> {
if let Some(sink) = get_update_sink() {
sink.send_state_update(self.to_update()?)?;
}
Ok(())
}
}