use crate::{error::VladError, Error};
use core::fmt;
use multi_base::Base;
use multi_codec::Codec;
use multi_key::{Multikey, Views};
use multi_sig::Multisig;
use multi_trait::{EncodeInto, EncodeIntoBuffer, Null, TryDecodeFrom};
use multi_util::{BaseEncoded, CodecInfo, DetectedEncoder, EncodingInfo};
pub const SIGIL: Codec = Codec::Vlad;
const WASM_MAGIC: [u8; 4] = [0x00, 0x61, 0x73, 0x6d];
pub type EncodedVlad = BaseEncoded<Vlad, DetectedEncoder>;
#[derive(Clone, Default, Eq, Ord, PartialEq, PartialOrd)]
pub struct Vlad(pub(crate) Multisig);
impl Vlad {
#[must_use]
pub const fn new(multisig: Multisig) -> Self {
Self(multisig)
}
pub fn validate(&self) -> Result<(), Error> {
if self.0.is_null() {
return Ok(());
}
if self.0.message.is_empty() {
return Err(VladError::NotCombined.into());
}
if self.0.message.len() < 4 || self.0.message[..4] != WASM_MAGIC {
return Err(VladError::InvalidWasm.into());
}
Ok(())
}
pub fn verify(&self, mk: &Multikey) -> Result<(), Error> {
let vv = mk.verify_view()?;
vv.verify(&self.0, Some(&self.0.message))?;
Ok(())
}
#[must_use]
pub const fn multisig(&self) -> &Multisig {
&self.0
}
#[must_use]
pub fn message(&self) -> &[u8] {
&self.0.message
}
#[must_use]
pub fn wasm(&self) -> &[u8] {
self.message()
}
}
impl CodecInfo for Vlad {
fn preferred_codec() -> Codec {
SIGIL
}
fn codec(&self) -> Codec {
Self::preferred_codec()
}
}
impl EncodingInfo for Vlad {
fn preferred_encoding() -> Base {
Base::Base32Lower
}
fn encoding(&self) -> Base {
Self::preferred_encoding()
}
}
impl From<Vlad> for Vec<u8> {
fn from(vlad: Vlad) -> Self {
let mut v = Self::default();
let sigil_bytes: Self = SIGIL.into();
v.extend_from_slice(&sigil_bytes);
let ms_bytes: Self = vlad.0.into();
v.extend_from_slice(&ms_bytes);
v
}
}
impl EncodeInto for Vlad {
fn encode_into(&self) -> Vec<u8> {
self.clone().into()
}
}
impl EncodeIntoBuffer for Vlad {
fn encode_into_buffer(&self, buffer: &mut Vec<u8>) {
buffer.extend_from_slice(&self.clone().encode_into());
}
}
impl<'a> TryFrom<&'a [u8]> for Vlad {
type Error = Error;
fn try_from(s: &'a [u8]) -> Result<Self, Self::Error> {
let (vlad, _) = Self::try_decode_from(s)?;
Ok(vlad)
}
}
impl<'a> TryDecodeFrom<'a> for Vlad {
type Error = Error;
fn try_decode_from(bytes: &'a [u8]) -> Result<(Self, &'a [u8]), Self::Error> {
let (sigil, ptr) = Codec::try_decode_from(bytes)?;
if sigil != SIGIL {
return Err(VladError::MissingSigil.into());
}
let (ms, ptr) = Multisig::try_decode_from(ptr)?;
let vlad = Self(ms);
vlad.validate()?;
Ok((vlad, ptr))
}
}
impl Null for Vlad {
fn null() -> Self {
Self(Multisig::null())
}
fn is_null(&self) -> bool {
*self == Self::null()
}
}
impl fmt::Debug for Vlad {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?} - {:?}", SIGIL, self.0)
}
}
#[derive(Clone, Debug, Default)]
pub struct Builder {
mk: Option<Multikey>,
message: Option<Vec<u8>>,
base_encoding: Option<Base>,
}
impl Builder {
#[must_use]
pub fn with_signing_key(mut self, mk: &Multikey) -> Self {
self.mk = Some(mk.clone());
self
}
#[must_use]
pub fn with_message(mut self, msg: &[u8]) -> Self {
self.message = Some(msg.to_vec());
self
}
#[must_use]
pub const fn with_base_encoding(mut self, base: Base) -> Self {
self.base_encoding = Some(base);
self
}
pub fn try_build_encoded(&self) -> Result<EncodedVlad, Error> {
Ok(EncodedVlad::new(
self.base_encoding.unwrap_or_else(Vlad::preferred_encoding),
self.try_build()?,
))
}
pub fn try_build(&self) -> Result<Vlad, Error> {
let mk = self.mk.as_ref().ok_or(VladError::MissingSigningKey)?;
let msg = self.message.as_ref().ok_or(VladError::MissingMessage)?;
if msg.len() < 4 || msg[..4] != WASM_MAGIC {
return Err(VladError::InvalidWasm.into());
}
let sv = mk.sign_view()?;
let ms = sv.sign(msg, true, None)?;
Ok(Vlad(ms))
}
}
#[cfg(test)]
mod tests {
use super::*;
use multi_key::EncodedMultikey;
use multi_util::{base_name, BaseIter};
fn test_signing_key() -> Multikey {
let s = "fba2480260874657374206b657901012064e58adf88f85cbec6a0448a0803f9d28cf9231a7141be413f83cf6aa883cd04";
EncodedMultikey::try_from(s).unwrap().to_inner()
}
fn test_wasm_message() -> Vec<u8> {
vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]
}
#[test]
fn test_default() {
let mk = test_signing_key();
let msg = test_wasm_message();
let vlad = Builder::default()
.with_signing_key(&mk)
.with_message(&msg)
.try_build()
.unwrap();
assert_eq!(Codec::Vlad, vlad.codec());
}
#[test]
fn test_binary_roundtrip() {
let mk = test_signing_key();
let msg = test_wasm_message();
let vlad = Builder::default()
.with_signing_key(&mk)
.with_message(&msg)
.try_build()
.unwrap();
let v: Vec<u8> = vlad.clone().into();
assert_eq!(vlad, Vlad::try_from(v.as_ref()).unwrap());
}
#[test]
fn test_encoded_roundtrip() {
let mk = test_signing_key();
let msg = test_wasm_message();
let vlad = Builder::default()
.with_signing_key(&mk)
.with_message(&msg)
.try_build_encoded()
.unwrap();
let s = vlad.to_string();
assert_eq!(vlad, EncodedVlad::try_from(s.as_str()).unwrap());
}
#[test]
fn test_encodings_roundtrip() {
let mk = test_signing_key();
let msg = test_wasm_message();
let itr: BaseIter = Base::Identity.into();
for encoding in itr {
let vlad = Builder::default()
.with_signing_key(&mk)
.with_message(&msg)
.with_base_encoding(encoding)
.try_build_encoded()
.unwrap();
let s = vlad.to_string();
println!("{}: ({}) {}", base_name(encoding), s.len(), s);
assert_eq!(vlad, EncodedVlad::try_from(s.as_str()).unwrap());
}
}
#[test]
fn test_signed_vlad() {
let mk = test_signing_key();
let msg = test_wasm_message();
let vlad = Builder::default()
.with_signing_key(&mk)
.with_message(&msg)
.with_base_encoding(Base::Base32Z)
.try_build_encoded()
.unwrap();
vlad.verify(&mk).unwrap();
let s = vlad.to_string();
let de = EncodedVlad::try_from(s.as_str()).unwrap();
assert_eq!(vlad, de);
assert_eq!(Base::Base32Z, de.encoding());
let vlad = vlad.to_inner();
let v: Vec<u8> = vlad.clone().into();
assert_eq!(vlad, Vlad::try_from(v.as_ref()).unwrap());
}
#[test]
fn test_verify_wrong_key() {
let mk = test_signing_key();
let msg = test_wasm_message();
let vlad = Builder::default()
.with_signing_key(&mk)
.with_message(&msg)
.try_build()
.unwrap();
let wrong_key_str = "fba2480260874657374206b657902012064e58adf88f85cbec6a0448a0803f9d28cf9231a7141be413f83cf6aa883cd04";
let wrong_key = EncodedMultikey::try_from(wrong_key_str);
if let Ok(wk) = wrong_key {
assert!(vlad.verify(&wk).is_err());
}
}
#[test]
fn test_message_accessor() {
let mk = test_signing_key();
let msg = test_wasm_message();
let vlad = Builder::default()
.with_signing_key(&mk)
.with_message(&msg)
.try_build()
.unwrap();
assert_eq!(vlad.message(), msg.as_slice());
}
#[test]
fn test_multisig_accessor() {
let mk = test_signing_key();
let msg = test_wasm_message();
let vlad = Builder::default()
.with_signing_key(&mk)
.with_message(&msg)
.try_build()
.unwrap();
assert!(!vlad.multisig().message.is_empty());
}
#[test]
fn test_null() {
let v1 = Vlad::null();
assert!(v1.is_null());
let v2 = Vlad::default();
assert_eq!(v1, v2);
assert!(v2.is_null());
}
#[test]
fn test_equality() {
let mk = test_signing_key();
let msg = test_wasm_message();
let vlad1 = Builder::default()
.with_signing_key(&mk)
.with_message(&msg)
.try_build()
.unwrap();
let v: Vec<u8> = vlad1.clone().into();
let vlad2 = Vlad::try_from(v.as_ref()).unwrap();
assert_eq!(vlad1, vlad2);
}
#[test]
fn test_missing_signing_key() {
let msg = test_wasm_message();
let result = Builder::default().with_message(&msg).try_build();
assert!(result.is_err());
}
#[test]
fn test_missing_message() {
let mk = test_signing_key();
let result = Builder::default().with_signing_key(&mk).try_build();
assert!(result.is_err());
}
#[test]
fn test_builder_rejects_non_wasm_message() {
let mk = test_signing_key();
let bad_msg = b"not a wasm module";
let result = Builder::default()
.with_signing_key(&mk)
.with_message(bad_msg)
.try_build();
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.kind(), "Vlad");
}
#[test]
fn test_builder_rejects_short_message() {
let mk = test_signing_key();
let short_msg = &[0x00, 0x61, 0x73]; let result = Builder::default()
.with_signing_key(&mk)
.with_message(short_msg)
.try_build();
assert!(result.is_err());
}
#[test]
fn test_decode_rejects_detached_multisig() {
let mk = test_signing_key();
let msg = test_wasm_message();
let sv = mk.sign_view().unwrap();
let ms = sv.sign(&msg, false, None).unwrap();
let mut v = Vec::default();
let sigil_bytes: Vec<u8> = SIGIL.into();
v.extend_from_slice(&sigil_bytes);
let ms_bytes: Vec<u8> = ms.into();
v.extend_from_slice(&ms_bytes);
let result = Vlad::try_from(v.as_slice());
assert!(result.is_err());
}
#[test]
fn test_decode_rejects_non_wasm_message() {
let mk = test_signing_key();
let bad_msg = b"not a wasm module at all!";
let sv = mk.sign_view().unwrap();
let ms = sv.sign(bad_msg.as_slice(), true, None).unwrap();
let mut v = Vec::default();
let sigil_bytes: Vec<u8> = SIGIL.into();
v.extend_from_slice(&sigil_bytes);
let ms_bytes: Vec<u8> = ms.into();
v.extend_from_slice(&ms_bytes);
let result = Vlad::try_from(v.as_slice());
assert!(result.is_err());
}
#[test]
fn test_plog_verification_flow() {
let mk = test_signing_key();
let msg = test_wasm_message();
let vlad = Builder::default()
.with_signing_key(&mk)
.with_message(&msg)
.try_build()
.unwrap();
vlad.validate().unwrap();
let wasm = vlad.wasm();
assert_eq!(wasm, msg.as_slice());
assert_eq!(&wasm[..4], &WASM_MAGIC);
vlad.verify(&mk).unwrap();
let bytes: Vec<u8> = vlad.clone().into();
let decoded = Vlad::try_from(bytes.as_ref()).unwrap();
assert_eq!(vlad, decoded);
decoded.validate().unwrap();
decoded.verify(&mk).unwrap();
assert_eq!(vlad.message(), vlad.wasm());
}
}