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 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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user