Move RingCT code to a deciated folder

Should help keep things ordered as more RingCT code is added.
This commit is contained in:
Luke Parker
2022-05-22 02:24:24 -04:00
parent 790e89902a
commit cc65b6e055
9 changed files with 141 additions and 132 deletions

View File

@@ -0,0 +1,131 @@
#![allow(non_snake_case)]
use rand_core::{RngCore, CryptoRng};
use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint};
use crate::{Commitment, wallet::TransactionError, serialize::*};
#[derive(Clone, Debug)]
pub struct Bulletproofs {
pub A: EdwardsPoint,
pub S: EdwardsPoint,
pub T1: EdwardsPoint,
pub T2: EdwardsPoint,
pub taux: Scalar,
pub mu: Scalar,
pub L: Vec<EdwardsPoint>,
pub R: Vec<EdwardsPoint>,
pub a: Scalar,
pub b: Scalar,
pub t: Scalar
}
impl Bulletproofs {
pub fn new<R: RngCore + CryptoRng>(rng: &mut R, outputs: &[Commitment]) -> Result<Bulletproofs, TransactionError> {
if outputs.len() > 16 {
return Err(TransactionError::TooManyOutputs)?;
}
let mut seed = [0; 32];
rng.fill_bytes(&mut seed);
let masks = outputs.iter().map(|commitment| commitment.mask.to_bytes()).collect::<Vec<_>>();
let amounts = outputs.iter().map(|commitment| commitment.amount).collect::<Vec<_>>();
let res;
unsafe {
#[link(name = "wrapper")]
extern "C" {
fn free(ptr: *const u8);
fn c_generate_bp(seed: *const u8, len: u8, amounts: *const u64, masks: *const [u8; 32]) -> *const u8;
}
let ptr = c_generate_bp(seed.as_ptr(), outputs.len() as u8, amounts.as_ptr(), masks.as_ptr());
let len = ((ptr.read() as usize) << 8) + (ptr.add(1).read() as usize);
res = Bulletproofs::deserialize(
// Wrap in a cursor to provide a mutable Reader
&mut std::io::Cursor::new(std::slice::from_raw_parts(ptr.add(2), len))
).expect("Couldn't deserialize Bulletproofs from Monero");
free(ptr);
};
Ok(res)
}
pub fn verify<R: RngCore + CryptoRng>(&self, rng: &mut R, commitments: &[EdwardsPoint]) -> bool {
if commitments.len() > 16 {
return false;
}
let mut seed = [0; 32];
rng.fill_bytes(&mut seed);
let mut serialized = Vec::with_capacity((9 + (2 * self.L.len())) * 32);
self.serialize(&mut serialized).unwrap();
let commitments: Vec<[u8; 32]> = commitments.iter().map(
|commitment| (commitment * Scalar::from(8 as u8).invert()).compress().to_bytes()
).collect();
unsafe {
#[link(name = "wrapper")]
extern "C" {
fn c_verify_bp(
seed: *const u8,
serialized_len: usize,
serialized: *const u8,
commitments_len: u8,
commitments: *const [u8; 32]
) -> bool;
}
c_verify_bp(seed.as_ptr(), serialized.len(), serialized.as_ptr(), commitments.len() as u8, commitments.as_ptr())
}
}
fn serialize_core<
W: std::io::Write,
F: Fn(&[EdwardsPoint], &mut W) -> std::io::Result<()>
>(&self, w: &mut W, specific_write_vec: F) -> std::io::Result<()> {
write_point(&self.A, w)?;
write_point(&self.S, w)?;
write_point(&self.T1, w)?;
write_point(&self.T2, w)?;
write_scalar(&self.taux, w)?;
write_scalar(&self.mu, w)?;
specific_write_vec(&self.L, w)?;
specific_write_vec(&self.R, w)?;
write_scalar(&self.a, w)?;
write_scalar(&self.b, w)?;
write_scalar(&self.t, w)
}
pub fn signature_serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
self.serialize_core(w, |points, w| write_raw_vec(write_point, points, w))
}
pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
self.serialize_core(w, |points, w| write_vec(write_point, points, w))
}
pub fn deserialize<R: std::io::Read>(r: &mut R) -> std::io::Result<Bulletproofs> {
let bp = Bulletproofs {
A: read_point(r)?,
S: read_point(r)?,
T1: read_point(r)?,
T2: read_point(r)?,
taux: read_scalar(r)?,
mu: read_scalar(r)?,
L: read_vec(read_point, r)?,
R: read_vec(read_point, r)?,
a: read_scalar(r)?,
b: read_scalar(r)?,
t: read_scalar(r)?
};
if bp.L.len() != bp.R.len() {
Err(std::io::Error::new(std::io::ErrorKind::Other, "mismatched L/R len"))?;
}
Ok(bp)
}
}

