Skip to content
Closed
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
72 changes: 66 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions 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 @@ -15,6 +19,9 @@ configure_me = "0.4.0"
clap = { version = "4", features = ["derive"] }
log = "0.4"
env_logger = "0.10"
wallet = { path = "crates/wallet" }
secp256k1 = "0.29"
hex = "0.4"
## 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
7 changes: 7 additions & 0 deletions capnp/wallet.capnp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@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);
}
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"] }
secp256k1 = "0.29"
bitcoin = "0.32.8"
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;
21 changes: 21 additions & 0 deletions crates/wallet/src/silentpayments/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
mod scanning;
mod wallet;

pub use ::silentpayments::receiving::Receiver;
pub use ::silentpayments::{Network, SilentPaymentAddress};
pub use scanning::{scan_block, scan_transaction, FoundPayment, InputData};
pub use wallet::{HistoryEntry, OwnedUtxo, SilentPaymentWallet, SpentBy};

use ::silentpayments::receiving::Label;
use secp256k1::{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)
}
104 changes: 104 additions & 0 deletions crates/wallet/src/silentpayments/scanning.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use ::silentpayments::{
receiving::Receiver,
utils::receiving::{calculate_ecdh_shared_secret, calculate_tweak_data, get_pubkey_from_input},
};
use bitcoin::{Block, Transaction};
use secp256k1::{SecretKey, XOnlyPublicKey};

pub struct InputData {
pub script_sig: Vec<u8>,
pub witness: Vec<Vec<u8>>,
pub prevout_script: Vec<u8>,
pub txid: String,
pub vout: u32,
}

pub struct FoundPayment {
pub output_index: usize,
pub pubkey: XOnlyPublicKey,
}

pub fn scan_transaction(
receiver: &Receiver,
b_scan: &SecretKey,
inputs: &[InputData],
tx: &Transaction,
) -> Vec<FoundPayment> {
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)| {
let spk = out.script_pubkey.as_bytes();
if spk.len() == 34 && spk[0] == 0x51 && spk[1] == 0x20 {
XOnlyPublicKey::from_slice(&spk[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 map in found.values() {
for pk in map.keys() {
if let Some((idx, _)) = taproot_outputs.iter().find(|(_, o)| o == pk) {
result.push(FoundPayment {
output_index: *idx,
pubkey: *pk,
});
}
}
}
result
}

pub fn scan_block(
receiver: &Receiver,
b_scan: &SecretKey,
block: &Block,
tx_input_data: Vec<Vec<InputData>>,
) -> Vec<(bitcoin::Txid, FoundPayment)> {
let mut all_found = Vec::new();
// Skip coinbase (index 0); tx_input_data[i] maps to block.txdata[i+1].
for (i, tx) in block.txdata.iter().skip(1).enumerate() {
if let Some(inputs) = tx_input_data.get(i) {
for payment in scan_transaction(receiver, b_scan, inputs, tx) {
all_found.push((tx.compute_txid(), payment));
}
}
}
all_found
}
Loading
Loading