Skip to content

Commit

Permalink
Constify as much as possible
Browse files Browse the repository at this point in the history
This changes many functions to be const-enabled and marks them as such.
  • Loading branch information
Kixunil committed Apr 1, 2023
1 parent c7f970d commit 2bd58a4
Show file tree
Hide file tree
Showing 42 changed files with 824 additions and 420 deletions.
2 changes: 1 addition & 1 deletion bitcoin/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn main() {
.parse::<u64>()
.expect("invalid Rust minor version");

for activate_version in &[53, 60] {
for activate_version in &[53, 57, 58, 60] {
if minor >= *activate_version {
println!("cargo:rustc-cfg=rust_v_1_{}", activate_version);
}
Expand Down
174 changes: 123 additions & 51 deletions bitcoin/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ use crate::blockdata::constants::{
};
use crate::blockdata::opcodes;
use crate::blockdata::opcodes::all::*;
use crate::blockdata::script::{
self, Instruction, PushBytes, PushBytesBuf, PushBytesErrorReport, Script, ScriptBuf,
};
use crate::blockdata::script::{self, Instruction, PushBytes, Script, ScriptBuf};
use crate::crypto::key::{PublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey};
use crate::error::ParseIntError;
use crate::hash_types::{PubkeyHash, ScriptHash};
Expand Down Expand Up @@ -263,20 +261,68 @@ impl FromStr for WitnessVersion {
}

impl WitnessVersion {
/// Creates witness version parsing the (first) opcode.
///
/// Note: This is not the same representation as [`from_num`](Self::from_num), make sure you're
/// using the right one.
pub const fn from_opcode(opcode: opcodes::All) -> Option<Self> {
match opcode.to_u8() {
0 => Some(WitnessVersion::V0),
version if version >= OP_PUSHNUM_1.to_u8() && version <= OP_PUSHNUM_16.to_u8() =>
WitnessVersion::from_num(version - OP_PUSHNUM_1.to_u8() + 1),
_ => None,
}
}

/// Creates witness version from numeric (human) representation.
///
/// Note: This is not the same representation as [`from_opcode`](Self::from_opcode), make sure
/// you're using the right one.
pub const fn from_num(numeric: u8) -> Option<Self> {
use WitnessVersion::*;

Some(match numeric {
0 => V0,
1 => V1,
2 => V2,
3 => V3,
4 => V4,
5 => V5,
6 => V6,
7 => V7,
8 => V8,
9 => V9,
10 => V10,
11 => V11,
12 => V12,
13 => V13,
14 => V14,
15 => V15,
16 => V16,
_ => return None,
})
}

/// Returns integer version number representation for a given [`WitnessVersion`] value.
///
/// NB: this is not the same as an integer representation of the opcode signifying witness
/// version in bitcoin script. Thus, there is no function to directly convert witness version
/// into a byte since the conversion requires context (bitcoin script or just a version number).
pub fn to_num(self) -> u8 { self as u8 }
pub const fn to_num(self) -> u8 { self as u8 }

/// Determines the checksum variant. See BIP-0350 for specification.
pub fn bech32_variant(&self) -> bech32::Variant {
pub const fn bech32_variant(&self) -> bech32::Variant {
match self {
WitnessVersion::V0 => bech32::Variant::Bech32,
_ => bech32::Variant::Bech32m,
}
}

/// Returns true if this witness version is 0.
pub const fn is_witness_v0(&self) -> bool { matches!(self, WitnessVersion::V0) }

/// Returns true if this witness version is 1 (taproot).
pub const fn is_taproot(&self) -> bool { matches!(self, WitnessVersion::V0) }
}

impl TryFrom<bech32::u5> for WitnessVersion {
Expand Down Expand Up @@ -306,28 +352,7 @@ impl TryFrom<u8> for WitnessVersion {
/// If the integer does not correspond to any witness version, errors with
/// [`Error::InvalidWitnessVersion`].
fn try_from(no: u8) -> Result<Self, Self::Error> {
use WitnessVersion::*;

Ok(match no {
0 => V0,
1 => V1,
2 => V2,
3 => V3,
4 => V4,
5 => V5,
6 => V6,
7 => V7,
8 => V8,
9 => V9,
10 => V10,
11 => V11,
12 => V12,
13 => V13,
14 => V14,
15 => V15,
16 => V16,
wrong => return Err(Error::InvalidWitnessVersion(wrong)),
})
Self::from_num(no).ok_or(Error::InvalidWitnessVersion(no))
}
}

Expand All @@ -343,12 +368,7 @@ impl TryFrom<opcodes::All> for WitnessVersion {
/// If the opcode does not correspond to any witness version, errors with
/// [`Error::MalformedWitnessVersion`].
fn try_from(opcode: opcodes::All) -> Result<Self, Self::Error> {
match opcode.to_u8() {
0 => Ok(WitnessVersion::V0),
version if version >= OP_PUSHNUM_1.to_u8() && version <= OP_PUSHNUM_16.to_u8() =>
WitnessVersion::try_from(version - OP_PUSHNUM_1.to_u8() + 1),
_ => Err(Error::MalformedWitnessVersion),
}
Self::from_opcode(opcode).ok_or(Error::MalformedWitnessVersion)
}
}

Expand Down Expand Up @@ -408,27 +428,27 @@ pub struct WitnessProgram {
/// The witness program version.
version: WitnessVersion,
/// The witness program. (Between 2 and 40 bytes)
program: PushBytesBuf,
program: ProgramBuf,
}

impl WitnessProgram {
/// Creates a new witness program.
pub fn new<P>(version: WitnessVersion, program: P) -> Result<Self, Error>
where
P: TryInto<PushBytesBuf>,
<P as TryInto<PushBytesBuf>>::Error: PushBytesErrorReport,
{
let program = program
.try_into()
.map_err(|error| Error::InvalidWitnessProgramLength(error.input_len()))?;
pub const fn new(version: WitnessVersion, program: &[u8]) -> Result<Self, Error> {
if program.len() < 2 || program.len() > 40 {
return Err(Error::InvalidWitnessProgramLength(program.len()));
}

// Specific segwit v0 check. These addresses can never spend funds sent to them.
if version == WitnessVersion::V0 && (program.len() != 20 && program.len() != 32) {
if version.is_witness_v0() && (program.len() != 20 && program.len() != 32) {
return Err(Error::InvalidSegwitV0ProgramLength(program.len()));
}
let mut bytes = [0; 40];
let mut i = 0;
while i < program.len() {
bytes[i] = program[i];
i += 1;
}
let program = ProgramBuf { bytes, len: program.len() as u8 };
Ok(WitnessProgram { version, program })
}

Expand All @@ -439,6 +459,42 @@ impl WitnessProgram {
pub fn program(&self) -> &PushBytes { &self.program }
}

#[derive(Clone, Eq)]
struct ProgramBuf {
bytes: [u8; 40],
len: u8,
}

impl core::ops::Deref for ProgramBuf {
type Target = PushBytes;

fn deref(&self) -> &Self::Target {
&AsRef::<PushBytes>::as_ref(&self.bytes)[..usize::from(self.len)]
}
}

impl fmt::Debug for ProgramBuf {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&**self, f) }
}

impl PartialEq for ProgramBuf {
fn eq(&self, other: &ProgramBuf) -> bool { **self == **other }
}

impl Ord for ProgramBuf {
fn cmp(&self, other: &Self) -> core::cmp::Ordering { (**self).cmp(&**other) }
}

impl PartialOrd for ProgramBuf {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
(**self).partial_cmp(&**other)
}
}

impl core::hash::Hash for ProgramBuf {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) { (**self).hash(state) }
}

impl Payload {
/// Constructs a [Payload] from an output script (`scriptPubkey`).
pub fn from_script(script: &Script) -> Result<Payload, Error> {
Expand All @@ -451,7 +507,7 @@ impl Payload {
} else if script.is_witness_program() {
let opcode = script.first_opcode().expect("witness_version guarantees len() > 4");

let witness_program = script.as_bytes()[2..].to_vec();
let witness_program = &script.as_bytes()[2..];

let witness_program =
WitnessProgram::new(WitnessVersion::try_from(opcode)?, witness_program)?;
Expand Down Expand Up @@ -501,7 +557,7 @@ impl Payload {
pub fn p2wpkh(pk: &PublicKey) -> Result<Payload, Error> {
let prog = WitnessProgram::new(
WitnessVersion::V0,
pk.wpubkey_hash().ok_or(Error::UncompressedPubkey)?,
pk.wpubkey_hash().ok_or(Error::UncompressedPubkey)?.as_byte_array(),
)?;
Ok(Payload::WitnessProgram(prog))
}
Expand All @@ -517,7 +573,7 @@ impl Payload {

/// Create a witness pay to script hash payload.
pub fn p2wsh(script: &Script) -> Payload {
let prog = WitnessProgram::new(WitnessVersion::V0, script.wscript_hash())
let prog = WitnessProgram::new(WitnessVersion::V0, script.wscript_hash().as_byte_array())
.expect("wscript_hash has len 32 compatible with segwitv0");
Payload::WitnessProgram(prog)
}
Expand All @@ -536,7 +592,7 @@ impl Payload {
merkle_root: Option<TapNodeHash>,
) -> Payload {
let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root);
let prog = WitnessProgram::new(WitnessVersion::V1, output_key.to_inner().serialize())
let prog = WitnessProgram::new(WitnessVersion::V1, &output_key.to_inner().serialize())
.expect("taproot output key has len 32 <= 40");
Payload::WitnessProgram(prog)
}
Expand All @@ -545,7 +601,7 @@ impl Payload {
///
/// This method is not recommended for use and [Payload::p2tr()] should be used where possible.
pub fn p2tr_tweaked(output_key: TweakedPublicKey) -> Payload {
let prog = WitnessProgram::new(WitnessVersion::V1, output_key.to_inner().serialize())
let prog = WitnessProgram::new(WitnessVersion::V1, &output_key.to_inner().serialize())
.expect("taproot output key has len 32 <= 40");
Payload::WitnessProgram(prog)
}
Expand Down Expand Up @@ -827,6 +883,14 @@ impl<V: NetworkValidation> Address<V> {

/// Methods and functions that can be called only on `Address<NetworkChecked>`.
impl Address {
/// Create new checked address from given components.
///
/// This is useful in `const` context or when inference is not possible.
#[inline]
pub const fn new_checked(network: Network, payload: Payload) -> Address {
Address { network, payload, _validation: PhantomData }
}

/// Creates a pay to (compressed) public key hash address from a public key.
///
/// This is the preferred non-witness type address.
Expand Down Expand Up @@ -998,6 +1062,14 @@ impl Address {

/// Methods that can be called only on `Address<NetworkUnchecked>`.
impl Address<NetworkUnchecked> {
/// Create new unchecked address from given components.
///
/// This is useful in `const` context or when inference is not possible.
#[inline]
pub const fn new_unchecked(network: Network, payload: Payload) -> Address<NetworkUnchecked> {
Address { network, payload, _validation: PhantomData }
}

/// Parsed addresses do not always have *one* network. The problem is that legacy testnet,
/// regtest and signet addresse use the same prefix instead of multiple different ones. When
/// parsing, such addresses are always assumed to be testnet addresses (the same is true for
Expand Down Expand Up @@ -1138,7 +1210,7 @@ impl FromStr for Address<NetworkUnchecked> {
(WitnessVersion::try_from(v[0])?, bech32::FromBase32::from_base32(p5)?)
};

let witness_program = WitnessProgram::new(version, program)?;
let witness_program = WitnessProgram::new(version, &program)?;

// Encoding check
let expected = version.bech32_variant();
Expand Down Expand Up @@ -1339,7 +1411,7 @@ mod tests {
let program = hex!(
"654f6ea368e0acdfd92976b7c2103a1b26313f430654f6ea368e0acdfd92976b7c2103a1b26313f4"
);
let witness_prog = WitnessProgram::new(WitnessVersion::V13, program.to_vec()).unwrap();
let witness_prog = WitnessProgram::new(WitnessVersion::V13, &program).unwrap();
let addr = Address::new(Bitcoin, Payload::WitnessProgram(witness_prog));
roundtrips(&addr);
}
Expand Down Expand Up @@ -1588,7 +1660,7 @@ mod tests {
Payload::WitnessProgram(
WitnessProgram::new(
WitnessVersion::try_from(version).unwrap(),
vec![0xab; 32], // Choose 32 to make test case valid for all witness versions(including v0)
&[0xab; 32], // Choose 32 to make test case valid for all witness versions(including v0)
)
.unwrap(),
)
Expand Down
16 changes: 8 additions & 8 deletions bitcoin/src/amount.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ impl Amount {
pub const fn from_sat(satoshi: u64) -> Amount { Amount(satoshi) }

/// Gets the number of satoshis in this [`Amount`].
pub fn to_sat(self) -> u64 { self.0 }
pub const fn to_sat(self) -> u64 { self.0 }

/// The maximum value of an [Amount].
pub const fn max_value() -> Amount { Amount(u64::max_value()) }
Expand Down Expand Up @@ -645,7 +645,7 @@ impl Amount {
pub fn checked_rem(self, rhs: u64) -> Option<Amount> { self.0.checked_rem(rhs).map(Amount) }

/// Convert to a signed amount.
pub fn to_signed(self) -> Result<SignedAmount, ParseAmountError> {
pub const fn to_signed(self) -> Result<SignedAmount, ParseAmountError> {
if self.to_sat() > SignedAmount::max_value().to_sat() as u64 {
Err(ParseAmountError::TooBig)
} else {
Expand Down Expand Up @@ -834,7 +834,7 @@ impl SignedAmount {
pub const fn from_sat(satoshi: i64) -> SignedAmount { SignedAmount(satoshi) }

/// Gets the number of satoshis in this [`SignedAmount`].
pub fn to_sat(self) -> i64 { self.0 }
pub const fn to_sat(self) -> i64 { self.0 }

/// The maximum value of an [SignedAmount].
pub const fn max_value() -> SignedAmount { SignedAmount(i64::max_value()) }
Expand Down Expand Up @@ -954,22 +954,22 @@ impl SignedAmount {
// Some arithmetic that doesn't fit in `core::ops` traits.

/// Get the absolute value of this [SignedAmount].
pub fn abs(self) -> SignedAmount { SignedAmount(self.0.abs()) }
pub const fn abs(self) -> SignedAmount { SignedAmount(self.0.abs()) }

/// Returns a number representing sign of this [SignedAmount].
///
/// - `0` if the amount is zero
/// - `1` if the amount is positive
/// - `-1` if the amount is negative
pub fn signum(self) -> i64 { self.0.signum() }
pub const fn signum(self) -> i64 { self.0.signum() }

/// Returns `true` if this [SignedAmount] is positive and `false` if
/// this [SignedAmount] is zero or negative.
pub fn is_positive(self) -> bool { self.0.is_positive() }
pub const fn is_positive(self) -> bool { self.0.is_positive() }

/// Returns `true` if this [SignedAmount] is negative and `false` if
/// this [SignedAmount] is zero or positive.
pub fn is_negative(self) -> bool { self.0.is_negative() }
pub const fn is_negative(self) -> bool { self.0.is_negative() }

/// Get the absolute value of this [SignedAmount].
/// Returns [None] if overflow occurred. (`self == min_value()`)
Expand Down Expand Up @@ -1018,7 +1018,7 @@ impl SignedAmount {
}

/// Convert to an unsigned amount.
pub fn to_unsigned(self) -> Result<Amount, ParseAmountError> {
pub const fn to_unsigned(self) -> Result<Amount, ParseAmountError> {
if self.is_negative() {
Err(ParseAmountError::Negative)
} else {
Expand Down

0 comments on commit 2bd58a4

Please sign in to comment.