View File

@@ -0,0 +1,353 @@
#![allow(non_snake_case)]
use lazy_static::lazy_static;
use thiserror::Error;
use rand_core::{RngCore, CryptoRng};
use curve25519_dalek::{
constants::ED25519_BASEPOINT_TABLE,
scalar::Scalar,
traits::VartimePrecomputedMultiscalarMul,
edwards::{EdwardsPoint, VartimeEdwardsPrecomputation}
};
use crate::{
Commitment,
wallet::decoys::Decoys,
random_scalar, hash_to_scalar, hash_to_point,
serialize::*
};
#[cfg(feature = "multisig")]
mod multisig;
#[cfg(feature = "multisig")]
pub use multisig::{ClsagDetails, ClsagMultisig};
lazy_static! {
static ref INV_EIGHT: Scalar = Scalar::from(8 as u8).invert();
}
#[derive(Error, Debug)]
pub enum ClsagError {
#[error("internal error ({0})")]
InternalError(String),
#[error("invalid ring member (member {0}, ring size {1})")]
InvalidRingMember(u8, u8),
#[error("invalid commitment")]
InvalidCommitment,
#[error("invalid D")]
InvalidD,
#[error("invalid s")]
InvalidS,
#[error("invalid c1")]
InvalidC1
}
#[derive(Clone, Debug)]
pub struct ClsagInput {
// The actual commitment for the true spend
pub commitment: Commitment,
// True spend index, offsets, and ring
pub decoys: Decoys
}
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".to_string()))?;
}
if decoys.i >= (n as u8) {
Err(ClsagError::InvalidRingMember(decoys.i, n as u8))?;
}
// Validate the commitment matches
if decoys.ring[usize::from(decoys.i)][1] != commitment.calculate() {
Err(ClsagError::InvalidCommitment)?;
}
Ok(ClsagInput { commitment, decoys })
}
}
enum Mode {
Sign(usize, EdwardsPoint, EdwardsPoint),
#[cfg(feature = "experimental")]
Verify(Scalar)
}
// Core of the CLSAG algorithm, applicable to both sign and verify with minimal differences
// Said differences are covered via the above Mode
fn core(
ring: &[[EdwardsPoint; 2]],
I: &EdwardsPoint,
pseudo_out: &EdwardsPoint,
msg: &[u8; 32],
D: &EdwardsPoint,
s: &[Scalar],
A_c1: Mode
) -> ((EdwardsPoint, Scalar, Scalar), Scalar) {
let n = ring.len();
let images_precomp = VartimeEdwardsPrecomputation::new([I, D]);
let D = D * *INV_EIGHT;
// Generate the transcript
// Instead of generating multiple, a single transcript is created and then edited as needed
let mut to_hash = vec![];
to_hash.reserve_exact(((2 * n) + 5) * 32);
const PREFIX: &[u8] = "CLSAG_".as_bytes();
const AGG_0: &[u8] = "CLSAG_agg_0".as_bytes();
const ROUND: &[u8] = "round".as_bytes();
to_hash.extend(AGG_0);
to_hash.extend([0; 32 - AGG_0.len()]);
let mut P = Vec::with_capacity(n);
for member in ring {
P.push(member[0]);
to_hash.extend(member[0].compress().to_bytes());
}
let mut C = Vec::with_capacity(n);
for member in ring {
C.push(member[1] - pseudo_out);
to_hash.extend(member[1].compress().to_bytes());
}
to_hash.extend(I.compress().to_bytes());
to_hash.extend(D.compress().to_bytes());
to_hash.extend(pseudo_out.compress().to_bytes());
// mu_P with agg_0
let mu_P = hash_to_scalar(&to_hash);
// mu_C with agg_1
to_hash[AGG_0.len() - 1] = '1' as u8;
let mu_C = hash_to_scalar(&to_hash);
// Truncate it for the round transcript, altering the DST as needed
to_hash.truncate(((2 * n) + 1) * 32);
for i in 0 .. ROUND.len() {
to_hash[PREFIX.len() + i] = ROUND[i] as u8;
}
// Unfortunately, it's I D pseudo_out instead of pseudo_out I D, meaning this needs to be
// truncated just to add it back
to_hash.extend(pseudo_out.compress().to_bytes());
to_hash.extend(msg);
// Configure the loop based on if we're signing or verifying
let start;
let end;
let mut c;
match A_c1 {
Mode::Sign(r, A, AH) => {
start = r + 1;
end = r + n;
to_hash.extend(A.compress().to_bytes());
to_hash.extend(AH.compress().to_bytes());
c = hash_to_scalar(&to_hash);
},
#[cfg(feature = "experimental")]
Mode::Verify(c1) => {
start = 0;
end = n;
c = c1;
}
}
// Perform the core loop
let mut c1 = None;
for i in (start .. end).map(|i| i % n) {
if i == 0 {
c1 = Some(c);
}
let c_p = mu_P * c;
let c_c = mu_C * c;
let L = (&s[i] * &ED25519_BASEPOINT_TABLE) + (c_p * P[i]) + (c_c * C[i]);
let PH = hash_to_point(&P[i]);
// Shouldn't be an issue as all of the variables in this vartime statement are public
let R = (s[i] * PH) + images_precomp.vartime_multiscalar_mul(&[c_p, c_c]);
to_hash.truncate(((2 * n) + 3) * 32);
to_hash.extend(L.compress().to_bytes());
to_hash.extend(R.compress().to_bytes());
c = hash_to_scalar(&to_hash);
}
// This first tuple is needed to continue signing, the latter is the c to be tested/worked with
((D, c * mu_P, c * mu_C), c1.unwrap_or(c))
}
#[derive(Clone, Debug)]
pub struct Clsag {
pub D: EdwardsPoint,
pub s: Vec<Scalar>,
pub c1: Scalar
}
impl Clsag {
// Sign core is the extension of core as needed for signing, yet is shared between single signer
// and multisig, hence why it's still core
pub(crate) fn sign_core<R: RngCore + CryptoRng>(
rng: &mut R,
I: &EdwardsPoint,
input: &ClsagInput,
mask: Scalar,
msg: &[u8; 32],
A: EdwardsPoint,
AH: EdwardsPoint
) -> (Clsag, EdwardsPoint, Scalar, Scalar) {
let r: usize = input.decoys.i.into();
let pseudo_out = Commitment::new(mask, input.commitment.amount).calculate();
let z = input.commitment.mask - mask;
let H = hash_to_point(&input.decoys.ring[r][0]);
let D = H * z;
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) = core(&input.decoys.ring, I, &pseudo_out, msg, &D, &s, Mode::Sign(r, A, AH));
(
Clsag { D, s, c1 },
pseudo_out,
p,
c * z
)
}
// Single signer CLSAG
pub fn sign<R: RngCore + CryptoRng>(
rng: &mut R,
inputs: &[(Scalar, EdwardsPoint, ClsagInput)],
sum_outputs: Scalar,
msg: [u8; 32]
) -> Vec<(Clsag, EdwardsPoint)> {
let nonce = random_scalar(rng);
let mut rand_source = [0; 64];
rng.fill_bytes(&mut rand_source);
let mut res = Vec::with_capacity(inputs.len());
let mut sum_pseudo_outs = Scalar::zero();
for i in 0 .. inputs.len() {
let mut mask = random_scalar(rng);
if i == (inputs.len() - 1) {
mask = sum_outputs - sum_pseudo_outs;
} else {
sum_pseudo_outs += mask;
}
let mut rand_source = [0; 64];
rng.fill_bytes(&mut rand_source);
let (mut clsag, pseudo_out, p, c) = Clsag::sign_core(
rng,
&inputs[i].1,
&inputs[i].2,
mask,
&msg,
&nonce * &ED25519_BASEPOINT_TABLE,
nonce * hash_to_point(&inputs[i].2.decoys.ring[usize::from(inputs[i].2.decoys.i)][0])
);
clsag.s[inputs[i].2.decoys.i as usize] = nonce - ((p * inputs[i].0) + c);
res.push((clsag, pseudo_out));
}
res
}
// Not extensively tested nor guaranteed to have expected parity with Monero
#[cfg(feature = "experimental")]
pub fn rust_verify(
&self,
ring: &[[EdwardsPoint; 2]],
I: &EdwardsPoint,
pseudo_out: &EdwardsPoint,
msg: &[u8; 32]
) -> Result<(), ClsagError> {
let (_, c1) = core(
ring,
I,
pseudo_out,
msg,
&self.D.mul_by_cofactor(),
&self.s,
Mode::Verify(self.c1)
);
if c1 != self.c1 {
Err(ClsagError::InvalidC1)?;
}
Ok(())
}
pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
write_raw_vec(write_scalar, &self.s, w)?;
w.write_all(&self.c1.to_bytes())?;
write_point(&self.D, w)
}
pub fn deserialize<R: std::io::Read>(decoys: usize, r: &mut R) -> std::io::Result<Clsag> {
Ok(
Clsag {
s: read_raw_vec(read_scalar, decoys, r)?,
c1: read_scalar(r)?,
D: read_point(r)?
}
)
}
pub fn verify(
&self,
ring: &[[EdwardsPoint; 2]],
I: &EdwardsPoint,
pseudo_out: &EdwardsPoint,
msg: &[u8; 32]
) -> Result<(), ClsagError> {
// Serialize it to pass the struct to Monero without extensive FFI
let mut serialized = Vec::with_capacity(1 + ((self.s.len() + 2) * 32));
write_varint(&self.s.len().try_into().unwrap(), &mut serialized).unwrap();
self.serialize(&mut serialized).unwrap();
let I_bytes = I.compress().to_bytes();
let mut ring_bytes = vec![];
for member in ring {
ring_bytes.extend(&member[0].compress().to_bytes());
ring_bytes.extend(&member[1].compress().to_bytes());
}
let pseudo_out_bytes = pseudo_out.compress().to_bytes();
unsafe {
// Uses Monero's C verification function to ensure compatibility with Monero
#[link(name = "wrapper")]
extern "C" {
pub(crate) fn c_verify_clsag(
serialized_len: usize,
serialized: *const u8,
ring_size: u8,
ring: *const u8,
I: *const u8,
pseudo_out: *const u8,
msg: *const u8
) -> bool;
}
if c_verify_clsag(
serialized.len(), serialized.as_ptr(),
ring.len() as u8, ring_bytes.as_ptr(),
I_bytes.as_ptr(), pseudo_out_bytes.as_ptr(), msg.as_ptr()
) {
Ok(())
} else {
Err(ClsagError::InvalidC1)
}
}
}
}

