Incomplete work on using Option to remove panic cases

This commit is contained in:
Luke Parker
2024-06-22 09:02:59 -04:00
parent b5b9d4a871
commit 1db40914eb
8 changed files with 255 additions and 165 deletions

View File

@@ -15,16 +15,26 @@ const CORRECT_BLOCK_HASH_202612: [u8; 32] =
const EXISTING_BLOCK_HASH_202612: [u8; 32] =
hex_literal::hex!("bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698");
/// A Monero block's header.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct BlockHeader {
/// The major version of the protocol, denoting the hard fork.
pub major_version: u8,
/// The minor version of the protocol.
pub minor_version: u8,
/// Seconds since the epoch.
pub timestamp: u64,
/// The previous block's hash.
pub previous: [u8; 32],
/// The nonce used to mine the block.
///
/// Miners should increment this while attempting to find a block with a hash satisfying the PoW
/// rules.
pub nonce: u32,
}
impl BlockHeader {
/// Write the BlockHeader.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
write_varint(&self.major_version, w)?;
write_varint(&self.minor_version, w)?;
@@ -33,12 +43,14 @@ impl BlockHeader {
w.write_all(&self.nonce.to_le_bytes())
}
/// Serialize the BlockHeader to a Vec<u8>.
pub fn serialize(&self) -> Vec<u8> {
let mut serialized = vec![];
self.write(&mut serialized).unwrap();
serialized
}
/// Read a BlockHeader.
pub fn read<R: Read>(r: &mut R) -> io::Result<BlockHeader> {
Ok(BlockHeader {
major_version: read_varint(r)?,
@@ -50,14 +62,22 @@ impl BlockHeader {
}
}
/// A Monero block.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Block {
/// The block's header.
pub header: BlockHeader,
/// The miner's transaction.
pub miner_tx: Transaction,
/// The transactions within this block.
pub txs: Vec<[u8; 32]>,
}
impl Block {
/// The zero-index position of this block within the blockchain.
///
/// This information comes from the Block's miner transaction. If the miner transaction isn't
/// structed as expected, this will return None.
pub fn number(&self) -> Option<u64> {
match self.miner_tx.prefix.inputs.first() {
Some(Input::Gen(number)) => Some(*number),
@@ -65,6 +85,7 @@ impl Block {
}
}
/// Write the BlockHeader.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
self.header.write(w)?;
self.miner_tx.write(w)?;
@@ -75,22 +96,25 @@ impl Block {
Ok(())
}
fn tx_merkle_root(&self) -> [u8; 32] {
merkle_root(self.miner_tx.hash(), &self.txs)
/// Serialize the BlockHeader to a Vec<u8>.
pub fn serialize(&self) -> Vec<u8> {
let mut serialized = vec![];
self.write(&mut serialized).unwrap();
serialized
}
/// Serialize the block as required for the proof of work hash.
///
/// This is distinct from the serialization required for the block hash. To get the block hash,
/// use the [`Block::hash`] function.
pub fn serialize_hashable(&self) -> Vec<u8> {
pub fn serialize_pow_hash(&self) -> Vec<u8> {
let mut blob = self.header.serialize();
blob.extend_from_slice(&self.tx_merkle_root());
blob.extend_from_slice(&merkle_root(self.miner_tx.hash(), &self.txs));
write_varint(&(1 + u64::try_from(self.txs.len()).unwrap()), &mut blob).unwrap();
blob
}
/// Get the hash of this block.
pub fn hash(&self) -> [u8; 32] {
let mut hashable = self.serialize_hashable();
// Monero pre-appends a VarInt of the block hashing blobs length before getting the block hash
@@ -103,16 +127,10 @@ impl Block {
if hash == CORRECT_BLOCK_HASH_202612 {
return EXISTING_BLOCK_HASH_202612;
};
hash
}
pub fn serialize(&self) -> Vec<u8> {
let mut serialized = vec![];
self.write(&mut serialized).unwrap();
serialized
}
/// Read a BlockHeader.
pub fn read<R: Read>(r: &mut R) -> io::Result<Block> {
let header = BlockHeader::read(r)?;

View File

@@ -15,11 +15,22 @@ pub mod ring_signatures;
/// RingCT structs and functionality.
pub mod ringct;
/// Transaction structs.
/// Transaction structs and functionality.
pub mod transaction;
/// Block structs.
/// Block structs and functionality.
pub mod block;
/// The minimum amount of blocks an output is locked for.
///
/// If Monero suffered a re-organization, any transactions which selected decoys belonging to
/// recent blocks would become invalidated. Accordingly, transactions must use decoys which are
/// presumed to not be invalidated in the future. If wallets only selected n-block-old outputs as
/// decoys, then any ring member within the past n blocks would have to be the real spend.
/// Preventing this at the consensus layer ensures privacy and integrity.
pub const DEFAULT_LOCK_WINDOW: usize = 10;
/// The minimum amount of blocks a coinbase output is locked for.
pub const COINBASE_LOCK_WINDOW: usize = 60;
/// Monero's block time target, in seconds.
pub const BLOCK_TIME: usize = 120;

View File

@@ -10,29 +10,33 @@ use curve25519_dalek::{EdwardsPoint, Scalar};
use crate::{io::*, generators::hash_to_point, primitives::keccak256_to_scalar};
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub struct Signature {
struct Signature {
c: Scalar,
r: Scalar,
s: Scalar,
}
impl Signature {
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
write_scalar(&self.c, w)?;
write_scalar(&self.r, w)?;
write_scalar(&self.s, w)?;
Ok(())
}
pub fn read<R: Read>(r: &mut R) -> io::Result<Signature> {
Ok(Signature { c: read_scalar(r)?, r: read_scalar(r)? })
fn read<R: Read>(r: &mut R) -> io::Result<Signature> {
Ok(Signature { c: read_scalar(r)?, s: read_scalar(r)? })
}
}
/// A ring signature.
///
/// This was used by the original Cryptonote transaction protocol and was deprecated with RingCT.
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub struct RingSignature {
sigs: Vec<Signature>,
}
impl RingSignature {
/// Write the RingSignature.
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
for sig in &self.sigs {
sig.write(w)?;
@@ -40,31 +44,49 @@ impl RingSignature {
Ok(())
}
/// Read a RingSignature.
pub fn read<R: Read>(members: usize, r: &mut R) -> io::Result<RingSignature> {
Ok(RingSignature { sigs: read_raw_vec(Signature::read, members, r)? })
}
/// Verify the ring signature.
pub fn verify(&self, msg: &[u8; 32], ring: &[EdwardsPoint], key_image: &EdwardsPoint) -> bool {
if ring.len() != self.sigs.len() {
return false;
}
let mut buf = Vec::with_capacity(32 + (32 * 2 * ring.len()));
let mut buf = Vec::with_capacity(32 + (2 * 32 * ring.len()));
buf.extend_from_slice(msg);
let mut sum = Scalar::ZERO;
for (ring_member, sig) in ring.iter().zip(&self.sigs) {
/*
The traditional Schnorr signature is:
r = sample()
c = H(r G || m)
s = r - c x
Verified as:
s G + c A == R
Each ring member here performs a dual-Schnorr signature for:
s G + c A
s HtP(A) + c K
Where the transcript is pushed both these values, r G, r HtP(A) for the real spend.
This also serves as a DLEq proof between the key and the key image.
Checking sum(c) == H(transcript) acts a disjunction, where any one of the `c`s can be
modified to cause the intended sum, if and only if a corresponding `s` value is known.
*/
#[allow(non_snake_case)]
let Li = EdwardsPoint::vartime_double_scalar_mul_basepoint(&sig.c, ring_member, &sig.r);
let Li = EdwardsPoint::vartime_double_scalar_mul_basepoint(&sig.c, ring_member, &sig.s);
buf.extend_from_slice(Li.compress().as_bytes());
#[allow(non_snake_case)]
let Ri = (sig.r * hash_to_point(ring_member.compress().to_bytes())) + (sig.c * key_image);
let Ri = (sig.s * hash_to_point(ring_member.compress().to_bytes())) + (sig.c * key_image);
buf.extend_from_slice(Ri.compress().as_bytes());
sum += sig.c;
}
sum == keccak256_to_scalar(buf)
}
}

View File

@@ -19,11 +19,6 @@ use crate::{
ringct::{mlsag::Mlsag, clsag::Clsag, borromean::BorromeanRange, bulletproofs::Bulletproof},
};
/// Generate a key image for a given key. Defined as `x * hash_to_point(xG)`.
pub fn generate_key_image(secret: &Zeroizing<Scalar>) -> EdwardsPoint {
hash_to_point((ED25519_BASEPOINT_TABLE * secret.deref()).compress().to_bytes()) * secret.deref()
}
/// An encrypted amount.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum EncryptedAmount {
@@ -58,25 +53,35 @@ impl EncryptedAmount {
pub enum RctType {
/// No RCT proofs.
Null,
/// One MLSAG for multiple inputs and Borromean range proofs (RCTTypeFull).
/// One MLSAG for multiple inputs and Borromean range proofs.
///
/// This lines up with RCTTypeFull.
MlsagAggregate,
// One MLSAG for each input and a Borromean range proof (RCTTypeSimple).
// One MLSAG for each input and a Borromean range proof.
///
/// This lines up with RCTTypeSimple.
MlsagIndividual,
// One MLSAG for each input and a Bulletproof (RCTTypeBulletproof).
// One MLSAG for each input and a Bulletproof.
///
/// This lines up with RCTTypeBulletproof.
Bulletproofs,
/// One MLSAG for each input and a Bulletproof, yet starting to use EncryptedAmount::Compact
/// (RCTTypeBulletproof2).
/// One MLSAG for each input and a Bulletproof, yet using EncryptedAmount::Compact.
///
/// This lines up with RCTTypeBulletproof2.
BulletproofsCompactAmount,
/// One CLSAG for each input and a Bulletproof (RCTTypeCLSAG).
/// One CLSAG for each input and a Bulletproof.
///
/// This lines up with RCTTypeCLSAG.
Clsag,
/// One CLSAG for each input and a Bulletproof+ (RCTTypeBulletproofPlus).
/// One CLSAG for each input and a Bulletproof+.
///
/// This lines up with RCTTypeBulletproofPlus.
BulletproofsPlus,
}
impl RctType {
/// Convert the RctType to its byte representation.
pub fn to_byte(self) -> u8 {
match self {
impl From<RctType> for u8 {
fn from(kind: RctType) -> u8 {
match kind {
RctType::Null => 0,
RctType::MlsagAggregate => 1,
RctType::MlsagIndividual => 2,
@@ -86,10 +91,12 @@ impl RctType {
RctType::BulletproofsPlus => 6,
}
}
}
/// Convert the RctType from its byte representation.
pub fn from_byte(byte: u8) -> Option<Self> {
Some(match byte {
impl TryFrom<u8> for RctType {
type Error = ();
fn try_from(byte: u8) -> Result<Self, ()> {
Ok(match byte {
0 => RctType::Null,
1 => RctType::MlsagAggregate,
2 => RctType::MlsagIndividual,
@@ -97,10 +104,12 @@ impl RctType {
4 => RctType::BulletproofsCompactAmount,
5 => RctType::Clsag,
6 => RctType::BulletproofsPlus,
_ => None?,
_ => Err(())?,
})
}
}
impl RctType {
/// Returns true if this RctType uses compact encrypted amounts, false otherwise.
pub fn compact_encrypted_amounts(&self) -> bool {
match self {
@@ -125,6 +134,8 @@ pub struct RctBase {
/// The fee used by this transaction.
pub fee: u64,
/// The re-randomized amount commitments used within inputs.
///
/// This field was deprecated and is empty for modern RctTypes.
pub pseudo_outs: Vec<EdwardsPoint>,
/// The encrypted amounts for the recipient to decrypt.
pub encrypted_amounts: Vec<EncryptedAmount>,
@@ -133,14 +144,15 @@ pub struct RctBase {
}
impl RctBase {
/// The weight of this RctBase as relevant for fees.
pub fn fee_weight(outputs: usize, fee: u64) -> usize {
// 1 byte for the RCT signature type
1 + (outputs * (8 + 32)) + varint_len(fee)
}
/// Write the RctBase to a writer.
/// Write the RctBase.
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
w.write_all(&[rct_type.to_byte()])?;
w.write_all(&[u8::from(rct_type)])?;
match rct_type {
RctType::Null => Ok(()),
_ => {
@@ -156,10 +168,10 @@ impl RctBase {
}
}
/// Read a RctBase from a writer.
/// Read a RctBase.
pub fn read<R: Read>(inputs: usize, outputs: usize, r: &mut R) -> io::Result<(RctBase, RctType)> {
let rct_type =
RctType::from_byte(read_byte(r)?).ok_or_else(|| io::Error::other("invalid RCT type"))?;
RctType::try_from(read_byte(r)?).map_err(|_| io::Error::other("invalid RCT type"))?;
match rct_type {
RctType::Null | RctType::MlsagAggregate | RctType::MlsagIndividual => {}
@@ -202,30 +214,27 @@ impl RctBase {
}
}
/// The prunable part of the RingCT data.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum RctPrunable {
/// Null.
Null,
AggregateMlsagBorromean {
borromean: Vec<BorromeanRange>,
mlsag: Mlsag,
},
MlsagBorromean {
borromean: Vec<BorromeanRange>,
mlsags: Vec<Mlsag>,
},
/// An aggregate MLSAG with Borromean range proofs.
AggregateMlsagBorromean { borromean: Vec<BorromeanRange>, mlsag: Mlsag },
/// MLSAGs with Borromean range proofs.
MlsagBorromean { borromean: Vec<BorromeanRange>, mlsags: Vec<Mlsag> },
/// MLSAGs with Bulletproofs.
MlsagBulletproofs {
bulletproofs: Bulletproof,
mlsags: Vec<Mlsag>,
pseudo_outs: Vec<EdwardsPoint>,
},
Clsag {
bulletproofs: Bulletproof,
clsags: Vec<Clsag>,
pseudo_outs: Vec<EdwardsPoint>,
},
/// CLSAGs with Bulletproofs(+).
Clsag { bulletproofs: Bulletproof, clsags: Vec<Clsag>, pseudo_outs: Vec<EdwardsPoint> },
}
impl RctPrunable {
/// The weight of this RctPrunable as relevant for fees.
#[rustfmt::skip]
pub fn fee_weight(bp_plus: bool, ring_len: usize, inputs: usize, outputs: usize) -> usize {
// 1 byte for number of BPs (technically a VarInt, yet there's always just zero or one)
@@ -235,6 +244,7 @@ impl RctPrunable {
(inputs * (Clsag::fee_weight(ring_len) + 32))
}
/// Write the RctPrunable.
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
match self {
RctPrunable::Null => Ok(()),
@@ -267,12 +277,14 @@ impl RctPrunable {
}
}
/// Serialize the RctPrunable to a Vec<u8>.
pub fn serialize(&self, rct_type: RctType) -> Vec<u8> {
let mut serialized = vec![];
self.write(&mut serialized, rct_type).unwrap();
serialized
}
/// Read a RctPrunable.
pub fn read<R: Read>(
rct_type: RctType,
ring_length: usize,
@@ -280,17 +292,6 @@ impl RctPrunable {
outputs: usize,
r: &mut R,
) -> io::Result<RctPrunable> {
// While we generally don't bother with misc consensus checks, this affects the safety of
// the below defined rct_type function
// The exact line preventing zero-input transactions is:
// https://github.com/monero-project/monero/blob/00fd416a99686f0956361d1cd0337fe56e58d4a7/
// src/ringct/rctSigs.cpp#L609
// And then for RctNull, that's only allowed for miner TXs which require one input of
// Input::Gen
if inputs == 0 {
Err(io::Error::other("transaction had no inputs"))?;
}
Ok(match rct_type {
RctType::Null => RctPrunable::Null,
RctType::MlsagAggregate => RctPrunable::AggregateMlsagBorromean {
@@ -333,16 +334,21 @@ impl RctPrunable {
})
}
pub(crate) fn signature_write<W: Write>(&self, w: &mut W) -> io::Result<()> {
match self {
RctPrunable::Null => panic!("Serializing RctPrunable::Null for a signature"),
/// Write the RctPrunable as necessary for signing the signature.
///
/// This function will return None if the object is `RctPrunable::Null` (and has no
/// representation here).
#[must_use]
pub(crate) fn signature_write<W: Write>(&self, w: &mut W) -> Option<io::Result<()>> {
Some(match self {
RctPrunable::Null => None?,
RctPrunable::AggregateMlsagBorromean { borromean, .. } |
RctPrunable::MlsagBorromean { borromean, .. } => {
borromean.iter().try_for_each(|rs| rs.write(w))
}
RctPrunable::MlsagBulletproofs { bulletproofs, .. } |
RctPrunable::Clsag { bulletproofs, .. } => bulletproofs.signature_write(w),
}
})
}
}
@@ -354,22 +360,18 @@ pub struct RctSignatures {
impl RctSignatures {
/// RctType for a given RctSignatures struct.
pub fn rct_type(&self) -> RctType {
match &self.prunable {
///
/// This is only guaranteed to return the type for a well-formed RctSignatures. For a malformed
/// RctSignatures, this will return either the presumed RctType (with no guarantee of compliance
/// with that type) or None.
#[must_use]
pub fn rct_type(&self) -> Option<RctType> {
Some(match &self.prunable {
RctPrunable::Null => RctType::Null,
RctPrunable::AggregateMlsagBorromean { .. } => RctType::MlsagAggregate,
RctPrunable::MlsagBorromean { .. } => RctType::MlsagIndividual,
// RctBase ensures there's at least one output, making the following
// inferences guaranteed/expects impossible on any valid RctSignatures
RctPrunable::MlsagBulletproofs { .. } => {
if matches!(
self
.base
.encrypted_amounts
.first()
.expect("MLSAG with Bulletproofs didn't have any outputs"),
EncryptedAmount::Original { .. }
) {
if matches!(self.base.encrypted_amounts.first()?, EncryptedAmount::Original { .. }) {
RctType::Bulletproofs
} else {
RctType::BulletproofsCompactAmount
@@ -382,9 +384,10 @@ impl RctSignatures {
RctType::BulletproofsPlus
}
}
}
})
}
/// The weight of this RctSignatures as relevant for fees.
pub fn fee_weight(
bp_plus: bool,
ring_len: usize,
@@ -395,16 +398,20 @@ impl RctSignatures {
RctBase::fee_weight(outputs, fee) + RctPrunable::fee_weight(bp_plus, ring_len, inputs, outputs)
}
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
let rct_type = self.rct_type();
self.base.write(w, rct_type)?;
self.prunable.write(w, rct_type)
#[must_use]
pub fn write<W: Write>(&self, w: &mut W) -> Option<io::Result<()>> {
let rct_type = self.rct_type()?;
if let Err(e) = self.base.write(w, rct_type) {
return Some(Err(e));
};
Some(self.prunable.write(w, rct_type))
}
pub fn serialize(&self) -> Vec<u8> {
#[must_use]
pub fn serialize(&self) -> Option<Vec<u8>> {
let mut serialized = vec![];
self.write(&mut serialized).unwrap();
serialized
self.write(&mut serialized)?.unwrap();
Some(serialized)
}
pub fn read<R: Read>(

View File

@@ -140,6 +140,7 @@ impl Timelock {
if raw == 0 {
Timelock::None
} else if raw < 500_000_000 {
// TODO: This is trivial to have panic
Timelock::Block(usize::try_from(raw).unwrap())
} else {
Timelock::Time(raw)
@@ -150,6 +151,7 @@ impl Timelock {
write_varint(
&match self {
Timelock::None => 0,
// TODO: Check this unwrap
Timelock::Block(block) => (*block).try_into().unwrap(),
Timelock::Time(time) => *time,
},
@@ -268,24 +270,33 @@ impl Transaction {
RctSignatures::fee_weight(bp_plus, ring_len, decoy_weights.len(), outputs, fee)
}
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
self.prefix.write(w)?;
#[must_use]
pub fn write<W: Write>(&self, w: &mut W) -> Option<io::Result<()>> {
if let Err(e) = self.prefix.write(w) {
return Some(Err(e));
};
if self.prefix.version == 1 {
for ring_sig in &self.signatures {
ring_sig.write(w)?;
if let Err(e) = ring_sig.write(w) {
return Some(Err(e));
};
}
Ok(())
Some(Ok(()))
} else if self.prefix.version == 2 {
self.rct_signatures.write(w)
if let Err(e) = self.rct_signatures.write(w)? {
return Some(Err(e));
}
Some(Ok(()))
} else {
panic!("Serializing a transaction with an unknown version");
Some(Err(io::Error::other("transaction had an unknown version")))
}
}
pub fn serialize(&self) -> Vec<u8> {
#[must_use]
pub fn serialize(&self) -> Option<Vec<u8>> {
let mut res = Vec::with_capacity(2048);
self.write(&mut res).unwrap();
res
self.write(&mut res)?.unwrap();
Some(res)
}
pub fn read<R: Read>(r: &mut R) -> io::Result<Transaction> {
@@ -352,36 +363,39 @@ impl Transaction {
Ok(Transaction { prefix, signatures, rct_signatures })
}
pub fn hash(&self) -> [u8; 32] {
#[must_use]
pub fn hash(&self) -> Option<[u8; 32]> {
let mut buf = Vec::with_capacity(2048);
if self.prefix.version == 1 {
self.write(&mut buf).unwrap();
keccak256(buf)
self.write(&mut buf)?.unwrap();
Some(keccak256(buf))
} else {
let mut hashes = Vec::with_capacity(96);
hashes.extend(self.prefix.hash());
self.rct_signatures.base.write(&mut buf, self.rct_signatures.rct_type()).unwrap();
let rct_type = self.rct_signatures.rct_type()?;
self.rct_signatures.base.write(&mut buf, rct_type).unwrap();
hashes.extend(keccak256(&buf));
buf.clear();
hashes.extend(&match self.rct_signatures.prunable {
RctPrunable::Null => [0; 32],
_ => {
self.rct_signatures.prunable.write(&mut buf, self.rct_signatures.rct_type()).unwrap();
self.rct_signatures.prunable.write(&mut buf, rct_type).unwrap();
keccak256(buf)
}
});
keccak256(hashes)
Some(keccak256(hashes))
}
}
/// Calculate the hash of this transaction as needed for signing it.
pub fn signature_hash(&self) -> [u8; 32] {
#[must_use]
pub fn signature_hash(&self) -> Option<[u8; 32]> {
if self.prefix.version == 1 {
return self.prefix.hash();
return Some(self.prefix.hash());
}
let mut buf = Vec::with_capacity(2048);
@@ -389,18 +403,19 @@ impl Transaction {
sig_hash.extend(self.prefix.hash());
self.rct_signatures.base.write(&mut buf, self.rct_signatures.rct_type()).unwrap();
self.rct_signatures.base.write(&mut buf, self.rct_signatures.rct_type()?).unwrap();
sig_hash.extend(keccak256(&buf));
buf.clear();
self.rct_signatures.prunable.signature_write(&mut buf).unwrap();
self.rct_signatures.prunable.signature_write(&mut buf)?.unwrap();
sig_hash.extend(keccak256(buf));
keccak256(sig_hash)
Some(keccak256(sig_hash))
}
fn is_rct_bulletproof(&self) -> bool {
match &self.rct_signatures.rct_type() {
let Some(rct_type) = self.rct_signatures.rct_type() else { return false };
match rct_type {
RctType::Bulletproofs | RctType::BulletproofsCompactAmount | RctType::Clsag => true,
RctType::Null |
RctType::MlsagAggregate |
@@ -410,7 +425,8 @@ impl Transaction {
}
fn is_rct_bulletproof_plus(&self) -> bool {
match &self.rct_signatures.rct_type() {
let Some(rct_type) = self.rct_signatures.rct_type() else { return false };
match rct_type {
RctType::BulletproofsPlus => true,
RctType::Null |
RctType::MlsagAggregate |
@@ -422,15 +438,15 @@ impl Transaction {
}
/// Calculate the transaction's weight.
pub fn weight(&self) -> usize {
let blob_size = self.serialize().len();
pub fn weight(&self) -> Option<usize> {
let blob_size = self.serialize()?.len();
let bp = self.is_rct_bulletproof();
let bp_plus = self.is_rct_bulletproof_plus();
if !(bp || bp_plus) {
Some(if !(bp || bp_plus) {
blob_size
} else {
blob_size + Bulletproof::calculate_bp_clawback(bp_plus, self.prefix.outputs.len()).0
}
})
}
}