use alloc::vec::Vec;
use core::{cmp::Ordering, marker::PhantomData};
use ed25519_dalek::{Signature, VerifyingKey};
use sedimentree_core::codec::{
decode::Decode,
encode::EncodeFields,
error::{DecodeError, InvalidSchema},
schema::Schema,
};
use thiserror::Error;
use crate::verified_signature::VerifiedSignature;
pub const SCHEMA_SIZE: usize = 4;
pub const VERIFYING_KEY_SIZE: usize = 32;
pub const SIGNATURE_SIZE: usize = 64;
pub const MIN_SIGNED_SIZE: usize = SCHEMA_SIZE + VERIFYING_KEY_SIZE + SIGNATURE_SIZE;
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Signed<T: Schema + EncodeFields + Decode> {
issuer: VerifyingKey,
signature: Signature,
bytes: Vec<u8>,
_marker: PhantomData<T>,
}
impl<T: Schema + EncodeFields + Decode> Clone for Signed<T> {
fn clone(&self) -> Self {
Self {
issuer: self.issuer,
signature: self.signature,
bytes: self.bytes.clone(),
_marker: PhantomData,
}
}
}
impl<T: Schema + EncodeFields + Decode> Signed<T> {
#[must_use]
pub const fn issuer(&self) -> VerifyingKey {
self.issuer
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
#[must_use]
pub fn payload_bytes(&self) -> &[u8] {
self.bytes
.get(..self.bytes.len().saturating_sub(SIGNATURE_SIZE))
.unwrap_or(&[])
}
#[must_use]
pub fn fields_bytes(&self) -> &[u8] {
let start = SCHEMA_SIZE + VERIFYING_KEY_SIZE;
let end = self.bytes.len().saturating_sub(SIGNATURE_SIZE);
self.bytes.get(start..end).unwrap_or(&[])
}
#[must_use]
pub const fn signature(&self) -> &Signature {
&self.signature
}
pub fn try_verify(&self) -> Result<VerifiedSignature<T>, VerificationError> {
VerifiedSignature::try_from_signed(self)
}
pub fn try_decode_trusted_payload(&self) -> Result<T, DecodeError> {
T::try_decode_fields(self.fields_bytes())
}
pub fn try_decode(mut bytes: Vec<u8>) -> Result<Self, DecodeError> {
if bytes.len() < T::MIN_SIZE {
return Err(DecodeError::MessageTooShort {
type_name: core::any::type_name::<T>(),
need: T::MIN_SIZE,
have: bytes.len(),
});
}
let schema: [u8; SCHEMA_SIZE] = bytes
.get(0..SCHEMA_SIZE)
.and_then(|s| s.try_into().ok())
.ok_or(DecodeError::MessageTooShort {
type_name: core::any::type_name::<T>(),
need: SCHEMA_SIZE,
have: bytes.len(),
})?;
if schema != T::SCHEMA {
return Err(InvalidSchema {
expected: T::SCHEMA,
got: schema,
}
.into());
}
let issuer_bytes: [u8; VERIFYING_KEY_SIZE] = bytes
.get(SCHEMA_SIZE..SCHEMA_SIZE + VERIFYING_KEY_SIZE)
.and_then(|s| s.try_into().ok())
.ok_or(DecodeError::MessageTooShort {
type_name: core::any::type_name::<T>(),
need: SCHEMA_SIZE + VERIFYING_KEY_SIZE,
have: bytes.len(),
})?;
let issuer = VerifyingKey::from_bytes(&issuer_bytes)
.map_err(|_| DecodeError::InvalidVerifyingKey)?;
let fields_start = SCHEMA_SIZE + VERIFYING_KEY_SIZE;
let fields_bytes = bytes
.get(fields_start..)
.ok_or(DecodeError::MessageTooShort {
type_name: core::any::type_name::<T>(),
need: fields_start + 1,
have: bytes.len(),
})?;
let payload = T::try_decode_fields(fields_bytes)?;
let fields_size = payload.fields_size();
let actual_size = SCHEMA_SIZE + VERIFYING_KEY_SIZE + fields_size + SIGNATURE_SIZE;
if bytes.len() < actual_size {
return Err(DecodeError::MessageTooShort {
type_name: core::any::type_name::<T>(),
need: actual_size,
have: bytes.len(),
});
}
let sig_start = fields_start + fields_size;
let sig_bytes: [u8; SIGNATURE_SIZE] = bytes
.get(sig_start..sig_start + SIGNATURE_SIZE)
.and_then(|s| s.try_into().ok())
.ok_or(DecodeError::MessageTooShort {
type_name: core::any::type_name::<T>(),
need: SIGNATURE_SIZE,
have: bytes.len().saturating_sub(sig_start),
})?;
let signature = Signature::from_bytes(&sig_bytes);
bytes.truncate(actual_size);
Ok(Self {
issuer,
signature,
bytes,
_marker: PhantomData,
})
}
#[must_use]
pub fn into_bytes(self) -> Vec<u8> {
self.bytes
}
pub async fn seal<K: future_form::FutureForm, S: crate::signer::Signer<K>>(
signer: &S,
payload: T,
) -> VerifiedSignature<T> {
let issuer = signer.verifying_key();
let total_size = payload.signed_size();
let mut bytes = Vec::with_capacity(total_size);
bytes.extend_from_slice(&T::SCHEMA);
bytes.extend_from_slice(issuer.as_bytes());
payload.encode_fields(&mut bytes);
let signature = signer.sign(&bytes).await;
bytes.extend_from_slice(&signature.to_bytes());
debug_assert_eq!(bytes.len(), total_size);
let result = Self {
issuer,
signature,
bytes,
_marker: PhantomData,
};
VerifiedSignature::from_parts(result, payload)
}
#[must_use]
pub fn from_parts(issuer: VerifyingKey, signature: Signature, payload: &T) -> Self {
let total_size = payload.signed_size();
let mut bytes = Vec::with_capacity(total_size);
bytes.extend_from_slice(&T::SCHEMA);
bytes.extend_from_slice(issuer.as_bytes());
payload.encode_fields(&mut bytes);
bytes.extend_from_slice(&signature.to_bytes());
Self {
issuer,
signature,
bytes,
_marker: PhantomData,
}
}
}
impl<T: Schema + EncodeFields + Decode> PartialEq for Signed<T> {
fn eq(&self, other: &Self) -> bool {
self.bytes == other.bytes
}
}
impl<T: Schema + EncodeFields + Decode> Eq for Signed<T> {}
impl<T: Schema + EncodeFields + Decode> core::hash::Hash for Signed<T> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.bytes.hash(state);
}
}
impl<T: Schema + EncodeFields + Decode> PartialOrd for Signed<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T: Schema + EncodeFields + Decode> Ord for Signed<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.bytes.cmp(&other.bytes)
}
}
#[derive(Debug, Clone, Copy, Error)]
pub enum VerificationError {
#[error("invalid signature")]
InvalidSignature,
#[error("codec error: {0}")]
Codec(#[from] DecodeError),
}
#[cfg(feature = "arbitrary")]
impl<'a, T: Schema + EncodeFields + Decode + arbitrary::Arbitrary<'a>> arbitrary::Arbitrary<'a>
for Signed<T>
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
use ed25519_dalek::{Signer as _, SigningKey};
let payload: T = u.arbitrary()?;
let key_bytes: [u8; 32] = u.arbitrary()?;
let signing_key = SigningKey::from_bytes(&key_bytes);
let issuer = signing_key.verifying_key();
let fields_size = payload.fields_size();
let total_size = SCHEMA_SIZE + VERIFYING_KEY_SIZE + fields_size + SIGNATURE_SIZE;
let mut bytes = Vec::with_capacity(total_size);
bytes.extend_from_slice(&T::SCHEMA);
bytes.extend_from_slice(issuer.as_bytes());
payload.encode_fields(&mut bytes);
let signature = signing_key.sign(&bytes);
bytes.extend_from_slice(&signature.to_bytes());
Ok(Self {
issuer,
signature,
bytes,
_marker: PhantomData,
})
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::indexing_slicing)]
mod tests {
use alloc::vec::Vec;
use sedimentree_core::codec::{
decode::{self, Decode},
encode::{self, EncodeFields},
error::DecodeError,
schema::{self, Schema},
};
use testresult::TestResult;
use crate::signer::memory::MemorySigner;
use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct TestPayload {
value: u64,
}
impl Schema for TestPayload {
const PREFIX: [u8; 2] = schema::SUBDUCTION_PREFIX;
const TYPE_BYTE: u8 = b'T';
const VERSION: u8 = 0;
}
impl EncodeFields for TestPayload {
fn encode_fields(&self, buf: &mut Vec<u8>) {
encode::u64(self.value, buf);
}
fn fields_size(&self) -> usize {
8
}
}
impl Decode for TestPayload {
const MIN_SIZE: usize = 4 + 32 + 8 + 64;
fn try_decode_fields(buf: &[u8]) -> Result<Self, DecodeError> {
let value = decode::u64(buf, 0)?;
Ok(Self { value })
}
}
fn test_signer(seed: u8) -> MemorySigner {
MemorySigner::from_bytes(&[seed; 32])
}
#[tokio::test]
async fn seal_produces_verifiable_bytes() -> TestResult {
let signer = test_signer(1);
let payload = TestPayload { value: 42 };
let verified = Signed::seal::<future_form::Sendable, _>(&signer, payload).await;
let sealed = verified.into_signed();
let bytes = sealed.as_bytes().to_vec();
let parsed = Signed::<TestPayload>::try_decode(bytes)?;
let verified = parsed.try_verify()?;
assert_eq!(verified.payload(), &payload);
assert_eq!(verified.issuer(), signer.verifying_key());
Ok(())
}
#[tokio::test]
async fn seal_wire_format_is_correct() {
let signer = test_signer(1);
let payload = TestPayload {
value: 0x1234_5678_9ABC_DEF0,
};
let verified = Signed::seal::<future_form::Sendable, _>(&signer, payload).await;
let bytes = verified.signed().as_bytes();
assert_eq!(bytes.len(), 108);
assert_eq!(&bytes[0..4], &TestPayload::SCHEMA);
assert_eq!(&bytes[4..36], signer.verifying_key().as_bytes());
assert_eq!(&bytes[36..44], &0x1234_5678_9ABC_DEF0_u64.to_be_bytes());
assert_eq!(bytes.len() - 44, 64);
}
#[tokio::test]
async fn tampered_bytes_fail_verification() -> TestResult {
let signer = test_signer(1);
let payload = TestPayload { value: 42 };
let verified = Signed::seal::<future_form::Sendable, _>(&signer, payload).await;
let mut bytes = verified.signed().as_bytes().to_vec();
bytes[36] ^= 0xFF;
let parsed = Signed::<TestPayload>::try_decode(bytes)?;
let result = parsed.try_verify();
assert!(result.is_err(), "tampered bytes should fail verification");
Ok(())
}
}