Merge branch 'new_login' into dev
This commit is contained in:
commit
d871b326c8
@ -4,12 +4,14 @@ use anyhow::{Error, Result};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sp_client::{
|
use sp_client::{
|
||||||
bitcoin::{
|
bitcoin::{
|
||||||
hex::{DisplayHex, FromHex}, key::constants::SECRET_KEY_SIZE, Txid
|
hex::{DisplayHex, FromHex},
|
||||||
|
key::constants::SECRET_KEY_SIZE,
|
||||||
|
Txid,
|
||||||
},
|
},
|
||||||
silentpayments::{
|
silentpayments::{
|
||||||
bitcoin_hashes::{sha256t_hash_newtype, Hash, HashEngine},
|
bitcoin_hashes::{sha256t_hash_newtype, Hash, HashEngine},
|
||||||
|
secp256k1::PublicKey,
|
||||||
utils::SilentPaymentAddress,
|
utils::SilentPaymentAddress,
|
||||||
secp256k1::PublicKey
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tsify::Tsify;
|
use tsify::Tsify;
|
||||||
@ -41,15 +43,17 @@ pub struct AnkSharedSecret {
|
|||||||
|
|
||||||
impl AnkSharedSecret {
|
impl AnkSharedSecret {
|
||||||
pub fn new(shared_point: PublicKey) -> Self {
|
pub fn new(shared_point: PublicKey) -> Self {
|
||||||
let mut shared_point_bin = [0u8;64];
|
let mut shared_point_bin = [0u8; 64];
|
||||||
shared_point_bin.copy_from_slice(&shared_point.serialize_uncompressed()[1..]);
|
shared_point_bin.copy_from_slice(&shared_point.serialize_uncompressed()[1..]);
|
||||||
let secret = AnkSharedSecretHash::from_shared_point(shared_point_bin).to_byte_array();
|
let secret = AnkSharedSecretHash::from_shared_point(shared_point_bin).to_byte_array();
|
||||||
Self { secret: secret.to_lower_hex_string() }
|
Self {
|
||||||
|
secret: secret.to_lower_hex_string(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_byte_array(&self) -> [u8; 32] {
|
pub fn to_byte_array(&self) -> [u8; 32] {
|
||||||
let bytes = Vec::from_hex(&self.secret).unwrap();
|
let bytes = Vec::from_hex(&self.secret).unwrap();
|
||||||
let mut buf = [0u8;32];
|
let mut buf = [0u8; 32];
|
||||||
buf.copy_from_slice(&bytes);
|
buf.copy_from_slice(&bytes);
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
@ -113,11 +117,7 @@ pub struct Aes256Decryption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Aes256Decryption {
|
impl Aes256Decryption {
|
||||||
pub fn new(
|
pub fn new(purpose: Purpose, cipher_text: CipherText, aes_key: [u8; 32]) -> Result<Self> {
|
||||||
purpose: Purpose,
|
|
||||||
cipher_text: CipherText,
|
|
||||||
aes_key: [u8;32],
|
|
||||||
) -> Result<Self> {
|
|
||||||
if cipher_text.len() <= 12 {
|
if cipher_text.len() <= 12 {
|
||||||
return Err(Error::msg("cipher_text is shorter than nonce length"));
|
return Err(Error::msg("cipher_text is shorter than nonce length"));
|
||||||
}
|
}
|
||||||
@ -254,7 +254,7 @@ impl Aes256Encryption {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn export_key(&self) -> [u8;32] {
|
pub fn export_key(&self) -> [u8; 32] {
|
||||||
self.aes_key
|
self.aes_key
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,8 +376,7 @@ mod tests {
|
|||||||
let mut plain_key = [0u8; 32];
|
let mut plain_key = [0u8; 32];
|
||||||
plain_key.copy_from_slice(&aes_key.to_vec());
|
plain_key.copy_from_slice(&aes_key.to_vec());
|
||||||
|
|
||||||
let aes_dec =
|
let aes_dec = Aes256Decryption::new(Purpose::Login, cipher.unwrap(), plain_key);
|
||||||
Aes256Decryption::new(Purpose::Login, cipher.unwrap(), plain_key);
|
|
||||||
|
|
||||||
assert!(aes_dec.is_ok());
|
assert!(aes_dec.is_ok());
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use std::fmt;
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
pub use sp_client;
|
pub use sp_client;
|
||||||
|
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
|
pub mod error;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod silentpayments;
|
pub mod silentpayments;
|
||||||
pub mod error;
|
|
||||||
|
127
src/network.rs
127
src/network.rs
@ -1,9 +1,16 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use aes_gcm::Aes256Gcm;
|
||||||
use anyhow::{Error, Result};
|
use anyhow::{Error, Result};
|
||||||
use js_sys::Date;
|
use js_sys::Date;
|
||||||
use rand::{thread_rng, RngCore};
|
use rand::{thread_rng, RngCore};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sp_client::bitcoin::consensus::serialize;
|
||||||
|
use sp_client::bitcoin::hashes::Hash;
|
||||||
use sp_client::bitcoin::hex::{DisplayHex, FromHex};
|
use sp_client::bitcoin::hex::{DisplayHex, FromHex};
|
||||||
use sp_client::bitcoin::OutPoint;
|
use sp_client::bitcoin::secp256k1::PublicKey;
|
||||||
|
use sp_client::bitcoin::{BlockHash, OutPoint, Transaction};
|
||||||
|
use sp_client::silentpayments::utils::SilentPaymentAddress;
|
||||||
use tsify::Tsify;
|
use tsify::Tsify;
|
||||||
|
|
||||||
use crate::crypto::{Aes256Decryption, Purpose};
|
use crate::crypto::{Aes256Decryption, Purpose};
|
||||||
@ -65,9 +72,13 @@ pub struct FaucetMessage {
|
|||||||
|
|
||||||
impl FaucetMessage {
|
impl FaucetMessage {
|
||||||
pub fn new(sp_address: String) -> Self {
|
pub fn new(sp_address: String) -> Self {
|
||||||
let mut buf = [0u8;64];
|
let mut buf = [0u8; 64];
|
||||||
thread_rng().fill_bytes(&mut buf);
|
thread_rng().fill_bytes(&mut buf);
|
||||||
Self { sp_address, commitment: buf.to_lower_hex_string(), error: None }
|
Self {
|
||||||
|
sp_address,
|
||||||
|
commitment: buf.to_lower_hex_string(),
|
||||||
|
error: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +116,10 @@ impl CipherMessage {
|
|||||||
error: None,
|
error: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_string(&self) -> String {
|
||||||
|
serde_json::to_string(self).unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Tsify)]
|
#[derive(Debug, Serialize, Deserialize, Tsify)]
|
||||||
@ -121,23 +136,32 @@ impl AnkNetworkMsg {
|
|||||||
content: raw.into(),
|
content: raw.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_string(json: &str) -> Result<Self> {
|
||||||
|
let res: Self = serde_json::from_str(json)?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_string(&self) -> String {
|
||||||
|
serde_json::to_string(self).unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Tsify, Clone)]
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Tsify, Clone)]
|
||||||
pub enum CachedMessageStatus {
|
pub enum CachedMessageStatus {
|
||||||
#[default]
|
#[default]
|
||||||
NoStatus,
|
NoStatus,
|
||||||
FaucetWaiting,
|
FaucetWaiting,
|
||||||
FaucetComplete,
|
|
||||||
CipherWaitingTx,
|
CipherWaitingTx,
|
||||||
TxWaitingCipher,
|
TxWaitingCipher,
|
||||||
SentWaitingConfirmation, // we're sender and wait for commited_in to be spent
|
SentWaitingConfirmation, // we're sender and wait for commited_in to be spent
|
||||||
ReceivedMustConfirm, // we're receiver and we spend commited_in to sender
|
ReceivedMustConfirm, // we're receiver and we spend commited_in to sender
|
||||||
MustSpendConfirmation, // we're sender and we must spend confirmed_by
|
MustSpendConfirmation, // we're sender and we must spend confirmed_by
|
||||||
Complete,
|
Trusted, // Can receive more messages
|
||||||
|
Closed, // No more messages will be trusted from this handshake
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unique struct for both 3nk messages and notification/key exchange, both rust and ts
|
/// Unique struct for both 4nk messages and notification/key exchange, both rust and ts
|
||||||
/// 0. Faucet: commited_in with nothing else, status is NoStatus
|
/// 0. Faucet: commited_in with nothing else, status is NoStatus
|
||||||
/// 1. notification:
|
/// 1. notification:
|
||||||
/// 0. sender: ciphertext, plaintext, commited_in, sender, recipient, shared_secret, key
|
/// 0. sender: ciphertext, plaintext, commited_in, sender, recipient, shared_secret, key
|
||||||
@ -155,14 +179,15 @@ pub enum CachedMessageStatus {
|
|||||||
pub struct CachedMessage {
|
pub struct CachedMessage {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
pub status: CachedMessageStatus,
|
pub status: CachedMessageStatus,
|
||||||
pub ciphertext: Option<String>, // When we receive message we can't decrypt we only have this and commited_in_tx
|
pub ciphertext: Option<String>, // Needed when we are waiting for a key in transaction, discarded after
|
||||||
pub plaintext: Option<String>, // Never None when message sent
|
pub plaintext: Vec<String>, // Append new messages received while in Trusted state
|
||||||
pub commited_in: Option<OutPoint>,
|
pub commited_in: Option<OutPoint>,
|
||||||
pub commitment: Option<String>, // content of the op_return
|
pub tie_by: Option<usize>, // index of the output that ties the proposal
|
||||||
pub sender: Option<String>, // Never None when message sent
|
pub commitment: Option<String>, // content of the op_return
|
||||||
pub recipient: Option<String>, // Never None when message sent
|
pub sender: Option<String>, // Never None when message sent
|
||||||
pub shared_secret: Option<String>, // Never None when message sent
|
pub recipient: Option<String>, // Never None when message sent
|
||||||
pub key: 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 confirmed_by: Option<OutPoint>, // If this None, Sender keeps sending
|
pub confirmed_by: Option<OutPoint>, // If this None, Sender keeps sending
|
||||||
pub timestamp: u64,
|
pub timestamp: u64,
|
||||||
pub error: Option<AnkError>,
|
pub error: Option<AnkError>,
|
||||||
@ -171,22 +196,30 @@ pub struct CachedMessage {
|
|||||||
impl CachedMessage {
|
impl CachedMessage {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut new = Self::default();
|
let mut new = Self::default();
|
||||||
let mut buf = [0u8;4];
|
let mut buf = [0u8; 4];
|
||||||
thread_rng().fill_bytes(&mut buf);
|
thread_rng().fill_bytes(&mut buf);
|
||||||
new.id = u32::from_be_bytes(buf);
|
new.id = u32::from_be_bytes(buf);
|
||||||
new.timestamp = Date::now().floor() as u64;
|
new.timestamp = Date::now().floor() as u64;
|
||||||
new
|
new
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_string(json: &str) -> Result<Self> {
|
||||||
|
let res = serde_json::from_str(json)?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_string(&self) -> String {
|
||||||
|
serde_json::to_string(self).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn try_decrypt_cipher(&self, cipher: Vec<u8>) -> Result<Vec<u8>> {
|
pub fn try_decrypt_cipher(&self, cipher: Vec<u8>) -> Result<Vec<u8>> {
|
||||||
if self.ciphertext.is_some() || self.shared_secret.is_none() {
|
if self.ciphertext.is_some() || self.shared_secret.is_none() {
|
||||||
return Err(Error::msg(
|
return Err(Error::msg(
|
||||||
"Can't try decrypt this message, there's already a ciphertext or no shared secret"
|
"Can't try decrypt this message, there's already a ciphertext or no shared secret",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let mut shared_secret = [0u8; 32];
|
let mut shared_secret = [0u8; 32];
|
||||||
shared_secret
|
shared_secret.copy_from_slice(&Vec::from_hex(self.shared_secret.as_ref().unwrap())?);
|
||||||
.copy_from_slice(&Vec::from_hex(self.shared_secret.as_ref().unwrap())?);
|
|
||||||
let aes_decrypt = Aes256Decryption::new(Purpose::Arbitrary, cipher, shared_secret)?;
|
let aes_decrypt = Aes256Decryption::new(Purpose::Arbitrary, cipher, shared_secret)?;
|
||||||
|
|
||||||
aes_decrypt.decrypt_with_key()
|
aes_decrypt.decrypt_with_key()
|
||||||
@ -195,13 +228,61 @@ impl CachedMessage {
|
|||||||
pub fn try_decrypt_with_shared_secret(&self, shared_secret: [u8; 32]) -> Result<Vec<u8>> {
|
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.ciphertext.is_none() || self.shared_secret.is_some() {
|
||||||
return Err(Error::msg(
|
return Err(Error::msg(
|
||||||
"Can't try decrypt this message, ciphertext is none or shared_secret already found"
|
"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 cipher_bin = Vec::from_hex(self.ciphertext.as_ref().unwrap())?;
|
||||||
let aes_decrypt =
|
let aes_decrypt = Aes256Decryption::new(Purpose::Arbitrary, cipher_bin, shared_secret)?;
|
||||||
Aes256Decryption::new(Purpose::Arbitrary, cipher_bin, shared_secret)?;
|
|
||||||
|
|
||||||
aes_decrypt.decrypt_with_key()
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,87 +5,43 @@ use std::str::FromStr;
|
|||||||
use anyhow::{Error, Result};
|
use anyhow::{Error, Result};
|
||||||
|
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
|
use sp_client::bitcoin::consensus::deserialize;
|
||||||
use sp_client::bitcoin::hashes::{sha256, Hash};
|
use sp_client::bitcoin::hashes::{sha256, Hash};
|
||||||
use sp_client::bitcoin::hex::FromHex;
|
use sp_client::bitcoin::hex::FromHex;
|
||||||
use sp_client::bitcoin::psbt::raw;
|
use sp_client::bitcoin::key::{Keypair, Secp256k1, TapTweak};
|
||||||
use sp_client::bitcoin::{Psbt, Transaction, Txid};
|
use sp_client::bitcoin::psbt::{raw, Output};
|
||||||
use sp_client::bitcoin::{Amount, OutPoint};
|
use sp_client::bitcoin::secp256k1::SecretKey;
|
||||||
use sp_client::bitcoin::consensus::deserialize;
|
use sp_client::bitcoin::{Address, Psbt, ScriptBuf, Transaction, Txid};
|
||||||
|
use sp_client::bitcoin::{Amount, OutPoint, TxOut};
|
||||||
|
use sp_client::constants::{
|
||||||
|
self, DUST_THRESHOLD, PSBT_SP_ADDRESS_KEY, PSBT_SP_PREFIX, PSBT_SP_SUBTYPE,
|
||||||
|
};
|
||||||
|
use sp_client::silentpayments::utils::sending::calculate_ecdh_shared_secret;
|
||||||
use sp_client::silentpayments::utils::SilentPaymentAddress;
|
use sp_client::silentpayments::utils::SilentPaymentAddress;
|
||||||
use sp_client::spclient::{OwnedOutput, Recipient, SpClient, SpWallet};
|
use sp_client::spclient::{OwnedOutput, Recipient, SpClient, SpWallet};
|
||||||
use sp_client::constants;
|
|
||||||
|
|
||||||
pub fn create_transaction(sp_address: SilentPaymentAddress, sp_wallet: &SpWallet, fee_rate: Amount) -> Result<Transaction> {
|
use crate::crypto::AnkSharedSecret;
|
||||||
let available_outpoints = sp_wallet.get_outputs().to_spendable_list();
|
|
||||||
|
|
||||||
// Here we need to add more heuristics about which outpoint we spend
|
pub fn create_transaction(
|
||||||
// For now let's keep it simple
|
mandatory_inputs: &[&OutPoint],
|
||||||
|
|
||||||
let mut inputs: HashMap<OutPoint, OwnedOutput> = HashMap::new();
|
|
||||||
|
|
||||||
let mut total_available = Amount::from_sat(0);
|
|
||||||
for (outpoint, output) in available_outpoints {
|
|
||||||
total_available += output.amount;
|
|
||||||
inputs.insert(outpoint, output);
|
|
||||||
if total_available > Amount::from_sat(1000) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if total_available < Amount::from_sat(1000) {
|
|
||||||
return Err(Error::msg("Not enough available funds"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let recipient = Recipient {
|
|
||||||
address: sp_address.into(),
|
|
||||||
amount: Amount::from_sat(1000),
|
|
||||||
nb_outputs: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut new_psbt = sp_wallet.get_client().create_new_psbt(
|
|
||||||
inputs,
|
|
||||||
vec![recipient],
|
|
||||||
None,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let change_addr = sp_wallet.get_client().sp_receiver.get_change_address();
|
|
||||||
SpClient::set_fees(&mut new_psbt, fee_rate, change_addr)?;
|
|
||||||
|
|
||||||
let partial_secret = sp_wallet
|
|
||||||
.get_client()
|
|
||||||
.get_partial_secret_from_psbt(&new_psbt)?;
|
|
||||||
|
|
||||||
// This wouldn't work with many recipients in the same transaction
|
|
||||||
// each address (or more precisely each scan public key) would have its own point
|
|
||||||
// let shared_point = shared_secret_point(&sp_address.get_scan_key(), &partial_secret);
|
|
||||||
|
|
||||||
sp_wallet
|
|
||||||
.get_client()
|
|
||||||
.fill_sp_outputs(&mut new_psbt, partial_secret)?;
|
|
||||||
let mut aux_rand = [0u8; 32];
|
|
||||||
rand::thread_rng().fill(&mut aux_rand);
|
|
||||||
let mut signed = sp_wallet.get_client().sign_psbt(new_psbt, &aux_rand)?;
|
|
||||||
SpClient::finalize_psbt(&mut signed)?;
|
|
||||||
|
|
||||||
let final_tx = signed.extract_tx()?;
|
|
||||||
|
|
||||||
Ok(final_tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_transaction_spend_outpoint(
|
|
||||||
outpoint: &OutPoint,
|
|
||||||
sp_wallet: &SpWallet,
|
sp_wallet: &SpWallet,
|
||||||
mut recipient: Recipient,
|
mut recipient: Recipient,
|
||||||
commited_in_txid: &Txid,
|
payload: Option<Vec<u8>>,
|
||||||
fee_rate: Amount
|
fee_rate: Amount,
|
||||||
|
fee_payer: Option<String>, // None means sender pays everything
|
||||||
) -> Result<Psbt> {
|
) -> Result<Psbt> {
|
||||||
let available_outpoints = sp_wallet.get_outputs().to_spendable_list();
|
let available_outpoints = sp_wallet.get_outputs().to_spendable_list();
|
||||||
|
let recipient_address = SilentPaymentAddress::try_from(recipient.address.as_str())?;
|
||||||
|
|
||||||
let mut inputs: HashMap<OutPoint, OwnedOutput> = HashMap::new();
|
let mut inputs: HashMap<OutPoint, OwnedOutput> = HashMap::new();
|
||||||
let mut total_available = Amount::from_sat(0);
|
let mut total_available = Amount::from_sat(0);
|
||||||
let (must_outpoint, must_output) = available_outpoints.get_key_value(outpoint).ok_or_else(|| Error::msg("Mandatory outpoint unknown"))?;
|
for outpoint in mandatory_inputs {
|
||||||
total_available += must_output.amount;
|
let (must_outpoint, must_output) = available_outpoints
|
||||||
inputs.insert(*must_outpoint, must_output.clone());
|
.get_key_value(outpoint)
|
||||||
|
.ok_or_else(|| Error::msg("Mandatory outpoint unknown"))?;
|
||||||
|
total_available += must_output.amount;
|
||||||
|
inputs.insert(*must_outpoint, must_output.clone());
|
||||||
|
}
|
||||||
|
|
||||||
for (outpoint, output) in available_outpoints {
|
for (outpoint, output) in available_outpoints {
|
||||||
if total_available > Amount::from_sat(1000) {
|
if total_available > Amount::from_sat(1000) {
|
||||||
@ -99,33 +55,99 @@ pub fn create_transaction_spend_outpoint(
|
|||||||
return Err(Error::msg("Not enough available funds"));
|
return Err(Error::msg("Not enough available funds"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the amount for the recipient
|
if recipient.amount == Amount::from_sat(0) {
|
||||||
recipient.amount = total_available;
|
// update the amount for the recipient
|
||||||
|
recipient.amount = total_available;
|
||||||
|
}
|
||||||
|
|
||||||
// Take the recipient address
|
let mut commitment = [0u8; 32];
|
||||||
let address = recipient.address.clone();
|
if let Some(ref p) = payload {
|
||||||
|
commitment.copy_from_slice(&p);
|
||||||
|
} else {
|
||||||
|
thread_rng().fill(&mut commitment);
|
||||||
|
}
|
||||||
|
|
||||||
// create a dummy commitment that is H(b_scan | commited_in txid)
|
let mut new_psbt =
|
||||||
let mut buf = [0u8;64];
|
sp_wallet
|
||||||
buf[..32].copy_from_slice(commited_in_txid.as_raw_hash().as_byte_array());
|
.get_client()
|
||||||
buf[32..].copy_from_slice(&sp_wallet.get_client().get_scan_key().secret_bytes());
|
.create_new_psbt(inputs, vec![recipient], Some(&commitment))?;
|
||||||
|
|
||||||
let mut engine = sha256::HashEngine::default();
|
|
||||||
engine.write_all(&buf)?;
|
|
||||||
let hash = sha256::Hash::from_engine(engine);
|
|
||||||
|
|
||||||
let mut new_psbt = sp_wallet.get_client().create_new_psbt(
|
let sender_address = sp_wallet.get_client().get_receiving_address();
|
||||||
inputs,
|
let change_address = sp_wallet.get_client().sp_receiver.get_change_address();
|
||||||
vec![recipient],
|
if let Some(address) = fee_payer {
|
||||||
Some(hash.as_byte_array()),
|
SpClient::set_fees(&mut new_psbt, fee_rate, address)?;
|
||||||
)?;
|
} else {
|
||||||
|
let candidates: Vec<Option<String>> = new_psbt.outputs
|
||||||
|
.iter()
|
||||||
|
.map(|o| {
|
||||||
|
if let Some(value) = o.proprietary.get(&raw::ProprietaryKey {
|
||||||
|
prefix: PSBT_SP_PREFIX.as_bytes().to_vec(),
|
||||||
|
subtype: PSBT_SP_SUBTYPE,
|
||||||
|
key: PSBT_SP_ADDRESS_KEY.as_bytes().to_vec(),
|
||||||
|
}) {
|
||||||
|
let candidate: String =
|
||||||
|
SilentPaymentAddress::try_from(deserialize::<String>(value).unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.into();
|
||||||
|
return Some(candidate);
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
SpClient::set_fees(&mut new_psbt, fee_rate, address)?;
|
let mut fee_set = false;
|
||||||
|
for candidate in candidates {
|
||||||
|
if let Some(c) = candidate {
|
||||||
|
if c == change_address {
|
||||||
|
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())?;
|
||||||
|
fee_set = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fee_set {
|
||||||
|
return Err(Error::msg("Must specify payer for fee"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let partial_secret = sp_wallet
|
let partial_secret = sp_wallet
|
||||||
.get_client()
|
.get_client()
|
||||||
.get_partial_secret_from_psbt(&new_psbt)?;
|
.get_partial_secret_from_psbt(&new_psbt)?;
|
||||||
|
|
||||||
|
// if we have a payload, it means we are notifying, so let's add a revokation output
|
||||||
|
if payload.is_some() {
|
||||||
|
let shared_point =
|
||||||
|
calculate_ecdh_shared_secret(&recipient_address.get_scan_key(), &partial_secret);
|
||||||
|
|
||||||
|
let shared_secret = AnkSharedSecret::new(shared_point);
|
||||||
|
|
||||||
|
// add the revokation output
|
||||||
|
let revokation_key =
|
||||||
|
Keypair::from_seckey_slice(&Secp256k1::signing_only(), &shared_secret.to_byte_array())?;
|
||||||
|
let spk = ScriptBuf::new_p2tr_tweaked(
|
||||||
|
revokation_key
|
||||||
|
.x_only_public_key()
|
||||||
|
.0
|
||||||
|
.dangerous_assume_tweaked(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let txout = TxOut {
|
||||||
|
value: Amount::from_sat(0),
|
||||||
|
script_pubkey: spk,
|
||||||
|
};
|
||||||
|
|
||||||
|
// For now let's just push it to the last output
|
||||||
|
new_psbt.unsigned_tx.output.push(txout);
|
||||||
|
|
||||||
|
new_psbt.outputs.push(Output::default());
|
||||||
|
}
|
||||||
|
|
||||||
sp_wallet
|
sp_wallet
|
||||||
.get_client()
|
.get_client()
|
||||||
.fill_sp_outputs(&mut new_psbt, partial_secret)?;
|
.fill_sp_outputs(&mut new_psbt, partial_secret)?;
|
||||||
@ -137,58 +159,6 @@ pub fn create_transaction_spend_outpoint(
|
|||||||
Ok(signed)
|
Ok(signed)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_transaction_for_address_with_shared_secret(
|
|
||||||
recipient: Recipient,
|
|
||||||
sp_wallet: &SpWallet,
|
|
||||||
message: Option<&str>,
|
|
||||||
fee_rate: Amount,
|
|
||||||
) -> Result<String> {
|
|
||||||
let available_outpoints = sp_wallet.get_outputs().to_spendable_list();
|
|
||||||
|
|
||||||
// Here we need to add more heuristics about which outpoint we spend
|
|
||||||
// For now let's keep it simple
|
|
||||||
|
|
||||||
let mut inputs: HashMap<OutPoint, OwnedOutput> = HashMap::new();
|
|
||||||
|
|
||||||
let mut total_available = Amount::from_sat(0);
|
|
||||||
for (outpoint, output) in available_outpoints {
|
|
||||||
total_available += output.amount;
|
|
||||||
inputs.insert(outpoint, output);
|
|
||||||
if total_available > Amount::from_sat(1000) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if total_available < Amount::from_sat(1000) {
|
|
||||||
return Err(Error::msg("Not enough available funds"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let message_bin = if message.is_some() { Vec::from_hex(message.unwrap())? } else { vec![] };
|
|
||||||
|
|
||||||
let mut new_psbt = sp_wallet.get_client().create_new_psbt(
|
|
||||||
inputs,
|
|
||||||
vec![recipient],
|
|
||||||
if !message_bin.is_empty() { Some(&message_bin) } else { None },
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let change_addr = sp_wallet.get_client().sp_receiver.get_change_address();
|
|
||||||
SpClient::set_fees(&mut new_psbt, fee_rate, change_addr)?;
|
|
||||||
|
|
||||||
let partial_secret = sp_wallet
|
|
||||||
.get_client()
|
|
||||||
.get_partial_secret_from_psbt(&new_psbt)?;
|
|
||||||
|
|
||||||
sp_wallet
|
|
||||||
.get_client()
|
|
||||||
.fill_sp_outputs(&mut new_psbt, partial_secret)?;
|
|
||||||
let mut aux_rand = [0u8; 32];
|
|
||||||
rand::thread_rng().fill(&mut aux_rand);
|
|
||||||
let mut signed = sp_wallet.get_client().sign_psbt(new_psbt, &aux_rand)?;
|
|
||||||
SpClient::finalize_psbt(&mut signed)?;
|
|
||||||
|
|
||||||
Ok(signed.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn map_outputs_to_sp_address(psbt_str: &str) -> Result<HashMap<String, Vec<usize>>> {
|
pub fn map_outputs_to_sp_address(psbt_str: &str) -> Result<HashMap<String, Vec<usize>>> {
|
||||||
let psbt = Psbt::from_str(&psbt_str)?;
|
let psbt = Psbt::from_str(&psbt_str)?;
|
||||||
|
|
||||||
@ -213,3 +183,198 @@ pub fn map_outputs_to_sp_address(psbt_str: &str) -> Result<HashMap<String, Vec<u
|
|||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::network::CipherMessage;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use sp_client::bitcoin::consensus::serialize;
|
||||||
|
use sp_client::bitcoin::hex::DisplayHex;
|
||||||
|
use sp_client::bitcoin::secp256k1::PublicKey;
|
||||||
|
use sp_client::bitcoin::Transaction;
|
||||||
|
use sp_client::silentpayments::utils::receiving::{
|
||||||
|
calculate_tweak_data, get_pubkey_from_input,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ALICE_WALLET: &str = "{\"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 = "{\"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 = "{\"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 = "{\"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 = "{\"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 = "{\"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);
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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());
|
||||||
|
|
||||||
|
assert!(commitment == "d12f3c5b37240bc3abf2976f41fdf9a594f0680aafd2781ac448f80440fbeb99");
|
||||||
|
|
||||||
|
let psbt = create_transaction(
|
||||||
|
&vec![],
|
||||||
|
&alice_wallet,
|
||||||
|
recipient,
|
||||||
|
Some(Vec::from_hex(COMMITMENT).unwrap()),
|
||||||
|
FEE_RATE,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let final_tx = psbt.extract_tx().unwrap();
|
||||||
|
let spk = "51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1";
|
||||||
|
|
||||||
|
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());
|
||||||
|
assert!(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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();
|
||||||
|
|
||||||
|
// Bob must spend notification output
|
||||||
|
let (confirmation_outpoint, _) = bob_wallet
|
||||||
|
.get_outputs()
|
||||||
|
.get_outpoint(
|
||||||
|
OutPoint::from_str(
|
||||||
|
"148e0faa2f203b6e9488e2da696d8a49ebff4212946672f0bb072ced0909360d:0",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let recipient = Recipient {
|
||||||
|
address: ALICE_ADDRESS.to_owned(),
|
||||||
|
amount: Amount::from_sat(0),
|
||||||
|
nb_outputs: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let psbt = create_transaction(
|
||||||
|
&vec![&confirmation_outpoint],
|
||||||
|
&bob_wallet,
|
||||||
|
recipient,
|
||||||
|
None,
|
||||||
|
FEE_RATE,
|
||||||
|
Some(ALICE_ADDRESS.to_owned()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let final_tx = psbt.extract_tx().unwrap();
|
||||||
|
// println!(
|
||||||
|
// "{}",
|
||||||
|
// serialize::<Transaction>(&final_tx).to_lower_hex_string()
|
||||||
|
// );
|
||||||
|
let spk = "512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c";
|
||||||
|
|
||||||
|
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());
|
||||||
|
assert!(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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();
|
||||||
|
|
||||||
|
// Bob must spend notification output
|
||||||
|
let (confirmation_outpoint, _) = alice_wallet
|
||||||
|
.get_outputs()
|
||||||
|
.get_outpoint(
|
||||||
|
OutPoint::from_str(
|
||||||
|
"bc207c02bc4f1d4359fcd604296c0938bf1e6ff827662a56410676b8cbd768d9:0",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let recipient = Recipient {
|
||||||
|
address: BOB_ADDRESS.to_owned(),
|
||||||
|
amount: Amount::from_sat(0),
|
||||||
|
nb_outputs: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let psbt = create_transaction(
|
||||||
|
&vec![&confirmation_outpoint],
|
||||||
|
&alice_wallet,
|
||||||
|
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());
|
||||||
|
assert!(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user