This commit is contained in:
Sosthene00 2024-04-19 00:23:09 +02:00
parent c57330d389
commit 5cddf0566a
3 changed files with 140 additions and 28 deletions

View File

@ -12,7 +12,6 @@ use sdk_common::crypto::AnkSharedSecret;
use serde_json::Error as SerdeJsonError; use serde_json::Error as SerdeJsonError;
use shamir::SecretData; use shamir::SecretData;
use sp_client::bitcoin::consensus::{deserialize, serialize}; use sp_client::bitcoin::consensus::{deserialize, serialize};
use sp_client::silentpayments::bitcoin_hashes::{HashEngine, sha256, Hash};
use sp_client::bitcoin::hex::{parse, DisplayHex, FromHex, HexToBytesError}; use sp_client::bitcoin::hex::{parse, DisplayHex, FromHex, HexToBytesError};
use sp_client::bitcoin::secp256k1::ecdh::SharedSecret; use sp_client::bitcoin::secp256k1::ecdh::SharedSecret;
use sp_client::bitcoin::secp256k1::{PublicKey, SecretKey}; use sp_client::bitcoin::secp256k1::{PublicKey, SecretKey};
@ -31,7 +30,9 @@ use sp_client::spclient::{derive_keys_from_seed, OutputList, OwnedOutput, SpClie
use sp_client::spclient::{SpWallet, SpendKey}; use sp_client::spclient::{SpWallet, SpendKey};
use crate::images; use crate::images;
use crate::silentpayments::{check_transaction, create_transaction_for_address}; use crate::silentpayments::{
check_transaction, create_transaction_for_address_with_shared_secret, ScannedTransaction,
};
use crate::user::{lock_connected_users, User, UserWallets, CONNECTED_USERS}; use crate::user::{lock_connected_users, User, UserWallets, CONNECTED_USERS};
use crate::process::Process; use crate::process::Process;
@ -392,7 +393,7 @@ pub fn is_tx_owned_by_user(pre_id: String, tx: String) -> ApiResult<bool> {
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub struct createNotificationTransactionReturn { pub struct createNotificationTransactionReturn {
pub transaction: String, pub transaction: String,
pub spaddress2secret: HashMap<String, String>, pub transaction2secret: ScannedTransaction,
} }
#[wasm_bindgen] #[wasm_bindgen]
@ -402,22 +403,17 @@ pub fn create_notification_transaction(
) -> ApiResult<createNotificationTransactionReturn> { ) -> ApiResult<createNotificationTransactionReturn> {
let sp_address: SilentPaymentAddress = recipient.try_into()?; let sp_address: SilentPaymentAddress = recipient.try_into()?;
let (transaction, shared_point) = let (transaction, shared_secret) =
create_transaction_for_address_with_shared_secret(sp_address, message)?; create_transaction_for_address_with_shared_secret(sp_address, message)?;
// The shared_point *must* be hashed to produce a proper ecdh secret
// For now we propose to implement a tagged hash for it
// It could be interesting to add some piece of data to allow for the derivation of multiple secrets
let mut eng = sha256::HashEngine::default();
eng.write_all(&shared_point);
let shared_secret = sha256::Hash::from_engine(eng);
let mut spaddress2secret: HashMap<String, String> = HashMap::new(); // update our cache
spaddress2secret.insert(sp_address.into(), shared_secret.as_byte_array().to_lower_hex_string()); let mut transaction2secret = ScannedTransaction::new();
transaction2secret.get_mut().insert(transaction.txid(), vec![shared_secret]);
Ok(createNotificationTransactionReturn { Ok(createNotificationTransactionReturn {
transaction: serialize(&transaction).to_lower_hex_string(), transaction: serialize(&transaction).to_lower_hex_string(),
spaddress2secret, transaction2secret,
}) })
} }

View File

@ -1,14 +1,23 @@
use std::collections::HashMap; // This file should move to common
use std::collections::{HashMap, HashSet};
use std::io::Write;
use std::iter::Once;
use std::sync::{Mutex, MutexGuard, OnceLock};
use anyhow::{Error, Result}; use anyhow::{Error, Result};
use log::debug;
use rand::Rng; use rand::Rng;
use sdk_common::crypto::{Aes256Encryption, Aes256Gcm, AeadCore, Purpose};
use serde::{Deserialize, Serialize};
use sp_client::bitcoin::policy::DUST_RELAY_TX_FEE; use sp_client::bitcoin::policy::DUST_RELAY_TX_FEE;
use sp_client::bitcoin::secp256k1::ecdh::shared_secret_point; use sp_client::bitcoin::secp256k1::ecdh::shared_secret_point;
use sp_client::bitcoin::{block, Amount, OutPoint}; use sp_client::bitcoin::{block, Amount, OutPoint, Txid};
use sp_client::silentpayments::sending::SilentPaymentAddress; use sp_client::silentpayments::sending::SilentPaymentAddress;
use sp_client::silentpayments::utils::receiving::calculate_shared_secret; use sp_client::silentpayments::utils::receiving::calculate_shared_secret;
use sp_client::spclient::{OutputList, OwnedOutput, Recipient, SpClient, SpWallet}; use sp_client::spclient::{OutputList, OwnedOutput, Recipient, SpClient, SpWallet};
use sp_client::silentpayments::bitcoin_hashes::{sha256, Hash, HashEngine};
use sp_client::{ use sp_client::{
bitcoin::{ bitcoin::{
secp256k1::{PublicKey, Scalar, XOnlyPublicKey}, secp256k1::{PublicKey, Scalar, XOnlyPublicKey},
@ -16,17 +25,75 @@ use sp_client::{
}, },
silentpayments::receiving::Label, silentpayments::receiving::Label,
}; };
use tsify::Tsify;
use crate::user::{lock_connected_users, CONNECTED_USERS}; use crate::user::{lock_connected_users, CONNECTED_USERS};
use crate::MutexExt;
#[derive(Debug, Serialize, Deserialize, Tsify)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct ScannedTransaction(HashMap<Txid, Vec<SharedSecret>>);
impl ScannedTransaction {
pub fn new() -> Self {
Self(HashMap::new())
}
pub fn get_mut(&mut self) -> &mut HashMap<Txid, Vec<SharedSecret>> {
&mut self.0
}
}
pub static TRANSACTIONCACHE: OnceLock<Mutex<ScannedTransaction>> = OnceLock::new();
pub fn lock_scanned_transactions() -> Result<MutexGuard<'static, ScannedTransaction>> {
TRANSACTIONCACHE
.get_or_init(|| Mutex::new(ScannedTransaction::new()))
.lock_anyhow()
}
type FoundOutputs = HashMap<Option<Label>, HashMap<XOnlyPublicKey, Scalar>>; type FoundOutputs = HashMap<Option<Label>, HashMap<XOnlyPublicKey, Scalar>>;
type SharedPoint = [u8;64]; #[derive(Debug)]
pub struct SharedPoint([u8; 64]);
impl SharedPoint {
pub fn as_inner(&self) -> &[u8; 64] {
&self.0
}
}
#[derive(Debug, Serialize, Deserialize, Tsify)]
#[tsify(from_wasm_abi, into_wasm_abi)]
pub struct SharedSecret {
secret: [u8; 32],
shared_with: Option<String>, // SilentPaymentAddress
}
impl SharedSecret {
pub fn new(secret: [u8;32], shared_with: Option<String>) -> Result<Self> {
if let Some(ref shared) = shared_with {
if let Ok(_) = SilentPaymentAddress::try_from(shared.as_str()) {
Ok(Self {
secret,
shared_with
})
} else {
Err(Error::msg("Invalid silent payment address"))
}
} else {
Ok(Self {
secret,
shared_with: None
})
}
}
}
pub fn create_transaction_for_address_with_shared_secret( pub fn create_transaction_for_address_with_shared_secret(
sp_address: SilentPaymentAddress, sp_address: SilentPaymentAddress,
message: String, message: String,
) -> Result<(Transaction, SharedPoint)> { ) -> Result<(Transaction, SharedSecret)> {
let connected_users = lock_connected_users()?; let connected_users = lock_connected_users()?;
let (_, wallets) = connected_users let (_, wallets) = connected_users
@ -34,6 +101,8 @@ pub fn create_transaction_for_address_with_shared_secret(
.last() .last()
.ok_or(Error::msg("Unknown sender"))?; .ok_or(Error::msg("Unknown sender"))?;
debug!("Got user wallets");
let sp_wallet = if sp_address.is_testnet() { let sp_wallet = if sp_address.is_testnet() {
&wallets.recover &wallets.recover
} else { } else {
@ -77,11 +146,38 @@ pub fn create_transaction_for_address_with_shared_secret(
let mut new_psbt = sp_wallet let mut new_psbt = sp_wallet
.get_client() .get_client()
.create_new_psbt(inputs, vec![recipient], None)?; .create_new_psbt(inputs, vec![recipient], Some(message.as_bytes()))?;
log::debug!("Created psbt: {}", new_psbt); log::debug!("Created psbt: {}", new_psbt);
SpClient::set_fees(&mut new_psbt, Amount::from_sat(1000), sp_address.into())?; SpClient::set_fees(&mut new_psbt, Amount::from_sat(1000), sp_address.into())?;
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 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);
// The shared_point *must* be hashed to produce a proper ecdh secret
let mut eng = sha256::HashEngine::default();
eng.write_all(&shared_point);
let shared_secret = sha256::Hash::from_engine(eng);
// encrypt the message with the new shared_secret
let message_encryption = Aes256Encryption::import_key(
Purpose::Arbitrary,
message.into_bytes(),
shared_secret.to_byte_array(),
Aes256Gcm::generate_nonce(&mut rand::thread_rng()).into(),
)?;
let cipher = message_encryption.encrypt_with_aes_key()?;
sp_client::spclient::SpClient::replace_op_return_with(&mut new_psbt, &cipher)?;
sp_wallet
.get_client()
.fill_sp_outputs(&mut new_psbt, partial_secret)?;
log::debug!("Definitive psbt: {}", new_psbt); log::debug!("Definitive psbt: {}", new_psbt);
let mut aux_rand = [0u8; 32]; let mut aux_rand = [0u8; 32];
rand::thread_rng().fill(&mut aux_rand); rand::thread_rng().fill(&mut aux_rand);
@ -91,17 +187,24 @@ pub fn create_transaction_for_address_with_shared_secret(
let final_tx = signed.extract_tx()?; let final_tx = signed.extract_tx()?;
// This should not be directly used without hashing Ok((final_tx, SharedSecret {
let shared_point = shared_secret_point(&sp_address.get_scan_key(), &partial_secret); secret: shared_secret.to_byte_array(),
shared_with: Some(sp_address.into())
Ok((final_tx, shared_point)) }))
} }
// This need to go
pub fn check_transaction( pub fn check_transaction(
tx: &Transaction, tx: &Transaction,
blockheight: u32, blockheight: u32,
tweak_data: PublicKey, tweak_data: PublicKey,
) -> Result<String> { ) -> Result<String> {
// first check that we haven't scanned this transaction yet
if let Some((txid, _)) = lock_scanned_transactions()?.0.get_key_value(&tx.txid()) {
let err_msg = format!("Already scanned tx {}", txid);
return Err(Error::msg(err_msg));
}
let connected_users = lock_connected_users()?; let connected_users = lock_connected_users()?;
let txid = tx.txid().to_string(); let txid = tx.txid().to_string();

View File

@ -1,4 +1,4 @@
import { createUserReturn, User, Process, createNotificationTransactionReturn, parse_network_msg, outputs_list, parseNetworkMsgReturn, FaucetMessage, AnkFlag } from '../dist/pkg/sdk_client'; import { createUserReturn, User, Process, createNotificationTransactionReturn, parse_network_msg, outputs_list, parseNetworkMsgReturn, FaucetMessage, AnkFlag, NewTxMessage } from '../dist/pkg/sdk_client';
import IndexedDB from './database' import IndexedDB from './database'
import { WebSocketClient } from './websockets'; import { WebSocketClient } from './websockets';
@ -86,7 +86,10 @@ class Services {
const message = messageElement.value; const message = messageElement.value;
const services = await Services.getInstance(); const services = await Services.getInstance();
let notificationInfo = services.notify_address_for_message(recipientSpAddress, message); let notificationInfo = await services.notify_address_for_message(recipientSpAddress, message);
if (notificationInfo) {
notificationInfo.transaction
}
console.log(notificationInfo); console.log(notificationInfo);
} }
@ -751,6 +754,10 @@ class Services {
public async notify_address_for_message(sp_address: string, message: string): Promise<createNotificationTransactionReturn | null> { public async notify_address_for_message(sp_address: string, message: string): Promise<createNotificationTransactionReturn | null> {
const services = await Services.getInstance(); const services = await Services.getInstance();
const connection = await services.pickWebsocketConnectionRandom();
if (!connection) {
return null;
}
let user: User; let user: User;
try { try {
let possibleUser = await services.getUserInfo(); let possibleUser = await services.getUserInfo();
@ -766,9 +773,15 @@ class Services {
try { try {
let notificationInfo: createNotificationTransactionReturn = services.sdkClient.create_notification_transaction(user, sp_address, message); let notificationInfo: createNotificationTransactionReturn = services.sdkClient.create_notification_transaction(user, sp_address, message);
const flag: AnkFlag = "NewTx";
const newTxMsg: NewTxMessage = {
'transaction': notificationInfo.transaction,
'tweak_data': null
}
connection.sendMessage(flag, JSON.stringify(newTxMsg));
return notificationInfo; return notificationInfo;
} catch { } catch (error) {
console.error("Failed to create notification transaction for user", user); console.error("Failed to create notification transaction:", error);
return null return null
} }
} }