penumbra_sdk_sct/
nullifier.rs

1use ark_r1cs_std::prelude::*;
2use ark_relations::r1cs::SynthesisError;
3use decaf377::{r1cs::FqVar, Fq};
4use penumbra_sdk_tct as tct;
5use penumbra_sdk_tct::{r1cs::StateCommitmentVar, StateCommitment};
6use poseidon377::hash_3;
7
8use once_cell::sync::Lazy;
9use penumbra_sdk_keys::keys::{NullifierKey, NullifierKeyVar};
10use penumbra_sdk_proto::{core::component::sct::v1 as pb, DomainType};
11use serde::{Deserialize, Serialize};
12
13#[derive(PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord, Serialize, Deserialize)]
14#[serde(try_from = "pb::Nullifier", into = "pb::Nullifier")]
15pub struct Nullifier(pub Fq);
16
17impl Nullifier {
18    pub fn parse_hex(str: &str) -> anyhow::Result<Nullifier> {
19        let bytes = hex::decode(str)?;
20        Nullifier::try_from(&bytes[..])
21    }
22}
23
24impl DomainType for Nullifier {
25    type Proto = pb::Nullifier;
26}
27
28impl From<Nullifier> for pb::Nullifier {
29    fn from(n: Nullifier) -> Self {
30        pb::Nullifier {
31            inner: n.0.to_bytes().to_vec(),
32        }
33    }
34}
35
36impl TryFrom<pb::Nullifier> for Nullifier {
37    type Error = anyhow::Error;
38    fn try_from(n: pb::Nullifier) -> Result<Self, Self::Error> {
39        n.inner.as_slice().try_into()
40    }
41}
42
43/// The domain separator used to derive nullifiers.
44pub static NULLIFIER_DOMAIN_SEP: Lazy<Fq> = Lazy::new(|| {
45    Fq::from_le_bytes_mod_order(blake2b_simd::blake2b(b"penumbra.nullifier").as_bytes())
46});
47
48impl std::fmt::Display for Nullifier {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        f.write_str(&hex::encode(self.to_bytes()))
51    }
52}
53
54impl std::fmt::Debug for Nullifier {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        f.debug_tuple("Nullifier")
57            .field(&hex::encode(self.to_bytes()))
58            .finish()
59    }
60}
61
62impl Nullifier {
63    pub fn to_bytes(&self) -> [u8; 32] {
64        self.0.to_bytes()
65    }
66
67    /// Derive the [`Nullifier`] for a positioned note or swap given its [`merkle::Position`]
68    /// and [`Commitment`].
69    pub fn derive(
70        nk: &NullifierKey,
71        pos: penumbra_sdk_tct::Position,
72        state_commitment: &StateCommitment,
73    ) -> Nullifier {
74        Nullifier(hash_3(
75            &NULLIFIER_DOMAIN_SEP,
76            (nk.0, state_commitment.0, (u64::from(pos)).into()),
77        ))
78    }
79}
80
81impl From<Nullifier> for [u8; 32] {
82    fn from(nullifier: Nullifier) -> [u8; 32] {
83        nullifier.0.to_bytes()
84    }
85}
86
87impl TryFrom<&[u8]> for Nullifier {
88    type Error = anyhow::Error;
89
90    fn try_from(slice: &[u8]) -> Result<Nullifier, Self::Error> {
91        let bytes: [u8; 32] = slice[..].try_into()?;
92        let inner = Fq::from_bytes_checked(&bytes).expect("convert from bytes");
93        Ok(Nullifier(inner))
94    }
95}
96
97impl TryFrom<Vec<u8>> for Nullifier {
98    type Error = anyhow::Error;
99
100    fn try_from(vec: Vec<u8>) -> Result<Nullifier, Self::Error> {
101        Self::try_from(&vec[..])
102    }
103}
104
105pub struct NullifierVar {
106    pub inner: FqVar,
107}
108
109impl AllocVar<Nullifier, Fq> for NullifierVar {
110    fn new_variable<T: std::borrow::Borrow<Nullifier>>(
111        cs: impl Into<ark_relations::r1cs::Namespace<Fq>>,
112        f: impl FnOnce() -> Result<T, SynthesisError>,
113        mode: ark_r1cs_std::prelude::AllocationMode,
114    ) -> Result<Self, SynthesisError> {
115        let ns = cs.into();
116        let cs = ns.cs();
117        let inner: Nullifier = *f()?.borrow();
118        match mode {
119            AllocationMode::Constant => unimplemented!(),
120            AllocationMode::Input => Ok(Self {
121                inner: FqVar::new_input(cs, || Ok(inner.0))?,
122            }),
123            AllocationMode::Witness => unimplemented!(),
124        }
125    }
126}
127
128impl R1CSVar<Fq> for NullifierVar {
129    type Value = Nullifier;
130
131    fn cs(&self) -> ark_relations::r1cs::ConstraintSystemRef<Fq> {
132        self.inner.cs()
133    }
134
135    fn value(&self) -> Result<Self::Value, SynthesisError> {
136        Ok(Nullifier(self.inner.value()?))
137    }
138}
139
140impl EqGadget<Fq> for NullifierVar {
141    fn is_eq(&self, other: &Self) -> Result<Boolean<Fq>, SynthesisError> {
142        self.inner.is_eq(&other.inner)
143    }
144}
145
146impl NullifierVar {
147    pub fn derive(
148        nk: &NullifierKeyVar,
149        position: &tct::r1cs::PositionVar,
150        state_commitment: &StateCommitmentVar,
151    ) -> Result<NullifierVar, SynthesisError> {
152        let cs = state_commitment.inner.cs();
153        let domain_sep = FqVar::new_constant(cs.clone(), *NULLIFIER_DOMAIN_SEP)?;
154        let nullifier = poseidon377::r1cs::hash_3(
155            cs,
156            &domain_sep,
157            (
158                nk.inner.clone(),
159                state_commitment.inner.clone(),
160                position.position.clone(),
161            ),
162        )?;
163
164        Ok(NullifierVar { inner: nullifier })
165    }
166}