Skip to content

Commit 9304937

Browse files
committed
Merge #279: Improve slicing and error handling around slices
5e0d577 pset: fix slicing in KeySource::deserialize (Andrew Poelstra) 4384012 address: do a better job slicing bech32 data (Andrew Poelstra) cae359d transaction: use better error typing for pegin destructuring (Andrew Poelstra) 6809a7f blind: use array splitting in TxOut::unblind (fix potential DoS?) (Andrew Poelstra) ee50a5f rewrite Address::from_base58 to eliminate all the unwraps (Andrew Poelstra) ed024b2 add bitcoin-internals dependency (Andrew Poelstra) Pull request description: Uses the `bitcoin-internals` library which provides us a bunch of array/slice methods that aren't available in std in our MSRV. Eventually we will be able to drop the dep and get these features directly from std. This replaces a bunch of slice accesses with arrays, cleaning up error paths and eliminating panics (though most are inaccessible assuming you use known-valid data from the blockchain). Builds on #278 (which I will merge in the next hour). Will do a followup that cleans up the error types; I deliberately avoided breaking the API to make this one easy to backport. ACKs for top commit: apoelstra: ACK 5e0d577; successfully ran local tests philipr-za: ACK 5e0d577 Tree-SHA512: b9238a0a2c642061c47671b955eb19771485448f8b455be268f2a5ee223f29f0ef09f67f792fcf9400481826c5a9c5ec2d0ee6eceb2a3da458819178f5f54d37
2 parents bab8a59 + 5e0d577 commit 9304937

9 files changed

Lines changed: 85 additions & 43 deletions

File tree

Cargo-recent.lock

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ version = "0.1.0"
2626
source = "registry+https://github.com/rust-lang/crates.io-index"
2727
checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f"
2828
dependencies = [
29-
"bitcoin-internals",
29+
"bitcoin-internals 0.3.0",
3030
"bitcoin_hashes",
3131
]
3232

