Working pairing

This commit is contained in:
Sosthene 2024-09-25 12:51:23 +02:00
parent 1871dc9a5d
commit 836f6cf900
5 changed files with 76 additions and 212 deletions

View File

@ -20,10 +20,9 @@ use anyhow::Error as AnyhowError;
use anyhow::Result as AnyhowResult;
use sdk_common::aes_gcm::aead::{Aead, Payload};
use sdk_common::crypto::{
AeadCore, Aes256Decryption, Aes256Encryption, Aes256Gcm, AnkSharedSecret, AnkSharedSecretHash,
KeyInit, Purpose, AAD,
encrypt_with_key, AeadCore, Aes256Gcm, AnkSharedSecretHash, KeyInit, AAD
};
use sdk_common::process::Process;
use sdk_common::process::{lock_processes, Process, ProcessState};
use sdk_common::signature::{AnkHash, AnkValidationNoHash, AnkValidationYesHash, Proof};
use sdk_common::sp_client::bitcoin::blockdata::fee_rate;
use sdk_common::sp_client::bitcoin::consensus::{deserialize, serialize};
@ -50,7 +49,6 @@ use sdk_common::sp_client::silentpayments::{
utils::{Network as SpNetwork, SilentPaymentAddress},
Error as SpError,
};
use sdk_common::uuid::Uuid;
use sdk_common::{signature, MAX_PRD_PAYLOAD_SIZE};
use serde_json::{Error as SerdeJsonError, Map, Value};
@ -77,7 +75,7 @@ use sdk_common::sp_client::spclient::{SpWallet, SpendKey};
use crate::user::{lock_local_device, set_new_device, LOCAL_DEVICE};
use crate::wallet::{generate_sp_wallet, lock_freezed_utxos};
use crate::{
lock_messages, lock_processes, ProcessState, RelevantProcess, CACHEDMESSAGES, CACHEDPROCESSES,
lock_messages, CACHEDMESSAGES,
};
#[derive(Debug, PartialEq, Tsify, Serialize, Deserialize, Default)]
@ -85,7 +83,7 @@ use crate::{
#[allow(non_camel_case_types)]
pub struct ApiReturn {
pub updated_cached_msg: Vec<CachedMessage>,
pub updated_process: Option<(String, RelevantProcess)>,
pub updated_process: Option<(String, Process)>,
pub new_tx_to_send: Option<Transaction>,
pub ciphers_to_send: Vec<String>,
pub commit_to_send: Option<CommitMessage>,
@ -263,6 +261,15 @@ pub fn pair_device(commitment_tx: String, mut sp_addresses: Vec<String>) -> ApiR
Ok(())
}
#[wasm_bindgen]
pub fn unpair_device() -> ApiResult<()> {
let mut local_device = lock_local_device()?;
local_device.unpair();
Ok(())
}
#[derive(Debug, Tsify, Serialize, Deserialize)]
#[tsify(from_wasm_abi, into_wasm_abi)]
#[allow(non_camel_case_types)]
@ -324,7 +331,7 @@ pub fn dump_process_cache() -> ApiResult<String> {
#[wasm_bindgen]
pub fn set_process_cache(processes: String) -> ApiResult<()> {
let processes: HashMap<OutPoint, RelevantProcess> = serde_json::from_str(&processes)?;
let processes: HashMap<OutPoint, Process> = serde_json::from_str(&processes)?;
let mut cached_processes = lock_processes()?;
@ -433,7 +440,7 @@ fn handle_transaction(
&tweak_data,
&sp_wallet.get_client().get_scan_key(),
);
let shared_secret = AnkSharedSecret::new(shared_point);
let shared_secret = AnkSharedSecretHash::from_shared_point(shared_point);
let mut plaintext: Vec<u8> = vec![];
if let Some(message) = messages.iter_mut().find(|m| {
@ -469,23 +476,20 @@ fn handle_transaction(
let outpoint = OutPoint::from_str(&prd.root_commitment)?;
let updated_process: RelevantProcess;
let updated_process: Process;
if let Some(process) = lock_processes()?.get_mut(&outpoint) {
process.shared_secrets.insert(
actual_sender,
shared_secret.to_byte_array().to_lower_hex_string(),
process.insert_shared_secret(
SilentPaymentAddress::try_from(actual_sender.as_str()).unwrap(),
shared_secret,
);
updated_process = process.clone();
} else {
let mut shared_secrets = HashMap::new();
shared_secrets.insert(
actual_sender,
shared_secret.to_byte_array().to_lower_hex_string(),
SilentPaymentAddress::try_from(actual_sender.as_str()).unwrap(),
shared_secret,
);
let new_process = RelevantProcess {
shared_secrets,
..Default::default()
};
let new_process = Process::new(vec![], shared_secrets, vec![]);
lock_processes()?.insert(outpoint, new_process.clone());
updated_process = new_process;
}
@ -575,18 +579,13 @@ pub fn parse_new_tx(new_tx_msg: String, block_height: u32, fee_rate: u32) -> Api
fn try_decrypt_with_processes(
cipher: &[u8],
processes: MutexGuard<HashMap<OutPoint, RelevantProcess>>,
processes: MutexGuard<HashMap<OutPoint, Process>>,
) -> Option<(Vec<u8>, SilentPaymentAddress, OutPoint)> {
let nonce = Nonce::from_slice(&cipher[..12]);
for (outpoint, process) in processes.iter() {
for (address, secret) in &process.shared_secrets {
let aes_key = match AnkSharedSecretHash::from_str(secret) {
Ok(key) => key,
Err(_) => continue,
};
let engine = Aes256Gcm::new(aes_key.as_byte_array().into());
for (address, secret) in process.get_all_secrets() {
let engine = Aes256Gcm::new(&secret.to_byte_array().into());
if let Ok(plain) = engine.decrypt(
&nonce,
Payload {
@ -596,7 +595,7 @@ fn try_decrypt_with_processes(
) {
return Some((
plain,
SilentPaymentAddress::try_from(address.as_str()).unwrap(),
address,
*outpoint,
));
}
@ -655,7 +654,7 @@ pub fn response_prd(
};
}
fn confirm_prd(prd: Prd, shared_secret: &str) -> AnyhowResult<String> {
fn confirm_prd(prd: Prd, shared_secret: &AnkSharedSecretHash) -> AnyhowResult<String> {
match prd.prd_type {
PrdType::Confirm | PrdType::Response | PrdType::List => {
return Err(AnyhowError::msg("Invalid prd type"));
@ -690,16 +689,16 @@ fn confirm_prd(prd: Prd, shared_secret: &str) -> AnyhowResult<String> {
let prd_msg = prd_confirm.to_network_msg(local_device.get_wallet())?;
Ok(encrypt_with_key(prd_msg, shared_secret.to_owned()).unwrap())
Ok(encrypt_with_key(shared_secret.as_byte_array(), prd_msg.as_bytes())?.to_lower_hex_string())
}
fn send_data(prd: &Prd, shared_secret: &str) -> AnyhowResult<ApiReturn> {
fn send_data(prd: &Prd, shared_secret: &AnkSharedSecretHash) -> AnyhowResult<ApiReturn> {
let pcd = &prd.payload;
let cipher = encrypt_with_key(pcd.clone(), shared_secret.to_owned()).unwrap();
let cipher = encrypt_with_key(shared_secret.as_byte_array(), pcd.as_bytes())?;
Ok(ApiReturn {
ciphers_to_send: vec![cipher],
ciphers_to_send: vec![cipher.to_lower_hex_string()],
..Default::default()
})
}
@ -767,17 +766,9 @@ fn decrypt_with_known_processes(cipher: &[u8]) -> anyhow::Result<Option<(Vec<u8>
let nonce = Nonce::from_slice(&cipher[..12]);
for (outpoint, process) in processes.iter() {
for (address, secret) in &process.shared_secrets {
for (address, secret) in process.get_all_secrets() {
debug!("Attempting decryption with key {} for {}", secret, address);
let aes_key = match AnkSharedSecretHash::from_str(secret) {
Ok(key) => key,
Err(_) => {
debug!("Invalid shared secret for process {}: {}", outpoint, secret);
continue;
}
};
let engine = Aes256Gcm::new(aes_key.as_byte_array().into());
let engine = Aes256Gcm::new(secret.as_byte_array().into());
if let Ok(plain) = engine.decrypt(
&nonce,
@ -832,13 +823,10 @@ fn handle_prd(
.ok_or_else(|| anyhow::Error::msg("Missing shared secret for new process"))?;
let mut shared_secrets = HashMap::new();
shared_secrets.insert(
sp_address.to_string(),
shared_secret.to_byte_array().to_lower_hex_string(),
sp_address,
shared_secret,
);
entry.insert(RelevantProcess {
shared_secrets,
..Default::default()
})
entry.insert(Process::new(vec![], shared_secrets, vec![]))
}
};
@ -848,8 +836,8 @@ fn handle_prd(
// We send the whole data in a pcd
debug!("Received confirm prd {:#?}", prd);
let original_request = relevant_process
.impending_requests
.iter()
.get_impending_requests()
.into_iter()
.find(|r| {
if r.prd_type != PrdType::Update {
return false;
@ -859,27 +847,25 @@ fn handle_prd(
})
.ok_or(anyhow::Error::msg("Original request not found"))?;
let shared_secret = relevant_process
.shared_secrets
.get(&sp_address.to_string())
.get_shared_secret_for_address(&sp_address)
.ok_or(anyhow::Error::msg(
"Missing shared secret for address in original request",
))?;
return send_data(original_request, shared_secret);
return send_data(original_request, &shared_secret);
}
PrdType::Update | PrdType::TxProposal | PrdType::Message => {
// Those all have some new data we don't know about yet
// We send a Confirm to get the pcd
// Add the prd to our list of actions for this process
relevant_process.impending_requests.push(prd.clone());
relevant_process.insert_impending_request(prd.clone());
let shared_secret = relevant_process
.shared_secrets
.get(&sp_address.to_string())
.get_shared_secret_for_address(&sp_address)
.ok_or(anyhow::Error::msg(
"Missing shared secret for address in original request",
))?;
let cipher = confirm_prd(prd, shared_secret)?;
let cipher = confirm_prd(prd, &shared_secret)?;
return Ok(ApiReturn {
ciphers_to_send: vec![cipher],
@ -890,8 +876,8 @@ fn handle_prd(
PrdType::Response => {
// We must know of a prd update that the response answers to
let original_request = relevant_process
.impending_requests
.iter()
.get_impending_requests()
.into_iter()
.find(|r| {
if r.prd_type != PrdType::Update {
return false;
@ -919,8 +905,8 @@ fn handle_pcd(plain: Vec<u8>, root_commitment: OutPoint) -> AnyhowResult<ApiRetu
// We match the pcd with a prd and act accordingly
let prd = relevant_process
.impending_requests
.iter_mut()
.get_impending_requests_mut()
.into_iter()
.find(|r| *r.payload == pcd_commitment.to_string())
.ok_or(AnyhowError::msg("Failed to retrieve the matching prd"))?;
@ -1023,7 +1009,7 @@ fn handle_decrypted_message(
// let mut processes = lock_processes()?;
// let updated_process: RelevantProcess;
// let updated_process: Process;
// if let Some(process) = processes.get_mut(&root_outpoint) {
// // we're actually replacing the shared_secret for that process and that sender if it exists
// process.shared_secrets.insert(
@ -1037,7 +1023,7 @@ fn handle_decrypted_message(
// actual_sender,
// Vec::from_hex(shared_secret)?.to_lower_hex_string(),
// );
// let new_process = RelevantProcess {
// let new_process = Process {
// shared_secrets,
// ..Default::default()
// };
@ -1181,14 +1167,14 @@ pub fn create_update_transaction(
let mut processes = lock_processes()?;
let commitment_outpoint: OutPoint;
let relevant_process: &mut RelevantProcess;
let relevant_process: &mut Process;
if let Some(s) = init_commitment {
// We're updating an existing contract
let outpoint = OutPoint::from_str(&s)?;
if let Some(p) = processes.get_mut(&outpoint) {
// compare the provided new_state with the process defined template
let previous_state = &p.states.first().unwrap().encrypted_pcd;
let previous_state = &p.get_status_at(0).unwrap().encrypted_pcd;
if !compare_maps(previous_state.as_object().unwrap(), pcd_map) {
return Err(ApiError::new(
"Provided updated state is not consistent with the process template".to_owned(),
@ -1198,7 +1184,7 @@ pub fn create_update_transaction(
commitment_outpoint = outpoint;
} else {
// This is a process we don't know about, so we insert a new entry
processes.insert(outpoint, RelevantProcess::default());
processes.insert(outpoint, Process::default());
relevant_process = processes.get_mut(&outpoint).unwrap();
commitment_outpoint = outpoint;
}
@ -1210,7 +1196,7 @@ pub fn create_update_transaction(
let dummy_outpoint = OutPoint::new(Txid::from_slice(dummy.as_byte_array())?, u32::MAX);
processes.insert(dummy_outpoint, RelevantProcess::default());
processes.insert(dummy_outpoint, Process::default());
relevant_process = processes.get_mut(&dummy_outpoint).unwrap();
commitment_outpoint = dummy_outpoint;
@ -1305,7 +1291,6 @@ pub fn create_update_transaction(
let final_tx = signed_psbt.extract_tx()?;
let mut ciphers = vec![];
let mut shared_secrets = HashMap::new();
for (member, visible_fields) in all_members {
let mut prd = full_prd.clone();
prd.filter_keys(visible_fields);
@ -1323,18 +1308,15 @@ pub fn create_update_transaction(
&partial_secret,
);
let shared_secret = AnkSharedSecret::new(shared_point)
.to_byte_array()
.to_lower_hex_string();
let shared_secret = AnkSharedSecretHash::from_shared_point(shared_point);
let cipher = encrypt_with_key(prd_msg.clone(), shared_secret.clone())?;
ciphers.push(cipher);
shared_secrets.insert(sp_address, shared_secret);
let cipher = encrypt_with_key(shared_secret.as_byte_array(), prd_msg.as_bytes())?;
ciphers.push(cipher.to_lower_hex_string());
relevant_process.insert_shared_secret(SilentPaymentAddress::try_from(sp_address)?, shared_secret);
}
}
relevant_process.impending_requests.push(full_prd);
relevant_process.shared_secrets.extend(shared_secrets);
relevant_process.states.push(ProcessState {
relevant_process.insert_impending_request(full_prd);
relevant_process.insert_state(ProcessState {
commited_in: OutPoint::null(),
encrypted_pcd: Value::Object(fields2cipher),
keys: fields2keys,
@ -1357,64 +1339,6 @@ pub struct encryptWithNewKeyResult {
pub key: String,
}
#[wasm_bindgen]
pub fn encrypt_with_key(plaintext: String, key: String) -> ApiResult<String> {
let nonce = Aes256Gcm::generate_nonce(&mut rand::thread_rng());
let mut aes_key = [0u8; 32];
aes_key.copy_from_slice(&Vec::from_hex(&key)?);
// encrypt
let aes_enc = Aes256Encryption::import_key(
Purpose::Arbitrary,
plaintext.into_bytes(),
aes_key,
nonce.into(),
)?;
let cipher = aes_enc.encrypt_with_aes_key()?;
Ok(cipher.to_lower_hex_string())
}
#[wasm_bindgen]
pub fn encrypt_with_new_key(plaintext: String) -> ApiResult<encryptWithNewKeyResult> {
let mut rng = rand::thread_rng();
// generate new key
let aes_key = Aes256Gcm::generate_key(&mut rng);
let nonce = Aes256Gcm::generate_nonce(&mut rng);
// encrypt
let aes_enc = Aes256Encryption::import_key(
Purpose::Arbitrary,
plaintext.into_bytes(),
aes_key.into(),
nonce.into(),
)?;
let cipher = aes_enc.encrypt_with_aes_key()?;
Ok(encryptWithNewKeyResult {
cipher: cipher.to_lower_hex_string(),
key: aes_key.to_lower_hex_string(),
})
}
#[wasm_bindgen]
pub fn try_decrypt_with_key(cipher: String, key: String) -> ApiResult<String> {
let key_bin = Vec::from_hex(&key)?;
if key_bin.len() != 32 {
return Err(ApiError::new("key of invalid lenght".to_owned()));
}
let mut aes_key = [0u8; 32];
aes_key.copy_from_slice(&Vec::from_hex(&key)?);
let aes_dec = Aes256Decryption::new(Purpose::Arbitrary, Vec::from_hex(&cipher)?, aes_key)?;
let plain = String::from_utf8(aes_dec.decrypt_with_key()?)?;
Ok(plain)
}
#[wasm_bindgen]
pub fn create_faucet_msg() -> ApiResult<String> {
let sp_address = lock_local_device()?
@ -1442,8 +1366,8 @@ pub fn get_update_proposals(process_outpoint: String) -> ApiResult<Vec<String>>
.ok_or(ApiError::new("process not found".to_owned()))?;
let update_proposals: Vec<&Prd> = relevant_process
.impending_requests
.iter()
.get_impending_requests()
.into_iter()
.filter(|r| r.prd_type == PrdType::Update)
.collect();

View File

@ -1,21 +1,8 @@
#![allow(warnings)]
use anyhow::Error;
use sdk_common::crypto::AnkSharedSecret;
use sdk_common::network::CachedMessage;
use sdk_common::pcd::AnkPcdHash;
use sdk_common::prd::Prd;
use sdk_common::process::Process;
use sdk_common::signature::Proof;
use sdk_common::sp_client::bitcoin::OutPoint;
use sdk_common::uuid::Uuid;
use sdk_common::MutexExt;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::collections::{HashMap, HashSet};
use std::fmt::Debug;
use std::ops::Index;
use std::sync::{Mutex, MutexGuard, OnceLock};
use tsify::Tsify;
pub mod api;
mod peers;
@ -29,41 +16,3 @@ pub fn lock_messages() -> Result<MutexGuard<'static, Vec<CachedMessage>>, Error>
.get_or_init(|| Mutex::new(vec![]))
.lock_anyhow()
}
// TODO move to sdk-common
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct ProcessState {
pub commited_in: OutPoint,
pub encrypted_pcd: Value,
pub keys: Map<String, Value>, // We may not always have all the keys
pub validation_token: Vec<Proof>, // This signs the encrypted pcd
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct RelevantProcess {
states: Vec<ProcessState>,
shared_secrets: HashMap<String, String>,
impending_requests: Vec<Prd>,
}
impl RelevantProcess {
pub fn get_status_at(&self, index: usize) -> Option<ProcessState> {
self.states.get(index).cloned()
}
pub fn get_latest_state(&self) -> Option<ProcessState> {
self.states.last().cloned()
}
pub fn get_impending_requests(&self) -> Vec<Prd> {
self.impending_requests.clone()
}
}
pub static CACHEDPROCESSES: OnceLock<Mutex<HashMap<OutPoint, RelevantProcess>>> = OnceLock::new();
pub fn lock_processes() -> Result<MutexGuard<'static, HashMap<OutPoint, RelevantProcess>>, Error> {
CACHEDPROCESSES
.get_or_init(|| Mutex::new(HashMap::new()))
.lock_anyhow()
}

View File

@ -9,7 +9,6 @@ use sdk_common::sp_client::bitcoin::{
Network, OutPoint, ScriptBuf, Transaction, Txid, XOnlyPublicKey,
};
use sdk_common::sp_client::spclient::SpClient;
use sdk_common::uuid::Uuid;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use tsify::Tsify;
@ -31,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();

View File

@ -57,12 +57,12 @@ fn test_pairing() {
"validation_rules":
[
{
"quorum": 0.0,
"quorum": 1.0,
"fields": [
"roles",
"pairing_tx"
],
"min_sig_member": 0.0
"min_sig_member": 1.0
}
]
}
@ -114,7 +114,6 @@ fn test_pairing() {
// TODO unpair device
// We can produce the prd response now even if we can't use it yet
// TODO pass the process as argument
let alice_prd_response = response_prd(
root_outpoint,
alice_init_process
@ -130,6 +129,8 @@ fn test_pairing() {
.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();
@ -263,6 +264,15 @@ fn test_pairing() {
.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

File diff suppressed because one or more lines are too long