From 6f58a5e5a3805f15beeea733b2cf93f790eb1162 Mon Sep 17 00:00:00 2001 From: Santiago Carmuega Date: Wed, 8 Dec 2021 07:52:48 -0300 Subject: [PATCH] chore(alonzo): ensure isomorphic decoding / encoding --- pallas-alonzo/src/lib.rs | 506 +++++++++++++++++++++++++++++++++++---- 1 file changed, 456 insertions(+), 50 deletions(-) diff --git a/pallas-alonzo/src/lib.rs b/pallas-alonzo/src/lib.rs index 5917340..6fd9f17 100644 --- a/pallas-alonzo/src/lib.rs +++ b/pallas-alonzo/src/lib.rs @@ -3,9 +3,9 @@ //! Handcrafted, idiomatic rust artifacts based on based on the [Alonzo CDDL](https://github.com/input-output-hk/cardano-ledger/blob/master/eras/alonzo/test-suite/cddl-files/alonzo.cddl) file in IOHK repo. use log::warn; -use minicbor::{bytes::ByteVec, data::Tag}; +use minicbor::{bytes::ByteVec, data::Tag, Decode}; use minicbor_derive::{Decode, Encode}; -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, BTreeSet}; #[derive(Debug, PartialEq, PartialOrd, Eq, Ord)] pub struct SkipCbor {} @@ -15,6 +15,7 @@ impl<'b, const N: usize> minicbor::Decode<'b> for SkipCbor { { let probe = d.probe(); warn!("skipped cbor value {}: {:?}", N, probe.datatype()?); + println!("skipped cbor value {}: {:?}", N, probe.datatype()?); } d.skip()?; @@ -33,10 +34,10 @@ impl minicbor::Encode for SkipCbor { pub type SomeSkipCbor = SkipCbor<0>; -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] pub struct VrfCert(#[n(0)] ByteVec, #[n(1)] ByteVec); -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] pub struct HeaderBody { #[n(0)] pub block_number: u64, @@ -87,7 +88,7 @@ pub struct HeaderBody { #[derive(Encode, Decode, Debug, PartialEq)] pub struct KesSignature {} -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Encode, Decode, Debug, PartialEq, Clone)] pub struct Header { #[n(0)] pub header_body: HeaderBody, @@ -111,7 +112,7 @@ pub type PolicyId = ScriptHash; pub type AssetName = ByteVec; -pub type Multiasset = HashMap>; +pub type Multiasset = BTreeMap>; pub type Mint = Multiasset; @@ -146,7 +147,19 @@ impl minicbor::encode::Encode for Value { &self, e: &mut minicbor::Encoder, ) -> Result<(), minicbor::encode::Error> { - todo!() + // TODO: check how to deal with uint variants (u32 vs u64) + match self { + Value::Coin(coin) => { + e.encode(coin)?; + } + Value::Multiasset(coin, other) => { + e.array(2)?; + e.encode(coin)?; + e.encode(other)?; + } + }; + + Ok(()) } } @@ -208,7 +221,32 @@ impl minicbor::encode::Encode for Relay { &self, e: &mut minicbor::Encoder, ) -> Result<(), minicbor::encode::Error> { - todo!() + match self { + Relay::SingleHostAddr(a, b, c) => { + e.array(4)?; + e.encode(0)?; + e.encode(a)?; + e.encode(b)?; + e.encode(c)?; + + Ok(()) + } + Relay::SingleHostName(a, b) => { + e.array(3)?; + e.encode(1)?; + e.encode(a)?; + e.encode(b)?; + + Ok(()) + } + Relay::MultiHostName(a) => { + e.array(2)?; + e.encode(2)?; + e.encode(a)?; + + Ok(()) + } + } } } @@ -249,7 +287,13 @@ impl minicbor::encode::Encode for RationalNumber { &self, e: &mut minicbor::Encoder, ) -> Result<(), minicbor::encode::Error> { - todo!() + // TODO: check if this is the correct tag + e.tag(Tag::Unassigned(30))?; + e.array(2)?; + e.encode(self.numerator)?; + e.encode(self.denominator)?; + + Ok(()) } } @@ -281,7 +325,22 @@ impl minicbor::encode::Encode for StakeCredential { &self, e: &mut minicbor::Encoder, ) -> Result<(), minicbor::encode::Error> { - todo!() + match self { + StakeCredential::AddrKeyhash(a) => { + e.array(2)?; + e.encode(0)?; + e.encode(a)?; + + Ok(()) + } + StakeCredential::Scripthash(a) => { + e.array(2)?; + e.encode(0)?; + e.encode(a)?; + + Ok(()) + } + } } } @@ -375,7 +434,80 @@ impl minicbor::encode::Encode for Certificate { &self, e: &mut minicbor::Encoder, ) -> Result<(), minicbor::encode::Error> { - todo!() + match self { + Certificate::StakeRegistration(a) => { + e.array(2)?; + e.u16(0)?; + e.encode(a)?; + + Ok(()) + } + Certificate::StakeDeregistration(a) => { + e.array(2)?; + e.u16(1)?; + e.encode(a)?; + + Ok(()) + } + Certificate::StakeDelegation(a, b) => { + e.array(3)?; + e.u16(2)?; + e.encode(a)?; + e.encode(b)?; + + Ok(()) + } + Certificate::PoolRegistration { + operator, + vrf_keyhash, + pledge, + cost, + margin, + reward_account, + pool_owners, + relays, + pool_metadata, + } => { + e.array(10)?; + e.u16(3)?; + + e.encode(operator)?; + e.encode(vrf_keyhash)?; + e.encode(pledge)?; + e.encode(cost)?; + e.encode(margin)?; + e.encode(reward_account)?; + e.encode(pool_owners)?; + e.encode(relays)?; + e.encode(pool_metadata)?; + + Ok(()) + } + Certificate::PoolRetirement(a, b) => { + e.array(3)?; + e.u16(4)?; + e.encode(a)?; + e.encode(b)?; + + Ok(()) + } + Certificate::GenesisKeyDelegation(a, b, c) => { + e.array(4)?; + e.u16(5)?; + e.encode(a)?; + e.encode(b)?; + e.encode(c)?; + + Ok(()) + } + Certificate::MoveInstantaneousRewardsCert(a) => { + e.array(3)?; + e.u16(6)?; + e.encode(a)?; + + Ok(()) + } + } } } @@ -388,7 +520,7 @@ pub enum NetworkId { Two, } -#[derive(Encode, Decode, Debug, PartialEq)] +#[derive(Decode, Debug, PartialEq)] #[cbor(map)] pub struct TransactionBody { #[n(0)] @@ -434,6 +566,96 @@ pub struct TransactionBody { pub network_id: Option, } +// Can't derive encode for TransactionBody because it seems to require a very +// particular order for each key in the map +impl minicbor::encode::Encode for TransactionBody { + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + let map_size = 3 + + self.ttl.map_or(0, |_| 1) + + self.certificates.as_ref().map_or(0, |_| 1) + + self.withdrawals.as_ref().map_or(0, |_| 1) + + self.update.as_ref().map_or(0, |_| 1) + + self.auxiliary_data_hash.as_deref().map_or(0, |_| 1) + + self.validity_interval_start.map_or(0, |_| 1) + + self.mint.as_ref().map_or(0, |_| 1) + + self.script_data_hash.as_deref().map_or(0, |_| 1) + + self.collateral.as_deref().map_or(0, |_| 1) + + self.required_signers.as_deref().map_or(0, |_| 1) + + self.network_id.as_ref().map_or(0, |_| 1); + + e.map(map_size)?; + + e.encode(0)?; + e.encode(&self.inputs)?; + + if let Some(x) = &self.collateral { + e.encode(13)?; + e.encode(x)?; + } + + e.encode(1)?; + e.encode(&self.outputs)?; + + e.encode(2)?; + e.encode(&self.fee)?; + + if let Some(x) = &self.ttl { + e.encode(3)?; + e.encode(x)?; + } + + if let Some(x) = &self.certificates { + e.encode(4)?; + e.encode(x)?; + } + + if let Some(x) = &self.withdrawals { + e.encode(5)?; + e.encode(x)?; + } + + if let Some(x) = &self.update { + e.encode(6)?; + e.encode(x)?; + } + + if let Some(x) = &self.validity_interval_start { + e.encode(8)?; + e.encode(x)?; + } + + if let Some(x) = &self.required_signers { + e.encode(14)?; + e.encode(x)?; + } + + if let Some(x) = &self.script_data_hash { + e.encode(11)?; + e.encode(x)?; + } + + if let Some(x) = &self.network_id { + e.encode(15)?; + e.encode(x)?; + } + + if let Some(x) = &self.auxiliary_data_hash { + e.encode(7)?; + e.encode(x)?; + } + + if let Some(x) = &self.mint { + e.encode(9)?; + e.encode(x)?; + } + + Ok(()) + } +} + #[derive(Encode, Decode, Debug, PartialEq)] pub struct VKeyWitness { #[n(0)] @@ -477,7 +699,37 @@ impl minicbor::encode::Encode for NativeScript { &self, e: &mut minicbor::Encoder, ) -> Result<(), minicbor::encode::Error> { - todo!() + e.array(2)?; + + match self { + NativeScript::ScriptPubkey(v) => { + e.encode(0)?; + e.encode(v)?; + } + NativeScript::ScriptAll(v) => { + e.encode(1)?; + e.encode(v)?; + } + NativeScript::ScriptAny(v) => { + e.encode(2)?; + e.encode(v)?; + } + NativeScript::ScriptNOfK(a, b) => { + e.encode(3)?; + e.encode(a)?; + e.encode(b)?; + } + NativeScript::InvalidBefore(v) => { + e.encode(4)?; + e.encode(v)?; + } + NativeScript::InvalidHereafter(v) => { + e.encode(5)?; + e.encode(v)?; + } + } + + Ok(()) } } @@ -490,6 +742,7 @@ pub enum PlutusData { BitInt(u64), BoundedBytes(ByteVec), Array(Vec), + ArrayIndef(IndefVec), } impl<'b> minicbor::decode::Decode<'b> for PlutusData { @@ -509,7 +762,7 @@ impl<'b> minicbor::decode::Decode<'b> for PlutusData { minicbor::data::Type::U64 => Ok(PlutusData::BitInt(d.decode()?)), minicbor::data::Type::Bytes => Ok(PlutusData::BoundedBytes(d.decode()?)), minicbor::data::Type::Array => Ok(PlutusData::Array(d.decode()?)), - minicbor::data::Type::ArrayIndef => Ok(PlutusData::Array(d.decode()?)), + minicbor::data::Type::ArrayIndef => Ok(PlutusData::ArrayIndef(d.decode()?)), _ => Err(minicbor::decode::Error::Message( "bad cbor data type for plutus data", )), @@ -522,7 +775,69 @@ impl minicbor::encode::Encode for PlutusData { &self, e: &mut minicbor::Encoder, ) -> Result<(), minicbor::encode::Error> { - todo!() + match self { + PlutusData::Constr(a) => { + e.encode(a)?; + Ok(()) + } + PlutusData::Map(a) => { + e.encode(a)?; + Ok(()) + } + PlutusData::BitInt(a) => { + e.encode(a)?; + Ok(()) + } + PlutusData::BoundedBytes(a) => { + e.encode(a)?; + Ok(()) + } + PlutusData::Array(a) => { + e.encode(a)?; + Ok(()) + } + PlutusData::ArrayIndef(a) => { + e.encode(a)?; + Ok(()) + } + } + } +} + +/// A struct that forces encode / decode using indef arrays +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct IndefVec(Vec); + +impl<'b, A> minicbor::decode::Decode<'b> for IndefVec +where + A: minicbor::decode::Decode<'b>, +{ + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + let values: Vec = d.decode()?; + + Ok(IndefVec(values)) + } +} + +impl minicbor::encode::Encode for IndefVec +where + A: minicbor::encode::Encode, +{ + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + if self.0.len() > 0 { + e.begin_array()?; + for v in &self.0 { + e.encode(v)?; + } + e.end()?; + } else { + e.array(0)?; + } + + Ok(()) } } @@ -530,7 +845,7 @@ impl minicbor::encode::Encode for PlutusData { pub struct Constr { pub tag: u64, pub prefix: Option, - pub values: Vec, + pub values: IndefVec, } impl<'b, A> minicbor::decode::Decode<'b> for Constr @@ -576,7 +891,22 @@ where &self, e: &mut minicbor::Encoder, ) -> Result<(), minicbor::encode::Error> { - todo!() + e.tag(Tag::Unassigned(self.tag))?; + + match self.tag { + 102 => { + e.array(2)?; + e.encode(self.prefix)?; + e.encode(&self.values)?; + + Ok(()) + } + _ => { + e.encode(&self.values)?; + + Ok(()) + } + } } } @@ -604,16 +934,38 @@ pub enum RedeemerTag { #[derive(Encode, Decode, Debug, PartialEq)] pub struct Redeemer { #[n(0)] - tag: RedeemerTag, + pub tag: RedeemerTag, #[n(1)] - index: u32, + pub index: u32, #[n(2)] - data: PlutusData, + pub data: PlutusData, #[n(3)] - ex_units: ExUnits, + pub ex_units: ExUnits, +} + +/* bootstrap_witness = +[ public_key : $vkey +, signature : $signature +, chain_code : bytes .size 32 +, attributes : bytes +] */ + +#[derive(Encode, Decode, Debug, PartialEq)] +pub struct BootstrapWitness { + #[n(0)] + pub public_key: ByteVec, + + #[n(1)] + pub signature: ByteVec, + + #[n(2)] + pub chain_code: ByteVec, + + #[n(3)] + pub attributes: ByteVec, } #[derive(Encode, Decode, Debug, PartialEq)] @@ -626,7 +978,7 @@ pub struct TransactionWitnessSet { pub native_script: Option>, #[n(2)] - pub bootstrap_witness: Option>>, + pub bootstrap_witness: Option>, #[n(3)] pub plutus_script: Option>, @@ -655,7 +1007,7 @@ pub enum Metadatum { Bytes(ByteVec), Text(String), Array(Vec), - Map(BTreeMap), + Map(Metadata), } impl<'b> minicbor::Decode<'b> for Metadatum { @@ -709,11 +1061,59 @@ impl minicbor::Encode for Metadatum { &self, e: &mut minicbor::Encoder, ) -> Result<(), minicbor::encode::Error> { - todo!() + match self { + Metadatum::Int(a) => { + e.encode(a)?; + } + Metadatum::Bytes(a) => { + e.encode(a)?; + } + Metadatum::Text(a) => { + e.encode(a)?; + } + Metadatum::Array(a) => { + e.encode(a)?; + } + Metadatum::Map(a) => { + e.encode(a)?; + } + }; + + Ok(()) } } -pub type Metadata = BTreeMap; +/// Custom struct to handle nested metadata maps +/// +/// Since the ordering of the entires requires a particular order to maintain +/// canonicalization for isomorphic decoding / encoding operators, we use a Vec +/// as the underlaying struct for storage of the items (as opposed to a BTreeMap +/// or HashMap). +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Metadata(Vec<(Metadatum, Metadatum)>); + +impl<'b> minicbor::decode::Decode<'b> for Metadata { + fn decode(d: &mut minicbor::Decoder<'b>) -> Result { + let items: Result, _> = d.map_iter::()?.collect(); + let items = items?; + Ok(Metadata(items)) + } +} + +impl minicbor::encode::Encode for Metadata { + fn encode( + &self, + e: &mut minicbor::Encoder, + ) -> Result<(), minicbor::encode::Error> { + e.map(self.0.len() as u64)?; + for (k, v) in &self.0 { + k.encode(e)?; + v.encode(e)?; + } + + Ok(()) + } +} #[derive(Debug, PartialEq)] pub enum AuxiliaryData { @@ -754,7 +1154,26 @@ impl minicbor::Encode for AuxiliaryData { &self, e: &mut minicbor::Encoder, ) -> Result<(), minicbor::encode::Error> { - todo!() + match self { + AuxiliaryData::Shelley(m) => { + e.encode(m)?; + } + AuxiliaryData::ShelleyMa { + transaction_metadata, + auxiliary_scripts, + } => { + e.array(2)?; + e.encode(transaction_metadata)?; + e.encode(auxiliary_scripts)?; + } + AuxiliaryData::Alonzo(v) => { + // TODO: check if this is the correct tag + e.tag(Tag::Unassigned(259))?; + e.encode(v)?; + } + }; + + Ok(()) } } @@ -772,37 +1191,22 @@ pub struct Block { pub transaction_witness_sets: Vec, #[n(3)] - pub auxiliary_data_set: HashMap, + pub auxiliary_data_set: BTreeMap, #[n(4)] pub invalid_transactions: Vec, } -impl TryFrom<&[u8]> for Block { - type Error = minicbor::decode::Error; - - fn try_from(value: &[u8]) -> Result { - // Hack to unwrap root array on top of the block. Can't find spec explaining - // what this value means. - let (_unknown, block): (u16, Block) = minicbor::decode(value)?; - Ok(block) - } -} - -impl TryFrom<&[u8]> for Header { - type Error = minicbor::decode::Error; - - fn try_from(value: &[u8]) -> Result { - minicbor::decode(value) - } -} +#[derive(Encode, Decode, Debug)] +pub struct BlockWrapper(#[n(0)] u16, #[n(1)] Block); #[cfg(test)] mod tests { - use crate::{Block, Header}; + use crate::BlockWrapper; + use minicbor::{self, to_vec}; #[test] - fn block_decode_works() { + fn block_isomorphic_decoding_encoding() { let test_blocks = vec![ include_str!("test_data/test1.block"), include_str!("test_data/test2.block"), @@ -815,9 +1219,11 @@ mod tests { ]; for (idx, block_str) in test_blocks.iter().enumerate() { - println!("decoding test block {}", idx + 1); - let bytes = hex::decode(block_str).unwrap(); - Block::try_from(&bytes[..]).unwrap(); + //println!("decoding test block {}", idx + 1); + let bytes = hex::decode(block_str).expect(&format!("bad block file {}", idx)); + let block: BlockWrapper = minicbor::decode(&bytes[..]).expect(&format!("error decoding cbor for file {}", idx)); + let bytes2 = to_vec(block).expect(&format!("error encoding block cbor for file {}", idx)); + assert_eq!(bytes, bytes2); } } }