Skip to content

Commit f77ab57

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 f77ab57

File tree

3 files changed

+73
-5
lines changed

3 files changed

+73
-5
lines changed

bdk-ffi/src/bitcoin.rs

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
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;
2530
use bdk_wallet::miniscript::psbt::PsbtExt;
2631
use bdk_wallet::serde_json;
2732

@@ -51,7 +56,7 @@ impl From<&BdkOutPoint> for OutPoint {
5156
impl From<OutPoint> for BdkOutPoint {
5257
fn from(outpoint: OutPoint) -> Self {
5358
BdkOutPoint {
54-
txid: Txid::from_str(&outpoint.txid).unwrap(),
59+
txid: BitcoinTxid::from_str(&outpoint.txid).unwrap(),
5560
vout: outpoint.vout,
5661
}
5762
}
@@ -575,6 +580,34 @@ impl From<&BdkTxOut> for TxOut {
575580
}
576581
}
577582

583+
/// A bitcoin Block hash
584+
#[derive(uniffi::Object)]
585+
#[uniffi::export(Display)]
586+
pub struct BlockHash(BitcoinBlockHash);
587+
588+
impl_hash_like!(BlockHash, BitcoinBlockHash);
589+
590+
/// A bitcoin transaction identifier
591+
#[derive(uniffi::Object)]
592+
#[uniffi::export(Display)]
593+
pub struct Txid(BitcoinTxid);
594+
595+
impl_hash_like!(Txid, BitcoinTxid);
596+
597+
/// A collision-proof unique identifier for a descriptor.
598+
#[derive(uniffi::Object)]
599+
#[uniffi::export(Display)]
600+
pub struct DescriptorId(BitcoinSha256Hash);
601+
602+
impl_hash_like!(DescriptorId, BitcoinSha256Hash);
603+
604+
/// The merkle root of the merkle tree corresponding to a block's transactions.
605+
#[derive(uniffi::Object)]
606+
#[uniffi::export(Display)]
607+
pub struct TxMerkleNode(BitcoinDoubleSha256Hash);
608+
609+
impl_hash_like!(TxMerkleNode, BitcoinDoubleSha256Hash);
610+
578611
#[cfg(test)]
579612
mod tests {
580613
use crate::bitcoin::Address;

bdk-ffi/src/error.rs

Lines changed: 6 additions & 0 deletions
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

Lines changed: 29 additions & 0 deletions
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+
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+
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)