Skip to content
Draft
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
23 changes: 17 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,35 @@ backend-blindbit-v1 = { path = "backend-blindbit-v1"}

# Core dependencies - shared across crates
anyhow = "1.0"
async-trait = "0.1"
base64 = "0.22"
bdk_coin_select = "0.4.0"
bech32 = "0.9"
# bdk_coin_select = "0.4.0"
bimap = "0.6"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"
bitcoin = { version = "0.32.8", features = ["serde", "rand", "base64"] }
bitcoin_hashes = "0.13.0"
rayon = "1.10.0"
dnssec-prover = "0.1"
futures = "0.3"
async-trait = "0.1"
log = "0.4"
hex = { version = "0.4.3", features = ["serde"] }
bdk_coin_select = "0.4.0"
hmac = "0.12"
log = "0.4"
reqwest = { version = "0.12.4", features = [
"json",
"rustls-tls",
"gzip",
], default-features = false }
psbt-v2 = { git = "https://github.com/macgyver13/rust-psbt.git", branch = "add-pairs-v2-map", features = ["silent-payments"] }
rayon = "1.10.0"
rust-dleq = { git = "https://github.com/macgyver13/rust-dleq.git", branch = "master", default-features = false }
secp256k1 = { version = "0.29.0", features = ["rand"] }
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"
sha2 = "0.10"
tempfile = "3.8"
thiserror = "1.0"
tokio = { version = "1.48.0", features = ["rt"], default-features = false }


[workspace.package]
repository = "https://github.com/cygnet3/spdk"
Expand Down
63 changes: 63 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Once just v1.39.0 is widely deployed, simplify with the `read` function.
NIGHTLY_VERSION := trim(read(justfile_directory() / "nightly-version"))

_default:
@just --list

# Install rbmt (Rust Bitcoin Maintainer Tools).
@_install-rbmt:
cargo install --quiet --git https://github.com/rust-bitcoin/rust-bitcoin-maintainer-tools.git --rev $(cat {{justfile_directory()}}/rbmt-version) cargo-rbmt

# Check spdk-core.
[group('spdk-core')]
check:
cargo check -p spdk-core

# Build spdk-core.
[group('spdk-core')]
build:
cargo build -p spdk-core

# Test spdk-core.
[group('spdk-core')]
test:
cargo test -p spdk-core

# Lint spdk-core.
[group('spdk-core')]
lint:
cargo +{{NIGHTLY_VERSION}} clippy -p spdk-core

# Run cargo fmt
fmt:
cargo +{{NIGHTLY_VERSION}} fmt --all

# Run dleq example (default)
run-dleq:
cargo run --example dleq_example

# Run dleq example (standalone)
run-dleq-standalone:
cargo run -p spdk-core --example dleq_example --no-default-features --features dleq-standalone

# Update the recent and minimal lock files using rbmt.
[group('tools')]
@update-lock-files: _install-rbmt
rustup run {{NIGHTLY_VERSION}} cargo rbmt lock

# Run CI tasks with rbmt.
[group('ci')]
@ci task toolchain="stable" lock="recent": _install-rbmt
RBMT_LOG_LEVEL=quiet rustup run {{toolchain}} cargo rbmt --lock-file {{lock}} {{task}}

# Test crate.
[group('ci')]
ci-test: (ci "test stable")

# Lint crate.
[group('ci')]
ci-lint: (ci "lint" NIGHTLY_VERSION)

# Bitcoin core integration tests.
[group('ci')]
ci-integration: (ci "integration")
1 change: 1 addition & 0 deletions nightly-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nightly-2025-01-21
21 changes: 21 additions & 0 deletions spdk-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,32 @@ repository.workspace = true

[lib]
crate-type = ["lib", "staticlib", "cdylib"]

[features]
# Default: both sync and async APIs available, with parallelization
default = ["dleq-native"]

# DLEQ proof implementation options (mutually exclusive)
# Native: Direct FFI to libsecp256k1 (default, uses vendored secp256k1)
dleq-native = ["rust-dleq/native"]
# Standalone: Pure Rust + rust-secp256k1 (portable)
dleq-standalone = ["rust-dleq/standalone"]

[dependencies]
anyhow.workspace = true
async-trait.workspace = true
bitcoin.workspace = true
dnssec-prover.workspace = true
futures.workspace = true
hex.workspace = true
hmac.workspace = true
serde.workspace = true
serde_json.workspace = true
silentpayments.workspace = true
psbt-v2.workspace = true
rust-dleq.workspace = true
secp256k1.workspace = true
thiserror.workspace = true
base64.workspace = true
sha2.workspace = true
tempfile.workspace = true
115 changes: 115 additions & 0 deletions spdk-core/examples/dleq_example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//! Example: Using DLEQ proofs with rust-dleq integration
//!
//! This example demonstrates how to use DLEQ proofs in SPDK with rust-dleq.
//! The same code works with both `dleq-standalone` and `dleq-native` features.
//!
//! Run with standalone (default):
//! cargo run --example dleq_example
//!
//! Run with native:
//! cargo run --example dleq_example --no-default-features --features dleq-native,async,parallel

use secp256k1::{PublicKey, Secp256k1, SecretKey};
use spdk_core::psbt::crypto::dleq::{dleq_generate_proof, dleq_verify_proof};
use spdk_core::psbt::{from_psbt_v2_proof, to_psbt_v2_proof};

