Skip to content
Open
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
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rust-analyzer.server.extraEnv": {
"RUSTUP_TOOLCHAIN": "nightly"
}
}
82 changes: 67 additions & 15 deletions bitcoin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,90 @@ repository = "https://github.com/rust-bitcoin/rust-bitcoin/"
documentation = "https://docs.rs/bitcoin/"
description = "General purpose library for using and interoperating with Bitcoin."
categories = ["cryptography::cryptocurrencies"]
keywords = [ "crypto", "bitcoin" ]
keywords = ["crypto", "bitcoin"]
readme = "../README.md"
edition = "2021"
rust-version = "1.63.0"
exclude = ["tests", "contrib"]

# If you change features or optional dependencies in any way please update the "# Cargo features" section in lib.rs as well.
[features]
default = [ "std", "secp-recovery" ]
std = ["base58/std", "bech32/std", "hashes/std", "hex/std", "internals/std", "io/std", "primitives/std", "secp256k1/std", "units/std", "bitcoinconsensus?/std"]
default = ["std", "secp-recovery"]
std = [
"base58/std",
"bech32/std",
"hashes/std",
"hex/std",
"internals/std",
"io/std",
"primitives/std",
"secp256k1/std",
"units/std",
"bitcoinconsensus?/std",
]
rand-std = ["secp256k1/rand", "std"]
rand = ["secp256k1/rand"]
serde = ["dep:serde", "hashes/serde", "internals/serde", "primitives/serde", "secp256k1/serde", "units/serde"]
serde = [
"dep:serde",
"hashes/serde",
"internals/serde",
"primitives/serde",
"secp256k1/serde",
"units/serde",
]
secp-lowmemory = ["secp256k1/lowmemory"]
secp-recovery = ["secp256k1/recovery"]
arbitrary = ["dep:arbitrary", "units/arbitrary", "primitives/arbitrary"]

[dependencies]
base58 = { package = "base58ck", version = "0.2.0", default-features = false, features = ["alloc"] }
base58 = { package = "base58ck", version = "0.2.0", default-features = false, features = [
"alloc",
] }
bech32 = { version = "0.11.0", default-features = false, features = ["alloc"] }
hashes = { package = "bitcoin_hashes", version = "0.16.0", default-features = false, features = ["alloc", "hex"] }
hex = { package = "hex-conservative", version = "0.3.0", default-features = false, features = ["alloc"] }
internals = { package = "bitcoin-internals", version = "0.4.0", features = ["alloc", "hex"] }
io = { package = "bitcoin-io", version = "0.2.0", default-features = false, features = ["alloc", "hashes"] }
primitives = { package = "bitcoin-primitives", version = "0.101.0", default-features = false, features = ["alloc"] }
secp256k1 = { version = "0.30.0", default-features = false, features = ["hashes", "alloc", "rand"] }
units = { package = "bitcoin-units", version = "0.2.0", default-features = false, features = ["alloc"] }
hashes = { package = "bitcoin_hashes", version = "0.16.0", default-features = false, features = [
"alloc",
"hex",
] }
hex = { package = "hex-conservative", version = "0.3.0", default-features = false, features = [
"alloc",
] }
internals = { package = "bitcoin-internals", version = "0.4.0", features = [
"alloc",
"hex",
] }
io = { package = "bitcoin-io", version = "0.2.0", default-features = false, features = [
"alloc",
"hashes",
] }
primitives = { package = "bitcoin-primitives", version = "0.101.0", default-features = false, features = [
"alloc",
] }
secp256k1 = { version = "0.30.0", default-features = false, features = [
"hashes",
"alloc",
"rand",
] }
units = { package = "bitcoin-units", version = "0.2.0", default-features = false, features = [
"alloc",
] }

arbitrary = { version = "1.4", optional = true }
base64 = { version = "0.22.0", optional = true }
# `bitcoinconsensus` version includes metadata which indicates the version of Core. Use `cargo tree` to see it.
bitcoinconsensus = { version = "0.106.0", default-features = false, optional = true }
serde = { version = "1.0.103", default-features = false, features = [ "derive", "alloc" ], optional = true }
serde = { version = "1.0.103", default-features = false, features = [
"derive",
"alloc",
], optional = true }

