Merge branch 'minimal_pcd_prd' into dev

This commit is contained in:
Sosthene 2024-10-02 16:22:39 +02:00
commit a592001fdb
9 changed files with 1360 additions and 1940 deletions

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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)))
}
}

View File

@ -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
}

View File

@ -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(())
}
}

View File

@ -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()
);

File diff suppressed because one or more lines are too long

283
tests/pairing.rs Normal file
View 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
View 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),
}
}