Skip to content

Commit b2295f0

Browse files
committed
Merge branch 'main' into feature/unspend_utxo_indexer
2 parents aad37b9 + e806742 commit b2295f0

File tree

7 files changed

+197
-1
lines changed

7 files changed

+197
-1
lines changed

utxo_indexer/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[workspace]
22
resolver = "3"
3-
members = ["indexer"]
3+
members = ["indexer", "utxo_test_data_generator"]

utxo_indexer/indexer/src/lib.rs

Whitespace-only changes.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "utxo_test_data_generator"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
rand = "0.9.2"
8+
k256 = { version = "0.11", features = ["ecdsa"] }
9+
anyhow = "1.0.99"
10+
hex = "0.4"
11+
sha2 = "0.10.9"
12+
ripemd = "0.1.3"
13+
clap = { version = "4.5.47", features = ["derive"] }
14+
bitcoin = "0.32.7"
15+
serde_json = "1.0.145"
16+
serde = { version = "1.0.226", features = ["derive"] }
17+
log = "0.4.28"
18+
env_logger = "0.11.8"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use std::path::PathBuf;
2+
3+
use clap::Parser;
4+
5+
#[derive(Parser)]
6+
#[command(version, about = "Sync blockchain block headers", long_about = None)]
7+
pub struct Cli {
8+
/// Amount of utxos that will be generated
9+
#[arg(long, default_value = "20")]
10+
pub utxos_amount: u32,
11+
12+
/// Message that will be signed with private key
13+
#[arg(long, default_value = "const message")]
14+
pub const_message: String,
15+
16+
/// Private key in hex (will be generated if it doesn't set)
17+
#[arg(long)]
18+
pub priv_key: Option<String>,
19+
20+
/// Path to file where generated utxos will be saved
21+
#[arg(long)]
22+
pub output: Option<PathBuf>,
23+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mod test_data_gen;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
mod cli;
2+
mod test_data_gen;
3+
4+
use clap::Parser;
5+
use log::info;
6+
use rand::Rng;
7+
use std::fs::{self, File};
8+
use std::io::Write;
9+
10+
use crate::test_data_gen::generate_test_utxos;
11+
12+
fn main() {
13+
env_logger::Builder::from_default_env()
14+
.filter_level(log::LevelFilter::Info)
15+
.init();
16+
17+
let args = cli::Cli::parse();
18+
19+
let message = args.const_message.as_bytes();
20+
21+
let priv_key = match args.priv_key {
22+
Some(k) => hex::decode(k).unwrap(),
23+
None => Vec::new(),
24+
};
25+
26+
let mut rng = rand::rng();
27+
28+
let private_key: [u8; 32] = if priv_key.is_empty() {
29+
rng.random()
30+
} else {
31+
priv_key.as_slice().try_into().unwrap()
32+
};
33+
34+
let test_utxos = generate_test_utxos(args.utxos_amount, &message, &private_key).unwrap();
35+
36+
for elem in test_utxos.iter().clone() {
37+
println!(
38+
"SPK: {}\nAmount: {}\nWitness: {}\n",
39+
elem.script_pub_key, elem.amount, elem.witness,
40+
)
41+
}
42+
43+
match args.output {
44+
Some(mut path) => {
45+
if path.extension().and_then(|s| s.to_str()) != Some("json") {
46+
path.set_extension("json");
47+
}
48+
49+
if let Some(parent) = path.parent() {
50+
fs::create_dir_all(parent).unwrap();
51+
}
52+
53+
let mut file = File::create(&path).unwrap();
54+
let as_json = serde_json::to_string_pretty(&test_utxos).unwrap();
55+
file.write_all(as_json.as_bytes()).unwrap();
56+
57+
info!(
58+
"UTXOs were successfully saved to {}",
59+
path.to_str().unwrap()
60+
)
61+
}
62+
_ => (),
63+
}
64+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use anyhow::{Ok, Result};
2+
use bitcoin::{
3+
opcodes::all::{OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160},
4+
script::Builder,
5+
};
6+
use k256::{
7+
ecdsa::{Signature, SigningKey, signature::Signer},
8+
elliptic_curve::sec1::ToEncodedPoint,
9+
};
10+
use rand::Rng;
11+
use ripemd::Ripemd160;
12+
use serde::{Deserialize, Serialize};
13+
use sha2::{Digest, Sha256};
14+
15+
#[allow(dead_code)]
16+
#[derive(Serialize, Deserialize)]
17+
pub struct TestUtxo {
18+
pub amount: u64,
19+
pub script_pub_key: String,
20+
pub witness: String,
21+
}
22+
23+
#[allow(dead_code)]
24+
pub fn generate_test_utxos(
25+
utxos_amount: u32,
26+
message: &[u8],
27+
priv_key: &[u8; 32],
28+
) -> Result<Vec<TestUtxo>> {
29+
let mut rng = rand::rng();
30+
let mut res: Vec<TestUtxo> = Vec::with_capacity(utxos_amount as usize);
31+
32+
for _ in 0..utxos_amount {
33+
let amount: u64 = rng.random_range(1000..=10000000);
34+
35+
let sign_key = SigningKey::from_bytes(priv_key)?;
36+
let pub_key = sign_key.verifying_key().to_encoded_point(true);
37+
let pub_key_bytes = pub_key.as_bytes();
38+
39+
let signature: Signature = sign_key.sign(message);
40+
let normalized = signature.normalize_s().unwrap_or(signature).to_der();
41+
let der_bytes = normalized.as_bytes();
42+
43+
let script_pub_key = Builder::new()
44+
.push_opcode(OP_DUP)
45+
.push_opcode(OP_HASH160)
46+
.push_slice(hash160(pub_key_bytes))
47+
.push_opcode(OP_EQUALVERIFY)
48+
.push_opcode(OP_CHECKSIG);
49+
50+
res.push(TestUtxo {
51+
amount,
52+
script_pub_key: hex::encode(script_pub_key.into_bytes()),
53+
witness: hex::encode(Vec::from(der_bytes)),
54+
});
55+
}
56+
57+
Ok(res)
58+
}
59+
60+
pub fn hash160(data: &[u8]) -> [u8; 20] {
61+
Ripemd160::digest(Sha256::digest(data)).into()
62+
}
63+
64+
#[cfg(test)]
65+
mod tests {
66+
use crate::test_data_gen::{generate_test_utxos, hash160};
67+
use k256::{
68+
ecdsa::{Signature, SigningKey, signature::Verifier},
69+
elliptic_curve::sec1::ToEncodedPoint,
70+
};
71+
72+
#[test]
73+
fn text_gen() {
74+
let priv_key = [1; 32];
75+
let utxos = generate_test_utxos(10, &[0; 32], &priv_key).unwrap();
76+
77+
for i in 0..10 {
78+
let sign_key = SigningKey::from_bytes(&priv_key).unwrap();
79+
let ver_key = sign_key.verifying_key();
80+
81+
assert!(
82+
hash160(ver_key.to_encoded_point(true).to_bytes().as_ref())
83+
== hex::decode(&utxos[i].script_pub_key).unwrap()[3..23]
84+
);
85+
86+
let sign = Signature::from_der(&hex::decode(&utxos[i].witness).unwrap()).unwrap();
87+
ver_key.verify(&[0; 32], &sign).unwrap();
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)