diff --git a/.github/discord.svg b/.github/discord.svg
new file mode 100644
index 0000000..a915ff0
--- /dev/null
+++ b/.github/discord.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
new file mode 100644
index 0000000..69d17ee
--- /dev/null
+++ b/.github/workflows/validate.yml
@@ -0,0 +1,78 @@
+# Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md
+
+on:
+ push: { }
+
+name: Validate
+
+jobs:
+ check:
+ name: Check
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ windows-latest, ubuntu-latest, macOS-latest ]
+ rust: [ stable ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v2
+
+ - name: Install stable toolchain
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ toolchain: ${{ matrix.rust }}
+
+ - name: Run cargo check
+ run: cargo check
+
+ test:
+ name: Test Suite
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v2
+
+ - name: Install stable toolchain
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ toolchain: stable
+
+ - name: Run cargo test
+ run: cargo test
+
+ test-windows:
+ name: Test Suite Windows
+ runs-on: windows-latest
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v2
+
+ - name: Install stable toolchain
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ toolchain: stable
+
+ - name: Run cargo test
+ run: cargo test
+
+ lints:
+ name: Lints
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v2
+
+ - name: Install stable toolchain
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ toolchain: stable
+ components: rustfmt, clippy
+
+ - name: Run cargo fmt
+ run: cargo fmt --all -- --check
+
+ - name: Run cargo clippy
+ run: cargo clippy -- -D warnings
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3e72f08
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,26 @@
+# Generated by Cargo
+# will have compiled files and executables
+debug/
+target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+**/Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# MSVC Windows builds of rustc generate these, which store debugging information
+*.pdb
+
+# RustRover
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+.idea/
+
+
+# Added by cargo
+
+/target
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..72a654a
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,6 @@
+[workspace]
+resolver = "2"
+members = [
+ "ouroboros",
+ "ouroboros-praos",
+]
diff --git a/README.md b/README.md
index 9823e78..b0a71a5 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,18 @@
-Initial Commit
+# Ouroboros
+
+
+
+Ouroboros is a family of proof-of-stake (PoS) consensus protocols used in blockchain technology. It was designed to be secure, scalable, and energy-efficient. Ouroboros is notable for being the first PoS protocol to be mathematically proven secure and for being the consensus algorithm behind the Cardano blockchain. Key features of Ouroboros include:
+
+**Proof-of-Stake**: Unlike proof-of-work (PoW) systems, Ouroboros relies on stakeholders to validate transactions and create new blocks, which significantly reduces energy consumption.
+
+**Security**: Ouroboros has been rigorously analyzed and proven secure under certain cryptographic assumptions.
+
+**Scalability**: The protocol is designed to support a large number of transactions per second, making it suitable for large-scale applications.
+
+**Incentives**: It includes mechanisms to incentivize honest behavior among participants, ensuring the network remains secure and efficient.
+Ouroboros operates in epochs, which are divided into slots. In each slot, a slot leader is elected to add a block to the blockchain. The election process is based on the stake each participant holds, with higher stakes increasing the probability of being selected as a slot leader.
+
+## Repository Layout
+
+The ouroboros crate contains the generic traits related to any Ouroboros consensus protocol. The sub-libraries contain the specific implementations of Ouroboros, such as Ouroboros TPraos, Ouroboros Praos, and Ouroboros Genesis.
diff --git a/ouroboros-praos/Cargo.toml b/ouroboros-praos/Cargo.toml
new file mode 100644
index 0000000..e1263e0
--- /dev/null
+++ b/ouroboros-praos/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "ouroboros-praos"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+hex = "0.4"
+ouroboros = { path = "../ouroboros" }
+pallas-crypto = { git = "https://github.com/txpipe/pallas", rev = "5db91a6994922f423badb88f77b3f9423c7367af" }
+pallas-math = { git = "https://github.com/txpipe/pallas", rev = "5db91a6994922f423badb88f77b3f9423c7367af" }
+pallas-primitives = { git = "https://github.com/txpipe/pallas", rev = "5db91a6994922f423badb88f77b3f9423c7367af" }
+tracing = "0.1"
+
+[dev-dependencies]
+ctor = "0.2"
+insta = { version = "1.40.0", features = ["yaml"] }
+mockall = "0.13"
+pallas-traverse = { git = "https://github.com/txpipe/pallas", rev = "5db91a6994922f423badb88f77b3f9423c7367af" }
+tracing-subscriber = "0.3"
\ No newline at end of file
diff --git a/ouroboros-praos/src/consensus/mod.rs b/ouroboros-praos/src/consensus/mod.rs
new file mode 100644
index 0000000..a7b9c85
--- /dev/null
+++ b/ouroboros-praos/src/consensus/mod.rs
@@ -0,0 +1,302 @@
+use ouroboros::ledger::{issuer_vkey_to_pool_id, PoolId, PoolInfo};
+use ouroboros::validator::Validator;
+use pallas_crypto::hash::{Hash, Hasher};
+use pallas_crypto::vrf::{
+ VrfProof, VrfProofBytes, VrfProofHashBytes, VrfPublicKey, VrfPublicKeyBytes,
+};
+use pallas_math::math::{ExpOrdering, FixedDecimal, FixedPrecision};
+use pallas_primitives::babbage;
+use pallas_primitives::babbage::{derive_tagged_vrf_output, VrfDerivation};
+use std::ops::Deref;
+use std::sync::LazyLock;
+use tracing::{error, span, trace, warn};
+
+/// The certified natural max value represents 2^256 in praos consensus
+static CERTIFIED_NATURAL_MAX: LazyLock = LazyLock::new(|| {
+ FixedDecimal::from_str(
+ "1157920892373161954235709850086879078532699846656405640394575840079131296399360000000000000000000000000000000000",
+ 34,
+ )
+ .expect("Infallible")
+});
+
+/// Validator for a block using praos consensus.
+pub struct BlockValidator<'b> {
+ header: &'b babbage::Header,
+ pool_info: &'b dyn PoolInfo,
+ epoch_nonce: &'b Hash<32>,
+ // c is the ln(1-active_slots_coeff). Usually ln(1-0.05)
+ c: &'b FixedDecimal,
+}
+
+impl<'b> BlockValidator<'b> {
+ pub fn new(
+ header: &'b babbage::Header,
+ pool_info: &'b dyn PoolInfo,
+ epoch_nonce: &'b Hash<32>,
+ c: &'b FixedDecimal,
+ ) -> Self {
+ Self {
+ header,
+ pool_info,
+ epoch_nonce,
+ c,
+ }
+ }
+
+ fn validate_babbage_compatible(&self) -> bool {
+ let span = span!(tracing::Level::TRACE, "validate_babbage_compatible");
+ let _enter = span.enter();
+
+ // Grab all the values we need to validate the block
+ let issuer_vkey = &self.header.header_body.issuer_vkey;
+ let pool_id: PoolId = issuer_vkey_to_pool_id(issuer_vkey);
+ let vrf_vkey: VrfPublicKeyBytes = (&self.header.header_body.vrf_vkey).into();
+ if !self.ledger_matches_block_vrf_key_hash(&pool_id, &vrf_vkey) {
+ // Fail fast if the vrf key hash in the block does not match the ledger
+ return false;
+ }
+ let sigma: FixedDecimal = match self.pool_info.sigma(&pool_id) {
+ Ok(sigma) => {
+ FixedDecimal::from(sigma.numerator) / FixedDecimal::from(sigma.denominator)
+ }
+ Err(error) => {
+ warn!("{:?} - {:?}", error, pool_id);
+ return false;
+ }
+ };
+ let absolute_slot = self.header.header_body.slot;
+
+ // Get the leader VRF output hash from the block vrf result
+ let leader_vrf_output = &self.header.header_body.leader_vrf_output();
+
+ let block_vrf_proof_hash: VrfProofHashBytes =
+ (&self.header.header_body.vrf_result.0).into();
+ let block_vrf_proof: VrfProofBytes = (&self.header.header_body.vrf_result.1).into();
+ let kes_signature = self.header.body_signature.as_slice();
+
+ trace!("pool_id: {}", pool_id);
+ trace!("block vrf_vkey: {}", hex::encode(vrf_vkey));
+ trace!("sigma: {}", sigma);
+ trace!("absolute_slot: {}", absolute_slot);
+ trace!("leader_vrf_output: {}", hex::encode(leader_vrf_output));
+ trace!(
+ "block_vrf_proof_hash: {}",
+ hex::encode(block_vrf_proof_hash.as_slice())
+ );
+ trace!(
+ "block_vrf_proof: {}",
+ hex::encode(block_vrf_proof.as_slice())
+ );
+ trace!("kes_signature: {}", hex::encode(kes_signature));
+
+ // Calculate the VRF input seed so we can verify the VRF output against it.
+ let vrf_input_seed = self.mk_vrf_input(absolute_slot, self.epoch_nonce.as_ref());
+ trace!("vrf_input_seed: {}", vrf_input_seed);
+
+ // Verify the VRF proof
+ let vrf_proof = VrfProof::from(&block_vrf_proof);
+ let vrf_vkey = VrfPublicKey::from(&vrf_vkey);
+ match vrf_proof.verify(&vrf_vkey, vrf_input_seed.as_ref()) {
+ Ok(proof_hash) => {
+ if proof_hash.as_slice() != block_vrf_proof_hash.as_slice() {
+ error!("VRF proof hash mismatch");
+ false
+ } else {
+ // The proof was valid. Make sure that our leader_vrf_output matches what was in the block
+ trace!("certified_proof_hash: {}", hex::encode(proof_hash));
+ let calculated_leader_vrf_output =
+ derive_tagged_vrf_output(proof_hash.as_slice(), VrfDerivation::Leader);
+ if calculated_leader_vrf_output.as_slice() != leader_vrf_output.as_slice() {
+ error!(
+ "Leader VRF output hash mismatch. was: {}, expected: {}",
+ hex::encode(calculated_leader_vrf_output),
+ hex::encode(leader_vrf_output)
+ );
+ false
+ } else {
+ // The leader VRF output hash matches what was in the block
+ // Now we need to check if the pool had enough sigma stake to produce this block
+ if self.pool_meets_delegation_threshold(
+ &sigma,
+ absolute_slot,
+ leader_vrf_output.as_slice(),
+ ) {
+ // TODO: Validate the KES signature
+ true
+ } else {
+ false
+ }
+ }
+ }
+ }
+ Err(error) => {
+ error!("Could not verify block vrf: {}", error);
+ false
+ }
+ }
+ }
+
+ /// Verify that the pool meets the delegation threshold
+ fn pool_meets_delegation_threshold(
+ &self,
+ sigma: &FixedDecimal,
+ absolute_slot: u64,
+ leader_vrf_output: &[u8],
+ ) -> bool {
+ let certified_leader_vrf: FixedDecimal = leader_vrf_output.into();
+ let denominator = CERTIFIED_NATURAL_MAX.deref() - &certified_leader_vrf;
+ let recip_q = CERTIFIED_NATURAL_MAX.deref() / &denominator;
+ let x = -(sigma * self.c);
+
+ trace!("certified_leader_vrf: {}", certified_leader_vrf);
+ trace!("denominator: {}", denominator);
+ trace!("recip_q: {}", recip_q);
+ trace!("c: {}", self.c);
+ trace!("x: {}", x);
+
+ let ordering = x.exp_cmp(1000, 3, &recip_q);
+ match ordering.estimation {
+ ExpOrdering::LT => {
+ trace!(
+ "Slot: {} - IS Leader: {} < {}",
+ absolute_slot,
+ recip_q,
+ ordering.approx
+ );
+ true
+ }
+ _ => {
+ trace!(
+ "Slot: {} - NOT Leader: {} >= {}",
+ absolute_slot,
+ recip_q,
+ ordering.approx
+ );
+ false
+ }
+ }
+ }
+
+ /// Validate that the VRF key hash in the block matches the VRF key hash in the ledger
+ fn ledger_matches_block_vrf_key_hash(
+ &self,
+ pool_id: &PoolId,
+ vrf_vkey: &VrfPublicKeyBytes,
+ ) -> bool {
+ let vrf_vkey_hash: Hash<32> = Hasher::<256>::hash(vrf_vkey);
+ trace!("block vrf_vkey_hash: {}", hex::encode(vrf_vkey_hash));
+ let ledger_vrf_vkey_hash = match self.pool_info.vrf_vkey_hash(pool_id) {
+ Ok(ledger_vrf_vkey_hash) => ledger_vrf_vkey_hash,
+ Err(error) => {
+ warn!("{:?} - {:?}", error, pool_id);
+ return false;
+ }
+ };
+ if vrf_vkey_hash != ledger_vrf_vkey_hash {
+ error!(
+ "VRF vkey hash in block ({}) does not match registered ledger vrf vkey hash ({})",
+ hex::encode(vrf_vkey_hash),
+ hex::encode(ledger_vrf_vkey_hash)
+ );
+ return false;
+ }
+ true
+ }
+
+ fn mk_vrf_input(&self, absolute_slot: u64, eta0: &[u8]) -> Hash<32> {
+ trace!("mk_vrf_input() absolute_slot {}", absolute_slot);
+ let mut hasher = Hasher::<256>::new();
+ hasher.input(&absolute_slot.to_be_bytes());
+ hasher.input(eta0);
+ hasher.finalize()
+ }
+}
+
+impl Validator for BlockValidator<'_> {
+ fn validate(&self) -> bool {
+ self.validate_babbage_compatible()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::consensus::BlockValidator;
+ use ctor::ctor;
+ use mockall::predicate::eq;
+ use ouroboros::ledger::{MockPoolInfo, PoolId, PoolSigma};
+ use ouroboros::validator::Validator;
+ use pallas_crypto::hash::Hash;
+ use pallas_math::math::{FixedDecimal, FixedPrecision};
+ use pallas_traverse::MultiEraHeader;
+
+ #[ctor]
+ fn init() {
+ // set rust log level to TRACE
+ // std::env::set_var("RUST_LOG", "ouroboros-praos=trace");
+
+ // initialize tracing crate
+ tracing_subscriber::fmt::init();
+ }
+
+ #[test]
+ fn test_validate_conway_block() {
+ let test_block = include_bytes!("../../tests/data/mainnet_blockheader_10817298.cbor");
+ let test_block_hex = hex::encode(test_block);
+ insta::assert_snapshot!(test_block_hex);
+ let test_vector = vec![
+ (
+ "00beef0a9be2f6d897ed24a613cf547bb20cd282a04edfc53d477114",
+ "c0d1f9b040d2f6fd7fc8775d24753d6db4b697429f11404a6178a0a4a005867b",
+ "c7937fc47fecbe687891b3decd71e904d1e129598aa3852481d295eea3ea3ada",
+ 25626202470912_u64,
+ 22586623335121436_u64,
+ true,
+ ),
+ (
+ "00beef0a9be2f6d897ed24a613cf547bb20cd282a04edfc53d477114",
+ "c0d1f9b040d2f6fd7fc8775d24753d6db4b697429f11404a6178a0a4a005867b",
+ "c7937fc47fecbe687891b3decd71e904d1e129598aa3852481d295eea3ea3ada",
+ 6026202470912_u64,
+ 22586623335121436_u64,
+ false,
+ ),
+ ];
+ insta::assert_yaml_snapshot!(test_vector);
+
+ for (pool_id_str, vrf_vkey_hash_str, epoch_nonce_str, numerator, denominator, expected) in
+ test_vector
+ {
+ let pool_id: PoolId = pool_id_str.parse().unwrap();
+ let vrf_vkey_hash: Hash<32> = vrf_vkey_hash_str.parse().unwrap();
+ let epoch_nonce: Hash<32> = epoch_nonce_str.parse().unwrap();
+
+ let active_slots_coeff: FixedDecimal =
+ FixedDecimal::from(5u64) / FixedDecimal::from(100u64);
+ let c = (FixedDecimal::from(1u64) - active_slots_coeff).ln();
+ let conway_block_tag: u8 = 6;
+ let multi_era_header =
+ MultiEraHeader::decode(conway_block_tag, None, test_block).unwrap();
+ let babbage_header = multi_era_header.as_babbage().expect("Infallible");
+ assert_eq!(babbage_header.header_body.slot, 134402628u64);
+
+ let mut pool_info = MockPoolInfo::new();
+ pool_info
+ .expect_sigma()
+ .with(eq(pool_id))
+ .returning(move |_| {
+ Ok(PoolSigma {
+ numerator,
+ denominator,
+ })
+ });
+ pool_info
+ .expect_vrf_vkey_hash()
+ .with(eq(pool_id))
+ .returning(move |_| Ok(vrf_vkey_hash));
+
+ let block_validator = BlockValidator::new(babbage_header, &pool_info, &epoch_nonce, &c);
+ assert_eq!(block_validator.validate(), expected);
+ }
+ }
+}
diff --git a/ouroboros-praos/src/consensus/snapshots/ouroboros_praos__consensus__tests__validate_conway_block-2.snap b/ouroboros-praos/src/consensus/snapshots/ouroboros_praos__consensus__tests__validate_conway_block-2.snap
new file mode 100644
index 0000000..e855468
--- /dev/null
+++ b/ouroboros-praos/src/consensus/snapshots/ouroboros_praos__consensus__tests__validate_conway_block-2.snap
@@ -0,0 +1,16 @@
+---
+source: ouroboros-praos/src/consensus/mod.rs
+expression: test_vector
+---
+- - 00beef0a9be2f6d897ed24a613cf547bb20cd282a04edfc53d477114
+ - c0d1f9b040d2f6fd7fc8775d24753d6db4b697429f11404a6178a0a4a005867b
+ - c7937fc47fecbe687891b3decd71e904d1e129598aa3852481d295eea3ea3ada
+ - 25626202470912
+ - 22586623335121436
+ - true
+- - 00beef0a9be2f6d897ed24a613cf547bb20cd282a04edfc53d477114
+ - c0d1f9b040d2f6fd7fc8775d24753d6db4b697429f11404a6178a0a4a005867b
+ - c7937fc47fecbe687891b3decd71e904d1e129598aa3852481d295eea3ea3ada
+ - 6026202470912
+ - 22586623335121436
+ - false
diff --git a/ouroboros-praos/src/consensus/snapshots/ouroboros_praos__consensus__tests__validate_conway_block.snap b/ouroboros-praos/src/consensus/snapshots/ouroboros_praos__consensus__tests__validate_conway_block.snap
new file mode 100644
index 0000000..0a8b6d3
--- /dev/null
+++ b/ouroboros-praos/src/consensus/snapshots/ouroboros_praos__consensus__tests__validate_conway_block.snap
@@ -0,0 +1,5 @@
+---
+source: ouroboros-praos/src/consensus/mod.rs
+expression: test_block_hex
+---
+828a1a00a50f121a0802d24458203deea82abe788d260b8987a522aadec86c9f098e88a57d7cfcdb24f474a7afb65820cad3c900ca6baee9e65bf61073d900bfbca458eeca6d0b9f9931f5b1017a8cd65820576d49e98adfab65623dc16f9fff2edd210e8dd1d4588bfaf8af250beda9d3c7825840d944b8c81000fc1182ec02194ca9eca510fd84995d22bfe1842190b39d468e5ecbd863969e0c717b0071a371f748d44c895fa9233094cefcd3107410baabb19a5850f2a29f985d37ca8eb671c2847fab9cc45c93738a430b4e43837e7f33028b190a7e55152b0e901548961a66d56eebe72d616f9e68fd13e9955ccd8611c201a5b422ac8ef56af74cb657b5b868ce9d850f1945d15820639d4986d17de3cac8079a3b25d671f339467aa3a9948e29992dafebf90f719f8458202e5823037de29647e495b97d9dd7bf739f7ebc11d3701c8d0720f55618e1b292171903e958401feeeabc7460b19370f4050e986b558b149fdc8724b4a4805af8fe45c8e7a7c6753894ad7a1b9c313da269ddc5922e150da3b378977f1dfea79fc52fd2c12f088209015901c020f1c8f9ae672e6ec75b0aa63a85e7ab7865b95f6b2907a26b54c14f49184ab52cf98ef441bb71de50380325b34f16d84fc78d137467a1b49846747cf8ee4701c56f08f198b94c468d46b67b271f5bc30ab2ad14b1bdbf2be0695a00fe4b02b3060fa52128f4cce9c5759df0ba8d71fe99456bd2e333671e45110908d03a2ec3b38599d26adf182ba63f79900fdb2732947cf8e940a4cf1e8db9b4cf4c001dbd37c60d0e38851de4910807896153be455e13161342d4c6f7bb3e4d2d35dbbbba0ebcd161be2f1ec030d2f5a6059ac89dfa70dc6b3d0bc2da179c62ae95c4f9c7ad9c0387b35bf2b45b325d1e0a18c0c783a0779003bf23e7a6b00cc126c5e3d51a57d41ff1707a76fb2c306a67c21473b41f1d9a7f64a670ec172a2421da03d796fa97086de8812304f4f96bd45243d0a2ad6c48a69d9e2c0afbb1333acee607d18eb3a33818c3c9d5bb72cade889379008bf60d436298cb0cfc6159332cb1af1de4f1d64e79c399d058ac4993704eed67917093f89db6cde830383e69aa400ba3225087cabad45407b891416330485891dc9a3875488a26428d20d581b629a8f4f42e3aa00cbcaae6c8e2b8f3fe033b874d1de6a3f8c321c92b77643f00d28e
diff --git a/ouroboros-praos/src/lib.rs b/ouroboros-praos/src/lib.rs
new file mode 100644
index 0000000..4021407
--- /dev/null
+++ b/ouroboros-praos/src/lib.rs
@@ -0,0 +1 @@
+pub mod consensus;
diff --git a/ouroboros-praos/tests/data/mainnet_blockheader_10817298.cbor b/ouroboros-praos/tests/data/mainnet_blockheader_10817298.cbor
new file mode 100644
index 0000000..e4fdec7
Binary files /dev/null and b/ouroboros-praos/tests/data/mainnet_blockheader_10817298.cbor differ
diff --git a/ouroboros/Cargo.toml b/ouroboros/Cargo.toml
new file mode 100644
index 0000000..9063e17
--- /dev/null
+++ b/ouroboros/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "ouroboros"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+hex = "0.4.3"
+mockall = "0.13"
+pallas-codec = { git = "https://github.com/txpipe/pallas", rev = "5db91a6994922f423badb88f77b3f9423c7367af" }
+pallas-crypto = { git = "https://github.com/txpipe/pallas", rev = "5db91a6994922f423badb88f77b3f9423c7367af" }
+thiserror = "1.0"
+
+[dev-dependencies]
+insta = { version = "1.40.0", features = ["yaml"] }
\ No newline at end of file
diff --git a/ouroboros/src/ledger/mod.rs b/ouroboros/src/ledger/mod.rs
new file mode 100644
index 0000000..52a5c8c
--- /dev/null
+++ b/ouroboros/src/ledger/mod.rs
@@ -0,0 +1,59 @@
+use mockall::automock;
+use pallas_codec::utils::Bytes;
+use pallas_crypto::hash::{Hash, Hasher};
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum Error {
+ #[error("PoolId not found")]
+ PoolIdNotFound,
+}
+
+pub type PoolId = Hash<28>;
+
+/// The sigma value of a pool. This is a rational number that represents the total value of the
+/// delegated stake in the pool over the total value of the active stake in the network. This value
+/// is tracked in the ledger state and recorded as a snapshot value at each epoch.
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct PoolSigma {
+ pub numerator: u64,
+ pub denominator: u64,
+}
+
+/// The pool info trait provides a lookup mechanism for pool data. This is sourced from the ledger
+#[automock]
+pub trait PoolInfo: Send + Sync {
+ /// Performs a lookup of a pool_id to its sigma value. This usually represents a different set of
+ /// sigma snapshot data depending on whether we need to look up the pool_id in the current epoch
+ /// or in the future.
+ fn sigma(&self, pool_id: &PoolId) -> Result;
+
+ /// Hashes the vrf vkey of a pool.
+ fn vrf_vkey_hash(&self, pool_id: &PoolId) -> Result, Error>;
+}
+
+/// The node's cold vkey is hashed with blake2b224 to create the pool id
+pub fn issuer_vkey_to_pool_id(issuer_vkey: &Bytes) -> PoolId {
+ Hasher::<224>::hash(issuer_vkey)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::ledger::issuer_vkey_to_pool_id;
+ use pallas_codec::utils::Bytes;
+
+ #[test]
+ fn test_issuer_vkey_to_pool_id() {
+ let test_vector = vec![(
+ "cad3c900ca6baee9e65bf61073d900bfbca458eeca6d0b9f9931f5b1017a8cd6",
+ "00beef0a9be2f6d897ed24a613cf547bb20cd282a04edfc53d477114",
+ )];
+ insta::assert_yaml_snapshot!(test_vector);
+
+ for (issuer_vkey_str, expected_pool_id_str) in test_vector {
+ let issuer_vkey: Bytes = issuer_vkey_str.parse().unwrap();
+ let pool_id = issuer_vkey_to_pool_id(&issuer_vkey);
+ assert_eq!(pool_id.to_string(), expected_pool_id_str);
+ }
+ }
+}
diff --git a/ouroboros/src/ledger/snapshots/ouroboros__ledger__tests__issuer_vkey_to_pool_id.snap b/ouroboros/src/ledger/snapshots/ouroboros__ledger__tests__issuer_vkey_to_pool_id.snap
new file mode 100644
index 0000000..e056008
--- /dev/null
+++ b/ouroboros/src/ledger/snapshots/ouroboros__ledger__tests__issuer_vkey_to_pool_id.snap
@@ -0,0 +1,6 @@
+---
+source: ouroboros/src/ledger/mod.rs
+expression: test_vector
+---
+- - cad3c900ca6baee9e65bf61073d900bfbca458eeca6d0b9f9931f5b1017a8cd6
+ - 00beef0a9be2f6d897ed24a613cf547bb20cd282a04edfc53d477114
diff --git a/ouroboros/src/lib.rs b/ouroboros/src/lib.rs
new file mode 100644
index 0000000..0f1ebd4
--- /dev/null
+++ b/ouroboros/src/lib.rs
@@ -0,0 +1,2 @@
+pub mod ledger;
+pub mod validator;
diff --git a/ouroboros/src/validator/mod.rs b/ouroboros/src/validator/mod.rs
new file mode 100644
index 0000000..47ff76d
--- /dev/null
+++ b/ouroboros/src/validator/mod.rs
@@ -0,0 +1,6 @@
+/// Generic trait for validating any type of data. Designed to be used across threads so validations
+/// can be done in parallel. Validators should handle all error cases internally and simply return
+/// a boolean indicating if the validation was successful.
+pub trait Validator: Send + Sync {
+ fn validate(&self) -> bool;
+}