schnorr_evm/
proof_of_knowledge.rs

1use k256::elliptic_curve::hash2curve::{hash_to_field, ExpandMsgXmd};
2use k256::elliptic_curve::sec1::FromEncodedPoint;
3use k256::elliptic_curve::PrimeField;
4use k256::{AffinePoint, EncodedPoint, Scalar};
5use sha2::Sha256;
6
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub enum Error {
9    InvalidCommitment,
10    InvalidProofOfKnowledge,
11}
12
13impl core::fmt::Display for Error {
14    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
15        match self {
16            Self::InvalidCommitment => write!(f, "invalid commitment"),
17            Self::InvalidProofOfKnowledge => write!(f, "invalid proof of knowledge"),
18        }
19    }
20}
21
22#[cfg(feature = "std")]
23impl std::error::Error for Error {}
24
25pub type Result<T, E = Error> = core::result::Result<T, E>;
26
27pub type ProofOfKnowledge = [u8; 65];
28pub type Commitment = [[u8; 33]];
29
30/// Context string from the ciphersuite in the [spec].
31///
32/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-1
33const ID_CONTEXT_STRING: &str = "FROST-secp256k1-SHA256-v1id";
34const DKG_CONTEXT_STRING: &str = "FROST-secp256k1-SHA256-v1dkg";
35
36fn hash_to_scalar(domain: &str, msg: &[u8]) -> Scalar {
37    let mut u = [Scalar::ZERO];
38    hash_to_field::<ExpandMsgXmd<Sha256>, Scalar>(&[msg], &[domain.as_bytes()], &mut u)
39        .expect("should never return error according to error cases described in ExpandMsgXmd");
40    u[0]
41}
42
43pub fn verify_proof_of_knowledge(
44    peer: &[u8],
45    commitment: &Commitment,
46    pok: ProofOfKnowledge,
47) -> Result<()> {
48    if commitment.is_empty() {
49        return Err(Error::InvalidCommitment);
50    }
51    let pk = commitment[0];
52    let r: [u8; 33] = pok[..33].try_into().unwrap();
53    let z: [u8; 32] = pok[33..].try_into().unwrap();
54    let id = hash_to_scalar(ID_CONTEXT_STRING, peer);
55
56    let mut preimage = [0; 98];
57    preimage[..32].copy_from_slice(&id.to_bytes());
58    preimage[32..65].copy_from_slice(&pk);
59    preimage[65..].copy_from_slice(&r);
60    let c = hash_to_scalar(DKG_CONTEXT_STRING, &preimage[..]);
61
62    let pk = EncodedPoint::from_bytes(pk).map_err(|_| Error::InvalidCommitment)?;
63    let pk = Option::<AffinePoint>::from(AffinePoint::from_encoded_point(&pk))
64        .ok_or(Error::InvalidCommitment)?;
65    let r = EncodedPoint::from_bytes(r).map_err(|_| Error::InvalidProofOfKnowledge)?;
66    let r = Option::<AffinePoint>::from(AffinePoint::from_encoded_point(&r))
67        .ok_or(Error::InvalidProofOfKnowledge)?;
68    let z = Option::<Scalar>::from(Scalar::from_repr(z.into()))
69        .ok_or(Error::InvalidProofOfKnowledge)?;
70    if r != AffinePoint::GENERATOR * z - pk * c {
71        return Err(Error::InvalidProofOfKnowledge);
72    }
73    Ok(())
74}
75
76#[cfg(feature = "std")]
77pub fn construct_proof_of_knowledge(
78    peer: &[u8],
79    coefficients: &[Scalar],
80    commitment: &Commitment,
81) -> Result<ProofOfKnowledge, frost_core::Error<frost_secp256k1::Secp256K1Sha256>> {
82    let sig = frost_core::keys::dkg::compute_proof_of_knowledge(
83        frost_core::Identifier::derive(peer)?,
84        coefficients,
85        &frost_core::keys::VerifiableSecretSharingCommitment::deserialize(commitment.to_vec())?,
86        rand_core::OsRng,
87    )?;
88    Ok(sig.serialize())
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use k256::elliptic_curve::sec1::ToEncodedPoint;
95    use k256::NonZeroScalar;
96
97    #[test]
98    fn test_verify() {
99        let rng = &mut OsRng;
100        let peer = b"some peer id";
101        let n = 3;
102        let mut coefficients = Vec::with_capacity(n);
103        let mut commitments = Vec::with_capacity(n);
104        for _ in 0..n {
105            let coefficient = *NonZeroScalar::random(rng).as_ref();
106            let commitment = AffinePoint::GENERATOR * coefficient;
107            let point = commitment.to_affine().to_encoded_point(true);
108            let bytes = point.as_bytes().try_into().unwrap();
109            coefficients.push(coefficient);
110            commitments.push(bytes);
111        }
112        let pok = construct_proof_of_knowledge(peer, &coefficients, &commitments).unwrap();
113        verify_proof_of_knowledge(peer, &commitments, pok).unwrap();
114    }
115}