penumbra_sdk_sct/
nullifier.rs1use 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
43pub 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 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}