2022-06-06 02:18:25 -04:00
|
|
|
use core::convert::TryInto;
|
2022-04-21 21:36:18 -04:00
|
|
|
|
2022-04-28 03:31:09 -04:00
|
|
|
use thiserror::Error;
|
2022-05-03 07:20:24 -04:00
|
|
|
use rand_core::{RngCore, CryptoRng};
|
2022-04-21 21:36:18 -04:00
|
|
|
|
2022-06-28 01:25:26 -04:00
|
|
|
use group::GroupEncoding;
|
|
|
|
|
|
2022-04-21 21:36:18 -04:00
|
|
|
use curve25519_dalek::{
|
|
|
|
|
constants::ED25519_BASEPOINT_TABLE as DTable,
|
|
|
|
|
scalar::Scalar as DScalar,
|
|
|
|
|
edwards::EdwardsPoint as DPoint
|
|
|
|
|
};
|
|
|
|
|
|
2022-06-24 18:58:24 -04:00
|
|
|
use transcript::{Transcript, RecommendedTranscript};
|
2022-05-03 08:49:46 -04:00
|
|
|
use dalek_ff_group as dfg;
|
2022-04-21 21:36:18 -04:00
|
|
|
|
2022-04-28 03:31:09 -04:00
|
|
|
use crate::random_scalar;
|
|
|
|
|
|
2022-05-28 05:08:37 -04:00
|
|
|
#[derive(Clone, Error, Debug)]
|
2022-04-28 03:31:09 -04:00
|
|
|
pub enum MultisigError {
|
|
|
|
|
#[error("internal error ({0})")]
|
|
|
|
|
InternalError(String),
|
2022-04-29 22:03:34 -04:00
|
|
|
#[error("invalid discrete log equality proof")]
|
2022-05-24 21:41:14 -04:00
|
|
|
InvalidDLEqProof(u16),
|
2022-04-28 03:31:09 -04:00
|
|
|
#[error("invalid key image {0}")]
|
2022-05-24 21:41:14 -04:00
|
|
|
InvalidKeyImage(u16)
|
2022-04-28 03:31:09 -04:00
|
|
|
}
|
2022-04-21 21:36:18 -04:00
|
|
|
|
2022-05-17 19:15:53 -04:00
|
|
|
// Used to prove legitimacy of key images and nonces which both involve other basepoints
|
2022-04-21 21:36:18 -04:00
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct DLEqProof {
|
|
|
|
|
s: DScalar,
|
|
|
|
|
c: DScalar
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
impl DLEqProof {
|
2022-05-23 03:24:33 -04:00
|
|
|
fn challenge(H: &DPoint, xG: &DPoint, xH: &DPoint, rG: &DPoint, rH: &DPoint) -> DScalar {
|
|
|
|
|
// Doesn't take in a larger transcript object due to the usage of this
|
|
|
|
|
// Every prover would immediately write their own DLEq proof, when they can only do so in
|
|
|
|
|
// the proper order if they want to reach consensus
|
|
|
|
|
// It'd be a poor API to have CLSAG define a new transcript solely to pass here, just to try to
|
|
|
|
|
// merge later in some form, when it should instead just merge xH (as it does)
|
2022-06-24 18:58:24 -04:00
|
|
|
let mut transcript = RecommendedTranscript::new(b"DLEq Proof");
|
2022-05-23 03:24:33 -04:00
|
|
|
// Bit redundant, keeps things consistent
|
|
|
|
|
transcript.domain_separate(b"DLEq");
|
|
|
|
|
// Doesn't include G which is constant, does include H which isn't, even though H manipulation
|
|
|
|
|
// shouldn't be possible in practice as it's independently calculated as a product of known data
|
|
|
|
|
transcript.append_message(b"H", &H.compress().to_bytes());
|
|
|
|
|
transcript.append_message(b"xG", &xG.compress().to_bytes());
|
|
|
|
|
transcript.append_message(b"xH", &xH.compress().to_bytes());
|
|
|
|
|
transcript.append_message(b"rG", &rG.compress().to_bytes());
|
|
|
|
|
transcript.append_message(b"rH", &rH.compress().to_bytes());
|
|
|
|
|
DScalar::from_bytes_mod_order_wide(
|
|
|
|
|
&transcript.challenge(b"challenge").try_into().expect("Blake2b512 output wasn't 64 bytes")
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-21 21:36:18 -04:00
|
|
|
pub fn prove<R: RngCore + CryptoRng>(
|
|
|
|
|
rng: &mut R,
|
2022-05-17 19:51:04 -04:00
|
|
|
H: &DPoint,
|
|
|
|
|
secret: &DScalar
|
2022-04-21 21:36:18 -04:00
|
|
|
) -> DLEqProof {
|
|
|
|
|
let r = random_scalar(rng);
|
2022-05-17 19:15:53 -04:00
|
|
|
let rG = &DTable * &r;
|
|
|
|
|
let rH = r * H;
|
2022-04-21 21:36:18 -04:00
|
|
|
|
2022-05-23 03:24:33 -04:00
|
|
|
// We can frequently (always?) save a scalar mul if we accept xH as an arg, yet it opens room
|
|
|
|
|
// for incorrect data to be passed, and therefore faults, making it not worth having
|
|
|
|
|
// We could also return xH but... it's really micro-optimizing
|
|
|
|
|
let c = DLEqProof::challenge(H, &(secret * &DTable), &(secret * H), &rG, &rH);
|
2022-04-21 21:36:18 -04:00
|
|
|
let s = r + (c * secret);
|
|
|
|
|
|
|
|
|
|
DLEqProof { s, c }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn verify(
|
|
|
|
|
&self,
|
|
|
|
|
H: &DPoint,
|
2022-05-24 21:41:14 -04:00
|
|
|
l: u16,
|
2022-05-23 03:24:33 -04:00
|
|
|
xG: &DPoint,
|
|
|
|
|
xH: &DPoint
|
2022-05-17 19:15:53 -04:00
|
|
|
) -> Result<(), MultisigError> {
|
2022-04-21 21:36:18 -04:00
|
|
|
let s = self.s;
|
|
|
|
|
let c = self.c;
|
|
|
|
|
|
2022-05-23 03:24:33 -04:00
|
|
|
let rG = (&s * &DTable) - (c * xG);
|
|
|
|
|
let rH = (s * H) - (c * xH);
|
2022-04-21 21:36:18 -04:00
|
|
|
|
2022-05-23 03:24:33 -04:00
|
|
|
if c != DLEqProof::challenge(H, &xG, &xH, &rG, &rH) {
|
2022-05-17 19:15:53 -04:00
|
|
|
Err(MultisigError::InvalidDLEqProof(l))?;
|
2022-04-21 21:36:18 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn serialize(
|
|
|
|
|
&self
|
|
|
|
|
) -> Vec<u8> {
|
|
|
|
|
let mut res = Vec::with_capacity(64);
|
|
|
|
|
res.extend(self.s.to_bytes());
|
|
|
|
|
res.extend(self.c.to_bytes());
|
|
|
|
|
res
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn deserialize(
|
|
|
|
|
serialized: &[u8]
|
|
|
|
|
) -> Option<DLEqProof> {
|
|
|
|
|
if serialized.len() != 64 {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-23 03:24:33 -04:00
|
|
|
DScalar::from_canonical_bytes(serialized[0 .. 32].try_into().unwrap()).and_then(
|
|
|
|
|
|s| DScalar::from_canonical_bytes(serialized[32 .. 64].try_into().unwrap()).and_then(
|
|
|
|
|
|c| Some(DLEqProof { s, c })
|
|
|
|
|
)
|
2022-04-21 21:36:18 -04:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-17 19:15:53 -04:00
|
|
|
|
|
|
|
|
#[allow(non_snake_case)]
|
2022-06-28 01:25:26 -04:00
|
|
|
pub(crate) fn read_dleq(
|
2022-05-17 19:15:53 -04:00
|
|
|
serialized: &[u8],
|
|
|
|
|
start: usize,
|
|
|
|
|
H: &DPoint,
|
2022-05-24 21:41:14 -04:00
|
|
|
l: u16,
|
2022-05-23 03:24:33 -04:00
|
|
|
xG: &DPoint
|
2022-05-17 19:15:53 -04:00
|
|
|
) -> Result<dfg::EdwardsPoint, MultisigError> {
|
2022-06-28 01:25:26 -04:00
|
|
|
if serialized.len() < start + 96 {
|
|
|
|
|
Err(MultisigError::InvalidDLEqProof(l))?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let bytes = (&serialized[(start + 0) .. (start + 32)]).try_into().unwrap();
|
|
|
|
|
// dfg ensures the point is torsion free
|
|
|
|
|
let other = Option::<dfg::EdwardsPoint>::from(
|
|
|
|
|
dfg::EdwardsPoint::from_bytes(&bytes)).ok_or(MultisigError::InvalidDLEqProof(l)
|
|
|
|
|
)?;
|
|
|
|
|
// Ensure this is a canonical point
|
|
|
|
|
if other.to_bytes() != bytes {
|
|
|
|
|
Err(MultisigError::InvalidDLEqProof(l))?;
|
|
|
|
|
}
|
2022-05-17 19:15:53 -04:00
|
|
|
|
2022-05-23 03:24:33 -04:00
|
|
|
DLEqProof::deserialize(&serialized[(start + 32) .. (start + 96)])
|
|
|
|
|
.ok_or(MultisigError::InvalidDLEqProof(l))?
|
|
|
|
|
.verify(H, l, xG, &other).map_err(|_| MultisigError::InvalidDLEqProof(l))?;
|
2022-05-17 19:15:53 -04:00
|
|
|
|
|
|
|
|
Ok(other)
|
|
|
|
|
}
|