semaphore/
identity.rs

1//! Identity Module
2
3use crate::{
4    baby_jubjub::{BabyJubjubConfig, EdwardsAffine},
5    error::SemaphoreError,
6};
7use ark_ec::{CurveConfig, CurveGroup, twisted_edwards::TECurveConfig};
8use ark_ed_on_bn254::{Fq, Fr};
9use ark_ff::{BigInteger, PrimeField};
10use blake::Blake;
11use light_poseidon::{Poseidon, PoseidonHasher};
12use num_bigint::{BigInt, Sign};
13use std::ops::Mul;
14
15/// Semaphore identity
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct Identity {
18    /// Private key
19    private_key: Vec<u8>,
20    /// Secret scalar
21    secret_scalar: Fr,
22    /// Public key
23    public_key: PublicKey,
24    /// Identity commitment
25    commitment: Fq,
26}
27
28impl Identity {
29    /// Creates a new identity from a private key
30    pub fn new(private_key: &[u8]) -> Self {
31        // Hash the private key
32        let secret_scalar = Self::gen_secret_scalar(private_key);
33
34        // Get the public key by multiplying the secret scalar by the base point
35        let public_key = PublicKey::from_scalar(&secret_scalar);
36
37        // Generate the identity commitment
38        let commitment = public_key.commitment();
39
40        Self {
41            private_key: private_key.to_vec(),
42            secret_scalar,
43            public_key,
44            commitment,
45        }
46    }
47
48    /// Returns the private key
49    pub fn private_key(&self) -> &[u8] {
50        &self.private_key
51    }
52
53    /// Returns the secret scalar
54    pub fn secret_scalar(&self) -> &Fr {
55        &self.secret_scalar
56    }
57
58    /// Returns the public key
59    pub fn public_key(&self) -> &PublicKey {
60        &self.public_key
61    }
62
63    /// Returns the identity commitment
64    pub fn commitment(&self) -> &Fq {
65        &self.commitment
66    }
67
68    /// Signs a message
69    pub fn sign_message(&self, message: &[u8]) -> Result<Signature, SemaphoreError> {
70        if message.len() > 32 {
71            return Err(SemaphoreError::MessageSizeExceeded(message.len()));
72        }
73
74        // Hash the private key and prune
75        let mut priv_key_hash = blake_512(&self.private_key);
76        priv_key_hash[0] &= 0xF8;
77        priv_key_hash[31] &= 0x7F;
78        priv_key_hash[31] |= 0x40;
79
80        // Prepare the message in little-endian format
81        let mut message_le = message.to_vec();
82        message_le.reverse();
83
84        // Compute ephemeral nonce scalar
85        let mut k_input = [0u8; 64];
86        k_input[..32].copy_from_slice(&priv_key_hash[32..]);
87        k_input[32..32 + message.len()].copy_from_slice(&message_le);
88        let k_fr = Fr::from_le_bytes_mod_order(&blake_512(&k_input));
89
90        // Calculate ephemeral point r = k * base point
91        let r = BabyJubjubConfig::GENERATOR.mul(k_fr).into_affine();
92
93        // Compute challenge scalar
94        let poseidon_inputs = [
95            r.x,
96            r.y,
97            self.public_key.x(),
98            self.public_key.y(),
99            Fq::from_be_bytes_mod_order(message),
100        ];
101        let c_fq = Poseidon::<Fq>::new_circom(5)
102            .unwrap()
103            .hash(&poseidon_inputs)
104            .unwrap();
105        let c_fr = Fr::from_le_bytes_mod_order(&c_fq.into_bigint().to_bytes_le());
106
107        // Calculate secret scalar (without dividing by cofactor)
108        let secret_scalar = Fr::from_le_bytes_mod_order(&priv_key_hash[..32]);
109
110        // s = nonce + challenge * secret
111        let s = k_fr + c_fr * secret_scalar;
112
113        Ok(Signature::new(r, s))
114    }
115
116    /// Generates the secret scalar from the private key
117    fn gen_secret_scalar(private_key: &[u8]) -> Fr {
118        // Hash the private key
119        let mut hash = blake_512(private_key);
120
121        // Prune hash
122        hash[0] &= 0xF8;
123        hash[31] &= 0x7F;
124        hash[31] |= 0x40;
125
126        // Use first half of hash and divide by cofactor (equivalent to shifting right by 3 bits)
127        let shifted: BigInt = BigInt::from_bytes_le(Sign::Plus, &hash[..32]) >> 3;
128
129        Fr::from_le_bytes_mod_order(&shifted.to_bytes_le().1)
130    }
131}
132
133/// Semaphore public key
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub struct PublicKey {
136    point: EdwardsAffine,
137}
138
139impl PublicKey {
140    /// Creates a new public key instance from a point
141    pub fn from_point(point: EdwardsAffine) -> Self {
142        Self { point }
143    }
144
145    /// Creates a new subgroup public key from a scalar
146    pub fn from_scalar(secret_scalar: &Fr) -> Self {
147        let point = BabyJubjubConfig::GENERATOR.mul(secret_scalar).into_affine();
148
149        Self { point }
150    }
151
152    /// Generates an identity commitment
153    pub fn commitment(&self) -> Fq {
154        Poseidon::<Fq>::new_circom(2)
155            .unwrap()
156            .hash(&[self.point.x, self.point.y])
157            .unwrap()
158    }
159
160    /// Returns the public key point in Affine form
161    pub fn point(&self) -> EdwardsAffine {
162        self.point
163    }
164
165    /// Returns the x coordinate of the public key point
166    pub fn x(&self) -> Fq {
167        self.point.x
168    }
169
170    /// Returns the y coordinate of the public key point
171    pub fn y(&self) -> Fq {
172        self.point.y
173    }
174}
175
176/// Signature
177#[derive(Debug, Clone, PartialEq, Eq)]
178pub struct Signature {
179    /// `r` point
180    pub r: EdwardsAffine,
181    /// `s` scalar
182    pub s: Fr,
183}
184
185impl Signature {
186    /// Creates a new signature from a point and scalar
187    pub fn new(r: EdwardsAffine, s: Fr) -> Self {
188        Self { r, s }
189    }
190
191    /// Verifies against a public key and message
192    pub fn verify(&self, public_key: &PublicKey, message: &[u8]) -> Result<(), SemaphoreError> {
193        if message.len() > 32 {
194            return Err(SemaphoreError::MessageSizeExceeded(message.len()));
195        }
196
197        if !self.r.is_on_curve() {
198            return Err(SemaphoreError::SignaturePointNotOnCurve);
199        }
200
201        if !public_key.point().is_on_curve() {
202            return Err(SemaphoreError::PublicKeyNotOnCurve);
203        }
204
205        // Compute challenge scalar
206        let poseidon_inputs = [
207            self.r.x,
208            self.r.y,
209            public_key.x(),
210            public_key.y(),
211            Fq::from_be_bytes_mod_order(message),
212        ];
213        let c_fq = Poseidon::<Fq>::new_circom(5)
214            .unwrap()
215            .hash(&poseidon_inputs)
216            .unwrap();
217        let mut c_fr = Fr::from_le_bytes_mod_order(&c_fq.into_bigint().to_bytes_le());
218
219        // Multiply challenge scalar by cofactor
220        c_fr *= Fr::from_be_bytes_mod_order(&[BabyJubjubConfig::COFACTOR[0] as u8]);
221
222        // s * generator
223        let left = BabyJubjubConfig::GENERATOR.mul(self.s);
224
225        // nonce + challenge * public_key
226        let right = self.r + public_key.point().mul(c_fr);
227
228        // s * generator = nonce + challenge * public_key
229        if left != right {
230            return Err(SemaphoreError::SignatureVerificationFailed);
231        }
232
233        Ok(())
234    }
235}
236
237/// Computes Blake 512 hash
238pub fn blake_512(input: &[u8]) -> [u8; 64] {
239    let mut output = [0u8; 64];
240    let mut hasher = Blake::new(512).unwrap();
241
242    hasher.update(input);
243    hasher.finalise(&mut output);
244
245    output
246}