challenge_response.rs
1 // Copyright (c) 2025 ADnet Contributors 2 // This file is part of the AlphaOS library. 3 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at: 7 8 // http://www.apache.org/licenses/LICENSE-2.0 9 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 use super::*; 17 18 use alphavm::{ 19 ledger::narwhal::Data, 20 prelude::{Field, FromBytes, ToBytes}, 21 }; 22 23 use std::borrow::Cow; 24 25 #[derive(Clone, Debug, PartialEq, Eq)] 26 pub struct ChallengeResponse<N: Network> { 27 pub genesis_header: Header<N>, 28 pub restrictions_id: Field<N>, 29 pub signature: Data<Signature<N>>, 30 pub nonce: u64, 31 } 32 33 impl<N: Network> MessageTrait for ChallengeResponse<N> { 34 /// Returns the message name. 35 #[inline] 36 fn name(&self) -> Cow<'static, str> { 37 "ChallengeResponse".into() 38 } 39 } 40 41 impl<N: Network> ToBytes for ChallengeResponse<N> { 42 fn write_le<W: io::Write>(&self, mut writer: W) -> io::Result<()> { 43 self.genesis_header.write_le(&mut writer)?; 44 self.restrictions_id.write_le(&mut writer)?; 45 self.signature.write_le(&mut writer)?; 46 self.nonce.write_le(&mut writer) 47 } 48 } 49 50 impl<N: Network> FromBytes for ChallengeResponse<N> { 51 fn read_le<R: io::Read>(mut reader: R) -> io::Result<Self> { 52 Ok(Self { 53 genesis_header: Header::read_le(&mut reader)?, 54 restrictions_id: Field::read_le(&mut reader)?, 55 signature: Data::read_le(&mut reader)?, 56 nonce: u64::read_le(reader)?, 57 }) 58 } 59 } 60 61 #[cfg(test)] 62 pub mod prop_tests { 63 use crate::ChallengeResponse; 64 use alphavm::{ 65 console::prelude::{FromBytes, ToBytes}, 66 ledger::{narwhal::Data, test_helpers::sample_genesis_block}, 67 prelude::{Field, PrivateKey, Signature, block::Header}, 68 utilities::rand::{TestRng, Uniform}, 69 }; 70 71 use bytes::{Buf, BufMut, BytesMut}; 72 use proptest::prelude::{BoxedStrategy, Strategy, any}; 73 use test_strategy::proptest; 74 75 type CurrentNetwork = alphavm::prelude::MainnetV0; 76 77 pub fn any_restrictions_id() -> Field<CurrentNetwork> { 78 Uniform::rand(&mut TestRng::default()) 79 } 80 81 pub fn any_signature() -> BoxedStrategy<Signature<CurrentNetwork>> { 82 (0..64) 83 .prop_map(|message_size| { 84 let rng = &mut TestRng::default(); 85 let message: Vec<_> = (0..message_size).map(|_| Uniform::rand(rng)).collect(); 86 let private_key = PrivateKey::new(rng).unwrap(); 87 Signature::sign(&private_key, &message, rng).unwrap() 88 }) 89 .boxed() 90 } 91 92 pub fn any_genesis_header() -> BoxedStrategy<Header<CurrentNetwork>> { 93 any::<u64>().prop_map(|seed| *sample_genesis_block(&mut TestRng::fixed(seed)).header()).boxed() 94 } 95 96 pub fn any_challenge_response() -> BoxedStrategy<ChallengeResponse<CurrentNetwork>> { 97 (any_genesis_header(), any_signature(), any::<u64>()) 98 .prop_map(|(genesis_header, sig, nonce)| ChallengeResponse { 99 genesis_header, 100 restrictions_id: any_restrictions_id(), 101 signature: Data::Object(sig), 102 nonce, 103 }) 104 .boxed() 105 } 106 107 #[proptest] 108 fn challenge_response_roundtrip(#[strategy(any_challenge_response())] original: ChallengeResponse<CurrentNetwork>) { 109 let mut buf = BytesMut::default().writer(); 110 ChallengeResponse::write_le(&original, &mut buf).unwrap(); 111 112 let deserialized: ChallengeResponse<CurrentNetwork> = 113 ChallengeResponse::read_le(buf.into_inner().reader()).unwrap(); 114 115 assert_eq!(original.genesis_header, deserialized.genesis_header); 116 assert_eq!( 117 original.signature.deserialize_blocking().unwrap(), 118 deserialized.signature.deserialize_blocking().unwrap() 119 ); 120 } 121 }