Skip to content

Commit e74a470

Browse files
authored
Feature/bitcoin utils (#19)
* feature: added script sig and serialize ecdsa utilities
1 parent c8bb7d1 commit e74a470

File tree

3 files changed

+194
-1
lines changed

3 files changed

+194
-1
lines changed

Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "omni-transaction"
3-
version = "0.1.1"
3+
version = "0.1.2"
44
authors = ["Proximity Labs Limited"]
55
license = "Apache-2.0"
66
edition = "2021"
@@ -63,6 +63,13 @@ bitcoin = { version = "0.32.0", default-features = false, features = [
6363
"secp-lowmemory",
6464
"secp-recovery",
6565
] }
66+
k256 = { version = "0.13.1", features = [
67+
"sha256",
68+
"ecdsa",
69+
"serde",
70+
"arithmetic",
71+
"expose-field",
72+
] }
6673

6774
# async
6875
tokio = { version = "1.38", features = ["full"] }

src/bitcoin/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ pub mod bitcoin_transaction_builder;
33
pub mod constants;
44
pub mod encoding;
55
pub mod types;
6+
pub mod utils;

src/bitcoin/utils.rs

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
fn encode_signature_as_der(signature_bytes: &[u8]) -> Vec<u8> {
2+
assert_eq!(
3+
signature_bytes.len(),
4+
64,
5+
"Signature must be 64 bytes long (32 bytes for R and 32 bytes for S)"
6+
);
7+
8+
let (r, s) = signature_bytes.split_at(32);
9+
let r_der = encode_asn1_integer(r);
10+
let s_der = encode_asn1_integer(s);
11+
12+
let total_len = r_der.len() + s_der.len();
13+
14+
let mut der = vec![0x30, total_len as u8];
15+
der.extend_from_slice(&r_der);
16+
der.extend_from_slice(&s_der);
17+
18+
der
19+
}
20+
21+
fn encode_asn1_integer(bytes: &[u8]) -> Vec<u8> {
22+
let mut integer = bytes.to_vec();
23+
24+
// if the most significant bit is set, prepend a 0x00 byte
25+
if integer[0] & 0x80 != 0 {
26+
integer.insert(0, 0x00);
27+
}
28+
29+
let mut result = vec![0x02, integer.len() as u8];
30+
result.extend_from_slice(&integer);
31+
32+
result
33+
}
34+
35+
pub fn build_script_sig(der_signature: &[u8], public_key_bytes: &[u8]) -> Vec<u8> {
36+
let mut script_sig = vec![];
37+
script_sig.push(der_signature.len() as u8);
38+
script_sig.extend_from_slice(der_signature);
39+
40+
script_sig.push(public_key_bytes.len() as u8);
41+
script_sig.extend_from_slice(public_key_bytes);
42+
43+
script_sig
44+
}
45+
46+
pub fn serialize_ecdsa_signature(signature_bytes: &[u8], sighash_type: u8) -> Vec<u8> {
47+
// 1. Encode the signature as DER format
48+
let mut der_signature = encode_signature_as_der(signature_bytes);
49+
50+
// 2. Append the SIGHASH type
51+
der_signature.push(sighash_type);
52+
53+
der_signature
54+
}
55+
56+
pub fn serialize_ecdsa_signature_from_str(big_r: &str, s: &str) -> Vec<u8> {
57+
// Generate the signature bytes from the hex strings
58+
let big_r_bytes = hex::decode(big_r).unwrap();
59+
let s_bytes = hex::decode(s).unwrap();
60+
61+
// Create the signature bytes without the compression byte (0x02 or 0x03)
62+
let mut signature_bytes = vec![];
63+
signature_bytes.extend_from_slice(&big_r_bytes[1..]);
64+
signature_bytes.extend_from_slice(&s_bytes);
65+
66+
// Serialize the signature using the custom function
67+
serialize_ecdsa_signature(&signature_bytes, 0x01) // 0x01 = SIGHASH_ALL
68+
}
69+
70+
#[cfg(test)]
71+
mod tests {
72+
use super::*;
73+
use bitcoin::script::Builder;
74+
use bitcoin::secp256k1::ecdsa::Signature;
75+
use bitcoin::secp256k1::{self};
76+
use k256::elliptic_curve::sec1::ToEncodedPoint;
77+
use omni_testing_utilities::address::{self, DerivedAddress};
78+
79+
#[test]
80+
fn test_create_signature() {
81+
let big_r = "03B96BFA3DA6BB4BB74EEEE9C20970725C5782F07724CD1BEFBD265C5AD5C63948";
82+
let s = "49283B618968DEFB0E660EA703D193BC1D213F5DD811A2D13307FCA01E20C5C0";
83+
84+
let signature_built = create_signature(big_r, s);
85+
86+
let signature = bitcoin::ecdsa::Signature {
87+
signature: signature_built.unwrap(),
88+
sighash_type: bitcoin::EcdsaSighashType::All,
89+
};
90+
91+
let serialized_with_bitcoin = signature.serialize();
92+
let serialized_with_bitcoin_bytes = serialized_with_bitcoin.to_vec();
93+
94+
let serialized_with_custom_function = serialize_ecdsa_signature_from_str(big_r, s);
95+
96+
assert_eq!(
97+
serialized_with_custom_function,
98+
serialized_with_bitcoin_bytes
99+
);
100+
}
101+
102+
#[test]
103+
fn test_script_sig() {
104+
const PATH: &str = "bitcoin-1";
105+
106+
let derived_address =
107+
address::get_derived_address(&"omnitester.testnet".parse().unwrap(), PATH);
108+
109+
let big_r = "03B96BFA3DA6BB4BB74EEEE9C20970725C5782F07724CD1BEFBD265C5AD5C63948";
110+
let s = "49283B618968DEFB0E660EA703D193BC1D213F5DD811A2D13307FCA01E20C5C0";
111+
112+
let signature_built = create_signature(big_r, s);
113+
114+
let signature = bitcoin::ecdsa::Signature {
115+
signature: signature_built.unwrap(),
116+
sighash_type: bitcoin::EcdsaSighashType::All,
117+
};
118+
119+
let result = build_script_sig_as_bytes(&derived_address, signature);
120+
121+
// Serialize the signature using the custom function
122+
let serialized_with_custom_function = serialize_ecdsa_signature_from_str(big_r, s);
123+
124+
let compressed_key = get_uncompressed_bitcoin_pubkey(&derived_address);
125+
126+
let result2 = build_script_sig(&serialized_with_custom_function, compressed_key.as_slice());
127+
128+
assert_eq!(result, result2);
129+
}
130+
131+
pub fn get_uncompressed_bitcoin_pubkey(derived_address: &DerivedAddress) -> Vec<u8> {
132+
let derived_public_key_bytes = derived_address.public_key.to_encoded_point(false); // no comprimida
133+
let derived_public_key_bytes_array = derived_public_key_bytes.as_bytes();
134+
135+
let secp_pubkey = bitcoin::secp256k1::PublicKey::from_slice(derived_public_key_bytes_array)
136+
.expect("Invalid public key");
137+
138+
secp_pubkey.serialize_uncompressed().to_vec()
139+
}
140+
141+
// using the bitcoin crate
142+
pub fn create_signature(big_r_hex: &str, s_hex: &str) -> Result<Signature, secp256k1::Error> {
143+
// Convert hex strings to byte arrays
144+
let big_r_bytes = hex::decode(big_r_hex).unwrap();
145+
let s_bytes = hex::decode(s_hex).unwrap();
146+
147+
// Remove the first byte from big_r (compressed point indicator)
148+
let big_r_x_bytes = &big_r_bytes[1..];
149+
150+
// Ensure the byte arrays are the correct length
151+
if big_r_x_bytes.len() != 32 || s_bytes.len() != 32 {
152+
return Err(secp256k1::Error::InvalidSignature);
153+
}
154+
155+
// Create the signature from the bytes
156+
let mut signature_bytes = [0u8; 64];
157+
signature_bytes[..32].copy_from_slice(big_r_x_bytes);
158+
signature_bytes[32..].copy_from_slice(&s_bytes);
159+
160+
// Create the signature object
161+
let signature = Signature::from_compact(&signature_bytes)?;
162+
163+
Ok(signature)
164+
}
165+
166+
pub fn build_script_sig_as_bytes(
167+
derived_address: &DerivedAddress,
168+
signature: bitcoin::ecdsa::Signature,
169+
) -> Vec<u8> {
170+
// Create the public key from the derived address
171+
let derived_public_key_bytes = derived_address.public_key.to_encoded_point(false); // Ensure this method exists
172+
let derived_public_key_bytes_array = derived_public_key_bytes.as_bytes();
173+
let secp_pubkey = bitcoin::secp256k1::PublicKey::from_slice(derived_public_key_bytes_array)
174+
.expect("Invalid public key");
175+
176+
let bitcoin_pubkey = bitcoin::PublicKey::new_uncompressed(secp_pubkey);
177+
178+
let script_sig_new = Builder::new()
179+
.push_slice(signature.serialize())
180+
.push_key(&bitcoin_pubkey)
181+
.into_script();
182+
183+
script_sig_new.as_bytes().to_vec()
184+
}
185+
}

0 commit comments

Comments
 (0)