feat(applying): add remaining validations for Byron era (#325)

This commit is contained in:
Maico Leberle 2023-11-09 14:50:46 -03:00 committed by GitHub
parent 1635f8eb95
commit 85d4470ddd
7 changed files with 562 additions and 224 deletions

View file

@ -1,19 +1,43 @@
//! Utilities required for Byron-era transaction validation.
use crate::types::{ByronProtParams, MultiEraInput, UTxOs, ValidationError, ValidationResult};
use std::borrow::Cow;
use pallas_primitives::byron::{MintedTxPayload, Tx};
use crate::types::{
ByronProtParams, MultiEraInput, MultiEraOutput, SigningTag, UTxOs, ValidationError,
ValidationResult,
};
use pallas_addresses::byron::{
AddrAttrs, AddrType, AddressId, AddressPayload, ByronAddress, SpendingData,
};
use pallas_codec::{
minicbor::{encode, Encoder},
utils::CborWrap,
};
use pallas_crypto::{
hash::Hash,
key::ed25519::{PublicKey, Signature},
};
use pallas_primitives::byron::{
Address, MintedTxPayload, PubKey, Signature as ByronSignature, Twit, Tx, TxIn, TxOut,
};
use pallas_traverse::OriginalHash;
// TODO: implement missing validation rules.
pub fn validate_byron_tx(
mtxp: &MintedTxPayload,
utxos: &UTxOs,
_prot_pps: &ByronProtParams,
prot_pps: &ByronProtParams,
prot_magic: &u32,
) -> ValidationResult {
let tx: &Tx = &mtxp.transaction;
let size: u64 = get_tx_size(tx)?;
check_ins_not_empty(tx)?;
check_outs_not_empty(tx)?;
check_ins_in_utxos(tx, utxos)
check_ins_in_utxos(tx, utxos)?;
check_outs_have_lovelace(tx)?;
check_fees(tx, &size, utxos, prot_pps)?;
check_size(&size, prot_pps)?;
check_witnesses(mtxp, utxos, prot_magic)
}
fn check_ins_not_empty(tx: &Tx) -> ValidationResult {
@ -38,3 +62,192 @@ fn check_ins_in_utxos(tx: &Tx, utxos: &UTxOs) -> ValidationResult {
}
Ok(())
}
fn check_outs_have_lovelace(tx: &Tx) -> ValidationResult {
for output in tx.outputs.iter() {
if output.amount == 0 {
return Err(ValidationError::OutputWithoutLovelace);
}
}
Ok(())
}
fn check_fees(tx: &Tx, size: &u64, utxos: &UTxOs, prot_pps: &ByronProtParams) -> ValidationResult {
let mut inputs_balance: u64 = 0;
for input in tx.inputs.iter() {
match utxos
.get(&MultiEraInput::from_byron(input))
.and_then(MultiEraOutput::as_byron)
{
Some(byron_utxo) => inputs_balance += byron_utxo.amount,
None => return Err(ValidationError::UnableToComputeFees),
}
}
let mut outputs_balance: u64 = 0;
for output in tx.outputs.iter() {
outputs_balance += output.amount
}
let total_balance: u64 = inputs_balance - outputs_balance;
let min_fees: u64 = prot_pps.min_fees_const + prot_pps.min_fees_factor * size;
if total_balance < min_fees {
return Err(ValidationError::FeesBelowMin);
}
Ok(())
}
fn check_size(size: &u64, prot_pps: &ByronProtParams) -> ValidationResult {
if *size > prot_pps.max_tx_size {
return Err(ValidationError::MaxTxSizeExceeded);
}
Ok(())
}
fn get_tx_size(tx: &Tx) -> Result<u64, ValidationError> {
let mut buff: Vec<u8> = Vec::new();
match encode(tx, &mut buff) {
Ok(()) => Ok(buff.len() as u64),
Err(_) => Err(ValidationError::UnknownTxSize),
}
}
pub enum TaggedSignature<'a> {
PkWitness(&'a ByronSignature),
RedeemWitness(&'a ByronSignature),
}
fn check_witnesses(mtxp: &MintedTxPayload, utxos: &UTxOs, prot_magic: &u32) -> ValidationResult {
let tx: &Tx = &mtxp.transaction;
let tx_hash: Hash<32> = mtxp.transaction.original_hash();
let witnesses: Vec<(&PubKey, TaggedSignature)> = tag_witnesses(&mtxp.witness)?;
let tx_inputs: &Vec<TxIn> = &tx.inputs;
for input in tx_inputs {
let tx_out: &TxOut = find_tx_out(input, utxos)?;
let (pub_key, sign): (&PubKey, &TaggedSignature) = find_raw_witness(tx_out, &witnesses)?;
let public_key: PublicKey = get_verification_key(pub_key);
let data_to_verify: Vec<u8> = get_data_to_verify(sign, prot_magic, &tx_hash)?;
let signature: Signature = get_signature(sign);
if !public_key.verify(data_to_verify, &signature) {
return Err(ValidationError::WrongSignature);
}
}
Ok(())
}
fn tag_witnesses(wits: &[Twit]) -> Result<Vec<(&PubKey, TaggedSignature)>, ValidationError> {
let mut res: Vec<(&PubKey, TaggedSignature)> = Vec::new();
for wit in wits.iter() {
match wit {
Twit::PkWitness(CborWrap((pk, sig))) => {
res.push((pk, TaggedSignature::PkWitness(sig)));
}
Twit::RedeemWitness(CborWrap((pk, sig))) => {
res.push((pk, TaggedSignature::RedeemWitness(sig)));
}
_ => return Err(ValidationError::UnableToProcessWitnesses),
}
}
Ok(res)
}
fn find_tx_out<'a>(input: &'a TxIn, utxos: &'a UTxOs) -> Result<&'a TxOut, ValidationError> {
let key: MultiEraInput = MultiEraInput::Byron(Box::new(Cow::Borrowed(input)));
utxos
.get(&key)
.ok_or(ValidationError::InputMissingInUTxO)?
.as_byron()
.ok_or(ValidationError::InputMissingInUTxO)
}
fn find_raw_witness<'a>(
tx_out: &TxOut,
witnesses: &'a Vec<(&'a PubKey, TaggedSignature<'a>)>,
) -> Result<(&'a PubKey, &'a TaggedSignature<'a>), ValidationError> {
let address: ByronAddress = mk_byron_address(&tx_out.address);
let addr_payload: AddressPayload = address
.decode()
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
let root: AddressId = addr_payload.root;
let attr: AddrAttrs = addr_payload.attributes;
let addr_type: AddrType = addr_payload.addrtype;
for (pub_key, sign) in witnesses {
if redeems(pub_key, sign, &root, &attr, &addr_type) {
match addr_type {
AddrType::PubKey | AddrType::Redeem => return Ok((pub_key, sign)),
_ => return Err(ValidationError::UnableToProcessWitnesses),
}
}
}
Err(ValidationError::MissingWitness)
}
fn mk_byron_address(addr: &Address) -> ByronAddress {
ByronAddress::new((*addr.payload.0).as_slice(), addr.crc)
}
fn redeems(
pub_key: &PubKey,
sign: &TaggedSignature,
root: &AddressId,
attrs: &AddrAttrs,
addr_type: &AddrType,
) -> bool {
let spending_data: SpendingData = mk_spending_data(pub_key, addr_type);
let hash_to_check: AddressId =
AddressPayload::hash_address_id(addr_type, &spending_data, attrs);
hash_to_check == *root && convert_to_addr_type(sign) == *addr_type
}
fn convert_to_addr_type(sign: &TaggedSignature) -> AddrType {
match sign {
TaggedSignature::PkWitness(_) => AddrType::PubKey,
TaggedSignature::RedeemWitness(_) => AddrType::Redeem,
}
}
fn mk_spending_data(pub_key: &PubKey, addr_type: &AddrType) -> SpendingData {
match addr_type {
AddrType::PubKey => SpendingData::PubKey(pub_key.clone()),
AddrType::Redeem => SpendingData::Redeem(pub_key.clone()),
_ => unreachable!(),
}
}
fn get_verification_key(pk: &PubKey) -> PublicKey {
let mut trunc_len: [u8; PublicKey::SIZE] = [0; PublicKey::SIZE];
trunc_len.copy_from_slice(&pk.as_slice()[0..PublicKey::SIZE]);
From::<[u8; PublicKey::SIZE]>::from(trunc_len)
}
fn get_data_to_verify(
sign: &TaggedSignature,
prot_magic: &u32,
tx_hash: &Hash<32>,
) -> Result<Vec<u8>, ValidationError> {
let buff: &mut Vec<u8> = &mut Vec::new();
let mut enc: Encoder<&mut Vec<u8>> = Encoder::new(buff);
match sign {
TaggedSignature::PkWitness(_) => {
enc.encode(SigningTag::Tx as u64)
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
}
TaggedSignature::RedeemWitness(_) => {
enc.encode(SigningTag::RedeemTx as u64)
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
}
}
enc.encode(prot_magic)
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
enc.encode(tx_hash)
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
Ok(enc.into_writer().clone())
}
fn get_signature(tagged_signature: &TaggedSignature<'_>) -> Signature {
let inner_sig = match tagged_signature {
TaggedSignature::PkWitness(sign) => sign,
TaggedSignature::RedeemWitness(sign) => sign,
};
let mut trunc_len: [u8; Signature::SIZE] = [0; Signature::SIZE];
trunc_len.copy_from_slice(inner_sig.as_slice());
From::<[u8; Signature::SIZE]>::from(trunc_len)
}

View file

@ -7,17 +7,17 @@ use byron::validate_byron_tx;
use pallas_traverse::{MultiEraTx, MultiEraTx::Byron as ByronTxPayload};
pub use types::{
MultiEraProtParams, MultiEraProtParams::Byron as ByronProtParams, UTxOs, ValidationResult,
};
pub use types::{Environment, MultiEraProtParams, UTxOs, ValidationResult};
pub fn validate(
metx: &MultiEraTx,
utxos: &UTxOs,
prot_pps: &MultiEraProtParams,
) -> ValidationResult {
match (metx, prot_pps) {
(ByronTxPayload(mtxp), ByronProtParams(bpp)) => validate_byron_tx(mtxp, utxos, bpp),
pub fn validate(metx: &MultiEraTx, utxos: &UTxOs, env: &Environment) -> ValidationResult {
match (metx, env) {
(
ByronTxPayload(mtxp),
Environment {
prot_params: MultiEraProtParams::Byron(bpp),
prot_magic,
},
) => validate_byron_tx(mtxp, utxos, bpp, prot_magic),
// TODO: implement the rest of the eras.
_ => Ok(()),
}

View file

@ -1,29 +1,51 @@
//! Base types used for validating transactions in each era.
use std::{borrow::Cow, collections::HashMap};
use std::collections::HashMap;
pub use pallas_traverse::{MultiEraInput, MultiEraOutput};
pub type UTxOs<'b> = HashMap<MultiEraInput<'b>, MultiEraOutput<'b>>;
// TODO: add a field for each protocol parameter in the Byron era.
#[derive(Debug, Clone)]
pub struct ByronProtParams;
pub struct ByronProtParams {
pub min_fees_const: u64,
pub min_fees_factor: u64,
pub max_tx_size: u64,
}
// TODO: add variants for the other eras.
#[derive(Debug)]
#[non_exhaustive]
pub enum MultiEraProtParams<'b> {
Byron(Box<Cow<'b, ByronProtParams>>),
pub enum MultiEraProtParams {
Byron(ByronProtParams),
}
#[derive(Debug)]
pub struct Environment {
pub prot_params: MultiEraProtParams,
pub prot_magic: u32,
}
#[non_exhaustive]
pub enum SigningTag {
Tx = 0x01,
RedeemTx = 0x02,
}
// TODO: replace this generic variant with validation-rule-specific ones.
#[derive(Debug)]
#[non_exhaustive]
pub enum ValidationError {
InputMissingInUTxO,
TxInsEmpty,
TxOutsEmpty,
OutputWithoutLovelace,
UnknownTxSize,
UnableToComputeFees,
FeesBelowMin,
MaxTxSizeExceeded,
UnableToProcessWitnesses,
MissingWitness,
WrongSignature,
}
pub type ValidationResult = Result<(), ValidationError>;