fn main() {
println!("DLEQ Proof Example with rust-dleq Integration\n");

let secp = Secp256k1::new();

// Sender A: Generate a keypair
let sender_input_secret = SecretKey::from_slice(&[0x01; 32]).expect("valid secret key");
let sender_input_public = PublicKey::from_secret_key(&secp, &sender_input_secret);
println!("Sender A public key: {}", sender_input_public);

// Receiver B: Generate a public key (scan key in silent payments context)
let receiver_scan_secret = SecretKey::from_slice(&[0x02; 32]).expect("valid secret key");
let receiver_scan_public = PublicKey::from_secret_key(&secp, &receiver_scan_secret);
println!("Receiver B scan public key: {}", receiver_scan_public);

// Compute ECDH share: C = a * B
let ecdh_share = receiver_scan_public
.mul_tweak(&secp, &sender_input_secret.into())
.expect("valid ECDH computation");
println!("ECDH share: {}\n", ecdh_share);

// Generate DLEQ proof
println!("Generating DLEQ proof...");
let aux_randomness = [0x42; 32]; // In practice, use secure randomness
let message = Some([0xAB; 32]); // Optional message to bind to proof

let proof = dleq_generate_proof(
&secp,
&sender_input_secret,
&receiver_scan_public,
&aux_randomness,
message.as_ref(),
)
.expect("proof generation successful");

println!("✓ Proof generated successfully");
let hex_proof: String = proof.0.iter().map(|b| format!("{:02x}", b)).collect();
println!(" Proof bytes (hex): {}\n", hex_proof);

// Verify the DLEQ proof
println!("Verifying DLEQ proof...");
let is_valid = dleq_verify_proof(
&secp,
&sender_input_public,
&receiver_scan_public,
&ecdh_share,
&proof,
message.as_ref(),
)
.expect("verification executed");

if is_valid {
println!("✓ Proof is VALID");
println!(" The prover knows the discrete log relationship:");
println!(" log_G(sender_input_public) = log_B(ecdh_share)\n");
} else {
println!("✗ Proof is INVALID");
}

// Demonstrate proof conversion between types
println!("Demonstrating type conversion...");
let rust_dleq_proof = from_psbt_v2_proof(&proof);
println!(" Converted psbt-v2 proof to rust-dleq proof");

let converted_back = to_psbt_v2_proof(&rust_dleq_proof);
println!(" Converted back to psbt-v2 proof");

assert_eq!(proof.0, converted_back.0);
println!("✓ Round-trip conversion successful\n");

// Test with invalid proof
println!("Testing with corrupted proof...");
let mut corrupted_proof = proof;
corrupted_proof.0[0] ^= 0xFF; // Flip bits

let is_valid_corrupted = dleq_verify_proof(
&secp,
&sender_input_public,
&receiver_scan_public,
&ecdh_share,
&corrupted_proof,
message.as_ref(),
)
.expect("verification executed");

if !is_valid_corrupted {
println!("✓ Corrupted proof correctly rejected\n");
} else {
println!("✗ Corrupted proof incorrectly accepted\n");
}

// Feature detection
#[cfg(all(feature = "dleq-standalone", not(feature = "dleq-native")))]
println!("Using: dleq-standalone feature (Pure Rust implementation)");

#[cfg(all(feature = "dleq-native", not(feature = "dleq-standalone")))]
println!("Using: dleq-native feature (Native FFI to libsecp256k1)");

println!("\n✓ Example completed successfully!");
}
1 change: 1 addition & 0 deletions spdk-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod chain;
pub mod constants;
pub mod psbt;
pub mod updater;
81 changes: 81 additions & 0 deletions spdk-core/src/psbt/core/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//! Error types for BIP-375 operations

use thiserror::Error;

/// Result type alias for BIP-375 operations
pub type Result<T> = std::result::Result<T, Error>;

/// Error types for BIP-375 PSBT operations
#[derive(Debug, Error)]
pub enum Error {
#[error("Invalid PSBT magic bytes")]
InvalidMagic,

#[error("Invalid PSBT version: expected {expected}, got {actual}")]
InvalidVersion { expected: u32, actual: u32 },

#[error("Invalid field type: {0}")]
InvalidFieldType(u8),

#[error("Missing required field: {0}")]
MissingField(String),

#[error("Invalid field data: {0}")]
InvalidFieldData(String),

#[error("Serialization error: {0}")]
Serialization(String),

#[error("Deserialization error: {0}")]
Deserialization(String),

#[error("Invalid ECDH share: {0}")]
InvalidEcdhShare(String),

#[error("Incomplete ECDH coverage for output {0}")]
IncompleteEcdhCoverage(usize),

#[error("Invalid signature: {0}")]
InvalidSignature(String),

#[error("DLEQ proof verification failed for input {0}")]
DleqVerificationFailed(usize),

#[error("Invalid silent payment address: {0}")]
InvalidAddress(String),

#[error("Transaction extraction failed: {0}")]
ExtractionFailed(String),

#[error("Invalid input index: {0}")]
InvalidInputIndex(usize),

#[error("Invalid output index: {0}")]
InvalidOutputIndex(usize),

#[error("Invalid public key (must be compressed)")]
InvalidPublicKey,

#[error("Invalid PSBT state: {0}")]
InvalidPsbtState(String),

#[error(
"Cannot add standard field type {0} via generic accessor - use specific method instead"
)]
StandardFieldNotAllowed(u8),

#[error("Bitcoin error: {0}")]
Bitcoin(#[from] bitcoin::consensus::encode::Error),

#[error("Secp256k1 error: {0}")]
Secp256k1(#[from] secp256k1::Error),

#[error("Hex decoding error: {0}")]
Hex(#[from] hex::FromHexError),

#[error("I/O error: {0}")]
Io(#[from] std::io::Error),

#[error("Other error: {0}")]
Other(String),
}
Loading