schnorr_evm/
proof_of_knowledge.rs1use 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
30const 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}