Skip to content

Commit 161dc92

Browse files
committed
feat(bitcoin): introduce hash-like types
Types like "Txid", "BlockHash", "DescriptorId" are all just 32 byte arrays that represent hashes with different meanings. Currently they are represented as strings at the FFI layer, but they are also meaningful arrays of bytes. Particularly if a user wants to implement persistence over the FFI layer, they would want to efficiently serialize these types. Here I am introducing a new group of types that all implement display, allow serialization to bytes, and may be constructed from an array of bytes. I went with a "rule of 3s" here, and also introduced a macro to do these implementations because there was a lot of boilerplate involved. Note that all of these are included in the wallet changeset, which is required to represent in-full for FFI-layer custom persistence.
1 parent 1d8ee94 commit 161dc92

File tree

3 files changed

+82
-5
lines changed

3 files changed

+82
-5
lines changed

bdk-ffi/src/bitcoin.rs

+47-5
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,33 @@
11
use crate::error::{
2-
AddressParseError, ExtractTxError, FeeRateError, FromScriptError, PsbtError, PsbtParseError,
3-
TransactionError,
2+
AddressParseError, ExtractTxError, FeeRateError, FromScriptError, HashParseError, PsbtError,
3+
PsbtParseError, TransactionError,
44
};
55
use crate::error::{ParseAmountError, PsbtFinalizeError};
6-
use crate::{impl_from_core_type, impl_into_core_type};
6+
use crate::{impl_from_core_type, impl_hash_like, impl_into_core_type};
77

88
use bdk_wallet::bitcoin::address::NetworkChecked;
99
use bdk_wallet::bitcoin::address::NetworkUnchecked;
1010
use bdk_wallet::bitcoin::address::{Address as BdkAddress, AddressData as BdkAddressData};
1111
use bdk_wallet::bitcoin::blockdata::block::Header as BdkHeader;
12+
use bdk_wallet::bitcoin::consensus::encode::deserialize;
1213
use bdk_wallet::bitcoin::consensus::encode::serialize;
1314
use bdk_wallet::bitcoin::consensus::Decodable;
15+
use bdk_wallet::bitcoin::hashes::sha256::Hash as BitcoinSha256Hash;
16+
use bdk_wallet::bitcoin::hashes::sha256d::Hash as BitcoinDoubleSha256Hash;
1417
use bdk_wallet::bitcoin::io::Cursor;
1518
use bdk_wallet::bitcoin::secp256k1::Secp256k1;
1619
use bdk_wallet::bitcoin::Amount as BdkAmount;
20+
use bdk_wallet::bitcoin::BlockHash as BitcoinBlockHash;
1721
use bdk_wallet::bitcoin::FeeRate as BdkFeeRate;
1822
use bdk_wallet::bitcoin::Network;
23+
use bdk_wallet::bitcoin::OutPoint as BdkOutPoint;
1924
use bdk_wallet::bitcoin::Psbt as BdkPsbt;
2025
use bdk_wallet::bitcoin::ScriptBuf as BdkScriptBuf;
2126
use bdk_wallet::bitcoin::Transaction as BdkTransaction;
2227
use bdk_wallet::bitcoin::TxIn as BdkTxIn;
2328
use bdk_wallet::bitcoin::TxOut as BdkTxOut;
24-
use bdk_wallet::bitcoin::{OutPoint as BdkOutPoint, Txid};
29+
use bdk_wallet::bitcoin::Txid as BitcoinTxid;
30+
use bdk_wallet::bitcoin::Wtxid as BitcoinWtxid;
2531
use bdk_wallet::miniscript::psbt::PsbtExt;
2632
use bdk_wallet::serde_json;
2733