View File

@@ -0,0 +1,265 @@
use core::fmt::Debug;
use std::{rc::Rc, cell::RefCell};
use rand_core::{RngCore, CryptoRng, SeedableRng};
use rand_chacha::ChaCha12Rng;
use curve25519_dalek::{
constants::ED25519_BASEPOINT_TABLE,
traits::Identity,
scalar::Scalar,
edwards::EdwardsPoint
};
use group::Group;
use transcript::Transcript as TranscriptTrait;
use frost::{FrostError, MultisigView, algorithm::Algorithm};
use dalek_ff_group as dfg;
use crate::{
hash_to_point,
frost::{Transcript, MultisigError, Ed25519, DLEqProof, read_dleq},
ringct::clsag::{ClsagInput, Clsag}
};
impl ClsagInput {
fn transcript<T: TranscriptTrait>(&self, transcript: &mut T) {
// Doesn't domain separate as this is considered part of the larger CLSAG proof
// Ring index
transcript.append_message(b"ring_index", &[self.decoys.i]);
// Ring
let mut ring = vec![];
for pair in &self.decoys.ring {
// Doesn't include global output indexes as CLSAG doesn't care and won't be affected by it
// They're just a unreliable reference to this data which will be included in the message
// if in use
ring.extend(&pair[0].compress().to_bytes());
ring.extend(&pair[1].compress().to_bytes());
}
transcript.append_message(b"ring", &ring);
// Doesn't include the commitment's parts as the above ring + index includes the commitment
// The only potential malleability would be if the G/H relationship is known breaking the
// discrete log problem, which breaks everything already
}
}
// pub to enable testing
// While we could move the CLSAG test inside this crate, that'd require duplicating the FROST test
// helper, and isn't worth doing right now when this is harmless enough (semver? TODO)
#[derive(Clone, Debug)]
pub struct ClsagDetails {
input: ClsagInput,
mask: Scalar
}
impl ClsagDetails {
pub fn new(input: ClsagInput, mask: Scalar) -> ClsagDetails {
ClsagDetails { input, mask }
}
}
#[allow(non_snake_case)]
#[derive(Clone, Debug)]
struct Interim {
p: Scalar,
c: Scalar,
clsag: Clsag,
pseudo_out: EdwardsPoint
}
#[allow(non_snake_case)]
#[derive(Clone, Debug)]
pub struct ClsagMultisig {
transcript: Transcript,
H: EdwardsPoint,
// Merged here as CLSAG needs it, passing it would be a mess, yet having it beforehand requires a round
image: EdwardsPoint,
AH: (dfg::EdwardsPoint, dfg::EdwardsPoint),
details: Rc<RefCell<Option<ClsagDetails>>>,
msg: Option<[u8; 32]>,
interim: Option<Interim>
}
impl ClsagMultisig {
pub fn new(
transcript: Transcript,
details: Rc<RefCell<Option<ClsagDetails>>>
) -> Result<ClsagMultisig, MultisigError> {
Ok(
ClsagMultisig {
transcript,
H: EdwardsPoint::identity(),
image: EdwardsPoint::identity(),
AH: (dfg::EdwardsPoint::identity(), dfg::EdwardsPoint::identity()),
details,
msg: None,
interim: None
}
)
}
pub fn serialized_len() -> usize {
3 * (32 + 64)
}
fn input(&self) -> ClsagInput {
self.details.borrow().as_ref().unwrap().input.clone()
}
fn mask(&self) -> Scalar {
self.details.borrow().as_ref().unwrap().mask
}
}
impl Algorithm<Ed25519> for ClsagMultisig {
type Transcript = Transcript;
type Signature = (Clsag, EdwardsPoint);
fn preprocess_addendum<R: RngCore + CryptoRng>(
&mut self,
rng: &mut R,
view: &MultisigView<Ed25519>,
nonces: &[dfg::Scalar; 2]
) -> Vec<u8> {
self.H = hash_to_point(&view.group_key().0);
let mut serialized = Vec::with_capacity(ClsagMultisig::serialized_len());
serialized.extend((view.secret_share().0 * self.H).compress().to_bytes());
serialized.extend(DLEqProof::prove(rng, &self.H, &view.secret_share().0).serialize());
serialized.extend((nonces[0].0 * self.H).compress().to_bytes());
serialized.extend(&DLEqProof::prove(rng, &self.H, &nonces[0].0).serialize());
serialized.extend((nonces[1].0 * self.H).compress().to_bytes());
serialized.extend(&DLEqProof::prove(rng, &self.H, &nonces[1].0).serialize());
serialized
}
fn process_addendum(
&mut self,
view: &MultisigView<Ed25519>,
l: usize,
commitments: &[dfg::EdwardsPoint; 2],
serialized: &[u8]
) -> Result<(), FrostError> {
if serialized.len() != ClsagMultisig::serialized_len() {
// Not an optimal error but...
Err(FrostError::InvalidCommitmentQuantity(l, 9, serialized.len() / 32))?;
}
if self.AH.0.is_identity().into() {
self.transcript.domain_separate(b"CLSAG");
self.input().transcript(&mut self.transcript);
self.transcript.append_message(b"mask", &self.mask().to_bytes());
}
let share = read_dleq(
serialized,
0,
&self.H,
l,
&view.verification_share(l).0
).map_err(|_| FrostError::InvalidCommitment(l))?.0;
// Given the fact there's only ever one possible value for this, this may technically not need
// to be committed to. If signing a TX, it'll be double committed to thanks to the message
// It doesn't hurt to have though and ensures security boundaries are well formed
self.transcript.append_message(b"image_share", &share.compress().to_bytes());
self.image += share;
// Uses the same format FROST does for the expected commitments (nonce * G where this is nonce * H)
// Given this is guaranteed to match commitments, which FROST commits to, this also technically
// doesn't need to be committed to if a canonical serialization is guaranteed
// It, again, doesn't hurt to include and ensures security boundaries are well formed
self.transcript.append_message(b"participant", &u16::try_from(l).unwrap().to_be_bytes());
self.transcript.append_message(b"commitment_D_H", &serialized[0 .. 32]);
self.AH.0 += read_dleq(serialized, 96, &self.H, l, &commitments[0]).map_err(|_| FrostError::InvalidCommitment(l))?;
self.transcript.append_message(b"commitment_E_H", &serialized[0 .. 32]);
self.AH.1 += read_dleq(serialized, 192, &self.H, l, &commitments[1]).map_err(|_| FrostError::InvalidCommitment(l))?;
Ok(())
}
fn transcript(&mut self) -> &mut Self::Transcript {
&mut self.transcript
}
fn sign_share(
&mut self,
view: &MultisigView<Ed25519>,
nonce_sum: dfg::EdwardsPoint,
b: dfg::Scalar,
nonce: dfg::Scalar,
msg: &[u8]
) -> dfg::Scalar {
// Apply the binding factor to the H variant of the nonce
self.AH.0 += self.AH.1 * b;
// Use the transcript to get a seeded random number generator
// The transcript contains private data, preventing passive adversaries from recreating this
// process even if they have access to commitments (specifically, the ring index being signed
// for, along with the mask which should not only require knowing the shared keys yet also the
// input commitment masks)
let mut rng = ChaCha12Rng::from_seed(self.transcript.rng_seed(b"decoy_responses", None));
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(
&mut rng,
&self.image,
&self.input(),
self.mask(),
&self.msg.as_ref().unwrap(),
nonce_sum.0,
self.AH.0.0
);
self.interim = Some(Interim { p, c, clsag, pseudo_out });
let share = dfg::Scalar(nonce.0 - (p * view.secret_share().0));
share
}
fn verify(
&self,
_: dfg::EdwardsPoint,
_: dfg::EdwardsPoint,
sum: dfg::Scalar
) -> Option<Self::Signature> {
let interim = self.interim.as_ref().unwrap();
let mut clsag = interim.clsag.clone();
clsag.s[usize::from(self.input().decoys.i)] = sum.0 - interim.c;
if clsag.verify(
&self.input().decoys.ring,
&self.image,
&interim.pseudo_out,
&self.msg.as_ref().unwrap()
).is_ok() {
return Some((clsag, interim.pseudo_out));
}
return None;
}
fn verify_share(
&self,
verification_share: dfg::EdwardsPoint,
nonce: dfg::EdwardsPoint,
share: dfg::Scalar,
) -> bool {
let interim = self.interim.as_ref().unwrap();
return (&share.0 * &ED25519_BASEPOINT_TABLE) == (
nonce.0 - (interim.p * verification_share.0)
);
}
}

