Merge branch 'minimal_prd_pcd' into dev
This commit is contained in:
commit
f3ab3525a3
@ -10,9 +10,10 @@ crate-type = ["cdylib", "rlib"]
|
||||
aes-gcm = "0.10.3"
|
||||
anyhow = "1.0"
|
||||
js-sys = "0.3.69"
|
||||
log = "0.4.6"
|
||||
rand = "0.8.5"
|
||||
serde = { version = "1.0.193", features = ["derive"] }
|
||||
serde_json = "1.0.108"
|
||||
serde_json = { version = "1.0.108", features = ["preserve_order"]}
|
||||
# sp_client = { path = "../sp-client" }
|
||||
sp_client = { git = "https://github.com/Sosthene00/sp-client.git", branch = "master" }
|
||||
tsify = { git = "https://github.com/Sosthene00/tsify", branch = "next" }
|
||||
|
386
src/crypto.rs
386
src/crypto.rs
@ -1,63 +1,11 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::{Error, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_client::{
|
||||
bitcoin::{
|
||||
hex::{DisplayHex, FromHex},
|
||||
key::constants::SECRET_KEY_SIZE,
|
||||
Txid,
|
||||
},
|
||||
silentpayments::{
|
||||
bitcoin_hashes::{sha256t_hash_newtype, Hash, HashEngine},
|
||||
secp256k1::PublicKey,
|
||||
utils::SilentPaymentAddress,
|
||||
},
|
||||
};
|
||||
use tsify::Tsify;
|
||||
use sp_client::silentpayments::{bitcoin_hashes::{sha256t_hash_newtype, Hash, HashEngine}, secp256k1::PublicKey};
|
||||
|
||||
use aes_gcm::aead::{Aead, Payload};
|
||||
pub use aes_gcm::{AeadCore, Aes256Gcm, KeyInit};
|
||||
use rand::thread_rng;
|
||||
|
||||
const AAD: &[u8] = "4nk".as_bytes();
|
||||
|
||||
const HALFKEYSIZE: usize = SECRET_KEY_SIZE / 2;
|
||||
|
||||
const THIRTYTWO: usize = 32;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SharedPoint([u8; 64]);
|
||||
|
||||
impl SharedPoint {
|
||||
pub fn as_inner(&self) -> &[u8; 64] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Tsify, Clone, Default, PartialEq)]
|
||||
#[tsify(from_wasm_abi, into_wasm_abi)]
|
||||
pub struct AnkSharedSecret {
|
||||
secret: String,
|
||||
}
|
||||
|
||||
impl AnkSharedSecret {
|
||||
pub fn new(shared_point: PublicKey) -> Self {
|
||||
let mut shared_point_bin = [0u8; 64];
|
||||
shared_point_bin.copy_from_slice(&shared_point.serialize_uncompressed()[1..]);
|
||||
let secret = AnkSharedSecretHash::from_shared_point(shared_point_bin).to_byte_array();
|
||||
Self {
|
||||
secret: secret.to_lower_hex_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_byte_array(&self) -> [u8; 32] {
|
||||
let bytes = Vec::from_hex(&self.secret).unwrap();
|
||||
let mut buf = [0u8; 32];
|
||||
buf.copy_from_slice(&bytes);
|
||||
buf
|
||||
}
|
||||
}
|
||||
pub const AAD: &[u8] = "4nk".as_bytes();
|
||||
|
||||
sha256t_hash_newtype! {
|
||||
pub struct AnkSharedSecretTag = hash_str("4nk/AnkSharedSecret");
|
||||
@ -67,317 +15,39 @@ sha256t_hash_newtype! {
|
||||
}
|
||||
|
||||
impl AnkSharedSecretHash {
|
||||
pub fn from_shared_point(shared_point: [u8; 64]) -> Self {
|
||||
pub fn from_shared_point(shared_point: PublicKey) -> Self {
|
||||
let mut eng = AnkSharedSecretHash::engine();
|
||||
eng.input(&shared_point);
|
||||
eng.input(&shared_point.serialize_uncompressed()[1..]);
|
||||
AnkSharedSecretHash::from_engine(eng)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HalfKey([u8; HALFKEYSIZE]);
|
||||
pub fn encrypt_with_key(key: &[u8; 32], plaintext: &[u8]) -> Result<Vec<u8>> {
|
||||
let encryption_eng = Aes256Gcm::new(key.into());
|
||||
let nonce = Aes256Gcm::generate_nonce(&mut thread_rng());
|
||||
let payload = Payload {
|
||||
msg: plaintext,
|
||||
aad: AAD,
|
||||
};
|
||||
let ciphertext = encryption_eng.encrypt(&nonce, payload)
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
|
||||
impl TryFrom<Vec<u8>> for HalfKey {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(value: Vec<u8>) -> std::prelude::v1::Result<Self, Error> {
|
||||
if value.len() == HALFKEYSIZE {
|
||||
let mut buf = [0u8; HALFKEYSIZE];
|
||||
buf.copy_from_slice(&value);
|
||||
Ok(HalfKey(buf))
|
||||
} else {
|
||||
Err(Error::msg("Invalid length for HalfKey"))
|
||||
}
|
||||
}
|
||||
let mut res: Vec<u8> = Vec::with_capacity(nonce.len() + ciphertext.len());
|
||||
res.extend_from_slice(&nonce);
|
||||
res.extend_from_slice(&ciphertext);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
impl HalfKey {
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
pub fn decrypt_with_key(key: &[u8; 32], ciphertext: &[u8]) -> Result<Vec<u8>> {
|
||||
let decryption_eng = Aes256Gcm::new(key.into());
|
||||
let nonce = &ciphertext[..12];
|
||||
let payload = Payload {
|
||||
msg: &ciphertext[12..],
|
||||
aad: AAD,
|
||||
};
|
||||
let plaintext = decryption_eng.decrypt(nonce.into(), payload)
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
|
||||
pub fn to_inner(&self) -> Vec<u8> {
|
||||
self.0.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Purpose {
|
||||
Login,
|
||||
ThirtyTwoBytes,
|
||||
Arbitrary,
|
||||
}
|
||||
|
||||
pub type CipherText = Vec<u8>;
|
||||
|
||||
pub type EncryptedKey = Vec<u8>;
|
||||
|
||||
pub struct Aes256Decryption {
|
||||
pub purpose: Purpose,
|
||||
cipher_text: CipherText,
|
||||
aes_key: [u8; 32],
|
||||
nonce: [u8; 12],
|
||||
}
|
||||
|
||||
impl Aes256Decryption {
|
||||
pub fn new(purpose: Purpose, cipher_text: CipherText, aes_key: [u8; 32]) -> Result<Self> {
|
||||
if cipher_text.len() <= 12 {
|
||||
return Err(Error::msg("cipher_text is shorter than nonce length"));
|
||||
}
|
||||
let (message_nonce, message_cipher) = cipher_text.split_at(12);
|
||||
let mut nonce = [0u8; 12];
|
||||
nonce.copy_from_slice(message_nonce);
|
||||
Ok(Self {
|
||||
purpose,
|
||||
cipher_text: message_cipher.to_vec(),
|
||||
aes_key,
|
||||
nonce,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decrypt_with_key(&self) -> Result<Vec<u8>> {
|
||||
match self.purpose {
|
||||
Purpose::Login => {
|
||||
let half_key = self.decrypt_login()?;
|
||||
Ok(half_key.to_inner())
|
||||
}
|
||||
Purpose::ThirtyTwoBytes => {
|
||||
let thirty_two_buf = self.decrypt_thirty_two()?;
|
||||
Ok(thirty_two_buf.to_vec())
|
||||
}
|
||||
Purpose::Arbitrary => {
|
||||
let arbitrary = self.decrypt_arbitrary()?;
|
||||
Ok(arbitrary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decrypt_login(&self) -> Result<HalfKey> {
|
||||
let cipher = Aes256Gcm::new(&self.aes_key.into());
|
||||
let plain = cipher
|
||||
.decrypt(&self.nonce.into(), &*self.cipher_text)
|
||||
.map_err(|e| Error::msg(format!("{}", e)))?;
|
||||
if plain.len() != SECRET_KEY_SIZE / 2 {
|
||||
return Err(Error::msg("Plain text of invalid lenght for a login"));
|
||||
}
|
||||
let mut key_half = [0u8; SECRET_KEY_SIZE / 2];
|
||||
key_half.copy_from_slice(&plain);
|
||||
Ok(HalfKey(key_half))
|
||||
}
|
||||
|
||||
fn decrypt_thirty_two(&self) -> Result<[u8; THIRTYTWO]> {
|
||||
let cipher = Aes256Gcm::new(&self.aes_key.into());
|
||||
let plain = cipher
|
||||
.decrypt(&self.nonce.into(), &*self.cipher_text)
|
||||
.map_err(|e| Error::msg(format!("{}", e)))?;
|
||||
if plain.len() != THIRTYTWO {
|
||||
return Err(Error::msg("Plain text of invalid length, should be 32"));
|
||||
}
|
||||
let mut thirty_two = [0u8; THIRTYTWO];
|
||||
thirty_two.copy_from_slice(&plain);
|
||||
Ok(thirty_two)
|
||||
}
|
||||
|
||||
fn decrypt_arbitrary(&self) -> Result<Vec<u8>> {
|
||||
let cipher = Aes256Gcm::new(&self.aes_key.into());
|
||||
let payload = Payload {
|
||||
msg: &self.cipher_text,
|
||||
aad: AAD,
|
||||
};
|
||||
let plain = cipher
|
||||
.decrypt(&self.nonce.into(), payload)
|
||||
.map_err(|e| Error::msg(format!("{}", e)))?;
|
||||
Ok(plain)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Aes256Encryption {
|
||||
pub purpose: Purpose,
|
||||
plaintext: Vec<u8>,
|
||||
aes_key: [u8; 32],
|
||||
nonce: [u8; 12],
|
||||
shared_secrets: HashMap<Txid, HashMap<SilentPaymentAddress, AnkSharedSecret>>,
|
||||
}
|
||||
|
||||
impl Aes256Encryption {
|
||||
pub fn new(purpose: Purpose, plaintext: Vec<u8>) -> Result<Self> {
|
||||
let mut rng = thread_rng();
|
||||
let aes_key: [u8; 32] = Aes256Gcm::generate_key(&mut rng).into();
|
||||
let nonce: [u8; 12] = Aes256Gcm::generate_nonce(&mut rng).into();
|
||||
Self::import_key(purpose, plaintext, aes_key, nonce)
|
||||
}
|
||||
|
||||
pub fn set_shared_secret(
|
||||
&mut self,
|
||||
shared_secrets: HashMap<Txid, HashMap<SilentPaymentAddress, AnkSharedSecret>>,
|
||||
) {
|
||||
self.shared_secrets = shared_secrets;
|
||||
}
|
||||
|
||||
pub fn encrypt_keys_with_shared_secrets(
|
||||
&self,
|
||||
) -> Result<HashMap<SilentPaymentAddress, EncryptedKey>> {
|
||||
let mut res = HashMap::new();
|
||||
let mut rng = thread_rng();
|
||||
|
||||
for (_, sp_address2shared_secret) in self.shared_secrets.iter() {
|
||||
for (sp_address, shared_secret) in sp_address2shared_secret {
|
||||
let cipher = Aes256Gcm::new_from_slice(&shared_secret.to_byte_array())
|
||||
.map_err(|e| Error::msg(format!("{}", e)))?;
|
||||
let nonce = Aes256Gcm::generate_nonce(&mut rng);
|
||||
let encrypted_key = cipher
|
||||
.encrypt(&nonce, self.aes_key.as_slice())
|
||||
.map_err(|e| Error::msg(format!("{}", e)))?;
|
||||
|
||||
let mut ciphertext = Vec::<u8>::with_capacity(nonce.len() + encrypted_key.len());
|
||||
ciphertext.extend(nonce);
|
||||
ciphertext.extend(encrypted_key);
|
||||
|
||||
res.insert(sp_address.to_owned(), ciphertext);
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn import_key(
|
||||
purpose: Purpose,
|
||||
plaintext: Vec<u8>,
|
||||
aes_key: [u8; 32],
|
||||
nonce: [u8; 12],
|
||||
) -> Result<Self> {
|
||||
if plaintext.len() == 0 {
|
||||
return Err(Error::msg("Can't create encryption for an empty message"));
|
||||
}
|
||||
Ok(Self {
|
||||
purpose,
|
||||
plaintext,
|
||||
aes_key,
|
||||
nonce,
|
||||
shared_secrets: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn export_key(&self) -> [u8; 32] {
|
||||
self.aes_key
|
||||
}
|
||||
|
||||
pub fn encrypt_with_aes_key(&self) -> Result<CipherText> {
|
||||
match self.purpose {
|
||||
Purpose::Login => self.encrypt_login(),
|
||||
Purpose::ThirtyTwoBytes => self.encrypt_thirty_two(),
|
||||
Purpose::Arbitrary => self.encrypt_arbitrary(),
|
||||
}
|
||||
}
|
||||
|
||||
fn encrypt_login(&self) -> Result<CipherText> {
|
||||
let half_key: HalfKey = self.plaintext.clone().try_into()?;
|
||||
let cipher = Aes256Gcm::new(&self.aes_key.into());
|
||||
let cipher_text = cipher
|
||||
.encrypt(&self.nonce.into(), half_key.as_slice())
|
||||
.map_err(|e| Error::msg(format!("{}", e)))?;
|
||||
let mut res = Vec::with_capacity(self.nonce.len() + cipher_text.len());
|
||||
res.extend_from_slice(&self.nonce);
|
||||
res.extend_from_slice(&cipher_text);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn encrypt_thirty_two(&self) -> Result<CipherText> {
|
||||
if self.plaintext.len() != 32 {
|
||||
return Err(Error::msg("Invalid length, should be 32"));
|
||||
}
|
||||
let mut thirty_two = [0u8; 32];
|
||||
thirty_two.copy_from_slice(&self.plaintext);
|
||||
let cipher = Aes256Gcm::new(&self.aes_key.into());
|
||||
let cipher_text = cipher
|
||||
.encrypt(&self.nonce.into(), thirty_two.as_slice())
|
||||
.map_err(|e| Error::msg(format!("{}", e)))?;
|
||||
let mut res = Vec::with_capacity(self.nonce.len() + cipher_text.len());
|
||||
res.extend_from_slice(&self.nonce);
|
||||
res.extend_from_slice(&cipher_text);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn encrypt_arbitrary(&self) -> Result<CipherText> {
|
||||
let cipher = Aes256Gcm::new(&self.aes_key.into());
|
||||
let payload = Payload {
|
||||
msg: &self.plaintext,
|
||||
aad: AAD,
|
||||
};
|
||||
let cipher_text = cipher
|
||||
.encrypt(&self.nonce.into(), payload)
|
||||
.map_err(|e| Error::msg(format!("{}", e)))?;
|
||||
let mut res = Vec::with_capacity(self.nonce.len() + cipher_text.len());
|
||||
res.extend_from_slice(&self.nonce);
|
||||
res.extend_from_slice(&cipher_text);
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{io::Read, str::FromStr};
|
||||
|
||||
use sp_client::bitcoin::hex::FromHex;
|
||||
|
||||
use super::*;
|
||||
|
||||
const ALICE_SP_ADDRESS: &str = "tsp1qqw3lqr6xravz9nf8ntazgwwl0fqv47kfjdxsnxs6eutavqfwyv5q6qk97mmyf6dtkdyzqlu2zv6h9j2ggclk7vn705q5u2phglpq7yw3dg5rwpdz";
|
||||
const BOB_SP_ADDRESS: &str = "tsp1qq2hlsgrj0gz8kcfkf9flqw5llz0u2vr04telqndku9mcqm6dl4fhvq60t8r78srrf56w9yr7w9e9dusc2wjqc30up6fjwnh9mw3e3veqegdmtf08";
|
||||
const TRANSACTION: &str = "4e6d03dec558e1b6624f813bf2da7cd8d8fb1c2296684c08cf38724dcfd8d10b";
|
||||
const ALICE_SHARED_SECRET: &str =
|
||||
"ccf02d364c2641ca129a3fdf49de57b705896e233f7ba6d738991993ea7e2106";
|
||||
const BOB_SHARED_SECRET: &str =
|
||||
"15ef3e377fb842e81de52dbaaea8ba30aeb051a81043ee19264afd27353da521";
|
||||
|
||||
#[test]
|
||||
fn new_aes_empty_plaintext() {
|
||||
let plaintext = Vec::new();
|
||||
let aes_enc = Aes256Encryption::new(Purpose::Login, plaintext);
|
||||
|
||||
assert!(aes_enc.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aes_encrypt_login_invalid_length() {
|
||||
let plaintext = "example";
|
||||
let aes_enc_short = Aes256Encryption::new(Purpose::Login, plaintext.as_bytes().to_vec());
|
||||
|
||||
assert!(aes_enc_short.is_ok());
|
||||
|
||||
let cipher = aes_enc_short.unwrap().encrypt_with_aes_key();
|
||||
|
||||
assert!(cipher.is_err());
|
||||
|
||||
let plaintext = [1u8; 64];
|
||||
let aes_enc_long = Aes256Encryption::new(Purpose::Login, plaintext.to_vec());
|
||||
|
||||
assert!(aes_enc_long.is_ok());
|
||||
|
||||
let cipher = aes_enc_long.unwrap().encrypt_with_aes_key();
|
||||
|
||||
assert!(cipher.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aes_encrypt_login() {
|
||||
let plaintext = [1u8; HALFKEYSIZE];
|
||||
let aes_key = Aes256Gcm::generate_key(&mut thread_rng());
|
||||
let nonce = Aes256Gcm::generate_nonce(&mut thread_rng());
|
||||
let aes_enc = Aes256Encryption::import_key(
|
||||
Purpose::Login,
|
||||
plaintext.to_vec(),
|
||||
aes_key.into(),
|
||||
nonce.into(),
|
||||
);
|
||||
|
||||
assert!(aes_enc.is_ok());
|
||||
|
||||
let cipher = aes_enc.unwrap().encrypt_with_aes_key();
|
||||
|
||||
assert!(cipher.is_ok());
|
||||
|
||||
let mut plain_key = [0u8; 32];
|
||||
plain_key.copy_from_slice(&aes_key.to_vec());
|
||||
|
||||
let aes_dec = Aes256Decryption::new(Purpose::Login, cipher.unwrap(), plain_key);
|
||||
|
||||
assert!(aes_dec.is_ok());
|
||||
}
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
60
src/device.rs
Normal file
60
src/device.rs
Normal file
@ -0,0 +1,60 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tsify::Tsify;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use sp_client::{bitcoin::{hashes::Hash, OutPoint, Txid}, spclient::SpWallet};
|
||||
|
||||
use crate::pcd::Member;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, Tsify)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct Device {
|
||||
sp_wallet: SpWallet,
|
||||
pairing_process_commitment: Option<Txid>,
|
||||
paired_member: Option<Member>,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
pub fn new(sp_wallet: SpWallet) -> Self {
|
||||
Self {
|
||||
sp_wallet,
|
||||
pairing_process_commitment: None,
|
||||
paired_member: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_wallet(&self) -> &SpWallet {
|
||||
&self.sp_wallet
|
||||
}
|
||||
|
||||
pub fn get_mut_wallet(&mut self) -> &mut SpWallet {
|
||||
&mut self.sp_wallet
|
||||
}
|
||||
|
||||
pub fn is_linking(&self) -> bool {
|
||||
match self.pairing_process_commitment {
|
||||
Some(ref value) => value.as_raw_hash().as_byte_array().iter().all(|&b| b == 0),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_linked(&self) -> bool {
|
||||
match self.pairing_process_commitment {
|
||||
Some(ref value) => !value.as_raw_hash().as_byte_array().iter().all(|&b| b == 0),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_process_commitment(&self) -> Option<Txid> {
|
||||
self.pairing_process_commitment.clone()
|
||||
}
|
||||
|
||||
pub fn pair(&mut self, commitment_tx: Txid, member: Member) {
|
||||
self.pairing_process_commitment = Some(commitment_tx);
|
||||
self.paired_member = Some(member);
|
||||
}
|
||||
|
||||
pub fn to_member(&self) -> Option<Member> {
|
||||
self.paired_member.clone()
|
||||
}
|
||||
}
|
23
src/lib.rs
23
src/lib.rs
@ -1,6 +1,29 @@
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub use sp_client;
|
||||
pub use log;
|
||||
pub use aes_gcm;
|
||||
|
||||
pub mod crypto;
|
||||
pub mod device;
|
||||
pub mod error;
|
||||
pub mod network;
|
||||
pub mod pcd;
|
||||
pub mod prd;
|
||||
pub mod process;
|
||||
pub mod silentpayments;
|
||||
pub mod signature;
|
||||
|
||||
pub const MAX_PRD_PAYLOAD_SIZE: usize = u16::MAX as usize; // 64KiB sounds reasonable for now
|
||||
|
||||
pub trait MutexExt<T> {
|
||||
fn lock_anyhow(&self) -> Result<MutexGuard<T>, anyhow::Error>;
|
||||
}
|
||||
|
||||
impl<T: Debug> MutexExt<T> for Mutex<T> {
|
||||
fn lock_anyhow(&self) -> Result<MutexGuard<T>, anyhow::Error> {
|
||||
self.lock()
|
||||
.map_err(|e| anyhow::Error::msg(format!("Failed to lock: {}", e)))
|
||||
}
|
||||
}
|
||||
|
196
src/network.rs
196
src/network.rs
@ -1,21 +1,17 @@
|
||||
use std::default;
|
||||
use std::str::FromStr;
|
||||
|
||||
use aes_gcm::{AeadCore, Aes256Gcm};
|
||||
use aes_gcm::aead::{Aead, Payload};
|
||||
use aes_gcm::{Aes256Gcm, KeyInit};
|
||||
use anyhow::{Error, Result};
|
||||
use js_sys::Date;
|
||||
use rand::{thread_rng, RngCore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_client::bitcoin::consensus::serialize;
|
||||
use sp_client::bitcoin::hashes::Hash;
|
||||
use serde_json::Value;
|
||||
use sp_client::bitcoin::hex::{DisplayHex, FromHex};
|
||||
use sp_client::bitcoin::secp256k1::PublicKey;
|
||||
use sp_client::bitcoin::{BlockHash, OutPoint, Transaction};
|
||||
use sp_client::silentpayments::utils::SilentPaymentAddress;
|
||||
use sp_client::bitcoin::OutPoint;
|
||||
use tsify::Tsify;
|
||||
|
||||
use crate::crypto::{Aes256Decryption, Aes256Encryption, Purpose};
|
||||
use crate::crypto::AAD;
|
||||
use crate::error::AnkError;
|
||||
use crate::pcd::Member;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Tsify)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
@ -109,36 +105,13 @@ impl NewTxMessage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Tsify)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct CipherMessage {
|
||||
pub sender: String,
|
||||
pub message: String,
|
||||
pub error: Option<AnkError>,
|
||||
}
|
||||
|
||||
impl CipherMessage {
|
||||
pub fn new(sender: String, message: String) -> Self {
|
||||
Self {
|
||||
sender,
|
||||
message,
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
serde_json::to_string(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Tsify)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct AnkNetworkMsg {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Envelope {
|
||||
pub flag: AnkFlag,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
impl AnkNetworkMsg {
|
||||
impl Envelope {
|
||||
pub fn new(flag: AnkFlag, raw: &str) -> Self {
|
||||
Self {
|
||||
flag,
|
||||
@ -160,48 +133,28 @@ impl AnkNetworkMsg {
|
||||
pub enum CachedMessageStatus {
|
||||
#[default]
|
||||
NoStatus,
|
||||
FaucetWaiting,
|
||||
Pairing,
|
||||
Login,
|
||||
CipherWaitingTx,
|
||||
TxWaitingCipher,
|
||||
SentWaitingConfirmation, // we're sender and wait for commited_in to be spent
|
||||
ReceivedMustConfirm, // we're receiver and we spend commited_in to sender
|
||||
MustSpendConfirmation, // we're sender and we must spend confirmed_by
|
||||
Trusted, // Can receive more messages
|
||||
Closed, // No more messages will be trusted from this handshake
|
||||
TxWaitingPrd,
|
||||
Opened, // Can receive more messages
|
||||
}
|
||||
|
||||
/// Unique struct for both 4nk messages and notification/key exchange, both rust and ts
|
||||
/// 0. Faucet: commited_in with nothing else, status is NoStatus
|
||||
/// 1. notification:
|
||||
/// 0. sender: ciphertext, plaintext, commited_in, sender, recipient, shared_secret, key
|
||||
/// 1. receiver (without tx): ciphertext
|
||||
/// 2. receiver (tx without msg): commited_in, commitment, recipient, shared_secret
|
||||
/// 3. receiver (receive tx after msg): plaintext, key, sender, commited_in, commitment, recipient, shared_secret
|
||||
/// 4. receiver (msg after tx): ciphertext, key, plaintext, sender
|
||||
/// 2. confirmation:
|
||||
/// 0. receiver (spend the smallest vout that pays him in the first tx): confirmed_by
|
||||
/// 1. sender (detect a transaction that pays him and spend commited_by): confirmed_by
|
||||
/// 2. sender toggle status to complete when it spent confirmed_by, receiver when it detects the confirmed_by is spent
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Tsify, Clone)]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Tsify, Clone, PartialEq)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct CachedMessage {
|
||||
pub id: u32,
|
||||
pub status: CachedMessageStatus,
|
||||
pub ciphertext: Option<String>, // Needed when we are waiting for a key in transaction, discarded after
|
||||
pub plaintext: Vec<String>, // Append new messages received while in Trusted state
|
||||
pub commited_in: Option<OutPoint>,
|
||||
pub tied_by: Option<u32>, // index of the output that ties the proposal
|
||||
pub transaction: Option<String>,
|
||||
pub commitment: Option<String>, // content of the op_return
|
||||
pub sender: Option<String>, // Never None when message sent
|
||||
pub recipient: Option<String>, // Never None when message sent
|
||||
pub shared_secret: Option<String>, // Never None when message sent
|
||||
pub key: Option<String>, // Never None when message sent
|
||||
pub sender: Option<Member>, // Never None when message sent
|
||||
pub recipient: Option<Member>, // Never None when message sent
|
||||
pub shared_secrets: Vec<String>, // Max 2 secrets in case we send to both address of the recipient
|
||||
pub cipher: Vec<String>, // Max 2 ciphers in case we send to both address of the recipient
|
||||
pub prd: Option<String>, // Never None when message sent
|
||||
pub pcd: Option<Value>, // Value is here an alias for impl Pcd
|
||||
pub confirmed_by: Option<OutPoint>, // If this None, Sender keeps sending
|
||||
pub timestamp: u64,
|
||||
pub error: Option<AnkError>,
|
||||
}
|
||||
|
||||
impl CachedMessage {
|
||||
@ -223,93 +176,54 @@ impl CachedMessage {
|
||||
serde_json::to_string(self).unwrap()
|
||||
}
|
||||
|
||||
pub fn try_decrypt_cipher(&self, cipher: Vec<u8>) -> Result<Vec<u8>> {
|
||||
if self.shared_secret.is_none() {
|
||||
pub fn try_decrypt_message(&self, cipher: Vec<u8>) -> Result<Vec<u8>> {
|
||||
if self.shared_secrets.is_empty() {
|
||||
return Err(Error::msg(
|
||||
"Can't try decrypt this message, there's no shared secret",
|
||||
));
|
||||
}
|
||||
let mut shared_secret = [0u8; 32];
|
||||
shared_secret.copy_from_slice(&Vec::from_hex(self.shared_secret.as_ref().unwrap())?);
|
||||
let aes_decrypt = Aes256Decryption::new(Purpose::Arbitrary, cipher, shared_secret)?;
|
||||
for shared_secret in &self.shared_secrets {
|
||||
let mut key = [0u8; 32];
|
||||
let mut nonce = [0u8; 12];
|
||||
key.copy_from_slice(&Vec::from_hex(shared_secret)?);
|
||||
nonce.copy_from_slice(&cipher[..12]);
|
||||
|
||||
aes_decrypt.decrypt_with_key()
|
||||
let engine = Aes256Gcm::new(&key.into());
|
||||
let payload = Payload {
|
||||
msg: &cipher[12..],
|
||||
aad: AAD,
|
||||
};
|
||||
match engine.decrypt(&nonce.into(), payload) {
|
||||
Ok(plain) => return Ok(plain),
|
||||
Err(_) => continue
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::msg("Failed to decrypt message"))
|
||||
}
|
||||
|
||||
pub fn try_decrypt_with_shared_secret(&self, shared_secret: [u8; 32]) -> Result<Vec<u8>> {
|
||||
if self.ciphertext.is_none() || self.shared_secret.is_some() {
|
||||
if self.cipher.is_empty() || !self.shared_secrets.is_empty() {
|
||||
return Err(Error::msg(
|
||||
"Can't try decrypt this message, ciphertext is none or shared_secret already found",
|
||||
));
|
||||
}
|
||||
let cipher_bin = Vec::from_hex(self.ciphertext.as_ref().unwrap())?;
|
||||
let aes_decrypt = Aes256Decryption::new(Purpose::Arbitrary, cipher_bin, shared_secret)?;
|
||||
for prd_cipher in &self.cipher {
|
||||
let cipher = Vec::from_hex(prd_cipher)?;
|
||||
let mut nonce = [0u8; 12];
|
||||
nonce.copy_from_slice(&cipher[..12]);
|
||||
|
||||
aes_decrypt.decrypt_with_key()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Tsify, Default)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct TrustedChannel {
|
||||
id: u32, // Just take the id of the cached message?
|
||||
revokation_outpoint: OutPoint, // revokation output is locked by the shared_secret
|
||||
shared_secret: [u8; 32],
|
||||
revoked_in_block: [u8; 32],
|
||||
with: String, // Silent payment address
|
||||
}
|
||||
|
||||
impl TrustedChannel {
|
||||
pub fn new(with: String) -> Result<Self> {
|
||||
// check that with is valid silent payment address
|
||||
SilentPaymentAddress::try_from(with.as_str())?;
|
||||
|
||||
// Generating random id
|
||||
let mut new = Self::default();
|
||||
let mut buf = [0u8; 4];
|
||||
thread_rng().fill_bytes(&mut buf);
|
||||
new.id = u32::from_be_bytes(buf);
|
||||
new.with = with;
|
||||
|
||||
Ok(new)
|
||||
}
|
||||
|
||||
pub fn set_revokation(
|
||||
&mut self,
|
||||
revokation_outpoint: String,
|
||||
shared_secret: String,
|
||||
) -> Result<()> {
|
||||
let mut buf = [0u8; 32];
|
||||
buf.copy_from_slice(&Vec::from_hex(&shared_secret)?);
|
||||
self.revokation_outpoint = OutPoint::from_str(&revokation_outpoint)?;
|
||||
self.shared_secret.copy_from_slice(&buf);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn revoke(&mut self, revoked_in_block: String) -> Result<()> {
|
||||
let block_hash = BlockHash::from_str(&revoked_in_block)?;
|
||||
let mut buf = [0u8; 32];
|
||||
buf.copy_from_slice(&serialize::<BlockHash>(&block_hash));
|
||||
self.revoked_in_block = buf;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_set(&self) -> bool {
|
||||
self.revokation_outpoint == OutPoint::default()
|
||||
}
|
||||
|
||||
pub fn is_revoked(&self) -> bool {
|
||||
self.revoked_in_block != [0u8; 32]
|
||||
}
|
||||
|
||||
pub fn encrypt_msg_for(&self, msg: String) -> Result<String> {
|
||||
let mut rng = thread_rng();
|
||||
let nonce: [u8; 12] = Aes256Gcm::generate_nonce(&mut rng).into();
|
||||
let aes256_encryption = Aes256Encryption::import_key(Purpose::Arbitrary, msg.into_bytes(), self.shared_secret, nonce)?;
|
||||
let cipher = aes256_encryption.encrypt_with_aes_key()?;
|
||||
Ok(cipher.to_lower_hex_string())
|
||||
let engine = Aes256Gcm::new(&shared_secret.into());
|
||||
let payload = Payload {
|
||||
msg: &cipher[12..],
|
||||
aad: AAD,
|
||||
};
|
||||
match engine.decrypt(&nonce.into(), payload) {
|
||||
Ok(plain) => return Ok(plain),
|
||||
Err(_) => continue
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::msg("Failed to decrypt message"))
|
||||
}
|
||||
}
|
||||
|
233
src/pcd.rs
Normal file
233
src/pcd.rs
Normal file
@ -0,0 +1,233 @@
|
||||
use std::{collections::HashSet, str::FromStr};
|
||||
use anyhow::{Result, Error};
|
||||
|
||||
use aes_gcm::{aead::{Aead, Payload}, AeadCore, Aes256Gcm, KeyInit};
|
||||
use log::debug;
|
||||
use rand::thread_rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
use sp_client::{bitcoin::{hashes::{sha256t_hash_newtype, Hash, HashEngine}, hex::{DisplayHex, FromHex}, Txid}, silentpayments::utils::SilentPaymentAddress};
|
||||
use tsify::Tsify;
|
||||
|
||||
use crate::crypto::AAD;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Tsify)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct Member {
|
||||
sp_addresses: Vec<String>
|
||||
}
|
||||
|
||||
impl Member {
|
||||
pub fn new(
|
||||
sp_addresses: Vec<SilentPaymentAddress>,
|
||||
) -> Result<Self> {
|
||||
if sp_addresses.is_empty() {
|
||||
return Err(Error::msg("empty address set"));
|
||||
}
|
||||
|
||||
let mut seen = HashSet::new();
|
||||
for s in sp_addresses.iter() {
|
||||
if !seen.insert(s.clone()) {
|
||||
return Err(Error::msg("Duplicate addresses found"));
|
||||
}
|
||||
}
|
||||
|
||||
let res: Vec<String> = sp_addresses.iter()
|
||||
.map(|a| Into::<String>::into(*a))
|
||||
.collect();
|
||||
|
||||
Ok(Self {
|
||||
sp_addresses: res
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_addresses(&self) -> Vec<String> {
|
||||
self.sp_addresses.clone()
|
||||
}
|
||||
}
|
||||
|
||||
sha256t_hash_newtype! {
|
||||
pub struct AnkPcdTag = hash_str("4nk/Pcd");
|
||||
|
||||
#[hash_newtype(forward)]
|
||||
pub struct AnkPcdHash(_);
|
||||
}
|
||||
|
||||
impl AnkPcdHash {
|
||||
pub fn from_value(value: &Value) -> Self {
|
||||
let mut eng = AnkPcdHash::engine();
|
||||
eng.input(value.to_string().as_bytes());
|
||||
AnkPcdHash::from_engine(eng)
|
||||
}
|
||||
|
||||
pub fn from_map(map: &Map<String, Value>) -> Self {
|
||||
let value = Value::Object(map.clone());
|
||||
let mut eng = AnkPcdHash::engine();
|
||||
eng.input(value.to_string().as_bytes());
|
||||
AnkPcdHash::from_engine(eng)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Pcd<'a>: Serialize + Deserialize<'a> {
|
||||
fn tagged_hash(&self) -> AnkPcdHash {
|
||||
AnkPcdHash::from_value(&self.to_value())
|
||||
}
|
||||
|
||||
fn encrypt_fields(&self, fields2keys: &mut Map<String, Value>, fields2cipher: &mut Map<String, Value>) -> Result<()> {
|
||||
let as_value = self.to_value();
|
||||
let as_map = as_value.as_object().ok_or_else(|| Error::msg("Expected object"))?;
|
||||
let mut rng = thread_rng();
|
||||
|
||||
for (field, value) in as_map {
|
||||
let aes_key = Aes256Gcm::generate_key(&mut rng);
|
||||
let nonce = Aes256Gcm::generate_nonce(&mut rng);
|
||||
fields2keys.insert(field.to_owned(), Value::String(aes_key.to_lower_hex_string()));
|
||||
|
||||
let encrypt_eng = Aes256Gcm::new(&aes_key);
|
||||
let value_string = value.to_string();
|
||||
let payload = Payload {
|
||||
msg: value_string.as_bytes(),
|
||||
aad: AAD,
|
||||
};
|
||||
let cipher = encrypt_eng.encrypt(&nonce, payload)
|
||||
.map_err(|e| Error::msg(format!("Encryption failed for field {}: {}", field, e)))?;
|
||||
|
||||
let mut res = Vec::with_capacity(nonce.len() + cipher.len());
|
||||
res.extend_from_slice(&nonce);
|
||||
res.extend_from_slice(&cipher);
|
||||
|
||||
fields2cipher.insert(field.to_owned(), Value::String(res.to_lower_hex_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decrypt_fields(&self, fields2keys: &Map<String, Value>, fields2plain: &mut Map<String, Value>) -> Result<()> {
|
||||
let value = self.to_value();
|
||||
let map = value.as_object().unwrap();
|
||||
|
||||
for (field, encrypted_value) in map.iter() {
|
||||
if let Some(aes_key) = fields2keys.get(field) {
|
||||
|
||||
let key_buf = Vec::from_hex(&aes_key.to_string().trim_matches('\"'))?;
|
||||
|
||||
let decrypt_eng = Aes256Gcm::new(key_buf.as_slice().into());
|
||||
|
||||
let raw_cipher = Vec::from_hex(&encrypted_value.as_str().ok_or_else(|| Error::msg("Expected string"))?.trim_matches('\"'))?;
|
||||
|
||||
if raw_cipher.len() < 28 {
|
||||
return Err(Error::msg(format!("Invalid ciphertext length for field {}", field)));
|
||||
}
|
||||
|
||||
let payload = Payload {
|
||||
msg: &raw_cipher[12..],
|
||||
aad: AAD,
|
||||
};
|
||||
|
||||
let plain = decrypt_eng.decrypt(raw_cipher[..12].into(), payload)
|
||||
.map_err(|_| Error::msg(format!("Failed to decrypt field {}", field)))?;
|
||||
let decrypted_value: String = String::from_utf8(plain)?;
|
||||
|
||||
fields2plain.insert(field.to_owned(), Value::String(decrypted_value));
|
||||
} else {
|
||||
fields2plain.insert(field.to_owned(), Value::Null);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::from_str(&serde_json::to_string(&self).unwrap()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Pcd<'_> for Value {}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Tsify)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct ValidationRule {
|
||||
quorum: f32, // Must be >= 0.0, <= 1.0, 0.0 means reading right
|
||||
pub fields: Vec<String>, // Which fields are concerned by this rule
|
||||
min_sig_member: f32, // Must be >= 0.0, <= 1.0, does each member need to sign with all it's devices?
|
||||
}
|
||||
|
||||
impl ValidationRule {
|
||||
pub fn new(quorum: f32, fields: Vec<String>, min_sig_member: f32) -> Result<Self> {
|
||||
if quorum < 0.0 || quorum > 1.0 {
|
||||
return Err(Error::msg("quorum must be 0.0 < quorum <= 1.0"));
|
||||
}
|
||||
|
||||
if min_sig_member < 0.0 || min_sig_member > 1.0 {
|
||||
return Err(Error::msg("min_signatures_member must be 0.0 < min_signatures_member <= 1.0"));
|
||||
}
|
||||
|
||||
if fields.is_empty() {
|
||||
return Err(Error::msg("Fields can't be empty"));
|
||||
}
|
||||
|
||||
let res = Self {
|
||||
quorum,
|
||||
fields,
|
||||
min_sig_member,
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Tsify)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct RoleDefinition {
|
||||
pub members: Vec<Member>,
|
||||
pub validation_rules: Vec<ValidationRule>,
|
||||
}
|
||||
|
||||
pub fn compare_maps(map1: &Map<String, Value>, map2: &Map<String, Value>) -> bool {
|
||||
// First, check if both maps have the same keys
|
||||
if map1.keys().collect::<Vec<&String>>() != map2.keys().collect::<Vec<&String>>() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Then, check if the corresponding values have the same type
|
||||
for key in map1.keys() {
|
||||
let value1 = map1.get(key).unwrap();
|
||||
let value2 = map2.get(key).unwrap();
|
||||
|
||||
if !compare_values(value1, value2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn compare_values(value1: &Value, value2: &Value) -> bool {
|
||||
if value1.is_null() && value2.is_null() {
|
||||
return true;
|
||||
} else if value1.is_boolean() && value2.is_boolean() {
|
||||
return true;
|
||||
} else if value1.is_number() && value2.is_number() {
|
||||
return true;
|
||||
} else if value1.is_string() && value2.is_string() {
|
||||
return true;
|
||||
} else if value1.is_array() && value2.is_array() {
|
||||
return compare_arrays(value1.as_array().unwrap(), value2.as_array().unwrap());
|
||||
} else if value1.is_object() && value2.is_object() {
|
||||
// Recursive comparison for nested objects
|
||||
return compare_maps(value1.as_object().unwrap(), value2.as_object().unwrap());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
fn compare_arrays(array1: &Vec<Value>, array2: &Vec<Value>) -> bool {
|
||||
// Compare the type of each element in the arrays
|
||||
for (elem1, elem2) in array1.iter().zip(array2.iter()) {
|
||||
if !compare_values(elem1, elem2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
209
src/prd.rs
Normal file
209
src/prd.rs
Normal file
@ -0,0 +1,209 @@
|
||||
use std::collections::HashSet;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{Result, Error};
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use serde_json::{Map, Value};
|
||||
use sp_client::bitcoin::hex::FromHex;
|
||||
use sp_client::bitcoin::secp256k1::SecretKey;
|
||||
use sp_client::bitcoin::{OutPoint, XOnlyPublicKey};
|
||||
use sp_client::silentpayments::utils::SilentPaymentAddress;
|
||||
use sp_client::spclient::SpWallet;
|
||||
use sp_client::bitcoin::hashes::{sha256t_hash_newtype, Hash, HashEngine};
|
||||
use tsify::Tsify;
|
||||
|
||||
use crate::pcd::{AnkPcdHash, Member, Pcd};
|
||||
use crate::signature::{AnkHash, AnkMessageHash, Proof};
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, Tsify)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum PrdType {
|
||||
#[default]
|
||||
None,
|
||||
Message,
|
||||
Update, // Update an existing process
|
||||
List, // request a list of items
|
||||
Response,
|
||||
Confirm,
|
||||
TxProposal, // Send a psbt asking for recipient signature, used for login not sure about other use cases
|
||||
}
|
||||
|
||||
sha256t_hash_newtype! {
|
||||
pub struct AnkPrdTag = hash_str("4nk/Prd");
|
||||
|
||||
#[hash_newtype(forward)]
|
||||
pub struct AnkPrdHash(_);
|
||||
}
|
||||
|
||||
impl AnkPrdHash {
|
||||
pub fn from_value(value: &Value) -> Self {
|
||||
let mut eng = AnkPrdHash::engine();
|
||||
eng.input(value.to_string().as_bytes());
|
||||
AnkPrdHash::from_engine(eng)
|
||||
}
|
||||
|
||||
pub fn from_map(map: &Map<String, Value>) -> Self {
|
||||
let value = Value::Object(map.clone());
|
||||
let mut eng = AnkPrdHash::engine();
|
||||
eng.input(value.to_string().as_bytes());
|
||||
AnkPrdHash::from_engine(eng)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Tsify)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct Prd {
|
||||
pub prd_type: PrdType,
|
||||
pub root_commitment: String,
|
||||
pub sender: String,
|
||||
pub keys: Map<String, Value>, // key is a key in pcd, value is the key to decrypt it
|
||||
pub validation_tokens: Vec<Proof>,
|
||||
pub payload: String, // Payload depends on the actual type
|
||||
pub proof: Option<Proof>, // This must be None up to the creation of the network message
|
||||
}
|
||||
|
||||
impl Prd {
|
||||
pub fn new_update(
|
||||
root_commitment: OutPoint,
|
||||
sender: String, // Should take Member as argument
|
||||
encrypted_pcd: Map<String, Value>,
|
||||
keys: Map<String, Value>
|
||||
) -> Self {
|
||||
Self {
|
||||
prd_type: PrdType::Update,
|
||||
root_commitment: root_commitment.to_string(),
|
||||
sender,
|
||||
validation_tokens: vec![],
|
||||
keys,
|
||||
payload: Value::Object(encrypted_pcd).to_string(),
|
||||
proof: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_response(
|
||||
root_commitment: OutPoint,
|
||||
sender: String,
|
||||
validation_token: Proof,
|
||||
pcd_commitment: AnkPcdHash,
|
||||
) -> Self {
|
||||
Self {
|
||||
prd_type: PrdType::Response,
|
||||
root_commitment: root_commitment.to_string(),
|
||||
sender,
|
||||
validation_tokens: vec![validation_token],
|
||||
keys: Map::new(),
|
||||
payload: pcd_commitment.to_string(),
|
||||
proof: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_confirm(
|
||||
root_commitment: OutPoint,
|
||||
sender: Member,
|
||||
pcd_commitment: AnkPcdHash,
|
||||
) -> Self {
|
||||
Self {
|
||||
prd_type: PrdType::Confirm,
|
||||
root_commitment: root_commitment.to_string(),
|
||||
sender: serde_json::to_string(&sender).unwrap(),
|
||||
validation_tokens: vec![],
|
||||
keys: Map::new(),
|
||||
payload: pcd_commitment.to_string(),
|
||||
proof: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn _extract_from_message(plain: &[u8], commitment: Option<&AnkPrdHash>) -> Result<Self> {
|
||||
let prd: Prd = serde_json::from_slice(plain)?;
|
||||
if let Some(commitment) = commitment {
|
||||
// check that the hash of the prd is consistent with what's commited in the op_return
|
||||
if prd.create_commitment() != *commitment {
|
||||
return Err(anyhow::Error::msg("Received prd is not what was commited in the transaction"));
|
||||
}
|
||||
}
|
||||
// check that the proof is consistent
|
||||
let sender: Member = serde_json::from_str(&prd.sender)?;
|
||||
if let Some(proof) = prd.proof {
|
||||
// take the spending keys in sender
|
||||
let addresses = sender.get_addresses();
|
||||
let mut spend_keys: Vec<XOnlyPublicKey> = vec![];
|
||||
for address in addresses {
|
||||
spend_keys.push(<SilentPaymentAddress>::try_from(address)?.get_spend_key().x_only_public_key().0);
|
||||
}
|
||||
// The key in proof must be one of the sender keys
|
||||
let proof_key = proof.get_key();
|
||||
let mut known_key = false;
|
||||
for key in spend_keys {
|
||||
if key == proof_key {
|
||||
known_key = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !known_key {
|
||||
return Err(anyhow::Error::msg("Proof signed with an unknown key"));
|
||||
}
|
||||
proof.verify()?;
|
||||
}
|
||||
// check that the commitment outpoint is valid, just in case
|
||||
OutPoint::from_str(&prd.root_commitment)?;
|
||||
Ok(prd)
|
||||
}
|
||||
|
||||
pub fn extract_from_message(plain: &[u8]) -> Result<Self> {
|
||||
Self::_extract_from_message(plain, None)
|
||||
}
|
||||
|
||||
pub fn extract_from_message_with_commitment(plain: &[u8], commitment: &AnkPrdHash) -> Result<Self> {
|
||||
Self::_extract_from_message(plain, Some(commitment))
|
||||
}
|
||||
|
||||
pub fn filter_keys(&mut self, to_keep: HashSet<String>) {
|
||||
let current_keys = self.keys.clone();
|
||||
let filtered_keys: Map<String, Value> = current_keys.into_iter()
|
||||
.filter(|(field, _)| to_keep.contains(field))
|
||||
.collect();
|
||||
self.keys = filtered_keys;
|
||||
}
|
||||
|
||||
/// We commit to everything except the keys and the proof
|
||||
/// Because 1) we need one commitment to common data for all recipients of the transaction
|
||||
/// 2) we already commit to the keys in the sender proof anyway
|
||||
pub fn create_commitment(&self) -> AnkPrdHash {
|
||||
let mut to_commit = self.clone();
|
||||
to_commit.keys = Map::new();
|
||||
to_commit.proof = None;
|
||||
|
||||
if to_commit.payload.len() != 64 && Vec::from_hex(&to_commit.payload).is_err() {
|
||||
to_commit.payload = Value::from_str(&to_commit.payload).unwrap().tagged_hash().to_string();
|
||||
}
|
||||
|
||||
AnkPrdHash::from_value(&to_commit.to_value())
|
||||
}
|
||||
|
||||
/// Generate the signed proof and serialize to send over the network
|
||||
pub fn to_network_msg(&self, sp_wallet: &SpWallet) -> Result<String> {
|
||||
let spend_sk: SecretKey = sp_wallet.get_client().get_spend_key().try_into()?;
|
||||
let to_sign = self.clone(); // we sign the whole prd, incl the keys, for each recipient
|
||||
|
||||
let message_hash = AnkHash::Message(AnkMessageHash::from_message(to_sign.to_string().as_bytes()));
|
||||
|
||||
let proof = Proof::new(message_hash, spend_sk);
|
||||
|
||||
let mut res = self.clone();
|
||||
res.proof = Some(proof);
|
||||
|
||||
Ok(res.to_string())
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
serde_json::to_string(self).unwrap()
|
||||
}
|
||||
|
||||
pub fn to_value(&self) -> Value {
|
||||
Value::from_str(&self.to_string()).unwrap()
|
||||
}
|
||||
}
|
86
src/process.rs
Normal file
86
src/process.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use std::{collections::HashMap, sync::{Mutex, MutexGuard, OnceLock}};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
use sp_client::{bitcoin::OutPoint, silentpayments::utils::SilentPaymentAddress};
|
||||
|
||||
use crate::{crypto::AnkSharedSecretHash, prd::Prd, signature::Proof, MutexExt};
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ProcessState {
|
||||
pub commited_in: OutPoint,
|
||||
pub encrypted_pcd: Value, // Some fields may be clear, if the owner of the process decides so
|
||||
pub keys: Map<String, Value>, // We may not always have all the keys
|
||||
pub validation_token: Vec<Proof>, // This signs the encrypted pcd
|
||||
}
|
||||
|
||||
/// A process is basically a succession of states
|
||||
/// If a process has nothing to do with us, shared_secrets and impending_requests will be empty
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Process {
|
||||
states: Vec<ProcessState>,
|
||||
shared_secrets: HashMap<String, AnkSharedSecretHash>,
|
||||
impending_requests: Vec<Prd>,
|
||||
}
|
||||
|
||||
impl Process {
|
||||
pub fn new(states: Vec<ProcessState>, shared_secrets: HashMap<SilentPaymentAddress, AnkSharedSecretHash>, impending_requests: Vec<Prd>) -> Self {
|
||||
Self {
|
||||
states,
|
||||
shared_secrets: shared_secrets.into_iter().map(|(k, v)| (k.to_string(), v)).collect(),
|
||||
impending_requests,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_shared_secret(&mut self, address: SilentPaymentAddress, secret: AnkSharedSecretHash) {
|
||||
self.shared_secrets.insert(address.to_string(), secret);
|
||||
}
|
||||
|
||||
pub fn get_shared_secret_for_address(&self, address: &SilentPaymentAddress) -> Option<AnkSharedSecretHash> {
|
||||
self.shared_secrets.get(&address.to_string()).cloned()
|
||||
}
|
||||
|
||||
pub fn get_all_secrets(&self) -> HashMap<SilentPaymentAddress, AnkSharedSecretHash> {
|
||||
self.shared_secrets.clone().into_iter().map(|(k, v)| (SilentPaymentAddress::try_from(k.as_str()).unwrap(), v)).collect()
|
||||
}
|
||||
|
||||
pub fn insert_state(&mut self, state: ProcessState) {
|
||||
self.states.push(state);
|
||||
}
|
||||
|
||||
pub fn get_status_at(&self, index: usize) -> Option<&ProcessState> {
|
||||
self.states.get(index)
|
||||
}
|
||||
|
||||
pub fn get_status_at_mut(&mut self, index: usize) -> Option<&mut ProcessState> {
|
||||
self.states.get_mut(index)
|
||||
}
|
||||
|
||||
pub fn get_latest_state(&self) -> Option<&ProcessState> {
|
||||
self.states.last()
|
||||
}
|
||||
|
||||
pub fn get_latest_state_mut(&mut self) -> Option<&mut ProcessState> {
|
||||
self.states.last_mut()
|
||||
}
|
||||
|
||||
pub fn insert_impending_request(&mut self, request: Prd) {
|
||||
self.impending_requests.push(request);
|
||||
}
|
||||
|
||||
pub fn get_impending_requests(&self) -> Vec<&Prd> {
|
||||
self.impending_requests.iter().collect()
|
||||
}
|
||||
|
||||
pub fn get_impending_requests_mut(&mut self) -> Vec<&mut Prd> {
|
||||
self.impending_requests.iter_mut().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub static CACHEDPROCESSES: OnceLock<Mutex<HashMap<OutPoint, Process>>> = OnceLock::new();
|
||||
|
||||
pub fn lock_processes() -> Result<MutexGuard<'static, HashMap<OutPoint, Process>>, anyhow::Error> {
|
||||
CACHEDPROCESSES
|
||||
.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
.lock_anyhow()
|
||||
}
|
111
src/signature.rs
Normal file
111
src/signature.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use anyhow::Result;
|
||||
use rand::{thread_rng, RngCore};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use sp_client::bitcoin::key::Secp256k1;
|
||||
use sp_client::bitcoin::secp256k1::schnorr::Signature;
|
||||
use sp_client::bitcoin::secp256k1::{Keypair, Message, SecretKey, XOnlyPublicKey};
|
||||
use sp_client::bitcoin::hashes::{sha256t_hash_newtype, Hash, HashEngine};
|
||||
|
||||
use crate::pcd::AnkPcdHash;
|
||||
|
||||
sha256t_hash_newtype! {
|
||||
pub struct AnkMessageTag = hash_str("4nk/Message");
|
||||
|
||||
#[hash_newtype(forward)]
|
||||
pub struct AnkMessageHash(_);
|
||||
|
||||
pub struct AnkValidationYesTag = hash_str("4nk/yes");
|
||||
|
||||
#[hash_newtype(forward)]
|
||||
pub struct AnkValidationYesHash(_);
|
||||
|
||||
pub struct AnkValidationNoTag = hash_str("4nk/no");
|
||||
|
||||
#[hash_newtype(forward)]
|
||||
pub struct AnkValidationNoHash(_);
|
||||
}
|
||||
|
||||
impl AnkMessageHash {
|
||||
pub fn from_message(message: &[u8]) -> Self {
|
||||
let mut eng = AnkMessageHash::engine();
|
||||
eng.input(&message);
|
||||
AnkMessageHash::from_engine(eng)
|
||||
}
|
||||
}
|
||||
|
||||
impl AnkValidationYesHash {
|
||||
pub fn from_commitment(commitment: AnkPcdHash) -> Self {
|
||||
let mut eng = AnkValidationYesHash::engine();
|
||||
eng.input(&commitment.to_byte_array());
|
||||
AnkValidationYesHash::from_engine(eng)
|
||||
}
|
||||
}
|
||||
|
||||
impl AnkValidationNoHash {
|
||||
pub fn from_commitment(commitment: AnkPcdHash) -> Self {
|
||||
let mut eng = AnkValidationNoHash::engine();
|
||||
eng.input(&commitment.to_byte_array());
|
||||
AnkValidationNoHash::from_engine(eng)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub enum AnkHash {
|
||||
Message(AnkMessageHash),
|
||||
ValidationYes(AnkValidationYesHash),
|
||||
ValidationNo(AnkValidationNoHash),
|
||||
}
|
||||
|
||||
impl AnkHash {
|
||||
pub fn to_byte_array(&self) -> [u8; 32] {
|
||||
match self {
|
||||
AnkHash::Message(hash) => hash.to_byte_array(),
|
||||
AnkHash::ValidationYes(hash) => hash.to_byte_array(),
|
||||
AnkHash::ValidationNo(hash) => hash.to_byte_array(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Proof {
|
||||
signature: Signature,
|
||||
message: AnkHash,
|
||||
key: XOnlyPublicKey
|
||||
}
|
||||
|
||||
impl Proof {
|
||||
pub fn new(message_hash: AnkHash, signing_key: SecretKey) -> Self {
|
||||
let secp = Secp256k1::signing_only();
|
||||
|
||||
let keypair = Keypair::from_secret_key(&secp, &signing_key);
|
||||
|
||||
let mut aux_rand = [0u8; 32];
|
||||
|
||||
thread_rng().fill_bytes(&mut aux_rand);
|
||||
|
||||
let sig = secp.sign_schnorr_with_aux_rand(&Message::from_digest(message_hash.to_byte_array()), &keypair, &aux_rand);
|
||||
|
||||
Self {
|
||||
signature: sig,
|
||||
message: message_hash,
|
||||
key: keypair.x_only_public_key().0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_key(&self) -> XOnlyPublicKey {
|
||||
self.key
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> Result<()> {
|
||||
let secp = Secp256k1::verification_only();
|
||||
secp.verify_schnorr(&self.signature, &Message::from_digest(self.message.to_byte_array()), &self.key)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
serde_json::to_string(self).unwrap()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,10 +5,8 @@ use anyhow::{Error, Result};
|
||||
|
||||
use rand::{thread_rng, Rng};
|
||||
use sp_client::bitcoin::consensus::deserialize;
|
||||
use sp_client::bitcoin::key::{Keypair, Secp256k1, TapTweak};
|
||||
use sp_client::bitcoin::psbt::{raw, Output};
|
||||
use sp_client::bitcoin::{Address, Psbt, ScriptBuf, Transaction, Txid};
|
||||
use sp_client::bitcoin::{Amount, OutPoint, TxOut};
|
||||
use sp_client::bitcoin::psbt::raw;
|
||||
use sp_client::bitcoin::{Amount, OutPoint, Psbt};
|
||||
use sp_client::constants::{
|
||||
self, DUST_THRESHOLD, PSBT_SP_ADDRESS_KEY, PSBT_SP_PREFIX, PSBT_SP_SUBTYPE,
|
||||
};
|
||||
@ -29,9 +27,7 @@ pub fn create_transaction(
|
||||
.to_spendable_list()
|
||||
// filter out freezed utxos
|
||||
.into_iter()
|
||||
.filter(|(outpoint, _)| {
|
||||
!freezed_utxos.contains(outpoint)
|
||||
})
|
||||
.filter(|(outpoint, _)| !freezed_utxos.contains(outpoint))
|
||||
.collect();
|
||||
|
||||
// if we have a payload, it means we are notifying, so let's add a revokation output
|
||||
@ -39,20 +35,24 @@ pub fn create_transaction(
|
||||
recipients.push(Recipient {
|
||||
address: sp_wallet.get_client().get_receiving_address(),
|
||||
amount: DUST_THRESHOLD,
|
||||
nb_outputs: 1
|
||||
nb_outputs: 1,
|
||||
})
|
||||
}
|
||||
|
||||
let sum_outputs = recipients.iter().fold(Amount::from_sat(0), |acc, x| acc + x.amount);
|
||||
let sum_outputs = recipients
|
||||
.iter()
|
||||
.fold(Amount::from_sat(0), |acc, x| acc + x.amount);
|
||||
|
||||
let zero_value_recipient = recipients.iter_mut().find(|r| r.amount == Amount::from_sat(0));
|
||||
let zero_value_recipient = recipients
|
||||
.iter_mut()
|
||||
.find(|r| r.amount == Amount::from_sat(0));
|
||||
|
||||
let mut inputs: HashMap<OutPoint, OwnedOutput> = HashMap::new();
|
||||
let mut total_available = Amount::from_sat(0);
|
||||
for outpoint in mandatory_inputs {
|
||||
let (must_outpoint, must_output) = available_outpoints
|
||||
.remove_entry(&outpoint)
|
||||
.ok_or_else(|| Error::msg("Mandatory outpoint unknown"))?;
|
||||
.ok_or_else(|| Error::msg(format!("Mandatory outpoint unknown: {}", outpoint)))?;
|
||||
total_available += must_output.amount;
|
||||
inputs.insert(must_outpoint, must_output);
|
||||
}
|
||||
@ -91,7 +91,8 @@ pub fn create_transaction(
|
||||
if let Some(address) = fee_payer {
|
||||
SpClient::set_fees(&mut new_psbt, fee_rate, address)?;
|
||||
} else {
|
||||
let candidates: Vec<Option<String>> = new_psbt.outputs
|
||||
let candidates: Vec<Option<String>> = new_psbt
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|o| {
|
||||
if let Some(value) = o.proprietary.get(&raw::ProprietaryKey {
|
||||
@ -114,17 +115,17 @@ pub fn create_transaction(
|
||||
for candidate in candidates {
|
||||
if let Some(c) = candidate {
|
||||
if c == change_address {
|
||||
SpClient::set_fees(&mut new_psbt, fee_rate, change_address.clone())?;
|
||||
SpClient::set_fees(&mut new_psbt, fee_rate, change_address.clone())?;
|
||||
fee_set = true;
|
||||
break;
|
||||
} else if c == sender_address {
|
||||
SpClient::set_fees(&mut new_psbt, fee_rate, sender_address.clone())?;
|
||||
SpClient::set_fees(&mut new_psbt, fee_rate, sender_address.clone())?;
|
||||
fee_set = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if !fee_set {
|
||||
return Err(Error::msg("Must specify payer for fee"));
|
||||
}
|
||||
@ -170,199 +171,247 @@ pub fn map_outputs_to_sp_address(psbt_str: &str) -> Result<HashMap<String, Vec<u
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Write;
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use std::io::Write;
|
||||
|
||||
use crate::network::CipherMessage;
|
||||
// use crate::pcd::Pcd;
|
||||
// use crate::prd::{Prd, PrdType};
|
||||
// use crate::process::{Member, Process, Role, ValidationRules};
|
||||
|
||||
use super::*;
|
||||
use sp_client::bitcoin::hashes::{sha256, Hash};
|
||||
use sp_client::bitcoin::hex::{DisplayHex, FromHex};
|
||||
use sp_client::bitcoin::secp256k1::PublicKey;
|
||||
use sp_client::bitcoin::Transaction;
|
||||
use sp_client::silentpayments::utils::receiving::{
|
||||
calculate_tweak_data, get_pubkey_from_input,
|
||||
};
|
||||
// use super::*;
|
||||
// use serde_json::Value;
|
||||
// use sp_client::bitcoin::hashes::{sha256, Hash};
|
||||
// use sp_client::bitcoin::hex::FromHex;
|
||||
// use sp_client::bitcoin::secp256k1::PublicKey;
|
||||
// use sp_client::bitcoin::{ScriptBuf, Transaction, Txid};
|
||||
// use sp_client::silentpayments::utils::receiving::{
|
||||
// calculate_tweak_data, get_pubkey_from_input,
|
||||
// };
|
||||
|
||||
const ALICE_WALLET: &str = "{\"tx_history\": [],\"client\":{\"network\":\"regtest\",\"label\":\"default\",\"scan_sk\":\"e3d8922a41a7cb1a84a90f4334e987bb5ea2df6a1fdf44f789b5302de119f9e2\",\"spend_key\":{\"Secret\":\"93292e5b21042c6cfc742ba30e9d2a1e01609b12d154a1825184ed12c7b9631b\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,104,242,105,185,6,124,208,34,44,149,52,163,38,63,221,150,12,198,24,95,143,126,235,37,149,233,88,118,32,86,233,152],\"spend_pubkey\":[3,198,82,196,243,12,59,126,109,143,144,157,128,176,168,94,54,134,232,139,115,102,11,178,128,244,239,251,40,228,67,153,72],\"change_label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"labels\":[[\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",[2,244,223,255,57,50,216,27,133,112,138,69,120,126,85,110,6,242,141,33,136,191,82,164,241,54,179,115,84,161,145,174,154]]]}},\"outputs\":{\"wallet_fingerprint\":[187,119,108,230,171,125,106,11],\"birthday\":1620,\"last_scan\":2146,\"outputs\":{\"9a4a67cc5a40bf882d8b300d91024d7c97024b3b68b2df7745a5b9ea1df1888c:1\":{\"blockheight\":1620,\"tweak\":\"b8b63b3ed97d297b744135cfac2fb4a344c881a77543b71f1fcd16bc67514f26\",\"amount\":3938643,\"script\":\"51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":\"Unspent\"}}}}";
|
||||
const BOB_WALLET: &str = "{\"tx_history\": [],\"client\":{\"network\":\"regtest\",\"label\":\"default\",\"scan_sk\":\"0de90b7195c1380d5fde13de3f1d66d53423a9896314839e36ba672653af60b4\",\"spend_key\":{\"Secret\":\"affe686075ecbe17b8ce7de45ec31314804259d0a4bc1c863de21ffd6dc598f8\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,85,96,92,243,247,237,192,205,9,178,146,101,237,132,232,15,2,69,138,31,118,76,140,142,207,90,13,192,94,254,150,133],\"spend_pubkey\":[3,5,157,91,250,169,41,61,190,37,30,98,152,253,180,138,250,86,162,102,82,148,130,220,44,153,127,83,43,246,93,17,232],\"change_label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"labels\":[[\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",[2,237,237,247,213,154,87,34,239,168,235,87,122,152,94,41,35,101,184,201,58,201,6,185,58,157,52,208,129,167,2,224,198]]]}},\"outputs\":{\"wallet_fingerprint\":[203,200,4,248,139,36,241,232],\"birthday\":2146,\"last_scan\":2146,\"outputs\":{\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":\"Unspent\"}}}}";
|
||||
const ALICE_WALLET_CONFIRMATION: &str = "{\"tx_history\": [],\"client\":{\"network\":\"regtest\",\"label\":\"default\",\"scan_sk\":\"e3d8922a41a7cb1a84a90f4334e987bb5ea2df6a1fdf44f789b5302de119f9e2\",\"spend_key\":{\"Secret\":\"93292e5b21042c6cfc742ba30e9d2a1e01609b12d154a1825184ed12c7b9631b\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,104,242,105,185,6,124,208,34,44,149,52,163,38,63,221,150,12,198,24,95,143,126,235,37,149,233,88,118,32,86,233,152],\"spend_pubkey\":[3,198,82,196,243,12,59,126,109,143,144,157,128,176,168,94,54,134,232,139,115,102,11,178,128,244,239,251,40,228,67,153,72],\"change_label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"labels\":[[\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",[2,244,223,255,57,50,216,27,133,112,138,69,120,126,85,110,6,242,141,33,136,191,82,164,241,54,179,115,84,161,145,174,154]]]}},\"outputs\":{\"wallet_fingerprint\":[187,119,108,230,171,125,106,11],\"birthday\":1620,\"last_scan\":2146,\"outputs\":{\"9a4a67cc5a40bf882d8b300d91024d7c97024b3b68b2df7745a5b9ea1df1888c:1\":{\"blockheight\":1620,\"tweak\":\"b8b63b3ed97d297b744135cfac2fb4a344c881a77543b71f1fcd16bc67514f26\",\"amount\":3938643,\"script\":\"51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"148e0faa2f203b6e9488e2da696d8a49ebff4212946672f0bb072ced0909360d\"}},\"148e0faa2f203b6e9488e2da696d8a49ebff4212946672f0bb072ced0909360d:1\":{\"blockheight\":0,\"tweak\":\"28994b2f2ee8e5f35d6d2dcdee1580d0455fe3dc37f81e0a36864473ee86f5c4\",\"amount\":3937246,\"script\":\"51207d06144e982b6fd38a85d6152f1f95746b059553258a31e04df97fe6b5f19ea1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":\"Unspent\"}}}}";
|
||||
const BOB_WALLET_CONFIRMATION: &str = "{\"tx_history\": [],\"client\":{\"network\":\"regtest\",\"label\":\"default\",\"scan_sk\":\"0de90b7195c1380d5fde13de3f1d66d53423a9896314839e36ba672653af60b4\",\"spend_key\":{\"Secret\":\"affe686075ecbe17b8ce7de45ec31314804259d0a4bc1c863de21ffd6dc598f8\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,85,96,92,243,247,237,192,205,9,178,146,101,237,132,232,15,2,69,138,31,118,76,140,142,207,90,13,192,94,254,150,133],\"spend_pubkey\":[3,5,157,91,250,169,41,61,190,37,30,98,152,253,180,138,250,86,162,102,82,148,130,220,44,153,127,83,43,246,93,17,232],\"change_label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"labels\":[[\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",[2,237,237,247,213,154,87,34,239,168,235,87,122,152,94,41,35,101,184,201,58,201,6,185,58,157,52,208,129,167,2,224,198]]]}},\"outputs\":{\"wallet_fingerprint\":[203,200,4,248,139,36,241,232],\"birthday\":2146,\"last_scan\":2146,\"outputs\":{\"148e0faa2f203b6e9488e2da696d8a49ebff4212946672f0bb072ced0909360d:0\":{\"blockheight\":0,\"tweak\":\"0e3395ff27bde9187ffaeeb2521f6277d3b83911f16ccbaf59a1a68d99a0ab93\",\"amount\":1200,\"script\":\"512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c\",\"label\":null,\"spend_status\":\"Unspent\"},\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":\"Unspent\"}}}}";
|
||||
const ALICE_WALLET_ANSWER: &str = "{\"tx_history\": [],\"client\":{\"network\":\"regtest\",\"label\":\"default\",\"scan_sk\":\"e3d8922a41a7cb1a84a90f4334e987bb5ea2df6a1fdf44f789b5302de119f9e2\",\"spend_key\":{\"Secret\":\"93292e5b21042c6cfc742ba30e9d2a1e01609b12d154a1825184ed12c7b9631b\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,104,242,105,185,6,124,208,34,44,149,52,163,38,63,221,150,12,198,24,95,143,126,235,37,149,233,88,118,32,86,233,152],\"spend_pubkey\":[3,198,82,196,243,12,59,126,109,143,144,157,128,176,168,94,54,134,232,139,115,102,11,178,128,244,239,251,40,228,67,153,72],\"change_label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"labels\":[[\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",[2,244,223,255,57,50,216,27,133,112,138,69,120,126,85,110,6,242,141,33,136,191,82,164,241,54,179,115,84,161,145,174,154]]]}},\"outputs\":{\"wallet_fingerprint\":[187,119,108,230,171,125,106,11],\"birthday\":1620,\"last_scan\":2146,\"outputs\":{\"148e0faa2f203b6e9488e2da696d8a49ebff4212946672f0bb072ced0909360d:1\":{\"blockheight\":0,\"tweak\":\"28994b2f2ee8e5f35d6d2dcdee1580d0455fe3dc37f81e0a36864473ee86f5c4\",\"amount\":3937246,\"script\":\"51207d06144e982b6fd38a85d6152f1f95746b059553258a31e04df97fe6b5f19ea1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":\"Unspent\"},\"bc207c02bc4f1d4359fcd604296c0938bf1e6ff827662a56410676b8cbd768d9:0\":{\"blockheight\":0,\"tweak\":\"3bf77beab9c053e1ed974288d5d246962376d2badddc623af1f2ef7af57067b7\",\"amount\":1046,\"script\":\"5120646bdb98d89a2573acc6064a5c806d00e34beb65588c91a32733b62255b4dafa\",\"label\":null,\"spend_status\":\"Unspent\"},\"9a4a67cc5a40bf882d8b300d91024d7c97024b3b68b2df7745a5b9ea1df1888c:1\":{\"blockheight\":1620,\"tweak\":\"b8b63b3ed97d297b744135cfac2fb4a344c881a77543b71f1fcd16bc67514f26\",\"amount\":3938643,\"script\":\"51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"148e0faa2f203b6e9488e2da696d8a49ebff4212946672f0bb072ced0909360d\"}}}}}";
|
||||
const BOB_WALLET_ANSWER: &str = "{\"tx_history\": [],\"client\":{\"network\":\"regtest\",\"label\":\"default\",\"scan_sk\":\"0de90b7195c1380d5fde13de3f1d66d53423a9896314839e36ba672653af60b4\",\"spend_key\":{\"Secret\":\"affe686075ecbe17b8ce7de45ec31314804259d0a4bc1c863de21ffd6dc598f8\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,85,96,92,243,247,237,192,205,9,178,146,101,237,132,232,15,2,69,138,31,118,76,140,142,207,90,13,192,94,254,150,133],\"spend_pubkey\":[3,5,157,91,250,169,41,61,190,37,30,98,152,253,180,138,250,86,162,102,82,148,130,220,44,153,127,83,43,246,93,17,232],\"change_label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"labels\":[[\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",[2,237,237,247,213,154,87,34,239,168,235,87,122,152,94,41,35,101,184,201,58,201,6,185,58,157,52,208,129,167,2,224,198]]]}},\"outputs\":{\"wallet_fingerprint\":[203,200,4,248,139,36,241,232],\"birthday\":2146,\"last_scan\":2146,\"outputs\":{\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":\"Unspent\"},\"148e0faa2f203b6e9488e2da696d8a49ebff4212946672f0bb072ced0909360d:0\":{\"blockheight\":0,\"tweak\":\"0e3395ff27bde9187ffaeeb2521f6277d3b83911f16ccbaf59a1a68d99a0ab93\",\"amount\":1200,\"script\":\"512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c\",\"label\":null,\"spend_status\":{\"Spent\":\"bc207c02bc4f1d4359fcd604296c0938bf1e6ff827662a56410676b8cbd768d9\"}}}}}";
|
||||
const ALICE_ADDRESS: &str = "sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q";
|
||||
const BOB_ADDRESS: &str = "sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u";
|
||||
const COMMITMENT: &str = "e4395114bdb1276bbcf3b0b6ef1c970a213f689b2bf8524e08599a1a65c146e7";
|
||||
const FEE_RATE: Amount = Amount::from_sat(1);
|
||||
// const ALICE_WALLET: &str = "{\"tx_history\": [],\"client\":{\"network\":\"regtest\",\"label\":\"default\",\"scan_sk\":\"e3d8922a41a7cb1a84a90f4334e987bb5ea2df6a1fdf44f789b5302de119f9e2\",\"spend_key\":{\"Secret\":\"93292e5b21042c6cfc742ba30e9d2a1e01609b12d154a1825184ed12c7b9631b\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,104,242,105,185,6,124,208,34,44,149,52,163,38,63,221,150,12,198,24,95,143,126,235,37,149,233,88,118,32,86,233,152],\"spend_pubkey\":[3,198,82,196,243,12,59,126,109,143,144,157,128,176,168,94,54,134,232,139,115,102,11,178,128,244,239,251,40,228,67,153,72],\"change_label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"labels\":[[\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",[2,244,223,255,57,50,216,27,133,112,138,69,120,126,85,110,6,242,141,33,136,191,82,164,241,54,179,115,84,161,145,174,154]]]}},\"outputs\":{\"wallet_fingerprint\":[187,119,108,230,171,125,106,11],\"birthday\":1620,\"last_scan\":2146,\"outputs\":{\"9a4a67cc5a40bf882d8b300d91024d7c97024b3b68b2df7745a5b9ea1df1888c:1\":{\"blockheight\":1620,\"tweak\":\"b8b63b3ed97d297b744135cfac2fb4a344c881a77543b71f1fcd16bc67514f26\",\"amount\":3938643,\"script\":\"51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":\"Unspent\"}}}}";
|
||||
// const BOB_WALLET: &str = "{\"tx_history\": [],\"client\":{\"network\":\"regtest\",\"label\":\"default\",\"scan_sk\":\"0de90b7195c1380d5fde13de3f1d66d53423a9896314839e36ba672653af60b4\",\"spend_key\":{\"Secret\":\"affe686075ecbe17b8ce7de45ec31314804259d0a4bc1c863de21ffd6dc598f8\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,85,96,92,243,247,237,192,205,9,178,146,101,237,132,232,15,2,69,138,31,118,76,140,142,207,90,13,192,94,254,150,133],\"spend_pubkey\":[3,5,157,91,250,169,41,61,190,37,30,98,152,253,180,138,250,86,162,102,82,148,130,220,44,153,127,83,43,246,93,17,232],\"change_label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"labels\":[[\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",[2,237,237,247,213,154,87,34,239,168,235,87,122,152,94,41,35,101,184,201,58,201,6,185,58,157,52,208,129,167,2,224,198]]]}},\"outputs\":{\"wallet_fingerprint\":[203,200,4,248,139,36,241,232],\"birthday\":2146,\"last_scan\":2146,\"outputs\":{\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":\"Unspent\"}}}}";
|
||||
// const ALICE_WALLET_CONFIRMATION: &str = "{\"tx_history\": [],\"client\":{\"network\":\"regtest\",\"label\":\"default\",\"scan_sk\":\"e3d8922a41a7cb1a84a90f4334e987bb5ea2df6a1fdf44f789b5302de119f9e2\",\"spend_key\":{\"Secret\":\"93292e5b21042c6cfc742ba30e9d2a1e01609b12d154a1825184ed12c7b9631b\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,104,242,105,185,6,124,208,34,44,149,52,163,38,63,221,150,12,198,24,95,143,126,235,37,149,233,88,118,32,86,233,152],\"spend_pubkey\":[3,198,82,196,243,12,59,126,109,143,144,157,128,176,168,94,54,134,232,139,115,102,11,178,128,244,239,251,40,228,67,153,72],\"change_label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"labels\":[[\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",[2,244,223,255,57,50,216,27,133,112,138,69,120,126,85,110,6,242,141,33,136,191,82,164,241,54,179,115,84,161,145,174,154]]]}},\"outputs\":{\"wallet_fingerprint\":[187,119,108,230,171,125,106,11],\"birthday\":1620,\"last_scan\":2146,\"outputs\":{\"9a4a67cc5a40bf882d8b300d91024d7c97024b3b68b2df7745a5b9ea1df1888c:1\":{\"blockheight\":1620,\"tweak\":\"b8b63b3ed97d297b744135cfac2fb4a344c881a77543b71f1fcd16bc67514f26\",\"amount\":3938643,\"script\":\"51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"148e0faa2f203b6e9488e2da696d8a49ebff4212946672f0bb072ced0909360d\"}},\"148e0faa2f203b6e9488e2da696d8a49ebff4212946672f0bb072ced0909360d:1\":{\"blockheight\":0,\"tweak\":\"28994b2f2ee8e5f35d6d2dcdee1580d0455fe3dc37f81e0a36864473ee86f5c4\",\"amount\":3937246,\"script\":\"51207d06144e982b6fd38a85d6152f1f95746b059553258a31e04df97fe6b5f19ea1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":\"Unspent\"}}}}";
|
||||
// const BOB_WALLET_CONFIRMATION: &str = "{\"tx_history\": [],\"client\":{\"network\":\"regtest\",\"label\":\"default\",\"scan_sk\":\"0de90b7195c1380d5fde13de3f1d66d53423a9896314839e36ba672653af60b4\",\"spend_key\":{\"Secret\":\"affe686075ecbe17b8ce7de45ec31314804259d0a4bc1c863de21ffd6dc598f8\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,85,96,92,243,247,237,192,205,9,178,146,101,237,132,232,15,2,69,138,31,118,76,140,142,207,90,13,192,94,254,150,133],\"spend_pubkey\":[3,5,157,91,250,169,41,61,190,37,30,98,152,253,180,138,250,86,162,102,82,148,130,220,44,153,127,83,43,246,93,17,232],\"change_label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"labels\":[[\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",[2,237,237,247,213,154,87,34,239,168,235,87,122,152,94,41,35,101,184,201,58,201,6,185,58,157,52,208,129,167,2,224,198]]]}},\"outputs\":{\"wallet_fingerprint\":[203,200,4,248,139,36,241,232],\"birthday\":2146,\"last_scan\":2146,\"outputs\":{\"148e0faa2f203b6e9488e2da696d8a49ebff4212946672f0bb072ced0909360d:0\":{\"blockheight\":0,\"tweak\":\"0e3395ff27bde9187ffaeeb2521f6277d3b83911f16ccbaf59a1a68d99a0ab93\",\"amount\":1200,\"script\":\"512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c\",\"label\":null,\"spend_status\":\"Unspent\"},\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":\"Unspent\"}}}}";
|
||||
// const ALICE_WALLET_ANSWER: &str = "{\"tx_history\": [],\"client\":{\"network\":\"regtest\",\"label\":\"default\",\"scan_sk\":\"e3d8922a41a7cb1a84a90f4334e987bb5ea2df6a1fdf44f789b5302de119f9e2\",\"spend_key\":{\"Secret\":\"93292e5b21042c6cfc742ba30e9d2a1e01609b12d154a1825184ed12c7b9631b\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,104,242,105,185,6,124,208,34,44,149,52,163,38,63,221,150,12,198,24,95,143,126,235,37,149,233,88,118,32,86,233,152],\"spend_pubkey\":[3,198,82,196,243,12,59,126,109,143,144,157,128,176,168,94,54,134,232,139,115,102,11,178,128,244,239,251,40,228,67,153,72],\"change_label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"labels\":[[\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",[2,244,223,255,57,50,216,27,133,112,138,69,120,126,85,110,6,242,141,33,136,191,82,164,241,54,179,115,84,161,145,174,154]]]}},\"outputs\":{\"wallet_fingerprint\":[187,119,108,230,171,125,106,11],\"birthday\":1620,\"last_scan\":2146,\"outputs\":{\"148e0faa2f203b6e9488e2da696d8a49ebff4212946672f0bb072ced0909360d:1\":{\"blockheight\":0,\"tweak\":\"28994b2f2ee8e5f35d6d2dcdee1580d0455fe3dc37f81e0a36864473ee86f5c4\",\"amount\":3937246,\"script\":\"51207d06144e982b6fd38a85d6152f1f95746b059553258a31e04df97fe6b5f19ea1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":\"Unspent\"},\"bc207c02bc4f1d4359fcd604296c0938bf1e6ff827662a56410676b8cbd768d9:0\":{\"blockheight\":0,\"tweak\":\"3bf77beab9c053e1ed974288d5d246962376d2badddc623af1f2ef7af57067b7\",\"amount\":1046,\"script\":\"5120646bdb98d89a2573acc6064a5c806d00e34beb65588c91a32733b62255b4dafa\",\"label\":null,\"spend_status\":\"Unspent\"},\"9a4a67cc5a40bf882d8b300d91024d7c97024b3b68b2df7745a5b9ea1df1888c:1\":{\"blockheight\":1620,\"tweak\":\"b8b63b3ed97d297b744135cfac2fb4a344c881a77543b71f1fcd16bc67514f26\",\"amount\":3938643,\"script\":\"51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"148e0faa2f203b6e9488e2da696d8a49ebff4212946672f0bb072ced0909360d\"}}}}}";
|
||||
// const BOB_WALLET_ANSWER: &str = "{\"tx_history\": [],\"client\":{\"network\":\"regtest\",\"label\":\"default\",\"scan_sk\":\"0de90b7195c1380d5fde13de3f1d66d53423a9896314839e36ba672653af60b4\",\"spend_key\":{\"Secret\":\"affe686075ecbe17b8ce7de45ec31314804259d0a4bc1c863de21ffd6dc598f8\"},\"mnemonic\":null,\"sp_receiver\":{\"version\":0,\"network\":\"Regtest\",\"scan_pubkey\":[2,85,96,92,243,247,237,192,205,9,178,146,101,237,132,232,15,2,69,138,31,118,76,140,142,207,90,13,192,94,254,150,133],\"spend_pubkey\":[3,5,157,91,250,169,41,61,190,37,30,98,152,253,180,138,250,86,162,102,82,148,130,220,44,153,127,83,43,246,93,17,232],\"change_label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"labels\":[[\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",[2,237,237,247,213,154,87,34,239,168,235,87,122,152,94,41,35,101,184,201,58,201,6,185,58,157,52,208,129,167,2,224,198]]]}},\"outputs\":{\"wallet_fingerprint\":[203,200,4,248,139,36,241,232],\"birthday\":2146,\"last_scan\":2146,\"outputs\":{\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":\"Unspent\"},\"148e0faa2f203b6e9488e2da696d8a49ebff4212946672f0bb072ced0909360d:0\":{\"blockheight\":0,\"tweak\":\"0e3395ff27bde9187ffaeeb2521f6277d3b83911f16ccbaf59a1a68d99a0ab93\",\"amount\":1200,\"script\":\"512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c\",\"label\":null,\"spend_status\":{\"Spent\":\"bc207c02bc4f1d4359fcd604296c0938bf1e6ff827662a56410676b8cbd768d9\"}}}}}";
|
||||
// const ALICE_ADDRESS: &str = "sprt1qqf50y6deqe7dqg3vj562xf3lmktqe3sct78ha6e9jh54sa3q2m5esq7x2tz0xrpm0ekclyyaszc2sh3ksm5gkumxpwegpa80lv5wgsuefqaufx8q";
|
||||
// const BOB_ADDRESS: &str = "sprt1qqf2kqh8n7lkupngfk2fxtmvyaq8sy3v2ramyeryweadqmsz7l6tg2qc9n4dl42ff8klz28nznr7mfzh6263xv555stwzextl2v4lvhg3aqq7ru8u";
|
||||
// const FEE_RATE: Amount = Amount::from_sat(1);
|
||||
// const KEY: &str = "442a5ea418921c4aa8ce3f7a95427d9450059a3ac11db3dced9abb709b2d9f42";
|
||||
// const INITIAL_COMMIT_TX: &str = "bc7d2ef1820cf67edb900e4c59cae6c692663cd690e691a55df919f002ede841";
|
||||
|
||||
fn helper_get_tweak_data(tx: &Transaction, spk: ScriptBuf) -> PublicKey {
|
||||
let prevout = tx.input.get(0).unwrap().to_owned();
|
||||
let outpoint_data = (
|
||||
prevout.previous_output.txid.to_string(),
|
||||
prevout.previous_output.vout,
|
||||
);
|
||||
let input_pubkey =
|
||||
get_pubkey_from_input(&vec![], &prevout.witness.to_vec(), spk.as_bytes()).unwrap();
|
||||
let tweak_data =
|
||||
calculate_tweak_data(&vec![&input_pubkey.unwrap()], &vec![outpoint_data]).unwrap();
|
||||
tweak_data
|
||||
}
|
||||
// fn helper_get_tweak_data(tx: &Transaction, spk: ScriptBuf) -> PublicKey {
|
||||
// let prevout = tx.input.get(0).unwrap().to_owned();
|
||||
// let outpoint_data = (
|
||||
// prevout.previous_output.txid.to_string(),
|
||||
// prevout.previous_output.vout,
|
||||
// );
|
||||
// let input_pubkey =
|
||||
// get_pubkey_from_input(&vec![], &prevout.witness.to_vec(), spk.as_bytes()).unwrap();
|
||||
// let tweak_data =
|
||||
// calculate_tweak_data(&vec![&input_pubkey.unwrap()], &vec![outpoint_data]).unwrap();
|
||||
// tweak_data
|
||||
// }
|
||||
|
||||
fn helper_create_commitment(payload_to_hash: String) -> String {
|
||||
let mut engine = sha256::HashEngine::default();
|
||||
engine.write_all(&payload_to_hash.as_bytes());
|
||||
let hash = sha256::Hash::from_engine(engine);
|
||||
hash.to_byte_array().to_lower_hex_string()
|
||||
}
|
||||
// fn helper_create_commitment(payload_to_hash: String) -> sha256::Hash {
|
||||
// let mut engine = sha256::HashEngine::default();
|
||||
// engine.write_all(&payload_to_hash.as_bytes()).unwrap();
|
||||
// let hash = sha256::Hash::from_engine(engine);
|
||||
// hash
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn it_creates_notification_transaction() {
|
||||
let recipient = Recipient {
|
||||
address: BOB_ADDRESS.to_owned(),
|
||||
amount: Amount::from_sat(1200),
|
||||
nb_outputs: 1,
|
||||
};
|
||||
let mut alice_wallet: SpWallet = serde_json::from_str(ALICE_WALLET).unwrap();
|
||||
let mut bob_wallet: SpWallet = serde_json::from_str(BOB_WALLET).unwrap();
|
||||
let message: CipherMessage = CipherMessage::new(ALICE_ADDRESS.to_owned(), "TEST".to_owned());
|
||||
let commitment = helper_create_commitment(serde_json::to_string(&message).unwrap());
|
||||
// #[test]
|
||||
// fn it_creates_notification_transaction() {
|
||||
// let recipient = Recipient {
|
||||
// address: BOB_ADDRESS.to_owned(),
|
||||
// amount: Amount::from_sat(1200),
|
||||
// nb_outputs: 1,
|
||||
// };
|
||||
// let mut alice_wallet: SpWallet = serde_json::from_str(ALICE_WALLET).unwrap();
|
||||
// let mut bob_wallet: SpWallet = serde_json::from_str(BOB_WALLET).unwrap();
|
||||
// let pcd = Pcd::new(Value::String("TEST".to_owned()));
|
||||
// let pcd_hash = helper_create_commitment(pcd.to_string());
|
||||
// let mut key = [0u8; 32];
|
||||
// key.copy_from_slice(&Vec::from_hex(KEY).unwrap());
|
||||
|
||||
assert!(commitment == "d12f3c5b37240bc3abf2976f41fdf9a594f0680aafd2781ac448f80440fbeb99");
|
||||
// let alice_member = Member::new(
|
||||
// "alice".to_owned(),
|
||||
// alice_wallet.get_client().get_receiving_address().try_into().unwrap(),
|
||||
// alice_wallet.get_client().sp_receiver.get_change_address().try_into().unwrap(),
|
||||
// Role::Admin,
|
||||
// );
|
||||
// let bob_member = Member::new(
|
||||
// "bob".to_owned(),
|
||||
// bob_wallet.get_client().get_receiving_address().try_into().unwrap(),
|
||||
// bob_wallet.get_client().sp_receiver.get_change_address().try_into().unwrap(),
|
||||
// Role::User,
|
||||
// );
|
||||
|
||||
let psbt = create_transaction(
|
||||
&vec![],
|
||||
&HashSet::new(),
|
||||
&alice_wallet,
|
||||
vec![recipient],
|
||||
Some(Vec::from_hex(COMMITMENT).unwrap()),
|
||||
FEE_RATE,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
// let validation_rules = ValidationRules::new(0.5, Role::User);
|
||||
|
||||
let final_tx = psbt.extract_tx().unwrap();
|
||||
let spk = "51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1";
|
||||
// let pcd_template = serde_json::json!({
|
||||
// "int": 0,
|
||||
// "string": "exemple_data",
|
||||
// "array": [
|
||||
// "element1",
|
||||
// "element2"
|
||||
// ]
|
||||
// });
|
||||
|
||||
let tweak_data = helper_get_tweak_data(&final_tx, ScriptBuf::from_hex(spk).unwrap());
|
||||
// let process = Process::new(
|
||||
// "default".to_owned(),
|
||||
// vec![alice_member, bob_member],
|
||||
// validation_rules,
|
||||
// Txid::from_str(INITIAL_COMMIT_TX).unwrap(),
|
||||
// "".to_owned(),
|
||||
// "".to_owned(),
|
||||
// "".to_owned(),
|
||||
// pcd_template,
|
||||
// );
|
||||
|
||||
// Check that Alice and Bob are both able to find that transaction
|
||||
let alice_update = alice_wallet
|
||||
.update_wallet_with_transaction(&final_tx, 0, tweak_data)
|
||||
.unwrap();
|
||||
assert!(alice_update.len() > 0);
|
||||
let bob_update = bob_wallet
|
||||
.update_wallet_with_transaction(&final_tx, 0, tweak_data)
|
||||
.unwrap();
|
||||
assert!(bob_update.len() > 0);
|
||||
println!("{:?}", alice_wallet.get_outputs().to_outpoints_list());
|
||||
println!("{:?}", bob_wallet.get_outputs().to_outpoints_list());
|
||||
}
|
||||
// let prd = Prd::new(
|
||||
// PrdType::Update,
|
||||
// process,
|
||||
// ALICE_ADDRESS.try_into().unwrap(),
|
||||
// key,
|
||||
// pcd_hash,
|
||||
// ).unwrap();
|
||||
// let commitment = helper_create_commitment(serde_json::to_string(&prd).unwrap());
|
||||
|
||||
#[test]
|
||||
fn it_creates_confirmation_transaction() {
|
||||
let mut alice_wallet: SpWallet = serde_json::from_str(ALICE_WALLET_CONFIRMATION).unwrap();
|
||||
let mut bob_wallet: SpWallet = serde_json::from_str(BOB_WALLET_CONFIRMATION).unwrap();
|
||||
// let psbt = create_transaction(
|
||||
// &vec![],
|
||||
// &HashSet::new(),
|
||||
// &alice_wallet,
|
||||
// vec![recipient],
|
||||
// Some(commitment.as_byte_array().to_vec()),
|
||||
// FEE_RATE,
|
||||
// None,
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// Bob must spend notification output
|
||||
let (confirmation_outpoint, _) = bob_wallet
|
||||
.get_outputs()
|
||||
.get_outpoint(
|
||||
OutPoint::from_str(
|
||||
"148e0faa2f203b6e9488e2da696d8a49ebff4212946672f0bb072ced0909360d:0",
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
// let final_tx = psbt.extract_tx().unwrap();
|
||||
// let spk = "51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1";
|
||||
|
||||
let recipient = Recipient {
|
||||
address: ALICE_ADDRESS.to_owned(),
|
||||
amount: Amount::from_sat(0),
|
||||
nb_outputs: 1,
|
||||
};
|
||||
// let tweak_data = helper_get_tweak_data(&final_tx, ScriptBuf::from_hex(spk).unwrap());
|
||||
|
||||
let psbt = create_transaction(
|
||||
&vec![&confirmation_outpoint],
|
||||
&HashSet::new(),
|
||||
&bob_wallet,
|
||||
vec![recipient],
|
||||
None,
|
||||
FEE_RATE,
|
||||
Some(ALICE_ADDRESS.to_owned()),
|
||||
)
|
||||
.unwrap();
|
||||
// // Check that Alice and Bob are both able to find that transaction
|
||||
// let alice_update = alice_wallet
|
||||
// .update_wallet_with_transaction(&final_tx, 0, tweak_data)
|
||||
// .unwrap();
|
||||
// assert!(alice_update.len() > 0);
|
||||
// let bob_update = bob_wallet
|
||||
// .update_wallet_with_transaction(&final_tx, 0, tweak_data)
|
||||
// .unwrap();
|
||||
// assert!(bob_update.len() > 0);
|
||||
// println!("{:?}", alice_wallet.get_outputs().to_outpoints_list());
|
||||
// println!("{:?}", bob_wallet.get_outputs().to_outpoints_list());
|
||||
// }
|
||||
|
||||
let final_tx = psbt.extract_tx().unwrap();
|
||||
// println!(
|
||||
// "{}",
|
||||
// serialize::<Transaction>(&final_tx).to_lower_hex_string()
|
||||
// );
|
||||
let spk = "512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c";
|
||||
// #[test]
|
||||
// fn it_creates_confirmation_transaction() {
|
||||
// let mut alice_wallet: SpWallet = serde_json::from_str(ALICE_WALLET_CONFIRMATION).unwrap();
|
||||
// let mut bob_wallet: SpWallet = serde_json::from_str(BOB_WALLET_CONFIRMATION).unwrap();
|
||||
|
||||
let tweak_data = helper_get_tweak_data(&final_tx, ScriptBuf::from_hex(spk).unwrap());
|
||||
// // Bob must spend notification output
|
||||
// let (confirmation_outpoint, _) = bob_wallet
|
||||
// .get_outputs()
|
||||
// .get_outpoint(
|
||||
// OutPoint::from_str(
|
||||
// "148e0faa2f203b6e9488e2da696d8a49ebff4212946672f0bb072ced0909360d:0",
|
||||
// )
|
||||
// .unwrap(),
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// Check that Alice and Bob are both able to find that transaction
|
||||
let alice_update = alice_wallet
|
||||
.update_wallet_with_transaction(&final_tx, 0, tweak_data)
|
||||
.unwrap();
|
||||
assert!(alice_update.len() > 0);
|
||||
let bob_update = bob_wallet
|
||||
.update_wallet_with_transaction(&final_tx, 0, tweak_data)
|
||||
.unwrap();
|
||||
assert!(bob_update.len() > 0);
|
||||
println!("{:?}", alice_wallet.get_outputs().to_outpoints_list());
|
||||
println!("{:?}", bob_wallet.get_outputs().to_outpoints_list());
|
||||
}
|
||||
// let recipient = Recipient {
|
||||
// address: ALICE_ADDRESS.to_owned(),
|
||||
// amount: Amount::from_sat(0),
|
||||
// nb_outputs: 1,
|
||||
// };
|
||||
|
||||
#[test]
|
||||
fn it_creates_answer_transaction() {
|
||||
let mut alice_wallet: SpWallet = serde_json::from_str(ALICE_WALLET_ANSWER).unwrap();
|
||||
let mut bob_wallet: SpWallet = serde_json::from_str(BOB_WALLET_ANSWER).unwrap();
|
||||
// let psbt = create_transaction(
|
||||
// &vec![&confirmation_outpoint],
|
||||
// &HashSet::new(),
|
||||
// &bob_wallet,
|
||||
// vec![recipient],
|
||||
// None,
|
||||
// FEE_RATE,
|
||||
// Some(ALICE_ADDRESS.to_owned()),
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// Bob must spend notification output
|
||||
let (confirmation_outpoint, _) = alice_wallet
|
||||
.get_outputs()
|
||||
.get_outpoint(
|
||||
OutPoint::from_str(
|
||||
"bc207c02bc4f1d4359fcd604296c0938bf1e6ff827662a56410676b8cbd768d9:0",
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
// let final_tx = psbt.extract_tx().unwrap();
|
||||
// // println!(
|
||||
// // "{}",
|
||||
// // serialize::<Transaction>(&final_tx).to_lower_hex_string()
|
||||
// // );
|
||||
// let spk = "512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c";
|
||||
|
||||
let recipient = Recipient {
|
||||
address: BOB_ADDRESS.to_owned(),
|
||||
amount: Amount::from_sat(0),
|
||||
nb_outputs: 1,
|
||||
};
|
||||
// let tweak_data = helper_get_tweak_data(&final_tx, ScriptBuf::from_hex(spk).unwrap());
|
||||
|
||||
let psbt = create_transaction(
|
||||
&vec![&confirmation_outpoint],
|
||||
&HashSet::new(),
|
||||
&alice_wallet,
|
||||
vec![recipient],
|
||||
None,
|
||||
FEE_RATE,
|
||||
Some(BOB_ADDRESS.to_owned()),
|
||||
)
|
||||
.unwrap();
|
||||
// // Check that Alice and Bob are both able to find that transaction
|
||||
// let alice_update = alice_wallet
|
||||
// .update_wallet_with_transaction(&final_tx, 0, tweak_data)
|
||||
// .unwrap();
|
||||
// assert!(alice_update.len() > 0);
|
||||
// let bob_update = bob_wallet
|
||||
// .update_wallet_with_transaction(&final_tx, 0, tweak_data)
|
||||
// .unwrap();
|
||||
// assert!(bob_update.len() > 0);
|
||||
// println!("{:?}", alice_wallet.get_outputs().to_outpoints_list());
|
||||
// println!("{:?}", bob_wallet.get_outputs().to_outpoints_list());
|
||||
// }
|
||||
|
||||
let final_tx = psbt.extract_tx().unwrap();
|
||||
// println!("{}", serialize::<Transaction>(&final_tx).to_lower_hex_string());
|
||||
let spk = "5120646bdb98d89a2573acc6064a5c806d00e34beb65588c91a32733b62255b4dafa";
|
||||
// #[test]
|
||||
// fn it_creates_answer_transaction() {
|
||||
// let mut alice_wallet: SpWallet = serde_json::from_str(ALICE_WALLET_ANSWER).unwrap();
|
||||
// let mut bob_wallet: SpWallet = serde_json::from_str(BOB_WALLET_ANSWER).unwrap();
|
||||
|
||||
let tweak_data = helper_get_tweak_data(&final_tx, ScriptBuf::from_hex(spk).unwrap());
|
||||
// // Bob must spend notification output
|
||||
// let (confirmation_outpoint, _) = alice_wallet
|
||||
// .get_outputs()
|
||||
// .get_outpoint(
|
||||
// OutPoint::from_str(
|
||||
// "bc207c02bc4f1d4359fcd604296c0938bf1e6ff827662a56410676b8cbd768d9:0",
|
||||
// )
|
||||
// .unwrap(),
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// Check that Alice and Bob are both able to find that transaction
|
||||
let alice_update = alice_wallet
|
||||
.update_wallet_with_transaction(&final_tx, 0, tweak_data)
|
||||
.unwrap();
|
||||
assert!(alice_update.len() > 0);
|
||||
let bob_update = bob_wallet
|
||||
.update_wallet_with_transaction(&final_tx, 0, tweak_data)
|
||||
.unwrap();
|
||||
assert!(bob_update.len() > 0);
|
||||
println!("{:?}", alice_wallet.get_outputs().to_outpoints_list());
|
||||
println!("{:?}", bob_wallet.get_outputs().to_outpoints_list());
|
||||
}
|
||||
}
|
||||
// let recipient = Recipient {
|
||||
// address: BOB_ADDRESS.to_owned(),
|
||||
// amount: Amount::from_sat(0),
|
||||
// nb_outputs: 1,
|
||||
// };
|
||||
|
||||
// let psbt = create_transaction(
|
||||
// &vec![&confirmation_outpoint],
|
||||
// &HashSet::new(),
|
||||
// &alice_wallet,
|
||||
// vec![recipient],
|
||||
// None,
|
||||
// FEE_RATE,
|
||||
// Some(BOB_ADDRESS.to_owned()),
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// let final_tx = psbt.extract_tx().unwrap();
|
||||
// // println!("{}", serialize::<Transaction>(&final_tx).to_lower_hex_string());
|
||||
// let spk = "5120646bdb98d89a2573acc6064a5c806d00e34beb65588c91a32733b62255b4dafa";
|
||||
|
||||
// let tweak_data = helper_get_tweak_data(&final_tx, ScriptBuf::from_hex(spk).unwrap());
|
||||
|
||||
// // Check that Alice and Bob are both able to find that transaction
|
||||
// let alice_update = alice_wallet
|
||||
// .update_wallet_with_transaction(&final_tx, 0, tweak_data)
|
||||
// .unwrap();
|
||||
// assert!(alice_update.len() > 0);
|
||||
// let bob_update = bob_wallet
|
||||
// .update_wallet_with_transaction(&final_tx, 0, tweak_data)
|
||||
// .unwrap();
|
||||
// assert!(bob_update.len() > 0);
|
||||
// println!("{:?}", alice_wallet.get_outputs().to_outpoints_list());
|
||||
// println!("{:?}", bob_wallet.get_outputs().to_outpoints_list());
|
||||
// }
|
||||
// }
|
||||
|
Loading…
x
Reference in New Issue
Block a user