hotshot_contract_adapter/
stake_table.rs1use alloy::{
2 primitives::{Address, Bytes},
3 sol_types::SolValue,
4};
5use ark_bn254::G2Affine;
6use ark_ec::{AffineRepr, CurveGroup as _};
7use ark_ed_on_bn254::EdwardsConfig;
8use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
9use hotshot_types::{
10 light_client::{hash_bytes_to_field, StateKeyPair, StateSignature, StateVerKey},
11 signature_key::{BLSKeyPair, BLSPubKey, BLSSignature},
12 traits::signature_key::SignatureKey,
13};
14use jf_signature::{
15 bls_over_bn254,
16 constants::{CS_ID_BLS_BN254, CS_ID_SCHNORR},
17 schnorr,
18};
19
20use crate::sol_types::{
21 StakeTableV2::{getVersionReturn, ConsensusKeysUpdatedV2, ValidatorRegisteredV2},
22 *,
23};
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct StateSignatureSol(pub Bytes);
28
29#[derive(Debug, Clone, Copy, Default)]
30pub enum StakeTableContractVersion {
31 V1,
32 #[default]
33 V2,
34}
35
36impl TryFrom<getVersionReturn> for StakeTableContractVersion {
37 type Error = anyhow::Error;
38
39 fn try_from(value: getVersionReturn) -> anyhow::Result<Self> {
40 match value.majorVersion {
41 1 => Ok(StakeTableContractVersion::V1),
42 2 => Ok(StakeTableContractVersion::V2),
43 _ => anyhow::bail!("Unsupported stake table contract version: {:?}", value),
44 }
45 }
46}
47
48impl From<G2PointSol> for BLSPubKey {
49 fn from(value: G2PointSol) -> Self {
50 let point: G2Affine = value.into();
51 let mut bytes = vec![];
52 point
53 .into_group()
54 .serialize_uncompressed(&mut bytes)
55 .unwrap();
56 Self::deserialize_uncompressed(&bytes[..]).unwrap()
57 }
58}
59
60impl From<BLSPubKey> for G2PointSol {
61 fn from(value: BLSPubKey) -> Self {
62 value.to_affine().into()
63 }
64}
65
66impl From<EdOnBN254PointSol> for StateVerKey {
67 fn from(value: EdOnBN254PointSol) -> Self {
68 let point: ark_ed_on_bn254::EdwardsAffine = value.into();
69 Self::from(point)
70 }
71}
72
73impl From<bls_over_bn254::Signature> for G1PointSol {
74 fn from(sig: bls_over_bn254::Signature) -> Self {
75 sig.sigma.into_affine().into()
76 }
77}
78
79impl From<StateVerKey> for EdOnBN254PointSol {
80 fn from(ver_key: StateVerKey) -> Self {
81 ver_key.to_affine().into()
82 }
83}
84
85impl From<StateSignature> for StateSignatureSol {
86 fn from(sig: StateSignature) -> Self {
87 let mut buf = vec![];
88 sig.serialize_compressed(&mut buf).expect("serialize works");
89 Self(buf.into())
90 }
91}
92
93impl From<StateSignatureSol> for Bytes {
94 fn from(sig_sol: StateSignatureSol) -> Self {
95 sig_sol.0
96 }
97}
98
99pub fn sign_address_bls(bls_key_pair: &BLSKeyPair, address: Address) -> bls_over_bn254::Signature {
100 bls_key_pair.sign(&address.abi_encode(), CS_ID_BLS_BN254)
101}
102
103pub fn sign_address_schnorr(schnorr_key_pair: &StateKeyPair, address: Address) -> StateSignature {
104 let msg = [hash_bytes_to_field(&address.abi_encode()).expect("hash to field works")];
105 schnorr_key_pair.sign(&msg, CS_ID_SCHNORR)
106}
107
108pub fn authenticate_schnorr_sig(
110 schnorr_vk: &StateVerKey,
111 address: Address,
112 schnorr_sig: &StateSignature,
113) -> Result<(), StakeTableSolError> {
114 let msg = [hash_bytes_to_field(&address.abi_encode()).expect("hash to field works")];
115 schnorr_vk.verify(&msg, schnorr_sig, CS_ID_SCHNORR)?;
116 Ok(())
117}
118
119pub fn authenticate_bls_sig(
121 bls_vk: &BLSPubKey,
122 address: Address,
123 bls_sig: &BLSSignature,
124) -> Result<(), StakeTableSolError> {
125 let msg = address.abi_encode();
126 if !bls_vk.validate(bls_sig, &msg) {
127 return Err(StakeTableSolError::InvalidBlsSignature);
128 }
129 Ok(())
130}
131
132fn authenticate_stake_table_validator_event(
133 account: Address,
134 bls_vk: G2PointSol,
135 schnorr_vk: EdOnBN254PointSol,
136 bls_sig: G1PointSol,
137 schnorr_sig: &[u8],
138) -> Result<(), StakeTableSolError> {
139 let bls_vk = {
141 let bls_vk_inner: ark_bn254::G2Affine = bls_vk.into();
142 let bls_vk_inner = bls_vk_inner.into_group();
143
144 let mut ser_bytes: Vec<u8> = Vec::new();
146 bls_vk_inner.serialize_uncompressed(&mut ser_bytes).unwrap();
147 BLSPubKey::deserialize_uncompressed(&ser_bytes[..]).unwrap()
148 };
149 let bls_sig_jellyfish = {
150 let sigma_affine: ark_bn254::G1Affine = bls_sig.into();
151 BLSSignature {
152 sigma: sigma_affine.into_group(),
153 }
154 };
155 authenticate_bls_sig(&bls_vk, account, &bls_sig_jellyfish)?;
156
157 let schnorr_vk: StateVerKey = schnorr_vk.into();
158 let schnorr_sig_jellyfish =
159 schnorr::Signature::<EdwardsConfig>::deserialize_compressed(schnorr_sig)?;
160 authenticate_schnorr_sig(&schnorr_vk, account, &schnorr_sig_jellyfish)?;
161 Ok(())
162}
163
164#[derive(Debug, thiserror::Error)]
166pub enum StakeTableSolError {
167 #[error("Failed to deserialize Schnorr signature")]
168 SchnorrSigDeserializationError(#[from] ark_serialize::SerializationError),
169 #[error("BLS signature invalid")]
170 InvalidBlsSignature,
171 #[error("Schnorr signature invalid")]
172 InvalidSchnorrSignature(#[from] jf_signature::SignatureError),
173}
174
175impl ValidatorRegisteredV2 {
176 pub fn authenticate(&self) -> Result<(), StakeTableSolError> {
178 authenticate_stake_table_validator_event(
179 self.account,
180 self.blsVK,
181 self.schnorrVK,
182 self.blsSig.into(),
183 &self.schnorrSig,
184 )?;
185 Ok(())
186 }
187}
188
189impl ConsensusKeysUpdatedV2 {
190 pub fn authenticate(&self) -> Result<(), StakeTableSolError> {
192 authenticate_stake_table_validator_event(
193 self.account,
194 self.blsVK,
195 self.schnorrVK,
196 self.blsSig.into(),
197 &self.schnorrSig,
198 )?;
199 Ok(())
200 }
201}
202
203impl From<StakeTable::ValidatorRegistered> for StakeTableV2::InitialCommission {
204 fn from(value: StakeTable::ValidatorRegistered) -> Self {
205 Self {
206 validator: value.account,
207 commission: value.commission,
208 }
209 }
210}
211
212#[cfg(test)]
213mod test {
214 use alloy::primitives::Address;
215 use hotshot_types::{
216 light_client::StateKeyPair,
217 signature_key::{BLSKeyPair, BLSPrivKey, BLSPubKey},
218 };
219
220 use super::{sign_address_bls, sign_address_schnorr, StateSignatureSol};
221 use crate::sol_types::{
222 G1PointSol, G2PointSol,
223 StakeTableV2::{ConsensusKeysUpdatedV2, ValidatorRegisteredV2},
224 };
225
226 fn check_round_trip(pk: BLSPubKey) {
227 let g2 = G2PointSol::from(pk);
228 let pk2 = BLSPubKey::from(g2);
229 assert_eq!(pk2, pk, "Failed to roundtrip G2PointSol to BLSPubKey: {pk}");
230 }
231
232 #[test]
233 fn test_bls_g2_point_roundtrip() {
234 let mut rng = rand::thread_rng();
235 for _ in 0..100 {
236 let pk = (&BLSPrivKey::generate(&mut rng)).into();
237 check_round_trip(pk);
238 }
239 }
240
241 #[test]
242 fn test_bls_g2_point_alloy_migration_regression() {
243 let s = "BLS_VER_KEY~JlRLUrn0T_MltAJXaaojwk_CnCgd0tyPny_IGdseMBLBPv9nWabIPAaS-aHmn0ARu5YZHJ7mfmGQ-alW42tkJM663Lse-Is80fyA1jnRxPsHcJDnO05oW1M1SC5LeE8sXITbuhmtG2JdTAgmLqWOxbMRmVIqS1AQXqvGGXdo5qpd";
245 let pk: BLSPubKey = s.parse().unwrap();
246 check_round_trip(pk);
247 }
248
249 #[test]
250 fn test_validator_registered_event_authentication() {
251 for _ in 0..10 {
252 let bls_key_pair = BLSKeyPair::generate(&mut rand::thread_rng());
253 let schnorr_key_pair = StateKeyPair::generate();
254 let address = Address::random();
255
256 let bls_sig = sign_address_bls(&bls_key_pair, address);
257 let schnorr_sig = sign_address_schnorr(&schnorr_key_pair, address);
258
259 let valid_event = ValidatorRegisteredV2 {
260 account: address,
261 blsVK: bls_key_pair.ver_key().into(),
262 schnorrVK: schnorr_key_pair.ver_key().into(),
263 commission: 1000, blsSig: G1PointSol::from(bls_sig.clone()).into(),
265 schnorrSig: StateSignatureSol::from(schnorr_sig.clone()).into(),
266 };
267 assert!(valid_event.authenticate().is_ok());
268
269 let wrong_bls_sig =
270 sign_address_bls(&BLSKeyPair::generate(&mut rand::thread_rng()), address);
271 let mut bad_bls_event = valid_event.clone();
272 bad_bls_event.blsSig = G1PointSol::from(wrong_bls_sig).into();
273 assert!(bad_bls_event.authenticate().is_err());
274
275 let wrong_schnorr_sig = sign_address_schnorr(&StateKeyPair::generate(), address);
276 let mut bad_schnorr_event = valid_event.clone();
277 bad_schnorr_event.schnorrSig = StateSignatureSol::from(wrong_schnorr_sig).into();
278 assert!(bad_schnorr_event.authenticate().is_err());
279 }
280 }
281
282 #[test]
283 fn test_consensus_keys_updated_event_authentication() {
284 for _ in 0..10 {
285 let bls_key_pair = BLSKeyPair::generate(&mut rand::thread_rng());
286 let schnorr_key_pair = StateKeyPair::generate();
287 let address = Address::random();
288
289 let bls_sig = sign_address_bls(&bls_key_pair, address);
290 let schnorr_sig = sign_address_schnorr(&schnorr_key_pair, address);
291
292 let valid_event = ConsensusKeysUpdatedV2 {
293 account: address,
294 blsVK: bls_key_pair.ver_key().into(),
295 schnorrVK: schnorr_key_pair.ver_key().into(),
296 blsSig: G1PointSol::from(bls_sig.clone()).into(),
297 schnorrSig: StateSignatureSol::from(schnorr_sig.clone()).into(),
298 };
299 assert!(valid_event.authenticate().is_ok());
300
301 let wrong_bls_sig =
302 sign_address_bls(&BLSKeyPair::generate(&mut rand::thread_rng()), address);
303 let mut bad_bls_event = valid_event.clone();
304 bad_bls_event.blsSig = G1PointSol::from(wrong_bls_sig).into();
305 assert!(bad_bls_event.authenticate().is_err());
306
307 let wrong_schnorr_sig = sign_address_schnorr(&StateKeyPair::generate(), address);
308 let mut bad_schnorr_event = valid_event.clone();
309 bad_schnorr_event.schnorrSig = StateSignatureSol::from(wrong_schnorr_sig).into();
310 assert!(bad_schnorr_event.authenticate().is_err());
311 }
312 }
313}