Move the Monero create to coins/

Includes misc bug fixes
This commit is contained in:
Luke Parker
2022-04-27 00:09:05 -04:00
parent 79f39c4433
commit df4be9ca0c
19 changed files with 13 additions and 3 deletions

View File

@@ -0,0 +1,241 @@
use rand_core::{RngCore, CryptoRng};
use blake2::{Digest, Blake2b512};
use curve25519_dalek::{
constants::ED25519_BASEPOINT_TABLE,
scalar::Scalar,
traits::VartimePrecomputedMultiscalarMul,
edwards::{EdwardsPoint, VartimeEdwardsPrecomputation}
};
use monero::{
consensus::Encodable,
util::ringct::{Key, Clsag}
};
use crate::{SignError, c_verify_clsag, random_scalar, commitment, hash_to_scalar, hash_to_point};
#[cfg(feature = "multisig")]
mod multisig;
#[cfg(feature = "multisig")]
pub use multisig::Multisig;
// Ring with both the index we're signing for and the data needed to rebuild its commitment
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct SemiSignableRing {
ring: Vec<[EdwardsPoint; 2]>,
i: usize,
randomness: Scalar,
amount: u64
}
pub(crate) fn validate_sign_args(
ring: Vec<[EdwardsPoint; 2]>,
i: u8,
private_key: Option<&Scalar>, // Option as multisig won't have access to this
randomness: &Scalar,
amount: u64
) -> Result<SemiSignableRing, SignError> {
let n = ring.len();
if n > u8::MAX.into() {
Err(SignError::InternalError("max ring size in this library is u8 max".to_string()))?;
}
if i >= (n as u8) {
Err(SignError::InvalidRingMember(i, n as u8))?;
}
let i: usize = i.into();
// Validate the secrets match these ring members
if private_key.is_some() && (ring[i][0] != (private_key.unwrap() * &ED25519_BASEPOINT_TABLE)) {
Err(SignError::InvalidSecret(0))?;
}
if ring[i][1] != commitment(&randomness, amount) {
Err(SignError::InvalidSecret(1))?;
}
Ok(SemiSignableRing { ring, i, randomness: *randomness, amount })
}
#[allow(non_snake_case)]
pub(crate) fn sign_core(
rand_source: [u8; 64],
image: EdwardsPoint,
ssr: &SemiSignableRing,
msg: &[u8; 32],
A: EdwardsPoint,
AH: EdwardsPoint
) -> (Clsag, Scalar, Scalar, Scalar, Scalar, EdwardsPoint) {
let n = ssr.ring.len();
let i: usize = ssr.i.into();
let C_out;
let mut P = vec![];
P.reserve_exact(n);
let mut C = vec![];
C.reserve_exact(n);
let mut C_non_zero = vec![];
C_non_zero.reserve_exact(n);
let z;
let mut next_rand = rand_source;
next_rand = Blake2b512::digest(&next_rand).as_slice().try_into().unwrap();
{
let a = Scalar::from_bytes_mod_order_wide(&next_rand);
next_rand = Blake2b512::digest(&next_rand).as_slice().try_into().unwrap();
C_out = commitment(&a, ssr.amount);
for member in &ssr.ring {
P.push(member[0]);
C_non_zero.push(member[1]);
C.push(C_non_zero[C_non_zero.len() - 1] - C_out);
}
z = ssr.randomness - a;
}
let H = hash_to_point(&P[i]);
let mut D = H * z;
// Doesn't use a constant time table as dalek takes longer to generate those then they save
let images_precomp = VartimeEdwardsPrecomputation::new(&[image, D]);
D = Scalar::from(8 as u8).invert() * D;
let mut to_hash = vec![];
to_hash.reserve_exact(((2 * n) + 4) * 32);
const PREFIX: &str = "CLSAG_";
const AGG_0: &str = "CLSAG_agg_0";
const ROUND: &str = "round";
to_hash.extend(AGG_0.bytes());
to_hash.extend([0; 32 - AGG_0.len()]);
for j in 0 .. n {
to_hash.extend(P[j].compress().to_bytes());
}
for j in 0 .. n {
to_hash.extend(C_non_zero[j].compress().to_bytes());
}
to_hash.extend(image.compress().to_bytes());
let D_bytes = D.compress().to_bytes();
to_hash.extend(D_bytes);
to_hash.extend(C_out.compress().to_bytes());
let mu_P = hash_to_scalar(&to_hash);
to_hash[AGG_0.len() - 1] = '1' as u8;
let mu_C = hash_to_scalar(&to_hash);
to_hash.truncate(((2 * n) + 1) * 32);
to_hash.reserve_exact(((2 * n) + 5) * 32);
for j in 0 .. ROUND.len() {
to_hash[PREFIX.len() + j] = ROUND.as_bytes()[j] as u8;
}
to_hash.extend(C_out.compress().to_bytes());
to_hash.extend(msg);
to_hash.extend(A.compress().to_bytes());
to_hash.extend(AH.compress().to_bytes());
let mut c = hash_to_scalar(&to_hash);
let mut c1 = Scalar::zero();
let mut j = (i + 1) % n;
if j == 0 {
c1 = c;
}
let mut s = vec![];
s.resize(n, Scalar::zero());
while j != i {
s[j] = Scalar::from_bytes_mod_order_wide(&next_rand);
next_rand = Blake2b512::digest(&next_rand).as_slice().try_into().unwrap();
let c_p = mu_P * c;
let c_c = mu_C * c;
let L = (&s[j] * &ED25519_BASEPOINT_TABLE) + (c_p * P[j]) + (c_c * C[j]);
let PH = hash_to_point(&P[j]);
// Shouldn't be an issue as all of the variables in this vartime statement are public
let R = (s[j] * 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);
j = (j + 1) % n;
if j == 0 {
c1 = c;
}
}
(
Clsag {
s: s.iter().map(|s| Key { key: s.to_bytes() }).collect(),
c1: Key { key: c1.to_bytes() },
D: Key { key: D_bytes }
},
c, mu_C, z, mu_P,
C_out
)
}
#[allow(non_snake_case)]
pub fn sign<R: RngCore + CryptoRng>(
rng: &mut R,
image: EdwardsPoint,
msg: [u8; 32],
ring: Vec<[EdwardsPoint; 2]>,
i: u8,
private_key: &Scalar,
randomness: &Scalar,
amount: u64
) -> Result<(Clsag, EdwardsPoint), SignError> {
let ssr = validate_sign_args(ring, i, Some(private_key), randomness, amount)?;
let a = random_scalar(rng);
let mut rand_source = [0; 64];
rng.fill_bytes(&mut rand_source);
let (mut clsag, c, mu_C, z, mu_P, C_out) = sign_core(
rand_source,
image,
&ssr,
&msg,
&a * &ED25519_BASEPOINT_TABLE, a * hash_to_point(&ssr.ring[ssr.i][0])
);
clsag.s[i as usize] = Key { key: (a - (c * ((mu_C * z) + (mu_P * private_key)))).to_bytes() };
Ok((clsag, C_out))
}
// Uses Monero's C verification function to ensure compatibility with Monero
pub fn verify(
clsag: &Clsag,
image: EdwardsPoint,
msg: &[u8; 32],
ring: &[[EdwardsPoint; 2]],
pseudo_out: EdwardsPoint
) -> Result<(), SignError> {
// Workaround for the fact monero-rs doesn't include the length of clsag.s in clsag encoding
// despite it being part of clsag encoding. Reason for the patch version pin
let mut serialized = vec![clsag.s.len() as u8];
clsag.consensus_encode(&mut serialized).unwrap();
let image_bytes = image.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();
let success;
unsafe {
success = c_verify_clsag(
serialized.len(), serialized.as_ptr(), image_bytes.as_ptr(),
ring.len() as u8, ring_bytes.as_ptr(), msg.as_ptr(), pseudo_out_bytes.as_ptr()
);
}
if success { Ok(()) } else { Err(SignError::InvalidSignature) }
}