@@ -51,7 +57,7 @@ impl From<&BdkOutPoint> for OutPoint {
5157
impl From<OutPoint> for BdkOutPoint {
5258
fn from(outpoint: OutPoint) -> Self {
5359
BdkOutPoint {
54-
txid: Txid::from_str(&outpoint.txid).unwrap(),
60+
txid: BitcoinTxid::from_str(&outpoint.txid).unwrap(),
5561
vout: outpoint.vout,
5662
}
5763
}
@@ -575,6 +581,42 @@ impl From<&BdkTxOut> for TxOut {
575581
}
576582
}
577583

584+
/// A bitcoin Block hash
585+
#[derive(Debug, PartialEq, Eq, std::hash::Hash, uniffi::Object)]
586+
#[uniffi::export(Display, Eq, Hash)]
587+
pub struct BlockHash(pub(crate) BitcoinBlockHash);
588+
589+
impl_hash_like!(BlockHash, BitcoinBlockHash);
590+
591+
/// A bitcoin transaction identifier
592+
#[derive(Debug, PartialEq, Eq, std::hash::Hash, uniffi::Object)]
593+
#[uniffi::export(Display, Eq, Hash)]
594+
pub struct Txid(pub(crate) BitcoinTxid);
595+
596+
impl_hash_like!(Txid, BitcoinTxid);
597+
598+
/// A bitcoin transaction identifier, including witness data.
599+
/// For transactions with no SegWit inputs, the `txid` will be equivalent to `wtxid`.
600+
#[derive(Debug, PartialEq, Eq, std::hash::Hash, uniffi::Object)]
601+
#[uniffi::export(Display, Eq, Hash)]
602+
pub struct Wtxid(pub(crate) BitcoinWtxid);
603+
604+
impl_hash_like!(Wtxid, BitcoinWtxid);
605+
606+
/// A collision-proof unique identifier for a descriptor.
607+
#[derive(Debug, PartialEq, Eq, std::hash::Hash, uniffi::Object)]
608+
#[uniffi::export(Display, Eq, Hash)]
609+
pub struct DescriptorId(pub(crate) BitcoinSha256Hash);
610+
611+
impl_hash_like!(DescriptorId, BitcoinSha256Hash);
612+
613+
/// The merkle root of the merkle tree corresponding to a block's transactions.
614+
#[derive(Debug, PartialEq, Eq, std::hash::Hash, uniffi::Object)]
615+
#[uniffi::export(Display, Eq, Hash)]
616+
pub struct TxMerkleNode(pub(crate) BitcoinDoubleSha256Hash);
617+
618+
impl_hash_like!(TxMerkleNode, BitcoinDoubleSha256Hash);
619+
578620
#[cfg(test)]
579621
mod tests {
580622
use crate::bitcoin::Address;

bdk-ffi/src/error.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1507,6 +1507,12 @@ impl From<BdkEncodeError> for TransactionError {
15071507
}
15081508
}
15091509

1510+
#[derive(Debug, thiserror::Error, uniffi::Error)]
1511+
pub enum HashParseError {
1512+
#[error("invalid hash: expected length 32 bytes, got {len} bytes")]
1513+
InvalidHash { len: u32 },
1514+
}
1515+
15101516
impl From<BdkSqliteError> for SqliteError {
15111517
fn from(error: BdkSqliteError) -> Self {
15121518
SqliteError::Sqlite {

bdk-ffi/src/macros.rs

+29
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,32 @@ macro_rules! impl_into_core_type {
1919
}
2020
};
2121
}
22+
23+
#[macro_export]
24+
macro_rules! impl_hash_like {
25+
($ffi_type:ident, $core_type:ident) => {
26+
#[uniffi::export]
27+
impl $ffi_type {
28+
/// Construct a hash-like type from 32 bytes.
29+
#[uniffi::constructor]
30+
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, HashParseError> {
31+
let hash_like: $core_type = deserialize(&bytes).map_err(|_| {
32+
let len = bytes.len() as u32;
33+
HashParseError::InvalidHash { len }
34+
})?;
35+
Ok(Self(hash_like))
36+
}
37+
38+
/// Serialize this type into a 32 byte array.
39+
pub fn serialize(&self) -> Vec<u8> {
40+
serialize(&self.0)
41+
}
42+
}
43+
44+
impl std::fmt::Display for $ffi_type {
45+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46+
self.0.fmt(f)
47+
}
48+
}
49+
};
50+
}

0 commit comments

Comments
 (0)