penumbra_sdk_governance/delegator_vote/
plan.rs1use ark_ff::Zero;
2use decaf377::{Fq, Fr};
3use decaf377_rdsa::{Signature, SpendAuth};
4use penumbra_sdk_asset::{Balance, Value};
5use penumbra_sdk_keys::FullViewingKey;
6use penumbra_sdk_num::Amount;
7use penumbra_sdk_proof_params::DELEGATOR_VOTE_PROOF_PROVING_KEY;
8use penumbra_sdk_proto::{core::component::governance::v1 as pb, DomainType};
9use penumbra_sdk_sct::Nullifier;
10use penumbra_sdk_shielded_pool::Note;
11use penumbra_sdk_tct as tct;
12use rand::{CryptoRng, RngCore};
13use serde::{Deserialize, Serialize};
14
15use crate::delegator_vote::action::{DelegatorVote, DelegatorVoteBody};
16use crate::delegator_vote::proof::DelegatorVoteProof;
17use crate::DelegatorVoteProofPrivate;
18use crate::DelegatorVoteProofPublic;
19use crate::{vote::Vote, VotingReceiptToken};
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23#[serde(try_from = "pb::DelegatorVotePlan", into = "pb::DelegatorVotePlan")]
24pub struct DelegatorVotePlan {
25 pub proposal: u64,
27 pub start_position: tct::Position,
29 pub vote: Vote,
31 pub staked_note: Note,
33 pub unbonded_amount: Amount,
35 pub position: tct::Position,
37 pub randomizer: Fr,
39 pub proof_blinding_r: Fq,
41 pub proof_blinding_s: Fq,
43}
44
45impl DelegatorVotePlan {
46 #[allow(clippy::too_many_arguments)]
48 pub fn new<R: CryptoRng + RngCore>(
49 rng: &mut R,
50 proposal: u64,
51 start_position: tct::Position,
52 vote: Vote,
53 staked_note: Note,
54 position: tct::Position,
55 unbonded_amount: Amount,
56 ) -> DelegatorVotePlan {
57 DelegatorVotePlan {
58 proposal,
59 start_position,
60 vote,
61 staked_note,
62 unbonded_amount,
63 position,
64 randomizer: Fr::rand(rng),
65 proof_blinding_r: Fq::rand(rng),
66 proof_blinding_s: Fq::rand(rng),
67 }
68 }
69
70 pub fn delegator_vote(
72 &self,
73 fvk: &FullViewingKey,
74 auth_sig: Signature<SpendAuth>,
75 auth_path: tct::Proof,
76 ) -> DelegatorVote {
77 DelegatorVote {
78 body: self.delegator_vote_body(fvk),
79 auth_sig,
80 proof: self.delegator_vote_proof(fvk, auth_path),
81 }
82 }
83
84 pub fn delegator_vote_body(&self, fvk: &FullViewingKey) -> DelegatorVoteBody {
86 DelegatorVoteBody {
87 proposal: self.proposal,
88 start_position: self.start_position,
89 vote: self.vote,
90 value: self.staked_note.value(),
91 unbonded_amount: self.unbonded_amount,
92 nullifier: Nullifier::derive(
93 fvk.nullifier_key(),
94 self.position,
95 &self.staked_note.commit(),
96 ),
97 rk: fvk.spend_verification_key().randomize(&self.randomizer),
98 }
99 }
100
101 pub fn delegator_vote_proof(
103 &self,
104 fvk: &FullViewingKey,
105 state_commitment_proof: tct::Proof,
106 ) -> DelegatorVoteProof {
107 let public = DelegatorVoteProofPublic {
108 anchor: state_commitment_proof.root(),
109 balance_commitment: self.staked_note.value().commit(Fr::zero()),
110 nullifier: self.nullifier(fvk),
111 rk: self.rk(fvk),
112 start_position: self.start_position,
113 };
114 let private = DelegatorVoteProofPrivate {
115 state_commitment_proof,
116 note: self.staked_note.clone(),
117 v_blinding: Fr::from(0u64),
118 spend_auth_randomizer: self.randomizer,
119 ak: *fvk.spend_verification_key(),
120 nk: *fvk.nullifier_key(),
121 };
122 DelegatorVoteProof::prove(
123 self.proof_blinding_r,
124 self.proof_blinding_s,
125 &DELEGATOR_VOTE_PROOF_PROVING_KEY,
126 public,
127 private,
128 )
129 .expect("can generate ZK delegator vote proof")
130 }
131
132 pub fn rk(&self, fvk: &FullViewingKey) -> decaf377_rdsa::VerificationKey<SpendAuth> {
134 fvk.spend_verification_key().randomize(&self.randomizer)
135 }
136
137 pub fn nullifier(&self, fvk: &FullViewingKey) -> Nullifier {
139 Nullifier::derive(
140 fvk.nullifier_key(),
141 self.position,
142 &self.staked_note.commit(),
143 )
144 }
145
146 pub fn balance(&self) -> Balance {
147 Value {
148 amount: self.unbonded_amount,
149 asset_id: VotingReceiptToken::new(self.proposal).id(),
150 }
151 .into()
152 }
153}
154
155impl From<DelegatorVotePlan> for pb::DelegatorVotePlan {
156 fn from(inner: DelegatorVotePlan) -> Self {
157 pb::DelegatorVotePlan {
158 proposal: inner.proposal,
159 vote: Some(inner.vote.into()),
160 start_position: inner.start_position.into(),
161 staked_note: Some(inner.staked_note.into()),
162 unbonded_amount: Some(inner.unbonded_amount.into()),
163 staked_note_position: inner.position.into(),
164 randomizer: inner.randomizer.to_bytes().to_vec(),
165 proof_blinding_r: inner.proof_blinding_r.to_bytes().to_vec(),
166 proof_blinding_s: inner.proof_blinding_s.to_bytes().to_vec(),
167 }
168 }
169}
170
171impl TryFrom<pb::DelegatorVotePlan> for DelegatorVotePlan {
172 type Error = anyhow::Error;
173
174 fn try_from(value: pb::DelegatorVotePlan) -> Result<Self, Self::Error> {
175 let proof_blinding_r_bytes: [u8; 32] = value
176 .proof_blinding_r
177 .try_into()
178 .map_err(|_| anyhow::anyhow!("malformed r in `DelegatorVotePlan`"))?;
179 let proof_blinding_s_bytes: [u8; 32] = value
180 .proof_blinding_s
181 .try_into()
182 .map_err(|_| anyhow::anyhow!("malformed s in `DelegatorVotePlan`"))?;
183
184 Ok(DelegatorVotePlan {
185 proposal: value.proposal,
186 start_position: value.start_position.into(),
187 vote: value
188 .vote
189 .ok_or_else(|| anyhow::anyhow!("missing vote in `DelegatorVotePlan`"))?
190 .try_into()?,
191 staked_note: value
192 .staked_note
193 .ok_or_else(|| anyhow::anyhow!("missing staked note in `DelegatorVotePlan`"))?
194 .try_into()?,
195 unbonded_amount: value
196 .unbonded_amount
197 .ok_or_else(|| anyhow::anyhow!("missing unbonded amount in `DelegatorVotePlan`"))?
198 .try_into()?,
199 position: value.staked_note_position.into(),
200 randomizer: Fr::from_bytes_checked(
201 value
202 .randomizer
203 .as_slice()
204 .try_into()
205 .map_err(|_| anyhow::anyhow!("invalid randomizer"))?,
206 )
207 .expect("randomizer malformed"),
208 proof_blinding_r: Fq::from_bytes_checked(&proof_blinding_r_bytes)
209 .expect("proof_blinding_r malformed"),
210 proof_blinding_s: Fq::from_bytes_checked(&proof_blinding_s_bytes)
211 .expect("proof_blinding_s malformed"),
212 })
213 }
214}
215
216impl DomainType for DelegatorVotePlan {
217 type Proto = pb::DelegatorVotePlan;
218}