Merge branch 'blindbit' into dev
This commit is contained in:
commit
e2369eaf6f
@ -5,11 +5,12 @@ use tsify::Tsify;
|
|||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
use sp_client::{
|
use sp_client::{
|
||||||
bitcoin::{absolute::Height, hashes::Hash, secp256k1::PublicKey, Amount, OutPoint, Transaction, XOnlyPublicKey},
|
bitcoin::{
|
||||||
silentpayments::{
|
absolute::Height, hashes::Hash, secp256k1::PublicKey, Amount, OutPoint, Transaction,
|
||||||
utils::receiving::calculate_ecdh_shared_secret, SilentPaymentAddress
|
XOnlyPublicKey,
|
||||||
},
|
},
|
||||||
OutputSpendStatus, OwnedOutput, SpClient
|
silentpayments::{utils::receiving::calculate_ecdh_shared_secret, SilentPaymentAddress},
|
||||||
|
OutputSpendStatus, OwnedOutput, SpClient,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{pcd::Member, silentpayments::SpWallet};
|
use crate::{pcd::Member, silentpayments::SpWallet};
|
||||||
@ -33,6 +34,10 @@ 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()
|
||||||
}
|
}
|
||||||
@ -46,24 +51,27 @@ impl Device {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_balance(&self) -> Amount {
|
pub fn get_balance(&self) -> Amount {
|
||||||
self.sp_wallet.get_outputs().values()
|
self.sp_wallet
|
||||||
|
.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(&mut self, tx: &Transaction, blockheight: u32, partial_tweak: PublicKey) -> anyhow::Result<HashMap<OutPoint, OwnedOutput>> {
|
pub fn update_outputs_with_transaction(
|
||||||
|
&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
|
if self.sp_wallet.get_outputs().contains_key(&OutPoint {
|
||||||
.sp_wallet
|
txid,
|
||||||
.get_outputs()
|
vout: i as u32,
|
||||||
.contains_key(&OutPoint {
|
}) {
|
||||||
txid,
|
|
||||||
vout: i as u32,
|
|
||||||
})
|
|
||||||
{
|
|
||||||
return Err(anyhow::Error::msg("Transaction already scanned"));
|
return Err(anyhow::Error::msg("Transaction already scanned"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,7 +132,11 @@ 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.sp_wallet.get_mut_outputs().get_mut(&input.previous_output) {
|
if let Some(prevout) = self
|
||||||
|
.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());
|
||||||
|
10
src/hash.rs
10
src/hash.rs
@ -1,6 +1,8 @@
|
|||||||
use sp_client::bitcoin::{
|
use sp_client::bitcoin::{
|
||||||
consensus::{serialize, Encodable}, hashes::{sha256t_hash_newtype, Hash, HashEngine}, OutPoint
|
consensus::{serialize, Encodable},
|
||||||
};
|
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");
|
||||||
@ -14,7 +16,9 @@ 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).consensus_encode(&mut eng).expect("hash engine don't return errors");
|
serialize(outpoint)
|
||||||
|
.consensus_encode(&mut eng)
|
||||||
|
.expect("hash engine don't return errors");
|
||||||
AnkPcdHash::from_engine(eng)
|
AnkPcdHash::from_engine(eng)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
src/lib.rs
18
src/lib.rs
@ -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 hash;
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod hash;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod pcd;
|
pub mod pcd;
|
||||||
pub mod prd;
|
pub mod prd;
|
||||||
@ -27,6 +27,7 @@ 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
|
||||||
|
|
||||||
@ -42,8 +43,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 {
|
||||||
@ -53,7 +54,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,
|
||||||
@ -113,10 +114,7 @@ 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!(
|
log::debug!("Failed to lock Mutex (poisoned). Data was: {:?}", data);
|
||||||
"Failed to lock Mutex (poisoned). Data was: {:?}",
|
|
||||||
data
|
|
||||||
);
|
|
||||||
|
|
||||||
Err(anyhow::anyhow!("Failed to lock Mutex (poisoned)"))
|
Err(anyhow::anyhow!("Failed to lock Mutex (poisoned)"))
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ pub enum AnkFlag {
|
|||||||
Cipher,
|
Cipher,
|
||||||
Commit,
|
Commit,
|
||||||
Handshake,
|
Handshake,
|
||||||
|
Sync,
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,6 +50,7 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,6 +62,7 @@ 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",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,7 +74,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,
|
||||||
@ -85,7 +89,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,
|
||||||
@ -164,14 +168,21 @@ 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(sp_address: String, peers_list: OutPointMemberMap, processes_list: OutPointProcessMap) -> Self {
|
pub fn new(
|
||||||
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
426
src/pcd.rs
426
src/pcd.rs
@ -3,16 +3,14 @@ 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::hash::{Hash as StdHash, Hasher};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::hash::{Hash as StdHash, Hasher};
|
||||||
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::{
|
bitcoin::{hashes::Hash, secp256k1::PublicKey, OutPoint},
|
||||||
hashes::Hash, secp256k1::PublicKey, OutPoint
|
|
||||||
},
|
|
||||||
silentpayments::SilentPaymentAddress,
|
silentpayments::SilentPaymentAddress,
|
||||||
};
|
};
|
||||||
use tsify::Tsify;
|
use tsify::Tsify;
|
||||||
@ -21,8 +19,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;
|
||||||
@ -30,40 +28,42 @@ 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> where Self: Sized;
|
fn deserialize_from_pcd(data: &[u8]) -> Result<Self>
|
||||||
|
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,23 +88,26 @@ 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 { r#type: type_str, data })
|
Ok(FileBlob {
|
||||||
},
|
r#type: type_str,
|
||||||
_ => Err(Error::msg("Invalid version or data type"))
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => Err(Error::msg("Invalid version or data type")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,7 +189,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
|
||||||
@ -209,11 +212,13 @@ 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.iter().find(|a| {
|
self.sp_addresses
|
||||||
let addr = SilentPaymentAddress::try_from(a.as_str()).unwrap();
|
.iter()
|
||||||
addr.get_spend_key() == *key
|
.find(|a| {
|
||||||
})
|
let addr = SilentPaymentAddress::try_from(a.as_str()).unwrap();
|
||||||
.cloned()
|
addr.get_spend_key() == *key
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,13 +238,18 @@ 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.as_object().ok_or_else(|| Error::msg("Pcd must be an object"))?;
|
let as_object = value
|
||||||
let map: Result<BTreeMap<String, Vec<u8>>> = as_object.into_iter().map(|(key, value)| {
|
.as_object()
|
||||||
// Use the trait method instead of manual serialization
|
.ok_or_else(|| Error::msg("Pcd must be an object"))?;
|
||||||
let compressed = value.serialize_to_pcd()?;
|
let map: Result<BTreeMap<String, Vec<u8>>> = as_object
|
||||||
Ok((key.clone(), compressed))
|
.into_iter()
|
||||||
}).collect();
|
.map(|(key, value)| {
|
||||||
|
// 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?))
|
||||||
}
|
}
|
||||||
@ -248,12 +258,15 @@ 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.into_iter().map(|(key, value)| {
|
let map: Result<BTreeMap<String, Vec<u8>>> = file_blob_map
|
||||||
// Use the trait method instead of manual serialization
|
.into_iter()
|
||||||
let compressed = value.serialize_to_pcd()?;
|
.map(|(key, value)| {
|
||||||
Ok((key, compressed))
|
// Use the trait method instead of manual serialization
|
||||||
}).collect();
|
let compressed = value.serialize_to_pcd()?;
|
||||||
|
Ok((key, compressed))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
Ok(Pcd(map?))
|
Ok(Pcd(map?))
|
||||||
}
|
}
|
||||||
@ -309,7 +322,11 @@ impl Pcd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_serializable<T: PcdSerializable>(&mut self, key: String, value: &T) -> Result<Option<Vec<u8>>> {
|
pub fn insert_serializable<T: PcdSerializable>(
|
||||||
|
&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))
|
||||||
}
|
}
|
||||||
@ -317,20 +334,29 @@ 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(#[serde(with = "hex_array_btree")] #[tsify(type = "Record<string, string>")] BTreeMap<String, [u8; 32]>);
|
pub struct PcdCommitments(
|
||||||
|
#[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 = AnkPcdHash::from_pcd_value(value.as_slice(), field.as_bytes(), commited_in);
|
let tagged_hash =
|
||||||
|
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(roles.to_bytes()?.as_slice(), roles_label.as_bytes(), commited_in);
|
let roles_hash = AnkPcdHash::from_pcd_value(
|
||||||
|
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
|
||||||
@ -346,7 +372,12 @@ impl PcdCommitments {
|
|||||||
self.0.is_empty()
|
self.0.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_with_value(&mut self, outpoint: &OutPoint, field: &str, new_value: &[u8]) -> Result<()> {
|
pub fn update_with_value(
|
||||||
|
&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);
|
||||||
@ -363,11 +394,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()
|
||||||
@ -383,10 +414,7 @@ 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
|
let leaves: Vec<[u8; 32]> = self.0.values().map(|hash| *hash).collect();
|
||||||
.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());
|
||||||
|
|
||||||
@ -447,7 +475,12 @@ 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 || self.quorum > 1.0 || self.quorum.is_sign_negative() || self.quorum.is_nan() { // Just to be sure
|
} else if self.quorum <= 0.0
|
||||||
|
|| 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"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,7 +488,9 @@ impl ValidationRule {
|
|||||||
let validating_members = members
|
let validating_members = members
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|member| {
|
.filter(|member| {
|
||||||
if member.sp_addresses.is_empty() { return false }; // This can happen when a member in the rule wasn't found in the network
|
if member.sp_addresses.is_empty() {
|
||||||
|
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()))
|
||||||
@ -466,7 +501,11 @@ impl ValidationRule {
|
|||||||
})
|
})
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
if validating_members >= required_members { Ok(()) } else { Err(Error::msg("Not enough members to validate"))}
|
if validating_members >= required_members {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::msg("Not enough members to validate"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn satisfy_min_sig_member(
|
pub fn satisfy_min_sig_member(
|
||||||
@ -540,22 +579,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
|
self.validation_rules.iter().any(|rule| {
|
||||||
.iter()
|
let members: Vec<&Member> = self
|
||||||
.any(|rule| {
|
.members
|
||||||
let members: Vec<&Member> = self.members.iter()
|
.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).is_ok()
|
rule.is_satisfied(field, new_state_merkle_root, proofs, &members)
|
||||||
})
|
.is_ok()
|
||||||
})
|
})
|
||||||
{
|
}) {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::msg("Failed to validate all rules"))
|
Err(Error::msg("Failed to validate all rules"))
|
||||||
@ -583,7 +622,6 @@ 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)
|
||||||
@ -592,7 +630,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)?)
|
||||||
}
|
}
|
||||||
@ -603,11 +641,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()
|
||||||
@ -620,12 +658,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::{
|
||||||
@ -635,10 +673,8 @@ mod tests {
|
|||||||
|
|
||||||
fn create_alice_wallet() -> SpClient {
|
fn create_alice_wallet() -> SpClient {
|
||||||
SpClient::new(
|
SpClient::new(
|
||||||
SecretKey::from_str(
|
SecretKey::from_str("a67fb6bf5639efd0aeb19c1c584dd658bceda87660ef1088d4a29d2e77846973")
|
||||||
"a67fb6bf5639efd0aeb19c1c584dd658bceda87660ef1088d4a29d2e77846973",
|
.unwrap(),
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
SpendKey::Secret(
|
SpendKey::Secret(
|
||||||
SecretKey::from_str(
|
SecretKey::from_str(
|
||||||
"a1e4e7947accf33567e716c9f4d186f26398660e36cf6d2e711af64b3518e65c",
|
"a1e4e7947accf33567e716c9f4d186f26398660e36cf6d2e711af64b3518e65c",
|
||||||
@ -652,10 +688,8 @@ mod tests {
|
|||||||
|
|
||||||
fn create_bob_wallet() -> SpClient {
|
fn create_bob_wallet() -> SpClient {
|
||||||
SpClient::new(
|
SpClient::new(
|
||||||
SecretKey::from_str(
|
SecretKey::from_str("4d9f62b2340de3f0bafd671b78b19edcfded918c4106baefd34512f12f520e9b")
|
||||||
"4d9f62b2340de3f0bafd671b78b19edcfded918c4106baefd34512f12f520e9b",
|
.unwrap(),
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
SpendKey::Secret(
|
SpendKey::Secret(
|
||||||
SecretKey::from_str(
|
SecretKey::from_str(
|
||||||
"dafb99602721577997a6fe3da54f86fd113b1b58f0c9a04783d486f87083a32e",
|
"dafb99602721577997a6fe3da54f86fd113b1b58f0c9a04783d486f87083a32e",
|
||||||
@ -672,13 +706,23 @@ mod tests {
|
|||||||
let bob_address = &addresses[1];
|
let bob_address = &addresses[1];
|
||||||
HashMap::from([
|
HashMap::from([
|
||||||
(
|
(
|
||||||
OutPoint::from_str("b2f105a9df436d16b99e46453b15a0ffc584d136ceda35c0baea28e7e3ade8be:0").unwrap(),
|
OutPoint::from_str(
|
||||||
Member::new(vec![SilentPaymentAddress::try_from(alice_address.as_str()).unwrap()])
|
"b2f105a9df436d16b99e46453b15a0ffc584d136ceda35c0baea28e7e3ade8be:0",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
Member::new(vec![
|
||||||
|
SilentPaymentAddress::try_from(alice_address.as_str()).unwrap()
|
||||||
|
]),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
OutPoint::from_str("3cb9e3bf8ec72625c0347a665ab383fda9213d4544ff114ac800a9837b585897:0").unwrap(),
|
OutPoint::from_str(
|
||||||
Member::new(vec![SilentPaymentAddress::try_from(bob_address.as_str()).unwrap()])
|
"3cb9e3bf8ec72625c0347a665ab383fda9213d4544ff114ac800a9837b585897:0",
|
||||||
)
|
)
|
||||||
|
.unwrap(),
|
||||||
|
Member::new(vec![
|
||||||
|
SilentPaymentAddress::try_from(bob_address.as_str()).unwrap()
|
||||||
|
]),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -727,16 +771,15 @@ 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 = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
|
let commitments =
|
||||||
|
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
|
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap();
|
||||||
.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);
|
||||||
@ -744,7 +787,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();
|
||||||
@ -788,16 +831,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 = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
|
let commitments =
|
||||||
|
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
|
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap();
|
||||||
.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);
|
||||||
@ -807,19 +849,27 @@ 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 =
|
let result = validation_rule.is_satisfied(
|
||||||
validation_rule.is_satisfied(fields[0].as_str(), new_state_merkle_root, &proofs, &vec![]);
|
fields[0].as_str(),
|
||||||
|
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 =
|
let result = validation_rule.is_satisfied(
|
||||||
validation_rule.is_satisfied("nonexistent_field", new_state_merkle_root, &proofs, &members);
|
"nonexistent_field",
|
||||||
|
new_state_merkle_root,
|
||||||
|
&proofs,
|
||||||
|
&members,
|
||||||
|
);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -836,15 +886,14 @@ 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 = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
|
let commitments =
|
||||||
|
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
|
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap();
|
||||||
.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);
|
||||||
@ -854,14 +903,18 @@ 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 =
|
let result = validation_rule.is_satisfied(
|
||||||
validation_rule.is_satisfied(fields[0].as_str(), new_state_merkle_root, &proofs, &members);
|
fields[0].as_str(),
|
||||||
|
new_state_merkle_root,
|
||||||
|
&proofs,
|
||||||
|
&members,
|
||||||
|
);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -878,15 +931,14 @@ 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 = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
|
let commitments =
|
||||||
|
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
|
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap();
|
||||||
.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);
|
||||||
@ -896,14 +948,18 @@ 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 =
|
let result = validation_rule.is_satisfied(
|
||||||
validation_rule.is_satisfied(fields[0].as_str(), new_state_merkle_root, &proofs, &members);
|
fields[0].as_str(),
|
||||||
|
new_state_merkle_root,
|
||||||
|
&proofs,
|
||||||
|
&members,
|
||||||
|
);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -923,21 +979,23 @@ 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 = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
|
let commitments =
|
||||||
|
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
|
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap();
|
||||||
.get_spend_key()
|
|
||||||
.try_into()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let proof = Proof::new(
|
let proof = Proof::new(
|
||||||
AnkHash::ValidationYes(AnkValidationYesHash::from_merkle_root(new_state_merkle_root)),
|
AnkHash::ValidationYes(AnkValidationYesHash::from_merkle_root(
|
||||||
|
new_state_merkle_root,
|
||||||
|
)),
|
||||||
alice_spend_key,
|
alice_spend_key,
|
||||||
);
|
);
|
||||||
let proofs = vec![&proof];
|
let proofs = vec![&proof];
|
||||||
|
|
||||||
let result = validation_rule.satisfy_min_sig_member(&member, new_state_merkle_root, &proofs);
|
let result =
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -971,15 +1029,14 @@ 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 = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
|
let commitments =
|
||||||
|
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
|
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap();
|
||||||
.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);
|
||||||
@ -987,19 +1044,31 @@ mod tests {
|
|||||||
|
|
||||||
let proofs = vec![alice_proof, bob_proof];
|
let proofs = vec![alice_proof, bob_proof];
|
||||||
|
|
||||||
let modified_fields: Vec<String> = new_state.as_object().unwrap().iter().map(|(key, _)| key.clone()).collect();
|
let modified_fields: Vec<String> = new_state
|
||||||
|
.as_object()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|(key, _)| key.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
assert!(role_def.is_satisfied(modified_fields, new_state_merkle_root, &proofs, &OutPointMemberMap(members)).is_ok());
|
assert!(role_def
|
||||||
|
.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()];
|
||||||
@ -1023,16 +1092,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 = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
|
let commitments =
|
||||||
|
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
|
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap();
|
||||||
.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);
|
||||||
@ -1040,9 +1108,21 @@ mod tests {
|
|||||||
|
|
||||||
let proofs = vec![alice_proof, bob_proof];
|
let proofs = vec![alice_proof, bob_proof];
|
||||||
|
|
||||||
let modified_fields: Vec<String> = new_state.as_object().unwrap().iter().map(|(key, _)| key.clone()).collect();
|
let modified_fields: Vec<String> = new_state
|
||||||
|
.as_object()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|(key, _)| key.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
assert!(role_def.is_satisfied(modified_fields, new_state_merkle_root, &proofs, &OutPointMemberMap(members_list)).is_err());
|
assert!(role_def
|
||||||
|
.is_satisfied(
|
||||||
|
modified_fields,
|
||||||
|
new_state_merkle_root,
|
||||||
|
&proofs,
|
||||||
|
&OutPointMemberMap(members_list)
|
||||||
|
)
|
||||||
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1076,16 +1156,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 = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
|
let commitments =
|
||||||
|
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
|
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap();
|
||||||
.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);
|
||||||
@ -1093,9 +1172,21 @@ mod tests {
|
|||||||
|
|
||||||
let proofs = vec![alice_proof, bob_proof];
|
let proofs = vec![alice_proof, bob_proof];
|
||||||
|
|
||||||
let modified_fields: Vec<String> = new_state.as_object().unwrap().iter().map(|(key, _)| key.clone()).collect();
|
let modified_fields: Vec<String> = new_state
|
||||||
|
.as_object()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|(key, _)| key.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
assert!(role_def.is_satisfied(modified_fields, new_state_merkle_root, &proofs, &OutPointMemberMap(members)).is_ok());
|
assert!(role_def
|
||||||
|
.is_satisfied(
|
||||||
|
modified_fields,
|
||||||
|
new_state_merkle_root,
|
||||||
|
&proofs,
|
||||||
|
&OutPointMemberMap(members)
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1129,16 +1220,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 = PcdCommitments::new(&OutPoint::null(), &Pcd::new(attributes), &Roles::new(roles)).unwrap();
|
let commitments =
|
||||||
|
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
|
let alice_spend_key: SecretKey = alice_wallet.get_spend_key().try_into().unwrap();
|
||||||
.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);
|
||||||
@ -1146,9 +1236,21 @@ mod tests {
|
|||||||
|
|
||||||
let proofs = vec![alice_proof];
|
let proofs = vec![alice_proof];
|
||||||
|
|
||||||
let modified_fields: Vec<String> = new_state.as_object().unwrap().iter().map(|(key, _)| key.clone()).collect();
|
let modified_fields: Vec<String> = new_state
|
||||||
|
.as_object()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|(key, _)| key.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
assert!(role_def.is_satisfied(modified_fields, new_state_merkle_root, &proofs, &OutPointMemberMap(members)).is_err());
|
assert!(role_def
|
||||||
|
.is_satisfied(
|
||||||
|
modified_fields,
|
||||||
|
new_state_merkle_root,
|
||||||
|
&proofs,
|
||||||
|
&OutPointMemberMap(members)
|
||||||
|
)
|
||||||
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
33
src/prd.rs
33
src/prd.rs
@ -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,10 +71,18 @@ 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(sender: Member, secret_hash: AnkMessageHash, previous_proof: Option<Proof>) -> Self {
|
pub fn new_connect(
|
||||||
let validation_tokens = if let Some(proof) = previous_proof { vec![proof] } else { vec![] };
|
sender: Member,
|
||||||
|
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(),
|
||||||
@ -159,16 +167,15 @@ 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("Proof signed by ourselves, we are parsing our own message"));
|
return Err(anyhow::Error::msg(
|
||||||
|
"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(
|
spend_keys.push(<SilentPaymentAddress>::try_from(address)?.get_spend_key());
|
||||||
<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;
|
||||||
|
984
src/process.rs
984
src/process.rs
File diff suppressed because it is too large
Load Diff
@ -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 crate::crypto::{Aes256Gcm, AnkSharedSecretHash, KeyInit, AAD};
|
use anyhow::{Error, Result};
|
||||||
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,14 +61,16 @@ 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 = AnkSharedSecretHash::from_str(&value_str).map_err(serde::de::Error::custom)?; // Convert hex string back to Vec<u8>
|
let value =
|
||||||
|
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 = AnkSharedSecretHash::from_str(&secret_str).map_err(serde::de::Error::custom)?;
|
let secret_bytes =
|
||||||
|
AnkSharedSecretHash::from_str(&secret_str).map_err(serde::de::Error::custom)?;
|
||||||
unconfirmed_secrets.push(secret_bytes);
|
unconfirmed_secrets.push(secret_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,9 +83,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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,11 +94,14 @@ 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(&mut self, secret: AnkSharedSecretHash, address: SilentPaymentAddress) -> Option<AnkSharedSecretHash> {
|
pub fn confirm_secret_for_address(
|
||||||
if let Some(pos) = self.unconfirmed_secrets.iter()
|
&mut self,
|
||||||
.position(|s| *s == secret)
|
secret: AnkSharedSecretHash,
|
||||||
{
|
address: SilentPaymentAddress,
|
||||||
self.shared_secrets.insert(address, self.unconfirmed_secrets.swap_remove(pos))
|
) -> Option<AnkSharedSecretHash> {
|
||||||
|
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
|
||||||
@ -104,7 +109,10 @@ impl SecretsStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_secret_for_address(&self, address: SilentPaymentAddress) -> Option<&AnkSharedSecretHash> {
|
pub fn get_secret_for_address(
|
||||||
|
&self,
|
||||||
|
address: SilentPaymentAddress,
|
||||||
|
) -> Option<&AnkSharedSecretHash> {
|
||||||
self.shared_secrets.get(&address)
|
self.shared_secrets.get(&address)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +124,10 @@ impl SecretsStore {
|
|||||||
self.unconfirmed_secrets.clone()
|
self.unconfirmed_secrets.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_secret_for_address(&mut self, address: SilentPaymentAddress) -> Result<(SilentPaymentAddress, AnkSharedSecretHash)> {
|
pub fn remove_secret_for_address(
|
||||||
|
&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 {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
use crate::{pcd::Member, process::Process};
|
||||||
use serde::de::Error;
|
use serde::de::Error;
|
||||||
|
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
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 tsify::Tsify;
|
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
use crate::{pcd::Member, process::Process};
|
use tsify::Tsify;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Tsify)]
|
#[derive(Debug, Serialize, Deserialize, Tsify)]
|
||||||
#[tsify(from_wasm_abi)]
|
#[tsify(from_wasm_abi)]
|
||||||
@ -41,23 +41,15 @@ pub mod members_map {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::pcd::Member;
|
use crate::pcd::Member;
|
||||||
|
|
||||||
pub fn serialize<S>(
|
pub fn serialize<S>(map: &HashMap<OutPoint, Member>, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
map: &HashMap<OutPoint, Member>,
|
|
||||||
serializer: S,
|
|
||||||
) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
where
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
{
|
{
|
||||||
let map: HashMap<OutPoint, Member> = map
|
let map: HashMap<OutPoint, Member> = map.iter().map(|(k, v)| (*k, v.to_owned())).collect();
|
||||||
.iter()
|
|
||||||
.map(|(k, v)| (*k, v.to_owned()))
|
|
||||||
.collect();
|
|
||||||
map.serialize(serializer)
|
map.serialize(serializer)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize<'de, D>(
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<HashMap<OutPoint, Member>, D::Error>
|
||||||
deserializer: D,
|
|
||||||
) -> Result<HashMap<OutPoint, Member>, D::Error>
|
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
@ -71,10 +63,7 @@ pub mod outpoint_map {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::process::Process;
|
use crate::process::Process;
|
||||||
|
|
||||||
pub fn serialize<S>(
|
pub fn serialize<S>(map: &HashMap<OutPoint, Process>, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
map: &HashMap<OutPoint, Process>,
|
|
||||||
serializer: S,
|
|
||||||
) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
where
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
{
|
{
|
||||||
@ -88,9 +77,7 @@ pub mod outpoint_map {
|
|||||||
map.serialize(serializer)
|
map.serialize(serializer)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize<'de, D>(
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<HashMap<OutPoint, Process>, D::Error>
|
||||||
deserializer: D,
|
|
||||||
) -> Result<HashMap<OutPoint, Process>, D::Error>
|
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
@ -101,9 +88,7 @@ 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
|
let outpoint = k.parse().map_err(serde::de::Error::custom)?;
|
||||||
.parse()
|
|
||||||
.map_err(serde::de::Error::custom)?;
|
|
||||||
Ok((outpoint, v))
|
Ok((outpoint, v))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@ -118,10 +103,7 @@ 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>(
|
pub fn serialize<S>(map: &BTreeMap<String, [u8; 32]>, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
map: &BTreeMap<String, [u8; 32]>,
|
|
||||||
serializer: S,
|
|
||||||
) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
where
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
{
|
{
|
||||||
@ -135,9 +117,7 @@ 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>(
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<BTreeMap<String, [u8; 32]>, D::Error>
|
||||||
deserializer: D,
|
|
||||||
) -> Result<BTreeMap<String, [u8; 32]>, D::Error>
|
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
|
@ -3,13 +3,16 @@ 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, OutPoint, Transaction, TxOut, XOnlyPublicKey};
|
use sp_client::bitcoin::{Amount, BlockHash, OutPoint, Transaction, TxOut, Txid, 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 {
|
||||||
@ -40,14 +43,16 @@ impl SpWallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_unspent_outputs(&self) -> HashMap<OutPoint, OwnedOutput> {
|
pub fn get_unspent_outputs(&self) -> HashMap<OutPoint, OwnedOutput> {
|
||||||
self.outputs.iter()
|
self.outputs
|
||||||
|
.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.values()
|
self.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)
|
||||||
}
|
}
|
||||||
@ -68,7 +73,39 @@ impl SpWallet {
|
|||||||
self.last_scan = last_scan;
|
self.last_scan = last_scan;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_with_transaction(&mut self, tx: &Transaction, public_tweak: &PublicKey, height: u32) -> Result<HashMap<OutPoint, OwnedOutput>> {
|
pub fn mark_output_spent(&mut self, outpoint: &OutPoint, txid: &Txid) {
|
||||||
|
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
|
||||||
@ -78,7 +115,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
|
||||||
@ -136,7 +173,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 {
|
||||||
@ -166,16 +203,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)?;
|
||||||
@ -183,7 +220,10 @@ pub fn create_transaction(
|
|||||||
Ok(finalized_transaction)
|
Ok(finalized_transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sign_transaction(sp_client: &SpClient, unsigned_transaction: SilentPaymentUnsignedTransaction) -> Result<Transaction> {
|
pub fn sign_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)
|
||||||
|
212
src/updates.rs
Normal file
212
src/updates.rs
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user