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, StateVerKey},
11 signature_key::{BLSKeyPair, BLSPubKey, BLSSignature},
12 traits::signature_key::SignatureKey,
13};
14use jf_signature::{
15 constants::{CS_ID_BLS_BN254, CS_ID_SCHNORR},
16 schnorr,
17};
18
19use crate::sol_types::{
20 StakeTableV2::{getVersionReturn, ConsensusKeysUpdatedV2, ValidatorRegisteredV2},
21 *,
22};
23
24#[derive(Debug, Clone, Copy, Default)]
25pub enum StakeTableContractVersion {
26 V1,
27 #[default]
28 V2,
29}
30
31impl TryFrom<getVersionReturn> for StakeTableContractVersion {
32 type Error = anyhow::Error;
33
34 fn try_from(value: getVersionReturn) -> anyhow::Result<Self> {
35 match value.majorVersion {
36 1 => Ok(StakeTableContractVersion::V1),
37 2 => Ok(StakeTableContractVersion::V2),
38 _ => anyhow::bail!("Unsupported stake table contract version: {:?}", value),
39 }
40 }
41}
42
43impl From<G2PointSol> for BLSPubKey {
44 fn from(value: G2PointSol) -> Self {
45 let point: G2Affine = value.into();
46 let mut bytes = vec![];
47 point
48 .into_group()
49 .serialize_uncompressed(&mut bytes)
50 .unwrap();
51 Self::deserialize_uncompressed(&bytes[..]).unwrap()
52 }
53}
54
55impl From<EdOnBN254PointSol> for StateVerKey {
56 fn from(value: EdOnBN254PointSol) -> Self {
57 let point: ark_ed_on_bn254::EdwardsAffine = value.into();
58 Self::from(point)
59 }
60}
61
62pub fn sign_address_bls(bls_key_pair: &BLSKeyPair, address: Address) -> G1PointSol {
63 bls_key_pair
64 .sign(&address.abi_encode(), CS_ID_BLS_BN254)
65 .sigma
66 .into_affine()
67 .into()
68}
69
70pub fn sign_address_schnorr(schnorr_key_pair: &StateKeyPair, address: Address) -> Bytes {
71 let msg = [hash_bytes_to_field(&address.abi_encode()).expect("hash to field works")];
72 let mut buf = vec![];
73 schnorr_key_pair
74 .sign(&msg, CS_ID_SCHNORR)
75 .serialize_compressed(&mut buf)
76 .expect("serialize works");
77 buf.into()
78}
79
80fn authenticate_schnorr_sig(
82 schnorr_vk: &StateVerKey,
83 address: Address,
84 schnorr_sig: &[u8],
85) -> Result<(), StakeTableSolError> {
86 let msg = [hash_bytes_to_field(&address.abi_encode()).expect("hash to field works")];
87 let sig = schnorr::Signature::<EdwardsConfig>::deserialize_compressed(schnorr_sig)?;
88 schnorr_vk.verify(&msg, &sig, CS_ID_SCHNORR)?;
89 Ok(())
90}
91
92fn authenticate_bls_sig(
94 bls_vk: &BLSPubKey,
95 address: Address,
96 bls_sig: &G1PointSol,
97) -> Result<(), StakeTableSolError> {
98 let msg = address.abi_encode();
99 let sig = {
100 let sigma_affine: ark_bn254::G1Affine = (*bls_sig).into();
101 BLSSignature {
102 sigma: sigma_affine.into_group(),
103 }
104 };
105 if !bls_vk.validate(&sig, &msg) {
106 return Err(StakeTableSolError::InvalidBlsSignature);
107 }
108 Ok(())
109}
110
111fn authenticate_stake_table_validator_event(
112 account: Address,
113 bls_vk: G2PointSol,
114 schnorr_vk: EdOnBN254PointSol,
115 bls_sig: G1PointSol,
116 schnorr_sig: &[u8],
117) -> Result<(), StakeTableSolError> {
118 let bls_vk = {
120 let bls_vk_inner: ark_bn254::G2Affine = bls_vk.into();
121 let bls_vk_inner = bls_vk_inner.into_group();
122
123 let mut ser_bytes: Vec<u8> = Vec::new();
125 bls_vk_inner.serialize_uncompressed(&mut ser_bytes).unwrap();
126 BLSPubKey::deserialize_uncompressed(&ser_bytes[..]).unwrap()
127 };
128 authenticate_bls_sig(&bls_vk, account, &bls_sig)?;
129
130 let schnorr_vk: StateVerKey = schnorr_vk.into();
131 authenticate_schnorr_sig(&schnorr_vk, account, schnorr_sig)?;
132 Ok(())
133}
134
135#[derive(Debug, thiserror::Error)]
137pub enum StakeTableSolError {
138 #[error("Failed to deserialize Schnorr signature")]
139 SchnorrSigDeserializationError(#[from] ark_serialize::SerializationError),
140 #[error("BLS signature invalid")]
141 InvalidBlsSignature,
142 #[error("Schnorr signature invalid")]
143 InvalidSchnorrSignature(#[from] jf_signature::SignatureError),
144}
145
146impl ValidatorRegisteredV2 {
147 pub fn authenticate(&self) -> Result<(), StakeTableSolError> {
149 authenticate_stake_table_validator_event(
150 self.account,
151 self.blsVK,
152 self.schnorrVK,
153 self.blsSig.into(),
154 &self.schnorrSig,
155 )?;
156 Ok(())
157 }
158}
159
160impl ConsensusKeysUpdatedV2 {
161 pub fn authenticate(&self) -> Result<(), StakeTableSolError> {
163 authenticate_stake_table_validator_event(
164 self.account,
165 self.blsVK,
166 self.schnorrVK,
167 self.blsSig.into(),
168 &self.schnorrSig,
169 )?;
170 Ok(())
171 }
172}
173
174#[cfg(test)]
175mod test {
176 use alloy::primitives::{Address, U256};
177 use hotshot_types::{
178 light_client::StateKeyPair,
179 signature_key::{BLSKeyPair, BLSPrivKey, BLSPubKey},
180 };
181
182 use super::{
183 authenticate_bls_sig, authenticate_schnorr_sig, sign_address_bls, sign_address_schnorr,
184 };
185 use crate::sol_types::G2PointSol;
186
187 fn check_round_trip(pk: BLSPubKey) {
188 let g2: G2PointSol = pk.to_affine().into();
189 let pk2: BLSPubKey = g2.into();
190 assert_eq!(pk2, pk, "Failed to roundtrip G2PointSol to BLSPubKey: {pk}");
191 }
192
193 #[test]
194 fn test_bls_g2_point_roundtrip() {
195 let mut rng = rand::thread_rng();
196 for _ in 0..100 {
197 let pk = (&BLSPrivKey::generate(&mut rng)).into();
198 check_round_trip(pk);
199 }
200 }
201
202 #[test]
203 fn test_bls_g2_point_alloy_migration_regression() {
204 let s = "BLS_VER_KEY~JlRLUrn0T_MltAJXaaojwk_CnCgd0tyPny_IGdseMBLBPv9nWabIPAaS-aHmn0ARu5YZHJ7mfmGQ-alW42tkJM663Lse-Is80fyA1jnRxPsHcJDnO05oW1M1SC5LeE8sXITbuhmtG2JdTAgmLqWOxbMRmVIqS1AQXqvGGXdo5qpd";
206 let pk: BLSPubKey = s.parse().unwrap();
207 check_round_trip(pk);
208 }
209
210 #[test]
211 fn test_schnorr_sigs() {
212 for _ in 0..10 {
213 let key_pair = StateKeyPair::generate();
214 let address = Address::random();
215 let sig = sign_address_schnorr(&key_pair, address);
216 authenticate_schnorr_sig(key_pair.ver_key_ref(), address, &sig).unwrap();
217
218 let sig = sign_address_schnorr(&StateKeyPair::generate(), address);
220 assert!(authenticate_schnorr_sig(key_pair.ver_key_ref(), address, &sig).is_err());
221
222 let mut bad_sig: Vec<u8> = sig.to_vec();
224 bad_sig[0] = bad_sig[0].wrapping_add(1);
225 assert!(authenticate_schnorr_sig(key_pair.ver_key_ref(), address, &bad_sig).is_err());
226 }
227 }
228
229 #[test]
230 fn test_bls_sigs() {
231 let key_pair = BLSKeyPair::generate(&mut rand::thread_rng());
232 let address = Address::random();
233 let sig = sign_address_bls(&key_pair, address);
234 authenticate_bls_sig(key_pair.ver_key_ref(), address, &sig).unwrap();
235
236 assert!(authenticate_bls_sig(
238 key_pair.ver_key_ref(),
239 address,
240 &sign_address_bls(&BLSKeyPair::generate(&mut rand::thread_rng()), address)
241 )
242 .is_err());
243
244 let mut sig = sig;
246 sig.x = sig.x.wrapping_add(U256::from(1));
247 assert!(authenticate_bls_sig(key_pair.ver_key_ref(), address, &sig).is_err());
248 }
249}