-
Notifications
You must be signed in to change notification settings - Fork 11
silent payments: integrate SPDK scanning and wallet IPC #50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,9 @@ | ||
| @0xc2e20cc9503cf68f; | ||
|
|
||
| using Wallet = import "wallet.capnp"; | ||
|
|
||
| interface Server { | ||
| echo @0 (msg :Text) -> (reply :Text); | ||
| shutdown @1 () -> (); | ||
| makeWallet @2 () -> (wallet :Wallet.Wallet); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| @0xb5d3a2f1e8c47690; | ||
|
|
||
| interface Wallet { | ||
| importKeys @0 (scanKey :Data, spendKey :Data) -> (ok :Bool, message :Text); | ||
| getBalance @1 () -> (sats :UInt64, scanHeight :UInt32, utxoCount :UInt32); | ||
| getHistory @2 () -> (entries :Text); | ||
| receive @3 () -> (address :Text); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| [package] | ||
| name = "wallet" | ||
| version = "0.1.0" | ||
| edition = "2021" | ||
|
|
||
| [dependencies] | ||
| silentpayments = { version = "0.5", features = ["receiving", "encode"] } | ||
| bitcoin = "0.32.8" | ||
| bitcoinkernel = "0.2" | ||
| log = "0.4" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| pub mod silentpayments; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| mod scanning; | ||
| mod wallet; | ||
|
|
||
| pub use ::silentpayments::receiving::{Label, Receiver}; | ||
| pub use ::silentpayments::{Network, SilentPaymentAddress}; | ||
| pub use scanning::{scan_transaction, InputData}; | ||
| pub use wallet::{Coin, HistoryEntry, SilentPaymentKeys, SpentBy, Wallet}; | ||
|
|
||
| use bitcoin::secp256k1::{self, PublicKey, SecretKey}; | ||
|
|
||
| pub fn build_receiver( | ||
| b_scan: &SecretKey, | ||
| b_spend_pub: PublicKey, | ||
| network: Network, | ||
| ) -> Result<Receiver, ::silentpayments::Error> { | ||
| let secp = secp256k1::Secp256k1::signing_only(); | ||
| let scan_pubkey = PublicKey::from_secret_key(&secp, b_scan); | ||
| let change_label = Label::new(*b_scan, 0); | ||
| Receiver::new(0, scan_pubkey, b_spend_pub, change_label, network) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| use ::silentpayments::{ | ||
| receiving::{Label, Receiver}, | ||
| utils::receiving::{calculate_ecdh_shared_secret, calculate_tweak_data, get_pubkey_from_input}, | ||
| }; | ||
| use bitcoin::consensus::encode; | ||
| use bitcoin::secp256k1::{self, Scalar, SecretKey, XOnlyPublicKey}; | ||
| use bitcoin::{OutPoint, Script, Transaction}; | ||
| use bitcoinkernel::prelude::{ | ||
| BlockSpentOutputsExt, CoinExt, ScriptPubkeyExt, TransactionExt, TransactionSpentOutputsExt, | ||
| TxOutExt, | ||
| }; | ||
|
|
||
| use crate::silentpayments::wallet::Coin; | ||
|
|
||
| pub struct InputData { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SPDK doesn't use |
||
| pub script_sig: Vec<u8>, | ||
| pub witness: Vec<Vec<u8>>, | ||
| pub prevout_script: Vec<u8>, | ||
| pub txid: String, | ||
| pub vout: u32, | ||
| } | ||
|
|
||
| pub fn scan_transaction( | ||
| receiver: &Receiver, | ||
| b_scan: &SecretKey, | ||
| inputs: &[InputData], | ||
| tx: &Transaction, | ||
| ) -> Vec<(usize, Scalar, Option<Label>)> { | ||
| let mut input_pub_keys = Vec::new(); | ||
| let mut outpoints = Vec::new(); | ||
| for input in inputs { | ||
| if let Ok(Some(pk)) = | ||
| get_pubkey_from_input(&input.script_sig, &input.witness, &input.prevout_script) | ||
| { | ||
| input_pub_keys.push(pk); | ||
| outpoints.push((input.txid.clone(), input.vout)); | ||
| } | ||
| } | ||
|
|
||
| if input_pub_keys.is_empty() { | ||
| return vec![]; | ||
| } | ||
|
|
||
| // Silent payments always produce taproot outputs — skip transactions without any. | ||
| let taproot_outputs: Vec<(usize, XOnlyPublicKey)> = tx | ||
| .output | ||
| .iter() | ||
| .enumerate() | ||
| .filter_map(|(i, out)| { | ||
| if out.script_pubkey.is_p2tr() { | ||
| XOnlyPublicKey::from_slice(&out.script_pubkey.as_bytes()[2..]) | ||
| .ok() | ||
| .map(|pk| (i, pk)) | ||
| } else { | ||
| None | ||
| } | ||
| }) | ||
| .collect(); | ||
|
|
||
| if taproot_outputs.is_empty() { | ||
| return vec![]; | ||
| } | ||
|
|
||
| let pubkey_refs: Vec<&secp256k1::PublicKey> = input_pub_keys.iter().collect(); | ||
| let tweak_data = match calculate_tweak_data(&pubkey_refs, &outpoints) { | ||
| Ok(td) => td, | ||
| Err(_) => return vec![], | ||
| }; | ||
| let shared_secret = calculate_ecdh_shared_secret(&tweak_data, b_scan); | ||
|
|
||
| let xonly_outputs: Vec<XOnlyPublicKey> = taproot_outputs.iter().map(|(_, pk)| *pk).collect(); | ||
| let found = match receiver.scan_transaction(&shared_secret, xonly_outputs) { | ||
| Ok(f) => f, | ||
| Err(_) => return vec![], | ||
| }; | ||
|
|
||
| let mut result = Vec::new(); | ||
| for (label, pubkey_map) in found.iter() { | ||
| for (pk, tweak) in pubkey_map { | ||
| if let Some((idx, _)) = taproot_outputs.iter().find(|(_, o)| o == pk) { | ||
| result.push((*idx, *tweak, label.clone())); | ||
| } | ||
| } | ||
| } | ||
| result | ||
| } | ||
|
|
||
| pub(crate) fn scan_block_inner( | ||
| receiver: &Receiver, | ||
| b_scan: &SecretKey, | ||
| kernel_block: &bitcoinkernel::Block, | ||
| spent_outputs: &bitcoinkernel::BlockSpentOutputs, | ||
| block_height: u32, | ||
| ) -> Vec<(OutPoint, Coin)> { | ||
| let mut found: Vec<(OutPoint, Coin)> = Vec::new(); | ||
|
|
||
| // Skip coinbase; spent_outputs[i] maps to kernel_block.transactions().skip(1)[i]. | ||
| for (kernel_tx, tx_spent) in kernel_block | ||
| .transactions() | ||
| .skip(1) | ||
| .zip(spent_outputs.iter()) | ||
| { | ||
| let has_p2tr = kernel_tx.outputs().any(|out| { | ||
| let bytes = out.script_pubkey().to_bytes(); | ||
| Script::from_bytes(&bytes).is_p2tr() | ||
| }); | ||
| if !has_p2tr { | ||
| continue; | ||
| } | ||
|
|
||
| let tx_bytes = kernel_tx | ||
| .consensus_encode() | ||
| .expect("kernel tx serialization"); | ||
| let btc_tx: Transaction = | ||
| encode::deserialize(&tx_bytes).expect("kernel tx deserialization"); | ||
|
|
||
| let mut inputs = Vec::with_capacity(btc_tx.input.len()); | ||
| for (input_idx, btc_input) in btc_tx.input.iter().enumerate() { | ||
| let coin = tx_spent | ||
| .coin(input_idx) | ||
| .expect("input/spent-output count mismatch"); | ||
| inputs.push(InputData { | ||
| script_sig: btc_input.script_sig.as_bytes().to_vec(), | ||
| witness: btc_input.witness.iter().map(|item| item.to_vec()).collect(), | ||
| prevout_script: coin.output().script_pubkey().to_bytes(), | ||
| txid: btc_input.previous_output.txid.to_string(), | ||
| vout: btc_input.previous_output.vout, | ||
| }); | ||
| } | ||
|
|
||
| let txid = btc_tx.compute_txid(); | ||
| for (output_index, tweak, label) in scan_transaction(receiver, b_scan, &inputs, &btc_tx) { | ||
| if let Some(out) = btc_tx.output.get(output_index) { | ||
| let outpoint = OutPoint { | ||
| txid, | ||
| vout: output_index as u32, | ||
| }; | ||
| found.push(( | ||
| outpoint, | ||
| Coin { | ||
| value: out.value, | ||
| script_pubkey: out.script_pubkey.clone(), | ||
| tweak, | ||
| label, | ||
| block_height, | ||
| spent_by: None, | ||
| }, | ||
| )); | ||
| } | ||
| } | ||
| } | ||
| found | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.