View File

@@ -0,0 +1,211 @@
use rand_core::{RngCore, CryptoRng};
use blake2::{Digest, Blake2b512};
use curve25519_dalek::{
constants::ED25519_BASEPOINT_TABLE,
scalar::Scalar,
edwards::EdwardsPoint
};
use dalek_ff_group as dfg;
use group::Group;
use frost::{Curve, FrostError, algorithm::Algorithm, sign::ParamsView};
use monero::util::ringct::{Key, Clsag};
use crate::{
SignError,
hash_to_point,
frost::{Ed25519, DLEqProof},
clsag::{SemiSignableRing, validate_sign_args, sign_core, verify}
};
#[allow(non_snake_case)]
#[derive(Clone, Debug)]
struct ClsagSignInterim {
c: Scalar,
mu_C: Scalar,
z: Scalar,
mu_P: Scalar,
clsag: Clsag,
C_out: EdwardsPoint
}
#[allow(non_snake_case)]
#[derive(Clone, Debug)]
pub struct Multisig {
b: Vec<u8>,
AH: dfg::EdwardsPoint,
image: EdwardsPoint,
ssr: SemiSignableRing,
msg: [u8; 32],
interim: Option<ClsagSignInterim>
}
impl Multisig {
pub fn new(
image: EdwardsPoint,
msg: [u8; 32],
ring: Vec<[EdwardsPoint; 2]>,
i: u8,
randomness: &Scalar,
amount: u64
) -> Result<Multisig, SignError> {
let ssr = validate_sign_args(ring, i, None, randomness, amount)?;
Ok(
Multisig {
b: vec![],
AH: dfg::EdwardsPoint::identity(),
image,
ssr,
msg,
interim: None
}
)
}
}
impl Algorithm<Ed25519> for Multisig {
type Signature = (Clsag, EdwardsPoint);
fn context(&self) -> Vec<u8> {
let mut context = self.image.compress().to_bytes().to_vec();
for pair in &self.ssr.ring {
context.extend(&pair[0].compress().to_bytes());
context.extend(&pair[1].compress().to_bytes());
}
context.extend(&u8::try_from(self.ssr.i).unwrap().to_le_bytes());
context.extend(&self.msg);
context
}
// We arguably don't have to commit to at all thanks to xG and yG being committed to, both of
// those being proven to have the same scalar as xH and yH, yet it doesn't hurt
fn addendum_commit_len() -> usize {
64
}
fn preprocess_addendum<R: RngCore + CryptoRng>(
rng: &mut R,
view: &ParamsView<Ed25519>,
nonces: &[dfg::Scalar; 2]
) -> Vec<u8> {
#[allow(non_snake_case)]
let H = hash_to_point(&view.group_key().0);
let h0 = nonces[0].0 * H;
let h1 = nonces[1].0 * H;
// 32 + 32 + 64 + 64
let mut serialized = Vec::with_capacity(192);
serialized.extend(h0.compress().to_bytes());
serialized.extend(h1.compress().to_bytes());
serialized.extend(&DLEqProof::prove(rng, &nonces[0].0, &H, &h0).serialize());
serialized.extend(&DLEqProof::prove(rng, &nonces[1].0, &H, &h1).serialize());
serialized
}
fn process_addendum(
&mut self,
_: &ParamsView<Ed25519>,
l: usize,
commitments: &[dfg::EdwardsPoint; 2],
p: &dfg::Scalar,
serialized: &[u8]
) -> Result<(), FrostError> {
if serialized.len() != 192 {
// Not an optimal error but...
Err(FrostError::InvalidCommitmentQuantity(l, 6, serialized.len() / 32))?;
}
let alt = &hash_to_point(&self.ssr.ring[self.ssr.i][0]);
let h0 = <Ed25519 as Curve>::G_from_slice(&serialized[0 .. 32]).map_err(|_| FrostError::InvalidCommitment(l))?;
DLEqProof::deserialize(&serialized[64 .. 128]).ok_or(FrostError::InvalidCommitment(l))?.verify(
&alt,
&commitments[0],
&h0
).map_err(|_| FrostError::InvalidCommitment(l))?;
let h1 = <Ed25519 as Curve>::G_from_slice(&serialized[32 .. 64]).map_err(|_| FrostError::InvalidCommitment(l))?;
DLEqProof::deserialize(&serialized[128 .. 192]).ok_or(FrostError::InvalidCommitment(l))?.verify(
&alt,
&commitments[1],
&h1
).map_err(|_| FrostError::InvalidCommitment(l))?;
self.b.extend(&l.to_le_bytes());
self.b.extend(&serialized[0 .. 64]);
self.AH += h0 + (h1 * p);
Ok(())
}
fn sign_share(
&mut self,
view: &ParamsView<Ed25519>,
nonce_sum: dfg::EdwardsPoint,
nonce: dfg::Scalar,
_: &[u8]
) -> dfg::Scalar {
// Use everyone's commitments to derive a random source all signers can agree upon
// Cannot be manipulated to effect and all signers must, and will, know this
let rand_source = Keccak::v512()
.chain("clsag_randomness")
.chain(&self.b)
.finalize()
.as_slice()
.try_into()
.unwrap();
#[allow(non_snake_case)]
let (clsag, c, mu_C, z, mu_P, C_out) = sign_core(
rand_source,
self.image,
&self.ssr,
&self.msg,
nonce_sum.0,
self.AH.0
);
let share = dfg::Scalar(nonce.0 - (c * (mu_P * view.secret_share().0)));
self.interim = Some(ClsagSignInterim { c, mu_C, z, mu_P, clsag, C_out });
share
}
fn verify(
&self,
_: dfg::EdwardsPoint,
_: dfg::EdwardsPoint,
sum: dfg::Scalar
) -> Option<Self::Signature> {
let interim = self.interim.as_ref().unwrap();
// Subtract the randomness's presence, which is done once and not fractionalized among shares
let s = sum.0 - (interim.c * (interim.mu_C * interim.z));
let mut clsag = interim.clsag.clone();
clsag.s[self.ssr.i] = Key { key: s.to_bytes() };
if verify(&clsag, self.image, &self.ssr.ring, &self.msg, interim.C_out).is_ok() {
return Some((clsag, interim.C_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.c * (interim.mu_P * verification_share.0))
);
}
}

186
coins/monero/src/frost.rs Normal file
View File

@@ -0,0 +1,186 @@
use core::convert::TryInto;
use rand_core::{RngCore, CryptoRng};
use blake2::{Digest, Blake2b512};
use curve25519_dalek::{
constants::ED25519_BASEPOINT_TABLE as DTable,
traits::VartimeMultiscalarMul,
scalar::Scalar as DScalar,
edwards::EdwardsPoint as DPoint
};
use dalek_ff_group::EdwardsPoint;
use ff::PrimeField;
use group::Group;
use dalek_ff_group as dfg;
use frost::{CurveError, Curve};
use crate::{SignError, random_scalar};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Ed25519;
impl Curve for Ed25519 {
type F = dfg::Scalar;
type G = dfg::EdwardsPoint;
type T = &'static dfg::EdwardsBasepointTable;
fn id() -> String {
"Ed25519".to_string()
}
fn id_len() -> u8 {
Self::id().len() as u8
}
fn generator() -> Self::G {
Self::G::generator()
}
fn generator_table() -> Self::T {
&dfg::ED25519_BASEPOINT_TABLE
}
fn multiexp_vartime(scalars: &[Self::F], points: &[Self::G]) -> Self::G {
EdwardsPoint(DPoint::vartime_multiscalar_mul(scalars, points))
}
fn hash_msg(msg: &[u8]) -> Vec<u8> {
Blake2b512::digest(msg)
}
fn hash_to_F(data: &[u8]) -> Self::F {
dfg::Scalar::from_hash(Blake2b512::new().chain(data))
}
fn F_len() -> usize {
32
}
fn G_len() -> usize {
32
}
fn F_from_le_slice(slice: &[u8]) -> Result<Self::F, CurveError> {
let scalar = Self::F::from_repr(
slice.try_into().map_err(|_| CurveError::InvalidLength(32, slice.len()))?
);
if scalar.is_some().unwrap_u8() == 1 {
Ok(scalar.unwrap())
} else {
Err(CurveError::InvalidScalar)
}
}
fn G_from_slice(slice: &[u8]) -> Result<Self::G, CurveError> {
let point = dfg::CompressedEdwardsY::new(
slice.try_into().map_err(|_| CurveError::InvalidLength(32, slice.len()))?
).decompress();
if point.is_some() {
let point = point.unwrap();
// Ban torsioned points
if !point.is_torsion_free() {
Err(CurveError::InvalidPoint)?
}
Ok(point)
} else {
Err(CurveError::InvalidPoint)
}
}
fn F_to_le_bytes(f: &Self::F) -> Vec<u8> {
f.to_repr().to_vec()
}
fn G_to_bytes(g: &Self::G) -> Vec<u8> {
g.compress().to_bytes().to_vec()
}
}
// Used to prove legitimacy in several locations
#[derive(Clone)]
pub struct DLEqProof {
s: DScalar,
c: DScalar
}
#[allow(non_snake_case)]
impl DLEqProof {
pub fn prove<R: RngCore + CryptoRng>(
rng: &mut R,
secret: &DScalar,
H: &DPoint,
alt: &DPoint
) -> DLEqProof {
let r = random_scalar(rng);
let R1 = &DTable * &r;
let R2 = r * H;
let c = DScalar::from_hash(
Blake2b512::new()
.chain(R1.compress().to_bytes())
.chain(R2.compress().to_bytes())
.chain((secret * &DTable).compress().to_bytes())
.chain(alt.compress().to_bytes())
);
let s = r + (c * secret);
DLEqProof { s, c }
}
pub fn verify(
&self,
H: &DPoint,
primary: &DPoint,
alt: &DPoint
) -> Result<(), SignError> {
let s = self.s;
let c = self.c;
let R1 = (&s * &DTable) - (c * primary);
let R2 = (s * H) - (c * alt);
let expected_c = DScalar::from_hash(
Blake2b512::new()
.chain(R1.compress().to_bytes())
.chain(R2.compress().to_bytes())
.chain(primary.compress().to_bytes())
.chain(alt.compress().to_bytes())
);
// Take the opportunity to ensure a lack of torsion in key images/randomness commitments
if (!primary.is_torsion_free()) || (!alt.is_torsion_free()) || (c != expected_c) {
Err(SignError::InvalidDLEqProof)?;
}
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;
}
Some(
DLEqProof {
s: DScalar::from_bytes_mod_order(serialized[0 .. 32].try_into().unwrap()),
c: DScalar::from_bytes_mod_order(serialized[32 .. 64].try_into().unwrap())
}
)
}
}

