Merge branch 'minimal_pcd_prd' into dev
This commit is contained in:
commit
a592001fdb
@ -15,11 +15,9 @@ wasm-bindgen = "0.2.91"
|
||||
getrandom = { version="0.2.12", features = ["js"] }
|
||||
wasm-logger = "0.2.0"
|
||||
rand = "0.8.5"
|
||||
log = "0.4.6"
|
||||
tsify = { git = "https://github.com/Sosthene00/tsify", branch = "next" }
|
||||
# sdk_common = { path = "../sdk_common" }
|
||||
sdk_common = { git = "https://git.4nkweb.com/4nk/sdk_common.git", branch = "dev" }
|
||||
shamir = { git = "https://github.com/Sosthene00/shamir", branch = "master" }
|
||||
sdk_common = { path = "../sdk_common" }
|
||||
# sdk_common = { git = "https://git.4nkweb.com/4nk/sdk_common.git", branch = "dev" }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3"
|
||||
|
1818
src/api.rs
1818
src/api.rs
File diff suppressed because it is too large
Load Diff
18
src/lib.rs
18
src/lib.rs
@ -1,16 +1,11 @@
|
||||
#![allow(warnings)]
|
||||
use anyhow::Error;
|
||||
use sdk_common::crypto::AnkSharedSecret;
|
||||
use sdk_common::network::CachedMessage;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Debug;
|
||||
use sdk_common::MutexExt;
|
||||
use std::sync::{Mutex, MutexGuard, OnceLock};
|
||||
use tsify::Tsify;
|
||||
|
||||
pub mod api;
|
||||
mod peers;
|
||||
mod process;
|
||||
mod user;
|
||||
mod wallet;
|
||||
|
||||
@ -21,14 +16,3 @@ pub fn lock_messages() -> Result<MutexGuard<'static, Vec<CachedMessage>>, Error>
|
||||
.get_or_init(|| Mutex::new(vec![]))
|
||||
.lock_anyhow()
|
||||
}
|
||||
|
||||
pub(crate) trait MutexExt<T> {
|
||||
fn lock_anyhow(&self) -> Result<MutexGuard<T>, Error>;
|
||||
}
|
||||
|
||||
impl<T: Debug> MutexExt<T> for Mutex<T> {
|
||||
fn lock_anyhow(&self) -> Result<MutexGuard<T>, Error> {
|
||||
self.lock()
|
||||
.map_err(|e| Error::msg(format!("Failed to lock: {}", e)))
|
||||
}
|
||||
}
|
||||
|
405
src/process.rs
405
src/process.rs
@ -1,405 +0,0 @@
|
||||
use std::fmt::DebugStruct;
|
||||
|
||||
use sdk_common::sp_client::silentpayments::utils::SilentPaymentAddress;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use tsify::Tsify;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub const HTML_KOTPART: &str = "
|
||||
<div class='card'>
|
||||
<div class='side-by-side'>
|
||||
<h3>Send encrypted messages</h3>
|
||||
<div>
|
||||
<a href='#' id='send messages'>Send Messages</a>
|
||||
</div>
|
||||
</div>
|
||||
<form id='form4nk' action='#'>
|
||||
<label for='sp_address'>Send to:</label>
|
||||
<input type='text' id='sp_address' />
|
||||
<hr/>
|
||||
<label for='message'>Message:</label>
|
||||
<input type='message' id='message' />
|
||||
<hr/>
|
||||
<button type='submit' id='submitButton' class='recover bg-primary'>Send</button>
|
||||
</form>
|
||||
</div>
|
||||
";
|
||||
|
||||
pub const HTML_STORAGE: &str = "
|
||||
<div class='card'>
|
||||
<div class='side-by-side'>
|
||||
<h3>Send encrypted messages</h3>
|
||||
<div>
|
||||
<a href='#' id='send messages'>Send Messages</a>
|
||||
</div>
|
||||
</div>
|
||||
<form id='form4nk' action='#'>
|
||||
<label for='sp_address'>Send to:</label>
|
||||
<input type='text' id='sp_address' />
|
||||
<hr/>
|
||||
<label for='message'>Message:</label>
|
||||
<input type='message' id='message' />
|
||||
<hr/>
|
||||
<button type='submit' id='submitButton' class='recover bg-primary'>Send</button>
|
||||
</form>
|
||||
</div>
|
||||
";
|
||||
|
||||
pub const HTML_MESSAGING: &str = "
|
||||
<div class='card'>
|
||||
<div class='side-by-side'>
|
||||
<h3>Send encrypted messages</h3>
|
||||
<div>
|
||||
<a href='#' id='send messages'>Send Messages</a>
|
||||
</div>
|
||||
</div>
|
||||
<form id='form4nk' action='#'>
|
||||
<div id='our_address' class='our_address'></div>
|
||||
<label for='sp_address'>Send to:</label>
|
||||
<input type='text' id='sp_address' />
|
||||
<hr/>
|
||||
<label for='message'>Message:</label>
|
||||
<input type='message' id='message' />
|
||||
<hr/>
|
||||
<button type='submit' id='submitButton' class='recover bg-primary'>Send</button>
|
||||
</form>
|
||||
</div>
|
||||
";
|
||||
|
||||
pub const CSS: &str = "
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background-color: #f4f4f4;
|
||||
font-family: 'Arial', sans-serif;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
}
|
||||
.card {
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
}
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* flex-wrap: wrap; */
|
||||
}
|
||||
label {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
hr {
|
||||
border: 0;
|
||||
height: 1px;
|
||||
background-color: #ddd;
|
||||
margin: 10px 0;
|
||||
}
|
||||
input, select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin: 8px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
select {
|
||||
padding: 10px;
|
||||
background-color: #f9f9f9;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
button {
|
||||
display: inline-block;
|
||||
background-color: #4caf50;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 12px 17px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
.side-by-side {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.side-by-side>* {
|
||||
display: inline-block;
|
||||
}
|
||||
button.recover {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
background-color: #4caf50;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 12px 17px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
button.recover:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
a.btn {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
background-color: #4caf50;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 12px 17px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a.btn:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #78a6de;
|
||||
}
|
||||
.bg-secondary {
|
||||
background-color: #2b81ed;
|
||||
}
|
||||
.bg-primary {
|
||||
background-color: #1A61ED;
|
||||
}
|
||||
.bg-primary:hover {
|
||||
background-color: #457be8;
|
||||
}
|
||||
.card-revoke {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
.card-revoke a {
|
||||
max-width: 50px;
|
||||
width: 100%;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.card-revoke button {
|
||||
max-width: 200px;
|
||||
width: 100%;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #78a6de;
|
||||
}
|
||||
.card-revoke svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
fill: #333;
|
||||
}
|
||||
.image-label {
|
||||
display: block;
|
||||
color: #fff;
|
||||
padding: 5px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.image-container {
|
||||
width: 400px;
|
||||
height: 300px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.image-container img {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center center;
|
||||
}
|
||||
.passwordalert {
|
||||
color: #FF0000;
|
||||
}
|
||||
";
|
||||
|
||||
pub const CSSUPDATE: &str = "
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background-color: #f4f4f4;
|
||||
font-family: 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
form {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1fr, 2fr);
|
||||
gap: 10px;
|
||||
max-width: 400px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.bg-primary {
|
||||
background-color: #1a61ed;
|
||||
}
|
||||
|
||||
.bg-primary:hover {
|
||||
background-color: #457be8;
|
||||
}
|
||||
|
||||
.bg-secondary {
|
||||
background-color: #2b81ed;
|
||||
}
|
||||
|
||||
.bg-secondary:hover {
|
||||
background-color: #5f9bff;
|
||||
}
|
||||
|
||||
label {
|
||||
text-align: left;
|
||||
padding-right: 10px;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
button {
|
||||
grid-column: span 2;
|
||||
display: inline-block;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 12px 17px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.div-text-area {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.side-by-side {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.circle-btn {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 0px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#fileInput {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
padding-left: 0px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
";
|
||||
|
||||
pub const JSUPDATE: &str = "
|
||||
var addSpAddressBtn = document.getElementById('add-sp-address-btn');
|
||||
var removeSpAddressBtn = document.querySelectorAll('.minus-sp-address-btn');
|
||||
|
||||
addSpAddressBtn.addEventListener('click', function (event) {
|
||||
addDynamicField(this);
|
||||
});
|
||||
|
||||
function addDynamicField(element) {
|
||||
var addSpAddressBlock = document.getElementById('sp-address-block');
|
||||
var spAddress = addSpAddressBlock.querySelector('#sp-address').value;
|
||||
addSpAddressBlock.querySelector('#sp-address').value = '';
|
||||
spAddress = spAddress.trim();
|
||||
if (spAddress != '') {
|
||||
var sideBySideDiv = document.createElement('div');
|
||||
sideBySideDiv.className = 'side-by-side';
|
||||
|
||||
var inputElement = document.createElement('input');
|
||||
inputElement.type = 'text';
|
||||
inputElement.name = 'spAddresses[]';
|
||||
inputElement.setAttribute('form', 'no-form');
|
||||
inputElement.value = spAddress;
|
||||
inputElement.disabled = true;
|
||||
|
||||
var buttonElement = document.createElement('button');
|
||||
buttonElement.type = 'button';
|
||||
buttonElement.className =
|
||||
'circle-btn bg-secondary minus-sp-address-btn';
|
||||
buttonElement.innerHTML = '-';
|
||||
|
||||
buttonElement.addEventListener('click', function (event) {
|
||||
removeDynamicField(this.parentElement);
|
||||
});
|
||||
|
||||
sideBySideDiv.appendChild(inputElement);
|
||||
sideBySideDiv.appendChild(buttonElement);
|
||||
|
||||
addSpAddressBlock.appendChild(sideBySideDiv);
|
||||
}
|
||||
function removeDynamicField(element) {
|
||||
element.remove();
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Tsify)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct Process {
|
||||
pub id: u32,
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub members: Vec<String>,
|
||||
pub html: String,
|
||||
pub style: String,
|
||||
pub script: String,
|
||||
// Add conditions : process, member, peer, artefact
|
||||
}
|
144
src/user.rs
144
src/user.rs
@ -21,6 +21,7 @@ use std::io::{Cursor, Read, Write};
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Mutex, MutexGuard, OnceLock};
|
||||
|
||||
use sdk_common::device::Device;
|
||||
use sdk_common::sp_client::bitcoin::secp256k1::constants::SECRET_KEY_SIZE;
|
||||
use sdk_common::sp_client::silentpayments::bitcoin_hashes::sha256;
|
||||
use sdk_common::sp_client::silentpayments::utils::{Network as SpNetwork, SilentPaymentAddress};
|
||||
@ -29,9 +30,7 @@ use sdk_common::sp_client::spclient::{OutputList, SpWallet, SpendKey};
|
||||
use crate::peers::Peer;
|
||||
use crate::wallet::generate_sp_wallet;
|
||||
use crate::MutexExt;
|
||||
use sdk_common::crypto::{
|
||||
AeadCore, Aes256Decryption, Aes256Encryption, Aes256Gcm, HalfKey, KeyInit, Purpose,
|
||||
};
|
||||
use sdk_common::crypto::{AeadCore, Aes256Gcm, KeyInit};
|
||||
|
||||
pub static LOCAL_DEVICE: OnceLock<Mutex<Device>> = OnceLock::new();
|
||||
|
||||
@ -58,142 +57,3 @@ pub fn lock_local_device() -> Result<MutexGuard<'static, Device>> {
|
||||
.get_or_init(|| Mutex::new(Device::default()))
|
||||
.lock_anyhow()
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, Tsify)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct PairedDevice {
|
||||
pub address: String,
|
||||
pub outgoing_pairing_transaction: [u8; 32],
|
||||
pub revokation_index: u32,
|
||||
pub incoming_pairing_transaction: [u8; 32],
|
||||
pub current_remote_key: [u8; 32],
|
||||
pub current_session_outpoint: OutPoint, // This will be spend by remote device to notify us of next login
|
||||
pub current_session_revokation_outpoint: OutPoint, // remote device can revoke current session by spending this
|
||||
}
|
||||
|
||||
impl PairedDevice {
|
||||
pub fn new(address: SilentPaymentAddress, pairing_txid: Txid, revokation_index: u32) -> Self {
|
||||
let mut pairing_transaction_buf = [0u8; 32];
|
||||
pairing_transaction_buf.copy_from_slice(&serialize(&pairing_txid));
|
||||
|
||||
Self {
|
||||
address: address.into(),
|
||||
outgoing_pairing_transaction: pairing_transaction_buf,
|
||||
revokation_index,
|
||||
incoming_pairing_transaction: [0u8; 32],
|
||||
current_session_revokation_outpoint: OutPoint::default(),
|
||||
current_session_outpoint: OutPoint::default(),
|
||||
current_remote_key: [0u8; 32],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, Tsify)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct Device {
|
||||
sp_wallet: SpWallet,
|
||||
current_session_outpoint: OutPoint, // This is the notification output of incoming login tx
|
||||
current_session_key: [u8; 32],
|
||||
current_session_revokation_outpoint: OutPoint, // This is the revokation outpoint of outgoing login tx
|
||||
paired_device: Option<PairedDevice>,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
pub fn new(sp_wallet: SpWallet) -> Self {
|
||||
Self {
|
||||
sp_wallet,
|
||||
current_session_outpoint: OutPoint::default(),
|
||||
current_session_key: [0u8; 32],
|
||||
current_session_revokation_outpoint: OutPoint::default(),
|
||||
paired_device: 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_linked(&self) -> bool {
|
||||
self.paired_device.is_some()
|
||||
}
|
||||
|
||||
pub fn is_pairing(&self) -> bool {
|
||||
self.current_session_key == [0u8; 32]
|
||||
}
|
||||
|
||||
pub fn get_paired_device_info(&self) -> Option<PairedDevice> {
|
||||
self.paired_device.clone()
|
||||
}
|
||||
|
||||
pub fn get_next_output_to_spend(&self) -> OutPoint {
|
||||
self.current_session_outpoint
|
||||
}
|
||||
|
||||
pub fn get_session_revokation_outpoint(&self) -> OutPoint {
|
||||
self.current_session_revokation_outpoint
|
||||
}
|
||||
|
||||
pub fn sign_with_current_session_key(&self) -> Result<()> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
pub fn encrypt_with_current_session_key(&self) -> Result<()> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
pub fn new_link(
|
||||
&mut self,
|
||||
link_with: SilentPaymentAddress,
|
||||
outgoing_pairing_tx: Txid,
|
||||
revokation_output: u32,
|
||||
incoming_pairing_tx: Txid,
|
||||
) -> Result<()> {
|
||||
let address_looked_for: String = link_with.into();
|
||||
if let Some(paired_device) = self.paired_device.as_ref() {
|
||||
return Err(Error::msg(format!(
|
||||
"Found an already paired device with address {} and revokable by {}:{}",
|
||||
paired_device.address,
|
||||
Txid::from_byte_array(paired_device.outgoing_pairing_transaction),
|
||||
paired_device.revokation_index
|
||||
)));
|
||||
} else {
|
||||
let mut new_device =
|
||||
PairedDevice::new(link_with, outgoing_pairing_tx, revokation_output);
|
||||
new_device.incoming_pairing_transaction = incoming_pairing_tx.to_byte_array();
|
||||
self.paired_device = Some(new_device);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// We call that when we spent to the remote device and it similarly spent to us
|
||||
pub fn update_session(
|
||||
&mut self,
|
||||
new_session_key: SecretKey,
|
||||
new_session_outpoint: OutPoint,
|
||||
new_revokation_outpoint: OutPoint,
|
||||
new_remote_key: XOnlyPublicKey,
|
||||
new_remote_session_outpoint: OutPoint,
|
||||
new_remote_revokation_outpoint: OutPoint,
|
||||
) -> Result<()> {
|
||||
if !self.is_linked() {
|
||||
return Err(Error::msg("Can't update an unpaired device"));
|
||||
}
|
||||
self.paired_device
|
||||
.as_mut()
|
||||
.map(|d| {
|
||||
d.current_remote_key = new_remote_key.serialize();
|
||||
d.current_session_outpoint = new_remote_session_outpoint;
|
||||
d.current_session_revokation_outpoint = new_remote_revokation_outpoint;
|
||||
});
|
||||
self.current_session_key = new_session_key.secret_bytes();
|
||||
self.current_session_outpoint = new_session_outpoint;
|
||||
self.current_session_revokation_outpoint = new_revokation_outpoint;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ pub fn generate_sp_wallet(label: Option<String>, network: Network) -> anyhow::Re
|
||||
network,
|
||||
)?;
|
||||
let our_address: SilentPaymentAddress = sp_client.get_receiving_address().try_into()?;
|
||||
log::info!(
|
||||
sdk_common::log::info!(
|
||||
"Created client for sp with address: {}",
|
||||
our_address.to_string()
|
||||
);
|
||||
|
550
tests/browser.rs
550
tests/browser.rs
File diff suppressed because one or more lines are too long
283
tests/pairing.rs
Normal file
283
tests/pairing.rs
Normal file
@ -0,0 +1,283 @@
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use sdk_client::api::{
|
||||
create_device_from_sp_wallet, create_update_transaction, dump_device, dump_process_cache,
|
||||
get_address, get_outputs, get_update_proposals, pair_device, parse_cipher, reset_device,
|
||||
response_prd, restore_device, set_process_cache, setup, ApiReturn,
|
||||
};
|
||||
use sdk_common::log::debug;
|
||||
use sdk_common::pcd::{Member, Pcd, RoleDefinition};
|
||||
use sdk_common::sp_client::bitcoin::OutPoint;
|
||||
use sdk_common::sp_client::spclient::OwnedOutput;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use tsify::JsValueSerdeExt;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
mod utils;
|
||||
|
||||
use utils::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_pairing() {
|
||||
setup();
|
||||
debug!("==============================================\nStarting test_pairing\n==============================================");
|
||||
|
||||
// ========================= Alice
|
||||
reset_device().unwrap();
|
||||
create_device_from_sp_wallet(ALICE_LOGIN_WALLET.to_owned()).unwrap();
|
||||
|
||||
// we get our own address
|
||||
let alice_address = get_address().unwrap();
|
||||
|
||||
// we scan the qr code or get the address by any other means
|
||||
let bob_address = helper_get_bob_address();
|
||||
|
||||
// Alice creates the new member with Bob address
|
||||
let new_member = Member::new(vec![
|
||||
alice_address.as_str().try_into().unwrap(),
|
||||
bob_address.as_str().try_into().unwrap(),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let pairing_init_state = json!({
|
||||
"html": "",
|
||||
"style": "",
|
||||
"script": "",
|
||||
"description": "AliceBob",
|
||||
"roles": {
|
||||
"owner": {
|
||||
"members":
|
||||
[
|
||||
new_member
|
||||
],
|
||||
"validation_rules":
|
||||
[
|
||||
{
|
||||
"quorum": 1.0,
|
||||
"fields": [
|
||||
"roles",
|
||||
"pairing_tx"
|
||||
],
|
||||
"min_sig_member": 1.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"pairing_tx": OutPoint::null(),
|
||||
});
|
||||
|
||||
debug!("Alice pairs her device");
|
||||
// we can update our local device now, first with an empty txid
|
||||
pair_device(OutPoint::null().to_string(), vec![helper_get_bob_address()]).unwrap();
|
||||
|
||||
debug!("Alice sends a transaction commiting to an update prd to Bob");
|
||||
let alice_pairing_return =
|
||||
create_update_transaction(None, pairing_init_state.to_string(), 1).unwrap();
|
||||
|
||||
let (root_outpoint, alice_init_process) = alice_pairing_return.updated_process.unwrap();
|
||||
let alice_pcd_commitment = Value::from_str(
|
||||
&alice_init_process
|
||||
.get_impending_requests()
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.payload,
|
||||
)
|
||||
.unwrap()
|
||||
.tagged_hash();
|
||||
|
||||
let pairing_tx = alice_pairing_return.new_tx_to_send.unwrap();
|
||||
|
||||
// This is only for testing, the relay takes care of that in prod
|
||||
let get_outputs_result = get_outputs().unwrap();
|
||||
|
||||
let alice_outputs: HashMap<OutPoint, OwnedOutput> = get_outputs_result.into_serde().unwrap();
|
||||
|
||||
let alice_pairing_tweak_data = helper_get_tweak_data(&pairing_tx, alice_outputs);
|
||||
|
||||
// End of the test only part
|
||||
|
||||
// Alice parses her own transaction
|
||||
helper_parse_transaction(&pairing_tx, &alice_pairing_tweak_data);
|
||||
|
||||
// Notify user that we're waiting for confirmation from the other device
|
||||
// We can update the local device with the actual pairing outpoint
|
||||
pair_device(
|
||||
OutPoint::new(pairing_tx.txid(), 0).to_string(),
|
||||
vec![helper_get_bob_address()],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// TODO unpair device
|
||||
|
||||
// We can produce the prd response now even if we can't use it yet
|
||||
let alice_prd_response = response_prd(
|
||||
root_outpoint,
|
||||
alice_init_process
|
||||
.get_impending_requests()
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
true,
|
||||
)
|
||||
.unwrap()
|
||||
.ciphers_to_send
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
// We can also create the first login transaction and sign it
|
||||
|
||||
// this is only for testing, as we're playing both parts
|
||||
let alice_device = dump_device().unwrap();
|
||||
let alice_processes = dump_process_cache().unwrap();
|
||||
|
||||
// ======================= Bob
|
||||
reset_device().unwrap();
|
||||
create_device_from_sp_wallet(BOB_LOGIN_WALLET.to_owned()).unwrap();
|
||||
|
||||
// Bob receives Alice pairing transaction
|
||||
debug!("Bob parses Alice pairing transaction");
|
||||
helper_parse_transaction(&pairing_tx, &alice_pairing_tweak_data);
|
||||
|
||||
debug!("Bob receives the prd");
|
||||
let mut bob_retrieved_prd: ApiReturn = ApiReturn::default();
|
||||
for cipher in alice_pairing_return.ciphers_to_send.iter() {
|
||||
// debug!("Parsing cipher: {:#?}", cipher);
|
||||
match parse_cipher(cipher.clone()) {
|
||||
Ok(res) => bob_retrieved_prd = res,
|
||||
Err(e) => {
|
||||
debug!("Error parsing cipher: {:#?}", e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert!(bob_retrieved_prd.ciphers_to_send.len() == 1);
|
||||
assert!(bob_retrieved_prd.updated_process.is_some());
|
||||
|
||||
debug!("Bob retrieved prd: {:#?}", bob_retrieved_prd);
|
||||
|
||||
let (root_commitment, relevant_process) = bob_retrieved_prd.updated_process.unwrap();
|
||||
let pcd_commitment = relevant_process
|
||||
.get_impending_requests()
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.payload
|
||||
.clone();
|
||||
let prd_confirm_cipher = bob_retrieved_prd.ciphers_to_send.iter().next().unwrap();
|
||||
|
||||
debug!("Bob sends a Confirm Prd to Alice");
|
||||
|
||||
// this is only for testing, as we're playing both parts
|
||||
let bob_device = dump_device().unwrap();
|
||||
let bob_processes = dump_process_cache().unwrap();
|
||||
|
||||
// ======================= Alice
|
||||
reset_device().unwrap();
|
||||
restore_device(alice_device).unwrap();
|
||||
set_process_cache(alice_processes).unwrap();
|
||||
|
||||
debug!("Alice receives the Confirm Prd");
|
||||
let alice_parsed_prd = parse_cipher(prd_confirm_cipher.clone()).unwrap();
|
||||
|
||||
debug!("Alice parsed Bob's Confirm Prd: {:#?}", alice_parsed_prd);
|
||||
|
||||
// ======================= Bob
|
||||
reset_device().unwrap();
|
||||
restore_device(bob_device).unwrap();
|
||||
set_process_cache(bob_processes).unwrap();
|
||||
|
||||
debug!("Bob parses Alice's pcd");
|
||||
let bob_parsed_pcd_return = parse_cipher(alice_parsed_prd.ciphers_to_send[0].clone()).unwrap();
|
||||
|
||||
debug!("bob_parsed_pcd: {:#?}", bob_parsed_pcd_return);
|
||||
|
||||
let (root_commitment, prd_update) = bob_parsed_pcd_return.updated_process.unwrap();
|
||||
|
||||
// At this point, user must validate the pairing proposal received from Alice
|
||||
// We decrypt the content of the pcd so that we can display to user what matters
|
||||
let alice_proposal =
|
||||
get_update_proposals(root_commitment.clone()).unwrap();
|
||||
|
||||
debug!("Alice proposal: {:#?}", alice_proposal);
|
||||
|
||||
// get the pairing tx from the proposal
|
||||
let proposal = Value::from_str(&alice_proposal.get(0).unwrap()).unwrap();
|
||||
debug!("proposal: {:#?}", proposal);
|
||||
let pairing_tx = proposal
|
||||
.get("pairing_tx")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.trim_matches('"');
|
||||
|
||||
let roles = proposal
|
||||
.get("roles")
|
||||
.and_then(|v| Value::from_str(v.as_str().unwrap()).ok())
|
||||
.unwrap()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(role_name, role_value)| {
|
||||
let role_def: RoleDefinition = serde_json::from_value(role_value.clone())?;
|
||||
Ok((role_name.clone(), role_def))
|
||||
})
|
||||
.collect::<Result<HashMap<String, RoleDefinition>, anyhow::Error>>();
|
||||
|
||||
let roles = roles.unwrap();
|
||||
|
||||
// we check that the proposal contains only one member
|
||||
assert!(roles.len() == 1);
|
||||
assert!(roles["owner"].members.len() == 1);
|
||||
|
||||
// we get all the addresses of the members of the proposal
|
||||
let proposal_members = roles
|
||||
.iter()
|
||||
.flat_map(|(_, members)| members.members.iter().flat_map(|m| m.get_addresses()))
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
// we can automatically check that a pairing member contains local device address + the one that sent the proposal
|
||||
assert!(proposal_members.contains(&alice_address));
|
||||
assert!(proposal_members.contains(&bob_address));
|
||||
assert!(proposal_members.len() == 2); // no free riders
|
||||
|
||||
// We remove the local address, but maybe that's the responsibility of the Member type
|
||||
let proposal_members = proposal_members
|
||||
.into_iter()
|
||||
.filter(|m| m != &bob_address)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
debug!("proposal_members: {:?}", proposal_members);
|
||||
|
||||
// we can now show all the addresses + pairing tx to the user on device to prompt confirmation
|
||||
|
||||
debug!("Bob pairs device with Alice");
|
||||
pair_device(pairing_tx.to_owned(), proposal_members.clone()).unwrap();
|
||||
|
||||
let prd_to_respond = prd_update.get_impending_requests().get(0).unwrap().to_string();
|
||||
|
||||
// Bob signs the proposal and sends a prd response too
|
||||
let bob_prd_response = response_prd(root_commitment, prd_to_respond, true)
|
||||
.unwrap()
|
||||
.ciphers_to_send
|
||||
.get(0)
|
||||
.unwrap();
|
||||
|
||||
// To make the pairing effective, alice and bob must now creates a new transaction where they both control one output
|
||||
|
||||
// login logic: user must have access to both devices to login
|
||||
// user must still have access to both devices in case an action needs both devices (as defined in the roles)
|
||||
// process can define that acting on some fields of the state only needs a fraction of the devices to sign
|
||||
|
||||
// Problem: we need both devices to sign the next login transaction.
|
||||
// Simplest solution: device A creates the transaction with both inputs and outputs, signs its input and sends to device B
|
||||
// device B notify user that a login is underway, user either accepts (sign the transaction and broadcast), or refuses (actually we should think of some revokation step from here)
|
||||
|
||||
// login();
|
||||
|
||||
// Once we know this tx id, we can commit to the relay
|
||||
}
|
74
tests/utils.rs
Normal file
74
tests/utils.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use sdk_client::api::{parse_new_tx, ApiReturn};
|
||||
use sdk_common::network::NewTxMessage;
|
||||
use sdk_common::sp_client::bitcoin::consensus::serialize;
|
||||
use sdk_common::sp_client::bitcoin::hex::DisplayHex;
|
||||
use sdk_common::sp_client::bitcoin::secp256k1::PublicKey;
|
||||
use sdk_common::sp_client::bitcoin::{OutPoint, ScriptBuf, Transaction};
|
||||
use sdk_common::sp_client::silentpayments::utils::receiving::{
|
||||
calculate_tweak_data, get_pubkey_from_input,
|
||||
};
|
||||
use sdk_common::sp_client::spclient::{OwnedOutput, SpWallet};
|
||||
use serde_json;
|
||||
|
||||
// We're using alice and bob for clarity, but it's important to remember that for pairing and login Alice and Bob are the same person
|
||||
pub const ALICE_START_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\"}}},\"tx_history\":[]}";
|
||||
pub const ALICE_LOGIN_WALLET: &str = "{\"client\":{\"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]]]},\"network\":\"regtest\"},\"outputs\":{\"wallet_fingerprint\":[187,119,108,230,171,125,106,11],\"birthday\":1620,\"last_scan\":2146,\"outputs\":{\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:1\":{\"blockheight\":0,\"tweak\":\"7c84a3074c18c23a65eceaca49a327989ef6e7240e0e080421afb80f06906d73\",\"amount\":306,\"script\":\"5120eb78084d7a2ccbdb7eb7e9bba7cf875c4e54a5aba0beac57c5d3e41133bd8fdd\",\"label\":null,\"spend_status\":\"Unspent\"},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:2\":{\"blockheight\":0,\"tweak\":\"e3e21d551e933199f8c977bc0362616bdfb128da7ca9728bd7254977541c39cc\",\"amount\":3936897,\"script\":\"5120df3af55a63bd056c5d6d09f47a3a19c382d938c08db8bc41a59c5235970209ad\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":\"Unspent\"},\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f:0\":{\"blockheight\":0,\"tweak\":\"6e5c1d4690b9ff4fa26e5185ada25af5c2987650e629a518db8a43fdaad7a26c\",\"amount\":349,\"script\":\"512088ac90e180c87b7fa6aa33db4c72a8620cd08fda3ba1c71430b904eb068c587f\",\"label\":null,\"spend_status\":\"Unspent\"},\"9a4a67cc5a40bf882d8b300d91024d7c97024b3b68b2df7745a5b9ea1df1888c:1\":{\"blockheight\":1620,\"tweak\":\"b8b63b3ed97d297b744135cfac2fb4a344c881a77543b71f1fcd16bc67514f26\",\"amount\":3938643,\"script\":\"51205b7b324bb71d411e32f2c61fda5d1db23f5c7d6d416a77fab87c913a1b120be1\",\"label\":\"ac14a827e2d023b8f7804303a47259366117d99ed932b641d4a8eaf1b82cc992\",\"spend_status\":{\"Spent\":\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292\"}}}},\"tx_history\":[]}";
|
||||
pub const BOB_START_WALLET: &str = "{\"client\":{\"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]]]},\"network\":\"regtest\"},\"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\"}}},\"tx_history\":[]}";
|
||||
pub const BOB_LOGIN_WALLET: &str = "{\"client\":{\"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]]]},\"network\":\"regtest\"},\"outputs\":{\"wallet_fingerprint\":[203,200,4,248,139,36,241,232],\"birthday\":2146,\"last_scan\":2146,\"outputs\":{\"93722ea2fb9b74954210b4cdd1360e280b7ff1bc156d6b75f847e62411c588fb:0\":{\"blockheight\":0,\"tweak\":\"da5e3aa2378e3a257f99eb1e0ae4c672916f6a2f32a8ed9a8e146f2074da981b\",\"amount\":443,\"script\":\"51209eb9e950127b6a7d81668f25b4d5b164b42dafe59ce40b80e6c489ec983540d7\",\"label\":null,\"spend_status\":\"Unspent\"},\"e2c6ff9927c8a5f7a60087117732c07ab7cd82c0c65462e9c780eb5ce9c35292:0\":{\"blockheight\":0,\"tweak\":\"0e3395ff27bde9187ffaeeb2521f6277d3b83911f16ccbaf59a1a68d99a0ab93\",\"amount\":1200,\"script\":\"512010f06f764cbc923ec3198db946307bf0c06a1b4f09206055e47a6fec0a33d52c\",\"label\":null,\"spend_status\":{\"Spent\":\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f\"}},\"576c2e53fe924d68deb7262cfc0e4023b5889652dec35671e1e7cf255d61c28f:1\":{\"blockheight\":0,\"tweak\":\"f0ad83734cdc7d73575e5a32651390cf30b92cc7e44cf94ec37da46900ecaf71\",\"amount\":654,\"script\":\"5120230cc1e85829be238e666f469653cbc2f1c0e3675a9bf33e1d1f91115f5dd306\",\"label\":\"56572fc770b52096879662f97f98263d3e126f5a4a38f00f2895a9dde4c47c1c\",\"spend_status\":\"Unspent\"},\"fbd9c63e0dd08c2569b51a0d6095a79ee2acfcac66acdb594328a095f1fadb63:1\":{\"blockheight\":2146,\"tweak\":\"678dbcbdb40cd3733c8dbbd508761a0937009cf75a9f466e3c98877e79037cbc\",\"amount\":99896595,\"script\":\"5120deab0c5a3d23de30477b0b5a95a261c96e29afdd9813c665d2bf025ad2b3f919\",\"label\":null,\"spend_status\":\"Unspent\"}}},\"tx_history\":[]}";
|
||||
|
||||
pub const RELAY_ADDRESS: &str = "sprt1qqfmqt0ngq99y8t4ke6uhtm2a2vc2zxvhj7hjrqu599kn30d4cs9rwqn6n079mdr4dfqg72yrtvuxf43yswscw86nvvl09mc5ljx65vfh75fkza35";
|
||||
pub const DEFAULT_NYM: &str = "AliceBob";
|
||||
|
||||
pub fn helper_get_alice_address() -> String {
|
||||
let wallet: SpWallet = serde_json::from_str(ALICE_START_WALLET).unwrap();
|
||||
wallet.get_client().get_receiving_address()
|
||||
}
|
||||
|
||||
pub fn helper_get_bob_address() -> String {
|
||||
let wallet: SpWallet = serde_json::from_str(BOB_START_WALLET).unwrap();
|
||||
wallet.get_client().get_receiving_address()
|
||||
}
|
||||
|
||||
pub fn helper_get_tweak_data(
|
||||
tx: &Transaction,
|
||||
outpoints: HashMap<OutPoint, OwnedOutput>,
|
||||
) -> String {
|
||||
let mut outpoint_data = vec![];
|
||||
let mut witnesses = vec![];
|
||||
let mut spks = vec![];
|
||||
for prevout in tx.input.iter() {
|
||||
outpoint_data.push((
|
||||
prevout.previous_output.txid.to_string(),
|
||||
prevout.previous_output.vout,
|
||||
));
|
||||
witnesses.push(prevout.witness.clone());
|
||||
if let Some(output) = outpoints.get(&prevout.previous_output) {
|
||||
spks.push(ScriptBuf::from_hex(&output.script).unwrap());
|
||||
}
|
||||
}
|
||||
let mut input_pubkeys = vec![];
|
||||
for (spk, witness) in spks.iter().zip(witnesses) {
|
||||
let input_pubkey =
|
||||
get_pubkey_from_input(&vec![], &witness.to_vec(), spk.as_bytes()).unwrap();
|
||||
input_pubkeys.push(input_pubkey.unwrap());
|
||||
}
|
||||
let ref_pubkeys: Vec<&PublicKey> = input_pubkeys.iter().collect();
|
||||
let tweak_data = calculate_tweak_data(&ref_pubkeys, &outpoint_data).unwrap();
|
||||
tweak_data.to_string()
|
||||
}
|
||||
|
||||
pub fn helper_parse_transaction(transaction: &Transaction, tweak_data: &str) -> ApiReturn {
|
||||
let new_tx_msg = serde_json::to_string(&NewTxMessage::new(
|
||||
serialize(transaction).to_lower_hex_string(),
|
||||
Some(tweak_data.to_owned()),
|
||||
))
|
||||
.unwrap();
|
||||
// debug!("new_tx_msg: {:?}", new_tx_msg);
|
||||
let result = parse_new_tx(new_tx_msg, 0, 1);
|
||||
match result {
|
||||
Ok(m) => m,
|
||||
Err(e) => panic!("Unexpected error: {}", e.message),
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user