View File

@@ -0,0 +1,126 @@
use curve25519_dalek::edwards::EdwardsPoint;
pub mod bulletproofs;
pub mod clsag;
use crate::{
serialize::*,
ringct::{clsag::Clsag, bulletproofs::Bulletproofs}
};
#[derive(Clone, Debug)]
pub struct RctBase {
pub fee: u64,
pub ecdh_info: Vec<[u8; 8]>,
pub commitments: Vec<EdwardsPoint>
}
impl RctBase {
pub fn serialize<W: std::io::Write>(&self, w: &mut W, rct_type: u8) -> std::io::Result<()> {
w.write_all(&[rct_type])?;
match rct_type {
0 => Ok(()),
5 => {
write_varint(&self.fee, w)?;
for ecdh in &self.ecdh_info {
w.write_all(ecdh)?;
}
write_raw_vec(write_point, &self.commitments, w)
},
_ => panic!("Serializing unknown RctType's Base")
}
}
pub fn deserialize<R: std::io::Read>(outputs: usize, r: &mut R) -> std::io::Result<(RctBase, u8)> {
let mut rct_type = [0];
r.read_exact(&mut rct_type)?;
Ok((
if rct_type[0] == 0 {
RctBase { fee: 0, ecdh_info: vec![], commitments: vec![] }
} else {
RctBase {
fee: read_varint(r)?,
ecdh_info: (0 .. outputs).map(
|_| { let mut ecdh = [0; 8]; r.read_exact(&mut ecdh).map(|_| ecdh) }
).collect::<Result<_, _>>()?,
commitments: read_raw_vec(read_point, outputs, r)?
}
},
rct_type[0]
))
}
}
#[derive(Clone, Debug)]
pub enum RctPrunable {
Null,
Clsag {
bulletproofs: Vec<Bulletproofs>,
clsags: Vec<Clsag>,
pseudo_outs: Vec<EdwardsPoint>
}
}
impl RctPrunable {
pub fn rct_type(&self) -> u8 {
match self {
RctPrunable::Null => 0,
RctPrunable::Clsag { .. } => 5
}
}
pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
match self {
RctPrunable::Null => Ok(()),
RctPrunable::Clsag { bulletproofs, clsags, pseudo_outs } => {
write_vec(Bulletproofs::serialize, &bulletproofs, w)?;
write_raw_vec(Clsag::serialize, &clsags, w)?;
write_raw_vec(write_point, &pseudo_outs, w)
}
}
}
pub fn deserialize<R: std::io::Read>(
rct_type: u8,
decoys: &[usize],
r: &mut R
) -> std::io::Result<RctPrunable> {
Ok(
match rct_type {
0 => RctPrunable::Null,
5 => RctPrunable::Clsag {
// TODO: Can the amount of outputs be calculated from the BPs for any validly formed TX?
bulletproofs: read_vec(Bulletproofs::deserialize, r)?,
clsags: (0 .. decoys.len()).map(|o| Clsag::deserialize(decoys[o], r)).collect::<Result<_, _>>()?,
pseudo_outs: read_raw_vec(read_point, decoys.len(), r)?
},
_ => Err(std::io::Error::new(std::io::ErrorKind::Other, "Tried to deserialize unknown RCT type"))?
}
)
}
pub fn signature_serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
match self {
RctPrunable::Null => panic!("Serializing RctPrunable::Null for a signature"),
RctPrunable::Clsag { bulletproofs, .. } => bulletproofs.iter().map(|bp| bp.signature_serialize(w)).collect(),
}
}
}
#[derive(Clone, Debug)]
pub struct RctSignatures {
pub base: RctBase,
pub prunable: RctPrunable
}
impl RctSignatures {
pub fn serialize<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
self.base.serialize(w, self.prunable.rct_type())?;
self.prunable.serialize(w)
}
pub fn deserialize<R: std::io::Read>(decoys: Vec<usize>, outputs: usize, r: &mut R) -> std::io::Result<RctSignatures> {
let base = RctBase::deserialize(outputs, r)?;
Ok(RctSignatures { base: base.0, prunable: RctPrunable::deserialize(base.1, &decoys, r)? })
}
}