Skip to content

Commit 44dc4ca

Browse files
authored
Merge pull request #1162 from interlay/feat/bitcoin-compat
feat: basic bitcoin type conversions
2 parents df11a4a + 2341671 commit 44dc4ca

5 files changed

Lines changed: 273 additions & 67 deletions

File tree

Cargo.lock

Lines changed: 85 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/bitcoin/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ spin = { version = "0.7.1", default-features = false }
1515
primitive-types = { version = "0.12.1", default-features = false, features = ["codec", "scale-info"] }
1616
bitcoin_hashes = { version = "0.7.3", default-features = false }
1717
secp256k1 = { package = "secp256k1", git = "https://github.com/rust-bitcoin/rust-secp256k1", rev = "8e61874", default-features = false }
18+
rust-bitcoin = { package = "bitcoin", version = "0.30.1", default-features = false, features = ["no-std"], optional = true }
1819

1920
[dev-dependencies]
2021
mocktopus = "0.8.0"
@@ -33,9 +34,11 @@ std = [
3334
"primitive-types/std",
3435
"primitive-types/serde",
3536
"secp256k1/std",
37+
"rust-bitcoin?/std"
3638
]
3739
parser = []
3840
runtime-benchmarks = []
41+
bitcoin-types-compat = ["rust-bitcoin", "parser"]
3942

4043
[[example]]
4144
name = "parse-transaction"

