WIP
This commit is contained in:
parent
c57330d389
commit
5cddf0566a
@ -12,7 +12,6 @@ use sdk_common::crypto::AnkSharedSecret;
|
||||
use serde_json::Error as SerdeJsonError;
|
||||
use shamir::SecretData;
|
||||
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::secp256k1::ecdh::SharedSecret;
|
||||
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 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::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)]
|
||||
pub struct createNotificationTransactionReturn {
|
||||
pub transaction: String,
|
||||
pub spaddress2secret: HashMap<String, String>,
|
||||
pub transaction2secret: ScannedTransaction,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
@ -402,22 +403,17 @@ pub fn create_notification_transaction(
|
||||
) -> ApiResult<createNotificationTransactionReturn> {
|
||||
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)?;
|
||||
|
||||
// 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 {
|
||||
transaction: serialize(&transaction).to_lower_hex_string(),
|
||||
spaddress2secret,
|
||||
transaction2secret,
|
||||
})
|
||||
}
|
||||
|
@ -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 log::debug;
|
||||
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::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::utils::receiving::calculate_shared_secret;
|
||||
use sp_client::spclient::{OutputList, OwnedOutput, Recipient, SpClient, SpWallet};
|
||||
use sp_client::silentpayments::bitcoin_hashes::{sha256, Hash, HashEngine};
|
||||
use sp_client::{
|
||||
bitcoin::{
|
||||
secp256k1::{PublicKey, Scalar, XOnlyPublicKey},
|
||||
@ -16,17 +25,75 @@ use sp_client::{
|
||||
},
|
||||
silentpayments::receiving::Label,
|
||||
};
|
||||
use tsify::Tsify;
|
||||
|
||||
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 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(
|
||||
sp_address: SilentPaymentAddress,
|
||||
message: String,
|
||||
) -> Result<(Transaction, SharedPoint)> {
|
||||
) -> Result<(Transaction, SharedSecret)> {
|
||||
let connected_users = lock_connected_users()?;
|
||||
|
||||
let (_, wallets) = connected_users
|
||||
@ -34,6 +101,8 @@ pub fn create_transaction_for_address_with_shared_secret(
|
||||
.last()
|
||||
.ok_or(Error::msg("Unknown sender"))?;
|
||||
|
||||
debug!("Got user wallets");
|
||||
|
||||
let sp_wallet = if sp_address.is_testnet() {
|
||||
&wallets.recover
|
||||
} else {
|
||||
@ -77,11 +146,38 @@ pub fn create_transaction_for_address_with_shared_secret(
|
||||
|
||||
let mut new_psbt = sp_wallet
|
||||
.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);
|
||||
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);
|
||||
let mut aux_rand = [0u8; 32];
|
||||
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()?;
|
||||
|
||||
// This should not be directly used without hashing
|
||||
let shared_point = shared_secret_point(&sp_address.get_scan_key(), &partial_secret);
|
||||
|
||||
Ok((final_tx, shared_point))
|
||||
Ok((final_tx, SharedSecret {
|
||||
secret: shared_secret.to_byte_array(),
|
||||
shared_with: Some(sp_address.into())
|
||||
}))
|
||||
}
|
||||
|
||||
// This need to go
|
||||
pub fn check_transaction(
|
||||
tx: &Transaction,
|
||||
blockheight: u32,
|
||||
tweak_data: PublicKey,
|
||||
) -> 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 txid = tx.txid().to_string();
|
||||
|
@ -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 { WebSocketClient } from './websockets';
|
||||
|
||||
@ -86,7 +86,10 @@ class Services {
|
||||
const message = messageElement.value;
|
||||
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);
|
||||
}
|
||||
|
||||
@ -751,6 +754,10 @@ class Services {
|
||||
|
||||
public async notify_address_for_message(sp_address: string, message: string): Promise<createNotificationTransactionReturn | null> {
|
||||
const services = await Services.getInstance();
|
||||
const connection = await services.pickWebsocketConnectionRandom();
|
||||
if (!connection) {
|
||||
return null;
|
||||
}
|
||||
let user: User;
|
||||
try {
|
||||
let possibleUser = await services.getUserInfo();
|
||||
@ -766,9 +773,15 @@ class Services {
|
||||
|
||||
try {
|
||||
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;
|
||||
} catch {
|
||||
console.error("Failed to create notification transaction for user", user);
|
||||
} catch (error) {
|
||||
console.error("Failed to create notification transaction:", error);
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user