mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-11 21:49:26 +00:00
Incomplete work on using Option to remove panic cases
This commit is contained in:
@@ -1,49 +1,11 @@
|
|||||||
# monero-serai
|
# monero-serai
|
||||||
|
|
||||||
A modern Monero transaction library intended for usage in wallets. It prides
|
A modern Monero transaction library. It provides a modern, Rust-friendly view of
|
||||||
itself on accuracy, correctness, and removing common pit falls developers may
|
the Monero protocol.
|
||||||
face.
|
|
||||||
|
|
||||||
monero-serai also offers the following features:
|
|
||||||
|
|
||||||
- Featured Addresses
|
|
||||||
- A FROST-based multisig orders of magnitude more performant than Monero's
|
|
||||||
|
|
||||||
### Purpose and support
|
### Purpose and support
|
||||||
|
|
||||||
monero-serai was written for Serai, a decentralized exchange aiming to support
|
monero-serai was written for Serai, a decentralized exchange aiming to support
|
||||||
Monero. Despite this, monero-serai is intended to be a widely usable library,
|
Monero. Despite this, monero-serai is intended to be a widely usable library,
|
||||||
accurate to Monero. monero-serai guarantees the functionality needed for Serai,
|
accurate to Monero. monero-serai guarantees the functionality needed for Serai,
|
||||||
yet will not deprive functionality from other users.
|
yet does not include any functionality specific to Serai.
|
||||||
|
|
||||||
Various legacy transaction formats are not currently implemented, yet we are
|
|
||||||
willing to add support for them. There aren't active development efforts around
|
|
||||||
them however.
|
|
||||||
|
|
||||||
### Caveats
|
|
||||||
|
|
||||||
This library DOES attempt to do the following:
|
|
||||||
|
|
||||||
- Create on-chain transactions identical to how wallet2 would (unless told not
|
|
||||||
to)
|
|
||||||
- Not be detectable as monero-serai when scanning outputs
|
|
||||||
- Not reveal spent outputs to the connected RPC node
|
|
||||||
|
|
||||||
This library DOES NOT attempt to do the following:
|
|
||||||
|
|
||||||
- Have identical RPC behavior when creating transactions
|
|
||||||
- Be a wallet
|
|
||||||
|
|
||||||
This means that monero-serai shouldn't be fingerprintable on-chain. It also
|
|
||||||
shouldn't be fingerprintable if a targeted attack occurs to detect if the
|
|
||||||
receiving wallet is monero-serai or wallet2. It also should be generally safe
|
|
||||||
for usage with remote nodes.
|
|
||||||
|
|
||||||
It won't hide from remote nodes it's monero-serai however, potentially
|
|
||||||
allowing a remote node to profile you. The implications of this are left to the
|
|
||||||
user to consider.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|||||||
@@ -15,16 +15,26 @@ const CORRECT_BLOCK_HASH_202612: [u8; 32] =
|
|||||||
const EXISTING_BLOCK_HASH_202612: [u8; 32] =
|
const EXISTING_BLOCK_HASH_202612: [u8; 32] =
|
||||||
hex_literal::hex!("bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698");
|
hex_literal::hex!("bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698");
|
||||||
|
|
||||||
|
/// A Monero block's header.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct BlockHeader {
|
pub struct BlockHeader {
|
||||||
|
/// The major version of the protocol, denoting the hard fork.
|
||||||
pub major_version: u8,
|
pub major_version: u8,
|
||||||
|
/// The minor version of the protocol.
|
||||||
pub minor_version: u8,
|
pub minor_version: u8,
|
||||||
|
/// Seconds since the epoch.
|
||||||
pub timestamp: u64,
|
pub timestamp: u64,
|
||||||
|
/// The previous block's hash.
|
||||||
pub previous: [u8; 32],
|
pub previous: [u8; 32],
|
||||||
|
/// The nonce used to mine the block.
|
||||||
|
///
|
||||||
|
/// Miners should increment this while attempting to find a block with a hash satisfying the PoW
|
||||||
|
/// rules.
|
||||||
pub nonce: u32,
|
pub nonce: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockHeader {
|
impl BlockHeader {
|
||||||
|
/// Write the BlockHeader.
|
||||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
write_varint(&self.major_version, w)?;
|
write_varint(&self.major_version, w)?;
|
||||||
write_varint(&self.minor_version, w)?;
|
write_varint(&self.minor_version, w)?;
|
||||||
@@ -33,12 +43,14 @@ impl BlockHeader {
|
|||||||
w.write_all(&self.nonce.to_le_bytes())
|
w.write_all(&self.nonce.to_le_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Serialize the BlockHeader to a Vec<u8>.
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
let mut serialized = vec![];
|
let mut serialized = vec![];
|
||||||
self.write(&mut serialized).unwrap();
|
self.write(&mut serialized).unwrap();
|
||||||
serialized
|
serialized
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a BlockHeader.
|
||||||
pub fn read<R: Read>(r: &mut R) -> io::Result<BlockHeader> {
|
pub fn read<R: Read>(r: &mut R) -> io::Result<BlockHeader> {
|
||||||
Ok(BlockHeader {
|
Ok(BlockHeader {
|
||||||
major_version: read_varint(r)?,
|
major_version: read_varint(r)?,
|
||||||
@@ -50,14 +62,22 @@ impl BlockHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A Monero block.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Block {
|
pub struct Block {
|
||||||
|
/// The block's header.
|
||||||
pub header: BlockHeader,
|
pub header: BlockHeader,
|
||||||
|
/// The miner's transaction.
|
||||||
pub miner_tx: Transaction,
|
pub miner_tx: Transaction,
|
||||||
|
/// The transactions within this block.
|
||||||
pub txs: Vec<[u8; 32]>,
|
pub txs: Vec<[u8; 32]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Block {
|
impl Block {
|
||||||
|
/// The zero-index position of this block within the blockchain.
|
||||||
|
///
|
||||||
|
/// This information comes from the Block's miner transaction. If the miner transaction isn't
|
||||||
|
/// structed as expected, this will return None.
|
||||||
pub fn number(&self) -> Option<u64> {
|
pub fn number(&self) -> Option<u64> {
|
||||||
match self.miner_tx.prefix.inputs.first() {
|
match self.miner_tx.prefix.inputs.first() {
|
||||||
Some(Input::Gen(number)) => Some(*number),
|
Some(Input::Gen(number)) => Some(*number),
|
||||||
@@ -65,6 +85,7 @@ impl Block {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write the BlockHeader.
|
||||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
self.header.write(w)?;
|
self.header.write(w)?;
|
||||||
self.miner_tx.write(w)?;
|
self.miner_tx.write(w)?;
|
||||||
@@ -75,22 +96,25 @@ impl Block {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tx_merkle_root(&self) -> [u8; 32] {
|
/// Serialize the BlockHeader to a Vec<u8>.
|
||||||
merkle_root(self.miner_tx.hash(), &self.txs)
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
|
let mut serialized = vec![];
|
||||||
|
self.write(&mut serialized).unwrap();
|
||||||
|
serialized
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serialize the block as required for the proof of work hash.
|
/// Serialize the block as required for the proof of work hash.
|
||||||
///
|
///
|
||||||
/// This is distinct from the serialization required for the block hash. To get the block hash,
|
/// This is distinct from the serialization required for the block hash. To get the block hash,
|
||||||
/// use the [`Block::hash`] function.
|
/// use the [`Block::hash`] function.
|
||||||
pub fn serialize_hashable(&self) -> Vec<u8> {
|
pub fn serialize_pow_hash(&self) -> Vec<u8> {
|
||||||
let mut blob = self.header.serialize();
|
let mut blob = self.header.serialize();
|
||||||
blob.extend_from_slice(&self.tx_merkle_root());
|
blob.extend_from_slice(&merkle_root(self.miner_tx.hash(), &self.txs));
|
||||||
write_varint(&(1 + u64::try_from(self.txs.len()).unwrap()), &mut blob).unwrap();
|
write_varint(&(1 + u64::try_from(self.txs.len()).unwrap()), &mut blob).unwrap();
|
||||||
|
|
||||||
blob
|
blob
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the hash of this block.
|
||||||
pub fn hash(&self) -> [u8; 32] {
|
pub fn hash(&self) -> [u8; 32] {
|
||||||
let mut hashable = self.serialize_hashable();
|
let mut hashable = self.serialize_hashable();
|
||||||
// Monero pre-appends a VarInt of the block hashing blobs length before getting the block hash
|
// Monero pre-appends a VarInt of the block hashing blobs length before getting the block hash
|
||||||
@@ -103,16 +127,10 @@ impl Block {
|
|||||||
if hash == CORRECT_BLOCK_HASH_202612 {
|
if hash == CORRECT_BLOCK_HASH_202612 {
|
||||||
return EXISTING_BLOCK_HASH_202612;
|
return EXISTING_BLOCK_HASH_202612;
|
||||||
};
|
};
|
||||||
|
|
||||||
hash
|
hash
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
/// Read a BlockHeader.
|
||||||
let mut serialized = vec![];
|
|
||||||
self.write(&mut serialized).unwrap();
|
|
||||||
serialized
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read<R: Read>(r: &mut R) -> io::Result<Block> {
|
pub fn read<R: Read>(r: &mut R) -> io::Result<Block> {
|
||||||
let header = BlockHeader::read(r)?;
|
let header = BlockHeader::read(r)?;
|
||||||
|
|
||||||
|
|||||||
@@ -15,11 +15,22 @@ pub mod ring_signatures;
|
|||||||
/// RingCT structs and functionality.
|
/// RingCT structs and functionality.
|
||||||
pub mod ringct;
|
pub mod ringct;
|
||||||
|
|
||||||
/// Transaction structs.
|
/// Transaction structs and functionality.
|
||||||
pub mod transaction;
|
pub mod transaction;
|
||||||
/// Block structs.
|
/// Block structs and functionality.
|
||||||
pub mod block;
|
pub mod block;
|
||||||
|
|
||||||
|
/// The minimum amount of blocks an output is locked for.
|
||||||
|
///
|
||||||
|
/// If Monero suffered a re-organization, any transactions which selected decoys belonging to
|
||||||
|
/// recent blocks would become invalidated. Accordingly, transactions must use decoys which are
|
||||||
|
/// presumed to not be invalidated in the future. If wallets only selected n-block-old outputs as
|
||||||
|
/// decoys, then any ring member within the past n blocks would have to be the real spend.
|
||||||
|
/// Preventing this at the consensus layer ensures privacy and integrity.
|
||||||
pub const DEFAULT_LOCK_WINDOW: usize = 10;
|
pub const DEFAULT_LOCK_WINDOW: usize = 10;
|
||||||
|
|
||||||
|
/// The minimum amount of blocks a coinbase output is locked for.
|
||||||
pub const COINBASE_LOCK_WINDOW: usize = 60;
|
pub const COINBASE_LOCK_WINDOW: usize = 60;
|
||||||
|
|
||||||
|
/// Monero's block time target, in seconds.
|
||||||
pub const BLOCK_TIME: usize = 120;
|
pub const BLOCK_TIME: usize = 120;
|
||||||
|
|||||||
@@ -10,29 +10,33 @@ use curve25519_dalek::{EdwardsPoint, Scalar};
|
|||||||
use crate::{io::*, generators::hash_to_point, primitives::keccak256_to_scalar};
|
use crate::{io::*, generators::hash_to_point, primitives::keccak256_to_scalar};
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
||||||
pub struct Signature {
|
struct Signature {
|
||||||
c: Scalar,
|
c: Scalar,
|
||||||
r: Scalar,
|
s: Scalar,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Signature {
|
impl Signature {
|
||||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
write_scalar(&self.c, w)?;
|
write_scalar(&self.c, w)?;
|
||||||
write_scalar(&self.r, w)?;
|
write_scalar(&self.s, w)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read<R: Read>(r: &mut R) -> io::Result<Signature> {
|
fn read<R: Read>(r: &mut R) -> io::Result<Signature> {
|
||||||
Ok(Signature { c: read_scalar(r)?, r: read_scalar(r)? })
|
Ok(Signature { c: read_scalar(r)?, s: read_scalar(r)? })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A ring signature.
|
||||||
|
///
|
||||||
|
/// This was used by the original Cryptonote transaction protocol and was deprecated with RingCT.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
||||||
pub struct RingSignature {
|
pub struct RingSignature {
|
||||||
sigs: Vec<Signature>,
|
sigs: Vec<Signature>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RingSignature {
|
impl RingSignature {
|
||||||
|
/// Write the RingSignature.
|
||||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
for sig in &self.sigs {
|
for sig in &self.sigs {
|
||||||
sig.write(w)?;
|
sig.write(w)?;
|
||||||
@@ -40,31 +44,49 @@ impl RingSignature {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a RingSignature.
|
||||||
pub fn read<R: Read>(members: usize, r: &mut R) -> io::Result<RingSignature> {
|
pub fn read<R: Read>(members: usize, r: &mut R) -> io::Result<RingSignature> {
|
||||||
Ok(RingSignature { sigs: read_raw_vec(Signature::read, members, r)? })
|
Ok(RingSignature { sigs: read_raw_vec(Signature::read, members, r)? })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verify the ring signature.
|
||||||
pub fn verify(&self, msg: &[u8; 32], ring: &[EdwardsPoint], key_image: &EdwardsPoint) -> bool {
|
pub fn verify(&self, msg: &[u8; 32], ring: &[EdwardsPoint], key_image: &EdwardsPoint) -> bool {
|
||||||
if ring.len() != self.sigs.len() {
|
if ring.len() != self.sigs.len() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut buf = Vec::with_capacity(32 + (32 * 2 * ring.len()));
|
let mut buf = Vec::with_capacity(32 + (2 * 32 * ring.len()));
|
||||||
buf.extend_from_slice(msg);
|
buf.extend_from_slice(msg);
|
||||||
|
|
||||||
let mut sum = Scalar::ZERO;
|
let mut sum = Scalar::ZERO;
|
||||||
|
|
||||||
for (ring_member, sig) in ring.iter().zip(&self.sigs) {
|
for (ring_member, sig) in ring.iter().zip(&self.sigs) {
|
||||||
|
/*
|
||||||
|
The traditional Schnorr signature is:
|
||||||
|
r = sample()
|
||||||
|
c = H(r G || m)
|
||||||
|
s = r - c x
|
||||||
|
Verified as:
|
||||||
|
s G + c A == R
|
||||||
|
|
||||||
|
Each ring member here performs a dual-Schnorr signature for:
|
||||||
|
s G + c A
|
||||||
|
s HtP(A) + c K
|
||||||
|
Where the transcript is pushed both these values, r G, r HtP(A) for the real spend.
|
||||||
|
This also serves as a DLEq proof between the key and the key image.
|
||||||
|
|
||||||
|
Checking sum(c) == H(transcript) acts a disjunction, where any one of the `c`s can be
|
||||||
|
modified to cause the intended sum, if and only if a corresponding `s` value is known.
|
||||||
|
*/
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
let Li = EdwardsPoint::vartime_double_scalar_mul_basepoint(&sig.c, ring_member, &sig.r);
|
let Li = EdwardsPoint::vartime_double_scalar_mul_basepoint(&sig.c, ring_member, &sig.s);
|
||||||
buf.extend_from_slice(Li.compress().as_bytes());
|
buf.extend_from_slice(Li.compress().as_bytes());
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
let Ri = (sig.r * hash_to_point(ring_member.compress().to_bytes())) + (sig.c * key_image);
|
let Ri = (sig.s * hash_to_point(ring_member.compress().to_bytes())) + (sig.c * key_image);
|
||||||
buf.extend_from_slice(Ri.compress().as_bytes());
|
buf.extend_from_slice(Ri.compress().as_bytes());
|
||||||
|
|
||||||
sum += sig.c;
|
sum += sig.c;
|
||||||
}
|
}
|
||||||
|
|
||||||
sum == keccak256_to_scalar(buf)
|
sum == keccak256_to_scalar(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,6 @@ use crate::{
|
|||||||
ringct::{mlsag::Mlsag, clsag::Clsag, borromean::BorromeanRange, bulletproofs::Bulletproof},
|
ringct::{mlsag::Mlsag, clsag::Clsag, borromean::BorromeanRange, bulletproofs::Bulletproof},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Generate a key image for a given key. Defined as `x * hash_to_point(xG)`.
|
|
||||||
pub fn generate_key_image(secret: &Zeroizing<Scalar>) -> EdwardsPoint {
|
|
||||||
hash_to_point((ED25519_BASEPOINT_TABLE * secret.deref()).compress().to_bytes()) * secret.deref()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An encrypted amount.
|
/// An encrypted amount.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum EncryptedAmount {
|
pub enum EncryptedAmount {
|
||||||
@@ -58,25 +53,35 @@ impl EncryptedAmount {
|
|||||||
pub enum RctType {
|
pub enum RctType {
|
||||||
/// No RCT proofs.
|
/// No RCT proofs.
|
||||||
Null,
|
Null,
|
||||||
/// One MLSAG for multiple inputs and Borromean range proofs (RCTTypeFull).
|
/// One MLSAG for multiple inputs and Borromean range proofs.
|
||||||
|
///
|
||||||
|
/// This lines up with RCTTypeFull.
|
||||||
MlsagAggregate,
|
MlsagAggregate,
|
||||||
// One MLSAG for each input and a Borromean range proof (RCTTypeSimple).
|
// One MLSAG for each input and a Borromean range proof.
|
||||||
|
///
|
||||||
|
/// This lines up with RCTTypeSimple.
|
||||||
MlsagIndividual,
|
MlsagIndividual,
|
||||||
// One MLSAG for each input and a Bulletproof (RCTTypeBulletproof).
|
// One MLSAG for each input and a Bulletproof.
|
||||||
|
///
|
||||||
|
/// This lines up with RCTTypeBulletproof.
|
||||||
Bulletproofs,
|
Bulletproofs,
|
||||||
/// One MLSAG for each input and a Bulletproof, yet starting to use EncryptedAmount::Compact
|
/// One MLSAG for each input and a Bulletproof, yet using EncryptedAmount::Compact.
|
||||||
/// (RCTTypeBulletproof2).
|
///
|
||||||
|
/// This lines up with RCTTypeBulletproof2.
|
||||||
BulletproofsCompactAmount,
|
BulletproofsCompactAmount,
|
||||||
/// One CLSAG for each input and a Bulletproof (RCTTypeCLSAG).
|
/// One CLSAG for each input and a Bulletproof.
|
||||||
|
///
|
||||||
|
/// This lines up with RCTTypeCLSAG.
|
||||||
Clsag,
|
Clsag,
|
||||||
/// One CLSAG for each input and a Bulletproof+ (RCTTypeBulletproofPlus).
|
/// One CLSAG for each input and a Bulletproof+.
|
||||||
|
///
|
||||||
|
/// This lines up with RCTTypeBulletproofPlus.
|
||||||
BulletproofsPlus,
|
BulletproofsPlus,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RctType {
|
impl From<RctType> for u8 {
|
||||||
/// Convert the RctType to its byte representation.
|
fn from(kind: RctType) -> u8 {
|
||||||
pub fn to_byte(self) -> u8 {
|
match kind {
|
||||||
match self {
|
|
||||||
RctType::Null => 0,
|
RctType::Null => 0,
|
||||||
RctType::MlsagAggregate => 1,
|
RctType::MlsagAggregate => 1,
|
||||||
RctType::MlsagIndividual => 2,
|
RctType::MlsagIndividual => 2,
|
||||||
@@ -86,10 +91,12 @@ impl RctType {
|
|||||||
RctType::BulletproofsPlus => 6,
|
RctType::BulletproofsPlus => 6,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert the RctType from its byte representation.
|
impl TryFrom<u8> for RctType {
|
||||||
pub fn from_byte(byte: u8) -> Option<Self> {
|
type Error = ();
|
||||||
Some(match byte {
|
fn try_from(byte: u8) -> Result<Self, ()> {
|
||||||
|
Ok(match byte {
|
||||||
0 => RctType::Null,
|
0 => RctType::Null,
|
||||||
1 => RctType::MlsagAggregate,
|
1 => RctType::MlsagAggregate,
|
||||||
2 => RctType::MlsagIndividual,
|
2 => RctType::MlsagIndividual,
|
||||||
@@ -97,10 +104,12 @@ impl RctType {
|
|||||||
4 => RctType::BulletproofsCompactAmount,
|
4 => RctType::BulletproofsCompactAmount,
|
||||||
5 => RctType::Clsag,
|
5 => RctType::Clsag,
|
||||||
6 => RctType::BulletproofsPlus,
|
6 => RctType::BulletproofsPlus,
|
||||||
_ => None?,
|
_ => Err(())?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RctType {
|
||||||
/// Returns true if this RctType uses compact encrypted amounts, false otherwise.
|
/// Returns true if this RctType uses compact encrypted amounts, false otherwise.
|
||||||
pub fn compact_encrypted_amounts(&self) -> bool {
|
pub fn compact_encrypted_amounts(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
@@ -125,6 +134,8 @@ pub struct RctBase {
|
|||||||
/// The fee used by this transaction.
|
/// The fee used by this transaction.
|
||||||
pub fee: u64,
|
pub fee: u64,
|
||||||
/// The re-randomized amount commitments used within inputs.
|
/// The re-randomized amount commitments used within inputs.
|
||||||
|
///
|
||||||
|
/// This field was deprecated and is empty for modern RctTypes.
|
||||||
pub pseudo_outs: Vec<EdwardsPoint>,
|
pub pseudo_outs: Vec<EdwardsPoint>,
|
||||||
/// The encrypted amounts for the recipient to decrypt.
|
/// The encrypted amounts for the recipient to decrypt.
|
||||||
pub encrypted_amounts: Vec<EncryptedAmount>,
|
pub encrypted_amounts: Vec<EncryptedAmount>,
|
||||||
@@ -133,14 +144,15 @@ pub struct RctBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RctBase {
|
impl RctBase {
|
||||||
|
/// The weight of this RctBase as relevant for fees.
|
||||||
pub fn fee_weight(outputs: usize, fee: u64) -> usize {
|
pub fn fee_weight(outputs: usize, fee: u64) -> usize {
|
||||||
// 1 byte for the RCT signature type
|
// 1 byte for the RCT signature type
|
||||||
1 + (outputs * (8 + 32)) + varint_len(fee)
|
1 + (outputs * (8 + 32)) + varint_len(fee)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the RctBase to a writer.
|
/// Write the RctBase.
|
||||||
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
|
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
|
||||||
w.write_all(&[rct_type.to_byte()])?;
|
w.write_all(&[u8::from(rct_type)])?;
|
||||||
match rct_type {
|
match rct_type {
|
||||||
RctType::Null => Ok(()),
|
RctType::Null => Ok(()),
|
||||||
_ => {
|
_ => {
|
||||||
@@ -156,10 +168,10 @@ impl RctBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read a RctBase from a writer.
|
/// Read a RctBase.
|
||||||
pub fn read<R: Read>(inputs: usize, outputs: usize, r: &mut R) -> io::Result<(RctBase, RctType)> {
|
pub fn read<R: Read>(inputs: usize, outputs: usize, r: &mut R) -> io::Result<(RctBase, RctType)> {
|
||||||
let rct_type =
|
let rct_type =
|
||||||
RctType::from_byte(read_byte(r)?).ok_or_else(|| io::Error::other("invalid RCT type"))?;
|
RctType::try_from(read_byte(r)?).map_err(|_| io::Error::other("invalid RCT type"))?;
|
||||||
|
|
||||||
match rct_type {
|
match rct_type {
|
||||||
RctType::Null | RctType::MlsagAggregate | RctType::MlsagIndividual => {}
|
RctType::Null | RctType::MlsagAggregate | RctType::MlsagIndividual => {}
|
||||||
@@ -202,30 +214,27 @@ impl RctBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The prunable part of the RingCT data.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum RctPrunable {
|
pub enum RctPrunable {
|
||||||
|
/// Null.
|
||||||
Null,
|
Null,
|
||||||
AggregateMlsagBorromean {
|
/// An aggregate MLSAG with Borromean range proofs.
|
||||||
borromean: Vec<BorromeanRange>,
|
AggregateMlsagBorromean { borromean: Vec<BorromeanRange>, mlsag: Mlsag },
|
||||||
mlsag: Mlsag,
|
/// MLSAGs with Borromean range proofs.
|
||||||
},
|
MlsagBorromean { borromean: Vec<BorromeanRange>, mlsags: Vec<Mlsag> },
|
||||||
MlsagBorromean {
|
/// MLSAGs with Bulletproofs.
|
||||||
borromean: Vec<BorromeanRange>,
|
|
||||||
mlsags: Vec<Mlsag>,
|
|
||||||
},
|
|
||||||
MlsagBulletproofs {
|
MlsagBulletproofs {
|
||||||
bulletproofs: Bulletproof,
|
bulletproofs: Bulletproof,
|
||||||
mlsags: Vec<Mlsag>,
|
mlsags: Vec<Mlsag>,
|
||||||
pseudo_outs: Vec<EdwardsPoint>,
|
pseudo_outs: Vec<EdwardsPoint>,
|
||||||
},
|
},
|
||||||
Clsag {
|
/// CLSAGs with Bulletproofs(+).
|
||||||
bulletproofs: Bulletproof,
|
Clsag { bulletproofs: Bulletproof, clsags: Vec<Clsag>, pseudo_outs: Vec<EdwardsPoint> },
|
||||||
clsags: Vec<Clsag>,
|
|
||||||
pseudo_outs: Vec<EdwardsPoint>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RctPrunable {
|
impl RctPrunable {
|
||||||
|
/// The weight of this RctPrunable as relevant for fees.
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
pub fn fee_weight(bp_plus: bool, ring_len: usize, inputs: usize, outputs: usize) -> usize {
|
pub fn fee_weight(bp_plus: bool, ring_len: usize, inputs: usize, outputs: usize) -> usize {
|
||||||
// 1 byte for number of BPs (technically a VarInt, yet there's always just zero or one)
|
// 1 byte for number of BPs (technically a VarInt, yet there's always just zero or one)
|
||||||
@@ -235,6 +244,7 @@ impl RctPrunable {
|
|||||||
(inputs * (Clsag::fee_weight(ring_len) + 32))
|
(inputs * (Clsag::fee_weight(ring_len) + 32))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write the RctPrunable.
|
||||||
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
|
pub fn write<W: Write>(&self, w: &mut W, rct_type: RctType) -> io::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
RctPrunable::Null => Ok(()),
|
RctPrunable::Null => Ok(()),
|
||||||
@@ -267,12 +277,14 @@ impl RctPrunable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Serialize the RctPrunable to a Vec<u8>.
|
||||||
pub fn serialize(&self, rct_type: RctType) -> Vec<u8> {
|
pub fn serialize(&self, rct_type: RctType) -> Vec<u8> {
|
||||||
let mut serialized = vec![];
|
let mut serialized = vec![];
|
||||||
self.write(&mut serialized, rct_type).unwrap();
|
self.write(&mut serialized, rct_type).unwrap();
|
||||||
serialized
|
serialized
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a RctPrunable.
|
||||||
pub fn read<R: Read>(
|
pub fn read<R: Read>(
|
||||||
rct_type: RctType,
|
rct_type: RctType,
|
||||||
ring_length: usize,
|
ring_length: usize,
|
||||||
@@ -280,17 +292,6 @@ impl RctPrunable {
|
|||||||
outputs: usize,
|
outputs: usize,
|
||||||
r: &mut R,
|
r: &mut R,
|
||||||
) -> io::Result<RctPrunable> {
|
) -> io::Result<RctPrunable> {
|
||||||
// While we generally don't bother with misc consensus checks, this affects the safety of
|
|
||||||
// the below defined rct_type function
|
|
||||||
// The exact line preventing zero-input transactions is:
|
|
||||||
// https://github.com/monero-project/monero/blob/00fd416a99686f0956361d1cd0337fe56e58d4a7/
|
|
||||||
// src/ringct/rctSigs.cpp#L609
|
|
||||||
// And then for RctNull, that's only allowed for miner TXs which require one input of
|
|
||||||
// Input::Gen
|
|
||||||
if inputs == 0 {
|
|
||||||
Err(io::Error::other("transaction had no inputs"))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(match rct_type {
|
Ok(match rct_type {
|
||||||
RctType::Null => RctPrunable::Null,
|
RctType::Null => RctPrunable::Null,
|
||||||
RctType::MlsagAggregate => RctPrunable::AggregateMlsagBorromean {
|
RctType::MlsagAggregate => RctPrunable::AggregateMlsagBorromean {
|
||||||
@@ -333,16 +334,21 @@ impl RctPrunable {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn signature_write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
/// Write the RctPrunable as necessary for signing the signature.
|
||||||
match self {
|
///
|
||||||
RctPrunable::Null => panic!("Serializing RctPrunable::Null for a signature"),
|
/// This function will return None if the object is `RctPrunable::Null` (and has no
|
||||||
|
/// representation here).
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn signature_write<W: Write>(&self, w: &mut W) -> Option<io::Result<()>> {
|
||||||
|
Some(match self {
|
||||||
|
RctPrunable::Null => None?,
|
||||||
RctPrunable::AggregateMlsagBorromean { borromean, .. } |
|
RctPrunable::AggregateMlsagBorromean { borromean, .. } |
|
||||||
RctPrunable::MlsagBorromean { borromean, .. } => {
|
RctPrunable::MlsagBorromean { borromean, .. } => {
|
||||||
borromean.iter().try_for_each(|rs| rs.write(w))
|
borromean.iter().try_for_each(|rs| rs.write(w))
|
||||||
}
|
}
|
||||||
RctPrunable::MlsagBulletproofs { bulletproofs, .. } |
|
RctPrunable::MlsagBulletproofs { bulletproofs, .. } |
|
||||||
RctPrunable::Clsag { bulletproofs, .. } => bulletproofs.signature_write(w),
|
RctPrunable::Clsag { bulletproofs, .. } => bulletproofs.signature_write(w),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,22 +360,18 @@ pub struct RctSignatures {
|
|||||||
|
|
||||||
impl RctSignatures {
|
impl RctSignatures {
|
||||||
/// RctType for a given RctSignatures struct.
|
/// RctType for a given RctSignatures struct.
|
||||||
pub fn rct_type(&self) -> RctType {
|
///
|
||||||
match &self.prunable {
|
/// This is only guaranteed to return the type for a well-formed RctSignatures. For a malformed
|
||||||
|
/// RctSignatures, this will return either the presumed RctType (with no guarantee of compliance
|
||||||
|
/// with that type) or None.
|
||||||
|
#[must_use]
|
||||||
|
pub fn rct_type(&self) -> Option<RctType> {
|
||||||
|
Some(match &self.prunable {
|
||||||
RctPrunable::Null => RctType::Null,
|
RctPrunable::Null => RctType::Null,
|
||||||
RctPrunable::AggregateMlsagBorromean { .. } => RctType::MlsagAggregate,
|
RctPrunable::AggregateMlsagBorromean { .. } => RctType::MlsagAggregate,
|
||||||
RctPrunable::MlsagBorromean { .. } => RctType::MlsagIndividual,
|
RctPrunable::MlsagBorromean { .. } => RctType::MlsagIndividual,
|
||||||
// RctBase ensures there's at least one output, making the following
|
|
||||||
// inferences guaranteed/expects impossible on any valid RctSignatures
|
|
||||||
RctPrunable::MlsagBulletproofs { .. } => {
|
RctPrunable::MlsagBulletproofs { .. } => {
|
||||||
if matches!(
|
if matches!(self.base.encrypted_amounts.first()?, EncryptedAmount::Original { .. }) {
|
||||||
self
|
|
||||||
.base
|
|
||||||
.encrypted_amounts
|
|
||||||
.first()
|
|
||||||
.expect("MLSAG with Bulletproofs didn't have any outputs"),
|
|
||||||
EncryptedAmount::Original { .. }
|
|
||||||
) {
|
|
||||||
RctType::Bulletproofs
|
RctType::Bulletproofs
|
||||||
} else {
|
} else {
|
||||||
RctType::BulletproofsCompactAmount
|
RctType::BulletproofsCompactAmount
|
||||||
@@ -382,9 +384,10 @@ impl RctSignatures {
|
|||||||
RctType::BulletproofsPlus
|
RctType::BulletproofsPlus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The weight of this RctSignatures as relevant for fees.
|
||||||
pub fn fee_weight(
|
pub fn fee_weight(
|
||||||
bp_plus: bool,
|
bp_plus: bool,
|
||||||
ring_len: usize,
|
ring_len: usize,
|
||||||
@@ -395,16 +398,20 @@ impl RctSignatures {
|
|||||||
RctBase::fee_weight(outputs, fee) + RctPrunable::fee_weight(bp_plus, ring_len, inputs, outputs)
|
RctBase::fee_weight(outputs, fee) + RctPrunable::fee_weight(bp_plus, ring_len, inputs, outputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
#[must_use]
|
||||||
let rct_type = self.rct_type();
|
pub fn write<W: Write>(&self, w: &mut W) -> Option<io::Result<()>> {
|
||||||
self.base.write(w, rct_type)?;
|
let rct_type = self.rct_type()?;
|
||||||
self.prunable.write(w, rct_type)
|
if let Err(e) = self.base.write(w, rct_type) {
|
||||||
|
return Some(Err(e));
|
||||||
|
};
|
||||||
|
Some(self.prunable.write(w, rct_type))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
#[must_use]
|
||||||
|
pub fn serialize(&self) -> Option<Vec<u8>> {
|
||||||
let mut serialized = vec![];
|
let mut serialized = vec![];
|
||||||
self.write(&mut serialized).unwrap();
|
self.write(&mut serialized)?.unwrap();
|
||||||
serialized
|
Some(serialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read<R: Read>(
|
pub fn read<R: Read>(
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ impl Timelock {
|
|||||||
if raw == 0 {
|
if raw == 0 {
|
||||||
Timelock::None
|
Timelock::None
|
||||||
} else if raw < 500_000_000 {
|
} else if raw < 500_000_000 {
|
||||||
|
// TODO: This is trivial to have panic
|
||||||
Timelock::Block(usize::try_from(raw).unwrap())
|
Timelock::Block(usize::try_from(raw).unwrap())
|
||||||
} else {
|
} else {
|
||||||
Timelock::Time(raw)
|
Timelock::Time(raw)
|
||||||
@@ -150,6 +151,7 @@ impl Timelock {
|
|||||||
write_varint(
|
write_varint(
|
||||||
&match self {
|
&match self {
|
||||||
Timelock::None => 0,
|
Timelock::None => 0,
|
||||||
|
// TODO: Check this unwrap
|
||||||
Timelock::Block(block) => (*block).try_into().unwrap(),
|
Timelock::Block(block) => (*block).try_into().unwrap(),
|
||||||
Timelock::Time(time) => *time,
|
Timelock::Time(time) => *time,
|
||||||
},
|
},
|
||||||
@@ -268,24 +270,33 @@ impl Transaction {
|
|||||||
RctSignatures::fee_weight(bp_plus, ring_len, decoy_weights.len(), outputs, fee)
|
RctSignatures::fee_weight(bp_plus, ring_len, decoy_weights.len(), outputs, fee)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
#[must_use]
|
||||||
self.prefix.write(w)?;
|
pub fn write<W: Write>(&self, w: &mut W) -> Option<io::Result<()>> {
|
||||||
|
if let Err(e) = self.prefix.write(w) {
|
||||||
|
return Some(Err(e));
|
||||||
|
};
|
||||||
if self.prefix.version == 1 {
|
if self.prefix.version == 1 {
|
||||||
for ring_sig in &self.signatures {
|
for ring_sig in &self.signatures {
|
||||||
ring_sig.write(w)?;
|
if let Err(e) = ring_sig.write(w) {
|
||||||
|
return Some(Err(e));
|
||||||
|
};
|
||||||
}
|
}
|
||||||
Ok(())
|
Some(Ok(()))
|
||||||
} else if self.prefix.version == 2 {
|
} else if self.prefix.version == 2 {
|
||||||
self.rct_signatures.write(w)
|
if let Err(e) = self.rct_signatures.write(w)? {
|
||||||
|
return Some(Err(e));
|
||||||
|
}
|
||||||
|
Some(Ok(()))
|
||||||
} else {
|
} else {
|
||||||
panic!("Serializing a transaction with an unknown version");
|
Some(Err(io::Error::other("transaction had an unknown version")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
#[must_use]
|
||||||
|
pub fn serialize(&self) -> Option<Vec<u8>> {
|
||||||
let mut res = Vec::with_capacity(2048);
|
let mut res = Vec::with_capacity(2048);
|
||||||
self.write(&mut res).unwrap();
|
self.write(&mut res)?.unwrap();
|
||||||
res
|
Some(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read<R: Read>(r: &mut R) -> io::Result<Transaction> {
|
pub fn read<R: Read>(r: &mut R) -> io::Result<Transaction> {
|
||||||
@@ -352,36 +363,39 @@ impl Transaction {
|
|||||||
Ok(Transaction { prefix, signatures, rct_signatures })
|
Ok(Transaction { prefix, signatures, rct_signatures })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hash(&self) -> [u8; 32] {
|
#[must_use]
|
||||||
|
pub fn hash(&self) -> Option<[u8; 32]> {
|
||||||
let mut buf = Vec::with_capacity(2048);
|
let mut buf = Vec::with_capacity(2048);
|
||||||
if self.prefix.version == 1 {
|
if self.prefix.version == 1 {
|
||||||
self.write(&mut buf).unwrap();
|
self.write(&mut buf)?.unwrap();
|
||||||
keccak256(buf)
|
Some(keccak256(buf))
|
||||||
} else {
|
} else {
|
||||||
let mut hashes = Vec::with_capacity(96);
|
let mut hashes = Vec::with_capacity(96);
|
||||||
|
|
||||||
hashes.extend(self.prefix.hash());
|
hashes.extend(self.prefix.hash());
|
||||||
|
|
||||||
self.rct_signatures.base.write(&mut buf, self.rct_signatures.rct_type()).unwrap();
|
let rct_type = self.rct_signatures.rct_type()?;
|
||||||
|
self.rct_signatures.base.write(&mut buf, rct_type).unwrap();
|
||||||
hashes.extend(keccak256(&buf));
|
hashes.extend(keccak256(&buf));
|
||||||
buf.clear();
|
buf.clear();
|
||||||
|
|
||||||
hashes.extend(&match self.rct_signatures.prunable {
|
hashes.extend(&match self.rct_signatures.prunable {
|
||||||
RctPrunable::Null => [0; 32],
|
RctPrunable::Null => [0; 32],
|
||||||
_ => {
|
_ => {
|
||||||
self.rct_signatures.prunable.write(&mut buf, self.rct_signatures.rct_type()).unwrap();
|
self.rct_signatures.prunable.write(&mut buf, rct_type).unwrap();
|
||||||
keccak256(buf)
|
keccak256(buf)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
keccak256(hashes)
|
Some(keccak256(hashes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the hash of this transaction as needed for signing it.
|
/// Calculate the hash of this transaction as needed for signing it.
|
||||||
pub fn signature_hash(&self) -> [u8; 32] {
|
#[must_use]
|
||||||
|
pub fn signature_hash(&self) -> Option<[u8; 32]> {
|
||||||
if self.prefix.version == 1 {
|
if self.prefix.version == 1 {
|
||||||
return self.prefix.hash();
|
return Some(self.prefix.hash());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut buf = Vec::with_capacity(2048);
|
let mut buf = Vec::with_capacity(2048);
|
||||||
@@ -389,18 +403,19 @@ impl Transaction {
|
|||||||
|
|
||||||
sig_hash.extend(self.prefix.hash());
|
sig_hash.extend(self.prefix.hash());
|
||||||
|
|
||||||
self.rct_signatures.base.write(&mut buf, self.rct_signatures.rct_type()).unwrap();
|
self.rct_signatures.base.write(&mut buf, self.rct_signatures.rct_type()?).unwrap();
|
||||||
sig_hash.extend(keccak256(&buf));
|
sig_hash.extend(keccak256(&buf));
|
||||||
buf.clear();
|
buf.clear();
|
||||||
|
|
||||||
self.rct_signatures.prunable.signature_write(&mut buf).unwrap();
|
self.rct_signatures.prunable.signature_write(&mut buf)?.unwrap();
|
||||||
sig_hash.extend(keccak256(buf));
|
sig_hash.extend(keccak256(buf));
|
||||||
|
|
||||||
keccak256(sig_hash)
|
Some(keccak256(sig_hash))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_rct_bulletproof(&self) -> bool {
|
fn is_rct_bulletproof(&self) -> bool {
|
||||||
match &self.rct_signatures.rct_type() {
|
let Some(rct_type) = self.rct_signatures.rct_type() else { return false };
|
||||||
|
match rct_type {
|
||||||
RctType::Bulletproofs | RctType::BulletproofsCompactAmount | RctType::Clsag => true,
|
RctType::Bulletproofs | RctType::BulletproofsCompactAmount | RctType::Clsag => true,
|
||||||
RctType::Null |
|
RctType::Null |
|
||||||
RctType::MlsagAggregate |
|
RctType::MlsagAggregate |
|
||||||
@@ -410,7 +425,8 @@ impl Transaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_rct_bulletproof_plus(&self) -> bool {
|
fn is_rct_bulletproof_plus(&self) -> bool {
|
||||||
match &self.rct_signatures.rct_type() {
|
let Some(rct_type) = self.rct_signatures.rct_type() else { return false };
|
||||||
|
match rct_type {
|
||||||
RctType::BulletproofsPlus => true,
|
RctType::BulletproofsPlus => true,
|
||||||
RctType::Null |
|
RctType::Null |
|
||||||
RctType::MlsagAggregate |
|
RctType::MlsagAggregate |
|
||||||
@@ -422,15 +438,15 @@ impl Transaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the transaction's weight.
|
/// Calculate the transaction's weight.
|
||||||
pub fn weight(&self) -> usize {
|
pub fn weight(&self) -> Option<usize> {
|
||||||
let blob_size = self.serialize().len();
|
let blob_size = self.serialize()?.len();
|
||||||
|
|
||||||
let bp = self.is_rct_bulletproof();
|
let bp = self.is_rct_bulletproof();
|
||||||
let bp_plus = self.is_rct_bulletproof_plus();
|
let bp_plus = self.is_rct_bulletproof_plus();
|
||||||
if !(bp || bp_plus) {
|
Some(if !(bp || bp_plus) {
|
||||||
blob_size
|
blob_size
|
||||||
} else {
|
} else {
|
||||||
blob_size + Bulletproof::calculate_bp_clawback(bp_plus, self.prefix.outputs.len()).0
|
blob_size + Bulletproof::calculate_bp_clawback(bp_plus, self.prefix.outputs.len()).0
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
49
coins/monero/wallet/README0.md
Normal file
49
coins/monero/wallet/README0.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# monero-serai
|
||||||
|
|
||||||
|
A modern Monero transaction library intended for usage in wallets. It prides
|
||||||
|
itself on accuracy, correctness, and removing common pit falls developers may
|
||||||
|
face.
|
||||||
|
|
||||||
|
monero-serai also offers the following features:
|
||||||
|
|
||||||
|
- Featured Addresses
|
||||||
|
- A FROST-based multisig orders of magnitude more performant than Monero's
|
||||||
|
|
||||||
|
### Purpose and support
|
||||||
|
|
||||||
|
monero-serai was written for Serai, a decentralized exchange aiming to support
|
||||||
|
Monero. Despite this, monero-serai is intended to be a widely usable library,
|
||||||
|
accurate to Monero. monero-serai guarantees the functionality needed for Serai,
|
||||||
|
yet will not deprive functionality from other users.
|
||||||
|
|
||||||
|
Various legacy transaction formats are not currently implemented, yet we are
|
||||||
|
willing to add support for them. There aren't active development efforts around
|
||||||
|
them however.
|
||||||
|
|
||||||
|
### Caveats
|
||||||
|
|
||||||
|
This library DOES attempt to do the following:
|
||||||
|
|
||||||
|
- Create on-chain transactions identical to how wallet2 would (unless told not
|
||||||
|
to)
|
||||||
|
- Not be detectable as monero-serai when scanning outputs
|
||||||
|
- Not reveal spent outputs to the connected RPC node
|
||||||
|
|
||||||
|
This library DOES NOT attempt to do the following:
|
||||||
|
|
||||||
|
- Have identical RPC behavior when creating transactions
|
||||||
|
- Be a wallet
|
||||||
|
|
||||||
|
This means that monero-serai shouldn't be fingerprintable on-chain. It also
|
||||||
|
shouldn't be fingerprintable if a targeted attack occurs to detect if the
|
||||||
|
receiving wallet is monero-serai or wallet2. It also should be generally safe
|
||||||
|
for usage with remote nodes.
|
||||||
|
|
||||||
|
It won't hide from remote nodes it's monero-serai however, potentially
|
||||||
|
allowing a remote node to profile you. The implications of this are left to the
|
||||||
|
user to consider.
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -27,7 +27,7 @@ use monero_serai::{
|
|||||||
io::*,
|
io::*,
|
||||||
primitives::{Commitment, keccak256},
|
primitives::{Commitment, keccak256},
|
||||||
ringct::{
|
ringct::{
|
||||||
generate_key_image,
|
hash_to_point,
|
||||||
clsag::{ClsagError, ClsagContext, Clsag},
|
clsag::{ClsagError, ClsagContext, Clsag},
|
||||||
bulletproofs::{MAX_COMMITMENTS, Bulletproof},
|
bulletproofs::{MAX_COMMITMENTS, Bulletproof},
|
||||||
RctBase, RctPrunable, RctSignatures,
|
RctBase, RctPrunable, RctSignatures,
|
||||||
@@ -53,6 +53,11 @@ mod multisig;
|
|||||||
pub use multisig::TransactionMachine;
|
pub use multisig::TransactionMachine;
|
||||||
use monero_serai::ringct::EncryptedAmount;
|
use monero_serai::ringct::EncryptedAmount;
|
||||||
|
|
||||||
|
/// Generate a key image for a given key. Defined as `x * hash_to_point(xG)`.
|
||||||
|
pub fn generate_key_image(secret: &Zeroizing<Scalar>) -> EdwardsPoint {
|
||||||
|
hash_to_point((ED25519_BASEPOINT_TABLE * secret.deref()).compress().to_bytes()) * secret.deref()
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
struct SendOutput {
|
struct SendOutput {
|
||||||
|
|||||||
Reference in New Issue
Block a user