View File

@@ -0,0 +1,16 @@
use curve25519_dalek::{
constants::ED25519_BASEPOINT_TABLE,
scalar::Scalar,
edwards::EdwardsPoint
};
use crate::hash_to_point;
#[cfg(feature = "multisig")]
mod multisig;
#[cfg(feature = "multisig")]
pub use crate::key_image::multisig::{Package, multisig};
pub fn single(secret: &Scalar) -> EdwardsPoint {
secret * hash_to_point(&(secret * &ED25519_BASEPOINT_TABLE))
}

View File

@@ -0,0 +1,75 @@
use rand_core::{RngCore, CryptoRng};
use curve25519_dalek::edwards::EdwardsPoint;
use dalek_ff_group::Scalar;
use frost::{MultisigKeys, sign::lagrange};
use crate::{SignError, hash_to_point, frost::{Ed25519, DLEqProof}};
#[derive(Clone)]
#[allow(non_snake_case)]
pub struct Package {
// Don't serialize
H: EdwardsPoint,
i: usize,
// Serialize
image: EdwardsPoint,
proof: DLEqProof
}
#[allow(non_snake_case)]
pub fn multisig<R: RngCore + CryptoRng>(
rng: &mut R,
keys: &MultisigKeys<Ed25519>,
included: &[usize]
) -> Package {
let i = keys.params().i();
let secret = (keys.secret_share() * lagrange::<Scalar>(i, included)).0;
let H = hash_to_point(&keys.group_key().0);
let image = secret * H;
// Includes a proof. Since:
// sum(lagranged_secrets) = group_private
// group_private * G = output_key
// group_private * H = key_image
// Then sum(lagranged_secrets * H) = key_image
// lagranged_secret * G is known. lagranged_secret * H is being sent
// Any discrete log equality proof confirms the same secret was used,
// forming a valid key_image share
Package { H, i, image, proof: DLEqProof::prove(rng, &secret, &H, &image) }
}
#[allow(non_snake_case)]
impl Package {
pub fn resolve(
self,
shares: Vec<Option<(EdwardsPoint, Package)>>
) -> Result<EdwardsPoint, SignError> {
let mut included = vec![self.i];
for i in 1 .. shares.len() {
if shares[i].is_some() {
included.push(i);
}
}
let mut image = self.image;
for i in 0 .. shares.len() {
if shares[i].is_none() {
continue;
}
let (other, shares) = shares[i].as_ref().unwrap();
let other = other * lagrange::<Scalar>(i, &included).0;
// Verify their proof
let share = shares.image;
shares.proof.verify(&self.H, &other, &share).map_err(|_| SignError::InvalidKeyImage(i))?;
// Add their share to the image
image += share;
}
Ok(image)
}
}

