hotshot_contract_adapter/
stake_table.rs

1use 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
80// Helper function useful for unit tests.
81fn 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
92// Helper function useful for unit tests.
93fn 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    // TODO(alex): simplify this once jellyfish has `VerKey::from_affine()`
119    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        // the two unwrap are safe since it's BLSPubKey is just a wrapper around G2Projective
124        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/// Errors encountered when processing stake table events
136#[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    /// verified the BLS and Schnorr signatures in the event
148    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    /// verified the BLS and Schnorr signatures in the event
162    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        // This pubkey fails the roundtrip if "serialize_{un,}compressed" are mixed
205        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            // signed with wrong key
219            let sig = sign_address_schnorr(&StateKeyPair::generate(), address);
220            assert!(authenticate_schnorr_sig(key_pair.ver_key_ref(), address, &sig).is_err());
221
222            // manipulate one byte
223            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        // signed with wrong key
237        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        // tamper with the signature
245        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}