feat(applying): add remaining validations for Byron era (#325)
This commit is contained in:
parent
1635f8eb95
commit
85d4470ddd
7 changed files with 562 additions and 224 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(()),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue