Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 114 additions & 56 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
[workspace]
members = [".", "crates/wallet"]
resolver = "2"

[package]
name = "kernel-node"
version = "0.1.0"
Expand All @@ -8,13 +12,14 @@ rust-version = "1.85.0"
# bitcoinkernel = { path = "../rust-bitcoinkernel", version = "0.0.15" }
bitcoinkernel = "0.2"
addrman = { package = "bitcoin-address-book", version = "0.1.1" }
bitcoin = "0.32.8"
bitcoin = { version = "0.32.8", features = ["rand-std"] }
p2p = { package = "bitcoin-p2p", git = "https://github.com/2140-dev/bitcoin-p2p.git", rev = "5dc03375636a10487c7e50659416876c28f58a3b" }
## Log and configuration
configure_me = "0.4.0"
clap = { version = "4", features = ["derive"] }
log = "0.4"
env_logger = "0.10"
wallet = { path = "crates/wallet" }
## IPC required dependencies
capnp = "0.25"
capnp-rpc = "0.25"
Expand Down
1 change: 1 addition & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ fn main() -> Result<(), configure_me_codegen::Error> {
CompilerCommand::new()
.src_prefix("capnp")
.file("capnp/server.capnp")
.file("capnp/wallet.capnp")
.run()
.unwrap();
configure_me_codegen::build_script_auto()
Expand Down
3 changes: 3 additions & 0 deletions capnp/server.capnp
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);
}
8 changes: 8 additions & 0 deletions capnp/wallet.capnp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@0xb5d3a2f1e8c47690;

interface Wallet {
Comment thread
rustaceanrob marked this conversation as resolved.
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);
}
10 changes: 10 additions & 0 deletions crates/wallet/Cargo.toml
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"
1 change: 1 addition & 0 deletions crates/wallet/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod silentpayments;
20 changes: 20 additions & 0 deletions crates/wallet/src/silentpayments/mod.rs
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)
}
153 changes: 153 additions & 0 deletions crates/wallet/src/silentpayments/scanning.rs
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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SPDK doesn't use bitcoin types so the conversion is unnecessary. That being said I think we can clean up the number of types introduced in this PR.

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
}
Loading
Loading