binius_examples/circuits/
bitcoin_p2pkh.rs1use std::array;
4
5use anyhow::Result;
6use binius_circuits::{
7 bignum::BigUint,
8 bitcoin::p2pkh_signature::{addr_bytes_to_le_words, build_p2pkh_circuit},
9};
10use binius_frontend::{CircuitBuilder, Wire, WitnessFiller};
11use bitcoin::{Network, PrivateKey, hashes::Hash, secp256k1::Secp256k1};
12use clap::Args;
13use rand::prelude::*;
14
15use crate::ExampleCircuit;
16
17pub struct BitcoinP2PKHExample {
26 private_key: BigUint,
27 expected_address: [Wire; 5],
28}
29
30#[derive(Args, Debug, Clone)]
31pub struct Params {
32 }
35
36#[derive(Args, Debug, Clone)]
37pub struct Instance {
38 #[arg(long, value_parser = parse_hex_private_key)]
41 pub private_key: Option<[u8; 32]>,
42
43 #[arg(long, value_parser = parse_hex_address)]
46 pub expected_address: Option<[u8; 20]>,
47
48 #[arg(long, default_value_t = 42)]
50 pub seed: u64,
51}
52
53impl ExampleCircuit for BitcoinP2PKHExample {
54 type Params = Params;
55 type Instance = Instance;
56
57 fn build(_params: Params, builder: &mut CircuitBuilder) -> Result<Self> {
58 let private_key = BigUint::new_witness(builder, 4);
60
61 let expected_address: [Wire; 5] = array::from_fn(|_| builder.add_witness());
63
64 build_p2pkh_circuit(builder, &private_key, expected_address);
66
67 Ok(Self {
68 private_key,
69 expected_address,
70 })
71 }
72
73 fn populate_witness(&self, instance: Instance, w: &mut WitnessFiller<'_>) -> Result<()> {
74 #[allow(clippy::option_if_let_else)]
77 let private_key_bytes = match instance.private_key {
78 Some(key) => {
79 tracing::info!("Using provided private key");
80 key
81 }
82 None => {
83 let mut rng = StdRng::seed_from_u64(instance.seed);
84 let mut key = [0u8; 32];
85
86 loop {
88 rng.fill_bytes(&mut key);
89
90 if !is_zero(&key) && is_valid_secp256k1_key(&key) {
94 break;
95 }
96 }
97
98 tracing::info!(
99 "Generated private key (seed={}): {}",
100 instance.seed,
101 hex::encode(key)
102 );
103 key
104 }
105 };
106
107 let expected_address_bytes = match instance.expected_address {
109 Some(addr) => {
110 tracing::info!("Using provided expected address: {}", hex::encode(addr));
111 addr
112 }
113 None => {
114 let secp = Secp256k1::new();
116 let private_key = PrivateKey::from_slice(&private_key_bytes, Network::Bitcoin)
117 .map_err(|e| anyhow::anyhow!("Invalid private key: {}", e))?;
118 let public_key = private_key.public_key(&secp);
119 let addr = public_key.pubkey_hash().to_byte_array();
120 tracing::info!("Computed expected address from private key: {}", hex::encode(addr));
121 addr
122 }
123 };
124
125 let private_key_limbs = bytes_to_le_limbs(&private_key_bytes);
127
128 let address_words = addr_bytes_to_le_words(&expected_address_bytes);
130
131 self.private_key.populate_limbs(w, &private_key_limbs);
133
134 for i in 0..5 {
135 w[self.expected_address[i]] =
136 binius_core::word::Word::from_u64(address_words[i] as u64);
137 }
138
139 tracing::info!("Successfully populated witness for Bitcoin P2PKH proof");
140 Ok(())
141 }
142}
143
144fn parse_hex_private_key(s: &str) -> Result<[u8; 32], String> {
146 let bytes = hex::decode(s).map_err(|e| format!("Invalid hex: {}", e))?;
147 if bytes.len() != 32 {
148 return Err(format!("Private key must be exactly 32 bytes, got {}", bytes.len()));
149 }
150 let mut key = [0u8; 32];
151 key.copy_from_slice(&bytes);
152
153 if is_zero(&key) || !is_valid_secp256k1_key(&key) {
154 return Err("Invalid secp256k1 private key".to_string());
155 }
156
157 Ok(key)
158}
159
160fn parse_hex_address(s: &str) -> Result<[u8; 20], String> {
162 let bytes = hex::decode(s).map_err(|e| format!("Invalid hex: {}", e))?;
163 if bytes.len() != 20 {
164 return Err(format!("Address must be exactly 20 bytes, got {}", bytes.len()));
165 }
166 let mut addr = [0u8; 20];
167 addr.copy_from_slice(&bytes);
168 Ok(addr)
169}
170
171fn is_zero(bytes: &[u8; 32]) -> bool {
173 bytes.iter().all(|&b| b == 0)
174}
175
176fn is_valid_secp256k1_key(bytes: &[u8; 32]) -> bool {
179 for i in 0..12 {
183 if bytes[i] != 0xFF {
184 return true;
185 }
186 }
187 false
188}
189
190fn bytes_to_le_limbs(bytes_be: &[u8; 32]) -> [u64; 4] {
192 let mut le = [0u8; 32];
194 for i in 0..32 {
195 le[i] = bytes_be[31 - i];
196 }
197 [
198 u64::from_le_bytes(le[0..8].try_into().unwrap()),
199 u64::from_le_bytes(le[8..16].try_into().unwrap()),
200 u64::from_le_bytes(le[16..24].try_into().unwrap()),
201 u64::from_le_bytes(le[24..32].try_into().unwrap()),
202 ]
203}