mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-11 13:39:25 +00:00
Make CLSAG signing private
Also adds a bit more documentation and does a bit more tidying.
This commit is contained in:
@@ -33,6 +33,7 @@ pub(crate) fn hadamard_fold(
|
|||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal structure representing a Bulletproof.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct OriginalStruct {
|
pub struct OriginalStruct {
|
||||||
pub(crate) A: DalekPoint,
|
pub(crate) A: DalekPoint,
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ impl AggregateRangeWitness {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal structure representing a Bulletproof+, as used in Monero.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
|
||||||
pub struct AggregateRangeProof {
|
pub struct AggregateRangeProof {
|
||||||
pub(crate) A: EdwardsPoint,
|
pub(crate) A: EdwardsPoint,
|
||||||
|
|||||||
@@ -26,31 +26,36 @@ use crate::{
|
|||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
mod multisig;
|
mod multisig;
|
||||||
#[cfg(feature = "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)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||||
pub enum ClsagError {
|
pub enum ClsagError {
|
||||||
#[cfg_attr(feature = "std", error("internal error ({0})"))]
|
/// The ring was invalid (such as being too small or too large).
|
||||||
InternalError(&'static str),
|
|
||||||
#[cfg_attr(feature = "std", error("invalid ring"))]
|
#[cfg_attr(feature = "std", error("invalid ring"))]
|
||||||
InvalidRing,
|
InvalidRing,
|
||||||
|
/// The specified ring member was invalid (index, ring size).
|
||||||
#[cfg_attr(feature = "std", error("invalid ring member (member {0}, ring size {1})"))]
|
#[cfg_attr(feature = "std", error("invalid ring member (member {0}, ring size {1})"))]
|
||||||
InvalidRingMember(u8, u8),
|
InvalidRingMember(u8, u8),
|
||||||
|
/// The commitment opening provided did not match the ring member's.
|
||||||
#[cfg_attr(feature = "std", error("invalid commitment"))]
|
#[cfg_attr(feature = "std", error("invalid commitment"))]
|
||||||
InvalidCommitment,
|
InvalidCommitment,
|
||||||
|
/// The key image was invalid (such as being identity or torsioned)
|
||||||
#[cfg_attr(feature = "std", error("invalid key image"))]
|
#[cfg_attr(feature = "std", error("invalid key image"))]
|
||||||
InvalidImage,
|
InvalidImage,
|
||||||
|
/// The `D` component was invalid.
|
||||||
#[cfg_attr(feature = "std", error("invalid D"))]
|
#[cfg_attr(feature = "std", error("invalid D"))]
|
||||||
InvalidD,
|
InvalidD,
|
||||||
|
/// The `s` vector was invalid.
|
||||||
#[cfg_attr(feature = "std", error("invalid s"))]
|
#[cfg_attr(feature = "std", error("invalid s"))]
|
||||||
InvalidS,
|
InvalidS,
|
||||||
|
/// The `c1` variable was invalid.
|
||||||
#[cfg_attr(feature = "std", error("invalid c1"))]
|
#[cfg_attr(feature = "std", error("invalid c1"))]
|
||||||
InvalidC1,
|
InvalidC1,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Input being signed for.
|
/// Context on the ring member being signed for.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct ClsagInput {
|
pub struct ClsagInput {
|
||||||
// The actual commitment for the true spend
|
// The actual commitment for the true spend
|
||||||
@@ -63,7 +68,7 @@ impl ClsagInput {
|
|||||||
pub fn new(commitment: Commitment, decoys: Decoys) -> Result<ClsagInput, ClsagError> {
|
pub fn new(commitment: Commitment, decoys: Decoys) -> Result<ClsagInput, ClsagError> {
|
||||||
let n = decoys.len();
|
let n = decoys.len();
|
||||||
if n > u8::MAX.into() {
|
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();
|
let n = u8::try_from(n).unwrap();
|
||||||
if decoys.i >= n {
|
if decoys.i >= n {
|
||||||
@@ -213,9 +218,16 @@ fn core(
|
|||||||
/// CLSAG signature, as used in Monero.
|
/// CLSAG signature, as used in Monero.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Clsag {
|
pub struct Clsag {
|
||||||
pub D: EdwardsPoint,
|
D: EdwardsPoint,
|
||||||
pub s: Vec<Scalar>,
|
pub(crate) s: Vec<Scalar>,
|
||||||
pub c1: Scalar,
|
pub(crate) c1: Scalar,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct ClsagSignCore {
|
||||||
|
incomplete_clsag: Clsag,
|
||||||
|
pseudo_out: EdwardsPoint,
|
||||||
|
key_challenge: Scalar,
|
||||||
|
challenged_mask: Scalar,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clsag {
|
impl Clsag {
|
||||||
@@ -229,28 +241,34 @@ impl Clsag {
|
|||||||
msg: &[u8; 32],
|
msg: &[u8; 32],
|
||||||
A: EdwardsPoint,
|
A: EdwardsPoint,
|
||||||
AH: EdwardsPoint,
|
AH: EdwardsPoint,
|
||||||
) -> (Clsag, EdwardsPoint, Scalar, Scalar) {
|
) -> ClsagSignCore {
|
||||||
let r: usize = input.decoys.i.into();
|
let r: usize = input.decoys.i.into();
|
||||||
|
|
||||||
let pseudo_out = Commitment::new(mask, input.commitment.amount).calculate();
|
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 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());
|
let mut s = Vec::with_capacity(input.decoys.ring.len());
|
||||||
for _ in 0 .. input.decoys.ring.len() {
|
for _ in 0 .. input.decoys.ring.len() {
|
||||||
s.push(random_scalar(rng));
|
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));
|
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.
|
/// Generate CLSAG signatures for the given inputs.
|
||||||
|
///
|
||||||
/// inputs is of the form (private key, key image, input).
|
/// inputs is of the form (private key, key image, input).
|
||||||
/// sum_outputs is for the sum of the outputs' commitment masks.
|
/// 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,
|
rng: &mut R,
|
||||||
mut inputs: Vec<(Zeroizing<Scalar>, EdwardsPoint, ClsagInput)>,
|
mut inputs: Vec<(Zeroizing<Scalar>, EdwardsPoint, ClsagInput)>,
|
||||||
sum_outputs: Scalar,
|
sum_outputs: Scalar,
|
||||||
@@ -267,20 +285,25 @@ impl Clsag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut nonce = Zeroizing::new(random_scalar(rng));
|
let mut nonce = Zeroizing::new(random_scalar(rng));
|
||||||
let (mut clsag, pseudo_out, p, c) = Clsag::sign_core(
|
let ClsagSignCore { mut incomplete_clsag, pseudo_out, key_challenge, challenged_mask } =
|
||||||
rng,
|
Clsag::sign_core(
|
||||||
&inputs[i].1,
|
rng,
|
||||||
&inputs[i].2,
|
&inputs[i].1,
|
||||||
mask,
|
&inputs[i].2,
|
||||||
&msg,
|
mask,
|
||||||
nonce.deref() * ED25519_BASEPOINT_TABLE,
|
&msg,
|
||||||
nonce.deref() *
|
nonce.deref() * ED25519_BASEPOINT_TABLE,
|
||||||
hash_to_point(&inputs[i].2.decoys.ring[usize::from(inputs[i].2.decoys.i)][0]),
|
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
|
// 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
|
// member's commitment and our input commitment (which will only have a known discrete log
|
||||||
// over G if the amounts cancel out)
|
// 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();
|
inputs[i].0.zeroize();
|
||||||
nonce.zeroize();
|
nonce.zeroize();
|
||||||
|
|
||||||
@@ -310,7 +333,7 @@ impl Clsag {
|
|||||||
if ring.len() != self.s.len() {
|
if ring.len() != self.s.len() {
|
||||||
Err(ClsagError::InvalidS)?;
|
Err(ClsagError::InvalidS)?;
|
||||||
}
|
}
|
||||||
if I.is_identity() {
|
if I.is_identity() || (!I.is_torsion_free()) {
|
||||||
Err(ClsagError::InvalidImage)?;
|
Err(ClsagError::InvalidImage)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,12 +353,14 @@ impl Clsag {
|
|||||||
(ring_len * 32) + 32 + 32
|
(ring_len * 32) + 32 + 32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write the CLSAG to a writer.
|
||||||
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
||||||
write_raw_vec(write_scalar, &self.s, w)?;
|
write_raw_vec(write_scalar, &self.s, w)?;
|
||||||
w.write_all(&self.c1.to_bytes())?;
|
w.write_all(&self.c1.to_bytes())?;
|
||||||
write_point(&self.D, w)
|
write_point(&self.D, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a CLSAG from a reader.
|
||||||
pub fn read<R: Read>(decoys: usize, r: &mut R) -> io::Result<Clsag> {
|
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)? })
|
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.
|
/// CLSAG input and the mask to use for it.
|
||||||
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
|
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct ClsagDetails {
|
pub(crate) struct ClsagDetails {
|
||||||
input: ClsagInput,
|
input: ClsagInput,
|
||||||
mask: Scalar,
|
mask: Scalar,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClsagDetails {
|
impl ClsagDetails {
|
||||||
pub fn new(input: ClsagInput, mask: Scalar) -> ClsagDetails {
|
pub(crate) fn new(input: ClsagInput, mask: Scalar) -> ClsagDetails {
|
||||||
ClsagDetails { input, mask }
|
ClsagDetails { input, mask }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +93,7 @@ struct Interim {
|
|||||||
/// FROST algorithm for producing a CLSAG signature.
|
/// FROST algorithm for producing a CLSAG signature.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ClsagMultisig {
|
pub(crate) struct ClsagMultisig {
|
||||||
transcript: RecommendedTranscript,
|
transcript: RecommendedTranscript,
|
||||||
|
|
||||||
pub(crate) H: EdwardsPoint,
|
pub(crate) H: EdwardsPoint,
|
||||||
@@ -107,7 +107,7 @@ pub struct ClsagMultisig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ClsagMultisig {
|
impl ClsagMultisig {
|
||||||
pub fn new(
|
pub(crate) fn new(
|
||||||
transcript: RecommendedTranscript,
|
transcript: RecommendedTranscript,
|
||||||
output_key: EdwardsPoint,
|
output_key: EdwardsPoint,
|
||||||
details: Arc<RwLock<Option<ClsagDetails>>>,
|
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"));
|
self.msg = Some(msg.try_into().expect("CLSAG message should be 32-bytes"));
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
let sign_core = Clsag::sign_core(
|
||||||
let (clsag, pseudo_out, p, c) = Clsag::sign_core(
|
|
||||||
&mut rng,
|
&mut rng,
|
||||||
&self.image.expect("verifying a share despite never processing any addendums").0,
|
&self.image.expect("verifying a share despite never processing any addendums").0,
|
||||||
&self.input(),
|
&self.input(),
|
||||||
@@ -229,10 +228,15 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
|||||||
nonce_sums[0][0].0,
|
nonce_sums[0][0].0,
|
||||||
nonce_sums[0][1].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
|
// 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]
|
#[must_use]
|
||||||
|
|||||||
Reference in New Issue
Block a user