@@ -66,7 +66,7 @@ dependencies = [
6666
"base58ck",
6767
"base64 0.21.7",
6868
"bech32",
69-
"bitcoin-internals",
69+
"bitcoin-internals 0.3.0",
7070
"bitcoin-io",
7171
"bitcoin-units",
7272
"bitcoin_hashes",
@@ -85,6 +85,12 @@ dependencies = [
8585
"serde",
8686
]
8787

88+
[[package]]
89+
name = "bitcoin-internals"
90+
version = "0.5.0"
91+
source = "registry+https://github.com/rust-lang/crates.io-index"
92+
checksum = "a30a22d1f112dde8e16be7b45c63645dc165cef254f835b3e1e9553e485cfa64"
93+
8894
[[package]]
8995
name = "bitcoin-io"
9096
version = "0.1.3"
@@ -103,7 +109,7 @@ version = "0.1.2"
103109
source = "registry+https://github.com/rust-lang/crates.io-index"
104110
checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2"
105111
dependencies = [
106-
"bitcoin-internals",
112+
"bitcoin-internals 0.3.0",
107113
"serde",
108114
]
109115

@@ -203,6 +209,7 @@ dependencies = [
203209
"bech32",
204210
"bincode",
205211
"bitcoin",
212+
"bitcoin-internals 0.5.0",
206213
"getrandom 0.2.16",
207214
"hex-conservative 1.1.0",
208215
"rand",

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ base64 = ["bitcoin/base64"]
2929
[dependencies]
3030
bech32 = "0.11.0"
3131
bitcoin = "0.32.2"
32+
internals = { package = "bitcoin-internals", version = "0.5" }
3233
secp256k1-zkp = { version = "0.11.0", features = ["global-context", "hashes"] }
3334

3435
# Used for ContractHash::from_json_contract.
@@ -188,6 +189,7 @@ zero_sized_map_values = "warn"
188189

189190
[package.metadata.rbmt.lint]
190191
allowed_duplicates = [
192+
"bitcoin-internals",
191193
"hex-conservative",
192194
]
193195

elementsd-tests/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ rand = "0.8"
1616
[package.metadata.rbmt.lint]
1717
# FIXME the bulk of these are because elementsd/bitcoind is much older than rust-bitcoin.
1818
allowed_duplicates = [
19+
"bitcoin-internals",
1920
"hex-conservative",
2021
"base64",
2122
"getrandom",

fuzz/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use_self = "warn"
2222

2323
[package.metadata.rbmt.lint]
2424
allowed_duplicates = [
25+
"bitcoin-internals",
2526
"hex-conservative",
2627
"getrandom",
2728
"wasi",

src/address.rs

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ use crate::blech32::{Blech32, Blech32m};
2626
use crate::hashes::Hash;
2727
use bitcoin::base58;
2828
use bitcoin::PublicKey;
29+
use internals::array::ArrayExt as _;
30+
use internals::slice::SliceExt;
2931
use secp256k1_zkp;
3032
use secp256k1_zkp::Secp256k1;
3133
use secp256k1_zkp::Verification;
@@ -467,13 +469,17 @@ impl Address {
467469
};
468470

469471
let (blinding_pubkey, program) = match blinded {
470-
true => (
472+
true => {
473+
let (pk, rest) = SliceExt::split_first_chunk::<33>(data.as_slice())
474+
.ok_or(AddressError::InvalidSegwitV0Encoding)?;
475+
(
471476
Some(
472-
secp256k1_zkp::PublicKey::from_slice(&data[..33])
477+
secp256k1_zkp::PublicKey::from_slice(pk)
473478
.map_err(AddressError::InvalidBlindingPubKey)?,
474-
),
475-
data[33..].to_vec(),
476-
),
479+
),
480+
rest.to_vec(),
481+
)
482+
},
477483
false => (None, data),
478484
};
479485

@@ -486,35 +492,39 @@ impl Address {
486492

487493
// data.len() should be >= 1 when this method is called
488494
fn from_base58(data: &[u8], params: &'static AddressParams) -> Result<Address, AddressError> {
495+
let len_error = AddressError::InvalidLength(data.len());
489496
// When unblinded, the structure is:
490497
// <1: regular prefix> <20: hash160>
491498
// When blinded, the structure is:
492499
// <1: blinding prefix> <1: regular prefix> <33: blinding pubkey> <20: hash160>
493500

494-
let blinded = data[0] == params.blinded_prefix;
495-
let prefix = match (blinded, data.len()) {
496-
(true, 55) => data[1],
497-
(false, 21) => data[0],
498-
(_, len) => return Err(AddressError::InvalidLength(len)),
501+
let Some((blinding_prefix, blinded_data)) = data.split_first() else {
502+
return Err(len_error);
499503
};
500504

501-
let (blinding_pubkey, payload_data) = match blinded {
502-
true => (
503-
Some(
504-
secp256k1_zkp::PublicKey::from_slice(&data[2..35])
505-
.map_err(AddressError::InvalidBlindingPubKey)?,
506-
),
507-
&data[35..],
508-
),
509-
false => (None, &data[1..]),
505+
let (prefix, blinding_pubkey, hash) = if *blinding_prefix == params.blinded_prefix {
506+
let Some((prefix, pubkey_and_hash)) = blinded_data.split_first() else {
507+
return Err(len_error);
508+
};
509+
510+
let pubkey_and_hash = <&[u8; 53]>::try_from(pubkey_and_hash).map_err(|_| len_error)?;
511+
let (pubkey, hash) = pubkey_and_hash.split_array::<33, 20>();
512+
513+
let blinding_pubkey = secp256k1_zkp::PublicKey::from_slice(pubkey)
514+
.map_err(AddressError::InvalidBlindingPubKey)?;
515+
516+
(prefix, Some(blinding_pubkey), hash)
517+
} else {
518+
let hash = <&[u8; 20]>::try_from(blinded_data).map_err(|_| len_error)?;
519+
(blinding_prefix, None, hash)
510520
};
511521

512-
let payload = if prefix == params.p2pkh_prefix {
513-
Payload::PubkeyHash(PubkeyHash::from_slice(payload_data).unwrap())
514-
} else if prefix == params.p2sh_prefix {
515-
Payload::ScriptHash(ScriptHash::from_slice(payload_data).unwrap())
522+
let payload = if *prefix == params.p2pkh_prefix {
523+
Payload::PubkeyHash(PubkeyHash::from_byte_array(*hash))
524+
} else if *prefix == params.p2sh_prefix {
525+
Payload::ScriptHash(ScriptHash::from_byte_array(*hash))
516526
} else {
517-
return Err(AddressError::InvalidAddressVersion(prefix));
527+
return Err(AddressError::InvalidAddressVersion(*prefix));
518528
};
519529

520530
Ok(Address {

src/blind.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
//! # Transactions Blinding
1616
//!
1717
18-
use core::convert::TryFrom;
18+
use internals::array::ArrayExt as _;
19+
use internals::slice::SliceExt;
1920
use std::{self, collections::BTreeMap, fmt};
2021

2122
use secp256k1_zkp::{
@@ -761,16 +762,31 @@ impl TxOut {
761762
additional_generator,
762763
)?;
763764

764-
let (asset, asset_bf) = opening.message.as_ref().split_at(32);
765-
let asset = <[u8; 32]>::try_from(asset).map_err(UnblindError::MalformedAssetId)?;
766-
let asset = AssetId::from_byte_array(asset);
767-
let asset_bf = AssetBlindingFactor::from_slice(&asset_bf[..32])?;
765+
// Use `MissingRangeproof` error because it's available so does not require
766+
// API breaks. In a later PR we should extend that enum and add #[non_exhaustive]
767+
// to it. The maybe-better `MalformedAssetId` error requires we start with a
768+
// std `FromSliceError` which we don't have.
769+
let asset_and_bf = SliceExt::split_first_chunk::<64>(opening.message.as_ref())
770+
.ok_or(UnblindError::MissingRangeproof)?
771+
.0;
772+
let (asset_id, asset_bf) = asset_and_bf.split_array();
773+
774+
let asset_id = AssetId::from_byte_array(*asset_id);
775+
let asset_bf = AssetBlindingFactor::from_byte_array(*asset_bf)?;
776+
if let Asset::Confidential(own_asset) = self.asset {
777+
let secp = Secp256k1::signing_only(); // needed to avoid API break
778+
let asset = Generator::new_blinded(&secp, asset_id.into_tag(), asset_bf.into_inner());
779+
if asset != own_asset {
780+
// See above about use of MissingRangeproof.
781+
return Err(UnblindError::MissingRangeproof);
782+
}
783+
}
768784

769785
let value = opening.value;
770786
let value_bf = ValueBlindingFactor(opening.blinding_factor);
771787

772788
Ok(TxOutSecrets {
773-
asset,
789+
asset: asset_id,
774790
asset_bf,
775791
value,
776792
value_bf,

src/confidential.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,11 @@ impl AssetBlindingFactor {
789789
s.parse()
790790
}
791791

792+
/// Create from bytes.
793+
pub fn from_byte_array(bytes: [u8; 32]) -> Result<Self, secp256k1_zkp::Error> {
794+
Ok(AssetBlindingFactor(Tweak::from_inner(bytes)?))
795+
}
796+
792797
/// Create from bytes.
793798
pub fn from_slice(bytes: &[u8]) -> Result<Self, secp256k1_zkp::Error> {
794799
Ok(AssetBlindingFactor(Tweak::from_slice(bytes)?))

src/pset/serialize.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
//! Defines traits used for (de)serializing PSET values into/from raw
1818
//! bytes in PSET key-value pairs.
1919
20-
use std::convert::TryFrom;
2120
use std::io;
2221

2322
use crate::confidential::{self, AssetBlindingFactor};
@@ -29,6 +28,7 @@ use crate::{AssetId, BlockHash, Script, Transaction, TxOut, Txid};
2928
use bitcoin;
3029
use bitcoin::bip32::{ChildNumber, Fingerprint, KeySource};
3130
use bitcoin::{key::XOnlyPublicKey, PublicKey};
31+
use internals::slice::SliceExt;
3232
use secp256k1_zkp::{self, RangeProof, SurjectionProof, Tweak};
3333

3434
use super::map::{PsbtSighashType, TapTree};
@@ -176,16 +176,15 @@ impl Serialize for KeySource {
176176

177177
impl Deserialize for KeySource {
178178
fn deserialize(bytes: &[u8]) -> Result<Self, encode::Error> {
179-
let Ok(prefix) = <[u8; 4]>::try_from(&bytes[0..4]) else {
179+
let Some((prefix, mut rest)) = SliceExt::split_first_chunk::<4>(bytes) else {
180180
return Err(io::Error::from(io::ErrorKind::UnexpectedEof).into());
181181
};
182182

183183
let fprint: Fingerprint = Fingerprint::from(prefix);
184184
let mut dpath: Vec<ChildNumber> = Vec::default();
185185

186-
let mut d = &bytes[4..];
187-
while !d.is_empty() {
188-
let index = u32::consensus_decode(&mut d)?;
186+
while !rest.is_empty() {
187+
let index = u32::consensus_decode(&mut rest)?;
189188
dpath.push(index.into());
190189
}
191190

src/transaction.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use std::collections::HashMap;
2020
use std::convert::TryFrom;
2121

2222
use bitcoin::{self, VarInt};
23+
use internals::slice::SliceExt;
2324
use crate::hashes::Hash;
2425

2526
use crate::{confidential, ContractHash};
@@ -432,12 +433,12 @@ impl<'tx> PeginData<'tx> {
432433
pegin_witness: &'tx [Vec<u8>],
433434
prevout: bitcoin::OutPoint,
434435
) -> Result<PeginData<'tx>, &'static str> {
435-
if pegin_witness.len() != 6 {
436+
let Ok(pegin_witness) = <&[Vec<u8>; 6]>::try_from(pegin_witness) else {
436437
return Err("size not 6");
437-
}
438-
if pegin_witness[5].len() < 80 {
438+
};
439+
let Some((block_header, _)) = SliceExt::split_first_chunk::<80>(pegin_witness[5].as_slice()) else {
439440
return Err("merkle proof too short");
440-
}
441+
};
441442

442443
Ok(PeginData {
443444
outpoint: prevout,
@@ -448,7 +449,7 @@ impl<'tx> PeginData<'tx> {
448449
claim_script: &pegin_witness[3],
449450
tx: &pegin_witness[4],
450451
merkle_proof: &pegin_witness[5],
451-
referenced_block: bitcoin::BlockHash::hash(&pegin_witness[5][0..80]),
452+
referenced_block: bitcoin::BlockHash::hash(block_header),
452453
})
453454
}
454455

0 commit comments

Comments
 (0)