use alloc::sync::Arc;
use alloc::vec::Vec;
use keetanetwork_account::cert::{CertSigner, CertVerifier};
use keetanetwork_asn1::vote::TbsCertificate;
use keetanetwork_block::{AccountRef, BlockHash, BlockTime, Send};
use keetanetwork_crypto::verify::Verifiable;
use num_bigint::BigInt;
use crate::cert::{build_tbs, decode_wrapper, encode_tbs, encode_vote, DecodedVote, SignatureAlgo};
use crate::error::VoteError;
use crate::fee::Fees;
use crate::hash::{Hashable, VoteHash};
use crate::validation::ValidationConfig;
use crate::validity::Validity;
#[derive(Debug, Clone)]
pub struct UnsignedVote {
serial: BigInt,
issuer: AccountRef,
validity: Validity,
blocks: Vec<BlockHash>,
fees: Option<Fees>,
}
impl UnsignedVote {
pub fn try_new(
serial: BigInt,
issuer: AccountRef,
validity: Validity,
blocks: Vec<BlockHash>,
fees: Option<Fees>,
) -> Result<Self, VoteError> {
SignatureAlgo::from_issuer(&issuer)?;
if blocks.is_empty() {
return Err(VoteError::MalformedVoteNoBlocksFound);
}
Ok(Self { serial, issuer, validity, blocks, fees })
}
pub fn serial(&self) -> &BigInt {
&self.serial
}
pub fn issuer(&self) -> &AccountRef {
&self.issuer
}
pub fn validity(&self) -> &Validity {
&self.validity
}
pub fn blocks(&self) -> &[BlockHash] {
&self.blocks
}
pub fn fees(&self) -> Option<&Fees> {
self.fees.as_ref()
}
pub fn is_quote(&self) -> bool {
matches!(&self.fees, Some(fees) if fees.quote())
}
pub fn tbs_bytes(&self) -> Result<Vec<u8>, VoteError> {
let algo = SignatureAlgo::from_issuer(&self.issuer)?;
let tbs = build_tbs(&self.serial, algo, &self.issuer, self.validity, &self.blocks, self.fees.as_ref())?;
encode_tbs(&tbs)
}
pub fn sign(self, signer: &(impl CertSigner + ?Sized)) -> Result<Vote, VoteError> {
let algo = SignatureAlgo::from_issuer(&self.issuer)?;
let tbs = build_tbs(&self.serial, algo, &self.issuer, self.validity, &self.blocks, self.fees.as_ref())?;
let tbs_bytes = encode_tbs(&tbs)?;
let signature = signer.sign_for_cert(&tbs_bytes)?;
let serialized = encode_vote(tbs, algo, signature.clone())?;
let decoded = DecodedVote {
serial: self.serial,
signature_algo: algo,
issuer: self.issuer,
validity: self.validity,
blocks: self.blocks,
fees: self.fees,
signature,
tbs_bytes,
};
Ok(Vote { decoded: Arc::new(decoded), serialized: Arc::new(serialized) })
}
}
#[derive(Debug, Clone)]
pub struct Vote {
decoded: Arc<DecodedVote>,
serialized: Arc<Vec<u8>>,
}
impl Vote {
pub fn from_serialized(bytes: impl Into<Vec<u8>>) -> Result<Self, VoteError> {
let bytes: Vec<u8> = bytes.into();
let decoded = decode_wrapper(&bytes)?;
let tbs: TbsCertificate = build_tbs(
&decoded.serial,
decoded.signature_algo,
&decoded.issuer,
decoded.validity,
&decoded.blocks,
decoded.fees.as_ref(),
)?;
let canonical_tbs = encode_tbs(&tbs)?;
if canonical_tbs != decoded.tbs_bytes {
return Err(VoteError::MalformedNonCanonicalEncoding);
}
let canonical = encode_vote(tbs, decoded.signature_algo, decoded.signature.clone())?;
if canonical != bytes {
return Err(VoteError::MalformedNonCanonicalEncoding);
}
Ok(Self { decoded: Arc::new(decoded), serialized: Arc::new(bytes) })
}
pub fn verify(bytes: impl Into<Vec<u8>>) -> Result<Self, VoteError> {
let vote = Self::from_serialized(bytes)?;
vote.verify_signature()?;
vote.assert_quote_flag(false)?;
Ok(vote)
}
fn verify_signature(&self) -> Result<(), VoteError> {
self.decoded
.issuer
.verify_for_cert(&self.decoded.tbs_bytes, &self.decoded.signature)?;
Ok(())
}
fn assert_quote_flag(&self, expected_quote: bool) -> Result<(), VoteError> {
match self.fees() {
Some(fees) if fees.quote() != expected_quote => Err(VoteError::MalformedFeesQuoteInvalid),
_ => Ok(()),
}
}
pub fn as_bytes(&self) -> &[u8] {
&self.serialized
}
pub fn into_bytes(self) -> Vec<u8> {
Arc::try_unwrap(self.serialized).unwrap_or_else(|arc| (*arc).clone())
}
pub fn hash(&self) -> VoteHash {
VoteHash::of(self.as_bytes())
}
pub fn serial(&self) -> &BigInt {
&self.decoded.serial
}
pub fn issuer(&self) -> &AccountRef {
&self.decoded.issuer
}
pub fn validity(&self) -> &Validity {
&self.decoded.validity
}
pub fn blocks(&self) -> &[BlockHash] {
&self.decoded.blocks
}
pub fn fees(&self) -> Option<&Fees> {
self.decoded.fees.as_ref()
}
pub fn fee_send(&self, base: &AccountRef, priority: &[AccountRef]) -> Option<Send> {
self.fees()?.to_send(base, priority, self.issuer())
}
pub fn is_quote(&self) -> bool {
matches!(self.fees(), Some(fees) if fees.quote())
}
pub fn is_expired_at(&self, moment: BlockTime, config: ValidationConfig) -> bool {
self.decoded.validity.is_expired_at(moment, config)
}
pub fn is_permanent_at(&self, moment: BlockTime, config: ValidationConfig) -> bool {
self.decoded.validity.is_permanent_at(moment, config)
}
}
impl AsRef<[u8]> for Vote {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Hashable for Vote {
type Digest = VoteHash;
fn hash(&self) -> Self::Digest {
Vote::hash(self)
}
}
impl TryFrom<Vec<u8>> for Vote {
type Error = VoteError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
Self::from_serialized(bytes)
}
}
impl TryFrom<&[u8]> for Vote {
type Error = VoteError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
Self::from_serialized(bytes.to_vec())
}
}
impl Verifiable for Vote {
type Context = ();
type Error = VoteError;
fn verify(bytes: impl Into<Vec<u8>>, _context: ()) -> Result<Self, VoteError> {
Vote::verify(bytes)
}
}
#[derive(Debug, Clone)]
pub struct VoteQuote(Vote);
impl VoteQuote {
pub fn try_from_vote(vote: Vote) -> Result<Self, VoteError> {
vote.assert_quote_flag(true)?;
if !vote.is_quote() {
return Err(VoteError::FeeNotQuote);
}
Ok(Self(vote))
}
pub fn verify(bytes: impl Into<Vec<u8>>) -> Result<Self, VoteError> {
let vote = Vote::from_serialized(bytes)?;
vote.verify_signature()?;
Self::try_from_vote(vote)
}
pub fn ensure_active_at(self, moment: BlockTime, config: ValidationConfig) -> Result<Self, VoteError> {
self.0.validity().ensure_active_at(moment, config)?;
Ok(self)
}
pub fn as_vote(&self) -> &Vote {
&self.0
}
pub fn into_vote(self) -> Vote {
self.0
}
pub fn hash(&self) -> VoteHash {
self.0.hash()
}
}
impl AsRef<Vote> for VoteQuote {
fn as_ref(&self) -> &Vote {
&self.0
}
}
impl Hashable for VoteQuote {
type Digest = VoteHash;
fn hash(&self) -> Self::Digest {
self.0.hash()
}
}
impl TryFrom<Vec<u8>> for VoteQuote {
type Error = VoteError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
Self::try_from_vote(Vote::from_serialized(bytes)?)
}
}
impl TryFrom<&[u8]> for VoteQuote {
type Error = VoteError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
Self::try_from_vote(Vote::from_serialized(bytes.to_vec())?)
}
}
#[derive(Debug, Clone)]
pub struct PossiblyExpiredVote(Vote);
impl PossiblyExpiredVote {
pub fn verify(bytes: impl Into<Vec<u8>>) -> Result<Self, VoteError> {
Ok(Self(Vote::verify(bytes)?))
}
pub fn ensure_active_at(self, moment: BlockTime, config: ValidationConfig) -> Result<Vote, VoteError> {
self.0.validity().ensure_active_at(moment, config)?;
Ok(self.0)
}
pub fn as_vote(&self) -> &Vote {
&self.0
}
}
impl AsRef<Vote> for PossiblyExpiredVote {
fn as_ref(&self) -> &Vote {
&self.0
}
}
impl Hashable for PossiblyExpiredVote {
type Digest = VoteHash;
fn hash(&self) -> Self::Digest {
self.0.hash()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::{
ed25519_issuer, find_version_tag, moment, quote_fees, secp256k1_issuer, secp256r1_issuer, sign_simple_vote,
validity_seconds,
};
const DEFAULT_SERIAL: u64 = 11;
fn alice() -> AccountRef {
ed25519_issuer(b"alice")
}
fn default_blocks() -> Vec<BlockHash> {
vec![BlockHash::from([7u8; 32])]
}
fn signed_alice_vote(fees: Option<Fees>) -> Vote {
sign_simple_vote(&alice(), DEFAULT_SERIAL, validity_seconds(0, 60), default_blocks(), fees)
}
#[test]
fn test_unsigned_vote_requires_blocks() {
let result = UnsignedVote::try_new(BigInt::from(1u8), alice(), validity_seconds(0, 60), Vec::new(), None);
assert!(matches!(result, Err(VoteError::MalformedVoteNoBlocksFound)));
}
#[test]
fn test_sign_verify_round_trip_ed25519() -> Result<(), VoteError> {
let vote = signed_alice_vote(None);
let verified = Vote::verify(vote.as_bytes().to_vec())?;
assert_eq!(verified.serial(), &BigInt::from(DEFAULT_SERIAL));
assert_eq!(verified.blocks(), default_blocks().as_slice());
assert_eq!(verified.hash(), vote.hash());
Ok(())
}
#[test]
fn test_sign_verify_round_trip_secp256k1() -> Result<(), VoteError> {
let issuer = secp256k1_issuer(b"alice");
let vote = sign_simple_vote(&issuer, DEFAULT_SERIAL, validity_seconds(0, 60), default_blocks(), None);
Vote::verify(vote.as_bytes().to_vec())?;
Ok(())
}
#[test]
fn test_sign_verify_round_trip_secp256r1() -> Result<(), VoteError> {
let issuer = secp256r1_issuer(b"alice");
let vote = sign_simple_vote(&issuer, DEFAULT_SERIAL, validity_seconds(0, 60), default_blocks(), None);
Vote::verify(vote.as_bytes().to_vec())?;
Ok(())
}
#[test]
fn test_corrupted_signature_rejected() {
let mut tampered = signed_alice_vote(None).as_bytes().to_vec();
let last = tampered.len() - 1;
tampered[last] ^= 0xFF;
assert!(Vote::verify(tampered).is_err());
}
#[test]
fn test_corrupted_tbs_rejected() -> Result<(), VoteError> {
let mut tampered = signed_alice_vote(None).as_bytes().to_vec();
let position = find_version_tag(&tampered)?;
tampered[position + 4] = 0xff;
assert!(Vote::verify(tampered).is_err());
Ok(())
}
#[test]
fn test_quote_invariant_enforced() -> Result<(), VoteError> {
let issuer = alice();
let vote =
sign_simple_vote(&issuer, DEFAULT_SERIAL, validity_seconds(0, 60), default_blocks(), Some(quote_fees(1)));
let quote = VoteQuote::try_from_vote(vote.clone())?;
assert!(quote.as_vote().is_quote());
let other_vote = sign_simple_vote(&issuer, 12, *vote.validity(), vote.blocks().to_vec(), None);
assert!(matches!(VoteQuote::try_from_vote(other_vote), Err(VoteError::FeeNotQuote)));
Ok(())
}
#[test]
fn test_vote_quote_try_from_bytes() -> Result<(), VoteError> {
let quote_bytes =
sign_simple_vote(&alice(), DEFAULT_SERIAL, validity_seconds(0, 60), default_blocks(), Some(quote_fees(1)))
.into_bytes();
assert!(VoteQuote::try_from(quote_bytes.clone()).is_ok());
assert!(VoteQuote::try_from(quote_bytes.as_slice()).is_ok());
let plain_bytes = signed_alice_vote(None).into_bytes();
assert!(matches!(VoteQuote::try_from(plain_bytes), Err(VoteError::FeeNotQuote)));
Ok(())
}
#[test]
fn test_vote_verify_rejects_quote() {
let bytes = signed_alice_vote(Some(quote_fees(1))).into_bytes();
assert!(matches!(Vote::verify(bytes.clone()), Err(VoteError::MalformedFeesQuoteInvalid)));
assert!(matches!(PossiblyExpiredVote::verify(bytes.clone()), Err(VoteError::MalformedFeesQuoteInvalid)));
assert!(VoteQuote::verify(bytes).is_ok());
}
#[test]
fn test_vote_quote_ensure_active_at() -> Result<(), VoteError> {
let quote = VoteQuote::verify(
sign_simple_vote(&alice(), DEFAULT_SERIAL, validity_seconds(0, 60), default_blocks(), Some(quote_fees(1)))
.into_bytes(),
)?;
quote
.clone()
.ensure_active_at(moment(0), ValidationConfig::default())?;
let expired = quote.ensure_active_at(moment(10_000_000), ValidationConfig::default());
assert!(matches!(expired, Err(VoteError::Expired)));
Ok(())
}
#[test]
fn test_possibly_expired_promotion() -> Result<(), VoteError> {
let issuer = alice();
let vote = sign_simple_vote(&issuer, DEFAULT_SERIAL, validity_seconds(0, 1), default_blocks(), None);
let possibly = PossiblyExpiredVote::verify(vote.as_bytes().to_vec())?;
possibly
.clone()
.ensure_active_at(moment(0), ValidationConfig::default())?;
let result = possibly.ensure_active_at(moment(10_000_000), ValidationConfig::default());
assert!(matches!(result, Err(VoteError::Expired)));
Ok(())
}
#[test]
fn test_non_canonical_bytes_rejected() {
let mut tampered = signed_alice_vote(None).as_bytes().to_vec();
tampered.push(0x00);
assert!(Vote::from_serialized(tampered).is_err());
}
#[test]
fn test_hashable_trait_matches_inherent_method() {
let vote = signed_alice_vote(None);
assert_eq!(<Vote as Hashable>::hash(&vote), vote.hash());
}
#[test]
fn test_try_from_bytes_round_trip() -> Result<(), VoteError> {
let vote = signed_alice_vote(None);
let bytes = vote.as_bytes().to_vec();
assert_eq!(Vote::try_from(bytes.clone())?.hash(), vote.hash());
assert_eq!(Vote::try_from(bytes.as_slice())?.hash(), vote.hash());
Ok(())
}
#[test]
fn test_verifiable_matches_inherent_verify() -> Result<(), VoteError> {
let vote = signed_alice_vote(None);
let decoded = <Vote as Verifiable>::verify(vote.as_bytes().to_vec(), ())?;
assert_eq!(decoded.hash(), vote.hash());
Ok(())
}
}