bitcoinpqc = { version = "0.1.3", default-features = false, features = [
"serde",
] }

[dev-dependencies]
internals = { package = "bitcoin-internals", version = "0.4.0", features = ["test-serde"] }
internals = { package = "bitcoin-internals", version = "0.4.0", features = [
"test-serde",
] }
serde_json = "1.0.0"
serde_test = "1.0.19"
bincode = "1.3.1"
Expand Down Expand Up @@ -95,4 +143,8 @@ name = "io"
required-features = ["std"]

[lints.rust]
unexpected_cfgs = { level = "deny", check-cfg = ['cfg(bench)', 'cfg(fuzzing)', 'cfg(kani)'] }
unexpected_cfgs = { level = "deny", check-cfg = [
'cfg(bench)',
'cfg(fuzzing)',
'cfg(kani)',
] }
169 changes: 169 additions & 0 deletions bitcoin/examples/p2qrh-example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
use bitcoin::hashes::sha256;
use bitcoin::qubit::{
Attestation, KeyAlgorithm, KeyTypeBitmask, P2QRHTemplate, Signature as QubitSignature,
};
use bitcoinpqc::Algorithm as PqcAlgorithm;

fn main() {
println!("P2QRH (Pay to Quantum Resistant Hash) Example");
println!("==============================================");
println!(
"This example demonstrates the P2QRH (BIP-360) output type using post-quantum signatures"
);

// Step 1: Create an attestation with multiple quantum-resistant public keys
println!("1. Creating post-quantum keypairs");

// Use hardcoded SPHINCS+ public key (32 bytes)
// In real implementation, this would be generated using the slh-dsa crate with Shake128s parameters
let sphincs_pubkey = vec![
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
0xff, 0x02, // Ending with 0x02 for valid x-only format
];
println!("- SPHINCS+-128s (SLH-DSA/FIPS-205) public key: {} bytes", sphincs_pubkey.len());

// Use hardcoded ML-DSA-44 public key (would be ~1184 bytes in a real implementation)
let ml_dsa_pubkey = vec![
0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x03, // Ending with 0x03 for valid x-only format
];
println!("- ML-DSA-44 (CRYSTALS-Dilithium/FIPS-204) public key: {} bytes (would be ~1184 bytes in reality)", ml_dsa_pubkey.len());

// Step 2: Create a key type bitmask that enables multiple quantum-resistant algorithms
// As specified in BIP-360, this allows flexibility in algorithm selection
println!("\n2. Creating key type bitmask for multiple quantum-resistant algorithms");
let bitmask = KeyTypeBitmask::new(&[
KeyAlgorithm::PostQuantum(PqcAlgorithm::SLH_DSA_128S),
KeyAlgorithm::PostQuantum(PqcAlgorithm::ML_DSA_44),
]);
println!("- Enabled algorithms: SPHINCS+, Dilithium");

// Step 3: Create the attestation with public keys for enabled algorithms
// The attestation structure contains the public keys as described in BIP-360
println!("\n3. Creating attestation structure with quantum-resistant public keys");
let attestation = Attestation::new(
bitmask,
vec![
(KeyAlgorithm::PostQuantum(PqcAlgorithm::SLH_DSA_128S), sphincs_pubkey.clone()),
(KeyAlgorithm::PostQuantum(PqcAlgorithm::ML_DSA_44), ml_dsa_pubkey.clone()),
],
);
println!("- Attestation created successfully");

// Step 4: Create a P2QRH template from the attestation
// The P2QRH template is the basis for creating the scriptPubKey
println!("\n4. Creating P2QRH template from attestation");
let template = P2QRHTemplate::new(&attestation);
println!("- P2QRH template created successfully");

// Step 5: Generate the scriptPubKey for this P2QRH template
// This creates a SegWit v3 output with the hash commitment of the attestation
println!("\n5. Generating P2QRH scriptPubKey (SegWit v3 output)");
let script_pubkey = template.script_pubkey();
println!("- P2QRH scriptPubKey: {}", script_pubkey);

// Step 6: Create simulated post-quantum signatures
println!("\n6. Creating post-quantum signatures for a transaction");
let message = b"Transaction to sign";

// Generate a deterministic simulated SPHINCS+-128s signature
println!("- Generating simulated SPHINCS+-128s signature");
let sphincs_signature = generate_deterministic_sphincs_signature(message);
println!("- Created simulated SPHINCS+-128s signature: {} bytes", sphincs_signature.len());

// Generate a deterministic simulated ML-DSA-44 signature
println!("- Generating simulated ML-DSA-44 signature");
let ml_dsa_signature = generate_deterministic_ml_dsa_signature(message);
println!("- Created simulated ML-DSA-44 signature: {} bytes", ml_dsa_signature.len());

// Step 7: Create QubitSignatures for both algorithms
let sphincs_qsig = QubitSignature::new(
KeyAlgorithm::PostQuantum(PqcAlgorithm::SLH_DSA_128S),
sphincs_signature,
);
let ml_dsa_qsig =
QubitSignature::new(KeyAlgorithm::PostQuantum(PqcAlgorithm::ML_DSA_44), ml_dsa_signature);

println!("\n7. Serializing the signatures");
let sphincs_serialized = sphincs_qsig.to_vec();
let ml_dsa_serialized = ml_dsa_qsig.to_vec();
println!("- SPHINCS+-128s serialized signature: {} bytes", sphincs_serialized.len());
println!("- ML-DSA-44 serialized signature: {} bytes", ml_dsa_serialized.len());
println!("- P2QRH transactions would be notably larger than traditional Bitcoin transactions");

// Step 8: Create and display a P2QRH address
// This creates a bech32m address for the P2QRH output
println!("\n8. Creating P2QRH address (bech32m format)");

// Get the hash commitment from the scriptPubKey
// The scriptPubKey format is: OP_PUSHNUM_3 OP_PUSHBYTES_32 <32-byte-hash>
let script = template.script_pubkey();

// Print the raw values for reference
println!("- Using SegWit v3 (witness version 3)");
println!("- Full P2QRH scriptPubKey: {}", script);
println!("- Bech32m prefix: bc");

// For now, just show that we would generate a SegWit v3 address
// In a real implementation, this would be a proper bech32m encoding
let address_placeholder = "bc1r...(witness v3)".to_string();
println!("- P2QRH Address: {} (SegWit v3 address)", address_placeholder);

// Step 9: Simulate signature verification
println!("\n9. Verifying the signatures");
println!("- SPHINCS+-128s signature verification: SUCCESS (simulated)");
println!("- ML-DSA-44 signature verification: SUCCESS (simulated)");

println!("\nImplementation Details (BIP-360):");
println!("- P2QRH uses SegWit v3 outputs with quantum-resistant signature algorithms");
println!(
"- Multiple algorithms can be supported in a single output (multi-algorithm security)"
);
println!("- Each algorithm has different security assumptions, sizes, and performance characteristics:");
println!(" * SPHINCS+-128s (hash-based): ~4.1KB signatures, no mathematical assumptions, smaller but slower signatures");
println!(" * ML-DSA-44 (lattice-based): ~2.1KB signatures, based on module learning with errors problem (security category 2)");
println!("- This provides protection against future quantum computing threats to Bitcoin");
}

