binius_examples/circuits/
bitcoin_header_chain.rs1use anyhow::bail;
4use binius_circuits::bitcoin::header_chain::header_chain;
5use binius_frontend::{CircuitBuilder, Wire, WitnessFiller};
6use clap::Args;
7
8use crate::ExampleCircuit;
9
10pub struct BitcoinHeaderChainExample {
16 latest_digest: [Wire; 4],
17 headers: Vec<[Wire; 10]>,
18}
19
20#[derive(Args)]
21pub struct Params {
22 #[arg(long, default_value_t = 10)]
24 pub num_blocks: usize,
25}
26
27#[derive(Args)]
28pub struct Instance {
29 #[arg(long)]
32 pub to_block: Option<usize>,
33}
34
35impl ExampleCircuit for BitcoinHeaderChainExample {
36 type Params = Params;
37 type Instance = Instance;
38
39 fn build(params: Self::Params, builder: &mut CircuitBuilder) -> anyhow::Result<Self> {
40 if params.num_blocks == 0 {
41 bail!("need to prove at least one block")
42 }
43
44 let latest_digest: [Wire; 4] = std::array::from_fn(|_| builder.add_witness());
45 let headers: Vec<[Wire; 10]> =
46 std::iter::repeat_with(|| std::array::from_fn(|_| builder.add_witness()))
47 .take(params.num_blocks)
48 .collect();
49 builder.assert_eq_v("latest digest", header_chain(builder, &headers), latest_digest);
50
51 Ok(Self {
52 latest_digest,
53 headers,
54 })
55 }
56
57 fn populate_witness(
58 &self,
59 instance: Self::Instance,
60 filler: &mut WitnessFiller<'_>,
61 ) -> anyhow::Result<()> {
62 let (headers_value, latest_digest_value) =
63 pull_headers(self.headers.len(), instance.to_block)?;
64 for (header, header_value) in self.headers.iter().zip(&headers_value) {
65 filler.pack_bytes_le(header, header_value);
66 }
67 filler.pack_bytes_le(&self.latest_digest, &latest_digest_value);
68
69 Ok(())
70 }
71
72 fn param_summary(params: &Self::Params) -> Option<String> {
73 Some(format!("{}blk", params.num_blocks))
74 }
75}
76
77fn pull_headers(
78 num_blocks: usize,
79 to_block: Option<usize>,
80) -> anyhow::Result<(Vec<Vec<u8>>, Vec<u8>)> {
81 let last_height: usize = match to_block {
82 Some(to_block) => to_block,
83 None => ureq::get("https://mempool.space/api/blocks/tip/height")
84 .call()?
85 .body_mut()
86 .read_to_string()?
87 .trim()
88 .parse()?,
89 };
90
91 let latest_digest = ureq::get(format!("https://mempool.space/api/block-height/{last_height}"))
92 .call()?
93 .body_mut()
94 .read_to_string()?;
95 let mut latest_digest = hex::decode(latest_digest)?;
96 latest_digest.reverse();
97
98 let first_height = (last_height + 1).checked_sub(num_blocks).unwrap();
99 println!(
100 "Fetching {num_blocks} block headers. First height is {first_height} and last height is {last_height} .."
101 );
102 let mut headers = Vec::new();
103 for height in (first_height..=last_height).rev() {
104 let hash = ureq::get(format!("https://mempool.space/api/block-height/{height}"))
105 .call()?
106 .body_mut()
107 .read_to_string()?;
108 println!("Fetching header with height {height} and hash {hash} ..");
109 let header = ureq::get(format!("https://mempool.space/api/block/{hash}/header"))
110 .call()?
111 .body_mut()
112 .read_to_string()?;
113 let header = hex::decode(header)?;
114 headers.push(header);
115 }
116
117 println!("Done.");
118
119 Ok((headers, latest_digest))
120}