mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-11 13:39:25 +00:00
Compare commits
15 Commits
undroppabl
...
68060b4efc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68060b4efc | ||
|
|
4af83bd0e5 | ||
|
|
d4b22e5136 | ||
|
|
58fe79da10 | ||
|
|
3f07dd13c6 | ||
|
|
4e1d86dae2 | ||
|
|
7ef21830a5 | ||
|
|
8cb4c5d167 | ||
|
|
f9e4b420ed | ||
|
|
817b8e99d3 | ||
|
|
925cef17f2 | ||
|
|
3283cd79e4 | ||
|
|
51e2f24bc1 | ||
|
|
372e29fe08 | ||
|
|
fccb1aea51 |
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -4672,7 +4672,6 @@ dependencies = [
|
||||
name = "monero-serai"
|
||||
version = "0.1.4-alpha"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"async-trait",
|
||||
"base58-monero",
|
||||
"curve25519-dalek",
|
||||
|
||||
@@ -47,8 +47,6 @@ frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.8
|
||||
|
||||
monero-generators = { path = "generators", version = "0.4", default-features = false }
|
||||
|
||||
async-lock = { version = "3", default-features = false, optional = true }
|
||||
|
||||
hex-literal = "0.4"
|
||||
hex = { version = "0.4", default-features = false, features = ["alloc"] }
|
||||
serde = { version = "1", default-features = false, features = ["derive", "alloc"] }
|
||||
@@ -93,8 +91,6 @@ std = [
|
||||
|
||||
"monero-generators/std",
|
||||
|
||||
"async-lock?/std",
|
||||
|
||||
"hex/std",
|
||||
"serde/std",
|
||||
"serde_json/std",
|
||||
@@ -102,10 +98,8 @@ std = [
|
||||
"base58-monero/std",
|
||||
]
|
||||
|
||||
cache-distribution = ["async-lock"]
|
||||
http-rpc = ["digest_auth", "simple-request", "tokio"]
|
||||
multisig = ["transcript", "frost", "std"]
|
||||
binaries = ["tokio/rt-multi-thread", "tokio/macros", "http-rpc"]
|
||||
experimental = []
|
||||
|
||||
default = ["std", "http-rpc"]
|
||||
|
||||
@@ -47,3 +47,15 @@ It also won't act as a wallet, just as a transaction library. wallet2 has
|
||||
several *non-transaction-level* policies, such as always attempting to use two
|
||||
inputs to create transactions. These are considered out of scope to
|
||||
monero-serai.
|
||||
|
||||
### Feature flags
|
||||
monero-serai has certain functionality behind feature flags:
|
||||
|
||||
- `std:` Enables usage of Rust's `std` and several other functionality. See `Cargo.toml` for the full list.
|
||||
- `http-rpc`: Enables an HTTP(S) transport type within the `rpc` module
|
||||
- `multisig`: Enables multi-signature features within the `wallet` module
|
||||
- `binaries`: TODO
|
||||
|
||||
The features enabled by default are:
|
||||
- `std`
|
||||
- `http-rpc`
|
||||
@@ -15,16 +15,43 @@ const CORRECT_BLOCK_HASH_202612: [u8; 32] =
|
||||
const EXISTING_BLOCK_HASH_202612: [u8; 32] =
|
||||
hex_literal::hex!("bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698");
|
||||
|
||||
/// The header of a [`Block`].
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct BlockHeader {
|
||||
/// This represents the hardfork number of the block.
|
||||
pub major_version: u8,
|
||||
/// This field is used to vote for a particular [hardfork](https://github.com/monero-project/monero/blob/c8214782fb2a769c57382a999eaf099691c836e7/src/cryptonote_basic/cryptonote_basic.h#L460).
|
||||
pub minor_version: u8,
|
||||
/// The UNIX time at which the block was mined.
|
||||
pub timestamp: u64,
|
||||
/// The previous [`Block::hash`].
|
||||
pub previous: [u8; 32],
|
||||
/// The block's nonce.
|
||||
pub nonce: u32,
|
||||
}
|
||||
|
||||
impl BlockHeader {
|
||||
/// Serialize [`Self`] into the writer `w`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use monero_serai::block::*;
|
||||
/// # fn main() -> std::io::Result<()> {
|
||||
/// let block_header = BlockHeader {
|
||||
/// major_version: 1,
|
||||
/// minor_version: 2,
|
||||
/// timestamp: 3,
|
||||
/// previous: [4; 32],
|
||||
/// nonce: 5,
|
||||
/// };
|
||||
///
|
||||
/// let mut writer = vec![];
|
||||
/// block_header.write(&mut writer)?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
/// This function returns any errors from the writer itself.
|
||||
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 +60,58 @@ impl BlockHeader {
|
||||
w.write_all(&self.nonce.to_le_bytes())
|
||||
}
|
||||
|
||||
/// Serialize [`Self`] into a new byte buffer.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use monero_serai::block::*;
|
||||
/// # fn main() -> std::io::Result<()> {
|
||||
/// let block_header = BlockHeader {
|
||||
/// major_version: 1,
|
||||
/// minor_version: 2,
|
||||
/// timestamp: 3,
|
||||
/// previous: [4; 32],
|
||||
/// nonce: 5,
|
||||
/// };
|
||||
///
|
||||
/// let mut writer = vec![];
|
||||
/// block_header.write(&mut writer)?;
|
||||
///
|
||||
/// let serialized = block_header.serialize();
|
||||
/// assert_eq!(serialized, writer);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
let mut serialized = vec![];
|
||||
self.write(&mut serialized).unwrap();
|
||||
serialized
|
||||
}
|
||||
|
||||
/// Create [`Self`] from the reader `r`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use monero_serai::block::*;
|
||||
/// # fn main() -> std::io::Result<()> {
|
||||
/// let block_header = BlockHeader {
|
||||
/// major_version: 1,
|
||||
/// minor_version: 2,
|
||||
/// timestamp: 3,
|
||||
/// previous: [4; 32],
|
||||
/// nonce: 5,
|
||||
/// };
|
||||
///
|
||||
/// let mut vec = vec![];
|
||||
/// block_header.write(&mut vec)?;
|
||||
///
|
||||
/// let read = BlockHeader::read(&mut vec.as_slice())?;
|
||||
/// assert_eq!(read, block_header);
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
/// This function returns an error if either the reader failed,
|
||||
/// or if the data could not be deserialized into a [`Self`].
|
||||
pub fn read<R: Read>(r: &mut R) -> io::Result<BlockHeader> {
|
||||
Ok(BlockHeader {
|
||||
major_version: read_varint(r)?,
|
||||
@@ -50,14 +123,19 @@ impl BlockHeader {
|
||||
}
|
||||
}
|
||||
|
||||
/// Block on the Monero blockchain.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Block {
|
||||
/// The header of this block.
|
||||
pub header: BlockHeader,
|
||||
/// The miner/coinbase transaction.
|
||||
pub miner_tx: Transaction,
|
||||
/// Hashes of all the transactions within this block.
|
||||
pub txs: Vec<[u8; 32]>,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
/// Return the amount of Monero generated in this block in atomic units.
|
||||
pub fn number(&self) -> Option<u64> {
|
||||
match self.miner_tx.prefix.inputs.first() {
|
||||
Some(Input::Gen(number)) => Some(*number),
|
||||
@@ -65,6 +143,10 @@ impl Block {
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize [`Self`] into the writer `w`.
|
||||
///
|
||||
/// # Errors
|
||||
/// This function returns any errors from the writer itself.
|
||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
self.header.write(w)?;
|
||||
self.miner_tx.write(w)?;
|
||||
@@ -75,6 +157,11 @@ impl Block {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the merkle root of this block.
|
||||
///
|
||||
/// In the case that this block has no transactions other than
|
||||
/// the miner transaction, the miner transaction hash is returned,
|
||||
/// i.e. the [`Transaction::hash`] of [`Self::miner_tx`] is returned.
|
||||
fn tx_merkle_root(&self) -> [u8; 32] {
|
||||
merkle_root(self.miner_tx.hash(), &self.txs)
|
||||
}
|
||||
@@ -91,6 +178,7 @@ impl Block {
|
||||
blob
|
||||
}
|
||||
|
||||
/// Calculate 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
|
||||
@@ -107,12 +195,18 @@ impl Block {
|
||||
hash
|
||||
}
|
||||
|
||||
/// Serialize [`Self`] into a new byte buffer.
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
let mut serialized = vec![];
|
||||
self.write(&mut serialized).unwrap();
|
||||
serialized
|
||||
}
|
||||
|
||||
/// Create [`Self`] from the reader `r`.
|
||||
///
|
||||
/// # Errors
|
||||
/// This function returns an error if either the reader failed,
|
||||
/// or if the data could not be deserialized into a [`Self`].
|
||||
pub fn read<R: Read>(r: &mut R) -> io::Result<Block> {
|
||||
let header = BlockHeader::read(r)?;
|
||||
|
||||
@@ -124,7 +218,7 @@ impl Block {
|
||||
Ok(Block {
|
||||
header,
|
||||
miner_tx,
|
||||
txs: (0_usize .. read_varint(r)?).map(|_| read_bytes(r)).collect::<Result<_, _>>()?,
|
||||
txs: (0_usize..read_varint(r)?).map(|_| read_bytes(r)).collect::<Result<_, _>>()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,26 @@ pub mod wallet;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// Default block lock time for transactions.
|
||||
///
|
||||
/// This is the amount of new blocks that must
|
||||
/// pass before a new transaction can be spent.
|
||||
///
|
||||
/// Equivalent to Monero's [`CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE`](https://github.com/monero-project/monero/blob/c8214782fb2a769c57382a999eaf099691c836e7/src/cryptonote_config.h#L49).
|
||||
pub const DEFAULT_LOCK_WINDOW: usize = 10;
|
||||
/// Block lock time for coinbase transactions.
|
||||
///
|
||||
/// This is the amount of new blocks that must
|
||||
/// pass before a coinbase/miner transaction can be spent.
|
||||
///
|
||||
/// Equivalent to Monero's [`CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW`](https://github.com/monero-project/monero/blob/c8214782fb2a769c57382a999eaf099691c836e7/src/cryptonote_config.h#L44).
|
||||
pub const COINBASE_LOCK_WINDOW: usize = 60;
|
||||
/// Average amount of seconds it takes for a block to be mined.
|
||||
///
|
||||
/// This is target amount of seconds mining difficulty will adjust to,
|
||||
/// i.e. a block will be mined every `BLOCK_TIME` seconds on average.
|
||||
///
|
||||
/// Equivalent to Monero's [`DIFFICULTY_TARGET_V2`](https://github.com/monero-project/monero/blob/c8214782fb2a769c57382a999eaf099691c836e7/src/cryptonote_config.h#L44).
|
||||
pub const BLOCK_TIME: usize = 120;
|
||||
|
||||
static INV_EIGHT_CELL: OnceLock<Scalar> = OnceLock::new();
|
||||
@@ -75,19 +93,34 @@ pub(crate) fn BASEPOINT_PRECOMP() -> &'static VartimeEdwardsPrecomputation {
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum Protocol {
|
||||
/// Version 14.
|
||||
v14,
|
||||
/// Version 16.
|
||||
v16,
|
||||
/// A custom version with customized properties.
|
||||
Custom {
|
||||
/// See [`Self::ring_len`].
|
||||
ring_len: usize,
|
||||
/// See [`Self::bp_plus`].
|
||||
bp_plus: bool,
|
||||
/// See [`Self::optimal_rct_type`].
|
||||
optimal_rct_type: RctType,
|
||||
/// See [`Self::view_tags`].
|
||||
view_tags: bool,
|
||||
/// See [`Self::v16_fee`].
|
||||
v16_fee: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl Protocol {
|
||||
/// Amount of ring members under this protocol version.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use monero_serai::*;
|
||||
/// assert_eq!(Protocol::v14.ring_len(), 11);
|
||||
/// assert_eq!(Protocol::v16.ring_len(), 16);
|
||||
/// ```
|
||||
pub fn ring_len(&self) -> usize {
|
||||
match self {
|
||||
Protocol::v14 => 11,
|
||||
@@ -99,6 +132,13 @@ impl Protocol {
|
||||
/// Whether or not the specified version uses Bulletproofs or Bulletproofs+.
|
||||
///
|
||||
/// This method will likely be reworked when versions not using Bulletproofs at all are added.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use monero_serai::*;
|
||||
/// assert_eq!(Protocol::v14.bp_plus(), false);
|
||||
/// assert_eq!(Protocol::v16.bp_plus(), true);
|
||||
/// ```
|
||||
pub fn bp_plus(&self) -> bool {
|
||||
match self {
|
||||
Protocol::v14 => false,
|
||||
@@ -107,6 +147,14 @@ impl Protocol {
|
||||
}
|
||||
}
|
||||
|
||||
/// The optimal RingCT type for this version.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use monero_serai::{*, ringct::*};
|
||||
/// assert_eq!(Protocol::v14.optimal_rct_type(), RctType::Clsag);
|
||||
/// assert_eq!(Protocol::v16.optimal_rct_type(), RctType::BulletproofsPlus);
|
||||
/// ```
|
||||
// TODO: Make this an Option when we support pre-RCT protocols
|
||||
pub fn optimal_rct_type(&self) -> RctType {
|
||||
match self {
|
||||
@@ -117,6 +165,13 @@ impl Protocol {
|
||||
}
|
||||
|
||||
/// Whether or not the specified version uses view tags.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use monero_serai::{*, ringct::*};
|
||||
/// assert_eq!(Protocol::v14.view_tags(), false);
|
||||
/// assert_eq!(Protocol::v16.view_tags(), true);
|
||||
/// ```
|
||||
pub fn view_tags(&self) -> bool {
|
||||
match self {
|
||||
Protocol::v14 => false,
|
||||
@@ -127,6 +182,13 @@ impl Protocol {
|
||||
|
||||
/// Whether or not the specified version uses the fee algorithm from Monero
|
||||
/// hard fork version 16 (released in v18 binaries).
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use monero_serai::{*, ringct::*};
|
||||
/// assert_eq!(Protocol::v14.v16_fee(), false);
|
||||
/// assert_eq!(Protocol::v16.v16_fee(), true);
|
||||
/// ```
|
||||
pub fn v16_fee(&self) -> bool {
|
||||
match self {
|
||||
Protocol::v14 => false,
|
||||
@@ -188,11 +250,15 @@ impl Protocol {
|
||||
}
|
||||
}
|
||||
|
||||
/// Transparent structure representing a Pedersen commitment's contents.
|
||||
/// Transparent structure representing a [Pedersen commitment](https://web.getmonero.org/resources/moneropedia/pedersen-commitment.html)'s contents.
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct Commitment {
|
||||
/// The value used to mask the `amount`.
|
||||
pub mask: Scalar,
|
||||
/// The value being masked.
|
||||
///
|
||||
/// In Monero's case, this is the amount of XMR in atomic units.
|
||||
pub amount: u64,
|
||||
}
|
||||
|
||||
@@ -208,6 +274,7 @@ impl Commitment {
|
||||
Commitment { mask: Scalar::ONE, amount: 0 }
|
||||
}
|
||||
|
||||
/// Create a new [`Self`].
|
||||
pub fn new(mask: Scalar, amount: u64) -> Commitment {
|
||||
Commitment { mask, amount }
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use monero_generators::hash_to_point;
|
||||
|
||||
use crate::{serialize::*, hash_to_scalar};
|
||||
|
||||
/// A signature within a [`RingSignature`].
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct Signature {
|
||||
c: Scalar,
|
||||
@@ -18,23 +19,37 @@ pub struct Signature {
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
/// Serialize [`Self`] into the writer `w`.
|
||||
///
|
||||
/// # Errors
|
||||
/// This function returns any errors from the writer itself.
|
||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
write_scalar(&self.c, w)?;
|
||||
write_scalar(&self.r, w)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create [`Self`] from the reader `r`.
|
||||
///
|
||||
/// # Errors
|
||||
/// This function returns an error if either the reader failed,
|
||||
/// or if the data could not be deserialized into a [`Self`].
|
||||
pub fn read<R: Read>(r: &mut R) -> io::Result<Signature> {
|
||||
Ok(Signature { c: read_scalar(r)?, r: read_scalar(r)? })
|
||||
}
|
||||
}
|
||||
|
||||
/// A [ring signature](https://en.wikipedia.org/wiki/Ring_signature).
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct RingSignature {
|
||||
sigs: Vec<Signature>,
|
||||
}
|
||||
|
||||
impl RingSignature {
|
||||
/// Serialize [`Self`] into the writer `w`.
|
||||
///
|
||||
/// # Errors
|
||||
/// This function returns any errors from the writer itself.
|
||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
for sig in &self.sigs {
|
||||
sig.write(w)?;
|
||||
@@ -42,6 +57,11 @@ impl RingSignature {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create [`Self`] from the reader `r`.
|
||||
///
|
||||
/// # Errors
|
||||
/// This function returns an error if either the reader failed,
|
||||
/// or if the data could not be deserialized into a [`Self`].
|
||||
pub fn read<R: Read>(members: usize, r: &mut R) -> io::Result<RingSignature> {
|
||||
Ok(RingSignature { sigs: read_raw_vec(Signature::read, members, r)? })
|
||||
}
|
||||
|
||||
@@ -7,20 +7,21 @@ use monero_generators::H_pow_2;
|
||||
|
||||
use crate::{hash_to_scalar, unreduced_scalar::UnreducedScalar, serialize::*};
|
||||
|
||||
/// 64 Borromean ring signatures.
|
||||
/// 64 Borromean ring signatures, as needed for a 64-bit range proof.
|
||||
///
|
||||
/// s0 and s1 are stored as `UnreducedScalar`s due to Monero not requiring they were reduced.
|
||||
/// `UnreducedScalar` preserves their original byte encoding and implements a custom reduction
|
||||
/// algorithm which was in use.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct BorromeanSignatures {
|
||||
pub s0: [UnreducedScalar; 64],
|
||||
pub s1: [UnreducedScalar; 64],
|
||||
pub ee: Scalar,
|
||||
struct BorromeanSignatures {
|
||||
s0: [UnreducedScalar; 64],
|
||||
s1: [UnreducedScalar; 64],
|
||||
ee: Scalar,
|
||||
}
|
||||
|
||||
impl BorromeanSignatures {
|
||||
pub fn read<R: Read>(r: &mut R) -> io::Result<BorromeanSignatures> {
|
||||
/// Read a set of BorromeanSignatures from a reader.
|
||||
fn read<R: Read>(r: &mut R) -> io::Result<BorromeanSignatures> {
|
||||
Ok(BorromeanSignatures {
|
||||
s0: read_array(UnreducedScalar::read, r)?,
|
||||
s1: read_array(UnreducedScalar::read, r)?,
|
||||
@@ -28,7 +29,8 @@ impl BorromeanSignatures {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
/// Write the set of BorromeanSignatures to a writer.
|
||||
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
for s0 in &self.s0 {
|
||||
s0.write(w)?;
|
||||
}
|
||||
@@ -64,22 +66,26 @@ impl BorromeanSignatures {
|
||||
/// A range proof premised on Borromean ring signatures.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct BorromeanRange {
|
||||
pub sigs: BorromeanSignatures,
|
||||
pub bit_commitments: [EdwardsPoint; 64],
|
||||
sigs: BorromeanSignatures,
|
||||
bit_commitments: [EdwardsPoint; 64],
|
||||
}
|
||||
|
||||
impl BorromeanRange {
|
||||
/// Read a BorromeanRange proof from a reader.
|
||||
pub fn read<R: Read>(r: &mut R) -> io::Result<BorromeanRange> {
|
||||
Ok(BorromeanRange {
|
||||
sigs: BorromeanSignatures::read(r)?,
|
||||
bit_commitments: read_array(read_point, r)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Write the BorromeanRange proof to a reader.
|
||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
self.sigs.write(w)?;
|
||||
write_raw_vec(write_point, &self.bit_commitments, w)
|
||||
}
|
||||
|
||||
/// Verify the commitment contains a 64-bit value.
|
||||
pub fn verify(&self, commitment: &EdwardsPoint) -> bool {
|
||||
if &self.bit_commitments.iter().sum::<EdwardsPoint>() != commitment {
|
||||
return false;
|
||||
|
||||
@@ -26,15 +26,15 @@ use self::plus::*;
|
||||
|
||||
pub(crate) const MAX_OUTPUTS: usize = self::core::MAX_M;
|
||||
|
||||
/// Bulletproofs enum, supporting the original and plus formulations.
|
||||
/// Bulletproof enum, encapsulating both Bulletproofs and Bulletproofs+.
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum Bulletproofs {
|
||||
pub enum Bulletproof {
|
||||
Original(OriginalStruct),
|
||||
Plus(AggregateRangeProof),
|
||||
}
|
||||
|
||||
impl Bulletproofs {
|
||||
impl Bulletproof {
|
||||
fn bp_fields(plus: bool) -> usize {
|
||||
if plus {
|
||||
6
|
||||
@@ -57,7 +57,7 @@ impl Bulletproofs {
|
||||
|
||||
let mut bp_clawback = 0;
|
||||
if n_padded_outputs > 2 {
|
||||
let fields = Bulletproofs::bp_fields(plus);
|
||||
let fields = Bulletproof::bp_fields(plus);
|
||||
let base = ((fields + (2 * (LOG_N + 1))) * 32) / 2;
|
||||
let size = (fields + (2 * LR_len)) * 32;
|
||||
bp_clawback = ((base * n_padded_outputs) - size) * 4 / 5;
|
||||
@@ -68,49 +68,51 @@ impl Bulletproofs {
|
||||
|
||||
pub(crate) fn fee_weight(plus: bool, outputs: usize) -> usize {
|
||||
#[allow(non_snake_case)]
|
||||
let (bp_clawback, LR_len) = Bulletproofs::calculate_bp_clawback(plus, outputs);
|
||||
32 * (Bulletproofs::bp_fields(plus) + (2 * LR_len)) + 2 + bp_clawback
|
||||
let (bp_clawback, LR_len) = Bulletproof::calculate_bp_clawback(plus, outputs);
|
||||
32 * (Bulletproof::bp_fields(plus) + (2 * LR_len)) + 2 + bp_clawback
|
||||
}
|
||||
|
||||
/// Prove the list of commitments are within [0 .. 2^64).
|
||||
/// Prove the list of commitments are within [0 .. 2^64) with an aggregate Bulletproof.
|
||||
pub fn prove<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
outputs: &[Commitment],
|
||||
plus: bool,
|
||||
) -> Result<Bulletproofs, TransactionError> {
|
||||
) -> Result<Bulletproof, TransactionError> {
|
||||
if outputs.is_empty() {
|
||||
Err(TransactionError::NoOutputs)?;
|
||||
}
|
||||
if outputs.len() > MAX_OUTPUTS {
|
||||
Err(TransactionError::TooManyOutputs)?;
|
||||
}
|
||||
Ok(if !plus {
|
||||
Bulletproofs::Original(OriginalStruct::prove(rng, outputs))
|
||||
} else {
|
||||
use dalek_ff_group::EdwardsPoint as DfgPoint;
|
||||
Bulletproofs::Plus(
|
||||
AggregateRangeStatement::new(outputs.iter().map(|com| DfgPoint(com.calculate())).collect())
|
||||
.unwrap()
|
||||
.prove(rng, &Zeroizing::new(AggregateRangeWitness::new(outputs.to_vec()).unwrap()))
|
||||
.unwrap(),
|
||||
)
|
||||
})
|
||||
Ok(Bulletproof::Original(OriginalStruct::prove(rng, outputs)))
|
||||
}
|
||||
|
||||
/// Verify the given Bulletproofs.
|
||||
/// Prove the list of commitments are within [0 .. 2^64) with an aggregate Bulletproof+.
|
||||
pub fn prove_plus<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
outputs: Vec<Commitment>,
|
||||
) -> Result<Bulletproof, TransactionError> {
|
||||
if outputs.is_empty() {
|
||||
Err(TransactionError::NoOutputs)?;
|
||||
}
|
||||
if outputs.len() > MAX_OUTPUTS {
|
||||
Err(TransactionError::TooManyOutputs)?;
|
||||
}
|
||||
Ok(Bulletproof::Plus(
|
||||
AggregateRangeStatement::new(outputs.iter().map(Commitment::calculate).collect())
|
||||
.unwrap()
|
||||
.prove(rng, &Zeroizing::new(AggregateRangeWitness::new(outputs).unwrap()))
|
||||
.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Verify the given Bulletproof(+).
|
||||
#[must_use]
|
||||
pub fn verify<R: RngCore + CryptoRng>(&self, rng: &mut R, commitments: &[EdwardsPoint]) -> bool {
|
||||
match self {
|
||||
Bulletproofs::Original(bp) => bp.verify(rng, commitments),
|
||||
Bulletproofs::Plus(bp) => {
|
||||
Bulletproof::Original(bp) => bp.verify(rng, commitments),
|
||||
Bulletproof::Plus(bp) => {
|
||||
let mut verifier = BatchVerifier::new(1);
|
||||
// If this commitment is torsioned (which is allowed), this won't be a well-formed
|
||||
// dfg::EdwardsPoint (expected to be of prime-order)
|
||||
// The actual BP+ impl will perform a torsion clear though, making this safe
|
||||
// TODO: Have AggregateRangeStatement take in dalek EdwardsPoint for clarity on this
|
||||
let Some(statement) = AggregateRangeStatement::new(
|
||||
commitments.iter().map(|c| dalek_ff_group::EdwardsPoint(*c)).collect(),
|
||||
) else {
|
||||
let Some(statement) = AggregateRangeStatement::new(commitments.to_vec()) else {
|
||||
return false;
|
||||
};
|
||||
if !statement.verify(rng, &mut verifier, (), bp.clone()) {
|
||||
@@ -121,9 +123,11 @@ impl Bulletproofs {
|
||||
}
|
||||
}
|
||||
|
||||
/// Accumulate the verification for the given Bulletproofs into the specified BatchVerifier.
|
||||
/// Returns false if the Bulletproofs aren't sane, without mutating the BatchVerifier.
|
||||
/// Returns true if the Bulletproofs are sane, regardless of their validity.
|
||||
/// Accumulate the verification for the given Bulletproof into the specified BatchVerifier.
|
||||
///
|
||||
/// Returns false if the Bulletproof isn't sane, leaving the BatchVerifier in an undefined
|
||||
/// state.
|
||||
/// Returns true if the Bulletproof is sane, regardless of their validity.
|
||||
#[must_use]
|
||||
pub fn batch_verify<ID: Copy + Zeroize, R: RngCore + CryptoRng>(
|
||||
&self,
|
||||
@@ -133,11 +137,9 @@ impl Bulletproofs {
|
||||
commitments: &[EdwardsPoint],
|
||||
) -> bool {
|
||||
match self {
|
||||
Bulletproofs::Original(bp) => bp.batch_verify(rng, verifier, id, commitments),
|
||||
Bulletproofs::Plus(bp) => {
|
||||
let Some(statement) = AggregateRangeStatement::new(
|
||||
commitments.iter().map(|c| dalek_ff_group::EdwardsPoint(*c)).collect(),
|
||||
) else {
|
||||
Bulletproof::Original(bp) => bp.batch_verify(rng, verifier, id, commitments),
|
||||
Bulletproof::Plus(bp) => {
|
||||
let Some(statement) = AggregateRangeStatement::new(commitments.to_vec()) else {
|
||||
return false;
|
||||
};
|
||||
statement.verify(rng, verifier, id, bp.clone())
|
||||
@@ -151,7 +153,7 @@ impl Bulletproofs {
|
||||
specific_write_vec: F,
|
||||
) -> io::Result<()> {
|
||||
match self {
|
||||
Bulletproofs::Original(bp) => {
|
||||
Bulletproof::Original(bp) => {
|
||||
write_point(&bp.A, w)?;
|
||||
write_point(&bp.S, w)?;
|
||||
write_point(&bp.T1, w)?;
|
||||
@@ -165,7 +167,7 @@ impl Bulletproofs {
|
||||
write_scalar(&bp.t, w)
|
||||
}
|
||||
|
||||
Bulletproofs::Plus(bp) => {
|
||||
Bulletproof::Plus(bp) => {
|
||||
write_point(&bp.A.0, w)?;
|
||||
write_point(&bp.wip.A.0, w)?;
|
||||
write_point(&bp.wip.B.0, w)?;
|
||||
@@ -182,19 +184,21 @@ impl Bulletproofs {
|
||||
self.write_core(w, |points, w| write_raw_vec(write_point, points, w))
|
||||
}
|
||||
|
||||
/// Write the Bulletproof(+) to a writer.
|
||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
self.write_core(w, |points, w| write_vec(write_point, points, w))
|
||||
}
|
||||
|
||||
/// Serialize the Bulletproof(+) to a `Vec<u8>`.
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
let mut serialized = vec![];
|
||||
self.write(&mut serialized).unwrap();
|
||||
serialized
|
||||
}
|
||||
|
||||
/// Read Bulletproofs.
|
||||
pub fn read<R: Read>(r: &mut R) -> io::Result<Bulletproofs> {
|
||||
Ok(Bulletproofs::Original(OriginalStruct {
|
||||
/// Read a Bulletproof.
|
||||
pub fn read<R: Read>(r: &mut R) -> io::Result<Bulletproof> {
|
||||
Ok(Bulletproof::Original(OriginalStruct {
|
||||
A: read_point(r)?,
|
||||
S: read_point(r)?,
|
||||
T1: read_point(r)?,
|
||||
@@ -209,11 +213,11 @@ impl Bulletproofs {
|
||||
}))
|
||||
}
|
||||
|
||||
/// Read Bulletproofs+.
|
||||
pub fn read_plus<R: Read>(r: &mut R) -> io::Result<Bulletproofs> {
|
||||
/// Read a Bulletproof+.
|
||||
pub fn read_plus<R: Read>(r: &mut R) -> io::Result<Bulletproof> {
|
||||
use dalek_ff_group::{Scalar as DfgScalar, EdwardsPoint as DfgPoint};
|
||||
|
||||
Ok(Bulletproofs::Plus(AggregateRangeProof {
|
||||
Ok(Bulletproof::Plus(AggregateRangeProof {
|
||||
A: DfgPoint(read_point(r)?),
|
||||
wip: WipProof {
|
||||
A: DfgPoint(read_point(r)?),
|
||||
|
||||
@@ -33,6 +33,7 @@ pub(crate) fn hadamard_fold(
|
||||
res
|
||||
}
|
||||
|
||||
/// Internal structure representing a Bulletproof.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct OriginalStruct {
|
||||
pub(crate) A: DalekPoint,
|
||||
|
||||
@@ -9,6 +9,7 @@ use group::{
|
||||
ff::{Field, PrimeField},
|
||||
Group, GroupEncoding,
|
||||
};
|
||||
use curve25519_dalek::EdwardsPoint as DalekPoint;
|
||||
use dalek_ff_group::{Scalar, EdwardsPoint};
|
||||
|
||||
use crate::{
|
||||
@@ -28,7 +29,7 @@ use crate::{
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct AggregateRangeStatement {
|
||||
generators: Generators,
|
||||
V: Vec<EdwardsPoint>,
|
||||
V: Vec<DalekPoint>,
|
||||
}
|
||||
|
||||
impl Zeroize for AggregateRangeStatement {
|
||||
@@ -50,6 +51,7 @@ impl AggregateRangeWitness {
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal structure representing a Bulletproof+, as used in Monero.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct AggregateRangeProof {
|
||||
pub(crate) A: EdwardsPoint,
|
||||
@@ -57,7 +59,7 @@ pub struct AggregateRangeProof {
|
||||
}
|
||||
|
||||
impl AggregateRangeStatement {
|
||||
pub(crate) fn new(V: Vec<EdwardsPoint>) -> Option<Self> {
|
||||
pub(crate) fn new(V: Vec<DalekPoint>) -> Option<Self> {
|
||||
if V.is_empty() || (V.len() > MAX_M) {
|
||||
return None;
|
||||
}
|
||||
@@ -98,11 +100,14 @@ impl AggregateRangeStatement {
|
||||
}
|
||||
let mn = V.len() * N;
|
||||
|
||||
// 2, 4, 6, 8... powers of z, of length equivalent to the amount of commitments
|
||||
let mut z_pow = Vec::with_capacity(V.len());
|
||||
// z**2
|
||||
z_pow.push(z * z);
|
||||
|
||||
let mut d = ScalarVector::new(mn);
|
||||
for j in 1 ..= V.len() {
|
||||
z_pow.push(z.pow(Scalar::from(2 * u64::try_from(j).unwrap()))); // TODO: Optimize this
|
||||
z_pow.push(*z_pow.last().unwrap() * z_pow[0]);
|
||||
d = d + &(Self::d_j(j, V.len()) * (z_pow[j - 1]));
|
||||
}
|
||||
|
||||
@@ -157,7 +162,7 @@ impl AggregateRangeStatement {
|
||||
return None;
|
||||
}
|
||||
for (commitment, witness) in self.V.iter().zip(witness.0.iter()) {
|
||||
if witness.calculate() != **commitment {
|
||||
if witness.calculate() != *commitment {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
@@ -170,9 +175,9 @@ impl AggregateRangeStatement {
|
||||
// Commitments aren't transmitted INV_EIGHT though, so this multiplies by INV_EIGHT to enable
|
||||
// clearing its cofactor without mutating the value
|
||||
// For some reason, these values are transcripted * INV_EIGHT, not as transmitted
|
||||
let mut V = V.into_iter().map(|V| EdwardsPoint(V.0 * crate::INV_EIGHT())).collect::<Vec<_>>();
|
||||
let V = V.into_iter().map(|V| V * crate::INV_EIGHT()).collect::<Vec<_>>();
|
||||
let mut transcript = initial_transcript(V.iter());
|
||||
V.iter_mut().for_each(|V| *V = V.mul_by_cofactor());
|
||||
let mut V = V.into_iter().map(|V| EdwardsPoint(V.mul_by_cofactor())).collect::<Vec<_>>();
|
||||
|
||||
// Pad V
|
||||
while V.len() < padded_pow_of_2(V.len()) {
|
||||
@@ -239,9 +244,11 @@ impl AggregateRangeStatement {
|
||||
) -> bool {
|
||||
let Self { generators, V } = self;
|
||||
|
||||
let mut V = V.into_iter().map(|V| EdwardsPoint(V.0 * crate::INV_EIGHT())).collect::<Vec<_>>();
|
||||
let V = V.into_iter().map(|V| V * crate::INV_EIGHT()).collect::<Vec<_>>();
|
||||
let mut transcript = initial_transcript(V.iter());
|
||||
V.iter_mut().for_each(|V| *V = V.mul_by_cofactor());
|
||||
// With the torsion clear, wrap it into a EdwardsPoint from dalek-ff-group
|
||||
// (which is prime-order)
|
||||
let V = V.into_iter().map(|V| EdwardsPoint(V.mul_by_cofactor())).collect::<Vec<_>>();
|
||||
|
||||
let generators = generators.reduce(V.len() * N);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std_shims::{sync::OnceLock, vec::Vec};
|
||||
|
||||
use dalek_ff_group::{Scalar, EdwardsPoint};
|
||||
use curve25519_dalek::EdwardsPoint;
|
||||
use dalek_ff_group::Scalar;
|
||||
|
||||
use monero_generators::{hash_to_point as raw_hash_to_point};
|
||||
use crate::{hash, hash_to_scalar as dalek_hash};
|
||||
|
||||
@@ -26,31 +26,36 @@ use crate::{
|
||||
#[cfg(feature = "multisig")]
|
||||
mod multisig;
|
||||
#[cfg(feature = "multisig")]
|
||||
pub use multisig::{ClsagDetails, ClsagAddendum, ClsagMultisig};
|
||||
pub(crate) use multisig::{ClsagDetails, ClsagAddendum, ClsagMultisig};
|
||||
|
||||
/// Errors returned when CLSAG signing fails.
|
||||
/// Errors when working with CLSAGs.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||
pub enum ClsagError {
|
||||
#[cfg_attr(feature = "std", error("internal error ({0})"))]
|
||||
InternalError(&'static str),
|
||||
/// The ring was invalid (such as being too small or too large).
|
||||
#[cfg_attr(feature = "std", error("invalid ring"))]
|
||||
InvalidRing,
|
||||
/// The specified ring member was invalid (index, ring size).
|
||||
#[cfg_attr(feature = "std", error("invalid ring member (member {0}, ring size {1})"))]
|
||||
InvalidRingMember(u8, u8),
|
||||
/// The commitment opening provided did not match the ring member's.
|
||||
#[cfg_attr(feature = "std", error("invalid commitment"))]
|
||||
InvalidCommitment,
|
||||
/// The key image was invalid (such as being identity or torsioned)
|
||||
#[cfg_attr(feature = "std", error("invalid key image"))]
|
||||
InvalidImage,
|
||||
/// The `D` component was invalid.
|
||||
#[cfg_attr(feature = "std", error("invalid D"))]
|
||||
InvalidD,
|
||||
/// The `s` vector was invalid.
|
||||
#[cfg_attr(feature = "std", error("invalid s"))]
|
||||
InvalidS,
|
||||
/// The `c1` variable was invalid.
|
||||
#[cfg_attr(feature = "std", error("invalid c1"))]
|
||||
InvalidC1,
|
||||
}
|
||||
|
||||
/// Input being signed for.
|
||||
/// Context on the ring member being signed for.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct ClsagInput {
|
||||
// The actual commitment for the true spend
|
||||
@@ -63,7 +68,7 @@ impl ClsagInput {
|
||||
pub fn new(commitment: Commitment, decoys: Decoys) -> Result<ClsagInput, ClsagError> {
|
||||
let n = decoys.len();
|
||||
if n > u8::MAX.into() {
|
||||
Err(ClsagError::InternalError("max ring size in this library is u8 max"))?;
|
||||
Err(ClsagError::InvalidRing)?;
|
||||
}
|
||||
let n = u8::try_from(n).unwrap();
|
||||
if decoys.i >= n {
|
||||
@@ -213,9 +218,16 @@ fn core(
|
||||
/// CLSAG signature, as used in Monero.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Clsag {
|
||||
pub D: EdwardsPoint,
|
||||
pub s: Vec<Scalar>,
|
||||
pub c1: Scalar,
|
||||
D: EdwardsPoint,
|
||||
pub(crate) s: Vec<Scalar>,
|
||||
pub(crate) c1: Scalar,
|
||||
}
|
||||
|
||||
pub(crate) struct ClsagSignCore {
|
||||
incomplete_clsag: Clsag,
|
||||
pseudo_out: EdwardsPoint,
|
||||
key_challenge: Scalar,
|
||||
challenged_mask: Scalar,
|
||||
}
|
||||
|
||||
impl Clsag {
|
||||
@@ -229,28 +241,34 @@ impl Clsag {
|
||||
msg: &[u8; 32],
|
||||
A: EdwardsPoint,
|
||||
AH: EdwardsPoint,
|
||||
) -> (Clsag, EdwardsPoint, Scalar, Scalar) {
|
||||
) -> ClsagSignCore {
|
||||
let r: usize = input.decoys.i.into();
|
||||
|
||||
let pseudo_out = Commitment::new(mask, input.commitment.amount).calculate();
|
||||
let z = input.commitment.mask - mask;
|
||||
let mask_delta = input.commitment.mask - mask;
|
||||
|
||||
let H = hash_to_point(&input.decoys.ring[r][0]);
|
||||
let D = H * z;
|
||||
let D = H * mask_delta;
|
||||
let mut s = Vec::with_capacity(input.decoys.ring.len());
|
||||
for _ in 0 .. input.decoys.ring.len() {
|
||||
s.push(random_scalar(rng));
|
||||
}
|
||||
let ((D, p, c), c1) =
|
||||
let ((D, c_p, c_c), c1) =
|
||||
core(&input.decoys.ring, I, &pseudo_out, msg, &D, &s, &Mode::Sign(r, A, AH));
|
||||
|
||||
(Clsag { D, s, c1 }, pseudo_out, p, c * z)
|
||||
ClsagSignCore {
|
||||
incomplete_clsag: Clsag { D, s, c1 },
|
||||
pseudo_out,
|
||||
key_challenge: c_p,
|
||||
challenged_mask: c_c * mask_delta,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate CLSAG signatures for the given inputs.
|
||||
///
|
||||
/// inputs is of the form (private key, key image, input).
|
||||
/// sum_outputs is for the sum of the outputs' commitment masks.
|
||||
pub fn sign<R: RngCore + CryptoRng>(
|
||||
pub(crate) fn sign<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
mut inputs: Vec<(Zeroizing<Scalar>, EdwardsPoint, ClsagInput)>,
|
||||
sum_outputs: Scalar,
|
||||
@@ -267,20 +285,25 @@ impl Clsag {
|
||||
}
|
||||
|
||||
let mut nonce = Zeroizing::new(random_scalar(rng));
|
||||
let (mut clsag, pseudo_out, p, c) = Clsag::sign_core(
|
||||
rng,
|
||||
&inputs[i].1,
|
||||
&inputs[i].2,
|
||||
mask,
|
||||
&msg,
|
||||
nonce.deref() * ED25519_BASEPOINT_TABLE,
|
||||
nonce.deref() *
|
||||
hash_to_point(&inputs[i].2.decoys.ring[usize::from(inputs[i].2.decoys.i)][0]),
|
||||
);
|
||||
let ClsagSignCore { mut incomplete_clsag, pseudo_out, key_challenge, challenged_mask } =
|
||||
Clsag::sign_core(
|
||||
rng,
|
||||
&inputs[i].1,
|
||||
&inputs[i].2,
|
||||
mask,
|
||||
&msg,
|
||||
nonce.deref() * ED25519_BASEPOINT_TABLE,
|
||||
nonce.deref() *
|
||||
hash_to_point(&inputs[i].2.decoys.ring[usize::from(inputs[i].2.decoys.i)][0]),
|
||||
);
|
||||
// Effectively r - cx, except cx is (c_p x) + (c_c z), where z is the delta between a ring
|
||||
// member's commitment and our input commitment (which will only have a known discrete log
|
||||
// over G if the amounts cancel out)
|
||||
clsag.s[usize::from(inputs[i].2.decoys.i)] = nonce.deref() - ((p * inputs[i].0.deref()) + c);
|
||||
incomplete_clsag.s[usize::from(inputs[i].2.decoys.i)] =
|
||||
nonce.deref() - ((key_challenge * inputs[i].0.deref()) + challenged_mask);
|
||||
let clsag = incomplete_clsag;
|
||||
|
||||
// Zeroize private keys and nonces.
|
||||
inputs[i].0.zeroize();
|
||||
nonce.zeroize();
|
||||
|
||||
@@ -310,7 +333,7 @@ impl Clsag {
|
||||
if ring.len() != self.s.len() {
|
||||
Err(ClsagError::InvalidS)?;
|
||||
}
|
||||
if I.is_identity() {
|
||||
if I.is_identity() || (!I.is_torsion_free()) {
|
||||
Err(ClsagError::InvalidImage)?;
|
||||
}
|
||||
|
||||
@@ -330,12 +353,14 @@ impl Clsag {
|
||||
(ring_len * 32) + 32 + 32
|
||||
}
|
||||
|
||||
/// Write the CLSAG to a writer.
|
||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
write_raw_vec(write_scalar, &self.s, w)?;
|
||||
w.write_all(&self.c1.to_bytes())?;
|
||||
write_point(&self.D, w)
|
||||
}
|
||||
|
||||
/// Read a CLSAG from a reader.
|
||||
pub fn read<R: Read>(decoys: usize, r: &mut R) -> io::Result<Clsag> {
|
||||
Ok(Clsag { s: read_raw_vec(read_scalar, decoys, r)?, c1: read_scalar(r)?, D: read_point(r)? })
|
||||
}
|
||||
|
||||
@@ -57,13 +57,13 @@ impl ClsagInput {
|
||||
|
||||
/// CLSAG input and the mask to use for it.
|
||||
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct ClsagDetails {
|
||||
pub(crate) struct ClsagDetails {
|
||||
input: ClsagInput,
|
||||
mask: Scalar,
|
||||
}
|
||||
|
||||
impl ClsagDetails {
|
||||
pub fn new(input: ClsagInput, mask: Scalar) -> ClsagDetails {
|
||||
pub(crate) fn new(input: ClsagInput, mask: Scalar) -> ClsagDetails {
|
||||
ClsagDetails { input, mask }
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ struct Interim {
|
||||
/// FROST algorithm for producing a CLSAG signature.
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ClsagMultisig {
|
||||
pub(crate) struct ClsagMultisig {
|
||||
transcript: RecommendedTranscript,
|
||||
|
||||
pub(crate) H: EdwardsPoint,
|
||||
@@ -107,7 +107,7 @@ pub struct ClsagMultisig {
|
||||
}
|
||||
|
||||
impl ClsagMultisig {
|
||||
pub fn new(
|
||||
pub(crate) fn new(
|
||||
transcript: RecommendedTranscript,
|
||||
output_key: EdwardsPoint,
|
||||
details: Arc<RwLock<Option<ClsagDetails>>>,
|
||||
@@ -219,8 +219,7 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
||||
|
||||
self.msg = Some(msg.try_into().expect("CLSAG message should be 32-bytes"));
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let (clsag, pseudo_out, p, c) = Clsag::sign_core(
|
||||
let sign_core = Clsag::sign_core(
|
||||
&mut rng,
|
||||
&self.image.expect("verifying a share despite never processing any addendums").0,
|
||||
&self.input(),
|
||||
@@ -229,10 +228,15 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
||||
nonce_sums[0][0].0,
|
||||
nonce_sums[0][1].0,
|
||||
);
|
||||
self.interim = Some(Interim { p, c, clsag, pseudo_out });
|
||||
self.interim = Some(Interim {
|
||||
p: sign_core.key_challenge,
|
||||
c: sign_core.challenged_mask,
|
||||
clsag: sign_core.incomplete_clsag,
|
||||
pseudo_out: sign_core.pseudo_out,
|
||||
});
|
||||
|
||||
// r - p x, where p is the challenge for the keys
|
||||
*nonces[0] - dfg::Scalar(p) * view.secret_share().deref()
|
||||
*nonces[0] - dfg::Scalar(sign_core.key_challenge) * view.secret_share().deref()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@@ -245,7 +249,7 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
||||
let interim = self.interim.as_ref().unwrap();
|
||||
let mut clsag = interim.clsag.clone();
|
||||
// We produced shares as `r - p x`, yet the signature is `r - p x - c x`
|
||||
// Substract `c x` (saved as `c`) now
|
||||
// Subtract `c x` (saved as `c`) now
|
||||
clsag.s[usize::from(self.input().decoys.i)] = sum.0 - interim.c;
|
||||
if clsag
|
||||
.verify(
|
||||
|
||||
@@ -11,28 +11,36 @@ use monero_generators::H;
|
||||
|
||||
use crate::{hash_to_scalar, ringct::hash_to_point, serialize::*};
|
||||
|
||||
/// Errors when working with MLSAGs.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||
pub enum MlsagError {
|
||||
/// Invalid ring (such as too small or too large).
|
||||
#[cfg_attr(feature = "std", error("invalid ring"))]
|
||||
InvalidRing,
|
||||
/// Invalid amount of key images.
|
||||
#[cfg_attr(feature = "std", error("invalid amount of key images"))]
|
||||
InvalidAmountOfKeyImages,
|
||||
/// Invalid ss matrix.
|
||||
#[cfg_attr(feature = "std", error("invalid ss"))]
|
||||
InvalidSs,
|
||||
#[cfg_attr(feature = "std", error("key image was identity"))]
|
||||
IdentityKeyImage,
|
||||
/// Invalid key image.
|
||||
#[cfg_attr(feature = "std", error("invalid key image"))]
|
||||
InvalidKeyImage,
|
||||
/// Invalid ci vector.
|
||||
#[cfg_attr(feature = "std", error("invalid ci"))]
|
||||
InvalidCi,
|
||||
}
|
||||
|
||||
/// A vector of rings, forming a matrix, to verify the MLSAG with.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct RingMatrix {
|
||||
matrix: Vec<Vec<EdwardsPoint>>,
|
||||
}
|
||||
|
||||
impl RingMatrix {
|
||||
pub fn new(matrix: Vec<Vec<EdwardsPoint>>) -> Result<Self, MlsagError> {
|
||||
/// Construct a ring matrix from an already formatted series of points.
|
||||
fn new(matrix: Vec<Vec<EdwardsPoint>>) -> Result<Self, MlsagError> {
|
||||
// Monero requires that there is more than one ring member for MLSAG signatures:
|
||||
// https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/
|
||||
// src/ringct/rctSigs.cpp#L462
|
||||
@@ -60,11 +68,12 @@ impl RingMatrix {
|
||||
RingMatrix::new(matrix)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &[EdwardsPoint]> {
|
||||
/// Iterate the members of the matrix.
|
||||
fn iter(&self) -> impl Iterator<Item = &[EdwardsPoint]> {
|
||||
self.matrix.iter().map(AsRef::as_ref)
|
||||
}
|
||||
|
||||
/// Return the amount of members in the ring.
|
||||
/// Returns the amount of members in the ring.
|
||||
pub fn members(&self) -> usize {
|
||||
self.matrix.len()
|
||||
}
|
||||
@@ -79,13 +88,15 @@ impl RingMatrix {
|
||||
}
|
||||
}
|
||||
|
||||
/// The MLSAG linkable ring signature, as used in Monero.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct Mlsag {
|
||||
pub ss: Vec<Vec<Scalar>>,
|
||||
pub cc: Scalar,
|
||||
ss: Vec<Vec<Scalar>>,
|
||||
cc: Scalar,
|
||||
}
|
||||
|
||||
impl Mlsag {
|
||||
/// Write the MLSAG to a writer.
|
||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
for ss in &self.ss {
|
||||
write_raw_vec(write_scalar, ss, w)?;
|
||||
@@ -93,6 +104,7 @@ impl Mlsag {
|
||||
write_scalar(&self.cc, w)
|
||||
}
|
||||
|
||||
/// Read the MLSAG from a reader.
|
||||
pub fn read<R: Read>(mixins: usize, ss_2_elements: usize, r: &mut R) -> io::Result<Mlsag> {
|
||||
Ok(Mlsag {
|
||||
ss: (0 .. mixins)
|
||||
@@ -102,6 +114,7 @@ impl Mlsag {
|
||||
})
|
||||
}
|
||||
|
||||
/// Verify the MLSAG.
|
||||
pub fn verify(
|
||||
&self,
|
||||
msg: &[u8; 32],
|
||||
@@ -142,8 +155,8 @@ impl Mlsag {
|
||||
// Not all dimensions need to be linkable, e.g. commitments, and only linkable layers need
|
||||
// to have key images.
|
||||
if let Some(ki) = ki {
|
||||
if ki.is_identity() {
|
||||
Err(MlsagError::IdentityKeyImage)?;
|
||||
if ki.is_identity() || (!ki.is_torsion_free()) {
|
||||
Err(MlsagError::InvalidKeyImage)?;
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
@@ -164,8 +177,9 @@ impl Mlsag {
|
||||
}
|
||||
}
|
||||
|
||||
/// An aggregate ring matrix builder, usable to set up the ring matrix to prove/verify an aggregate
|
||||
/// MLSAG signature.
|
||||
/// Builder for a RingMatrix when using an aggregate signature.
|
||||
///
|
||||
/// This handles the formatting as necessary.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct AggregateRingMatrixBuilder {
|
||||
key_ring: Vec<Vec<EdwardsPoint>>,
|
||||
@@ -206,7 +220,7 @@ impl AggregateRingMatrixBuilder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Build and return the [`RingMatrix`]
|
||||
/// Build and return the [`RingMatrix`].
|
||||
pub fn build(mut self) -> Result<RingMatrix, MlsagError> {
|
||||
for (i, amount_commitment) in self.amounts_ring.drain(..).enumerate() {
|
||||
self.key_ring[i].push(amount_commitment);
|
||||
|
||||
@@ -23,7 +23,7 @@ pub mod bulletproofs;
|
||||
use crate::{
|
||||
Protocol,
|
||||
serialize::*,
|
||||
ringct::{mlsag::Mlsag, clsag::Clsag, borromean::BorromeanRange, bulletproofs::Bulletproofs},
|
||||
ringct::{mlsag::Mlsag, clsag::Clsag, borromean::BorromeanRange, bulletproofs::Bulletproof},
|
||||
};
|
||||
|
||||
/// Generate a key image for a given key. Defined as `x * hash_to_point(xG)`.
|
||||
@@ -31,6 +31,7 @@ pub fn generate_key_image(secret: &Zeroizing<Scalar>) -> EdwardsPoint {
|
||||
hash_to_point(&(ED25519_BASEPOINT_TABLE * secret.deref())) * secret.deref()
|
||||
}
|
||||
|
||||
/// An encrypted amount.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum EncryptedAmount {
|
||||
Original { mask: [u8; 32], amount: [u8; 32] },
|
||||
@@ -38,6 +39,7 @@ pub enum EncryptedAmount {
|
||||
}
|
||||
|
||||
impl EncryptedAmount {
|
||||
/// Read an EncryptedAmount from a reader.
|
||||
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)? }
|
||||
@@ -46,6 +48,7 @@ impl EncryptedAmount {
|
||||
})
|
||||
}
|
||||
|
||||
/// Write the EncryptedAmount to a writer.
|
||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
match self {
|
||||
EncryptedAmount::Original { mask, amount } => {
|
||||
@@ -57,6 +60,7 @@ impl EncryptedAmount {
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of the RingCT data.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub enum RctType {
|
||||
/// No RCT proofs.
|
||||
@@ -77,6 +81,18 @@ pub enum RctType {
|
||||
}
|
||||
|
||||
impl RctType {
|
||||
/// Convert [`self`] to its byte representation.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use monero_serai::ringct::*;
|
||||
/// assert_eq!(RctType::Null.to_byte(), 0);
|
||||
/// assert_eq!(RctType::MlsagAggregate.to_byte(), 1);
|
||||
/// assert_eq!(RctType::MlsagIndividual.to_byte(), 2);
|
||||
/// assert_eq!(RctType::Bulletproofs.to_byte(), 3);
|
||||
/// assert_eq!(RctType::BulletproofsCompactAmount.to_byte(), 4);
|
||||
/// assert_eq!(RctType::Clsag.to_byte(), 5);
|
||||
/// assert_eq!(RctType::BulletproofsPlus.to_byte(), 6);
|
||||
/// ```
|
||||
pub fn to_byte(self) -> u8 {
|
||||
match self {
|
||||
RctType::Null => 0,
|
||||
@@ -89,6 +105,25 @@ impl RctType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create [`Self`] from a byte representation.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use monero_serai::ringct::*;
|
||||
/// assert_eq!(RctType::from_byte(0).unwrap(), RctType::Null);
|
||||
/// assert_eq!(RctType::from_byte(1).unwrap(), RctType::MlsagAggregate);
|
||||
/// assert_eq!(RctType::from_byte(2).unwrap(), RctType::MlsagIndividual);
|
||||
/// assert_eq!(RctType::from_byte(3).unwrap(), RctType::Bulletproofs);
|
||||
/// assert_eq!(RctType::from_byte(4).unwrap(), RctType::BulletproofsCompactAmount);
|
||||
/// assert_eq!(RctType::from_byte(5).unwrap(), RctType::Clsag);
|
||||
/// assert_eq!(RctType::from_byte(6).unwrap(), RctType::BulletproofsPlus);
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
/// This function returns [`None`] if the byte representation is invalid.
|
||||
/// ```rust
|
||||
/// # use monero_serai::ringct::*;
|
||||
/// assert_eq!(RctType::from_byte(7), None);
|
||||
/// ```
|
||||
pub fn from_byte(byte: u8) -> Option<Self> {
|
||||
Some(match byte {
|
||||
0 => RctType::Null,
|
||||
@@ -102,22 +137,45 @@ impl RctType {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns true if this RctType uses compact encrypted amounts, false otherwise.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use monero_serai::ringct::*;
|
||||
/// assert_eq!(RctType::Null.compact_encrypted_amounts(), false);
|
||||
/// assert_eq!(RctType::MlsagAggregate.compact_encrypted_amounts(), false);
|
||||
/// assert_eq!(RctType::MlsagIndividual.compact_encrypted_amounts(), false);
|
||||
/// assert_eq!(RctType::Bulletproofs.compact_encrypted_amounts(), false);
|
||||
/// assert_eq!(RctType::BulletproofsCompactAmount.compact_encrypted_amounts(), true);
|
||||
/// assert_eq!(RctType::Clsag.compact_encrypted_amounts(), true);
|
||||
/// assert_eq!(RctType::BulletproofsPlus.compact_encrypted_amounts(), true);
|
||||
/// ```
|
||||
pub fn compact_encrypted_amounts(&self) -> bool {
|
||||
match self {
|
||||
RctType::Null |
|
||||
RctType::MlsagAggregate |
|
||||
RctType::MlsagIndividual |
|
||||
RctType::Bulletproofs => false,
|
||||
RctType::Null
|
||||
| RctType::MlsagAggregate
|
||||
| RctType::MlsagIndividual
|
||||
| RctType::Bulletproofs => false,
|
||||
RctType::BulletproofsCompactAmount | RctType::Clsag | RctType::BulletproofsPlus => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The base of the RingCT data.
|
||||
///
|
||||
/// This excludes all proofs (which once initially verified do not need to be kept around) and
|
||||
/// solely keeps data which either impacts the effects of the transactions or is needed to scan it.
|
||||
///
|
||||
/// The one exception for this is `pseudo_outs`, which was originally present here yet moved to
|
||||
/// RctPrunable in a later hard fork (causing it to be present in both).
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct RctBase {
|
||||
/// The fee used by this transaction.
|
||||
pub fee: u64,
|
||||
/// The re-randomized amount commitments used within inputs.
|
||||
pub pseudo_outs: Vec<EdwardsPoint>,
|
||||
/// The encrypted amounts for the recipient to decrypt.
|
||||
pub encrypted_amounts: Vec<EncryptedAmount>,
|
||||
/// The output commitments.
|
||||
pub commitments: Vec<EdwardsPoint>,
|
||||
}
|
||||
|
||||
@@ -127,6 +185,7 @@ impl RctBase {
|
||||
1 + (outputs * (8 + 32)) + varint_len(fee)
|
||||
}
|
||||
|
||||
/// Write the RctBase to a writer.
|
||||
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
|
||||
w.write_all(&[rct_type.to_byte()])?;
|
||||
match rct_type {
|
||||
@@ -144,16 +203,17 @@ impl RctBase {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a RctBase from a writer.
|
||||
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"))?;
|
||||
|
||||
match rct_type {
|
||||
RctType::Null | RctType::MlsagAggregate | RctType::MlsagIndividual => {}
|
||||
RctType::Bulletproofs |
|
||||
RctType::BulletproofsCompactAmount |
|
||||
RctType::Clsag |
|
||||
RctType::BulletproofsPlus => {
|
||||
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
|
||||
@@ -171,12 +231,14 @@ impl RctBase {
|
||||
} else {
|
||||
RctBase {
|
||||
fee: read_varint(r)?,
|
||||
// Only read pseudo_outs if they have yet to be moved to RctPrunable
|
||||
// TODO: Shouldn't this be any Mlsag*?
|
||||
pseudo_outs: if rct_type == RctType::MlsagIndividual {
|
||||
read_raw_vec(read_point, inputs, r)?
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
encrypted_amounts: (0 .. outputs)
|
||||
encrypted_amounts: (0..outputs)
|
||||
.map(|_| EncryptedAmount::read(rct_type.compact_encrypted_amounts(), r))
|
||||
.collect::<Result<_, _>>()?,
|
||||
commitments: read_raw_vec(read_point, outputs, r)?,
|
||||
@@ -187,6 +249,7 @@ impl RctBase {
|
||||
}
|
||||
}
|
||||
|
||||
/// The prunable portion of the RingCT data.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum RctPrunable {
|
||||
Null,
|
||||
@@ -199,12 +262,12 @@ pub enum RctPrunable {
|
||||
mlsags: Vec<Mlsag>,
|
||||
},
|
||||
MlsagBulletproofs {
|
||||
bulletproofs: Bulletproofs,
|
||||
bulletproofs: Bulletproof,
|
||||
mlsags: Vec<Mlsag>,
|
||||
pseudo_outs: Vec<EdwardsPoint>,
|
||||
},
|
||||
Clsag {
|
||||
bulletproofs: Bulletproofs,
|
||||
bulletproofs: Bulletproof,
|
||||
clsags: Vec<Clsag>,
|
||||
pseudo_outs: Vec<EdwardsPoint>,
|
||||
},
|
||||
@@ -213,10 +276,14 @@ pub enum RctPrunable {
|
||||
impl RctPrunable {
|
||||
pub(crate) fn fee_weight(protocol: Protocol, inputs: usize, outputs: usize) -> usize {
|
||||
// 1 byte for number of BPs (technically a VarInt, yet there's always just zero or one)
|
||||
1 + Bulletproofs::fee_weight(protocol.bp_plus(), outputs) +
|
||||
(inputs * (Clsag::fee_weight(protocol.ring_len()) + 32))
|
||||
1 + Bulletproof::fee_weight(protocol.bp_plus(), outputs)
|
||||
+ (inputs * (Clsag::fee_weight(protocol.ring_len()) + 32))
|
||||
}
|
||||
|
||||
/// Serialize [`Self`] into the writer `w`.
|
||||
///
|
||||
/// # Errors
|
||||
/// This function returns any errors from the writer itself.
|
||||
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
|
||||
match self {
|
||||
RctPrunable::Null => Ok(()),
|
||||
@@ -249,12 +316,18 @@ impl RctPrunable {
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize [`Self`] into a new byte buffer.
|
||||
pub fn serialize(&self, rct_type: RctType) -> Vec<u8> {
|
||||
let mut serialized = vec![];
|
||||
self.write(&mut serialized, rct_type).unwrap();
|
||||
serialized
|
||||
}
|
||||
|
||||
/// Create [`Self`] from the reader `r`.
|
||||
///
|
||||
/// # Errors
|
||||
/// This function returns an error if either the reader failed,
|
||||
/// or if the data could not be deserialized into a [`Self`].
|
||||
pub fn read<R: Read>(
|
||||
rct_type: RctType,
|
||||
ring_length: usize,
|
||||
@@ -281,7 +354,7 @@ impl RctPrunable {
|
||||
},
|
||||
RctType::MlsagIndividual => RctPrunable::MlsagBorromean {
|
||||
borromean: read_raw_vec(BorromeanRange::read, outputs, r)?,
|
||||
mlsags: (0 .. inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::<Result<_, _>>()?,
|
||||
mlsags: (0..inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::<Result<_, _>>()?,
|
||||
},
|
||||
RctType::Bulletproofs | RctType::BulletproofsCompactAmount => {
|
||||
RctPrunable::MlsagBulletproofs {
|
||||
@@ -294,11 +367,9 @@ impl RctPrunable {
|
||||
{
|
||||
Err(io::Error::other("n bulletproofs instead of one"))?;
|
||||
}
|
||||
Bulletproofs::read(r)?
|
||||
Bulletproof::read(r)?
|
||||
},
|
||||
mlsags: (0 .. inputs)
|
||||
.map(|_| Mlsag::read(ring_length, 2, r))
|
||||
.collect::<Result<_, _>>()?,
|
||||
mlsags: (0..inputs).map(|_| Mlsag::read(ring_length, 2, r)).collect::<Result<_, _>>()?,
|
||||
pseudo_outs: read_raw_vec(read_point, inputs, r)?,
|
||||
}
|
||||
}
|
||||
@@ -307,11 +378,9 @@ impl RctPrunable {
|
||||
if read_varint::<_, u64>(r)? != 1 {
|
||||
Err(io::Error::other("n bulletproofs instead of one"))?;
|
||||
}
|
||||
(if rct_type == RctType::Clsag { Bulletproofs::read } else { Bulletproofs::read_plus })(
|
||||
r,
|
||||
)?
|
||||
(if rct_type == RctType::Clsag { Bulletproof::read } else { Bulletproof::read_plus })(r)?
|
||||
},
|
||||
clsags: (0 .. inputs).map(|_| Clsag::read(ring_length, r)).collect::<Result<_, _>>()?,
|
||||
clsags: (0..inputs).map(|_| Clsag::read(ring_length, r)).collect::<Result<_, _>>()?,
|
||||
pseudo_outs: read_raw_vec(read_point, inputs, r)?,
|
||||
},
|
||||
})
|
||||
@@ -320,19 +389,22 @@ 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"),
|
||||
RctPrunable::AggregateMlsagBorromean { borromean, .. } |
|
||||
RctPrunable::MlsagBorromean { borromean, .. } => {
|
||||
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),
|
||||
RctPrunable::MlsagBulletproofs { bulletproofs, .. }
|
||||
| RctPrunable::Clsag { bulletproofs, .. } => bulletproofs.signature_write(w),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// RingCT signature data.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct RctSignatures {
|
||||
/// The base of the RingCT data.
|
||||
pub base: RctBase,
|
||||
/// The prunable portion of the RingCT data.
|
||||
pub prunable: RctPrunable,
|
||||
}
|
||||
|
||||
@@ -360,7 +432,7 @@ impl RctSignatures {
|
||||
}
|
||||
}
|
||||
RctPrunable::Clsag { bulletproofs, .. } => {
|
||||
if matches!(bulletproofs, Bulletproofs::Original { .. }) {
|
||||
if matches!(bulletproofs, Bulletproof::Original { .. }) {
|
||||
RctType::Clsag
|
||||
} else {
|
||||
RctType::BulletproofsPlus
|
||||
@@ -373,18 +445,28 @@ impl RctSignatures {
|
||||
RctBase::fee_weight(outputs, fee) + RctPrunable::fee_weight(protocol, inputs, outputs)
|
||||
}
|
||||
|
||||
/// Serialize [`Self`] into the writer `w`.
|
||||
///
|
||||
/// # Errors
|
||||
/// This function returns any errors from the writer itself.
|
||||
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)
|
||||
}
|
||||
|
||||
/// Serialize [`Self`] into a new byte buffer.
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
let mut serialized = vec![];
|
||||
self.write(&mut serialized).unwrap();
|
||||
serialized
|
||||
}
|
||||
|
||||
/// Create [`Self`] from the reader `r` and other data.
|
||||
///
|
||||
/// # Errors
|
||||
/// This function returns an error if either the reader failed,
|
||||
/// or if the data could not be deserialized into a [`Self`].
|
||||
pub fn read<R: Read>(
|
||||
ring_length: usize,
|
||||
inputs: usize,
|
||||
|
||||
@@ -34,8 +34,10 @@ pub use http::*;
|
||||
// src/wallet/wallet2.cpp#L121
|
||||
const GRACE_BLOCKS_FOR_FEE_ESTIMATE: u64 = 10;
|
||||
|
||||
/// A empty marker struct representing an empty response.
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct EmptyResponse {}
|
||||
/// A generic JSON-RPC response.
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct JsonRpcResponse<T> {
|
||||
result: T,
|
||||
@@ -54,6 +56,7 @@ struct TransactionsResponse {
|
||||
txs: Vec<TransactionResponse>,
|
||||
}
|
||||
|
||||
/// The response data from an [`Rpc::get_outs`] call.
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct OutputResponse {
|
||||
pub height: usize,
|
||||
@@ -63,27 +66,41 @@ pub struct OutputResponse {
|
||||
txid: String,
|
||||
}
|
||||
|
||||
/// Possible errors that can occur from an RPC call.
|
||||
///
|
||||
/// This represents errors on the client side, as well
|
||||
/// as valid error responses from the server.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||
pub enum RpcError {
|
||||
/// There was an internal error.
|
||||
#[cfg_attr(feature = "std", error("internal error ({0})"))]
|
||||
InternalError(&'static str),
|
||||
/// There was a connection error.
|
||||
#[cfg_attr(feature = "std", error("connection error ({0})"))]
|
||||
ConnectionError(String),
|
||||
/// The data response received from the node was invalid.
|
||||
#[cfg_attr(feature = "std", error("invalid node ({0})"))]
|
||||
InvalidNode(String),
|
||||
/// The Monero [`Protocol`] version was invalid.
|
||||
#[cfg_attr(feature = "std", error("unsupported protocol version ({0})"))]
|
||||
UnsupportedProtocol(usize),
|
||||
/// Requested transaction hashes were not found.
|
||||
#[cfg_attr(feature = "std", error("transactions not found"))]
|
||||
TransactionsNotFound(Vec<[u8; 32]>),
|
||||
/// A curve point received from the node was invalid.
|
||||
#[cfg_attr(feature = "std", error("invalid point ({0})"))]
|
||||
InvalidPoint(String),
|
||||
/// The transaction(s) requested were pruned from the node.
|
||||
#[cfg_attr(feature = "std", error("pruned transaction"))]
|
||||
PrunedTransaction,
|
||||
/// An invalid transaction was either sent/received to/from the node.
|
||||
#[cfg_attr(feature = "std", error("invalid transaction ({0:?})"))]
|
||||
InvalidTransaction([u8; 32]),
|
||||
/// The node failed to return a fee.
|
||||
#[cfg_attr(feature = "std", error("unexpected fee response"))]
|
||||
InvalidFee,
|
||||
/// The transaction priority level given was invalid.
|
||||
#[cfg_attr(feature = "std", error("invalid priority"))]
|
||||
InvalidPriority,
|
||||
}
|
||||
@@ -114,12 +131,15 @@ fn read_epee_vi<R: io::Read>(reader: &mut R) -> io::Result<u64> {
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut vi = u64::from(vi_start >> 2);
|
||||
for i in 1 .. len {
|
||||
for i in 1..len {
|
||||
vi |= u64::from(read_byte(reader)?) << (((i - 1) * 8) + 6);
|
||||
}
|
||||
Ok(vi)
|
||||
}
|
||||
|
||||
/// A trait representing an RPC connection.
|
||||
///
|
||||
/// Note that [`HttpRpc`] already implements this trait.
|
||||
#[async_trait]
|
||||
pub trait RpcConnection: Clone + Debug {
|
||||
/// Perform a POST request to the specified route with the specified body.
|
||||
@@ -128,6 +148,7 @@ pub trait RpcConnection: Clone + Debug {
|
||||
async fn post(&self, route: &str, body: Vec<u8>) -> Result<Vec<u8>, RpcError>;
|
||||
}
|
||||
|
||||
/// A generic RPC client.
|
||||
// TODO: Make this provided methods for RpcConnection?
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Rpc<R: RpcConnection>(R);
|
||||
@@ -202,6 +223,7 @@ impl<R: RpcConnection> Rpc<R> {
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the node's current block height.
|
||||
pub async fn get_height(&self) -> Result<usize, RpcError> {
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct HeightResponse {
|
||||
@@ -210,6 +232,7 @@ impl<R: RpcConnection> Rpc<R> {
|
||||
Ok(self.rpc_call::<Option<()>, HeightResponse>("get_height", None).await?.height)
|
||||
}
|
||||
|
||||
/// Get [`Transaction`]s by their `hashes`.
|
||||
pub async fn get_transactions(&self, hashes: &[[u8; 32]]) -> Result<Vec<Transaction>, RpcError> {
|
||||
if hashes.is_empty() {
|
||||
return Ok(vec![]);
|
||||
@@ -274,6 +297,7 @@ impl<R: RpcConnection> Rpc<R> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get a single [`Transaction`] by its hash.
|
||||
pub async fn get_transaction(&self, tx: [u8; 32]) -> Result<Transaction, RpcError> {
|
||||
self.get_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0))
|
||||
}
|
||||
@@ -314,6 +338,7 @@ impl<R: RpcConnection> Rpc<R> {
|
||||
Ok(block)
|
||||
}
|
||||
|
||||
/// Get a [`Block`] by its height number.
|
||||
pub async fn get_block_by_number(&self, number: usize) -> Result<Block, RpcError> {
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct BlockResponse {
|
||||
@@ -341,6 +366,7 @@ impl<R: RpcConnection> Rpc<R> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all the [`Transaction`]s belonging to the corresponding block `hash`.
|
||||
pub async fn get_block_transactions(&self, hash: [u8; 32]) -> Result<Vec<Transaction>, RpcError> {
|
||||
let block = self.get_block(hash).await?;
|
||||
let mut res = vec![block.miner_tx];
|
||||
@@ -405,7 +431,7 @@ impl<R: RpcConnection> Rpc<R> {
|
||||
let read_object = |reader: &mut &[u8]| -> io::Result<Vec<u64>> {
|
||||
let fields = read_byte(reader)? >> 2;
|
||||
|
||||
for _ in 0 .. fields {
|
||||
for _ in 0..fields {
|
||||
let name_len = read_byte(reader)?;
|
||||
let name = read_raw_vec(read_byte, name_len.into(), reader)?;
|
||||
|
||||
@@ -458,7 +484,7 @@ impl<R: RpcConnection> Rpc<R> {
|
||||
};
|
||||
|
||||
let mut bytes_res = vec![];
|
||||
for _ in 0 .. iters {
|
||||
for _ in 0..iters {
|
||||
bytes_res.push(f(reader)?);
|
||||
}
|
||||
|
||||
@@ -478,8 +504,8 @@ impl<R: RpcConnection> Rpc<R> {
|
||||
if bytes_res
|
||||
.first()
|
||||
.ok_or_else(|| io::Error::other("status wasn't a string"))?
|
||||
.as_slice() !=
|
||||
b"OK"
|
||||
.as_slice()
|
||||
!= b"OK"
|
||||
{
|
||||
// TODO: Better handle non-OK responses
|
||||
Err(io::Error::other("response wasn't OK"))?;
|
||||
@@ -623,6 +649,7 @@ impl<R: RpcConnection> Rpc<R> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get a [`Fee`] based on [`Protocol::v14`] rules.
|
||||
async fn get_fee_v14(&self, priority: FeePriority) -> Result<Fee, RpcError> {
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct FeeResponseV14 {
|
||||
@@ -702,6 +729,7 @@ impl<R: RpcConnection> Rpc<R> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Broadcast a [`Transaction`] to the network.
|
||||
pub async fn publish_transaction(&self, tx: &Transaction) -> Result<(), RpcError> {
|
||||
#[allow(dead_code)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
@@ -730,6 +758,13 @@ impl<R: RpcConnection> Rpc<R> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate blocks.
|
||||
///
|
||||
/// - `address` is the address that will receive the coinbase reward
|
||||
/// - `block_count` is the number of blocks that will be generated
|
||||
///
|
||||
/// Note this is only for testing with nodes started with `--regtest`, see:
|
||||
/// <https://www.getmonero.org/resources/developer-guides/daemon-rpc.html#generateblocks>.
|
||||
// TODO: Take &Address, not &str?
|
||||
pub async fn generate_blocks(
|
||||
&self,
|
||||
|
||||
@@ -7,7 +7,8 @@ use multiexp::BatchVerifier;
|
||||
|
||||
use crate::{
|
||||
Commitment, random_scalar,
|
||||
ringct::bulletproofs::{Bulletproofs, original::OriginalStruct},
|
||||
ringct::bulletproofs::{Bulletproof, original::OriginalStruct},
|
||||
wallet::TransactionError,
|
||||
};
|
||||
|
||||
mod plus;
|
||||
@@ -18,7 +19,7 @@ fn bulletproofs_vector() {
|
||||
let point = |point| decompress_point(point).unwrap();
|
||||
|
||||
// Generated from Monero
|
||||
assert!(Bulletproofs::Original(OriginalStruct {
|
||||
assert!(Bulletproof::Original(OriginalStruct {
|
||||
A: point(hex!("ef32c0b9551b804decdcb107eb22aa715b7ce259bf3c5cac20e24dfa6b28ac71")),
|
||||
S: point(hex!("e1285960861783574ee2b689ae53622834eb0b035d6943103f960cd23e063fa0")),
|
||||
T1: point(hex!("4ea07735f184ba159d0e0eb662bac8cde3eb7d39f31e567b0fbda3aa23fe5620")),
|
||||
@@ -70,7 +71,11 @@ macro_rules! bulletproofs_tests {
|
||||
.map(|i| Commitment::new(random_scalar(&mut OsRng), u64::try_from(i).unwrap()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let bp = Bulletproofs::prove(&mut OsRng, &commitments, $plus).unwrap();
|
||||
let bp = if $plus {
|
||||
Bulletproof::prove_plus(&mut OsRng, commitments.clone()).unwrap()
|
||||
} else {
|
||||
Bulletproof::prove(&mut OsRng, &commitments).unwrap()
|
||||
};
|
||||
|
||||
let commitments = commitments.iter().map(Commitment::calculate).collect::<Vec<_>>();
|
||||
assert!(bp.verify(&mut OsRng, &commitments));
|
||||
@@ -86,7 +91,15 @@ macro_rules! bulletproofs_tests {
|
||||
for _ in 0 .. 17 {
|
||||
commitments.push(Commitment::new(Scalar::ZERO, 0));
|
||||
}
|
||||
assert!(Bulletproofs::prove(&mut OsRng, &commitments, $plus).is_err());
|
||||
assert_eq!(
|
||||
(if $plus {
|
||||
Bulletproof::prove_plus(&mut OsRng, commitments)
|
||||
} else {
|
||||
Bulletproof::prove(&mut OsRng, &commitments)
|
||||
})
|
||||
.unwrap_err(),
|
||||
TransactionError::TooManyOutputs,
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use rand_core::{RngCore, OsRng};
|
||||
|
||||
use multiexp::BatchVerifier;
|
||||
use group::ff::Field;
|
||||
use dalek_ff_group::{Scalar, EdwardsPoint};
|
||||
use dalek_ff_group::Scalar;
|
||||
|
||||
use crate::{
|
||||
Commitment,
|
||||
@@ -19,7 +19,7 @@ fn test_aggregate_range_proof() {
|
||||
for _ in 0 .. m {
|
||||
commitments.push(Commitment::new(*Scalar::random(&mut OsRng), OsRng.next_u64()));
|
||||
}
|
||||
let commitment_points = commitments.iter().map(|com| EdwardsPoint(com.calculate())).collect();
|
||||
let commitment_points = commitments.iter().map(Commitment::calculate).collect();
|
||||
let statement = AggregateRangeStatement::new(commitment_points).unwrap();
|
||||
let witness = AggregateRangeWitness::new(commitments).unwrap();
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::{
|
||||
Protocol, hash,
|
||||
serialize::*,
|
||||
ring_signatures::RingSignature,
|
||||
ringct::{bulletproofs::Bulletproofs, RctType, RctBase, RctPrunable, RctSignatures},
|
||||
ringct::{bulletproofs::Bulletproof, RctType, RctBase, RctPrunable, RctSignatures},
|
||||
};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
@@ -426,7 +426,7 @@ impl Transaction {
|
||||
if !(bp || bp_plus) {
|
||||
blob_size
|
||||
} else {
|
||||
blob_size + Bulletproofs::calculate_bp_clawback(bp_plus, self.prefix.outputs.len()).0
|
||||
blob_size + Bulletproof::calculate_bp_clawback(bp_plus, self.prefix.outputs.len()).0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,10 +34,6 @@ impl UnreducedScalar {
|
||||
Ok(UnreducedScalar(read_bytes(r)?))
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn as_bits(&self) -> [u8; 256] {
|
||||
let mut bits = [0; 256];
|
||||
for (i, bit) in bits.iter_mut().enumerate() {
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
use std_shims::{vec::Vec, collections::HashSet};
|
||||
|
||||
#[cfg(feature = "cache-distribution")]
|
||||
use std_shims::sync::OnceLock;
|
||||
|
||||
#[cfg(all(feature = "cache-distribution", not(feature = "std")))]
|
||||
use std_shims::sync::Mutex;
|
||||
#[cfg(all(feature = "cache-distribution", feature = "std"))]
|
||||
use async_lock::Mutex;
|
||||
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
@@ -29,16 +21,6 @@ const BLOCKS_PER_YEAR: usize = 365 * 24 * 60 * 60 / BLOCK_TIME;
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
const TIP_APPLICATION: f64 = (DEFAULT_LOCK_WINDOW * BLOCK_TIME) as f64;
|
||||
|
||||
// TODO: Resolve safety of this in case a reorg occurs/the network changes
|
||||
// TODO: Update this when scanning a block, as possible
|
||||
#[cfg(feature = "cache-distribution")]
|
||||
static DISTRIBUTION_CELL: OnceLock<Mutex<Vec<u64>>> = OnceLock::new();
|
||||
#[cfg(feature = "cache-distribution")]
|
||||
#[allow(non_snake_case)]
|
||||
fn DISTRIBUTION() -> &'static Mutex<Vec<u64>> {
|
||||
DISTRIBUTION_CELL.get_or_init(|| Mutex::new(Vec::with_capacity(3000000)))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn select_n<'a, R: RngCore + CryptoRng, RPC: RpcConnection>(
|
||||
rng: &mut R,
|
||||
@@ -158,14 +140,6 @@ async fn select_decoys<R: RngCore + CryptoRng, RPC: RpcConnection>(
|
||||
inputs: &[SpendableOutput],
|
||||
fingerprintable_canonical: bool,
|
||||
) -> Result<Vec<Decoys>, RpcError> {
|
||||
#[cfg(feature = "cache-distribution")]
|
||||
#[cfg(not(feature = "std"))]
|
||||
let mut distribution = DISTRIBUTION().lock();
|
||||
#[cfg(feature = "cache-distribution")]
|
||||
#[cfg(feature = "std")]
|
||||
let mut distribution = DISTRIBUTION().lock().await;
|
||||
|
||||
#[cfg(not(feature = "cache-distribution"))]
|
||||
let mut distribution = vec![];
|
||||
|
||||
let decoy_count = ring_len - 1;
|
||||
|
||||
@@ -10,7 +10,7 @@ use curve25519_dalek::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
hash, hash_to_scalar, serialize::write_varint, ringct::EncryptedAmount, transaction::Input,
|
||||
hash, hash_to_scalar, serialize::write_varint, Commitment, ringct::EncryptedAmount, transaction::Input,
|
||||
};
|
||||
|
||||
pub mod extra;
|
||||
@@ -94,18 +94,21 @@ pub(crate) fn commitment_mask(shared_key: Scalar) -> Scalar {
|
||||
hash_to_scalar(&mask)
|
||||
}
|
||||
|
||||
pub(crate) fn amount_encryption(amount: u64, key: Scalar) -> [u8; 8] {
|
||||
pub(crate) fn compact_amount_encryption(amount: u64, key: Scalar) -> [u8; 8] {
|
||||
let mut amount_mask = b"amount".to_vec();
|
||||
amount_mask.extend(key.to_bytes());
|
||||
(amount ^ u64::from_le_bytes(hash(&amount_mask)[.. 8].try_into().unwrap())).to_le_bytes()
|
||||
}
|
||||
|
||||
// TODO: Move this under EncryptedAmount?
|
||||
fn amount_decryption(amount: &EncryptedAmount, key: Scalar) -> (Scalar, u64) {
|
||||
match amount {
|
||||
EncryptedAmount::Original { mask, amount } => {
|
||||
#[cfg(feature = "experimental")]
|
||||
{
|
||||
impl EncryptedAmount {
|
||||
/// Decrypt an EncryptedAmount into the Commitment it encrypts.
|
||||
///
|
||||
/// The caller must verify the decrypted Commitment matches with the actual Commitment used
|
||||
/// within in the Monero protocol.
|
||||
pub fn decrypt(&self, key: Scalar) -> Commitment {
|
||||
match self {
|
||||
// TODO: Add a test vector for this
|
||||
EncryptedAmount::Original { mask, amount } => {
|
||||
let mask_shared_sec = hash(key.as_bytes());
|
||||
let mask =
|
||||
Scalar::from_bytes_mod_order(*mask) - Scalar::from_bytes_mod_order(mask_shared_sec);
|
||||
@@ -116,20 +119,13 @@ fn amount_decryption(amount: &EncryptedAmount, key: Scalar) -> (Scalar, u64) {
|
||||
// d2b from rctTypes.cpp
|
||||
let amount = u64::from_le_bytes(amount_scalar.to_bytes()[0 .. 8].try_into().unwrap());
|
||||
|
||||
(mask, amount)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "experimental"))]
|
||||
{
|
||||
let _ = mask;
|
||||
let _ = amount;
|
||||
todo!("decrypting a legacy monero transaction's amount")
|
||||
Commitment::new(mask, amount)
|
||||
}
|
||||
EncryptedAmount::Compact { amount } => Commitment::new(
|
||||
commitment_mask(key),
|
||||
u64::from_le_bytes(compact_amount_encryption(u64::from_le_bytes(*amount), key)),
|
||||
),
|
||||
}
|
||||
EncryptedAmount::Compact { amount } => (
|
||||
commitment_mask(key),
|
||||
u64::from_le_bytes(amount_encryption(u64::from_le_bytes(*amount), key)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,7 @@ use crate::{
|
||||
transaction::{Input, Timelock, Transaction},
|
||||
block::Block,
|
||||
rpc::{RpcError, RpcConnection, Rpc},
|
||||
wallet::{
|
||||
PaymentId, Extra, address::SubaddressIndex, Scanner, uniqueness, shared_key, amount_decryption,
|
||||
},
|
||||
wallet::{PaymentId, Extra, address::SubaddressIndex, Scanner, uniqueness, shared_key},
|
||||
};
|
||||
|
||||
/// An absolute output ID, defined as its transaction hash and output index.
|
||||
@@ -427,15 +425,13 @@ impl Scanner {
|
||||
commitment.amount = amount;
|
||||
// Regular transaction
|
||||
} else {
|
||||
let (mask, amount) = match tx.rct_signatures.base.encrypted_amounts.get(o) {
|
||||
Some(amount) => amount_decryption(amount, shared_key),
|
||||
commitment = match tx.rct_signatures.base.encrypted_amounts.get(o) {
|
||||
Some(amount) => amount.decrypt(shared_key),
|
||||
// This should never happen, yet it may be possible with miner transactions?
|
||||
// Using get just decreases the possibility of a panic and lets us move on in that case
|
||||
None => break,
|
||||
};
|
||||
|
||||
// Rebuild the commitment to verify it
|
||||
commitment = Commitment::new(mask, amount);
|
||||
// If this is a malicious commitment, move to the next output
|
||||
// Any other R value will calculate to a different spend key and are therefore ignorable
|
||||
if Some(&commitment.calculate()) != tx.rct_signatures.base.commitments.get(o) {
|
||||
|
||||
@@ -31,7 +31,7 @@ use crate::{
|
||||
ringct::{
|
||||
generate_key_image,
|
||||
clsag::{ClsagError, ClsagInput, Clsag},
|
||||
bulletproofs::{MAX_OUTPUTS, Bulletproofs},
|
||||
bulletproofs::{MAX_OUTPUTS, Bulletproof},
|
||||
RctBase, RctPrunable, RctSignatures,
|
||||
},
|
||||
transaction::{Input, Output, Timelock, TransactionPrefix, Transaction},
|
||||
@@ -39,7 +39,7 @@ use crate::{
|
||||
wallet::{
|
||||
address::{Network, AddressSpec, MoneroAddress},
|
||||
ViewPair, SpendableOutput, Decoys, PaymentId, ExtraField, Extra, key_image_sort, uniqueness,
|
||||
shared_key, commitment_mask, amount_encryption,
|
||||
shared_key, commitment_mask, compact_amount_encryption,
|
||||
extra::{ARBITRARY_DATA_MARKER, MAX_ARBITRARY_DATA_SIZE},
|
||||
},
|
||||
};
|
||||
@@ -92,7 +92,7 @@ impl SendOutput {
|
||||
view_tag,
|
||||
dest: ((&shared_key * ED25519_BASEPOINT_TABLE) + output.0.spend),
|
||||
commitment: Commitment::new(commitment_mask(shared_key), output.1),
|
||||
amount: amount_encryption(output.1, shared_key),
|
||||
amount: compact_amount_encryption(output.1, shared_key),
|
||||
},
|
||||
payment_id,
|
||||
)
|
||||
@@ -783,7 +783,11 @@ impl SignableTransaction {
|
||||
let sum = commitments.iter().map(|commitment| commitment.mask).sum();
|
||||
|
||||
// Safe due to the constructor checking MAX_OUTPUTS
|
||||
let bp = Bulletproofs::prove(rng, &commitments, self.protocol.bp_plus()).unwrap();
|
||||
let bp = if self.protocol.bp_plus() {
|
||||
Bulletproof::prove_plus(rng, commitments.clone()).unwrap()
|
||||
} else {
|
||||
Bulletproof::prove(rng, &commitments).unwrap()
|
||||
};
|
||||
|
||||
// Create the TX extra
|
||||
let extra = Self::extra(
|
||||
@@ -932,7 +936,7 @@ impl Eventuality {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Remove this when the following for loop is updated
|
||||
// TODO: Remove this if/when the following for loop is updated to support older TXs
|
||||
assert!(
|
||||
rct_type.compact_encrypted_amounts(),
|
||||
"created an Eventuality for a very old RctType we don't support proving for"
|
||||
|
||||
@@ -36,4 +36,4 @@ dkg = { path = "../../crypto/dkg", default-features = false }
|
||||
bitcoin-serai = { path = "../../coins/bitcoin", default-features = false, features = ["hazmat"] }
|
||||
|
||||
monero-generators = { path = "../../coins/monero/generators", default-features = false }
|
||||
monero-serai = { path = "../../coins/monero", default-features = false, features = ["cache-distribution"] }
|
||||
monero-serai = { path = "../../coins/monero", default-features = false }
|
||||
|
||||
Reference in New Issue
Block a user