// Generate a deterministic simulated SPHINCS+-128s signature
fn generate_deterministic_sphincs_signature(message: &[u8]) -> Vec<u8> {
// SPHINCS+-128s (small) signatures are ~4,124 bytes
// This is the smallest size parameter set (more compact but slower to verify)
let mut signature = vec![0u8; 4124];

// Get the message hash
let hash = sha256::Hash::hash(message);

// Copy hash to the first 32 bytes
let hash_bytes = hash.as_byte_array();
signature[0..32].copy_from_slice(hash_bytes);

// Fill the rest with a deterministic pattern
for i in 32..signature.len() {
signature[i] = ((i % 256) as u8).wrapping_add(hash_bytes[i % 32]);
}

signature
}

// Generate a deterministic simulated ML-DSA-44 signature
fn generate_deterministic_ml_dsa_signature(message: &[u8]) -> Vec<u8> {
// ML-DSA-44 signatures are ~2,100 bytes (security category 2)
let mut signature = vec![0u8; 2100];

// Get the message hash
let hash = sha256::Hash::hash(message);

// Copy hash to the first 32 bytes
let hash_bytes = hash.as_byte_array();
signature[0..32].copy_from_slice(hash_bytes);

// Fill the rest with a deterministic pattern
for i in 32..signature.len() {
signature[i] = ((i % 256) as u8).wrapping_add(hash_bytes[i % 32]);
}

signature
}
13 changes: 13 additions & 0 deletions bitcoin/src/address/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ pub enum AddressType {
P2tr,
/// Pay to anchor.
P2a,
/// Pay to quantum resistant hash.
P2qrh,
}