82
coins/monero/src/lib.rs Normal file
View File

@@ -0,0 +1,82 @@
use lazy_static::lazy_static;
use thiserror::Error;
use rand_core::{RngCore, CryptoRng};
use tiny_keccak::{Hasher, Keccak};
use curve25519_dalek::{
constants::ED25519_BASEPOINT_TABLE,
scalar::Scalar,
edwards::{EdwardsPoint, EdwardsBasepointTable, CompressedEdwardsY}
};
use monero::util::key;
#[cfg(feature = "multisig")]
pub mod frost;
pub mod key_image;
pub mod clsag;
#[link(name = "wrapper")]
extern "C" {
fn c_hash_to_point(point: *const u8);
pub(crate) fn c_verify_clsag(
serialized_len: usize, serialized: *const u8, I: *const u8,
ring_size: u8, ring: *const u8, msg: *const u8, pseudo_out: *const u8
) -> bool;
}
#[derive(Error, Debug)]
pub enum SignError {
#[error("internal error ({0})")]
InternalError(String),
#[error("invalid discrete log equality proof")]
InvalidDLEqProof,
#[error("invalid key image {0}")]
InvalidKeyImage(usize),
#[error("invalid ring member (member {0}, ring size {1})")]
InvalidRingMember(u8, u8),
#[error("invalid secret for ring (index {0})")]
InvalidSecret(u8),
#[error("invalid commitment {0}")]
InvalidCommitment(usize),
#[error("invalid share {0}")]
InvalidShare(usize),
#[error("invalid signature")]
InvalidSignature
}
// Allows using a modern rand as dalek's is notoriously dated
pub fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Scalar {
let mut r = [0; 64];
rng.fill_bytes(&mut r);
Scalar::from_bytes_mod_order_wide(&r)
}
lazy_static! {
static ref H_TABLE: EdwardsBasepointTable = EdwardsBasepointTable::create(&key::H.point.decompress().unwrap());
}
// aG + bH
pub fn commitment(randomness: &Scalar, amount: u64) -> EdwardsPoint {
(randomness * &ED25519_BASEPOINT_TABLE) + (&Scalar::from(amount) * &*H_TABLE)
}
pub fn hash_to_scalar(data: &[u8]) -> Scalar {
let mut keccak = Keccak::v256();
keccak.update(data);
let mut res = [0; 32];
keccak.finalize(&mut res);
Scalar::from_bytes_mod_order(res)
}
pub fn hash_to_point(point: &EdwardsPoint) -> EdwardsPoint {
let mut bytes = point.compress().to_bytes();
unsafe {
c_hash_to_point(bytes.as_mut_ptr());
}
CompressedEdwardsY::from_slice(&bytes).decompress().unwrap()
}