use crate::encoding::{self, Tag};
use crate::error::{Error, Result};
use core::fmt;
use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint};
use curve25519_dalek::scalar::Scalar;
use curve25519_dalek::traits::IsIdentity;
use zeroize::Zeroize;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PublicKey {
pub(crate) point: RistrettoPoint,
}
impl core::hash::Hash for PublicKey {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.to_bytes().hash(state);
}
}
impl PublicKey {
pub fn to_bytes(&self) -> [u8; 32] {
self.point.compress().to_bytes()
}
pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self> {
let compressed = CompressedRistretto::from_slice(bytes).map_err(|_| Error::InvalidPoint)?;
let point = compressed.decompress().ok_or(Error::InvalidPoint)?;
if point.is_identity() {
return Err(Error::InvalidIdentityPoint);
}
Ok(PublicKey { point })
}
pub fn to_hex(&self) -> String {
hex::encode(self.to_bytes())
}
pub fn from_hex(s: &str) -> Result<Self> {
let bytes = hex::decode(s).map_err(|_| Error::InvalidHex)?;
let arr: [u8; 32] = bytes
.as_slice()
.try_into()
.map_err(|_| Error::InvalidLength {
what: "PublicKey",
expected: 32,
got: bytes.len(),
})?;
Self::from_bytes(&arr)
}
pub fn to_prefixed(&self) -> String {
encoding::encode_prefixed(Tag::PublicKey, &self.to_bytes())
}
pub fn from_prefixed(s: &str) -> Result<Self> {
let bytes = encoding::decode_prefixed(Tag::PublicKey, s)?;
let arr: [u8; 32] = bytes
.as_slice()
.try_into()
.map_err(|_| Error::InvalidLength {
what: "PublicKey",
expected: 32,
got: bytes.len(),
})?;
Self::from_bytes(&arr)
}
}
pub struct SecretKey {
pub(crate) scalar: Scalar,
}
impl fmt::Debug for SecretKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SecretKey(..)")
}
}
impl Drop for SecretKey {
fn drop(&mut self) {
self.scalar.zeroize();
}
}
impl SecretKey {
pub fn public_key(&self) -> PublicKey {
PublicKey {
point: self.scalar * curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT,
}
}
pub fn to_bytes(&self) -> [u8; 32] {
self.scalar.to_bytes()
}
pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self> {
Ok(SecretKey {
scalar: parse_secret_scalar(bytes)?,
})
}
pub fn to_hex(&self) -> String {
hex::encode(self.to_bytes())
}
pub fn from_hex(s: &str) -> Result<Self> {
let bytes = hex::decode(s).map_err(|_| Error::InvalidHex)?;
let arr: [u8; 32] = bytes
.as_slice()
.try_into()
.map_err(|_| Error::InvalidLength {
what: "SecretKey",
expected: 32,
got: bytes.len(),
})?;
Self::from_bytes(&arr)
}
pub fn is_valid_bytes(bytes: &[u8; 32]) -> bool {
parse_secret_scalar(bytes).is_ok()
}
pub fn is_valid_hex(s: &str) -> bool {
let Ok(bytes) = hex::decode(s) else {
return false;
};
let Ok(arr) = <[u8; 32]>::try_from(bytes.as_slice()) else {
return false;
};
Self::is_valid_bytes(&arr)
}
pub fn to_prefixed(&self) -> String {
encoding::encode_prefixed(Tag::SecretKey, &self.to_bytes())
}
pub fn from_prefixed(s: &str) -> Result<Self> {
let bytes = encoding::decode_prefixed(Tag::SecretKey, s)?;
let arr: [u8; 32] = bytes
.as_slice()
.try_into()
.map_err(|_| Error::InvalidLength {
what: "SecretKey",
expected: 32,
got: bytes.len(),
})?;
Self::from_bytes(&arr)
}
pub fn is_valid_prefixed(s: &str) -> bool {
let Ok(bytes) = encoding::decode_prefixed(Tag::SecretKey, s) else {
return false;
};
let Ok(arr) = <[u8; 32]>::try_from(bytes.as_slice()) else {
return false;
};
Self::is_valid_bytes(&arr)
}
}
fn parse_secret_scalar(bytes: &[u8; 32]) -> Result<Scalar> {
let scalar =
Option::<Scalar>::from(Scalar::from_canonical_bytes(*bytes)).ok_or(Error::InvalidScalar)?;
if scalar == Scalar::ZERO {
return Err(Error::InvalidSecretKey);
}
Ok(scalar)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct KeyImage {
pub(crate) point: RistrettoPoint,
}
impl core::hash::Hash for KeyImage {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.to_bytes().hash(state);
}
}
impl KeyImage {
pub fn to_bytes(&self) -> [u8; 32] {
self.point.compress().to_bytes()
}
pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self> {
let compressed = CompressedRistretto::from_slice(bytes).map_err(|_| Error::InvalidPoint)?;
let point = compressed.decompress().ok_or(Error::InvalidPoint)?;
if point.is_identity() {
return Err(Error::InvalidIdentityPoint);
}
Ok(KeyImage { point })
}
pub fn to_hex(&self) -> String {
hex::encode(self.to_bytes())
}
pub fn from_hex(s: &str) -> Result<Self> {
let bytes = hex::decode(s).map_err(|_| Error::InvalidHex)?;
let arr: [u8; 32] = bytes
.as_slice()
.try_into()
.map_err(|_| Error::InvalidLength {
what: "KeyImage",
expected: 32,
got: bytes.len(),
})?;
Self::from_bytes(&arr)
}
pub fn to_prefixed(&self) -> String {
encoding::encode_prefixed(Tag::KeyImage, &self.to_bytes())
}
pub fn from_prefixed(s: &str) -> Result<Self> {
let bytes = encoding::decode_prefixed(Tag::KeyImage, s)?;
let arr: [u8; 32] = bytes
.as_slice()
.try_into()
.map_err(|_| Error::InvalidLength {
what: "KeyImage",
expected: 32,
got: bytes.len(),
})?;
Self::from_bytes(&arr)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Signature {
pub(crate) challenge: Scalar,
pub(crate) responses: Vec<Scalar>,
}
impl Signature {
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(32 * (1 + self.responses.len()));
out.extend_from_slice(&self.challenge.to_bytes());
for r in &self.responses {
out.extend_from_slice(&r.to_bytes());
}
out
}
pub fn from_bytes(bytes: &[u8], ring_size: usize) -> Result<Self> {
let expected = 32 * (1 + ring_size);
if bytes.len() != expected {
return Err(Error::InvalidLength {
what: "Signature",
expected,
got: bytes.len(),
});
}
let mut chunks = bytes.chunks_exact(32);
let challenge = scalar_from_chunk(chunks.next().expect("challenge present"))?;
let mut responses = Vec::with_capacity(ring_size);
for _ in 0..ring_size {
responses.push(scalar_from_chunk(chunks.next().expect("response present"))?);
}
Ok(Signature {
challenge,
responses,
})
}
pub fn to_hex(&self) -> String {
hex::encode(self.to_bytes())
}
pub fn from_hex(s: &str, ring_size: usize) -> Result<Self> {
let bytes = hex::decode(s).map_err(|_| Error::InvalidHex)?;
Self::from_bytes(&bytes, ring_size)
}
pub fn to_prefixed(&self) -> String {
encoding::encode_prefixed(Tag::Signature, &self.to_bytes())
}
pub fn from_prefixed(s: &str, ring_size: usize) -> Result<Self> {
let bytes = encoding::decode_prefixed(Tag::Signature, s)?;
Self::from_bytes(&bytes, ring_size)
}
}
fn scalar_from_chunk(chunk: &[u8]) -> Result<Scalar> {
let arr: [u8; 32] = chunk.try_into().map_err(|_| Error::InvalidLength {
what: "Scalar",
expected: 32,
got: chunk.len(),
})?;
Option::<Scalar>::from(Scalar::from_canonical_bytes(arr)).ok_or(Error::InvalidScalar)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VoteProof {
pub signature: Signature,
pub key_image: KeyImage,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OwnershipProof {
pub(crate) challenge: Scalar,
pub(crate) response: Scalar,
}
impl OwnershipProof {
pub fn to_bytes(&self) -> [u8; 64] {
let mut out = [0u8; 64];
out[..32].copy_from_slice(&self.challenge.to_bytes());
out[32..].copy_from_slice(&self.response.to_bytes());
out
}
pub fn from_bytes(bytes: &[u8; 64]) -> Result<Self> {
let challenge = scalar_from_chunk(&bytes[..32])?;
let response = scalar_from_chunk(&bytes[32..])?;
Ok(OwnershipProof {
challenge,
response,
})
}
pub fn to_hex(&self) -> String {
hex::encode(self.to_bytes())
}
pub fn from_hex(s: &str) -> Result<Self> {
let bytes = hex::decode(s).map_err(|_| Error::InvalidHex)?;
let arr: [u8; 64] = bytes
.as_slice()
.try_into()
.map_err(|_| Error::InvalidLength {
what: "OwnershipProof",
expected: 64,
got: bytes.len(),
})?;
Self::from_bytes(&arr)
}
pub fn to_prefixed(&self) -> String {
encoding::encode_prefixed(Tag::Ownership, &self.to_bytes())
}
pub fn from_prefixed(s: &str) -> Result<Self> {
let bytes = encoding::decode_prefixed(Tag::Ownership, s)?;
let arr: [u8; 64] = bytes
.as_slice()
.try_into()
.map_err(|_| Error::InvalidLength {
what: "OwnershipProof",
expected: 64,
got: bytes.len(),
})?;
Self::from_bytes(&arr)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Nonce {
pub(crate) bytes: [u8; 32],
}
impl Nonce {
pub fn from_bytes(bytes: [u8; 32]) -> Self {
Nonce { bytes }
}
pub fn to_bytes(&self) -> [u8; 32] {
self.bytes
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
pub fn to_hex(&self) -> String {
hex::encode(self.bytes)
}
pub fn from_hex(s: &str) -> Result<Self> {
let bytes = hex::decode(s).map_err(|_| Error::InvalidHex)?;
let arr: [u8; 32] = bytes
.as_slice()
.try_into()
.map_err(|_| Error::InvalidLength {
what: "Nonce",
expected: 32,
got: bytes.len(),
})?;
Ok(Nonce::from_bytes(arr))
}
pub fn to_prefixed(&self) -> String {
encoding::encode_prefixed(Tag::Nonce, &self.bytes)
}
pub fn from_prefixed(s: &str) -> Result<Self> {
let bytes = encoding::decode_prefixed(Tag::Nonce, s)?;
let arr: [u8; 32] = bytes
.as_slice()
.try_into()
.map_err(|_| Error::InvalidLength {
what: "Nonce",
expected: 32,
got: bytes.len(),
})?;
Ok(Nonce::from_bytes(arr))
}
}
#[cfg(test)]
mod tests {
use super::*;
const IDENTITY_HEX: &str = "0000000000000000000000000000000000000000000000000000000000000000";
#[test]
fn rejects_zero_secret_key() {
let zero = [0u8; 32];
assert_eq!(
SecretKey::from_bytes(&zero).unwrap_err(),
Error::InvalidSecretKey
);
assert_eq!(
SecretKey::from_hex(IDENTITY_HEX).unwrap_err(),
Error::InvalidSecretKey
);
}
#[test]
fn rejects_identity_points_at_api_boundary() {
assert_eq!(
PublicKey::from_hex(IDENTITY_HEX).unwrap_err(),
Error::InvalidIdentityPoint
);
assert_eq!(
KeyImage::from_hex(IDENTITY_HEX).unwrap_err(),
Error::InvalidIdentityPoint
);
}
#[test]
fn is_valid_secret_key_matches_from_bytes() {
let mut valid = [0u8; 32];
valid[0] = 1;
assert!(SecretKey::is_valid_bytes(&valid));
assert!(SecretKey::is_valid_hex(&hex::encode(valid)));
let zero = [0u8; 32];
assert!(!SecretKey::is_valid_bytes(&zero));
assert!(!SecretKey::is_valid_hex(IDENTITY_HEX));
let non_canonical = [0xffu8; 32];
assert!(!SecretKey::is_valid_bytes(&non_canonical));
assert!(!SecretKey::is_valid_hex(&hex::encode(non_canonical)));
assert!(!SecretKey::is_valid_hex("not hex at all!!"));
assert!(!SecretKey::is_valid_hex("aa")); assert!(!SecretKey::is_valid_hex(&"aa".repeat(33))); }
#[test]
fn is_valid_agrees_with_from_bytes_on_generated_keys() {
let id = crate::identity::generate_identity();
let bytes = id.secret_key.to_bytes();
assert!(SecretKey::is_valid_bytes(&bytes));
assert!(SecretKey::is_valid_hex(&hex::encode(bytes)));
}
#[test]
fn secret_key_debug_is_redacted() {
let sk =
SecretKey::from_hex("0100000000000000000000000000000000000000000000000000000000000000")
.unwrap();
let debug = format!("{sk:?}");
assert_eq!(debug, "SecretKey(..)");
assert!(!debug.contains("1"));
}
}