crates/bitcoin/src/compat.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
//! Provides conversions between rust-bitcoin and interbtc types.
2+
//! Please note that these operations involve (unbounded) re-encoding
3+
//! and decoding so may be expensive to use.
4+
5+
use crate::{formatter::TryFormat, parser::Parsable};
6+
use rust_bitcoin::consensus::{Decodable, Encodable};
7+
8+
pub use rust_bitcoin;
9+
10+
#[cfg(not(feature = "std"))]
11+
use alloc::vec::Vec;
12+
13+
#[derive(Debug)]
14+
pub enum ConversionError {
15+
ParsingError,
16+
FormattingError,
17+
}
18+
19+
/// Macro to implement type conversion from interbtc types to rust-bitcoin, using consensus encoding
20+
macro_rules! impl_bitcoin_conversion {
21+
($a:path, $b:path) => {
22+
impl TryFrom<$a> for $b {
23+
type Error = ConversionError;
24+
fn try_from(value: $a) -> Result<Self, Self::Error> {
25+
let mut bytes = Vec::<u8>::new();
26+
value
27+
.try_format(&mut bytes)
28+
.map_err(|_| ConversionError::FormattingError)?;
29+
let result = Self::consensus_decode_from_finite_reader(&mut &bytes[..])
30+
.map_err(|_| ConversionError::ParsingError)?;
31+
Ok(result)
32+
}
33+
}
34+
};
35+
}
36+
37+
/// Macro to implement type conversion to interbtc types from rust-bitcoin, using consensus encoding
38+
macro_rules! impl_interbtc_conversion {
39+
($a:path, $b:path) => {
40+
impl TryFrom<$b> for $a {
41+
type Error = ConversionError;
42+
fn try_from(value: $b) -> Result<Self, Self::Error> {
43+
let mut data: Vec<u8> = Vec::new();
44+
value
45+
.consensus_encode(&mut data)
46+
.map_err(|_| ConversionError::FormattingError)?;
47+
let result = Self::parse(&data, 0).map_err(|_| ConversionError::ParsingError)?;
48+
Ok(result.0)
49+
}
50+
}
51+
};
52+
}
53+
54+
macro_rules! impl_bidirectional_conversions {
55+
($a:path, $b:path) => {
56+
impl_bitcoin_conversion!($a, $b);
57+
impl_interbtc_conversion!($a, $b);
58+
};
59+
}
60+
61+
// NOTE: rust_bitcoin::Script exists but we can't convert to that because it's unsized
62+
impl_bitcoin_conversion!(crate::Script, rust_bitcoin::ScriptBuf);
63+
64+
// Transaction conversions
65+
impl_bidirectional_conversions!(crate::types::Transaction, rust_bitcoin::Transaction);
66+
67+
// Payload -> Address
68+
impl TryFrom<rust_bitcoin::address::Payload> for crate::Address {
69+
type Error = ConversionError;
70+
fn try_from(value: rust_bitcoin::address::Payload) -> Result<Self, Self::Error> {
71+
let bitcoin_script = value.script_pubkey();
72+
let bitcoin_script_bytes = bitcoin_script.to_bytes();
73+
let interlay_script = crate::Script::from(bitcoin_script_bytes);
74+
crate::Address::from_script_pub_key(&interlay_script).map_err(|_| ConversionError::ParsingError)
75+
}
76+
}
77+
78+
// Address -> Payload
79+
impl TryFrom<crate::Address> for rust_bitcoin::address::Payload {
80+
type Error = ConversionError;
81+
fn try_from(value: crate::Address) -> Result<Self, Self::Error> {
82+
let interlay_script = value.to_script_pub_key();
83+
let bitcoin_script = rust_bitcoin::blockdata::script::Script::from_bytes(interlay_script.as_bytes());
84+
rust_bitcoin::address::Payload::from_script(&bitcoin_script).map_err(|_| ConversionError::ParsingError)
85+
}
86+
}
87+
88+
#[cfg(test)]
89+
mod tests {
90+
use super::*;
91+
use crate::parser::parse_transaction;
92+
93+
#[test]
94+
fn test_transaction_compat() {
95+
// txid eb3db053cd139147f2fd676cf59a491fd5aebc54bddfde829704585b659126fc
96+
let raw_tx = "0100000000010120e6fb8f0e2cfb8667a140a92d045d5db7c1b56635790bc907c3e71d43720a150e00000017160014641e441c2ba32dd7cf05afde7922144dd106b09bffffffff019dbd54000000000017a914bd847a4912984cf6152547feca51c1b9c2bcbe2787024830450221008f00033064c26cfca4dc98e5dba800b18729c3441dca37b49358ae0df9be7fad02202a81085318466ea66ef390d5dab6737e44a05f7f2e747932ebba917e0098f37d012102c109fc47335c3a2e206d462ad52590b1842aa9d6e0eb9c683c896fa8723590b400000000";
97+
let tx_bytes = hex::decode(&raw_tx).unwrap();
98+
let interlay_transaction = parse_transaction(&tx_bytes).unwrap();
99+
100+
let rust_bitcoin_transaction: rust_bitcoin::Transaction = interlay_transaction.clone().try_into().unwrap();
101+
102+
// check that the rust-bitcoin type encodes to the same bytes
103+
let mut re_encoded_bytes: Vec<u8> = Vec::new();
104+
rust_bitcoin_transaction
105+
.consensus_encode(&mut re_encoded_bytes)
106+
.unwrap();
107+
assert_eq!(tx_bytes, re_encoded_bytes);
108+
109+
// check that the conversion back works
110+
assert_eq!(interlay_transaction, rust_bitcoin_transaction.try_into().unwrap());
111+
}
112+
113+
#[test]
114+
fn test_address_compat() {
115+
let interbtc_address = crate::Address::P2WPKHv0(primitive_types::H160([1; 20]));
116+
let rust_bitcoin_address: rust_bitcoin::address::Payload = interbtc_address.clone().try_into().unwrap();
117+
assert_eq!(interbtc_address, rust_bitcoin_address.try_into().unwrap());
118+
}
119+
}

crates/bitcoin/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ pub mod formatter;
4545
#[cfg(any(feature = "parser", test))]
4646
pub mod parser;
4747

48+
#[cfg(feature = "bitcoin-types-compat")]
49+
pub mod compat;
50+
4851
pub mod utils;
4952

5053
pub mod pow;

0 commit comments

Comments
 (0)