mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-10 21:19:24 +00:00
Monero: support for legacy transactions (#308)
* add mlsag * fix last commit * fix miner v1 txs * fix non-miner v1 txs * add borromean + fix mlsag * add block hash calculations * fix for the jokester that added unreduced scalars to the borromean signature of 2368d846e671bf79a1f84c6d3af9f0bfe296f043f50cf17ae5e485384a53707b * Add Borromean range proof verifying functionality * Add MLSAG verifying functionality * fmt & clippy :) * update MLSAG, ss2_elements will always be 2 * Add MgSig proving * Tidy block.rs * Tidy Borromean, fix bugs in last commit, replace todo! with unreachable! * Mark legacy EcdhInfo amount decryption as experimental * Correct comments * Write a new impl of the merkle algorithm This one tries to be understandable. * Only pull in things only needed for experimental when experimental * Stop caching the Monero block hash now in processor that we have Block::hash * Corrections for recent processor commit * Use a clearer algorithm for the merkle Should also be more efficient due to not shifting as often. * Tidy Mlsag * Remove verify_rct_* from Mlsag Both methods were ports from Monero, overtly specific without clear documentation. They need to be added back in, with documentation, or included in a node which provides the necessary further context for them to be naturally understandable. * Move mlsag/mod.rs to mlsag.rs This should only be a folder if it has multiple files. * Replace EcdhInfo terminology The ECDH encrypted the amount, yet this struct contained the encrypted amount, not some ECDH. Also corrects the types on the original EcdhInfo struct. * Correct handling of commitment masks when scanning * Route read_array through read_raw_vec * Misc lint * Make a proper RctType enum No longer caches RctType in the RctSignatures as well. * Replace Vec<Bulletproofs> with Bulletproofs Monero uses aggregated range proofs, so there's only ever one Bulletproof. This is enforced with a consensus rule as well, making this safe. As for why Monero uses a vec, it's probably due to the lack of variadic typing used. Its effectively an Option for them, yet we don't need an Option since we do have variadic typing (enums). * Add necessary checks to Eventuality re: supported protocols * Fix for block 202612 and fix merkel root calculations * MLSAG (de)serialisation fix ss_2_elements will not always be 2 as rct type 1 transactions are not enforced to have one input * Revert "MLSAG (de)serialisation fix" This reverts commit5e710e0c96. here it checks number of MGs == number of inputs:0a1eaf26f9/src/cryptonote_core/tx_verification_utils.cpp (L60-59)and here it checks for RctTypeFull number of MGs == 1:0a1eaf26f9/src/ringct/rctSigs.cpp (L1325)so number of inputs == 1 so ss_2_elements == 2 * update `MlsagAggregate` comment * cargo update Resolves a yanked crate * Move location of serai-client in Cargo.toml --------- Co-authored-by: Luke Parker <lukeparker5132@gmail.com>
This commit is contained in:
108
coins/monero/src/ringct/borromean.rs
Normal file
108
coins/monero/src/ringct/borromean.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use core::fmt::Debug;
|
||||
use std_shims::io::{self, Read, Write};
|
||||
|
||||
use curve25519_dalek::edwards::EdwardsPoint;
|
||||
#[cfg(feature = "experimental")]
|
||||
use curve25519_dalek::{traits::Identity, scalar::Scalar};
|
||||
|
||||
#[cfg(feature = "experimental")]
|
||||
use monero_generators::H_pow_2;
|
||||
#[cfg(feature = "experimental")]
|
||||
use crate::hash_to_scalar;
|
||||
use crate::serialize::*;
|
||||
|
||||
/// 64 Borromean ring signatures.
|
||||
///
|
||||
/// This type keeps the data as raw bytes as Monero has some transactions with unreduced scalars in
|
||||
/// this field. While we could use `from_bytes_mod_order`, we'd then not be able to encode this
|
||||
/// back into it's original form.
|
||||
///
|
||||
/// Those scalars also have a custom reduction algorithm...
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct BorromeanSignatures {
|
||||
pub s0: [[u8; 32]; 64],
|
||||
pub s1: [[u8; 32]; 64],
|
||||
pub ee: [u8; 32],
|
||||
}
|
||||
|
||||
impl BorromeanSignatures {
|
||||
pub fn read<R: Read>(r: &mut R) -> io::Result<BorromeanSignatures> {
|
||||
Ok(BorromeanSignatures {
|
||||
s0: read_array(read_bytes, r)?,
|
||||
s1: read_array(read_bytes, r)?,
|
||||
ee: read_bytes(r)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
for s0 in self.s0.iter() {
|
||||
w.write_all(s0)?;
|
||||
}
|
||||
for s1 in self.s1.iter() {
|
||||
w.write_all(s1)?;
|
||||
}
|
||||
w.write_all(&self.ee)
|
||||
}
|
||||
|
||||
#[cfg(feature = "experimental")]
|
||||
fn verify(&self, keys_a: &[EdwardsPoint], keys_b: &[EdwardsPoint]) -> bool {
|
||||
let mut transcript = [0; 2048];
|
||||
for i in 0 .. 64 {
|
||||
// TODO: These aren't the correct reduction
|
||||
// TODO: Can either of these be tightened?
|
||||
#[allow(non_snake_case)]
|
||||
let LL = EdwardsPoint::vartime_double_scalar_mul_basepoint(
|
||||
&Scalar::from_bytes_mod_order(self.ee),
|
||||
&keys_a[i],
|
||||
&Scalar::from_bytes_mod_order(self.s0[i]),
|
||||
);
|
||||
#[allow(non_snake_case)]
|
||||
let LV = EdwardsPoint::vartime_double_scalar_mul_basepoint(
|
||||
&hash_to_scalar(LL.compress().as_bytes()),
|
||||
&keys_b[i],
|
||||
&Scalar::from_bytes_mod_order(self.s1[i]),
|
||||
);
|
||||
transcript[i .. ((i + 1) * 32)].copy_from_slice(LV.compress().as_bytes());
|
||||
}
|
||||
|
||||
// TODO: This isn't the correct reduction
|
||||
// TODO: Can this be tightened to from_canonical_bytes?
|
||||
hash_to_scalar(&transcript) == Scalar::from_bytes_mod_order(self.ee)
|
||||
}
|
||||
}
|
||||
|
||||
/// A range proof premised on Borromean ring signatures.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct BorromeanRange {
|
||||
pub sigs: BorromeanSignatures,
|
||||
pub bit_commitments: [EdwardsPoint; 64],
|
||||
}
|
||||
|
||||
impl BorromeanRange {
|
||||
pub fn read<R: Read>(r: &mut R) -> io::Result<BorromeanRange> {
|
||||
Ok(BorromeanRange {
|
||||
sigs: BorromeanSignatures::read(r)?,
|
||||
bit_commitments: read_array(read_point, r)?,
|
||||
})
|
||||
}
|
||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
self.sigs.write(w)?;
|
||||
write_raw_vec(write_point, &self.bit_commitments, w)
|
||||
}
|
||||
|
||||
#[cfg(feature = "experimental")]
|
||||
pub fn verify(&self, commitment: &EdwardsPoint) -> bool {
|
||||
if &self.bit_commitments.iter().sum::<EdwardsPoint>() != commitment {
|
||||
return false;
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let H_pow_2 = H_pow_2();
|
||||
let mut commitments_sub_one = [EdwardsPoint::identity(); 64];
|
||||
for i in 0 .. 64 {
|
||||
commitments_sub_one[i] = self.bit_commitments[i] - H_pow_2[i];
|
||||
}
|
||||
|
||||
self.sigs.verify(&self.bit_commitments, &commitments_sub_one)
|
||||
}
|
||||
}
|
||||
71
coins/monero/src/ringct/mlsag.rs
Normal file
71
coins/monero/src/ringct/mlsag.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use std_shims::{
|
||||
vec::Vec,
|
||||
io::{self, Read, Write},
|
||||
};
|
||||
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
#[cfg(feature = "experimental")]
|
||||
use curve25519_dalek::edwards::EdwardsPoint;
|
||||
|
||||
use crate::serialize::*;
|
||||
#[cfg(feature = "experimental")]
|
||||
use crate::{hash_to_scalar, ringct::hash_to_point};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Mlsag {
|
||||
pub ss: Vec<[Scalar; 2]>,
|
||||
pub cc: Scalar,
|
||||
}
|
||||
|
||||
impl Mlsag {
|
||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
for ss in self.ss.iter() {
|
||||
write_raw_vec(write_scalar, ss, w)?;
|
||||
}
|
||||
write_scalar(&self.cc, w)
|
||||
}
|
||||
|
||||
pub fn read<R: Read>(mixins: usize, r: &mut R) -> io::Result<Mlsag> {
|
||||
Ok(Mlsag {
|
||||
ss: (0 .. mixins).map(|_| read_array(read_scalar, r)).collect::<Result<_, _>>()?,
|
||||
cc: read_scalar(r)?,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "experimental")]
|
||||
pub fn verify(
|
||||
&self,
|
||||
msg: &[u8; 32],
|
||||
ring: &[[EdwardsPoint; 2]],
|
||||
key_image: &EdwardsPoint,
|
||||
) -> bool {
|
||||
if ring.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut buf = Vec::with_capacity(6 * 32);
|
||||
let mut ci = self.cc;
|
||||
for (i, ring_member) in ring.iter().enumerate() {
|
||||
buf.extend_from_slice(msg);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let L =
|
||||
|r| EdwardsPoint::vartime_double_scalar_mul_basepoint(&ci, &ring_member[r], &self.ss[i][r]);
|
||||
|
||||
buf.extend_from_slice(ring_member[0].compress().as_bytes());
|
||||
buf.extend_from_slice(L(0).compress().as_bytes());
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let R = (self.ss[i][0] * hash_to_point(ring_member[0])) + (ci * key_image);
|
||||
buf.extend_from_slice(R.compress().as_bytes());
|
||||
|
||||
buf.extend_from_slice(ring_member[1].compress().as_bytes());
|
||||
buf.extend_from_slice(L(1).compress().as_bytes());
|
||||
|
||||
ci = hash_to_scalar(&buf);
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
ci == self.cc
|
||||
}
|
||||
}
|
||||
@@ -4,22 +4,26 @@ use std_shims::{
|
||||
io::{self, Read, Write},
|
||||
};
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
|
||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, edwards::EdwardsPoint};
|
||||
|
||||
pub(crate) mod hash_to_point;
|
||||
pub use hash_to_point::{raw_hash_to_point, hash_to_point};
|
||||
|
||||
/// MLSAG struct, along with verifying functionality.
|
||||
pub mod mlsag;
|
||||
/// CLSAG struct, along with signing and verifying functionality.
|
||||
pub mod clsag;
|
||||
/// BorromeanRange struct, along with verifying functionality.
|
||||
pub mod borromean;
|
||||
/// Bulletproofs(+) structs, along with proving and verifying functionality.
|
||||
pub mod bulletproofs;
|
||||
|
||||
use crate::{
|
||||
Protocol,
|
||||
serialize::*,
|
||||
ringct::{clsag::Clsag, bulletproofs::Bulletproofs},
|
||||
ringct::{mlsag::Mlsag, clsag::Clsag, borromean::BorromeanRange, bulletproofs::Bulletproofs},
|
||||
};
|
||||
|
||||
/// Generate a key image for a given key. Defined as `x * hash_to_point(xG)`.
|
||||
@@ -27,10 +31,95 @@ pub fn generate_key_image(secret: &Zeroizing<Scalar>) -> EdwardsPoint {
|
||||
hash_to_point(&ED25519_BASEPOINT_TABLE * secret.deref()) * secret.deref()
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum EncryptedAmount {
|
||||
Original { mask: [u8; 32], amount: [u8; 32] },
|
||||
Compact { amount: [u8; 8] },
|
||||
}
|
||||
|
||||
impl EncryptedAmount {
|
||||
pub fn read<R: Read>(compact: bool, r: &mut R) -> io::Result<EncryptedAmount> {
|
||||
Ok(if !compact {
|
||||
EncryptedAmount::Original { mask: read_bytes(r)?, amount: read_bytes(r)? }
|
||||
} else {
|
||||
EncryptedAmount::Compact { amount: read_bytes(r)? }
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
match self {
|
||||
EncryptedAmount::Original { mask, amount } => {
|
||||
w.write_all(mask)?;
|
||||
w.write_all(amount)
|
||||
}
|
||||
EncryptedAmount::Compact { amount } => w.write_all(amount),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub enum RctType {
|
||||
/// No RCT proofs.
|
||||
Null,
|
||||
/// One MLSAG for a single input and a Borromean range proof (RCTTypeFull).
|
||||
MlsagAggregate,
|
||||
// One MLSAG for each input and a Borromean range proof (RCTTypeSimple).
|
||||
MlsagIndividual,
|
||||
// One MLSAG for each input and a Bulletproof (RCTTypeBulletproof).
|
||||
Bulletproofs,
|
||||
/// One MLSAG for each input and a Bulletproof, yet starting to use EncryptedAmount::Compact
|
||||
/// (RCTTypeBulletproof2).
|
||||
BulletproofsCompactAmount,
|
||||
/// One CLSAG for each input and a Bulletproof (RCTTypeCLSAG).
|
||||
Clsag,
|
||||
/// One CLSAG for each input and a Bulletproof+ (RCTTypeBulletproofPlus).
|
||||
BulletproofsPlus,
|
||||
}
|
||||
|
||||
impl RctType {
|
||||
pub fn to_byte(self) -> u8 {
|
||||
match self {
|
||||
RctType::Null => 0,
|
||||
RctType::MlsagAggregate => 1,
|
||||
RctType::MlsagIndividual => 2,
|
||||
RctType::Bulletproofs => 3,
|
||||
RctType::BulletproofsCompactAmount => 4,
|
||||
RctType::Clsag => 5,
|
||||
RctType::BulletproofsPlus => 6,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_byte(byte: u8) -> Option<Self> {
|
||||
Some(match byte {
|
||||
0 => RctType::Null,
|
||||
1 => RctType::MlsagAggregate,
|
||||
2 => RctType::MlsagIndividual,
|
||||
3 => RctType::Bulletproofs,
|
||||
4 => RctType::BulletproofsCompactAmount,
|
||||
5 => RctType::Clsag,
|
||||
6 => RctType::BulletproofsPlus,
|
||||
_ => None?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn compact_encrypted_amounts(&self) -> bool {
|
||||
match self {
|
||||
RctType::Null => false,
|
||||
RctType::MlsagAggregate => false,
|
||||
RctType::MlsagIndividual => false,
|
||||
RctType::Bulletproofs => false,
|
||||
RctType::BulletproofsCompactAmount => true,
|
||||
RctType::Clsag => true,
|
||||
RctType::BulletproofsPlus => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct RctBase {
|
||||
pub fee: u64,
|
||||
pub ecdh_info: Vec<[u8; 8]>,
|
||||
pub pseudo_outs: Vec<EdwardsPoint>,
|
||||
pub encrypted_amounts: Vec<EncryptedAmount>,
|
||||
pub commitments: Vec<EdwardsPoint>,
|
||||
}
|
||||
|
||||
@@ -39,30 +128,60 @@ impl RctBase {
|
||||
1 + 8 + (outputs * (8 + 32))
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, w: &mut W, rct_type: u8) -> io::Result<()> {
|
||||
w.write_all(&[rct_type])?;
|
||||
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
|
||||
w.write_all(&[rct_type.to_byte()])?;
|
||||
match rct_type {
|
||||
0 => Ok(()),
|
||||
5 | 6 => {
|
||||
RctType::Null => Ok(()),
|
||||
_ => {
|
||||
write_varint(&self.fee, w)?;
|
||||
for ecdh in &self.ecdh_info {
|
||||
w.write_all(ecdh)?;
|
||||
if rct_type == RctType::MlsagIndividual {
|
||||
write_raw_vec(write_point, &self.pseudo_outs, w)?;
|
||||
}
|
||||
for encrypted_amount in &self.encrypted_amounts {
|
||||
encrypted_amount.write(w)?;
|
||||
}
|
||||
write_raw_vec(write_point, &self.commitments, w)
|
||||
}
|
||||
_ => panic!("Serializing unknown RctType's Base"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read<R: Read>(outputs: usize, r: &mut R) -> io::Result<(RctBase, u8)> {
|
||||
let rct_type = read_byte(r)?;
|
||||
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::new(io::ErrorKind::Other, "invalid RCT type"))?;
|
||||
|
||||
match rct_type {
|
||||
RctType::Null => {}
|
||||
RctType::MlsagAggregate => {}
|
||||
RctType::MlsagIndividual => {}
|
||||
RctType::Bulletproofs |
|
||||
RctType::BulletproofsCompactAmount |
|
||||
RctType::Clsag |
|
||||
RctType::BulletproofsPlus => {
|
||||
if outputs == 0 {
|
||||
// Because the Bulletproofs(+) layout must be canonical, there must be 1 Bulletproof if
|
||||
// Bulletproofs are in use
|
||||
// If there are Bulletproofs, there must be a matching amount of outputs, implicitly
|
||||
// banning 0 outputs
|
||||
// Since HF 12 (CLSAG being 13), a 2-output minimum has also been enforced
|
||||
Err(io::Error::new(io::ErrorKind::Other, "RCT with Bulletproofs(+) had 0 outputs"))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
if rct_type == 0 {
|
||||
RctBase { fee: 0, ecdh_info: vec![], commitments: vec![] }
|
||||
if rct_type == RctType::Null {
|
||||
RctBase { fee: 0, pseudo_outs: vec![], encrypted_amounts: vec![], commitments: vec![] }
|
||||
} else {
|
||||
RctBase {
|
||||
fee: read_varint(r)?,
|
||||
ecdh_info: (0 .. outputs).map(|_| read_bytes(r)).collect::<Result<_, _>>()?,
|
||||
pseudo_outs: if rct_type == RctType::MlsagIndividual {
|
||||
read_raw_vec(read_point, inputs, r)?
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
encrypted_amounts: (0 .. outputs)
|
||||
.map(|_| EncryptedAmount::read(rct_type.compact_encrypted_amounts(), r))
|
||||
.collect::<Result<_, _>>()?,
|
||||
commitments: read_raw_vec(read_point, outputs, r)?,
|
||||
}
|
||||
},
|
||||
@@ -74,67 +193,114 @@ impl RctBase {
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum RctPrunable {
|
||||
Null,
|
||||
Clsag { bulletproofs: Vec<Bulletproofs>, clsags: Vec<Clsag>, pseudo_outs: Vec<EdwardsPoint> },
|
||||
MlsagBorromean {
|
||||
borromean: Vec<BorromeanRange>,
|
||||
mlsags: Vec<Mlsag>,
|
||||
},
|
||||
MlsagBulletproofs {
|
||||
bulletproofs: Bulletproofs,
|
||||
mlsags: Vec<Mlsag>,
|
||||
pseudo_outs: Vec<EdwardsPoint>,
|
||||
},
|
||||
Clsag {
|
||||
bulletproofs: Bulletproofs,
|
||||
clsags: Vec<Clsag>,
|
||||
pseudo_outs: Vec<EdwardsPoint>,
|
||||
},
|
||||
}
|
||||
|
||||
impl RctPrunable {
|
||||
/// RCT Type byte for a given RctPrunable struct.
|
||||
pub fn rct_type(&self) -> u8 {
|
||||
match self {
|
||||
RctPrunable::Null => 0,
|
||||
RctPrunable::Clsag { bulletproofs, .. } => {
|
||||
if matches!(bulletproofs[0], Bulletproofs::Original { .. }) {
|
||||
5
|
||||
} else {
|
||||
6
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn fee_weight(protocol: Protocol, inputs: usize, outputs: usize) -> usize {
|
||||
1 + Bulletproofs::fee_weight(protocol.bp_plus(), outputs) +
|
||||
(inputs * (Clsag::fee_weight(protocol.ring_len()) + 32))
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
|
||||
match self {
|
||||
RctPrunable::Null => Ok(()),
|
||||
RctPrunable::Clsag { bulletproofs, clsags, pseudo_outs, .. } => {
|
||||
write_vec(Bulletproofs::write, bulletproofs, w)?;
|
||||
RctPrunable::MlsagBorromean { borromean, mlsags } => {
|
||||
write_raw_vec(BorromeanRange::write, borromean, w)?;
|
||||
write_raw_vec(Mlsag::write, mlsags, w)
|
||||
}
|
||||
RctPrunable::MlsagBulletproofs { bulletproofs, mlsags, pseudo_outs } => {
|
||||
if rct_type == RctType::Bulletproofs {
|
||||
w.write_all(&1u32.to_le_bytes())?;
|
||||
} else {
|
||||
w.write_all(&[1])?;
|
||||
}
|
||||
bulletproofs.write(w)?;
|
||||
|
||||
write_raw_vec(Mlsag::write, mlsags, w)?;
|
||||
write_raw_vec(write_point, pseudo_outs, w)
|
||||
}
|
||||
RctPrunable::Clsag { bulletproofs, clsags, pseudo_outs } => {
|
||||
w.write_all(&[1])?;
|
||||
bulletproofs.write(w)?;
|
||||
|
||||
write_raw_vec(Clsag::write, clsags, w)?;
|
||||
write_raw_vec(write_point, pseudo_outs, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
pub fn serialize(&self, rct_type: RctType) -> Vec<u8> {
|
||||
let mut serialized = vec![];
|
||||
self.write(&mut serialized).unwrap();
|
||||
self.write(&mut serialized, rct_type).unwrap();
|
||||
serialized
|
||||
}
|
||||
|
||||
pub fn read<R: Read>(rct_type: u8, decoys: &[usize], r: &mut R) -> io::Result<RctPrunable> {
|
||||
pub fn read<R: Read>(
|
||||
rct_type: RctType,
|
||||
decoys: &[usize],
|
||||
outputs: usize,
|
||||
r: &mut R,
|
||||
) -> io::Result<RctPrunable> {
|
||||
Ok(match rct_type {
|
||||
0 => RctPrunable::Null,
|
||||
5 | 6 => RctPrunable::Clsag {
|
||||
bulletproofs: read_vec(
|
||||
if rct_type == 5 { Bulletproofs::read } else { Bulletproofs::read_plus },
|
||||
r,
|
||||
)?,
|
||||
RctType::Null => RctPrunable::Null,
|
||||
RctType::MlsagAggregate | RctType::MlsagIndividual => RctPrunable::MlsagBorromean {
|
||||
borromean: read_raw_vec(BorromeanRange::read, outputs, r)?,
|
||||
mlsags: decoys.iter().map(|d| Mlsag::read(*d, r)).collect::<Result<_, _>>()?,
|
||||
},
|
||||
RctType::Bulletproofs | RctType::BulletproofsCompactAmount => {
|
||||
RctPrunable::MlsagBulletproofs {
|
||||
bulletproofs: {
|
||||
if (if rct_type == RctType::Bulletproofs {
|
||||
u64::from(read_u32(r)?)
|
||||
} else {
|
||||
read_varint(r)?
|
||||
}) != 1
|
||||
{
|
||||
Err(io::Error::new(io::ErrorKind::Other, "n bulletproofs instead of one"))?;
|
||||
}
|
||||
Bulletproofs::read(r)?
|
||||
},
|
||||
mlsags: decoys.iter().map(|d| Mlsag::read(*d, r)).collect::<Result<_, _>>()?,
|
||||
pseudo_outs: read_raw_vec(read_point, decoys.len(), r)?,
|
||||
}
|
||||
}
|
||||
RctType::Clsag | RctType::BulletproofsPlus => RctPrunable::Clsag {
|
||||
bulletproofs: {
|
||||
if read_varint(r)? != 1 {
|
||||
Err(io::Error::new(io::ErrorKind::Other, "n bulletproofs instead of one"))?;
|
||||
}
|
||||
(if rct_type == RctType::Clsag { Bulletproofs::read } else { Bulletproofs::read_plus })(
|
||||
r,
|
||||
)?
|
||||
},
|
||||
clsags: (0 .. decoys.len()).map(|o| Clsag::read(decoys[o], r)).collect::<Result<_, _>>()?,
|
||||
pseudo_outs: read_raw_vec(read_point, decoys.len(), r)?,
|
||||
},
|
||||
_ => Err(io::Error::new(io::ErrorKind::Other, "Tried to deserialize unknown RCT type"))?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn signature_write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
match self {
|
||||
RctPrunable::Null => panic!("Serializing RctPrunable::Null for a signature"),
|
||||
RctPrunable::Clsag { bulletproofs, .. } => {
|
||||
bulletproofs.iter().try_for_each(|bp| bp.signature_write(w))
|
||||
RctPrunable::MlsagBorromean { borromean, .. } => {
|
||||
borromean.iter().try_for_each(|rs| rs.write(w))
|
||||
}
|
||||
RctPrunable::MlsagBulletproofs { bulletproofs, .. } => bulletproofs.signature_write(w),
|
||||
RctPrunable::Clsag { bulletproofs, .. } => bulletproofs.signature_write(w),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,13 +312,68 @@ pub struct RctSignatures {
|
||||
}
|
||||
|
||||
impl RctSignatures {
|
||||
/// RctType for a given RctSignatures struct.
|
||||
pub fn rct_type(&self) -> RctType {
|
||||
match &self.prunable {
|
||||
RctPrunable::Null => RctType::Null,
|
||||
RctPrunable::MlsagBorromean { .. } => {
|
||||
/*
|
||||
This type of RctPrunable may have no outputs, yet pseudo_outs are per input
|
||||
This will only be a valid RctSignatures if it's for a TX with inputs
|
||||
That makes this valid for any valid RctSignatures
|
||||
|
||||
While it will be invalid for any invalid RctSignatures, potentially letting an invalid
|
||||
MlsagAggregate be interpreted as a valid MlsagIndividual (or vice versa), they have
|
||||
incompatible deserializations
|
||||
|
||||
This means it's impossible to receive a MlsagAggregate over the wire and interpret it
|
||||
as a MlsagIndividual (or vice versa)
|
||||
|
||||
That only makes manual manipulation unsafe, which will always be true since these fields
|
||||
are all pub
|
||||
|
||||
TODO: Consider making them private with read-only accessors?
|
||||
*/
|
||||
if self.base.pseudo_outs.is_empty() {
|
||||
RctType::MlsagAggregate
|
||||
} else {
|
||||
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
|
||||
.get(0)
|
||||
.expect("MLSAG with Bulletproofs didn't have any outputs"),
|
||||
EncryptedAmount::Original { .. }
|
||||
) {
|
||||
RctType::Bulletproofs
|
||||
} else {
|
||||
RctType::BulletproofsCompactAmount
|
||||
}
|
||||
}
|
||||
RctPrunable::Clsag { bulletproofs, .. } => {
|
||||
if matches!(bulletproofs, Bulletproofs::Original { .. }) {
|
||||
RctType::Clsag
|
||||
} else {
|
||||
RctType::BulletproofsPlus
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn fee_weight(protocol: Protocol, inputs: usize, outputs: usize) -> usize {
|
||||
RctBase::fee_weight(outputs) + RctPrunable::fee_weight(protocol, inputs, outputs)
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
self.base.write(w, self.prunable.rct_type())?;
|
||||
self.prunable.write(w)
|
||||
let rct_type = self.rct_type();
|
||||
self.base.write(w, rct_type)?;
|
||||
self.prunable.write(w, rct_type)
|
||||
}
|
||||
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
@@ -162,7 +383,7 @@ impl RctSignatures {
|
||||
}
|
||||
|
||||
pub fn read<R: Read>(decoys: Vec<usize>, outputs: usize, r: &mut R) -> io::Result<RctSignatures> {
|
||||
let base = RctBase::read(outputs, r)?;
|
||||
Ok(RctSignatures { base: base.0, prunable: RctPrunable::read(base.1, &decoys, r)? })
|
||||
let base = RctBase::read(decoys.len(), outputs, r)?;
|
||||
Ok(RctSignatures { base: base.0, prunable: RctPrunable::read(base.1, &decoys, outputs, r)? })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user