impl fmt::Display for AddressType {
Expand All @@ -104,6 +106,7 @@ impl fmt::Display for AddressType {
AddressType::P2wsh => "p2wsh",
AddressType::P2tr => "p2tr",
AddressType::P2a => "p2a",
AddressType::P2qrh => "p2qrh",
})
}
}
Expand All @@ -118,6 +121,7 @@ impl FromStr for AddressType {
"p2wsh" => Ok(AddressType::P2wsh),
"p2tr" => Ok(AddressType::P2tr),
"p2a" => Ok(AddressType::P2a),
"p2qrh" => Ok(AddressType::P2qrh),
_ => Err(UnknownAddressTypeError(s.to_owned())),
}
}
Expand Down Expand Up @@ -598,6 +602,8 @@ impl Address {
Some(AddressType::P2tr)
} else if program.is_p2a() {
Some(AddressType::P2a)
} else if program.is_p2qrh() {
Some(AddressType::P2qrh)
} else {
None
},
Expand Down Expand Up @@ -782,6 +788,13 @@ impl Address {
Segwit { ref program, hrp: _ } => program.program().as_bytes(),
}
}

/// Creates a P2QRH (Pay to Quantum Resistant Hash) address from a 32-byte hash.
pub fn p2qrh_from_hash(hash: [u8; 32], hrp: impl Into<KnownHrp>) -> Address {
let program = WitnessProgram::new(WitnessVersion::V2, &hash)
.expect("32 bytes is a valid program length");
Address::from_witness_program(program, hrp)
}
}

/// Methods that can be called only on `Address<NetworkUnchecked>`.
Expand Down
8 changes: 8 additions & 0 deletions bitcoin/src/blockdata/script/borrowed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,14 @@ crate::internal_macros::define_extension_trait! {
&& self.as_bytes()[1] == OP_PUSHBYTES_20.to_u8()
}

/// Checks whether a script pubkey is a P2QRH output.
#[inline]
fn is_p2qrh(&self) -> bool {
self.len() == 34
&& self.witness_version() == Some(WitnessVersion::V2)
&& self.as_bytes()[1] == OP_PUSHBYTES_32.to_u8()
}

/// Checks whether a script pubkey is a P2TR output.
#[inline]
fn is_p2tr(&self) -> bool {
Expand Down
19 changes: 18 additions & 1 deletion bitcoin/src/blockdata/script/owned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ use core::ops::Deref;
use hex::FromHex;
use internals::ToU64 as _;

use super::{opcode_to_verify, Builder, Instruction, PushBytes, ScriptExtPriv as _};
use super::{opcode_to_verify, Builder, Instruction, PushBytes, ScriptExt, ScriptExtPriv as _};
use crate::opcodes::all::*;
use crate::opcodes::{self, Opcode};
use crate::prelude::Vec;
use crate::script::witness_version::WitnessVersion;

#[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)]
Expand Down Expand Up @@ -78,6 +79,22 @@ crate::internal_macros::define_extension_trait! {
/// `Builder` if you're creating the script from scratch or if you want to push `OP_VERIFY`
/// multiple times.
fn scan_and_push_verify(&mut self) { self.push_verify(self.last_opcode()); }

/// Checks whether a script pubkey is a P2WPKH output.
#[inline]
fn is_p2wpkh(&self) -> bool {
self.len() == 22
&& self.witness_version() == Some(WitnessVersion::V0)
&& self.as_bytes()[1] == OP_PUSHBYTES_20.to_u8()
}

/// Checks whether a script pubkey is a P2QRH output.
#[inline]
fn is_p2qrh(&self) -> bool {
self.len() == 34
&& self.witness_version() == Some(WitnessVersion::V3)
&& self.as_bytes()[1] == OP_PUSHBYTES_32.to_u8()
}
}
}

Expand Down
Loading