From 794c609125a7514d557dba7dd7334d0c77a302d6 Mon Sep 17 00:00:00 2001 From: cong-or Date: Wed, 9 Oct 2024 17:00:20 +0100 Subject: [PATCH 01/53] feat(decode and encode block header): wip barebones --- rust/Cargo.toml | 1 + rust/ledger/Cargo.toml | 20 ++++ rust/ledger/src/lib.rs | 232 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 253 insertions(+) create mode 100644 rust/ledger/Cargo.toml create mode 100644 rust/ledger/src/lib.rs diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 930e2fc19c..854429fbd5 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -8,6 +8,7 @@ members = [ "cbork-abnf-parser", "cbork-cddl-parser", "catalyst-voting", + "ledger" ] [workspace.package] diff --git a/rust/ledger/Cargo.toml b/rust/ledger/Cargo.toml new file mode 100644 index 0000000000..357fe1e457 --- /dev/null +++ b/rust/ledger/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "ledger" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +ed25519-dalek = "2.1.1" +anyhow = "1.0.86" +minicbor = { version = "0.24", features = ["std"] } +uuid = { version = "1.10.0", features = ["v4", "serde"] } +ulid = { version = "1.1.3", features = ["serde", "uuid"] } +cddl = "0.9.4" +hex = "0.4.3" + +[lints] +workspace = true diff --git a/rust/ledger/src/lib.rs b/rust/ledger/src/lib.rs new file mode 100644 index 0000000000..6c912bba5e --- /dev/null +++ b/rust/ledger/src/lib.rs @@ -0,0 +1,232 @@ +//! Block Serialization +//! +//! Facilitates block serialization for immutable ledger + +use anyhow::Ok; +use core::result::Result::Ok as ResultOk; + +use ulid::Ulid; +use uuid::Uuid; + +/// Kid (The key identifier) size in bytes +const KID_BYTES: usize = 16; + +/// Key ID - Blake2b-128 hash of the Role 0 Certificate defining the Session public key. +/// BLAKE2b-128 produces digest side of 16 bytes. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Kid(pub [u8; KID_BYTES]); + +/// Unique identifier of the chain. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ChainId(pub Ulid); + +/// Block height. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Height(pub u32); + +/// Block epoch-based date/time. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BlockTimeStamp(pub i64); + +/// Previous Block hash. +#[derive(Debug, Clone, PartialEq)] +pub struct PreviousBlockHash(pub Vec); + +/// unique identifier of the ledger type. +/// In general, this is the way to strictly bound and specify block_data of the ledger for the specific ledger_type. +#[derive(Debug, Clone, PartialEq)] +pub struct LedgerType(pub Uuid); + +/// unique identifier of the purpose, each Ledger instance will have a strict time boundaries, so each of them will run for different purposes. +#[derive(Debug, Clone, PartialEq)] +pub struct PurposeId(pub Ulid); + +/// Identifier or identifiers of the entity who was produced and processed a block. +#[derive(Debug, Clone, PartialEq)] +pub struct Validator(pub Vec); + +/// Optional field, to add some arbitrary metadata to the block. +#[derive(Debug, Clone, PartialEq)] +pub struct Metadata(pub Vec); + +/// Decoder block header +type DecodedBlockHeader = ( + ChainId, + Height, + BlockTimeStamp, + PreviousBlockHash, + LedgerType, + PurposeId, + Validator, + Option, +); + +/// Encode block header +pub fn encode_block_header( + chain_id: ChainId, height: Height, ts: BlockTimeStamp, prev_block_hash: PreviousBlockHash, + ledger_type: LedgerType, pid: PurposeId, validator: Validator, metadata: Option, +) -> anyhow::Result> { + let out: Vec = Vec::new(); + let mut encoder = minicbor::Encoder::new(out); + + encoder.bytes(&chain_id.0.to_bytes())?; + encoder.bytes(&height.0.to_be_bytes())?; + encoder.bytes(&ts.0.to_be_bytes())?; + encoder.bytes(&prev_block_hash.0.as_slice())?; + encoder.bytes(ledger_type.0.as_bytes())?; + encoder.bytes(&pid.0.to_bytes())?; + encoder.bytes(&validator.0.len().to_be_bytes())?; + + for validator in validator.0.iter() { + encoder.bytes(&validator.0)?; + } + + if let Some(meta) = metadata { + encoder.bytes(&meta.0)?; + } + + Ok(encoder.writer().to_vec()) +} +/// Decode block header +pub fn decode_block_header(block_hdr: Vec) -> anyhow::Result { + // Decode cbor to bytes + let mut cbor_decoder = minicbor::Decoder::new(&block_hdr); + + // Raw chain_id + let chain_id = ChainId(Ulid::from_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for chain id : {e}")))? + .try_into()?, + )); + + // Raw Block height + let block_height = Height(u32::from_be_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block height : {e}")))? + .try_into()?, + )); + + // Raw time stamp + let ts = BlockTimeStamp(i64::from_be_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for timestamp : {e}")))? + .try_into()?, + )); + + // Raw prev block hash + let prev_block_hash = PreviousBlockHash( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for prev block hash : {e}")))? + .to_vec(), + ); + + // Raw ledger type + let ledger_type = LedgerType(Uuid::from_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for ledger type : {e}")))? + .try_into()?, + )); + + // Raw purpose id + let purpose_id = PurposeId(Ulid::from_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for purpose id : {e}")))? + .try_into()?, + )); + + // Number of validators + let number_of_validators = usize::from_be_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for number of validators : {e}")))? + .try_into()?, + ); + + // Extract validators + let mut validators = Vec::new(); + for _validator in 0..number_of_validators { + let validator_kid: [u8; 16] = cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for validators : {e}")))? + .try_into()?; + + validators.push(Kid(validator_kid)); + } + + let metadata = match cbor_decoder.bytes() { + ResultOk(meta) => Some(Metadata(meta.to_vec())), + Err(_) => None, + }; + + Ok(( + chain_id, + block_height, + ts, + prev_block_hash, + ledger_type, + purpose_id, + Validator(validators), + metadata, + )) +} + +#[cfg(test)] +mod tests { + use ulid::Ulid; + use uuid::Uuid; + + use crate::{ + decode_block_header, encode_block_header, BlockTimeStamp, ChainId, Height, Kid, LedgerType, + Metadata, PreviousBlockHash, PurposeId, Validator, + }; + + #[test] + fn block_header_encode_decode() { + let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let chain_id = ChainId(Ulid::new()); + let block_height = Height(5); + let block_ts = BlockTimeStamp(1728474515); + let prev_block_height = PreviousBlockHash(vec![0; 64]); + let ledger_type = LedgerType(Uuid::new_v4()); + let purpose_id = PurposeId(Ulid::new()); + let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); + let metadata = Some(Metadata(vec![1; 128])); + + let encoded_block_hdr = encode_block_header( + chain_id, + block_height, + block_ts, + prev_block_height.clone(), + ledger_type.clone(), + purpose_id.clone(), + validators.clone(), + metadata.clone(), + ) + .unwrap(); + + let decoded_hdr = decode_block_header(encoded_block_hdr).unwrap(); + assert_eq!(decoded_hdr.0, chain_id); + assert_eq!(decoded_hdr.1, block_height); + assert_eq!(decoded_hdr.2, block_ts); + assert_eq!(decoded_hdr.3, prev_block_height); + assert_eq!(decoded_hdr.4, ledger_type); + assert_eq!(decoded_hdr.5, purpose_id); + assert_eq!(decoded_hdr.6, validators); + assert_eq!(decoded_hdr.7, metadata); + } +} From d35b42f26b8f64ae372f2cfb1289ec6752035841 Mon Sep 17 00:00:00 2001 From: cong-or Date: Sun, 13 Oct 2024 21:21:42 +0100 Subject: [PATCH 02/53] feat(block decoding): wip --- rust/ledger/Cargo.toml | 3 + rust/ledger/src/lib.rs | 158 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 159 insertions(+), 2 deletions(-) diff --git a/rust/ledger/Cargo.toml b/rust/ledger/Cargo.toml index 357fe1e457..7e55c33321 100644 --- a/rust/ledger/Cargo.toml +++ b/rust/ledger/Cargo.toml @@ -15,6 +15,9 @@ uuid = { version = "1.10.0", features = ["v4", "serde"] } ulid = { version = "1.1.3", features = ["serde", "uuid"] } cddl = "0.9.4" hex = "0.4.3" +blake2b_simd = "1.0.2" +blake3 = "=0.1.3" +rand = "0.8.5" [lints] workspace = true diff --git a/rust/ledger/src/lib.rs b/rust/ledger/src/lib.rs index 6c912bba5e..cb46c9c97b 100644 --- a/rust/ledger/src/lib.rs +++ b/rust/ledger/src/lib.rs @@ -3,7 +3,9 @@ //! Facilitates block serialization for immutable ledger use anyhow::Ok; +use blake2b_simd::{self, Params}; use core::result::Result::Ok as ResultOk; +use ed25519_dalek::{ed25519::signature::SignerMut, SigningKey, SECRET_KEY_LENGTH}; use ulid::Ulid; use uuid::Uuid; @@ -49,6 +51,10 @@ pub struct Validator(pub Vec); #[derive(Debug, Clone, PartialEq)] pub struct Metadata(pub Vec); +/// Size of block hdr +#[derive(Debug, Clone, PartialEq)] +pub struct BlockHdrSize(usize); + /// Decoder block header type DecodedBlockHeader = ( ChainId, @@ -59,8 +65,90 @@ type DecodedBlockHeader = ( PurposeId, Validator, Option, + BlockHdrSize, ); +/// Choice of hash function: +/// must be the same as the hash of the previous block. +pub enum HashFunction { + /// BLAKE3 is based on an optimized instance of the established hash function BLAKE2 and on the original Bao tree mode + Blake3, + /// BLAKE2b-512 produces digest side of 512 bits. + Blake2b, +} + +/// Encode block +pub fn encode_block( + block_hdr_cbor: Vec, block_data: Vec, validator_keys: Vec<&[u8; SECRET_KEY_LENGTH]>, + hasher: HashFunction, +) -> anyhow::Result> { + let hashed_block_header = match hasher { + HashFunction::Blake3 => blake3(&block_hdr_cbor)?.to_vec(), + HashFunction::Blake2b => blake2b_512(&block_hdr_cbor)?.to_vec(), + }; + + // validator_signature MUST be a signature of the hashed block_header bytes + // and the block_data bytes + + let data_to_sign = [hashed_block_header, block_data.clone()].concat(); + + // if validator is only one id => validator_signature contains only 1 signature; + // if validator is array => validator_signature contains an array with the same length; + + let signatures: Vec<[u8; 64]> = validator_keys + .iter() + .map(|sk| { + let mut sk: SigningKey = SigningKey::from_bytes(&sk); + sk.sign(&data_to_sign).to_bytes() + }) + .collect(); + + let out = block_hdr_cbor; + + let mut encoder = minicbor::Encoder::new(out); + encoder.bytes(&block_data)?; + encoder.bytes(&signatures.concat())?; + + Ok(encoder.writer().to_vec()) +} + +/// Decoded block +pub fn decode_block(encoded_block: Vec) -> anyhow::Result> { + // Decode cbor to bytes + let binding = encoded_block.clone(); + let mut cbor_decoder = minicbor::Decoder::new(&binding); + + println!("ab {:?}", encoded_block.len()); + + // Decoded block hdr + let block_hdr: DecodedBlockHeader = decode_block_header(encoded_block.clone())?; + let block_hdr_size: BlockHdrSize = block_hdr.8; + + cbor_decoder.set_position(block_hdr_size.0); + + // Block data + let block_data = cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block data : {e}")))?; + + println!("bloc {:?}", block_data.len()); + + Ok(vec![0; 64]) +} + +/// Produce BLAKE3 hash +pub(crate) fn blake3(value: &[u8]) -> anyhow::Result<[u8; 32]> { + Ok(*blake3::hash(value).as_bytes()) +} + +/// BLAKE2b-512 produces digest side of 512 bits. +pub(crate) fn blake2b_512(value: &[u8]) -> anyhow::Result<[u8; 64]> { + let h = Params::new().hash_length(64).hash(value); + let b = h.as_bytes(); + b.try_into() + .map_err(|_| anyhow::anyhow!("Invalid length of blake2b_512, expected 64 got {}", b.len())) +} + /// Encode block header pub fn encode_block_header( chain_id: ChainId, height: Height, ts: BlockTimeStamp, prev_block_hash: PreviousBlockHash, @@ -164,6 +252,8 @@ pub fn decode_block_header(block_hdr: Vec) -> anyhow::Result None, }; + let block_hdr_size = BlockHdrSize((cbor_decoder.position()).try_into()?); + Ok(( chain_id, block_height, @@ -173,17 +263,20 @@ pub fn decode_block_header(block_hdr: Vec) -> anyhow::Result = Vec::new(); + let mut encoder = minicbor::Encoder::new(out); + + encoder + .bytes(&[ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, + 068, 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, + ]) + .unwrap(); + + let encoded_block = encode_block( + encoded_block_hdr, + encoder.writer().to_vec(), + vec![&secret_key_bytes], + crate::HashFunction::Blake2b, + ) + .unwrap(); + + let decoded = decode_block(encoded_block).unwrap(); + println!("decoced {:?}", decoded); + } } From 11df9f3ff8274a1e02a06d08ad8fef947a2215b4 Mon Sep 17 00:00:00 2001 From: cong-or Date: Wed, 16 Oct 2024 12:48:30 +0100 Subject: [PATCH 03/53] feat(validators): block signatures --- rust/ledger/src/lib.rs | 90 +++++++++++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 28 deletions(-) diff --git a/rust/ledger/src/lib.rs b/rust/ledger/src/lib.rs index cb46c9c97b..1641b1ad23 100644 --- a/rust/ledger/src/lib.rs +++ b/rust/ledger/src/lib.rs @@ -5,7 +5,7 @@ use anyhow::Ok; use blake2b_simd::{self, Params}; use core::result::Result::Ok as ResultOk; -use ed25519_dalek::{ed25519::signature::SignerMut, SigningKey, SECRET_KEY_LENGTH}; +use ed25519_dalek::{ed25519::signature::SignerMut, Signature, SigningKey, SECRET_KEY_LENGTH}; use ulid::Ulid; use uuid::Uuid; @@ -51,9 +51,20 @@ pub struct Validator(pub Vec); #[derive(Debug, Clone, PartialEq)] pub struct Metadata(pub Vec); -/// Size of block hdr +/// block data + sig #[derive(Debug, Clone, PartialEq)] -pub struct BlockHdrSize(usize); +pub struct BlockDataAndSignature(Vec); + +/// Decoded block data +#[derive(Debug, Clone, PartialEq)] +pub struct DecodedBlockData(Vec); + +/// Signatures +#[derive(Debug, Clone, PartialEq)] +pub struct Signatures(Vec); + +/// Decoder block +type DecodedBlock = (DecodedBlockHeader, DecodedBlockData, Signatures); /// Decoder block header type DecodedBlockHeader = ( @@ -65,7 +76,7 @@ type DecodedBlockHeader = ( PurposeId, Validator, Option, - BlockHdrSize, + BlockDataAndSignature, ); /// Choice of hash function: @@ -106,34 +117,48 @@ pub fn encode_block( let out = block_hdr_cbor; let mut encoder = minicbor::Encoder::new(out); + encoder.bytes(&block_data)?; - encoder.bytes(&signatures.concat())?; + + for sig in signatures.iter() { + encoder.bytes(sig)?; + } Ok(encoder.writer().to_vec()) } /// Decoded block -pub fn decode_block(encoded_block: Vec) -> anyhow::Result> { +pub fn decode_block(encoded_block: Vec) -> anyhow::Result { // Decode cbor to bytes - let binding = encoded_block.clone(); - let mut cbor_decoder = minicbor::Decoder::new(&binding); - - println!("ab {:?}", encoded_block.len()); // Decoded block hdr let block_hdr: DecodedBlockHeader = decode_block_header(encoded_block.clone())?; - let block_hdr_size: BlockHdrSize = block_hdr.8; - cbor_decoder.set_position(block_hdr_size.0); + // block data + sigs + let remaining_block = block_hdr.clone().8 .0; + let mut cbor_decoder = minicbor::Decoder::new(&remaining_block); // Block data let block_data = cbor_decoder .bytes() .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block data : {e}")))?; - println!("bloc {:?}", block_data.len()); + // Extract signatures, block hdr indicates how many validators. + let mut sigs = Vec::new(); + for _sig in 0..block_hdr.6 .0.len() { + let sig: [u8; 64] = cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for signature : {e}")))? + .try_into()?; - Ok(vec![0; 64]) + sigs.push(Signature::from_bytes(&sig)); + } + + Ok(( + block_hdr, + DecodedBlockData(block_data.to_vec()), + Signatures(sigs), + )) } /// Produce BLAKE3 hash @@ -176,9 +201,9 @@ pub fn encode_block_header( Ok(encoder.writer().to_vec()) } /// Decode block header -pub fn decode_block_header(block_hdr: Vec) -> anyhow::Result { +pub fn decode_block_header(block: Vec) -> anyhow::Result { // Decode cbor to bytes - let mut cbor_decoder = minicbor::Decoder::new(&block_hdr); + let mut cbor_decoder = minicbor::Decoder::new(&block); // Raw chain_id let chain_id = ChainId(Ulid::from_bytes( @@ -252,7 +277,8 @@ pub fn decode_block_header(block_hdr: Vec) -> anyhow::Result None, }; - let block_hdr_size = BlockHdrSize((cbor_decoder.position()).try_into()?); + // return remaining blocks for further processing. + let (_block_hdr_bytes, remaining_block_bytes) = block.split_at(cbor_decoder.position()); Ok(( chain_id, @@ -263,7 +289,7 @@ pub fn decode_block_header(block_hdr: Vec) -> anyhow::Result = Vec::new(); - let mut encoder = minicbor::Encoder::new(out); + let mut block_data = minicbor::Encoder::new(out); - encoder - .bytes(&[ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, - 068, 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, - ]) - .unwrap(); + let block_data_bytes = &[ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, + ]; + + block_data.bytes(block_data_bytes).unwrap(); let encoded_block = encode_block( encoded_block_hdr, - encoder.writer().to_vec(), - vec![&secret_key_bytes], + block_data.writer().to_vec(), + vec![&secret_key_bytes, &secret_key_bytes], crate::HashFunction::Blake2b, ) .unwrap(); let decoded = decode_block(encoded_block).unwrap(); - println!("decoced {:?}", decoded); + assert_eq!(decoded.0 .0, chain_id); + assert_eq!(decoded.0 .1, block_height); + assert_eq!(decoded.0 .2, block_ts); + assert_eq!(decoded.0 .3, prev_block_height); + assert_eq!(decoded.0 .4, ledger_type); + assert_eq!(decoded.0 .5, purpose_id); + assert_eq!(decoded.0 .6, validators); + assert_eq!(decoded.0 .7, metadata); + + assert_eq!(decoded.1 .0, block_data_bytes.to_vec()); } } From 074d6dd07e7ce83eb3e9973d6d15a716da383fb6 Mon Sep 17 00:00:00 2001 From: cong-or Date: Wed, 16 Oct 2024 13:24:41 +0100 Subject: [PATCH 04/53] feat(validators): block signatures --- rust/ledger/src/lib.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/rust/ledger/src/lib.rs b/rust/ledger/src/lib.rs index 1641b1ad23..9e1736484b 100644 --- a/rust/ledger/src/lib.rs +++ b/rust/ledger/src/lib.rs @@ -51,9 +51,9 @@ pub struct Validator(pub Vec); #[derive(Debug, Clone, PartialEq)] pub struct Metadata(pub Vec); -/// block data + sig +/// Block header size #[derive(Debug, Clone, PartialEq)] -pub struct BlockDataAndSignature(Vec); +pub struct BlockHeaderSize(usize); /// Decoded block data #[derive(Debug, Clone, PartialEq)] @@ -76,7 +76,7 @@ type DecodedBlockHeader = ( PurposeId, Validator, Option, - BlockDataAndSignature, + BlockHeaderSize, ); /// Choice of hash function: @@ -114,8 +114,7 @@ pub fn encode_block( }) .collect(); - let out = block_hdr_cbor; - + let out: Vec = Vec::new(); let mut encoder = minicbor::Encoder::new(out); encoder.bytes(&block_data)?; @@ -124,7 +123,7 @@ pub fn encode_block( encoder.bytes(sig)?; } - Ok(encoder.writer().to_vec()) + Ok([block_hdr_cbor, encoder.writer().to_vec()].concat()) } /// Decoded block @@ -134,9 +133,9 @@ pub fn decode_block(encoded_block: Vec) -> anyhow::Result { // Decoded block hdr let block_hdr: DecodedBlockHeader = decode_block_header(encoded_block.clone())?; - // block data + sigs - let remaining_block = block_hdr.clone().8 .0; - let mut cbor_decoder = minicbor::Decoder::new(&remaining_block); + let mut cbor_decoder = minicbor::Decoder::new(&encoded_block); + // Decode remaining block, set position after block hdr data. + cbor_decoder.set_position(block_hdr.8 .0); // Block data let block_data = cbor_decoder @@ -277,9 +276,6 @@ pub fn decode_block_header(block: Vec) -> anyhow::Result Err(_) => None, }; - // return remaining blocks for further processing. - let (_block_hdr_bytes, remaining_block_bytes) = block.split_at(cbor_decoder.position()); - Ok(( chain_id, block_height, @@ -289,7 +285,7 @@ pub fn decode_block_header(block: Vec) -> anyhow::Result purpose_id, Validator(validators), metadata, - BlockDataAndSignature(remaining_block_bytes.to_vec()), + BlockHeaderSize(cbor_decoder.position()), )) } @@ -383,7 +379,7 @@ mod tests { .unwrap(); // validators - let secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ + let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, ]; @@ -392,15 +388,19 @@ mod tests { let mut block_data = minicbor::Encoder::new(out); let block_data_bytes = &[ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, + 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, + 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, + 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, + 157, ]; block_data.bytes(block_data_bytes).unwrap(); let encoded_block = encode_block( encoded_block_hdr, - block_data.writer().to_vec(), - vec![&secret_key_bytes, &secret_key_bytes], + block_data_bytes.to_vec(), + vec![&validator_secret_key_bytes, &validator_secret_key_bytes], crate::HashFunction::Blake2b, ) .unwrap(); From c11042c8accd4b1ae09a41c5a40b0b0cea9f7ede Mon Sep 17 00:00:00 2001 From: cong-or Date: Wed, 16 Oct 2024 13:41:05 +0100 Subject: [PATCH 05/53] feat(validators): block signatures --- rust/ledger/src/lib.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/rust/ledger/src/lib.rs b/rust/ledger/src/lib.rs index 9e1736484b..f2ca951927 100644 --- a/rust/ledger/src/lib.rs +++ b/rust/ledger/src/lib.rs @@ -291,14 +291,15 @@ pub fn decode_block_header(block: Vec) -> anyhow::Result #[cfg(test)] mod tests { - use ed25519_dalek::SECRET_KEY_LENGTH; + use ed25519_dalek::{SigningKey, SECRET_KEY_LENGTH}; use ulid::Ulid; use uuid::Uuid; use crate::{ - decode_block, decode_block_header, encode_block, encode_block_header, BlockTimeStamp, - ChainId, Height, Kid, LedgerType, Metadata, PreviousBlockHash, PurposeId, Validator, + blake2b_512, decode_block, decode_block_header, encode_block, encode_block_header, + BlockTimeStamp, ChainId, Height, Kid, LedgerType, Metadata, PreviousBlockHash, PurposeId, + Validator, }; #[test] @@ -398,7 +399,7 @@ mod tests { block_data.bytes(block_data_bytes).unwrap(); let encoded_block = encode_block( - encoded_block_hdr, + encoded_block_hdr.clone(), block_data_bytes.to_vec(), vec![&validator_secret_key_bytes, &validator_secret_key_bytes], crate::HashFunction::Blake2b, @@ -416,5 +417,17 @@ mod tests { assert_eq!(decoded.0 .7, metadata); assert_eq!(decoded.1 .0, block_data_bytes.to_vec()); + + let data_to_sign = [ + blake2b_512(&encoded_block_hdr).unwrap().to_vec(), + block_data_bytes.to_vec(), + ] + .concat(); + + let verifying_key = SigningKey::from_bytes(&validator_secret_key_bytes); + + for sig in decoded.2 .0 { + verifying_key.verify_strict(&data_to_sign, &sig).unwrap(); + } } } From 9a5916aeec57d3b20fbad1eb9b602787fbf6fd13 Mon Sep 17 00:00:00 2001 From: cong-or Date: Sun, 20 Oct 2024 15:26:42 +0100 Subject: [PATCH 06/53] feat(block validation): ledger --- rust/Cargo.toml | 2 +- rust/ledger/Cargo.toml | 23 --- rust/ledger/src/lib.rs | 433 ----------------------------------------- 3 files changed, 1 insertion(+), 457 deletions(-) delete mode 100644 rust/ledger/Cargo.toml delete mode 100644 rust/ledger/src/lib.rs diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 854429fbd5..a8f71dfa64 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -8,7 +8,7 @@ members = [ "cbork-abnf-parser", "cbork-cddl-parser", "catalyst-voting", - "ledger" + "immutable-ledger" ] [workspace.package] diff --git a/rust/ledger/Cargo.toml b/rust/ledger/Cargo.toml deleted file mode 100644 index 7e55c33321..0000000000 --- a/rust/ledger/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "ledger" -version = "0.1.0" -edition.workspace = true -authors.workspace = true -homepage.workspace = true -repository.workspace = true -license.workspace = true - -[dependencies] -ed25519-dalek = "2.1.1" -anyhow = "1.0.86" -minicbor = { version = "0.24", features = ["std"] } -uuid = { version = "1.10.0", features = ["v4", "serde"] } -ulid = { version = "1.1.3", features = ["serde", "uuid"] } -cddl = "0.9.4" -hex = "0.4.3" -blake2b_simd = "1.0.2" -blake3 = "=0.1.3" -rand = "0.8.5" - -[lints] -workspace = true diff --git a/rust/ledger/src/lib.rs b/rust/ledger/src/lib.rs deleted file mode 100644 index f2ca951927..0000000000 --- a/rust/ledger/src/lib.rs +++ /dev/null @@ -1,433 +0,0 @@ -//! Block Serialization -//! -//! Facilitates block serialization for immutable ledger - -use anyhow::Ok; -use blake2b_simd::{self, Params}; -use core::result::Result::Ok as ResultOk; -use ed25519_dalek::{ed25519::signature::SignerMut, Signature, SigningKey, SECRET_KEY_LENGTH}; - -use ulid::Ulid; -use uuid::Uuid; - -/// Kid (The key identifier) size in bytes -const KID_BYTES: usize = 16; - -/// Key ID - Blake2b-128 hash of the Role 0 Certificate defining the Session public key. -/// BLAKE2b-128 produces digest side of 16 bytes. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Kid(pub [u8; KID_BYTES]); - -/// Unique identifier of the chain. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct ChainId(pub Ulid); - -/// Block height. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Height(pub u32); - -/// Block epoch-based date/time. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct BlockTimeStamp(pub i64); - -/// Previous Block hash. -#[derive(Debug, Clone, PartialEq)] -pub struct PreviousBlockHash(pub Vec); - -/// unique identifier of the ledger type. -/// In general, this is the way to strictly bound and specify block_data of the ledger for the specific ledger_type. -#[derive(Debug, Clone, PartialEq)] -pub struct LedgerType(pub Uuid); - -/// unique identifier of the purpose, each Ledger instance will have a strict time boundaries, so each of them will run for different purposes. -#[derive(Debug, Clone, PartialEq)] -pub struct PurposeId(pub Ulid); - -/// Identifier or identifiers of the entity who was produced and processed a block. -#[derive(Debug, Clone, PartialEq)] -pub struct Validator(pub Vec); - -/// Optional field, to add some arbitrary metadata to the block. -#[derive(Debug, Clone, PartialEq)] -pub struct Metadata(pub Vec); - -/// Block header size -#[derive(Debug, Clone, PartialEq)] -pub struct BlockHeaderSize(usize); - -/// Decoded block data -#[derive(Debug, Clone, PartialEq)] -pub struct DecodedBlockData(Vec); - -/// Signatures -#[derive(Debug, Clone, PartialEq)] -pub struct Signatures(Vec); - -/// Decoder block -type DecodedBlock = (DecodedBlockHeader, DecodedBlockData, Signatures); - -/// Decoder block header -type DecodedBlockHeader = ( - ChainId, - Height, - BlockTimeStamp, - PreviousBlockHash, - LedgerType, - PurposeId, - Validator, - Option, - BlockHeaderSize, -); - -/// Choice of hash function: -/// must be the same as the hash of the previous block. -pub enum HashFunction { - /// BLAKE3 is based on an optimized instance of the established hash function BLAKE2 and on the original Bao tree mode - Blake3, - /// BLAKE2b-512 produces digest side of 512 bits. - Blake2b, -} - -/// Encode block -pub fn encode_block( - block_hdr_cbor: Vec, block_data: Vec, validator_keys: Vec<&[u8; SECRET_KEY_LENGTH]>, - hasher: HashFunction, -) -> anyhow::Result> { - let hashed_block_header = match hasher { - HashFunction::Blake3 => blake3(&block_hdr_cbor)?.to_vec(), - HashFunction::Blake2b => blake2b_512(&block_hdr_cbor)?.to_vec(), - }; - - // validator_signature MUST be a signature of the hashed block_header bytes - // and the block_data bytes - - let data_to_sign = [hashed_block_header, block_data.clone()].concat(); - - // if validator is only one id => validator_signature contains only 1 signature; - // if validator is array => validator_signature contains an array with the same length; - - let signatures: Vec<[u8; 64]> = validator_keys - .iter() - .map(|sk| { - let mut sk: SigningKey = SigningKey::from_bytes(&sk); - sk.sign(&data_to_sign).to_bytes() - }) - .collect(); - - let out: Vec = Vec::new(); - let mut encoder = minicbor::Encoder::new(out); - - encoder.bytes(&block_data)?; - - for sig in signatures.iter() { - encoder.bytes(sig)?; - } - - Ok([block_hdr_cbor, encoder.writer().to_vec()].concat()) -} - -/// Decoded block -pub fn decode_block(encoded_block: Vec) -> anyhow::Result { - // Decode cbor to bytes - - // Decoded block hdr - let block_hdr: DecodedBlockHeader = decode_block_header(encoded_block.clone())?; - - let mut cbor_decoder = minicbor::Decoder::new(&encoded_block); - // Decode remaining block, set position after block hdr data. - cbor_decoder.set_position(block_hdr.8 .0); - - // Block data - let block_data = cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block data : {e}")))?; - - // Extract signatures, block hdr indicates how many validators. - let mut sigs = Vec::new(); - for _sig in 0..block_hdr.6 .0.len() { - let sig: [u8; 64] = cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for signature : {e}")))? - .try_into()?; - - sigs.push(Signature::from_bytes(&sig)); - } - - Ok(( - block_hdr, - DecodedBlockData(block_data.to_vec()), - Signatures(sigs), - )) -} - -/// Produce BLAKE3 hash -pub(crate) fn blake3(value: &[u8]) -> anyhow::Result<[u8; 32]> { - Ok(*blake3::hash(value).as_bytes()) -} - -/// BLAKE2b-512 produces digest side of 512 bits. -pub(crate) fn blake2b_512(value: &[u8]) -> anyhow::Result<[u8; 64]> { - let h = Params::new().hash_length(64).hash(value); - let b = h.as_bytes(); - b.try_into() - .map_err(|_| anyhow::anyhow!("Invalid length of blake2b_512, expected 64 got {}", b.len())) -} - -/// Encode block header -pub fn encode_block_header( - chain_id: ChainId, height: Height, ts: BlockTimeStamp, prev_block_hash: PreviousBlockHash, - ledger_type: LedgerType, pid: PurposeId, validator: Validator, metadata: Option, -) -> anyhow::Result> { - let out: Vec = Vec::new(); - let mut encoder = minicbor::Encoder::new(out); - - encoder.bytes(&chain_id.0.to_bytes())?; - encoder.bytes(&height.0.to_be_bytes())?; - encoder.bytes(&ts.0.to_be_bytes())?; - encoder.bytes(&prev_block_hash.0.as_slice())?; - encoder.bytes(ledger_type.0.as_bytes())?; - encoder.bytes(&pid.0.to_bytes())?; - encoder.bytes(&validator.0.len().to_be_bytes())?; - - for validator in validator.0.iter() { - encoder.bytes(&validator.0)?; - } - - if let Some(meta) = metadata { - encoder.bytes(&meta.0)?; - } - - Ok(encoder.writer().to_vec()) -} -/// Decode block header -pub fn decode_block_header(block: Vec) -> anyhow::Result { - // Decode cbor to bytes - let mut cbor_decoder = minicbor::Decoder::new(&block); - - // Raw chain_id - let chain_id = ChainId(Ulid::from_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for chain id : {e}")))? - .try_into()?, - )); - - // Raw Block height - let block_height = Height(u32::from_be_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block height : {e}")))? - .try_into()?, - )); - - // Raw time stamp - let ts = BlockTimeStamp(i64::from_be_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for timestamp : {e}")))? - .try_into()?, - )); - - // Raw prev block hash - let prev_block_hash = PreviousBlockHash( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for prev block hash : {e}")))? - .to_vec(), - ); - - // Raw ledger type - let ledger_type = LedgerType(Uuid::from_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for ledger type : {e}")))? - .try_into()?, - )); - - // Raw purpose id - let purpose_id = PurposeId(Ulid::from_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for purpose id : {e}")))? - .try_into()?, - )); - - // Number of validators - let number_of_validators = usize::from_be_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for number of validators : {e}")))? - .try_into()?, - ); - - // Extract validators - let mut validators = Vec::new(); - for _validator in 0..number_of_validators { - let validator_kid: [u8; 16] = cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for validators : {e}")))? - .try_into()?; - - validators.push(Kid(validator_kid)); - } - - let metadata = match cbor_decoder.bytes() { - ResultOk(meta) => Some(Metadata(meta.to_vec())), - Err(_) => None, - }; - - Ok(( - chain_id, - block_height, - ts, - prev_block_hash, - ledger_type, - purpose_id, - Validator(validators), - metadata, - BlockHeaderSize(cbor_decoder.position()), - )) -} - -#[cfg(test)] -mod tests { - use ed25519_dalek::{SigningKey, SECRET_KEY_LENGTH}; - - use ulid::Ulid; - use uuid::Uuid; - - use crate::{ - blake2b_512, decode_block, decode_block_header, encode_block, encode_block_header, - BlockTimeStamp, ChainId, Height, Kid, LedgerType, Metadata, PreviousBlockHash, PurposeId, - Validator, - }; - - #[test] - fn block_header_encode_decode() { - let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - - let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - - let chain_id = ChainId(Ulid::new()); - let block_height = Height(5); - let block_ts = BlockTimeStamp(1728474515); - let prev_block_height = PreviousBlockHash(vec![0; 64]); - let ledger_type = LedgerType(Uuid::new_v4()); - let purpose_id = PurposeId(Ulid::new()); - let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); - let metadata = Some(Metadata(vec![1; 128])); - - let encoded_block_hdr = encode_block_header( - chain_id, - block_height, - block_ts, - prev_block_height.clone(), - ledger_type.clone(), - purpose_id.clone(), - validators.clone(), - metadata.clone(), - ) - .unwrap(); - - let decoded_hdr = decode_block_header(encoded_block_hdr).unwrap(); - assert_eq!(decoded_hdr.0, chain_id); - assert_eq!(decoded_hdr.1, block_height); - assert_eq!(decoded_hdr.2, block_ts); - assert_eq!(decoded_hdr.3, prev_block_height); - assert_eq!(decoded_hdr.4, ledger_type); - assert_eq!(decoded_hdr.5, purpose_id); - assert_eq!(decoded_hdr.6, validators); - assert_eq!(decoded_hdr.7, metadata); - } - - #[test] - fn block_encode_decode() { - let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - - let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - - let chain_id = ChainId(Ulid::new()); - let block_height = Height(5); - let block_ts = BlockTimeStamp(1728474515); - let prev_block_height = PreviousBlockHash(vec![0; 64]); - let ledger_type = LedgerType(Uuid::new_v4()); - let purpose_id = PurposeId(Ulid::new()); - let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); - let metadata = Some(Metadata(vec![1; 128])); - - let encoded_block_hdr = encode_block_header( - chain_id, - block_height, - block_ts, - prev_block_height.clone(), - ledger_type.clone(), - purpose_id.clone(), - validators.clone(), - metadata.clone(), - ) - .unwrap(); - - // validators - let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, - 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, - ]; - - let out: Vec = Vec::new(); - let mut block_data = minicbor::Encoder::new(out); - - let block_data_bytes = &[ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, - 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, - 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, - 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, - 157, - ]; - - block_data.bytes(block_data_bytes).unwrap(); - - let encoded_block = encode_block( - encoded_block_hdr.clone(), - block_data_bytes.to_vec(), - vec![&validator_secret_key_bytes, &validator_secret_key_bytes], - crate::HashFunction::Blake2b, - ) - .unwrap(); - - let decoded = decode_block(encoded_block).unwrap(); - assert_eq!(decoded.0 .0, chain_id); - assert_eq!(decoded.0 .1, block_height); - assert_eq!(decoded.0 .2, block_ts); - assert_eq!(decoded.0 .3, prev_block_height); - assert_eq!(decoded.0 .4, ledger_type); - assert_eq!(decoded.0 .5, purpose_id); - assert_eq!(decoded.0 .6, validators); - assert_eq!(decoded.0 .7, metadata); - - assert_eq!(decoded.1 .0, block_data_bytes.to_vec()); - - let data_to_sign = [ - blake2b_512(&encoded_block_hdr).unwrap().to_vec(), - block_data_bytes.to_vec(), - ] - .concat(); - - let verifying_key = SigningKey::from_bytes(&validator_secret_key_bytes); - - for sig in decoded.2 .0 { - verifying_key.verify_strict(&data_to_sign, &sig).unwrap(); - } - } -} From 41de047052057a08f91cd7a1492a8de93164716f Mon Sep 17 00:00:00 2001 From: cong-or Date: Sun, 20 Oct 2024 15:26:54 +0100 Subject: [PATCH 07/53] feat(block validation): ledger --- rust/immutable-ledger/Cargo.toml | 25 ++ rust/immutable-ledger/src/lib.rs | 9 + rust/immutable-ledger/src/serialize.rs | 446 +++++++++++++++++++++++++ rust/immutable-ledger/src/validate.rs | 161 +++++++++ 4 files changed, 641 insertions(+) create mode 100644 rust/immutable-ledger/Cargo.toml create mode 100644 rust/immutable-ledger/src/lib.rs create mode 100644 rust/immutable-ledger/src/serialize.rs create mode 100644 rust/immutable-ledger/src/validate.rs diff --git a/rust/immutable-ledger/Cargo.toml b/rust/immutable-ledger/Cargo.toml new file mode 100644 index 0000000000..7deec2e82e --- /dev/null +++ b/rust/immutable-ledger/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "immutable-ledger" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +ed25519-dalek = "2.1.1" +anyhow = "1.0.86" +minicbor = { version = "0.24", features = ["std"] } +uuid = { version = "1.10.0", features = ["v4", "serde"] } +ulid = { version = "1.1.3", features = ["serde", "uuid"] } +cddl = "0.9.4" +hex = "0.4.3" +blake2b_simd = "1.0.2" +blake3 = "=0.1.3" +rand = "0.8.5" + + + +[lints] +workspace = true diff --git a/rust/immutable-ledger/src/lib.rs b/rust/immutable-ledger/src/lib.rs new file mode 100644 index 0000000000..1e05ec62ea --- /dev/null +++ b/rust/immutable-ledger/src/lib.rs @@ -0,0 +1,9 @@ +//! Block Serialization +//! +//! Facilitates block serializatio and validation for immutable ledger + +/// Block validation logic +pub mod validate; + +/// Block encoding decoding +pub mod serialize; diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs new file mode 100644 index 0000000000..0c4f95ac75 --- /dev/null +++ b/rust/immutable-ledger/src/serialize.rs @@ -0,0 +1,446 @@ +//! Block Serialization +//! +//! Facilitates block serialization for immutable ledger + +use anyhow::Ok; +use blake2b_simd::{self, Params}; +use core::result::Result::Ok as ResultOk; +use ed25519_dalek::{ed25519::signature::SignerMut, Signature, SigningKey, SECRET_KEY_LENGTH}; + +use ulid::Ulid; +use uuid::Uuid; + +/// Kid (The key identifier) size in bytes +const KID_BYTES: usize = 16; + +/// Key ID - Blake2b-128 hash of the Role 0 Certificate defining the Session public key. +/// BLAKE2b-128 produces digest side of 16 bytes. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Kid(pub [u8; KID_BYTES]); + +/// Unique identifier of the chain. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ChainId(pub Ulid); + +/// Block height. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Height(pub u32); + +/// Block epoch-based date/time. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct BlockTimeStamp(pub i64); + +/// Previous Block hash. +#[derive(Debug, Clone, PartialEq)] +pub struct PreviousBlockHash(pub Vec); + +/// unique identifier of the ledger type. +/// In general, this is the way to strictly bound and specify block_data of the ledger for the specific ledger_type. +#[derive(Debug, Clone, PartialEq)] +pub struct LedgerType(pub Uuid); + +/// unique identifier of the purpose, each Ledger instance will have a strict time boundaries, so each of them will run for different purposes. +#[derive(Debug, Clone, PartialEq)] +pub struct PurposeId(pub Ulid); + +/// Identifier or identifiers of the entity who was produced and processed a block. +#[derive(Debug, Clone, PartialEq)] +pub struct Validator(pub Vec); + +/// Optional field, to add some arbitrary metadata to the block. +#[derive(Debug, Clone, PartialEq)] +pub struct Metadata(pub Vec); + +/// Block header size +#[derive(Debug, Clone, PartialEq)] +pub struct BlockHeaderSize(usize); + +/// Decoded block data +#[derive(Debug, Clone, PartialEq)] +pub struct DecodedBlockData(Vec); + +/// Encoded block data as cbor +#[derive(Debug, Clone, PartialEq)] +pub struct EncodedBlockData(pub Vec); + +/// Signatures +#[derive(Debug, Clone, PartialEq)] +pub struct Signatures(Vec); + +/// Validator's keys defined in the corresponding certificates referenced by the validator. +pub struct ValidatorKeys(pub Vec<[u8; SECRET_KEY_LENGTH]>); + +/// Decoder block +pub type DecodedBlock = (DecodedBlockHeader, DecodedBlockData, Signatures); + +/// Decoder block header +pub type DecodedBlockHeader = ( + ChainId, + Height, + BlockTimeStamp, + PreviousBlockHash, + LedgerType, + PurposeId, + Validator, + Option, + BlockHeaderSize, +); + +/// Encoded whole block including block header, cbor encoded block data and signatures. +pub type EncodedBlock = Vec; + +/// Choice of hash function: +/// must be the same as the hash of the previous block. +pub enum HashFunction { + /// BLAKE3 is based on an optimized instance of the established hash function BLAKE2 and on the original Bao tree mode + Blake3, + /// BLAKE2b-512 produces digest side of 512 bits. + Blake2b, +} + +/// Encode block +pub fn encode_block( + block_hdr_cbor: Vec, block_data: EncodedBlockData, validator_keys: ValidatorKeys, + hasher: HashFunction, +) -> anyhow::Result { + let hashed_block_header = match hasher { + HashFunction::Blake3 => blake3(&block_hdr_cbor)?.to_vec(), + HashFunction::Blake2b => blake2b_512(&block_hdr_cbor)?.to_vec(), + }; + + // validator_signature MUST be a signature of the hashed block_header bytes + // and the block_data bytes + + let data_to_sign = [hashed_block_header, block_data.0.clone()].concat(); + + // if validator is only one id => validator_signature contains only 1 signature; + // if validator is array => validator_signature contains an array with the same length; + + let signatures: Vec<[u8; 64]> = validator_keys + .0 + .iter() + .map(|sk| { + let mut sk: SigningKey = SigningKey::from_bytes(&sk); + sk.sign(&data_to_sign).to_bytes() + }) + .collect(); + + let out: Vec = Vec::new(); + let mut encoder = minicbor::Encoder::new(out); + + encoder.bytes(&block_data.0)?; + + for sig in signatures.iter() { + encoder.bytes(sig)?; + } + + Ok([block_hdr_cbor, encoder.writer().to_vec()].concat()) +} + +/// Decoded block +pub fn decode_block(encoded_block: Vec) -> anyhow::Result { + // Decode cbor to bytes + + // Decoded block hdr + let block_hdr: DecodedBlockHeader = decode_block_header(encoded_block.clone())?; + + let mut cbor_decoder = minicbor::Decoder::new(&encoded_block); + // Decode remaining block, set position after block hdr data. + cbor_decoder.set_position(block_hdr.8 .0); + + // Block data + let block_data = cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block data : {e}")))?; + + // Extract signatures, block hdr indicates how many validators. + let mut sigs = Vec::new(); + for _sig in 0..block_hdr.6 .0.len() { + let sig: [u8; 64] = cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for signature : {e}")))? + .try_into()?; + + sigs.push(Signature::from_bytes(&sig)); + } + + Ok(( + block_hdr, + DecodedBlockData(block_data.to_vec()), + Signatures(sigs), + )) +} + +/// Produce BLAKE3 hash +pub(crate) fn blake3(value: &[u8]) -> anyhow::Result<[u8; 32]> { + Ok(*blake3::hash(value).as_bytes()) +} + +/// BLAKE2b-512 produces digest side of 512 bits. +pub(crate) fn blake2b_512(value: &[u8]) -> anyhow::Result<[u8; 64]> { + let h = Params::new().hash_length(64).hash(value); + let b = h.as_bytes(); + b.try_into() + .map_err(|_| anyhow::anyhow!("Invalid length of blake2b_512, expected 64 got {}", b.len())) +} + +/// Encode block header +pub fn encode_block_header( + chain_id: ChainId, height: Height, ts: BlockTimeStamp, prev_block_hash: PreviousBlockHash, + ledger_type: LedgerType, pid: PurposeId, validator: Validator, metadata: Option, +) -> anyhow::Result> { + let out: Vec = Vec::new(); + let mut encoder = minicbor::Encoder::new(out); + + encoder.bytes(&chain_id.0.to_bytes())?; + encoder.bytes(&height.0.to_be_bytes())?; + encoder.bytes(&ts.0.to_be_bytes())?; + encoder.bytes(&prev_block_hash.0.as_slice())?; + encoder.bytes(ledger_type.0.as_bytes())?; + encoder.bytes(&pid.0.to_bytes())?; + + // marks how many validators for decoding side. + encoder.bytes(&validator.0.len().to_be_bytes())?; + for validator in validator.0.iter() { + encoder.bytes(&validator.0)?; + } + + if let Some(meta) = metadata { + encoder.bytes(&meta.0)?; + } + + Ok(encoder.writer().to_vec()) +} +/// Decode block header +pub fn decode_block_header(block: Vec) -> anyhow::Result { + // Decode cbor to bytes + let mut cbor_decoder = minicbor::Decoder::new(&block); + + // Raw chain_id + let chain_id = ChainId(Ulid::from_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for chain id : {e}")))? + .try_into()?, + )); + + // Raw Block height + let block_height = Height(u32::from_be_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block height : {e}")))? + .try_into()?, + )); + + // Raw time stamp + let ts = BlockTimeStamp(i64::from_be_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for timestamp : {e}")))? + .try_into()?, + )); + + // Raw prev block hash + let prev_block_hash = PreviousBlockHash( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for prev block hash : {e}")))? + .to_vec(), + ); + + // Raw ledger type + let ledger_type = LedgerType(Uuid::from_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for ledger type : {e}")))? + .try_into()?, + )); + + // Raw purpose id + let purpose_id = PurposeId(Ulid::from_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for purpose id : {e}")))? + .try_into()?, + )); + + // Number of validators + let number_of_validators = usize::from_be_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for number of validators : {e}")))? + .try_into()?, + ); + + // Extract validators + let mut validators = Vec::new(); + for _validator in 0..number_of_validators { + let validator_kid: [u8; 16] = cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for validators : {e}")))? + .try_into()?; + + validators.push(Kid(validator_kid)); + } + + let metadata = match cbor_decoder.bytes() { + ResultOk(meta) => Some(Metadata(meta.to_vec())), + Err(_) => None, + }; + + Ok(( + chain_id, + block_height, + ts, + prev_block_hash, + ledger_type, + purpose_id, + Validator(validators), + metadata, + BlockHeaderSize(cbor_decoder.position()), + )) +} + +#[cfg(test)] +mod tests { + use ed25519_dalek::{SigningKey, SECRET_KEY_LENGTH}; + + use ulid::Ulid; + use uuid::Uuid; + + use crate::serialize::{ + blake2b_512, decode_block, decode_block_header, encode_block, encode_block_header, + BlockTimeStamp, ChainId, EncodedBlockData, Height, Kid, LedgerType, Metadata, + PreviousBlockHash, PurposeId, Validator, ValidatorKeys, + }; + + use crate::serialize::HashFunction::Blake2b; + #[test] + fn block_header_encode_decode() { + let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let chain_id = ChainId(Ulid::new()); + let block_height = Height(5); + let block_ts = BlockTimeStamp(1728474515); + let prev_block_height = PreviousBlockHash(vec![0; 64]); + let ledger_type = LedgerType(Uuid::new_v4()); + let purpose_id = PurposeId(Ulid::new()); + let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); + let metadata = Some(Metadata(vec![1; 128])); + + let encoded_block_hdr = encode_block_header( + chain_id, + block_height, + block_ts, + prev_block_height.clone(), + ledger_type.clone(), + purpose_id.clone(), + validators.clone(), + metadata.clone(), + ) + .unwrap(); + + let decoded_hdr = decode_block_header(encoded_block_hdr).unwrap(); + assert_eq!(decoded_hdr.0, chain_id); + assert_eq!(decoded_hdr.1, block_height); + assert_eq!(decoded_hdr.2, block_ts); + assert_eq!(decoded_hdr.3, prev_block_height); + assert_eq!(decoded_hdr.4, ledger_type); + assert_eq!(decoded_hdr.5, purpose_id); + assert_eq!(decoded_hdr.6, validators); + assert_eq!(decoded_hdr.7, metadata); + } + + #[test] + fn block_encode_decode() { + let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let chain_id = ChainId(Ulid::new()); + let block_height = Height(5); + let block_ts = BlockTimeStamp(1728474515); + let prev_block_height = PreviousBlockHash(vec![0; 64]); + let ledger_type = LedgerType(Uuid::new_v4()); + let purpose_id = PurposeId(Ulid::new()); + let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); + let metadata = Some(Metadata(vec![1; 128])); + + let encoded_block_hdr = encode_block_header( + chain_id, + block_height, + block_ts, + prev_block_height.clone(), + ledger_type.clone(), + purpose_id.clone(), + validators.clone(), + metadata.clone(), + ) + .unwrap(); + + // validators + let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, + 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, + ]; + + let out: Vec = Vec::new(); + let mut block_data = minicbor::Encoder::new(out); + + let block_data_bytes = &[ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, + 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, + 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, + 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, + 157, + ]; + + block_data.bytes(block_data_bytes).unwrap(); + + let encoded_block = encode_block( + encoded_block_hdr.clone(), + EncodedBlockData(block_data_bytes.to_vec()), + ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), + Blake2b, + ) + .unwrap(); + + let decoded = decode_block(encoded_block).unwrap(); + assert_eq!(decoded.0 .0, chain_id); + assert_eq!(decoded.0 .1, block_height); + assert_eq!(decoded.0 .2, block_ts); + assert_eq!(decoded.0 .3, prev_block_height); + assert_eq!(decoded.0 .4, ledger_type); + assert_eq!(decoded.0 .5, purpose_id); + assert_eq!(decoded.0 .6, validators); + assert_eq!(decoded.0 .7, metadata); + + assert_eq!(decoded.1 .0, block_data_bytes.to_vec()); + + let data_to_sign = [ + blake2b_512(&encoded_block_hdr).unwrap().to_vec(), + block_data_bytes.to_vec(), + ] + .concat(); + + let verifying_key = SigningKey::from_bytes(&validator_secret_key_bytes); + + for sig in decoded.2 .0 { + verifying_key.verify_strict(&data_to_sign, &sig).unwrap(); + } + } +} diff --git a/rust/immutable-ledger/src/validate.rs b/rust/immutable-ledger/src/validate.rs new file mode 100644 index 0000000000..116348e832 --- /dev/null +++ b/rust/immutable-ledger/src/validate.rs @@ -0,0 +1,161 @@ +//! Block validation +//! +//! Facilitates validation for immutable ledger + +use crate::serialize::EncodedBlock; + +/// Validate block +pub fn validate_block(_current_block: EncodedBlock, _previous_block: EncodedBlock) -> bool { + todo!() +} + +#[cfg(test)] +mod tests { + + use ed25519_dalek::SECRET_KEY_LENGTH; + + use ulid::Ulid; + use uuid::Uuid; + + use crate::serialize::{ + encode_block, encode_block_header, BlockTimeStamp, ChainId, EncodedBlockData, Height, Kid, + LedgerType, Metadata, PreviousBlockHash, PurposeId, Validator, ValidatorKeys, + }; + + use crate::serialize::HashFunction::Blake2b; + + use super::validate_block; + + #[test] + fn validate_block_test() { + // + // CURRENT BLOCK + // + // + let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let chain_id = ChainId(Ulid::new()); + let block_height = Height(5); + let block_ts = BlockTimeStamp(1728474515); + let prev_block_height = PreviousBlockHash(vec![0; 64]); + let ledger_type = LedgerType(Uuid::new_v4()); + let purpose_id = PurposeId(Ulid::new()); + let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); + let metadata = Some(Metadata(vec![1; 128])); + + let encoded_block_hdr = encode_block_header( + chain_id, + block_height, + block_ts, + prev_block_height.clone(), + ledger_type.clone(), + purpose_id.clone(), + validators.clone(), + metadata.clone(), + ) + .unwrap(); + + // validators + let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, + 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, + ]; + + let out: Vec = Vec::new(); + let mut block_data = minicbor::Encoder::new(out); + + let block_data_bytes = &[ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, + 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, + 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, + 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, + 157, + ]; + + block_data.bytes(block_data_bytes).unwrap(); + + let current_block = encode_block( + encoded_block_hdr.clone(), + EncodedBlockData(block_data_bytes.to_vec()), + ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), + Blake2b, + ) + .unwrap(); + + // + // PREVIOUS B + // + // + let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let chain_id = ChainId(Ulid::new()); + let block_height = Height(5); + let block_ts = BlockTimeStamp(1728474515); + let prev_block_height = PreviousBlockHash(vec![0; 64]); + let ledger_type = LedgerType(Uuid::new_v4()); + let purpose_id = PurposeId(Ulid::new()); + let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); + let metadata = Some(Metadata(vec![1; 128])); + + let encoded_block_hdr = encode_block_header( + chain_id, + block_height, + block_ts, + prev_block_height.clone(), + ledger_type.clone(), + purpose_id.clone(), + validators.clone(), + metadata.clone(), + ) + .unwrap(); + + // validators + let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, + 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, + ]; + + let out: Vec = Vec::new(); + let mut block_data = minicbor::Encoder::new(out); + + let block_data_bytes = &[ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, + 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, + 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, + 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, + 157, + ]; + + block_data.bytes(block_data_bytes).unwrap(); + + let previous_block = encode_block( + encoded_block_hdr.clone(), + EncodedBlockData(block_data_bytes.to_vec()), + ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), + Blake2b, + ) + .unwrap(); + + // + // VALIDATE BLOCK + // + + validate_block(current_block, previous_block); + } +} From 609a99b591557fb168179f65b2dd27c4e68d6b44 Mon Sep 17 00:00:00 2001 From: cong-or Date: Sun, 20 Oct 2024 19:27:09 +0100 Subject: [PATCH 08/53] feat(block validation): ledger --- rust/immutable-ledger/src/validate.rs | 141 ++++++++++++++++++-------- 1 file changed, 96 insertions(+), 45 deletions(-) diff --git a/rust/immutable-ledger/src/validate.rs b/rust/immutable-ledger/src/validate.rs index 116348e832..8067039a76 100644 --- a/rust/immutable-ledger/src/validate.rs +++ b/rust/immutable-ledger/src/validate.rs @@ -2,11 +2,89 @@ //! //! Facilitates validation for immutable ledger -use crate::serialize::EncodedBlock; +use crate::serialize::blake3; +use anyhow::Ok; -/// Validate block -pub fn validate_block(_current_block: EncodedBlock, _previous_block: EncodedBlock) -> bool { - todo!() +use crate::serialize::{blake2b_512, decode_block, EncodedBlock, HashFunction}; + +/// Validate current block against previous block. +pub fn validate_block( + current_block: EncodedBlock, previous_block: EncodedBlock, hasher: HashFunction, +) -> anyhow::Result<()> { + let current_block = decode_block(current_block)?; + + let hashed_previous_block = match hasher { + HashFunction::Blake3 => blake3(&previous_block)?.to_vec(), + HashFunction::Blake2b => blake2b_512(&previous_block)?.to_vec(), + }; + let previous_block = decode_block(previous_block)?; + + // chain_id MUST be the same as for the previous block (except for genesis). + if current_block.0 .0 != previous_block.0 .0 { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: Chain_id MUST be NOT the same as for the previous block {:?} {:?}", + current_block.0 .0, + previous_block.0 .0 + )); + }; + + // height MUST be incremented by 1 from the previous block height value (except for genesis and final block). + // Genesis block MUST have 0 value. Final block MUST hash be incremented by 1 from the previous block height + // and changed the sign to negative. E.g. previous block height is 9 and the Final block height is -10. + if current_block.0 .1 .0 != previous_block.0 .1 .0 + 1 { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: height validation failed: {:?} {:?}", + current_block.0 .1 .0, + previous_block.0 .1 .0 + )); + } + + // timestamp MUST be greater or equals than the timestamp of the previous block (except for genesis) + if current_block.0 .2 .0 <= previous_block.0 .2 .0 { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: timestamp validation failed: {:?} {:?}", + current_block.0 .2 .0, + previous_block.0 .2 .0 + )); + } + + // prev_block_id MUST be a hash of the previous block bytes (except for genesis). + if current_block.0 .3 .0 != hashed_previous_block { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: previous hash validation failed: {:?} {:?}", + current_block.0 .3 .0, + previous_block.0 .3 .0 + )); + } + + // ledger_type MUST be the same as for the previous block if present (except for genesis). + if current_block.0 .4 .0 != previous_block.0 .4 .0 { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: ledger type validation failed: {:?} {:?}", + current_block.0 .4 .0, + previous_block.0 .4 .0 + )); + } + + // purpose_id MUST be the same as for the previous block if present (except for genesis). + if current_block.0 .5 .0 != previous_block.0 .5 .0 { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: purpose id validation failed: {:?} {:?}", + current_block.0 .5 .0, + previous_block.0 .5 .0 + )); + } + + // validator MUST be the same as for the previous block if present (except for genesis) + if current_block.0 .6 .0 != previous_block.0 .6 .0 { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: validator validation failed: {:?} {:?}", + current_block.0 .6 .0, + previous_block.0 .6 .0 + )); + } + + Ok(()) } #[cfg(test)] @@ -18,8 +96,8 @@ mod tests { use uuid::Uuid; use crate::serialize::{ - encode_block, encode_block_header, BlockTimeStamp, ChainId, EncodedBlockData, Height, Kid, - LedgerType, Metadata, PreviousBlockHash, PurposeId, Validator, ValidatorKeys, + blake2b_512, encode_block, encode_block_header, BlockTimeStamp, ChainId, EncodedBlockData, + Height, Kid, LedgerType, Metadata, PreviousBlockHash, PurposeId, Validator, ValidatorKeys, }; use crate::serialize::HashFunction::Blake2b; @@ -29,7 +107,7 @@ mod tests { #[test] fn validate_block_test() { // - // CURRENT BLOCK + // PREVIOUS BLOCK // // let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") @@ -82,7 +160,7 @@ mod tests { block_data.bytes(block_data_bytes).unwrap(); - let current_block = encode_block( + let previous_block = encode_block( encoded_block_hdr.clone(), EncodedBlockData(block_data_bytes.to_vec()), ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), @@ -91,25 +169,12 @@ mod tests { .unwrap(); // - // PREVIOUS B - // + // CURRENT BLOCK // - let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - - let chain_id = ChainId(Ulid::new()); - let block_height = Height(5); - let block_ts = BlockTimeStamp(1728474515); - let prev_block_height = PreviousBlockHash(vec![0; 64]); - let ledger_type = LedgerType(Uuid::new_v4()); - let purpose_id = PurposeId(Ulid::new()); + let block_height = Height(6); + let block_ts = BlockTimeStamp(1728474518); + let prev_block_hash = PreviousBlockHash(blake2b_512(&previous_block).unwrap().to_vec()); let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); let metadata = Some(Metadata(vec![1; 128])); @@ -117,7 +182,7 @@ mod tests { chain_id, block_height, block_ts, - prev_block_height.clone(), + prev_block_hash, ledger_type.clone(), purpose_id.clone(), validators.clone(), @@ -125,26 +190,9 @@ mod tests { ) .unwrap(); - // validators - let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, - 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, - ]; - - let out: Vec = Vec::new(); - let mut block_data = minicbor::Encoder::new(out); - - let block_data_bytes = &[ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, - 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, - 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, - 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, - 157, - ]; - block_data.bytes(block_data_bytes).unwrap(); - let previous_block = encode_block( + let current_block = encode_block( encoded_block_hdr.clone(), EncodedBlockData(block_data_bytes.to_vec()), ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), @@ -156,6 +204,9 @@ mod tests { // VALIDATE BLOCK // - validate_block(current_block, previous_block); + match validate_block(current_block, previous_block, Blake2b) { + Ok(_) => (), + Err(err) => panic!("Block validation failed: {:?}", err), + }; } } From 672e2d73f9940bc91a790bf3037b4ab9ce799951 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 13:44:14 +0100 Subject: [PATCH 09/53] feat(block validation): ledger --- rust/immutable-ledger/src/lib.rs | 5 +- rust/immutable-ledger/src/serialize.rs | 224 ++++++++++++++++++++++++- rust/immutable-ledger/src/validate.rs | 82 ++++++++- 3 files changed, 299 insertions(+), 12 deletions(-) diff --git a/rust/immutable-ledger/src/lib.rs b/rust/immutable-ledger/src/lib.rs index 1e05ec62ea..857191c0d1 100644 --- a/rust/immutable-ledger/src/lib.rs +++ b/rust/immutable-ledger/src/lib.rs @@ -1,6 +1,9 @@ //! Block Serialization //! -//! Facilitates block serializatio and validation for immutable ledger +//! Facilitates block serialization and validation for immutable ledger +//! +//! Spec: https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/immutable_ledger/ledger/ +//! /// Block validation logic pub mod validate; diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index 0c4f95ac75..bc3cca6e6c 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -63,6 +63,10 @@ pub struct DecodedBlockData(Vec); #[derive(Debug, Clone, PartialEq)] pub struct EncodedBlockData(pub Vec); +/// Encoded genesis Block contents as cbor, used for hash validation +#[derive(Debug, Clone, PartialEq)] +pub struct EncodedGenesisBlockContents(pub Vec); + /// Signatures #[derive(Debug, Clone, PartialEq)] pub struct Signatures(Vec); @@ -70,10 +74,10 @@ pub struct Signatures(Vec); /// Validator's keys defined in the corresponding certificates referenced by the validator. pub struct ValidatorKeys(pub Vec<[u8; SECRET_KEY_LENGTH]>); -/// Decoder block +/// Decoded block pub type DecodedBlock = (DecodedBlockHeader, DecodedBlockData, Signatures); -/// Decoder block header +/// Decoded block header pub type DecodedBlockHeader = ( ChainId, Height, @@ -86,9 +90,25 @@ pub type DecodedBlockHeader = ( BlockHeaderSize, ); +/// Decoded Genesis block +pub type DecodedBlockGenesis = ( + ChainId, + Height, + BlockTimeStamp, + PreviousBlockHash, + LedgerType, + PurposeId, + Validator, + BlockHeaderSize, + EncodedGenesisBlockContents, +); + /// Encoded whole block including block header, cbor encoded block data and signatures. pub type EncodedBlock = Vec; +/// Encoded genesis block, see genesis_to_prev_hash +pub type EncodedGenesisBlock = Vec; + /// Choice of hash function: /// must be the same as the hash of the previous block. pub enum HashFunction { @@ -98,7 +118,7 @@ pub enum HashFunction { Blake2b, } -/// Encode block +/// Encode standard block pub fn encode_block( block_hdr_cbor: Vec, block_data: EncodedBlockData, validator_keys: ValidatorKeys, hasher: HashFunction, @@ -134,13 +154,15 @@ pub fn encode_block( encoder.bytes(sig)?; } - Ok([block_hdr_cbor, encoder.writer().to_vec()].concat()) + let block_data_with_sigs = encoder.writer().to_vec(); + // block hdr + block data + sigs + let encoded_block = [block_hdr_cbor, block_data_with_sigs].concat(); + + Ok(encoded_block) } -/// Decoded block +/// Decoded standard block pub fn decode_block(encoded_block: Vec) -> anyhow::Result { - // Decode cbor to bytes - // Decoded block hdr let block_hdr: DecodedBlockHeader = decode_block_header(encoded_block.clone())?; @@ -211,6 +233,7 @@ pub fn encode_block_header( Ok(encoder.writer().to_vec()) } + /// Decode block header pub fn decode_block_header(block: Vec) -> anyhow::Result { // Decode cbor to bytes @@ -301,6 +324,146 @@ pub fn decode_block_header(block: Vec) -> anyhow::Result )) } +/// Encode genesis block +pub fn encode_genesis( + chain_id: ChainId, ts: BlockTimeStamp, ledger_type: LedgerType, pid: PurposeId, + validator: Validator, hasher: HashFunction, +) -> anyhow::Result> { + // Genesis block MUST have 0 value + const BLOCK_HEIGHT: u32 = 0; + + let out: Vec = Vec::new(); + let mut encoder = minicbor::Encoder::new(out); + + encoder.bytes(&chain_id.0.to_bytes())?; + encoder.bytes(&BLOCK_HEIGHT.to_be_bytes())?; + encoder.bytes(&ts.0.to_be_bytes())?; + encoder.bytes(ledger_type.0.as_bytes())?; + encoder.bytes(&pid.0.to_bytes())?; + + // marks how many validators for decoding side. + encoder.bytes(&validator.0.len().to_be_bytes())?; + for validator in validator.0.iter() { + encoder.bytes(&validator.0)?; + } + + // Get hash of the genesis_to_prev_hash bytes i.e hash of itself + let genesis_prev_bytes = encoder.writer().to_vec(); + + // Size of encoded contents which is hashed + encoder.bytes(&genesis_prev_bytes.len().to_be_bytes())?; + + let genesis_prev_hash = match hasher { + HashFunction::Blake3 => blake3(&genesis_prev_bytes)?.to_vec(), + HashFunction::Blake2b => blake2b_512(&genesis_prev_bytes)?.to_vec(), + }; + + // prev_block_id for the Genesis block MUST be a hash of the genesis_to_prev_hash bytes + // last 64 bytes (depending on given hash function) of encoding are the hash of the genesis contents + encoder.bytes(&genesis_prev_hash.as_slice())?; + + Ok(encoder.writer().to_vec()) +} + +/// Decode genesis +pub fn decode_genesis_block(genesis_block: Vec) -> anyhow::Result { + let binding = genesis_block.clone(); + let mut cbor_decoder = minicbor::Decoder::new(&binding); + + // Raw chain_id + let chain_id = ChainId(Ulid::from_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for chain id : {e}")))? + .try_into()?, + )); + + // Raw Block height + let block_height = Height(u32::from_be_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block height : {e}")))? + .try_into()?, + )); + + // Raw time stamp + let ts = BlockTimeStamp(i64::from_be_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for timestamp : {e}")))? + .try_into()?, + )); + + // Raw ledger type + let ledger_type = LedgerType(Uuid::from_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for ledger type : {e}")))? + .try_into()?, + )); + + // Raw purpose id + let purpose_id = PurposeId(Ulid::from_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for purpose id : {e}")))? + .try_into()?, + )); + + // Number of validators + let number_of_validators = usize::from_be_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for number of validators : {e}")))? + .try_into()?, + ); + + // Extract validators + let mut validators = Vec::new(); + for _validator in 0..number_of_validators { + let validator_kid: [u8; 16] = cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for validators : {e}")))? + .try_into()?; + + validators.push(Kid(validator_kid)); + } + + // Size of encoded contents + let encoded_content_size = usize::from_be_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for encoded contents size : {e}")))? + .try_into()?, + ); + + // prev_block_id for the Genesis block MUST be a hash of the genesis_to_prev_hash bytes + // last 64 bytes (depending on hash function) of encoding are the hash of the contents + let prev_block_hash = PreviousBlockHash( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for prev block hash : {e}")))? + .to_vec(), + ); + + let genesis_block_contents: Vec = genesis_block + .into_iter() + .take(encoded_content_size) + .collect(); + + Ok(( + chain_id, + block_height, + ts, + prev_block_hash, + ledger_type, + purpose_id, + Validator(validators), + BlockHeaderSize(cbor_decoder.position()), + EncodedGenesisBlockContents(genesis_block_contents), + )) +} + #[cfg(test)] mod tests { use ed25519_dalek::{SigningKey, SECRET_KEY_LENGTH}; @@ -315,6 +478,8 @@ mod tests { }; use crate::serialize::HashFunction::Blake2b; + + use super::{decode_genesis_block, encode_genesis}; #[test] fn block_header_encode_decode() { let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") @@ -443,4 +608,49 @@ mod tests { verifying_key.verify_strict(&data_to_sign, &sig).unwrap(); } } + + #[test] + fn genesis_block_encode_decode() { + let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let chain_id = ChainId(Ulid::new()); + let block_ts = BlockTimeStamp(0); + let ledger_type = LedgerType(Uuid::new_v4()); + let purpose_id = PurposeId(Ulid::new()); + let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); + + let encoded_block_genesis = encode_genesis( + chain_id, + block_ts, + ledger_type.clone(), + purpose_id.clone(), + validators.clone(), + Blake2b, + ) + .unwrap(); + + let decoded_genesis = decode_genesis_block(encoded_block_genesis.clone()).unwrap(); + assert_eq!(decoded_genesis.0, chain_id); + assert_eq!(decoded_genesis.1, Height(0)); + assert_eq!(decoded_genesis.2, block_ts); + assert_eq!(decoded_genesis.4, ledger_type); + assert_eq!(decoded_genesis.5, purpose_id); + assert_eq!(decoded_genesis.6, validators); + + // prev_block_id for the Genesis block MUST be a hash of the genesis_to_prev_hash bytes + let prev_block_hash = decoded_genesis.3 .0; + + // last 64 bytes of encoding are the hash of the contents + let prev_block_from_original_encoding = &encoded_block_genesis[110..]; + + assert_eq!(prev_block_hash, prev_block_from_original_encoding); + } } diff --git a/rust/immutable-ledger/src/validate.rs b/rust/immutable-ledger/src/validate.rs index 8067039a76..dba41e91d1 100644 --- a/rust/immutable-ledger/src/validate.rs +++ b/rust/immutable-ledger/src/validate.rs @@ -2,7 +2,7 @@ //! //! Facilitates validation for immutable ledger -use crate::serialize::blake3; +use crate::serialize::{blake3, decode_genesis_block, EncodedGenesisBlock}; use anyhow::Ok; use crate::serialize::{blake2b_512, decode_block, EncodedBlock, HashFunction}; @@ -87,6 +87,45 @@ pub fn validate_block( Ok(()) } +/// Validate genesis block +pub fn validate_genesis(genesis: EncodedGenesisBlock, hasher: HashFunction) -> anyhow::Result<()> { + // Genesis block MUST have 0 value + const BLOCK_HEIGHT: u32 = 0; + + let genesis_block = decode_genesis_block(genesis.clone())?; + + // Genesis block MUST have 0 value + if genesis_block.1 .0 != BLOCK_HEIGHT { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: Validate genesis failed {:?}", + genesis_block.1 + )); + }; + + // prev_block_id for the Genesis block MUST be a hash of the genesis_to_prev_hash bytes + let _hash_size = match hasher { + HashFunction::Blake3 => 32, + HashFunction::Blake2b => 64, + }; + + // last N bytes of encoding are the hash of the contents + let genesis_block_contents = genesis_block.8 .0; + + let hashed_contents = match hasher { + HashFunction::Blake3 => blake3(&genesis_block_contents)?.to_vec(), + HashFunction::Blake2b => blake2b_512(&genesis_block_contents)?.to_vec(), + }; + + if genesis_block.3 .0 != hashed_contents { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: Validate genesis failed {:?}", + genesis_block.3 + )); + }; + + Ok(()) +} + #[cfg(test)] mod tests { @@ -96,13 +135,14 @@ mod tests { use uuid::Uuid; use crate::serialize::{ - blake2b_512, encode_block, encode_block_header, BlockTimeStamp, ChainId, EncodedBlockData, - Height, Kid, LedgerType, Metadata, PreviousBlockHash, PurposeId, Validator, ValidatorKeys, + blake2b_512, encode_block, encode_block_header, encode_genesis, BlockTimeStamp, ChainId, + EncodedBlockData, Height, Kid, LedgerType, Metadata, PreviousBlockHash, PurposeId, + Validator, ValidatorKeys, }; use crate::serialize::HashFunction::Blake2b; - use super::validate_block; + use super::{validate_block, validate_genesis}; #[test] fn validate_block_test() { @@ -209,4 +249,38 @@ mod tests { Err(err) => panic!("Block validation failed: {:?}", err), }; } + + #[test] + fn validate_genesis_test() { + let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let chain_id = ChainId(Ulid::new()); + let block_ts = BlockTimeStamp(0); + let ledger_type = LedgerType(Uuid::new_v4()); + let purpose_id = PurposeId(Ulid::new()); + let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); + + let encoded_block_genesis = encode_genesis( + chain_id, + block_ts, + ledger_type.clone(), + purpose_id.clone(), + validators.clone(), + Blake2b, + ) + .unwrap(); + + match validate_genesis(encoded_block_genesis, Blake2b) { + Ok(_) => (), + Err(err) => panic!("Genesis Block validation failed: {:?}", err), + }; + } } From 3f5ace8075fb6c92edff360aeac3cfe64804a2ca Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 13:44:45 +0100 Subject: [PATCH 10/53] feat(block validation): ledger --- rust/immutable-ledger/src/lib.rs | 2 +- rust/immutable-ledger/src/serialize.rs | 28 +++++++++++++------------- rust/immutable-ledger/src/validate.rs | 12 +++++------ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/rust/immutable-ledger/src/lib.rs b/rust/immutable-ledger/src/lib.rs index 857191c0d1..34db478e3a 100644 --- a/rust/immutable-ledger/src/lib.rs +++ b/rust/immutable-ledger/src/lib.rs @@ -2,7 +2,7 @@ //! //! Facilitates block serialization and validation for immutable ledger //! -//! Spec: https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/immutable_ledger/ledger/ +//! Spec: / //! /// Block validation logic diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index bc3cca6e6c..cfbd111884 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -35,7 +35,7 @@ pub struct BlockTimeStamp(pub i64); pub struct PreviousBlockHash(pub Vec); /// unique identifier of the ledger type. -/// In general, this is the way to strictly bound and specify block_data of the ledger for the specific ledger_type. +/// In general, this is the way to strictly bound and specify `block_data` of the ledger for the specific `ledger_type`. #[derive(Debug, Clone, PartialEq)] pub struct LedgerType(pub Uuid); @@ -106,7 +106,7 @@ pub type DecodedBlockGenesis = ( /// Encoded whole block including block header, cbor encoded block data and signatures. pub type EncodedBlock = Vec; -/// Encoded genesis block, see genesis_to_prev_hash +/// Encoded genesis block, see `genesis_to_prev_hash` pub type EncodedGenesisBlock = Vec; /// Choice of hash function: @@ -140,7 +140,7 @@ pub fn encode_block( .0 .iter() .map(|sk| { - let mut sk: SigningKey = SigningKey::from_bytes(&sk); + let mut sk: SigningKey = SigningKey::from_bytes(sk); sk.sign(&data_to_sign).to_bytes() }) .collect(); @@ -150,11 +150,11 @@ pub fn encode_block( encoder.bytes(&block_data.0)?; - for sig in signatures.iter() { + for sig in &signatures { encoder.bytes(sig)?; } - let block_data_with_sigs = encoder.writer().to_vec(); + let block_data_with_sigs = encoder.writer().clone(); // block hdr + block data + sigs let encoded_block = [block_hdr_cbor, block_data_with_sigs].concat(); @@ -217,13 +217,13 @@ pub fn encode_block_header( encoder.bytes(&chain_id.0.to_bytes())?; encoder.bytes(&height.0.to_be_bytes())?; encoder.bytes(&ts.0.to_be_bytes())?; - encoder.bytes(&prev_block_hash.0.as_slice())?; + encoder.bytes(prev_block_hash.0.as_slice())?; encoder.bytes(ledger_type.0.as_bytes())?; encoder.bytes(&pid.0.to_bytes())?; // marks how many validators for decoding side. encoder.bytes(&validator.0.len().to_be_bytes())?; - for validator in validator.0.iter() { + for validator in &validator.0 { encoder.bytes(&validator.0)?; } @@ -231,7 +231,7 @@ pub fn encode_block_header( encoder.bytes(&meta.0)?; } - Ok(encoder.writer().to_vec()) + Ok(encoder.writer().clone()) } /// Decode block header @@ -343,12 +343,12 @@ pub fn encode_genesis( // marks how many validators for decoding side. encoder.bytes(&validator.0.len().to_be_bytes())?; - for validator in validator.0.iter() { + for validator in &validator.0 { encoder.bytes(&validator.0)?; } // Get hash of the genesis_to_prev_hash bytes i.e hash of itself - let genesis_prev_bytes = encoder.writer().to_vec(); + let genesis_prev_bytes = encoder.writer().clone(); // Size of encoded contents which is hashed encoder.bytes(&genesis_prev_bytes.len().to_be_bytes())?; @@ -360,9 +360,9 @@ pub fn encode_genesis( // prev_block_id for the Genesis block MUST be a hash of the genesis_to_prev_hash bytes // last 64 bytes (depending on given hash function) of encoding are the hash of the genesis contents - encoder.bytes(&genesis_prev_hash.as_slice())?; + encoder.bytes(genesis_prev_hash.as_slice())?; - Ok(encoder.writer().to_vec()) + Ok(encoder.writer().clone()) } /// Decode genesis @@ -494,7 +494,7 @@ mod tests { let chain_id = ChainId(Ulid::new()); let block_height = Height(5); - let block_ts = BlockTimeStamp(1728474515); + let block_ts = BlockTimeStamp(1_728_474_515); let prev_block_height = PreviousBlockHash(vec![0; 64]); let ledger_type = LedgerType(Uuid::new_v4()); let purpose_id = PurposeId(Ulid::new()); @@ -538,7 +538,7 @@ mod tests { let chain_id = ChainId(Ulid::new()); let block_height = Height(5); - let block_ts = BlockTimeStamp(1728474515); + let block_ts = BlockTimeStamp(1_728_474_515); let prev_block_height = PreviousBlockHash(vec![0; 64]); let ledger_type = LedgerType(Uuid::new_v4()); let purpose_id = PurposeId(Ulid::new()); diff --git a/rust/immutable-ledger/src/validate.rs b/rust/immutable-ledger/src/validate.rs index dba41e91d1..781deb4b2f 100644 --- a/rust/immutable-ledger/src/validate.rs +++ b/rust/immutable-ledger/src/validate.rs @@ -162,7 +162,7 @@ mod tests { let chain_id = ChainId(Ulid::new()); let block_height = Height(5); - let block_ts = BlockTimeStamp(1728474515); + let block_ts = BlockTimeStamp(1_728_474_515); let prev_block_height = PreviousBlockHash(vec![0; 64]); let ledger_type = LedgerType(Uuid::new_v4()); let purpose_id = PurposeId(Ulid::new()); @@ -213,7 +213,7 @@ mod tests { // let block_height = Height(6); - let block_ts = BlockTimeStamp(1728474518); + let block_ts = BlockTimeStamp(1_728_474_518); let prev_block_hash = PreviousBlockHash(blake2b_512(&previous_block).unwrap().to_vec()); let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); let metadata = Some(Metadata(vec![1; 128])); @@ -245,8 +245,8 @@ mod tests { // match validate_block(current_block, previous_block, Blake2b) { - Ok(_) => (), - Err(err) => panic!("Block validation failed: {:?}", err), + Ok(()) => (), + Err(err) => panic!("Block validation failed: {err:?}"), }; } @@ -279,8 +279,8 @@ mod tests { .unwrap(); match validate_genesis(encoded_block_genesis, Blake2b) { - Ok(_) => (), - Err(err) => panic!("Genesis Block validation failed: {:?}", err), + Ok(()) => (), + Err(err) => panic!("Genesis Block validation failed: {err:?}"), }; } } From ece6e6ae2868474c7e2a8411bdf1322e561f5562 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 14:13:58 +0100 Subject: [PATCH 11/53] feat(block validation): ledger --- rust/immutable-ledger/src/lib.rs | 3 +- rust/immutable-ledger/src/serialize.rs | 108 +++++++++++++++---------- rust/immutable-ledger/src/validate.rs | 89 ++++++++++---------- 3 files changed, 112 insertions(+), 88 deletions(-) diff --git a/rust/immutable-ledger/src/lib.rs b/rust/immutable-ledger/src/lib.rs index 34db478e3a..4ec70bb704 100644 --- a/rust/immutable-ledger/src/lib.rs +++ b/rust/immutable-ledger/src/lib.rs @@ -2,8 +2,7 @@ //! //! Facilitates block serialization and validation for immutable ledger //! -//! Spec: / -//! +//! Spec: `` /// Block validation logic pub mod validate; diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index cfbd111884..d1b3d55739 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -2,11 +2,11 @@ //! //! Facilitates block serialization for immutable ledger +use core::result::Result::Ok as ResultOk; + use anyhow::Ok; use blake2b_simd::{self, Params}; -use core::result::Result::Ok as ResultOk; use ed25519_dalek::{ed25519::signature::SignerMut, Signature, SigningKey, SECRET_KEY_LENGTH}; - use ulid::Ulid; use uuid::Uuid; @@ -35,11 +35,13 @@ pub struct BlockTimeStamp(pub i64); pub struct PreviousBlockHash(pub Vec); /// unique identifier of the ledger type. -/// In general, this is the way to strictly bound and specify `block_data` of the ledger for the specific `ledger_type`. +/// In general, this is the way to strictly bound and specify `block_data` of the ledger +/// for the specific `ledger_type`. #[derive(Debug, Clone, PartialEq)] pub struct LedgerType(pub Uuid); -/// unique identifier of the purpose, each Ledger instance will have a strict time boundaries, so each of them will run for different purposes. +/// unique identifier of the purpose, each Ledger instance will have a strict time +/// boundaries, so each of them will run for different purposes. #[derive(Debug, Clone, PartialEq)] pub struct PurposeId(pub Ulid); @@ -71,7 +73,8 @@ pub struct EncodedGenesisBlockContents(pub Vec); #[derive(Debug, Clone, PartialEq)] pub struct Signatures(Vec); -/// Validator's keys defined in the corresponding certificates referenced by the validator. +/// Validator's keys defined in the corresponding certificates referenced by the +/// validator. pub struct ValidatorKeys(pub Vec<[u8; SECRET_KEY_LENGTH]>); /// Decoded block @@ -112,16 +115,20 @@ pub type EncodedGenesisBlock = Vec; /// Choice of hash function: /// must be the same as the hash of the previous block. pub enum HashFunction { - /// BLAKE3 is based on an optimized instance of the established hash function BLAKE2 and on the original Bao tree mode + /// BLAKE3 is based on an optimized instance of the established hash function BLAKE2 + /// and on the original Bao tree mode Blake3, /// BLAKE2b-512 produces digest side of 512 bits. Blake2b, } /// Encode standard block +/// ## Errors +/// +/// Returns an error if block encoding fails pub fn encode_block( - block_hdr_cbor: Vec, block_data: EncodedBlockData, validator_keys: ValidatorKeys, - hasher: HashFunction, + block_hdr_cbor: Vec, block_data: &EncodedBlockData, validator_keys: &ValidatorKeys, + hasher: &HashFunction, ) -> anyhow::Result { let hashed_block_header = match hasher { HashFunction::Blake3 => blake3(&block_hdr_cbor)?.to_vec(), @@ -161,12 +168,15 @@ pub fn encode_block( Ok(encoded_block) } -/// Decoded standard block -pub fn decode_block(encoded_block: Vec) -> anyhow::Result { +/// Decodes standard block +/// ## Errors +/// +/// Returns an error if block decoding fails +pub fn decode_block(encoded_block: &[u8]) -> anyhow::Result { // Decoded block hdr - let block_hdr: DecodedBlockHeader = decode_block_header(encoded_block.clone())?; + let block_hdr: DecodedBlockHeader = decode_block_header(encoded_block)?; - let mut cbor_decoder = minicbor::Decoder::new(&encoded_block); + let mut cbor_decoder = minicbor::Decoder::new(encoded_block); // Decode remaining block, set position after block hdr data. cbor_decoder.set_position(block_hdr.8 .0); @@ -207,9 +217,13 @@ pub(crate) fn blake2b_512(value: &[u8]) -> anyhow::Result<[u8; 64]> { } /// Encode block header +/// ## Errors +/// +/// Returns an error if block header encoding fails. +#[allow(clippy::too_many_arguments)] pub fn encode_block_header( - chain_id: ChainId, height: Height, ts: BlockTimeStamp, prev_block_hash: PreviousBlockHash, - ledger_type: LedgerType, pid: PurposeId, validator: Validator, metadata: Option, + chain_id: ChainId, height: Height, ts: BlockTimeStamp, prev_block_hash: &PreviousBlockHash, + ledger_type: &LedgerType, pid: &PurposeId, validator: &Validator, metadata: Option, ) -> anyhow::Result> { let out: Vec = Vec::new(); let mut encoder = minicbor::Encoder::new(out); @@ -235,9 +249,12 @@ pub fn encode_block_header( } /// Decode block header -pub fn decode_block_header(block: Vec) -> anyhow::Result { +/// ## Errors +/// +/// Returns an error if decoding block header fails. +pub fn decode_block_header(block: &[u8]) -> anyhow::Result { // Decode cbor to bytes - let mut cbor_decoder = minicbor::Decoder::new(&block); + let mut cbor_decoder = minicbor::Decoder::new(block); // Raw chain_id let chain_id = ChainId(Ulid::from_bytes( @@ -325,11 +342,14 @@ pub fn decode_block_header(block: Vec) -> anyhow::Result } /// Encode genesis block +/// ## Errors +/// +/// Returns an error if genesis block encoding fails. pub fn encode_genesis( - chain_id: ChainId, ts: BlockTimeStamp, ledger_type: LedgerType, pid: PurposeId, - validator: Validator, hasher: HashFunction, + chain_id: ChainId, ts: BlockTimeStamp, ledger_type: &LedgerType, pid: &PurposeId, + validator: &Validator, hasher: &HashFunction, ) -> anyhow::Result> { - // Genesis block MUST have 0 value + /// Genesis block MUST have 0 value const BLOCK_HEIGHT: u32 = 0; let out: Vec = Vec::new(); @@ -359,13 +379,17 @@ pub fn encode_genesis( }; // prev_block_id for the Genesis block MUST be a hash of the genesis_to_prev_hash bytes - // last 64 bytes (depending on given hash function) of encoding are the hash of the genesis contents + // last 64 bytes (depending on given hash function) of encoding are the hash of the + // genesis contents encoder.bytes(genesis_prev_hash.as_slice())?; Ok(encoder.writer().clone()) } /// Decode genesis +/// ## Errors +/// +/// Returns an error if block decoding for genesis fails. pub fn decode_genesis_block(genesis_block: Vec) -> anyhow::Result { let binding = genesis_block.clone(); let mut cbor_decoder = minicbor::Decoder::new(&binding); @@ -467,19 +491,15 @@ pub fn decode_genesis_block(genesis_block: Vec) -> anyhow::Result anyhow::Result<()> { let current_block = decode_block(current_block)?; let hashed_previous_block = match hasher { - HashFunction::Blake3 => blake3(&previous_block)?.to_vec(), - HashFunction::Blake2b => blake2b_512(&previous_block)?.to_vec(), + HashFunction::Blake3 => blake3(previous_block)?.to_vec(), + HashFunction::Blake2b => blake2b_512(previous_block)?.to_vec(), }; let previous_block = decode_block(previous_block)?; @@ -28,9 +33,10 @@ pub fn validate_block( )); }; - // height MUST be incremented by 1 from the previous block height value (except for genesis and final block). - // Genesis block MUST have 0 value. Final block MUST hash be incremented by 1 from the previous block height - // and changed the sign to negative. E.g. previous block height is 9 and the Final block height is -10. + // height MUST be incremented by 1 from the previous block height value (except for + // genesis and final block). Genesis block MUST have 0 value. Final block MUST hash be + // incremented by 1 from the previous block height and changed the sign to negative. + // E.g. previous block height is 9 and the Final block height is -10. if current_block.0 .1 .0 != previous_block.0 .1 .0 + 1 { return Err(anyhow::anyhow!( "Module: Immutable ledger, Message: height validation failed: {:?} {:?}", @@ -39,7 +45,8 @@ pub fn validate_block( )); } - // timestamp MUST be greater or equals than the timestamp of the previous block (except for genesis) + // timestamp MUST be greater or equals than the timestamp of the previous block (except + // for genesis) if current_block.0 .2 .0 <= previous_block.0 .2 .0 { return Err(anyhow::anyhow!( "Module: Immutable ledger, Message: timestamp validation failed: {:?} {:?}", @@ -88,8 +95,13 @@ pub fn validate_block( } /// Validate genesis block -pub fn validate_genesis(genesis: EncodedGenesisBlock, hasher: HashFunction) -> anyhow::Result<()> { - // Genesis block MUST have 0 value +/// ## Errors +/// +/// Genesis validation +pub fn genesis_validation( + genesis: &EncodedGenesisBlock, hasher: &HashFunction, +) -> anyhow::Result<()> { + /// Genesis block MUST have 0 value const BLOCK_HEIGHT: u32 = 0; let genesis_block = decode_genesis_block(genesis.clone())?; @@ -130,23 +142,18 @@ pub fn validate_genesis(genesis: EncodedGenesisBlock, hasher: HashFunction) -> a mod tests { use ed25519_dalek::SECRET_KEY_LENGTH; - use ulid::Ulid; use uuid::Uuid; + use super::{block_validation, genesis_validation}; use crate::serialize::{ blake2b_512, encode_block, encode_block_header, encode_genesis, BlockTimeStamp, ChainId, - EncodedBlockData, Height, Kid, LedgerType, Metadata, PreviousBlockHash, PurposeId, - Validator, ValidatorKeys, + EncodedBlockData, HashFunction::Blake2b, Height, Kid, LedgerType, Metadata, + PreviousBlockHash, PurposeId, Validator, ValidatorKeys, }; - use crate::serialize::HashFunction::Blake2b; - - use super::{validate_block, validate_genesis}; - #[test] fn validate_block_test() { - // // PREVIOUS BLOCK // // @@ -173,10 +180,10 @@ mod tests { chain_id, block_height, block_ts, - prev_block_height.clone(), - ledger_type.clone(), - purpose_id.clone(), - validators.clone(), + &prev_block_height.clone(), + &ledger_type.clone(), + &purpose_id.clone(), + &validators.clone(), metadata.clone(), ) .unwrap(); @@ -202,13 +209,12 @@ mod tests { let previous_block = encode_block( encoded_block_hdr.clone(), - EncodedBlockData(block_data_bytes.to_vec()), - ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), - Blake2b, + &EncodedBlockData(block_data_bytes.to_vec()), + &ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), + &Blake2b, ) .unwrap(); - // // CURRENT BLOCK // @@ -222,10 +228,10 @@ mod tests { chain_id, block_height, block_ts, - prev_block_hash, - ledger_type.clone(), - purpose_id.clone(), - validators.clone(), + &prev_block_hash, + &ledger_type.clone(), + &purpose_id.clone(), + &validators.clone(), metadata.clone(), ) .unwrap(); @@ -234,17 +240,16 @@ mod tests { let current_block = encode_block( encoded_block_hdr.clone(), - EncodedBlockData(block_data_bytes.to_vec()), - ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), - Blake2b, + &EncodedBlockData(block_data_bytes.to_vec()), + &ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), + &Blake2b, ) .unwrap(); - // // VALIDATE BLOCK // - match validate_block(current_block, previous_block, Blake2b) { + match block_validation(¤t_block, &previous_block, &Blake2b) { Ok(()) => (), Err(err) => panic!("Block validation failed: {err:?}"), }; @@ -271,14 +276,14 @@ mod tests { let encoded_block_genesis = encode_genesis( chain_id, block_ts, - ledger_type.clone(), - purpose_id.clone(), - validators.clone(), - Blake2b, + &ledger_type.clone(), + &purpose_id.clone(), + &validators.clone(), + &Blake2b, ) .unwrap(); - match validate_genesis(encoded_block_genesis, Blake2b) { + match genesis_validation(&encoded_block_genesis, &Blake2b) { Ok(()) => (), Err(err) => panic!("Genesis Block validation failed: {err:?}"), }; From 133acd3467ca2b7aa2d77b801b4b273ff8c123c7 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 14:19:17 +0100 Subject: [PATCH 12/53] feat(block validation): ledger --- rust/immutable-ledger/src/serialize.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index d1b3d55739..61a305fef9 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -669,7 +669,8 @@ mod tests { let prev_block_hash = decoded_genesis.3 .0; // last 64 bytes of encoding are the hash of the contents - let prev_block_from_original_encoding = &encoded_block_genesis[110..]; + let prev_block_from_original_encoding = + &encoded_block_genesis[encoded_block_genesis.len() - 64..]; assert_eq!(prev_block_hash, prev_block_from_original_encoding); } From a7748e6a3d3803af342484fabe76e2277222ed91 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 14:40:04 +0100 Subject: [PATCH 13/53] ci(pr name): housekeeping --- .github/workflows/semantic_pull_request.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/semantic_pull_request.yml b/.github/workflows/semantic_pull_request.yml index b788d8a508..7f83771dd7 100644 --- a/.github/workflows/semantic_pull_request.yml +++ b/.github/workflows/semantic_pull_request.yml @@ -19,6 +19,7 @@ jobs: rust/c509-certificate rust/cardano-chain-follower rust/catalyst-voting + rust/immutable-ledger rust/cbork rust/hermes-ipfs dart From 15a277099a99d29173dd3e8dc40c921ad8d6e73e Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 15:36:54 +0100 Subject: [PATCH 14/53] ci(pr name): housekeeping --- rust/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index a8f71dfa64..53cc8bcb70 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -8,7 +8,7 @@ members = [ "cbork-abnf-parser", "cbork-cddl-parser", "catalyst-voting", - "immutable-ledger" + "immutable-ledger", ] [workspace.package] From 9e2de4e64d44b855ed085f04e56ddd3cdccd8c0c Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 15:42:36 +0100 Subject: [PATCH 15/53] ci(pr name): housekeeping --- rust/Earthfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/Earthfile b/rust/Earthfile index e7911ea50d..de8be49d48 100644 --- a/rust/Earthfile +++ b/rust/Earthfile @@ -11,6 +11,7 @@ COPY_SRC: c509-certificate \ cardano-chain-follower \ catalyst-voting \ + immutable-ledger \ cbork cbork-abnf-parser cbork-cddl-parser \ hermes-ipfs \ . @@ -54,8 +55,9 @@ build: --args1="--libs=c509-certificate --libs=cardano-chain-follower --libs=hermes-ipfs" \ --args2="--libs=cbork-cddl-parser --libs=cbork-abnf-parser" \ --args3="--libs=catalyst-voting" \ - --args4="--bins=cbork/cbork" \ - --args5="--cov_report=$HOME/build/coverage-report.info" \ + --args4="--libs=immutable-ledger" \ + --args5="--bins=cbork/cbork" \ + --args6="--cov_report=$HOME/build/coverage-report.info" \ --output="release/[^\./]+" \ --junit="cat-libs.junit-report.xml" \ --coverage="cat-libs.coverage.info" \ From af6417e9e4082c671775251e96102bfe8b3c5604 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 15:53:17 +0100 Subject: [PATCH 16/53] ci(pr name): housekeeping --- rust/immutable-ledger/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rust/immutable-ledger/Cargo.toml b/rust/immutable-ledger/Cargo.toml index 7deec2e82e..b7924443f4 100644 --- a/rust/immutable-ledger/Cargo.toml +++ b/rust/immutable-ledger/Cargo.toml @@ -13,11 +13,10 @@ anyhow = "1.0.86" minicbor = { version = "0.24", features = ["std"] } uuid = { version = "1.10.0", features = ["v4", "serde"] } ulid = { version = "1.1.3", features = ["serde", "uuid"] } -cddl = "0.9.4" hex = "0.4.3" blake2b_simd = "1.0.2" blake3 = "=0.1.3" -rand = "0.8.5" + From 0cf2af6311dc6ff671ee80d86444101a80eca6f9 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 16:04:20 +0100 Subject: [PATCH 17/53] ci(pr name): housekeeping --- rust/immutable-ledger/src/validate.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/immutable-ledger/src/validate.rs b/rust/immutable-ledger/src/validate.rs index f5d59e566c..5676f5b5c7 100644 --- a/rust/immutable-ledger/src/validate.rs +++ b/rust/immutable-ledger/src/validate.rs @@ -153,6 +153,7 @@ mod tests { }; #[test] + #[allow(clippy::zero_prefixed_literal)] fn validate_block_test() { // PREVIOUS BLOCK // From 7697412d6dfe3e5d88ac981af91ea46dde7ea6e6 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 16:08:53 +0100 Subject: [PATCH 18/53] feat(block validation): ledger --- rust/immutable-ledger/src/validate.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/rust/immutable-ledger/src/validate.rs b/rust/immutable-ledger/src/validate.rs index 5676f5b5c7..8a7a7aa33b 100644 --- a/rust/immutable-ledger/src/validate.rs +++ b/rust/immutable-ledger/src/validate.rs @@ -114,12 +114,6 @@ pub fn genesis_validation( )); }; - // prev_block_id for the Genesis block MUST be a hash of the genesis_to_prev_hash bytes - let _hash_size = match hasher { - HashFunction::Blake3 => 32, - HashFunction::Blake2b => 64, - }; - // last N bytes of encoding are the hash of the contents let genesis_block_contents = genesis_block.8 .0; From b413b0533344c20360672f0ef31a97b4c21de0d3 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 16:47:34 +0100 Subject: [PATCH 19/53] feat(block validation): ledger --- rust/Earthfile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/rust/Earthfile b/rust/Earthfile index de8be49d48..49a3f542b2 100644 --- a/rust/Earthfile +++ b/rust/Earthfile @@ -11,9 +11,9 @@ COPY_SRC: c509-certificate \ cardano-chain-follower \ catalyst-voting \ - immutable-ledger \ cbork cbork-abnf-parser cbork-cddl-parser \ hermes-ipfs \ + immutable-ledger\ . # builder : Set up our target toolchains, and copy our files. @@ -52,12 +52,11 @@ build: DO rust-ci+EXECUTE \ --cmd="/scripts/std_build.py" \ - --args1="--libs=c509-certificate --libs=cardano-chain-follower --libs=hermes-ipfs" \ + --args1="--libs=c509-certificate --libs=cardano-chain-follower --libs=hermes-ipfs --libs=immutable-ledger" \ --args2="--libs=cbork-cddl-parser --libs=cbork-abnf-parser" \ --args3="--libs=catalyst-voting" \ - --args4="--libs=immutable-ledger" \ - --args5="--bins=cbork/cbork" \ - --args6="--cov_report=$HOME/build/coverage-report.info" \ + --args4="--bins=cbork/cbork" \ + --args5="--cov_report=$HOME/build/coverage-report.info" \ --output="release/[^\./]+" \ --junit="cat-libs.junit-report.xml" \ --coverage="cat-libs.coverage.info" \ From 26e9a6b3c8dc5739ca6d32ef953fb38de681c345 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 17:03:00 +0100 Subject: [PATCH 20/53] feat(block validation): ledger --- rust/Earthfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/Earthfile b/rust/Earthfile index 49a3f542b2..cb3272f9b0 100644 --- a/rust/Earthfile +++ b/rust/Earthfile @@ -52,9 +52,9 @@ build: DO rust-ci+EXECUTE \ --cmd="/scripts/std_build.py" \ - --args1="--libs=c509-certificate --libs=cardano-chain-follower --libs=hermes-ipfs --libs=immutable-ledger" \ + --args1="--libs=c509-certificate --libs=cardano-chain-follower --libs=hermes-ipfs" \ --args2="--libs=cbork-cddl-parser --libs=cbork-abnf-parser" \ - --args3="--libs=catalyst-voting" \ + --args3="--libs=catalyst-voting --libs=immutable-ledger" \ --args4="--bins=cbork/cbork" \ --args5="--cov_report=$HOME/build/coverage-report.info" \ --output="release/[^\./]+" \ From 99a652374dc6df207f1b553c261221c22cbfa324 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 17:08:07 +0100 Subject: [PATCH 21/53] feat(block validation): ledger --- rust/Earthfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/Earthfile b/rust/Earthfile index cb3272f9b0..392f4de3c3 100644 --- a/rust/Earthfile +++ b/rust/Earthfile @@ -54,9 +54,10 @@ build: --cmd="/scripts/std_build.py" \ --args1="--libs=c509-certificate --libs=cardano-chain-follower --libs=hermes-ipfs" \ --args2="--libs=cbork-cddl-parser --libs=cbork-abnf-parser" \ - --args3="--libs=catalyst-voting --libs=immutable-ledger" \ + --args3="--libs=catalyst-voting" \ --args4="--bins=cbork/cbork" \ --args5="--cov_report=$HOME/build/coverage-report.info" \ + --args6="--libs=immutable-ledger" \ --output="release/[^\./]+" \ --junit="cat-libs.junit-report.xml" \ --coverage="cat-libs.coverage.info" \ From 1b1f1efe99cc5d5685890594ce6f125679641023 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 17:11:26 +0100 Subject: [PATCH 22/53] feat(block validation): ledger --- rust/Earthfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/Earthfile b/rust/Earthfile index 392f4de3c3..f06dc15397 100644 --- a/rust/Earthfile +++ b/rust/Earthfile @@ -55,9 +55,9 @@ build: --args1="--libs=c509-certificate --libs=cardano-chain-follower --libs=hermes-ipfs" \ --args2="--libs=cbork-cddl-parser --libs=cbork-abnf-parser" \ --args3="--libs=catalyst-voting" \ - --args4="--bins=cbork/cbork" \ - --args5="--cov_report=$HOME/build/coverage-report.info" \ - --args6="--libs=immutable-ledger" \ + --args4="--libs=immutable-ledger" \ + --args5="--bins=cbork/cbork" \ + --args6="--cov_report=$HOME/build/coverage-report.info" \ --output="release/[^\./]+" \ --junit="cat-libs.junit-report.xml" \ --coverage="cat-libs.coverage.info" \ From 36867a2ec71615a8bd35b1a6005aa26881bb8075 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 17:13:58 +0100 Subject: [PATCH 23/53] feat(block validation): ledger --- rust/Earthfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/Earthfile b/rust/Earthfile index f06dc15397..de8be49d48 100644 --- a/rust/Earthfile +++ b/rust/Earthfile @@ -11,9 +11,9 @@ COPY_SRC: c509-certificate \ cardano-chain-follower \ catalyst-voting \ + immutable-ledger \ cbork cbork-abnf-parser cbork-cddl-parser \ hermes-ipfs \ - immutable-ledger\ . # builder : Set up our target toolchains, and copy our files. From ea8bb4f94f1036e58817a21c1286fec35ec5709a Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 17:27:20 +0100 Subject: [PATCH 24/53] feat(block validation): ledger --- rust/Earthfile | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/rust/Earthfile b/rust/Earthfile index de8be49d48..b2ae8ea975 100644 --- a/rust/Earthfile +++ b/rust/Earthfile @@ -5,16 +5,9 @@ IMPORT github.com/input-output-hk/catalyst-ci/earthly/rust:v3.2.15 AS rust-ci COPY_SRC: FUNCTION - COPY --keep-ts --dir \ - Cargo.toml clippy.toml deny.toml rustfmt.toml \ - .cargo .config \ - c509-certificate \ - cardano-chain-follower \ - catalyst-voting \ - immutable-ledger \ - cbork cbork-abnf-parser cbork-cddl-parser \ - hermes-ipfs \ - . + COPY --dir .cargo .config c509-certificate cardano-chain-follower \ + cbork cbork-abnf-parser cbork-cddl-parser \ + hermes-ipfs catalyst-voting immutable-ledger . # builder : Set up our target toolchains, and copy our files. builder: @@ -52,15 +45,15 @@ build: DO rust-ci+EXECUTE \ --cmd="/scripts/std_build.py" \ - --args1="--libs=c509-certificate --libs=cardano-chain-follower --libs=hermes-ipfs" \ - --args2="--libs=cbork-cddl-parser --libs=cbork-abnf-parser" \ - --args3="--libs=catalyst-voting" \ - --args4="--libs=immutable-ledger" \ - --args5="--bins=cbork/cbork" \ - --args6="--cov_report=$HOME/build/coverage-report.info" \ --output="release/[^\./]+" \ - --junit="cat-libs.junit-report.xml" \ - --coverage="cat-libs.coverage.info" \ + --args1="--libs=c509-certificate" \ + --args2="--libs=cardano-chain-follower" \ + --args3="--libs=cbork-cddl-parser" \ + --args4="--libs=cbork-abnf-parser" \ + --args5="--libs=hermes-ipfs" \ + --args6="--libs=catalyst-voting" \ + --args7="--libs=immutable-ledger" \ + --args8="--bins=cbork/cbork" \ --docs="true" SAVE ARTIFACT target/$TARGETARCH/doc doc From 3304232274ef82513a4fd3c0ddf010928e28247c Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 17:41:31 +0100 Subject: [PATCH 25/53] feat(block validation): ledger --- rust/Earthfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/Earthfile b/rust/Earthfile index b2ae8ea975..5916e297b1 100644 --- a/rust/Earthfile +++ b/rust/Earthfile @@ -51,9 +51,9 @@ build: --args3="--libs=cbork-cddl-parser" \ --args4="--libs=cbork-abnf-parser" \ --args5="--libs=hermes-ipfs" \ - --args6="--libs=catalyst-voting" \ - --args7="--libs=immutable-ledger" \ - --args8="--bins=cbork/cbork" \ + --args6="--bins=cbork/cbork" \ + --args7="--libs=catalyst-voting" \ + --args8="--libs=immutable-ledger" \ --docs="true" SAVE ARTIFACT target/$TARGETARCH/doc doc From cc2fbe3eb855e1006d1ed42b4867ed2917d3e839 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 18:02:47 +0100 Subject: [PATCH 26/53] feat(block validation): ledger --- rust/Earthfile | 1 - 1 file changed, 1 deletion(-) diff --git a/rust/Earthfile b/rust/Earthfile index 5916e297b1..e69b938222 100644 --- a/rust/Earthfile +++ b/rust/Earthfile @@ -53,7 +53,6 @@ build: --args5="--libs=hermes-ipfs" \ --args6="--bins=cbork/cbork" \ --args7="--libs=catalyst-voting" \ - --args8="--libs=immutable-ledger" \ --docs="true" SAVE ARTIFACT target/$TARGETARCH/doc doc From 85dc6047f4f6857f28002e691be2fac81c4ada85 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 18:06:19 +0100 Subject: [PATCH 27/53] feat(block validation): ledger --- rust/Earthfile | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/rust/Earthfile b/rust/Earthfile index e69b938222..99b07a20bd 100644 --- a/rust/Earthfile +++ b/rust/Earthfile @@ -5,9 +5,15 @@ IMPORT github.com/input-output-hk/catalyst-ci/earthly/rust:v3.2.15 AS rust-ci COPY_SRC: FUNCTION - COPY --dir .cargo .config c509-certificate cardano-chain-follower \ - cbork cbork-abnf-parser cbork-cddl-parser \ - hermes-ipfs catalyst-voting immutable-ledger . + COPY --keep-ts --dir \ + Cargo.toml clippy.toml deny.toml rustfmt.toml \ + .cargo .config \ + c509-certificate \ + cardano-chain-follower \ + catalyst-voting \ + cbork cbork-abnf-parser cbork-cddl-parser \ + hermes-ipfs \ + . # builder : Set up our target toolchains, and copy our files. builder: @@ -45,14 +51,14 @@ build: DO rust-ci+EXECUTE \ --cmd="/scripts/std_build.py" \ + --args1="--libs=c509-certificate --libs=cardano-chain-follower --libs=hermes-ipfs" \ + --args2="--libs=cbork-cddl-parser --libs=cbork-abnf-parser" \ + --args3="--libs=catalyst-voting" \ + --args4="--bins=cbork/cbork" \ + --args5="--cov_report=$HOME/build/coverage-report.info" \ --output="release/[^\./]+" \ - --args1="--libs=c509-certificate" \ - --args2="--libs=cardano-chain-follower" \ - --args3="--libs=cbork-cddl-parser" \ - --args4="--libs=cbork-abnf-parser" \ - --args5="--libs=hermes-ipfs" \ - --args6="--bins=cbork/cbork" \ - --args7="--libs=catalyst-voting" \ + --junit="cat-libs.junit-report.xml" \ + --coverage="cat-libs.coverage.info" \ --docs="true" SAVE ARTIFACT target/$TARGETARCH/doc doc @@ -84,4 +90,4 @@ check-builder-src-cache: # local-ci-run: This step simulates the full CI run for local purposes only. local-ci-run: BUILD +check - BUILD +build + BUILD +build \ No newline at end of file From 2a2b2748746baaaf9c2e2880a24ee68df9edd3ec Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 18:09:06 +0100 Subject: [PATCH 28/53] feat(block validation): ledger --- rust/Earthfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/Earthfile b/rust/Earthfile index 99b07a20bd..39d4551647 100644 --- a/rust/Earthfile +++ b/rust/Earthfile @@ -13,7 +13,7 @@ COPY_SRC: catalyst-voting \ cbork cbork-abnf-parser cbork-cddl-parser \ hermes-ipfs \ - . + immutable-ledger . # builder : Set up our target toolchains, and copy our files. builder: From 52ec56a78a1cf97fa2d7af4403af1fe2480fdffe Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 18:46:42 +0100 Subject: [PATCH 29/53] ci(lints): fix --- rust/immutable-ledger/src/serialize.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index 61a305fef9..6dfb4a6370 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -545,6 +545,7 @@ mod tests { } #[test] + #[allow(clippy::zero_prefixed_literal)] fn block_encode_decode() { let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") .unwrap() @@ -630,6 +631,7 @@ mod tests { } #[test] + #[allow(clippy::zero_prefixed_literal)] fn genesis_block_encode_decode() { let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") .unwrap() From 1173a0496a47805c4a9fd1865cc5a0ec498aa6fa Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 19:00:34 +0100 Subject: [PATCH 30/53] ci(lints): fix --- rust/immutable-ledger/src/validate.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/rust/immutable-ledger/src/validate.rs b/rust/immutable-ledger/src/validate.rs index 8a7a7aa33b..a469924bc9 100644 --- a/rust/immutable-ledger/src/validate.rs +++ b/rust/immutable-ledger/src/validate.rs @@ -244,10 +244,7 @@ mod tests { // VALIDATE BLOCK // - match block_validation(¤t_block, &previous_block, &Blake2b) { - Ok(()) => (), - Err(err) => panic!("Block validation failed: {err:?}"), - }; + assert!(block_validation(¤t_block, &previous_block, &Blake2b).is_ok()); } #[test] @@ -278,9 +275,6 @@ mod tests { ) .unwrap(); - match genesis_validation(&encoded_block_genesis, &Blake2b) { - Ok(()) => (), - Err(err) => panic!("Genesis Block validation failed: {err:?}"), - }; + assert!(genesis_validation(&encoded_block_genesis, &Blake2b).is_ok()); } } From be3e72abe2f9eaff3692ac513f7a18d0bd0351f4 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 19:07:22 +0100 Subject: [PATCH 31/53] ci(lints): fix --- rust/immutable-ledger/src/serialize.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index 6dfb4a6370..63bf237f07 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -631,7 +631,7 @@ mod tests { } #[test] - #[allow(clippy::zero_prefixed_literal)] + #[allow(clippy::zero_prefixed_literal, clippy::indexing_slicing)] fn genesis_block_encode_decode() { let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") .unwrap() From 10521e83c7a93dc80899513a8634de0100afa9d9 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 20:53:31 +0100 Subject: [PATCH 32/53] ci(lints): fix --- rust/immutable-ledger/src/serialize.rs | 35 +++++++++++++++++--------- rust/immutable-ledger/src/validate.rs | 13 +++++----- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index 63bf237f07..de7ff585ad 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -57,6 +57,10 @@ pub struct Metadata(pub Vec); #[derive(Debug, Clone, PartialEq)] pub struct BlockHeaderSize(usize); +/// Encoded block header as cbor +#[derive(Debug, Clone, PartialEq)] +pub struct EncodedBlockHeader(pub Vec); + /// Decoded block data #[derive(Debug, Clone, PartialEq)] pub struct DecodedBlockData(Vec); @@ -127,12 +131,18 @@ pub enum HashFunction { /// /// Returns an error if block encoding fails pub fn encode_block( - block_hdr_cbor: Vec, block_data: &EncodedBlockData, validator_keys: &ValidatorKeys, - hasher: &HashFunction, + block_hdr_cbor: EncodedBlockHeader, block_data: &EncodedBlockData, + validator_keys: &ValidatorKeys, hasher: &HashFunction, ) -> anyhow::Result { + // Enforce block data to be cbor encoded in the form of CBOR byte strings + // which are just (ordered) series of bytes without further interpretation + let binding = block_data.0.clone(); + let mut block_data_cbor_encoding_check = minicbor::Decoder::new(&binding); + let _ = block_data_cbor_encoding_check.bytes()?; + let hashed_block_header = match hasher { - HashFunction::Blake3 => blake3(&block_hdr_cbor)?.to_vec(), - HashFunction::Blake2b => blake2b_512(&block_hdr_cbor)?.to_vec(), + HashFunction::Blake3 => blake3(&block_hdr_cbor.0)?.to_vec(), + HashFunction::Blake2b => blake2b_512(&block_hdr_cbor.0)?.to_vec(), }; // validator_signature MUST be a signature of the hashed block_header bytes @@ -163,7 +173,7 @@ pub fn encode_block( let block_data_with_sigs = encoder.writer().clone(); // block hdr + block data + sigs - let encoded_block = [block_hdr_cbor, block_data_with_sigs].concat(); + let encoded_block = [block_hdr_cbor.0, block_data_with_sigs].concat(); Ok(encoded_block) } @@ -216,7 +226,7 @@ pub(crate) fn blake2b_512(value: &[u8]) -> anyhow::Result<[u8; 64]> { .map_err(|_| anyhow::anyhow!("Invalid length of blake2b_512, expected 64 got {}", b.len())) } -/// Encode block header +/// Encode block header as cbor /// ## Errors /// /// Returns an error if block header encoding fails. @@ -497,8 +507,8 @@ mod tests { use super::{decode_genesis_block, encode_genesis}; use crate::serialize::{ blake2b_512, decode_block, decode_block_header, encode_block, encode_block_header, - BlockTimeStamp, ChainId, EncodedBlockData, HashFunction::Blake2b, Height, Kid, LedgerType, - Metadata, PreviousBlockHash, PurposeId, Validator, ValidatorKeys, + BlockTimeStamp, ChainId, EncodedBlockData, EncodedBlockHeader, HashFunction::Blake2b, + Height, Kid, LedgerType, Metadata, PreviousBlockHash, PurposeId, Validator, ValidatorKeys, }; #[test] fn block_header_encode_decode() { @@ -596,10 +606,11 @@ mod tests { ]; block_data.bytes(block_data_bytes).unwrap(); + let encoded_block_data = block_data.writer().to_vec(); let encoded_block = encode_block( - encoded_block_hdr.clone(), - &EncodedBlockData(block_data_bytes.to_vec()), + EncodedBlockHeader(encoded_block_hdr.clone()), + &EncodedBlockData(encoded_block_data.clone()), &ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), &Blake2b, ) @@ -615,11 +626,11 @@ mod tests { assert_eq!(decoded.0 .6, validators); assert_eq!(decoded.0 .7, metadata); - assert_eq!(decoded.1 .0, block_data_bytes.to_vec()); + assert_eq!(decoded.1 .0, encoded_block_data); let data_to_sign = [ blake2b_512(&encoded_block_hdr).unwrap().to_vec(), - block_data_bytes.to_vec(), + encoded_block_data.to_vec(), ] .concat(); diff --git a/rust/immutable-ledger/src/validate.rs b/rust/immutable-ledger/src/validate.rs index a469924bc9..28f726f7de 100644 --- a/rust/immutable-ledger/src/validate.rs +++ b/rust/immutable-ledger/src/validate.rs @@ -142,8 +142,8 @@ mod tests { use super::{block_validation, genesis_validation}; use crate::serialize::{ blake2b_512, encode_block, encode_block_header, encode_genesis, BlockTimeStamp, ChainId, - EncodedBlockData, HashFunction::Blake2b, Height, Kid, LedgerType, Metadata, - PreviousBlockHash, PurposeId, Validator, ValidatorKeys, + EncodedBlockData, EncodedBlockHeader, HashFunction::Blake2b, Height, Kid, LedgerType, + Metadata, PreviousBlockHash, PurposeId, Validator, ValidatorKeys, }; #[test] @@ -201,10 +201,11 @@ mod tests { ]; block_data.bytes(block_data_bytes).unwrap(); + let encoded_block_data = block_data.writer().to_vec(); let previous_block = encode_block( - encoded_block_hdr.clone(), - &EncodedBlockData(block_data_bytes.to_vec()), + EncodedBlockHeader(encoded_block_hdr.clone()), + &EncodedBlockData(encoded_block_data.clone()), &ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), &Blake2b, ) @@ -234,8 +235,8 @@ mod tests { block_data.bytes(block_data_bytes).unwrap(); let current_block = encode_block( - encoded_block_hdr.clone(), - &EncodedBlockData(block_data_bytes.to_vec()), + EncodedBlockHeader(encoded_block_hdr.clone()), + &EncodedBlockData(encoded_block_data.clone()), &ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), &Blake2b, ) From f387e917b0c5ce32032f29e15e89c63c9b541265 Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 21:00:41 +0100 Subject: [PATCH 33/53] ci(lints): fix --- rust/immutable-ledger/src/serialize.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index de7ff585ad..df9fc1ea56 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -131,9 +131,14 @@ pub enum HashFunction { /// /// Returns an error if block encoding fails pub fn encode_block( - block_hdr_cbor: EncodedBlockHeader, block_data: &EncodedBlockData, - validator_keys: &ValidatorKeys, hasher: &HashFunction, + block_hdr: EncodedBlockHeader, block_data: &EncodedBlockData, validator_keys: &ValidatorKeys, + hasher: &HashFunction, ) -> anyhow::Result { + // Ensure block header is cbor encoded + let binding = block_hdr.0.clone(); + let mut block_hdr_cbor_encoding_check = minicbor::Decoder::new(&binding); + let _ = block_hdr_cbor_encoding_check.bytes()?; + // Enforce block data to be cbor encoded in the form of CBOR byte strings // which are just (ordered) series of bytes without further interpretation let binding = block_data.0.clone(); @@ -141,8 +146,8 @@ pub fn encode_block( let _ = block_data_cbor_encoding_check.bytes()?; let hashed_block_header = match hasher { - HashFunction::Blake3 => blake3(&block_hdr_cbor.0)?.to_vec(), - HashFunction::Blake2b => blake2b_512(&block_hdr_cbor.0)?.to_vec(), + HashFunction::Blake3 => blake3(&block_hdr.0)?.to_vec(), + HashFunction::Blake2b => blake2b_512(&block_hdr.0)?.to_vec(), }; // validator_signature MUST be a signature of the hashed block_header bytes @@ -173,7 +178,7 @@ pub fn encode_block( let block_data_with_sigs = encoder.writer().clone(); // block hdr + block data + sigs - let encoded_block = [block_hdr_cbor.0, block_data_with_sigs].concat(); + let encoded_block = [block_hdr.0, block_data_with_sigs].concat(); Ok(encoded_block) } From 4290604c0f828e41c12fa6e107bf5b3e6d66566c Mon Sep 17 00:00:00 2001 From: cong-or Date: Tue, 22 Oct 2024 21:08:07 +0100 Subject: [PATCH 34/53] ci(lints): fix --- rust/immutable-ledger/src/serialize.rs | 4 ++-- rust/immutable-ledger/src/validate.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index df9fc1ea56..b2d5e67d0e 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -611,7 +611,7 @@ mod tests { ]; block_data.bytes(block_data_bytes).unwrap(); - let encoded_block_data = block_data.writer().to_vec(); + let encoded_block_data = block_data.writer().clone(); let encoded_block = encode_block( EncodedBlockHeader(encoded_block_hdr.clone()), @@ -635,7 +635,7 @@ mod tests { let data_to_sign = [ blake2b_512(&encoded_block_hdr).unwrap().to_vec(), - encoded_block_data.to_vec(), + encoded_block_data.clone(), ] .concat(); diff --git a/rust/immutable-ledger/src/validate.rs b/rust/immutable-ledger/src/validate.rs index 28f726f7de..e4a5695f0b 100644 --- a/rust/immutable-ledger/src/validate.rs +++ b/rust/immutable-ledger/src/validate.rs @@ -201,7 +201,7 @@ mod tests { ]; block_data.bytes(block_data_bytes).unwrap(); - let encoded_block_data = block_data.writer().to_vec(); + let encoded_block_data = block_data.writer().clone(); let previous_block = encode_block( EncodedBlockHeader(encoded_block_hdr.clone()), From 4c6fee95e2edefaca9806f442f2a609c3c4f963f Mon Sep 17 00:00:00 2001 From: cong-or Date: Wed, 23 Oct 2024 10:48:38 +0100 Subject: [PATCH 35/53] refactor(encode block): simpler api --- rust/immutable-ledger/src/serialize.rs | 59 +++++++++++++++++++------- rust/immutable-ledger/src/validate.rs | 38 ++++++++--------- 2 files changed, 62 insertions(+), 35 deletions(-) diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index b2d5e67d0e..ee08e65e33 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -84,6 +84,18 @@ pub struct ValidatorKeys(pub Vec<[u8; SECRET_KEY_LENGTH]>); /// Decoded block pub type DecodedBlock = (DecodedBlockHeader, DecodedBlockData, Signatures); +/// Block header +pub struct BlockHeader( + pub ChainId, + pub Height, + pub BlockTimeStamp, + pub PreviousBlockHash, + pub LedgerType, + pub PurposeId, + pub Validator, + pub Option, +); + /// Decoded block header pub type DecodedBlockHeader = ( ChainId, @@ -131,23 +143,29 @@ pub enum HashFunction { /// /// Returns an error if block encoding fails pub fn encode_block( - block_hdr: EncodedBlockHeader, block_data: &EncodedBlockData, validator_keys: &ValidatorKeys, + block_hdr: BlockHeader, block_data: &EncodedBlockData, validator_keys: &ValidatorKeys, hasher: &HashFunction, ) -> anyhow::Result { - // Ensure block header is cbor encoded - let binding = block_hdr.0.clone(); - let mut block_hdr_cbor_encoding_check = minicbor::Decoder::new(&binding); - let _ = block_hdr_cbor_encoding_check.bytes()?; - // Enforce block data to be cbor encoded in the form of CBOR byte strings // which are just (ordered) series of bytes without further interpretation let binding = block_data.0.clone(); let mut block_data_cbor_encoding_check = minicbor::Decoder::new(&binding); let _ = block_data_cbor_encoding_check.bytes()?; + let encoded_block_hdr = encode_block_header( + block_hdr.0, + block_hdr.1, + block_hdr.2, + &block_hdr.3, + &block_hdr.4, + &block_hdr.5, + &block_hdr.6, + block_hdr.7, + )?; + let hashed_block_header = match hasher { - HashFunction::Blake3 => blake3(&block_hdr.0)?.to_vec(), - HashFunction::Blake2b => blake2b_512(&block_hdr.0)?.to_vec(), + HashFunction::Blake3 => blake3(&encoded_block_hdr)?.to_vec(), + HashFunction::Blake2b => blake2b_512(&encoded_block_hdr)?.to_vec(), }; // validator_signature MUST be a signature of the hashed block_header bytes @@ -178,7 +196,7 @@ pub fn encode_block( let block_data_with_sigs = encoder.writer().clone(); // block hdr + block data + sigs - let encoded_block = [block_hdr.0, block_data_with_sigs].concat(); + let encoded_block = [encoded_block_hdr, block_data_with_sigs].concat(); Ok(encoded_block) } @@ -512,8 +530,8 @@ mod tests { use super::{decode_genesis_block, encode_genesis}; use crate::serialize::{ blake2b_512, decode_block, decode_block_header, encode_block, encode_block_header, - BlockTimeStamp, ChainId, EncodedBlockData, EncodedBlockHeader, HashFunction::Blake2b, - Height, Kid, LedgerType, Metadata, PreviousBlockHash, PurposeId, Validator, ValidatorKeys, + BlockHeader, BlockTimeStamp, ChainId, EncodedBlockData, HashFunction::Blake2b, Height, Kid, + LedgerType, Metadata, PreviousBlockHash, PurposeId, Validator, ValidatorKeys, }; #[test] fn block_header_encode_decode() { @@ -582,9 +600,9 @@ mod tests { let metadata = Some(Metadata(vec![1; 128])); let encoded_block_hdr = encode_block_header( - chain_id, - block_height, - block_ts, + chain_id.clone(), + block_height.clone(), + block_ts.clone(), &prev_block_height.clone(), &ledger_type.clone(), &purpose_id.clone(), @@ -593,6 +611,17 @@ mod tests { ) .unwrap(); + let block_hdr = BlockHeader( + chain_id, + block_height, + block_ts, + prev_block_height.clone(), + ledger_type.clone(), + purpose_id.clone(), + validators.clone(), + metadata.clone(), + ); + // validators let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, @@ -614,7 +643,7 @@ mod tests { let encoded_block_data = block_data.writer().clone(); let encoded_block = encode_block( - EncodedBlockHeader(encoded_block_hdr.clone()), + block_hdr, &EncodedBlockData(encoded_block_data.clone()), &ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), &Blake2b, diff --git a/rust/immutable-ledger/src/validate.rs b/rust/immutable-ledger/src/validate.rs index e4a5695f0b..cf40c97c02 100644 --- a/rust/immutable-ledger/src/validate.rs +++ b/rust/immutable-ledger/src/validate.rs @@ -141,9 +141,9 @@ mod tests { use super::{block_validation, genesis_validation}; use crate::serialize::{ - blake2b_512, encode_block, encode_block_header, encode_genesis, BlockTimeStamp, ChainId, - EncodedBlockData, EncodedBlockHeader, HashFunction::Blake2b, Height, Kid, LedgerType, - Metadata, PreviousBlockHash, PurposeId, Validator, ValidatorKeys, + blake2b_512, encode_block, encode_genesis, BlockHeader, BlockTimeStamp, ChainId, + EncodedBlockData, HashFunction::Blake2b, Height, Kid, LedgerType, Metadata, + PreviousBlockHash, PurposeId, Validator, ValidatorKeys, }; #[test] @@ -165,23 +165,22 @@ mod tests { let chain_id = ChainId(Ulid::new()); let block_height = Height(5); let block_ts = BlockTimeStamp(1_728_474_515); - let prev_block_height = PreviousBlockHash(vec![0; 64]); + let prev_block_hash = PreviousBlockHash(vec![0; 64]); let ledger_type = LedgerType(Uuid::new_v4()); let purpose_id = PurposeId(Ulid::new()); let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); let metadata = Some(Metadata(vec![1; 128])); - let encoded_block_hdr = encode_block_header( + let block_hdr = BlockHeader( chain_id, block_height, block_ts, - &prev_block_height.clone(), - &ledger_type.clone(), - &purpose_id.clone(), - &validators.clone(), + prev_block_hash.clone(), + ledger_type.clone(), + purpose_id.clone(), + validators.clone(), metadata.clone(), - ) - .unwrap(); + ); // validators let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ @@ -204,7 +203,7 @@ mod tests { let encoded_block_data = block_data.writer().clone(); let previous_block = encode_block( - EncodedBlockHeader(encoded_block_hdr.clone()), + block_hdr, &EncodedBlockData(encoded_block_data.clone()), &ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), &Blake2b, @@ -220,22 +219,21 @@ mod tests { let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); let metadata = Some(Metadata(vec![1; 128])); - let encoded_block_hdr = encode_block_header( + let block_hdr = BlockHeader( chain_id, block_height, block_ts, - &prev_block_hash, - &ledger_type.clone(), - &purpose_id.clone(), - &validators.clone(), + prev_block_hash.clone(), + ledger_type.clone(), + purpose_id.clone(), + validators.clone(), metadata.clone(), - ) - .unwrap(); + ); block_data.bytes(block_data_bytes).unwrap(); let current_block = encode_block( - EncodedBlockHeader(encoded_block_hdr.clone()), + block_hdr, &EncodedBlockData(encoded_block_data.clone()), &ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), &Blake2b, From 0f10aef38a136d8ed2698145e3ab03cdebf986f3 Mon Sep 17 00:00:00 2001 From: cong-or Date: Wed, 23 Oct 2024 10:51:08 +0100 Subject: [PATCH 36/53] refactor(encode block): simpler api --- rust/immutable-ledger/src/serialize.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index ee08e65e33..40c6da0b17 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -152,6 +152,7 @@ pub fn encode_block( let mut block_data_cbor_encoding_check = minicbor::Decoder::new(&binding); let _ = block_data_cbor_encoding_check.bytes()?; + // cbor encode block hdr let encoded_block_hdr = encode_block_header( block_hdr.0, block_hdr.1, @@ -170,12 +171,10 @@ pub fn encode_block( // validator_signature MUST be a signature of the hashed block_header bytes // and the block_data bytes - let data_to_sign = [hashed_block_header, block_data.0.clone()].concat(); // if validator is only one id => validator_signature contains only 1 signature; // if validator is array => validator_signature contains an array with the same length; - let signatures: Vec<[u8; 64]> = validator_keys .0 .iter() From 6128913e88d8e14a1bf9487dad4c82c26e6d069f Mon Sep 17 00:00:00 2001 From: cong-or Date: Wed, 23 Oct 2024 10:56:06 +0100 Subject: [PATCH 37/53] refactor(encode block): simpler api --- rust/immutable-ledger/src/serialize.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index 40c6da0b17..134e85bdc6 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -61,9 +61,9 @@ pub struct BlockHeaderSize(usize); #[derive(Debug, Clone, PartialEq)] pub struct EncodedBlockHeader(pub Vec); -/// Decoded block data +/// Block data #[derive(Debug, Clone, PartialEq)] -pub struct DecodedBlockData(Vec); +pub struct BlockData(Vec); /// Encoded block data as cbor #[derive(Debug, Clone, PartialEq)] @@ -82,7 +82,7 @@ pub struct Signatures(Vec); pub struct ValidatorKeys(pub Vec<[u8; SECRET_KEY_LENGTH]>); /// Decoded block -pub type DecodedBlock = (DecodedBlockHeader, DecodedBlockData, Signatures); +pub type DecodedBlock = (DecodedBlockHeader, BlockData, Signatures); /// Block header pub struct BlockHeader( @@ -228,11 +228,7 @@ pub fn decode_block(encoded_block: &[u8]) -> anyhow::Result { sigs.push(Signature::from_bytes(&sig)); } - Ok(( - block_hdr, - DecodedBlockData(block_data.to_vec()), - Signatures(sigs), - )) + Ok((block_hdr, BlockData(block_data.to_vec()), Signatures(sigs))) } /// Produce BLAKE3 hash From 24400d9d962ca7b6a4bc73d3850e0bae0742076d Mon Sep 17 00:00:00 2001 From: cong-or Date: Wed, 23 Oct 2024 11:04:01 +0100 Subject: [PATCH 38/53] refactor(encode block): simpler api --- rust/immutable-ledger/src/serialize.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index 134e85bdc6..a10bd622e4 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -595,9 +595,9 @@ mod tests { let metadata = Some(Metadata(vec![1; 128])); let encoded_block_hdr = encode_block_header( - chain_id.clone(), - block_height.clone(), - block_ts.clone(), + chain_id, + block_height, + block_ts, &prev_block_height.clone(), &ledger_type.clone(), &purpose_id.clone(), From e3a1fbe9576d8b92e247f0d673cf24a8730abc5f Mon Sep 17 00:00:00 2001 From: cong-or Date: Sun, 3 Nov 2024 20:22:15 +0000 Subject: [PATCH 39/53] refactor(add block types and cddl tests): verifcation and refactor --- rust/immutable-ledger/Cargo.toml | 2 +- rust/immutable-ledger/src/cddl/block.cddl | 33 + .../src/cddl/block_header.cddl | 26 + rust/immutable-ledger/src/lib.rs | 8 +- rust/immutable-ledger/src/ser.rs | 917 ++++++++++++ rust/immutable-ledger/src/serialize.rs | 1254 +++++++++-------- rust/immutable-ledger/src/validate.rs | 279 ---- 7 files changed, 1673 insertions(+), 846 deletions(-) create mode 100644 rust/immutable-ledger/src/cddl/block.cddl create mode 100644 rust/immutable-ledger/src/cddl/block_header.cddl create mode 100644 rust/immutable-ledger/src/ser.rs delete mode 100644 rust/immutable-ledger/src/validate.rs diff --git a/rust/immutable-ledger/Cargo.toml b/rust/immutable-ledger/Cargo.toml index b7924443f4..ce7792da67 100644 --- a/rust/immutable-ledger/Cargo.toml +++ b/rust/immutable-ledger/Cargo.toml @@ -16,7 +16,7 @@ ulid = { version = "1.1.3", features = ["serde", "uuid"] } hex = "0.4.3" blake2b_simd = "1.0.2" blake3 = "=0.1.3" - +cddl = "0.9.4" diff --git a/rust/immutable-ledger/src/cddl/block.cddl b/rust/immutable-ledger/src/cddl/block.cddl new file mode 100644 index 0000000000..f380b6a020 --- /dev/null +++ b/rust/immutable-ledger/src/cddl/block.cddl @@ -0,0 +1,33 @@ +block = [ + block_header, + block_data: metadata, + validator_signature:metadata, +] + +block_header = [ + chain_id: ULID, + height: int, + timestamp: #6.1(uint .ge 1722470400), ; Epoch-based date/time + prev_block_id: hash_bytes, ; hash of the previous block + ledger_type: UUID, + purpose_id: ULID / UUID, + validator: validator, + metadata: metadata, +] + +ULID = #6.32780(bytes) ; ULID type +UUID = #6.37(bytes) ; UUID type + +BLAKE_3 = #6.32781(bytes) ; Blake3 hash +BLAKE_2B = #6.32782(bytes) ; Blake2b hash +BLAKE_2S = #6.32783(bytes) ; Blake2s hash + +hash_bytes = BLAKE_2B / BLAKE_3 / BLAKE_2S + +kid = hash_bytes ; hash of the x509/c509 certificate + +validator = (kid / [2* kid]) + +metadata = [ *any ] + +validator_signature = (bytes / [2* bytes]) diff --git a/rust/immutable-ledger/src/cddl/block_header.cddl b/rust/immutable-ledger/src/cddl/block_header.cddl new file mode 100644 index 0000000000..b4a57de778 --- /dev/null +++ b/rust/immutable-ledger/src/cddl/block_header.cddl @@ -0,0 +1,26 @@ +block_header = [ + chain_id: ULID, + height: int, + timestamp: #6.1(uint .ge 1722470400), ; Epoch-based date/time + prev_block_id: hash_bytes, ; hash of the previous block + ledger_type: UUID, + purpose_id: ULID / UUID, + validator: validator, + metadata: metadata, +] + +ULID = #6.32780(bytes) ; ULID type +UUID = #6.37(bytes) ; UUID type + +BLAKE_3 = #6.32781(bytes) ; Blake3 hash +BLAKE_2B = #6.32782(bytes) ; Blake2b hash +BLAKE_2S = #6.32783(bytes) ; Blake2s hash + +hash_bytes = BLAKE_2B / BLAKE_3 / BLAKE_2S + +kid = hash_bytes ; hash of the x509/c509 certificate + +validator = (kid / [2* kid]) + +metadata = [ *any ] + diff --git a/rust/immutable-ledger/src/lib.rs b/rust/immutable-ledger/src/lib.rs index 4ec70bb704..3c11fab089 100644 --- a/rust/immutable-ledger/src/lib.rs +++ b/rust/immutable-ledger/src/lib.rs @@ -4,8 +4,8 @@ //! //! Spec: `` -/// Block validation logic -pub mod validate; - -/// Block encoding decoding +/// Block encoding decoding and validation pub mod serialize; + +/// ac +pub mod ser; diff --git a/rust/immutable-ledger/src/ser.rs b/rust/immutable-ledger/src/ser.rs new file mode 100644 index 0000000000..e8d0b325ed --- /dev/null +++ b/rust/immutable-ledger/src/ser.rs @@ -0,0 +1,917 @@ +//! Block structure + +//! Block structure + +use anyhow::Ok; +use blake2b_simd::{self, Params}; +use ed25519_dalek::{ + ed25519::signature::SignerMut, Signature, SigningKey, SECRET_KEY_LENGTH, SIGNATURE_LENGTH, +}; +use ulid::Ulid; +use uuid::Uuid; + +/// Genesis block MUST have 0 value height. +const GENESIS_BLOCK: i64 = 0; + +/// Block header size +#[derive(Debug, Clone, PartialEq)] +pub struct BlockHeaderSize(usize); + +/// Decoded block header +pub type DecodedBlockHeader = BlockHeader; + +/// Signatures +#[derive(Debug, Clone, PartialEq)] +pub struct Signatures(Vec); + +/// Decoded block +pub type DecodedBlock = (DecodedBlockHeader, BlockData, Signatures); + +/// Encoded genesis Block contents as cbor, used for hash validation +#[derive(Debug, Clone, PartialEq)] +pub struct EncodedGenesisBlockContents(pub Vec); + +/// Choice of hash function: +/// must be the same as the hash of the previous block. +pub enum HashFunction { + /// BLAKE3 is based on an optimized instance of the established hash function BLAKE2 + /// and on the original Bao tree mode + Blake3, + /// BLAKE2b-512 produces digest side of 512 bits. + Blake2b, +} + +/// Kid (The key identifier) size in bytes +const KID_BYTES: usize = 16; + +/// Key ID - Blake2b-128 hash of the Role 0 Certificate defining the Session public key. +/// BLAKE2b-128 produces digest side of 16 bytes. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Kid(pub [u8; KID_BYTES]); + +/// Encoded whole block including block header, cbor encoded block data and signatures. +pub type EncodedBlock = Vec; + +/// Produce BLAKE3 hash +pub(crate) fn blake3(value: &[u8]) -> anyhow::Result<[u8; 32]> { + Ok(*blake3::hash(value).as_bytes()) +} + +/// BLAKE2b-512 produces digest side of 512 bits. +pub(crate) fn blake2b_512(value: &[u8]) -> anyhow::Result<[u8; 64]> { + let h = Params::new().hash_length(64).hash(value); + let b = h.as_bytes(); + b.try_into() + .map_err(|_| anyhow::anyhow!("Invalid length of blake2b_512, expected 64 got {}", b.len())) +} + +/// Block data +#[derive(Debug, Clone, PartialEq)] +pub struct BlockData(Vec); + +/// Validator's keys defined in the corresponding certificates referenced by the +/// validator. +pub struct ValidatorKeys(pub Vec<[u8; SECRET_KEY_LENGTH]>); + +/// CBOR tag for timestamp +const TIMESTAMP_CBOR_TAG: u64 = 1; + +/// CBOR tag for UUID +const UUID_CBOR_TAG: u64 = 37; + +/// CBOR tag for UUID +const ULID_CBOR_TAG: u64 = 32780; + +/// CBOR tags for BLAKE2 [2] and BLAKE3 [3] hash functions +/// `https://github.com/input-output-hk/catalyst-voices/blob/main/docs/src/catalyst-standards/cbor_tags/blake.md` + +/// CBOR tag for UUID +const BLAKE3_CBOR_TAG: u64 = 32781; + +/// CBOR tag for blake2b +const BLAKE_2B_CBOR_TAG: u64 = 32782; + +/// Block +pub struct Block { + /// Block header + pub block_header: BlockHeader, + /// cbor encoded block data + pub block_data: BlockData, + /// Validators + pub validator_keys: ValidatorKeys, + /// Hash function + pub hasher: HashFunction, +} + +impl Block { + /// New block + #[must_use] + pub fn new( + block_header: BlockHeader, block_data: BlockData, validator_keys: ValidatorKeys, + hasher: HashFunction, + ) -> Self { + Self { + block_header, + block_data, + validator_keys, + hasher, + } + } + + /// Encode block + /// ## Errors + /// + /// Returns an error if encoding fails. + pub fn to_bytes(&self) -> anyhow::Result { + // Enforce block data to be cbor encoded in the form of CBOR byte strings + // which are just (ordered) series of bytes without further interpretation + let _ = minicbor::Decoder::new(&self.block_data.0).bytes()?; + + // cbor encode block hdr + let encoded_block_hdr = self.block_header.to_bytes(&self.hasher)?; + + let hashed_block_header = match self.hasher { + HashFunction::Blake3 => blake3(&encoded_block_hdr)?.to_vec(), + HashFunction::Blake2b => blake2b_512(&encoded_block_hdr)?.to_vec(), + }; + + // validator_signature MUST be a signature of the hashed block_header bytes + // and the block_data bytes + let data_to_sign = [hashed_block_header, self.block_data.0.clone()].concat(); + + // if validator is only one id => validator_signature contains only 1 signature; + // if validator is array => validator_signature contains an array with the same length; + let signatures: Vec<[u8; 64]> = self + .validator_keys + .0 + .iter() + .map(|sk| { + let mut sk: SigningKey = SigningKey::from_bytes(sk); + sk.sign(&data_to_sign).to_bytes() + }) + .collect(); + + let out: Vec = Vec::new(); + let mut encoder = minicbor::Encoder::new(out); + encoder.array(signatures.len().try_into()?)?; + for sig in signatures { + encoder.bytes(&sig)?; + } + + Ok([ + [encoded_block_hdr, self.block_data.0.clone()].concat(), + encoder.writer().to_vec(), + ] + .concat()) + } + + /// Decode block + /// ## Errors + /// + /// Returns an error if decoding fails. + pub fn from_bytes(encoded_block: &[u8], hasher: &HashFunction) -> anyhow::Result { + // Decoded block hdr + let (block_hdr, block_hdr_size, _) = BlockHeader::from_bytes(encoded_block, hasher)?; + + // Init decoder + let mut cbor_decoder = minicbor::Decoder::new(encoded_block); + + // Decode remaining block, set position after block hdr data. + cbor_decoder.set_position(block_hdr_size.0); + + // Block data + let block_data = cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block data : {e}")))?; + + // Extract signatures + let number_of_sigs = cbor_decoder + .array()? + .ok_or(anyhow::anyhow!(format!("Invalid signature.")))?; + + let mut sigs = Vec::new(); + for _sig in 0..number_of_sigs { + let sig: [u8; SIGNATURE_LENGTH] = cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for signature : {e}")))? + .try_into()?; + + sigs.push(Signature::from_bytes(&sig)); + } + + Ok((block_hdr, BlockData(block_data.to_vec()), Signatures(sigs))) + } + + /// Validate block against previous block or validate itself if genesis block. + /// ## Errors + /// + /// Returns an error if validation fails. + pub fn validate(&self, previous_block: Option) -> anyhow::Result<()> { + if let Some(previous_block) = previous_block { + // Standard block + let hashed_previous_block = match self.hasher { + HashFunction::Blake3 => blake3(&previous_block.to_bytes()?)?.to_vec(), + HashFunction::Blake2b => blake2b_512(&previous_block.to_bytes()?)?.to_vec(), + }; + + // chain_id MUST be the same as for the previous block (except for genesis). + if self.block_header.chain_id != previous_block.block_header.chain_id { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: Chain_id MUST be the same as for the previous block {:?} {:?}", + self.block_header, + previous_block.block_header + )); + }; + + // height MUST be incremented by 1 from the previous block height value (except for + // genesis and final block). Genesis block MUST have 0 value. Final block MUST hash be + // incremented by 1 from the previous block height and changed the sign to negative. + // E.g. previous block height is 9 and the Final block height is -10. + if self.block_header.height != previous_block.block_header.height + 1 { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: height validation failed: {:?} {:?}", + self.block_header, + previous_block.block_header + )); + } + + // timestamp MUST be greater or equals than the timestamp of the previous block (except + // for genesis) + if self.block_header.block_time_stamp <= previous_block.block_header.block_time_stamp { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: timestamp validation failed: {:?} {:?}", + self.block_header, + previous_block.block_header + )); + } + + // prev_block_id MUST be a hash of the previous block bytes (except for genesis). + if self.block_header.previous_block_hash != hashed_previous_block { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: previous hash validation failed: {:?} {:?}", + self.block_header, + previous_block.block_header + )); + } + + // ledger_type MUST be the same as for the previous block if present (except for + // genesis). + if self.block_header.ledger_type != previous_block.block_header.ledger_type { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: ledger type validation failed: {:?} {:?}", + self.block_header, + previous_block.block_header + )); + } + + // purpose_id MUST be the same as for the previous block if present (except for + // genesis). + if self.block_header.purpose_id != previous_block.block_header.purpose_id { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: purpose id validation failed: {:?} {:?}", + self.block_header, + previous_block.block_header + )); + } + + // validator MUST be the same as for the previous block if present (except for genesis) + if self.block_header.validator != previous_block.block_header.validator { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: validator validation failed: {:?} {:?}", + self.block_header, + previous_block.block_header + )); + } + } else if self.block_header.height == GENESIS_BLOCK { + // Validate genesis block + { + let genesis_to_prev_hash = GenesisPreviousHash::new( + self.block_header.chain_id, + self.block_header.block_time_stamp, + self.block_header.ledger_type, + self.block_header.purpose_id, + self.block_header.validator.clone(), + ) + .hash(&self.hasher)?; + + if self.block_header.previous_block_hash != genesis_to_prev_hash { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: Genesis block prev hash is invalid {:?}", + self.block_header, + )); + } + } + } + + Ok(()) + } +} + +/// Block header +#[derive(Debug, Clone, PartialEq)] +pub struct BlockHeader { + /// Unique identifier of the chain. + pub chain_id: Ulid, + /// Block height. + pub height: i64, + /// Block epoch-based date/time. + pub block_time_stamp: i64, + /// Previous Block hash. + pub previous_block_hash: Vec, + /// unique identifier of the ledger type. + /// In general, this is the way to strictly bound and specify `block_data` of the + /// ledger for the specific `ledger_type`. + pub ledger_type: Uuid, + /// unique identifier of the purpose, each Ledger instance will have a strict time + /// boundaries, so each of them will run for different purposes. + pub purpose_id: Ulid, + /// Identifier or identifiers of the entity who was produced and processed a block. + pub validator: Vec, + /// Add arbitrary metadata to the block. + pub metadata: Vec, +} + +impl BlockHeader { + /// Create new block + #[must_use] + #[allow(clippy::too_many_arguments)] + pub fn new( + chain_id: Ulid, height: i64, block_time_stamp: i64, previous_block_hash: Vec, + ledger_type: Uuid, purpose_id: Ulid, validator: Vec, metadata: Vec, + ) -> Self { + Self { + chain_id, + height, + block_time_stamp, + previous_block_hash, + ledger_type, + purpose_id, + validator, + metadata, + } + } + + /// Encode block header + /// ## Errors + /// + /// Returns an error encoding fails + pub fn to_bytes(&self, hasher: &HashFunction) -> anyhow::Result> { + /// # of elements in block header + const BLOCK_HEADER_SIZE: u64 = 8; + + let out: Vec = Vec::new(); + let mut encoder = minicbor::Encoder::new(out); + + encoder.array(BLOCK_HEADER_SIZE)?; + + // Chain id + encoder.tag(minicbor::data::Tag::new(ULID_CBOR_TAG))?; + encoder.bytes(&self.chain_id.to_bytes())?; + + // Block height + encoder.int(self.height.into())?; + + // Block timestamp + encoder.tag(minicbor::data::Tag::new(TIMESTAMP_CBOR_TAG))?; + encoder.int(self.block_time_stamp.into())?; + + let cbor_hash_tag = match hasher { + HashFunction::Blake3 => BLAKE3_CBOR_TAG, + HashFunction::Blake2b => BLAKE_2B_CBOR_TAG, + }; + + // Prev block hash + encoder.tag(minicbor::data::Tag::new(cbor_hash_tag))?; + encoder.bytes(&self.previous_block_hash)?; + + // Ledger type + encoder.tag(minicbor::data::Tag::new(UUID_CBOR_TAG))?; + encoder.bytes(self.ledger_type.as_bytes())?; + + // Purpose id + encoder.tag(minicbor::data::Tag::new(ULID_CBOR_TAG))?; + encoder.bytes(&self.purpose_id.to_bytes())?; + + // Validators + encoder.array(self.validator.len().try_into()?)?; + for val in self.validator.clone() { + encoder.tag(minicbor::data::Tag::new(cbor_hash_tag))?; + encoder.bytes(&val.0)?; + } + + // Metadata + encoder.bytes(&self.metadata)?; + + Ok(encoder.writer().clone()) + } + + /// Decode block header + /// ## Errors + /// + /// Returns an error decoding fails + pub fn from_bytes( + block: &[u8], _hasher: &HashFunction, + ) -> anyhow::Result<( + BlockHeader, + BlockHeaderSize, + Option, + )> { + // Decode cbor to bytes + let mut cbor_decoder = minicbor::Decoder::new(block); + cbor_decoder.array()?; + + // Raw chain_id + cbor_decoder.tag()?; + let chain_id = Ulid::from_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for chain id : {e}")))? + .try_into()?, + ); + + // Raw Block height + let block_height: i64 = cbor_decoder.int()?.try_into()?; + + // Raw time stamp + cbor_decoder.tag()?; + let ts: i64 = cbor_decoder.int()?.try_into()?; + + // Raw prev block hash + cbor_decoder.tag()?; + let prev_block_hash = cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for prev block hash : {e}")))? + .to_vec(); + + // Raw ledger type + cbor_decoder.tag()?; + let ledger_type = Uuid::from_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for ledger type : {e}")))? + .try_into()?, + ); + + // Raw purpose id + cbor_decoder.tag()?; + let purpose_id = Ulid::from_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for purpose id : {e}")))? + .try_into()?, + ); + + // Validators + let mut validators = Vec::new(); + let number_of_validators = cbor_decoder.array()?.ok_or(anyhow::anyhow!(format!( + "Invalid amount of validators, should be at least two" + )))?; + + for _validator in 0..number_of_validators { + cbor_decoder.tag()?; + let validator_kid: [u8; 16] = cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for validators : {e}")))? + .try_into()?; + + validators.push(Kid(validator_kid)); + } + + let metadata = cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for metadata : {e}")))? + .try_into()?; + + let block_header = BlockHeader { + chain_id, + height: block_height, + block_time_stamp: ts, + previous_block_hash: prev_block_hash, + ledger_type, + purpose_id, + validator: validators, + metadata, + }; + + Ok((block_header, BlockHeaderSize(cbor_decoder.position()), None)) + } +} + +/// Genesis block previous identifier type i.e hash of itself +pub struct GenesisPreviousHash { + /// Unique identifier of the chain. + pub chain_id: Ulid, + /// Block epoch-based date/time. + pub block_time_stamp: i64, + /// unique identifier of the ledger type. + /// In general, this is the way to strictly bound and specify `block_data` of the + /// ledger for the specific `ledger_type`. + pub ledger_type: Uuid, + /// unique identifier of the purpose, each Ledger instance will have a strict time + /// boundaries, so each of them will run for different purposes. + pub purpose_id: Ulid, + /// Identifier or identifiers of the entity who was produced and processed a block. + pub validator: Vec, +} + +impl GenesisPreviousHash { + /// Create previous block id + #[must_use] + pub fn new( + chain_id: Ulid, block_time_stamp: i64, ledger_type: Uuid, purpose_id: Ulid, + validator: Vec, + ) -> Self { + Self { + chain_id, + block_time_stamp, + ledger_type, + purpose_id, + validator, + } + } + + /// Encode genesis previous hash to cbor + /// ## Errors + /// + /// Returns an error encoding fails + pub fn to_bytes(&self, hasher: &HashFunction) -> anyhow::Result> { + // # of elements in Genesis to previous block hash + // const GENESIS_TO_PREV_HASH_SIZE: u64 = 5; + let out: Vec = Vec::new(); + let mut encoder = minicbor::Encoder::new(out); + encoder.array(5)?; + + // Chain id + encoder.tag(minicbor::data::Tag::new(ULID_CBOR_TAG))?; + encoder.bytes(&self.chain_id.to_bytes())?; + + // Block timestamp + encoder.tag(minicbor::data::Tag::new(TIMESTAMP_CBOR_TAG))?; + encoder.int(self.block_time_stamp.into())?; + + let cbor_hash_tag = match hasher { + HashFunction::Blake3 => BLAKE3_CBOR_TAG, + HashFunction::Blake2b => BLAKE_2B_CBOR_TAG, + }; + + // Ledger type + encoder.tag(minicbor::data::Tag::new(UUID_CBOR_TAG))?; + encoder.bytes(self.ledger_type.as_bytes())?; + + // Purpose id + encoder.tag(minicbor::data::Tag::new(ULID_CBOR_TAG))?; + encoder.bytes(&self.purpose_id.to_bytes())?; + + // Validators + encoder.array(self.validator.len().try_into()?)?; + for val in self.validator.clone() { + encoder.tag(minicbor::data::Tag::new(cbor_hash_tag))?; + encoder.bytes(&val.0)?; + } + + Ok(encoder.writer().clone()) + } + + /// Generate hash of cbor encoded self + /// ## Errors + /// + /// Returns an error if hashing fails + pub fn hash(&self, hasher: &HashFunction) -> anyhow::Result> { + let encoding = self.to_bytes(hasher)?; + + // get hash of genesis_to_prev_hash + let genesis_prev_hash = match hasher { + HashFunction::Blake3 => blake3(&encoding)?.to_vec(), + HashFunction::Blake2b => blake2b_512(&encoding)?.to_vec(), + }; + + Ok(genesis_prev_hash) + } +} + +#[cfg(test)] +mod tests { + + use ed25519_dalek::{SigningKey, SECRET_KEY_LENGTH}; + use ulid::Ulid; + use uuid::Uuid; + + use super::{BlockHeader, Kid}; + use crate::ser::{ + blake2b_512, Block, BlockData, GenesisPreviousHash, HashFunction::Blake2b, ValidatorKeys, + }; + + #[test] + fn block_header_encoding() { + let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let block_hdr = BlockHeader::new( + Ulid::new(), + 5, + 1_728_474_515, + vec![0; 64], + Uuid::new_v4(), + Ulid::new(), + vec![Kid(kid_a), Kid(kid_b)], + vec![7; 356], + ); + + let encoded_block_hdr = block_hdr.to_bytes(&Blake2b).unwrap(); + + const CDDL: &str = include_str!("./cddl/block_header.cddl"); + + cddl::validate_cbor_from_slice(CDDL, &encoded_block_hdr, None).unwrap(); + + let (block_hdr_from_bytes, ..) = + BlockHeader::from_bytes(&encoded_block_hdr, &Blake2b).unwrap(); + assert_eq!(block_hdr_from_bytes.chain_id, block_hdr.chain_id); + assert_eq!(block_hdr_from_bytes.height, block_hdr.height); + assert_eq!( + block_hdr_from_bytes.block_time_stamp, + block_hdr.block_time_stamp + ); + assert_eq!( + block_hdr_from_bytes.previous_block_hash, + block_hdr.previous_block_hash + ); + assert_eq!(block_hdr_from_bytes.ledger_type, block_hdr.ledger_type); + assert_eq!(block_hdr_from_bytes.purpose_id, block_hdr.purpose_id); + assert_eq!(block_hdr_from_bytes.validator, block_hdr.validator); + assert_eq!(block_hdr_from_bytes.metadata, block_hdr.metadata); + } + + #[test] + fn block_encoding() { + // validators + let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, + 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, + ]; + + let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let block_hdr = BlockHeader::new( + Ulid::new(), + 5, + 1_728_474_515, + vec![0; 64], + Uuid::new_v4(), + Ulid::new(), + vec![Kid(kid_a), Kid(kid_b)], + vec![1; 128], + ); + + let out: Vec = Vec::new(); + let mut block_data = minicbor::Encoder::new(out); + + let block_data_bytes = &[ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, + 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, + 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, + 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, + 157, + ]; + + block_data.bytes(block_data_bytes).unwrap(); + let encoded_block_data = block_data.writer().clone(); + + let block = Block::new( + block_hdr.clone(), + BlockData(encoded_block_data.clone()), + ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), + Blake2b, + ); + + let encoded_block = block.to_bytes().unwrap(); + + const CDDL: &str = include_str!("./cddl/block.cddl"); + + cddl::validate_cbor_from_slice(CDDL, &encoded_block, None).unwrap(); + + let (block_header, block_data, sigs) = Block::from_bytes(&encoded_block, &Blake2b).unwrap(); + + assert_eq!(block_header, block_hdr); + + // signatures are over encoded block data + // block data is returned as plain bytes decoded from cbor + assert_eq!(block_data.0, block_data_bytes); + let data_to_sign = [ + blake2b_512(&block_hdr.to_bytes(&Blake2b).unwrap()) + .unwrap() + .to_vec(), + encoded_block_data, + ] + .concat(); + + let verifying_key = SigningKey::from_bytes(&validator_secret_key_bytes); + + for sig in sigs.0 { + verifying_key.verify_strict(&data_to_sign, &sig).unwrap(); + } + + // ENCODING SHOULD FAIL with block data that is NOT cbor encoded + let block = Block::new( + block_hdr.clone(), + BlockData(block_data_bytes.to_vec()), + ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), + Blake2b, + ); + + assert!(block.to_bytes().is_err()); + } + + #[test] + #[allow(clippy::zero_prefixed_literal)] + fn validate_block_test() { + // PREVIOUS BLOCK + // + // + // validators + let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, + 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, + ]; + + let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let chain_id = Ulid::new(); + let ledger_type = Uuid::new_v4(); + let purpose_id = Ulid::new(); + + let block_hdr = BlockHeader::new( + chain_id, + 5, + 1_728_474_515, + vec![0; 64], + ledger_type, + purpose_id, + vec![Kid(kid_a), Kid(kid_b)], + vec![1; 128], + ); + + let out: Vec = Vec::new(); + let mut block_data = minicbor::Encoder::new(out); + + let block_data_bytes = &[ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, + 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, + 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, + 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, + 157, + ]; + + block_data.bytes(block_data_bytes).unwrap(); + let encoded_block_data = block_data.writer().clone(); + + let previous_block = Block::new( + block_hdr.clone(), + BlockData(encoded_block_data.clone()), + ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), + Blake2b, + ); + + // CURRENT BLOCK + + let prev_block_hash = blake2b_512(&previous_block.to_bytes().unwrap()).unwrap(); + + let block_hdr = BlockHeader::new( + chain_id, + 6, + 1_728_474_516, + prev_block_hash.to_vec(), + ledger_type, + purpose_id, + vec![Kid(kid_a), Kid(kid_b)], + vec![1; 128], + ); + + let out: Vec = Vec::new(); + let mut block_data = minicbor::Encoder::new(out); + + block_data.bytes(block_data_bytes).unwrap(); + let encoded_block_data = block_data.writer().clone(); + + let current_block = Block::new( + block_hdr.clone(), + BlockData(encoded_block_data.clone()), + ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), + Blake2b, + ); + + assert!(current_block.validate(Some(previous_block)).is_ok()); + } + + #[test] + fn genesis_encoding_and_validation() { + // validators + let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, + 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, + ]; + + let chain_id = Ulid::new(); + let ledger_type = Uuid::new_v4(); + let purpose_id = Ulid::new(); + let block_time_stamp = 1_728_474_515; + + let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let validator = vec![Kid(kid_a), Kid(kid_b)]; + + let genesis_to_prev_hash = GenesisPreviousHash::new( + chain_id, + block_time_stamp, + ledger_type, + purpose_id, + validator.clone(), + ); + + let block_hdr = BlockHeader::new( + chain_id, + 0, + block_time_stamp, + genesis_to_prev_hash.hash(&Blake2b).unwrap(), + ledger_type, + purpose_id, + validator.clone(), + vec![1; 128], + ); + + let out: Vec = Vec::new(); + let mut block_data = minicbor::Encoder::new(out); + + let block_data_bytes = &[ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, + 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, + 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, + 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, + 157, + ]; + + block_data.bytes(block_data_bytes).unwrap(); + let encoded_block_data = block_data.writer().clone(); + + let block = Block::new( + block_hdr.clone(), + BlockData(encoded_block_data.clone()), + ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), + Blake2b, + ); + + assert!(block.validate(None).is_ok()); + + // SHOULD FAIL as previous block hash for genesis is invalid, it should be a hash of + // itself like above. + let block_hdr = BlockHeader::new( + chain_id, + 0, + block_time_stamp, + vec![1; 128], + ledger_type, + purpose_id, + validator, + vec![1; 128], + ); + + let block = Block::new( + block_hdr.clone(), + BlockData(encoded_block_data.clone()), + ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), + Blake2b, + ); + + assert!(block.validate(None).is_err()); + } +} diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index a10bd622e4..a2faa2c91f 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -1,132 +1,35 @@ -//! Block Serialization -//! -//! Facilitates block serialization for immutable ledger +//! Block structure use core::result::Result::Ok as ResultOk; use anyhow::Ok; use blake2b_simd::{self, Params}; -use ed25519_dalek::{ed25519::signature::SignerMut, Signature, SigningKey, SECRET_KEY_LENGTH}; +use ed25519_dalek::{ + ed25519::signature::SignerMut, Signature, SigningKey, SECRET_KEY_LENGTH, SIGNATURE_LENGTH, +}; use ulid::Ulid; use uuid::Uuid; -/// Kid (The key identifier) size in bytes -const KID_BYTES: usize = 16; - -/// Key ID - Blake2b-128 hash of the Role 0 Certificate defining the Session public key. -/// BLAKE2b-128 produces digest side of 16 bytes. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Kid(pub [u8; KID_BYTES]); - -/// Unique identifier of the chain. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct ChainId(pub Ulid); - -/// Block height. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Height(pub u32); - -/// Block epoch-based date/time. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct BlockTimeStamp(pub i64); - -/// Previous Block hash. -#[derive(Debug, Clone, PartialEq)] -pub struct PreviousBlockHash(pub Vec); - -/// unique identifier of the ledger type. -/// In general, this is the way to strictly bound and specify `block_data` of the ledger -/// for the specific `ledger_type`. -#[derive(Debug, Clone, PartialEq)] -pub struct LedgerType(pub Uuid); - -/// unique identifier of the purpose, each Ledger instance will have a strict time -/// boundaries, so each of them will run for different purposes. -#[derive(Debug, Clone, PartialEq)] -pub struct PurposeId(pub Ulid); - -/// Identifier or identifiers of the entity who was produced and processed a block. -#[derive(Debug, Clone, PartialEq)] -pub struct Validator(pub Vec); - -/// Optional field, to add some arbitrary metadata to the block. -#[derive(Debug, Clone, PartialEq)] -pub struct Metadata(pub Vec); +/// Genesis block MUST have 0 value height. +const GENESIS_BLOCK: i64 = 0; /// Block header size #[derive(Debug, Clone, PartialEq)] pub struct BlockHeaderSize(usize); -/// Encoded block header as cbor -#[derive(Debug, Clone, PartialEq)] -pub struct EncodedBlockHeader(pub Vec); - -/// Block data -#[derive(Debug, Clone, PartialEq)] -pub struct BlockData(Vec); - -/// Encoded block data as cbor -#[derive(Debug, Clone, PartialEq)] -pub struct EncodedBlockData(pub Vec); - -/// Encoded genesis Block contents as cbor, used for hash validation -#[derive(Debug, Clone, PartialEq)] -pub struct EncodedGenesisBlockContents(pub Vec); +/// Decoded block header +pub type DecodedBlockHeader = BlockHeader; /// Signatures #[derive(Debug, Clone, PartialEq)] pub struct Signatures(Vec); -/// Validator's keys defined in the corresponding certificates referenced by the -/// validator. -pub struct ValidatorKeys(pub Vec<[u8; SECRET_KEY_LENGTH]>); - /// Decoded block pub type DecodedBlock = (DecodedBlockHeader, BlockData, Signatures); -/// Block header -pub struct BlockHeader( - pub ChainId, - pub Height, - pub BlockTimeStamp, - pub PreviousBlockHash, - pub LedgerType, - pub PurposeId, - pub Validator, - pub Option, -); - -/// Decoded block header -pub type DecodedBlockHeader = ( - ChainId, - Height, - BlockTimeStamp, - PreviousBlockHash, - LedgerType, - PurposeId, - Validator, - Option, - BlockHeaderSize, -); - -/// Decoded Genesis block -pub type DecodedBlockGenesis = ( - ChainId, - Height, - BlockTimeStamp, - PreviousBlockHash, - LedgerType, - PurposeId, - Validator, - BlockHeaderSize, - EncodedGenesisBlockContents, -); - -/// Encoded whole block including block header, cbor encoded block data and signatures. -pub type EncodedBlock = Vec; - -/// Encoded genesis block, see `genesis_to_prev_hash` -pub type EncodedGenesisBlock = Vec; +/// Encoded genesis Block contents as cbor, used for hash validation +#[derive(Debug, Clone, PartialEq)] +pub struct EncodedGenesisBlockContents(pub Vec); /// Choice of hash function: /// must be the same as the hash of the previous block. @@ -138,98 +41,16 @@ pub enum HashFunction { Blake2b, } -/// Encode standard block -/// ## Errors -/// -/// Returns an error if block encoding fails -pub fn encode_block( - block_hdr: BlockHeader, block_data: &EncodedBlockData, validator_keys: &ValidatorKeys, - hasher: &HashFunction, -) -> anyhow::Result { - // Enforce block data to be cbor encoded in the form of CBOR byte strings - // which are just (ordered) series of bytes without further interpretation - let binding = block_data.0.clone(); - let mut block_data_cbor_encoding_check = minicbor::Decoder::new(&binding); - let _ = block_data_cbor_encoding_check.bytes()?; - - // cbor encode block hdr - let encoded_block_hdr = encode_block_header( - block_hdr.0, - block_hdr.1, - block_hdr.2, - &block_hdr.3, - &block_hdr.4, - &block_hdr.5, - &block_hdr.6, - block_hdr.7, - )?; - - let hashed_block_header = match hasher { - HashFunction::Blake3 => blake3(&encoded_block_hdr)?.to_vec(), - HashFunction::Blake2b => blake2b_512(&encoded_block_hdr)?.to_vec(), - }; - - // validator_signature MUST be a signature of the hashed block_header bytes - // and the block_data bytes - let data_to_sign = [hashed_block_header, block_data.0.clone()].concat(); - - // if validator is only one id => validator_signature contains only 1 signature; - // if validator is array => validator_signature contains an array with the same length; - let signatures: Vec<[u8; 64]> = validator_keys - .0 - .iter() - .map(|sk| { - let mut sk: SigningKey = SigningKey::from_bytes(sk); - sk.sign(&data_to_sign).to_bytes() - }) - .collect(); - - let out: Vec = Vec::new(); - let mut encoder = minicbor::Encoder::new(out); - - encoder.bytes(&block_data.0)?; - - for sig in &signatures { - encoder.bytes(sig)?; - } - - let block_data_with_sigs = encoder.writer().clone(); - // block hdr + block data + sigs - let encoded_block = [encoded_block_hdr, block_data_with_sigs].concat(); - - Ok(encoded_block) -} - -/// Decodes standard block -/// ## Errors -/// -/// Returns an error if block decoding fails -pub fn decode_block(encoded_block: &[u8]) -> anyhow::Result { - // Decoded block hdr - let block_hdr: DecodedBlockHeader = decode_block_header(encoded_block)?; - - let mut cbor_decoder = minicbor::Decoder::new(encoded_block); - // Decode remaining block, set position after block hdr data. - cbor_decoder.set_position(block_hdr.8 .0); - - // Block data - let block_data = cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block data : {e}")))?; - - // Extract signatures, block hdr indicates how many validators. - let mut sigs = Vec::new(); - for _sig in 0..block_hdr.6 .0.len() { - let sig: [u8; 64] = cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for signature : {e}")))? - .try_into()?; +/// Kid (The key identifier) size in bytes +const KID_BYTES: usize = 16; - sigs.push(Signature::from_bytes(&sig)); - } +/// Key ID - Blake2b-128 hash of the Role 0 Certificate defining the Session public key. +/// BLAKE2b-128 produces digest side of 16 bytes. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Kid(pub [u8; KID_BYTES]); - Ok((block_hdr, BlockData(block_data.to_vec()), Signatures(sigs))) -} +/// Encoded whole block including block header, cbor encoded block data and signatures. +pub type EncodedBlock = Vec; /// Produce BLAKE3 hash pub(crate) fn blake3(value: &[u8]) -> anyhow::Result<[u8; 32]> { @@ -244,292 +65,485 @@ pub(crate) fn blake2b_512(value: &[u8]) -> anyhow::Result<[u8; 64]> { .map_err(|_| anyhow::anyhow!("Invalid length of blake2b_512, expected 64 got {}", b.len())) } -/// Encode block header as cbor -/// ## Errors -/// -/// Returns an error if block header encoding fails. -#[allow(clippy::too_many_arguments)] -pub fn encode_block_header( - chain_id: ChainId, height: Height, ts: BlockTimeStamp, prev_block_hash: &PreviousBlockHash, - ledger_type: &LedgerType, pid: &PurposeId, validator: &Validator, metadata: Option, -) -> anyhow::Result> { - let out: Vec = Vec::new(); - let mut encoder = minicbor::Encoder::new(out); - - encoder.bytes(&chain_id.0.to_bytes())?; - encoder.bytes(&height.0.to_be_bytes())?; - encoder.bytes(&ts.0.to_be_bytes())?; - encoder.bytes(prev_block_hash.0.as_slice())?; - encoder.bytes(ledger_type.0.as_bytes())?; - encoder.bytes(&pid.0.to_bytes())?; - - // marks how many validators for decoding side. - encoder.bytes(&validator.0.len().to_be_bytes())?; - for validator in &validator.0 { - encoder.bytes(&validator.0)?; +/// Block data +#[derive(Debug, Clone, PartialEq)] +pub struct BlockData(Vec); + +/// Validator's keys defined in the corresponding certificates referenced by the +/// validator. +pub struct ValidatorKeys(pub Vec<[u8; SECRET_KEY_LENGTH]>); + +/// Block +pub struct Block { + /// Block header + pub block_header: BlockHeader, + /// cbor encoded block data + pub block_data: BlockData, + /// Validators + pub validator_keys: ValidatorKeys, + /// Hash function + pub hasher: HashFunction, +} + +impl Block { + /// New block + #[must_use] + pub fn new( + block_header: BlockHeader, block_data: BlockData, validator_keys: ValidatorKeys, + hasher: HashFunction, + ) -> Self { + Self { + block_header, + block_data, + validator_keys, + hasher, + } } - if let Some(meta) = metadata { - encoder.bytes(&meta.0)?; + /// Encode block + /// ## Errors + /// + /// Returns an error if encoding fails. + pub fn to_bytes(&self) -> anyhow::Result { + // Enforce block data to be cbor encoded in the form of CBOR byte strings + // which are just (ordered) series of bytes without further interpretation + let _ = minicbor::Decoder::new(&self.block_data.0).bytes()?; + + // cbor encode block hdr + let encoded_block_hdr = self.block_header.to_bytes(&self.hasher)?; + + let hashed_block_header = match self.hasher { + HashFunction::Blake3 => blake3(&encoded_block_hdr)?.to_vec(), + HashFunction::Blake2b => blake2b_512(&encoded_block_hdr)?.to_vec(), + }; + + // validator_signature MUST be a signature of the hashed block_header bytes + // and the block_data bytes + let data_to_sign = [hashed_block_header, self.block_data.0.clone()].concat(); + + // if validator is only one id => validator_signature contains only 1 signature; + // if validator is array => validator_signature contains an array with the same length; + let signatures: Vec<[u8; 64]> = self + .validator_keys + .0 + .iter() + .map(|sk| { + let mut sk: SigningKey = SigningKey::from_bytes(sk); + sk.sign(&data_to_sign).to_bytes() + }) + .collect(); + + let out: Vec = Vec::new(); + let mut encoder = minicbor::Encoder::new(out); + + encoder.bytes(&self.block_data.0)?; + + for sig in &signatures { + encoder.bytes(sig)?; + } + + let block_data_with_sigs = encoder.writer().clone(); + // block hdr + block data + sigs + let encoded_block = [encoded_block_hdr, block_data_with_sigs].concat(); + + Ok(encoded_block) } - Ok(encoder.writer().clone()) -} + /// Decode block + /// ## Errors + /// + /// Returns an error if decoding fails. + pub fn from_bytes(encoded_block: &[u8], hasher: &HashFunction) -> anyhow::Result { + // Decoded block hdr + let (block_hdr, block_hdr_size, _) = BlockHeader::from_bytes(encoded_block, hasher)?; -/// Decode block header -/// ## Errors -/// -/// Returns an error if decoding block header fails. -pub fn decode_block_header(block: &[u8]) -> anyhow::Result { - // Decode cbor to bytes - let mut cbor_decoder = minicbor::Decoder::new(block); - - // Raw chain_id - let chain_id = ChainId(Ulid::from_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for chain id : {e}")))? - .try_into()?, - )); + // Init decoder + let mut cbor_decoder = minicbor::Decoder::new(encoded_block); - // Raw Block height - let block_height = Height(u32::from_be_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block height : {e}")))? - .try_into()?, - )); + // Decode remaining block, set position after block hdr data. + cbor_decoder.set_position(block_hdr_size.0); - // Raw time stamp - let ts = BlockTimeStamp(i64::from_be_bytes( - cbor_decoder + // Block data + let block_data = cbor_decoder .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for timestamp : {e}")))? - .try_into()?, - )); + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block data : {e}")))?; - // Raw prev block hash - let prev_block_hash = PreviousBlockHash( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for prev block hash : {e}")))? - .to_vec(), - ); + // Extract signatures, block hdr indicates how many validators. + let mut sigs = Vec::new(); + for _sig in 0..block_hdr.validator.len() { + let sig: [u8; SIGNATURE_LENGTH] = cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for signature : {e}")))? + .try_into()?; - // Raw ledger type - let ledger_type = LedgerType(Uuid::from_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for ledger type : {e}")))? - .try_into()?, - )); + sigs.push(Signature::from_bytes(&sig)); + } - // Raw purpose id - let purpose_id = PurposeId(Ulid::from_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for purpose id : {e}")))? - .try_into()?, - )); + Ok((block_hdr, BlockData(block_data.to_vec()), Signatures(sigs))) + } - // Number of validators - let number_of_validators = usize::from_be_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for number of validators : {e}")))? - .try_into()?, - ); - - // Extract validators - let mut validators = Vec::new(); - for _validator in 0..number_of_validators { - let validator_kid: [u8; 16] = cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for validators : {e}")))? - .try_into()?; + /// Validate block against previous block or validate itself if genesis block. + /// ## Errors + /// + /// Returns an error if validation fails. + pub fn validate(&self, previous_block: Option) -> anyhow::Result<()> { + if let Some(previous_block) = previous_block { + // Standard block + let hashed_previous_block = match self.hasher { + HashFunction::Blake3 => blake3(&previous_block.to_bytes()?)?.to_vec(), + HashFunction::Blake2b => blake2b_512(&previous_block.to_bytes()?)?.to_vec(), + }; + + // chain_id MUST be the same as for the previous block (except for genesis). + if self.block_header.chain_id != previous_block.block_header.chain_id { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: Chain_id MUST be the same as for the previous block {:?} {:?}", + self.block_header, + previous_block.block_header + )); + }; + + // height MUST be incremented by 1 from the previous block height value (except for + // genesis and final block). Genesis block MUST have 0 value. Final block MUST hash be + // incremented by 1 from the previous block height and changed the sign to negative. + // E.g. previous block height is 9 and the Final block height is -10. + if self.block_header.height != previous_block.block_header.height + 1 { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: height validation failed: {:?} {:?}", + self.block_header, + previous_block.block_header + )); + } + + // timestamp MUST be greater or equals than the timestamp of the previous block (except + // for genesis) + if self.block_header.block_time_stamp <= previous_block.block_header.block_time_stamp { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: timestamp validation failed: {:?} {:?}", + self.block_header, + previous_block.block_header + )); + } + + // prev_block_id MUST be a hash of the previous block bytes (except for genesis). + if self.block_header.previous_block_hash != hashed_previous_block { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: previous hash validation failed: {:?} {:?}", + self.block_header, + previous_block.block_header + )); + } + + // ledger_type MUST be the same as for the previous block if present (except for + // genesis). + if self.block_header.ledger_type != previous_block.block_header.ledger_type { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: ledger type validation failed: {:?} {:?}", + self.block_header, + previous_block.block_header + )); + } + + // purpose_id MUST be the same as for the previous block if present (except for + // genesis). + if self.block_header.purpose_id != previous_block.block_header.purpose_id { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: purpose id validation failed: {:?} {:?}", + self.block_header, + previous_block.block_header + )); + } + + // validator MUST be the same as for the previous block if present (except for genesis) + if self.block_header.validator != previous_block.block_header.validator { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: validator validation failed: {:?} {:?}", + self.block_header, + previous_block.block_header + )); + } + } else if self.block_header.height == GENESIS_BLOCK { + // Validate genesis block + { + let genesis_to_prev_hash = GenesisPreviousHash::new( + self.block_header.chain_id, + self.block_header.block_time_stamp, + self.block_header.ledger_type, + self.block_header.purpose_id, + self.block_header.validator.clone(), + ) + .hash(&self.hasher)?; + + if self.block_header.previous_block_hash != genesis_to_prev_hash { + return Err(anyhow::anyhow!( + "Module: Immutable ledger, Message: Genesis block prev hash is invalid {:?}", + self.block_header, + )); + } + } + } - validators.push(Kid(validator_kid)); + Ok(()) } +} - let metadata = match cbor_decoder.bytes() { - ResultOk(meta) => Some(Metadata(meta.to_vec())), - Err(_) => None, - }; - - Ok(( - chain_id, - block_height, - ts, - prev_block_hash, - ledger_type, - purpose_id, - Validator(validators), - metadata, - BlockHeaderSize(cbor_decoder.position()), - )) +/// Block header +#[derive(Debug, Clone, PartialEq)] +pub struct BlockHeader { + /// Unique identifier of the chain. + pub chain_id: Ulid, + /// Block height. + pub height: i64, + /// Block epoch-based date/time. + pub block_time_stamp: i64, + /// Previous Block hash. + pub previous_block_hash: Vec, + /// unique identifier of the ledger type. + /// In general, this is the way to strictly bound and specify `block_data` of the + /// ledger for the specific `ledger_type`. + pub ledger_type: Uuid, + /// unique identifier of the purpose, each Ledger instance will have a strict time + /// boundaries, so each of them will run for different purposes. + pub purpose_id: Ulid, + /// Identifier or identifiers of the entity who was produced and processed a block. + pub validator: Vec, + /// Optional field, to add some arbitrary metadata to the block. + pub metadata: Option>, } -/// Encode genesis block -/// ## Errors -/// -/// Returns an error if genesis block encoding fails. -pub fn encode_genesis( - chain_id: ChainId, ts: BlockTimeStamp, ledger_type: &LedgerType, pid: &PurposeId, - validator: &Validator, hasher: &HashFunction, -) -> anyhow::Result> { - /// Genesis block MUST have 0 value - const BLOCK_HEIGHT: u32 = 0; - - let out: Vec = Vec::new(); - let mut encoder = minicbor::Encoder::new(out); - - encoder.bytes(&chain_id.0.to_bytes())?; - encoder.bytes(&BLOCK_HEIGHT.to_be_bytes())?; - encoder.bytes(&ts.0.to_be_bytes())?; - encoder.bytes(ledger_type.0.as_bytes())?; - encoder.bytes(&pid.0.to_bytes())?; - - // marks how many validators for decoding side. - encoder.bytes(&validator.0.len().to_be_bytes())?; - for validator in &validator.0 { - encoder.bytes(&validator.0)?; +impl BlockHeader { + /// Create new block + #[must_use] + #[allow(clippy::too_many_arguments)] + pub fn new( + chain_id: Ulid, height: i64, block_time_stamp: i64, previous_block_hash: Vec, + ledger_type: Uuid, purpose_id: Ulid, validator: Vec, metadata: Option>, + ) -> Self { + Self { + chain_id, + height, + block_time_stamp, + previous_block_hash, + ledger_type, + purpose_id, + validator, + metadata, + } } - // Get hash of the genesis_to_prev_hash bytes i.e hash of itself - let genesis_prev_bytes = encoder.writer().clone(); + /// Encode block header + /// ## Errors + /// + /// Returns an error encoding fails + pub fn to_bytes(&self, _hasher: &HashFunction) -> anyhow::Result> { + let out: Vec = Vec::new(); + let mut encoder = minicbor::Encoder::new(out); + + encoder.bytes(&self.chain_id.to_bytes())?; + encoder.bytes(&self.height.to_be_bytes())?; + encoder.bytes(&self.block_time_stamp.to_be_bytes())?; + encoder.bytes(self.previous_block_hash.as_slice())?; + encoder.bytes(self.ledger_type.as_bytes())?; + encoder.bytes(&self.purpose_id.to_bytes())?; + + // marks how many validators for decoding side. + encoder.bytes(&self.validator.len().to_be_bytes())?; + for validator in self.validator.clone() { + encoder.bytes(&validator.0)?; + } - // Size of encoded contents which is hashed - encoder.bytes(&genesis_prev_bytes.len().to_be_bytes())?; + if let Some(meta) = &self.metadata { + encoder.bytes(meta)?; + } - let genesis_prev_hash = match hasher { - HashFunction::Blake3 => blake3(&genesis_prev_bytes)?.to_vec(), - HashFunction::Blake2b => blake2b_512(&genesis_prev_bytes)?.to_vec(), - }; + Ok(encoder.writer().clone()) + } - // prev_block_id for the Genesis block MUST be a hash of the genesis_to_prev_hash bytes - // last 64 bytes (depending on given hash function) of encoding are the hash of the - // genesis contents - encoder.bytes(genesis_prev_hash.as_slice())?; + /// Decode block header + /// ## Errors + /// + /// Returns an error decoding fails + pub fn from_bytes( + block: &[u8], _hasher: &HashFunction, + ) -> anyhow::Result<( + BlockHeader, + BlockHeaderSize, + Option, + )> { + // Decode cbor to bytes + let mut cbor_decoder = minicbor::Decoder::new(block); + + // Raw chain_id + let chain_id = Ulid::from_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for chain id : {e}")))? + .try_into()?, + ); - Ok(encoder.writer().clone()) -} + // Raw Block height + let block_height = i64::from_be_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block height : {e}")))? + .try_into()?, + ); -/// Decode genesis -/// ## Errors -/// -/// Returns an error if block decoding for genesis fails. -pub fn decode_genesis_block(genesis_block: Vec) -> anyhow::Result { - let binding = genesis_block.clone(); - let mut cbor_decoder = minicbor::Decoder::new(&binding); - - // Raw chain_id - let chain_id = ChainId(Ulid::from_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for chain id : {e}")))? - .try_into()?, - )); + // Raw time stamp + let ts = i64::from_be_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for timestamp : {e}")))? + .try_into()?, + ); - // Raw Block height - let block_height = Height(u32::from_be_bytes( - cbor_decoder + // Raw prev block hash + let prev_block_hash = cbor_decoder .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block height : {e}")))? - .try_into()?, - )); + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for prev block hash : {e}")))? + .to_vec(); + + // Raw ledger type + let ledger_type = Uuid::from_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for ledger type : {e}")))? + .try_into()?, + ); - // Raw time stamp - let ts = BlockTimeStamp(i64::from_be_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for timestamp : {e}")))? - .try_into()?, - )); + // Raw purpose id + let purpose_id = Ulid::from_bytes( + cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for purpose id : {e}")))? + .try_into()?, + ); - // Raw ledger type - let ledger_type = LedgerType(Uuid::from_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for ledger type : {e}")))? - .try_into()?, - )); + // Number of validators + let number_of_validators = usize::from_be_bytes( + cbor_decoder + .bytes() + .map_err(|e| { + anyhow::anyhow!(format!("Invalid cbor for number of validators : {e}")) + })? + .try_into()?, + ); - // Raw purpose id - let purpose_id = PurposeId(Ulid::from_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for purpose id : {e}")))? - .try_into()?, - )); + // Extract validators + let mut validators = Vec::new(); + for _validator in 0..number_of_validators { + let validator_kid: [u8; 16] = cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for validators : {e}")))? + .try_into()?; - // Number of validators - let number_of_validators = usize::from_be_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for number of validators : {e}")))? - .try_into()?, - ); - - // Extract validators - let mut validators = Vec::new(); - for _validator in 0..number_of_validators { - let validator_kid: [u8; 16] = cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for validators : {e}")))? - .try_into()?; + validators.push(Kid(validator_kid)); + } - validators.push(Kid(validator_kid)); + let metadata = match cbor_decoder.bytes() { + ResultOk(meta) => Some(meta.to_vec()), + Err(_) => None, + }; + + let block_header = BlockHeader { + chain_id, + height: block_height, + block_time_stamp: ts, + previous_block_hash: prev_block_hash, + ledger_type, + purpose_id, + validator: validators, + metadata, + }; + + Ok((block_header, BlockHeaderSize(cbor_decoder.position()), None)) } +} - // Size of encoded contents - let encoded_content_size = usize::from_be_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for encoded contents size : {e}")))? - .try_into()?, - ); - - // prev_block_id for the Genesis block MUST be a hash of the genesis_to_prev_hash bytes - // last 64 bytes (depending on hash function) of encoding are the hash of the contents - let prev_block_hash = PreviousBlockHash( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for prev block hash : {e}")))? - .to_vec(), - ); - - let genesis_block_contents: Vec = genesis_block - .into_iter() - .take(encoded_content_size) - .collect(); - - Ok(( - chain_id, - block_height, - ts, - prev_block_hash, - ledger_type, - purpose_id, - Validator(validators), - BlockHeaderSize(cbor_decoder.position()), - EncodedGenesisBlockContents(genesis_block_contents), - )) +/// Genesis block previous identifier type i.e hash of itself +pub struct GenesisPreviousHash { + /// Unique identifier of the chain. + pub chain_id: Ulid, + /// Block epoch-based date/time. + pub block_time_stamp: i64, + /// unique identifier of the ledger type. + /// In general, this is the way to strictly bound and specify `block_data` of the + /// ledger for the specific `ledger_type`. + pub ledger_type: Uuid, + /// unique identifier of the purpose, each Ledger instance will have a strict time + /// boundaries, so each of them will run for different purposes. + pub purpose_id: Ulid, + /// Identifier or identifiers of the entity who was produced and processed a block. + pub validator: Vec, +} + +impl GenesisPreviousHash { + /// Create previous block id + #[must_use] + pub fn new( + chain_id: Ulid, block_time_stamp: i64, ledger_type: Uuid, purpose_id: Ulid, + validator: Vec, + ) -> Self { + Self { + chain_id, + block_time_stamp, + ledger_type, + purpose_id, + validator, + } + } + + /// Encode genesis previous hash to cbor + /// ## Errors + /// + /// Returns an error encoding fails + pub fn to_bytes(&self) -> anyhow::Result> { + let out: Vec = Vec::new(); + let mut encoder = minicbor::Encoder::new(out); + + encoder.bytes(&self.chain_id.to_bytes())?; + encoder.bytes(&self.block_time_stamp.to_be_bytes())?; + encoder.bytes(self.ledger_type.as_bytes())?; + encoder.bytes(&self.purpose_id.to_bytes())?; + + // marks how many validators for decoding side. + encoder.bytes(&self.validator.len().to_be_bytes())?; + for validator in self.validator.clone() { + encoder.bytes(&validator.0)?; + } + + Ok(encoder.writer().clone()) + } + + /// Generate hash of cbor encoded self + /// ## Errors + /// + /// Returns an error if hashing fails + pub fn hash(&self, hasher: &HashFunction) -> anyhow::Result> { + let encoding = self.to_bytes()?; + + // get hash of genesis_to_prev_hash + let genesis_prev_hash = match hasher { + HashFunction::Blake3 => blake3(&encoding)?.to_vec(), + HashFunction::Blake2b => blake2b_512(&encoding)?.to_vec(), + }; + + Ok(genesis_prev_hash) + } } #[cfg(test)] mod tests { + use ed25519_dalek::{SigningKey, SECRET_KEY_LENGTH}; use ulid::Ulid; use uuid::Uuid; - use super::{decode_genesis_block, encode_genesis}; - use crate::serialize::{ - blake2b_512, decode_block, decode_block_header, encode_block, encode_block_header, - BlockHeader, BlockTimeStamp, ChainId, EncodedBlockData, HashFunction::Blake2b, Height, Kid, - LedgerType, Metadata, PreviousBlockHash, PurposeId, Validator, ValidatorKeys, - }; + use super::{Block, BlockData, BlockHeader, Kid, ValidatorKeys}; + use crate::serialize::{blake2b_512, GenesisPreviousHash, HashFunction::Blake2b}; + #[test] - fn block_header_encode_decode() { + fn block_header_encoding() { let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") .unwrap() .try_into() @@ -540,41 +554,48 @@ mod tests { .try_into() .unwrap(); - let chain_id = ChainId(Ulid::new()); - let block_height = Height(5); - let block_ts = BlockTimeStamp(1_728_474_515); - let prev_block_height = PreviousBlockHash(vec![0; 64]); - let ledger_type = LedgerType(Uuid::new_v4()); - let purpose_id = PurposeId(Ulid::new()); - let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); - let metadata = Some(Metadata(vec![1; 128])); + let block_hdr = BlockHeader::new( + Ulid::new(), + 5, + 1_728_474_515, + vec![0; 64], + Uuid::new_v4(), + Ulid::new(), + vec![Kid(kid_a), Kid(kid_b)], + Some(vec![1; 128]), + ); + + let encoded_block_hdr = block_hdr.to_bytes(&Blake2b).unwrap(); - let encoded_block_hdr = encode_block_header( - chain_id, - block_height, - block_ts, - &prev_block_height.clone(), - &ledger_type.clone(), - &purpose_id.clone(), - &validators.clone(), - metadata.clone(), - ) - .unwrap(); - - let decoded_hdr = decode_block_header(&encoded_block_hdr).unwrap(); - assert_eq!(decoded_hdr.0, chain_id); - assert_eq!(decoded_hdr.1, block_height); - assert_eq!(decoded_hdr.2, block_ts); - assert_eq!(decoded_hdr.3, prev_block_height); - assert_eq!(decoded_hdr.4, ledger_type); - assert_eq!(decoded_hdr.5, purpose_id); - assert_eq!(decoded_hdr.6, validators); - assert_eq!(decoded_hdr.7, metadata); + let (block_hdr_from_bytes, ..) = + BlockHeader::from_bytes(&encoded_block_hdr, &Blake2b).unwrap(); + assert_eq!(block_hdr_from_bytes.chain_id, block_hdr.chain_id); + assert_eq!(block_hdr_from_bytes.height, block_hdr.height); + assert_eq!( + block_hdr_from_bytes.block_time_stamp, + block_hdr.block_time_stamp + ); + assert_eq!( + block_hdr_from_bytes.previous_block_hash, + block_hdr.previous_block_hash + ); + assert_eq!(block_hdr_from_bytes.ledger_type, block_hdr.ledger_type); + assert_eq!(block_hdr_from_bytes.purpose_id, block_hdr.purpose_id); + assert_eq!(block_hdr_from_bytes.validator, block_hdr.validator); + assert_eq!( + block_hdr_from_bytes.metadata, + Some(block_hdr.metadata.unwrap()) + ); } #[test] - #[allow(clippy::zero_prefixed_literal)] - fn block_encode_decode() { + fn block_encoding() { + // validators + let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, + 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, + ]; + let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") .unwrap() .try_into() @@ -585,44 +606,17 @@ mod tests { .try_into() .unwrap(); - let chain_id = ChainId(Ulid::new()); - let block_height = Height(5); - let block_ts = BlockTimeStamp(1_728_474_515); - let prev_block_height = PreviousBlockHash(vec![0; 64]); - let ledger_type = LedgerType(Uuid::new_v4()); - let purpose_id = PurposeId(Ulid::new()); - let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); - let metadata = Some(Metadata(vec![1; 128])); - - let encoded_block_hdr = encode_block_header( - chain_id, - block_height, - block_ts, - &prev_block_height.clone(), - &ledger_type.clone(), - &purpose_id.clone(), - &validators.clone(), - metadata.clone(), - ) - .unwrap(); - - let block_hdr = BlockHeader( - chain_id, - block_height, - block_ts, - prev_block_height.clone(), - ledger_type.clone(), - purpose_id.clone(), - validators.clone(), - metadata.clone(), + let block_hdr = BlockHeader::new( + Ulid::new(), + 5, + 1_728_474_515, + vec![0; 64], + Uuid::new_v4(), + Ulid::new(), + vec![Kid(kid_a), Kid(kid_b)], + Some(vec![1; 128]), ); - // validators - let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, - 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, - ]; - let out: Vec = Vec::new(); let mut block_data = minicbor::Encoder::new(out); @@ -637,42 +631,55 @@ mod tests { block_data.bytes(block_data_bytes).unwrap(); let encoded_block_data = block_data.writer().clone(); - let encoded_block = encode_block( - block_hdr, - &EncodedBlockData(encoded_block_data.clone()), - &ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), - &Blake2b, - ) - .unwrap(); - - let decoded = decode_block(&encoded_block).unwrap(); - assert_eq!(decoded.0 .0, chain_id); - assert_eq!(decoded.0 .1, block_height); - assert_eq!(decoded.0 .2, block_ts); - assert_eq!(decoded.0 .3, prev_block_height); - assert_eq!(decoded.0 .4, ledger_type); - assert_eq!(decoded.0 .5, purpose_id); - assert_eq!(decoded.0 .6, validators); - assert_eq!(decoded.0 .7, metadata); - - assert_eq!(decoded.1 .0, encoded_block_data); + let block = Block::new( + block_hdr.clone(), + BlockData(encoded_block_data.clone()), + ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), + Blake2b, + ); + + let encoded_block = block.to_bytes().unwrap(); + + let decoded_block = Block::from_bytes(&encoded_block.clone(), &Blake2b).unwrap(); + assert_eq!(decoded_block.0, block_hdr); let data_to_sign = [ - blake2b_512(&encoded_block_hdr).unwrap().to_vec(), + blake2b_512(&block_hdr.to_bytes(&Blake2b).unwrap()) + .unwrap() + .to_vec(), encoded_block_data.clone(), ] .concat(); let verifying_key = SigningKey::from_bytes(&validator_secret_key_bytes); - for sig in decoded.2 .0 { + for sig in decoded_block.2 .0 { verifying_key.verify_strict(&data_to_sign, &sig).unwrap(); } + + // ENCODING SHOULD FAIL with block data that is NOT cbor encoded + let block = Block::new( + block_hdr.clone(), + BlockData(block_data_bytes.to_vec()), + ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), + Blake2b, + ); + + assert!(block.to_bytes().is_err()); } #[test] - #[allow(clippy::zero_prefixed_literal, clippy::indexing_slicing)] - fn genesis_block_encode_decode() { + #[allow(clippy::zero_prefixed_literal)] + fn validate_block_test() { + // PREVIOUS BLOCK + // + // + // validators + let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, + 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, + ]; + let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") .unwrap() .try_into() @@ -683,37 +690,160 @@ mod tests { .try_into() .unwrap(); - let chain_id = ChainId(Ulid::new()); - let block_ts = BlockTimeStamp(0); - let ledger_type = LedgerType(Uuid::new_v4()); - let purpose_id = PurposeId(Ulid::new()); - let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); + let chain_id = Ulid::new(); + let ledger_type = Uuid::new_v4(); + let purpose_id = Ulid::new(); - let encoded_block_genesis = encode_genesis( + let block_hdr = BlockHeader::new( chain_id, - block_ts, - &ledger_type.clone(), - &purpose_id.clone(), - &validators.clone(), - &Blake2b, - ) - .unwrap(); - - let decoded_genesis = decode_genesis_block(encoded_block_genesis.clone()).unwrap(); - assert_eq!(decoded_genesis.0, chain_id); - assert_eq!(decoded_genesis.1, Height(0)); - assert_eq!(decoded_genesis.2, block_ts); - assert_eq!(decoded_genesis.4, ledger_type); - assert_eq!(decoded_genesis.5, purpose_id); - assert_eq!(decoded_genesis.6, validators); - - // prev_block_id for the Genesis block MUST be a hash of the genesis_to_prev_hash bytes - let prev_block_hash = decoded_genesis.3 .0; - - // last 64 bytes of encoding are the hash of the contents - let prev_block_from_original_encoding = - &encoded_block_genesis[encoded_block_genesis.len() - 64..]; - - assert_eq!(prev_block_hash, prev_block_from_original_encoding); + 5, + 1_728_474_515, + vec![0; 64], + ledger_type, + purpose_id, + vec![Kid(kid_a), Kid(kid_b)], + Some(vec![1; 128]), + ); + + let out: Vec = Vec::new(); + let mut block_data = minicbor::Encoder::new(out); + + let block_data_bytes = &[ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, + 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, + 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, + 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, + 157, + ]; + + block_data.bytes(block_data_bytes).unwrap(); + let encoded_block_data = block_data.writer().clone(); + + let previous_block = Block::new( + block_hdr.clone(), + BlockData(encoded_block_data.clone()), + ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), + Blake2b, + ); + + // CURRENT BLOCK + + let prev_block_hash = blake2b_512(&previous_block.to_bytes().unwrap()).unwrap(); + + let block_hdr = BlockHeader::new( + chain_id, + 6, + 1_728_474_516, + prev_block_hash.to_vec(), + ledger_type, + purpose_id, + vec![Kid(kid_a), Kid(kid_b)], + Some(vec![1; 128]), + ); + + let out: Vec = Vec::new(); + let mut block_data = minicbor::Encoder::new(out); + + block_data.bytes(block_data_bytes).unwrap(); + let encoded_block_data = block_data.writer().clone(); + + let current_block = Block::new( + block_hdr.clone(), + BlockData(encoded_block_data.clone()), + ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), + Blake2b, + ); + + assert!(current_block.validate(Some(previous_block)).is_ok()); + } + + #[test] + fn genesis_encoding_and_validation() { + // validators + let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, + 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, + ]; + + let chain_id = Ulid::new(); + let ledger_type = Uuid::new_v4(); + let purpose_id = Ulid::new(); + let block_time_stamp = 1_728_474_515; + + let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") + .unwrap() + .try_into() + .unwrap(); + + let validator = vec![Kid(kid_a), Kid(kid_b)]; + + let genesis_to_prev_hash = GenesisPreviousHash::new( + chain_id, + block_time_stamp, + ledger_type, + purpose_id, + validator.clone(), + ); + + let block_hdr = BlockHeader::new( + chain_id, + 0, + block_time_stamp, + genesis_to_prev_hash.hash(&Blake2b).unwrap(), + ledger_type, + purpose_id, + validator.clone(), + Some(vec![1; 128]), + ); + + let out: Vec = Vec::new(); + let mut block_data = minicbor::Encoder::new(out); + + let block_data_bytes = &[ + 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, + 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, + 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, + 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, + 157, + ]; + + block_data.bytes(block_data_bytes).unwrap(); + let encoded_block_data = block_data.writer().clone(); + + let block = Block::new( + block_hdr.clone(), + BlockData(encoded_block_data.clone()), + ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), + Blake2b, + ); + + assert!(block.validate(None).is_ok()); + + // SHOULD FAIL as previous block hash for genesis is invalid, it should be a hash of + // itself like above. + let block_hdr = BlockHeader::new( + chain_id, + 0, + block_time_stamp, + vec![1; 128], + ledger_type, + purpose_id, + validator, + Some(vec![1; 128]), + ); + + let block = Block::new( + block_hdr.clone(), + BlockData(encoded_block_data.clone()), + ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), + Blake2b, + ); + + assert!(block.validate(None).is_err()); } } diff --git a/rust/immutable-ledger/src/validate.rs b/rust/immutable-ledger/src/validate.rs deleted file mode 100644 index cf40c97c02..0000000000 --- a/rust/immutable-ledger/src/validate.rs +++ /dev/null @@ -1,279 +0,0 @@ -//! Block validation -//! -//! Facilitates validation for immutable ledger - -use anyhow::Ok; - -use crate::serialize::{ - blake2b_512, blake3, decode_block, decode_genesis_block, EncodedBlock, EncodedGenesisBlock, - HashFunction, -}; - -/// Validate current block against previous block. -/// ## Errors -/// -/// Returns an error if block validation fails. -pub fn block_validation( - current_block: &EncodedBlock, previous_block: &EncodedBlock, hasher: &HashFunction, -) -> anyhow::Result<()> { - let current_block = decode_block(current_block)?; - - let hashed_previous_block = match hasher { - HashFunction::Blake3 => blake3(previous_block)?.to_vec(), - HashFunction::Blake2b => blake2b_512(previous_block)?.to_vec(), - }; - let previous_block = decode_block(previous_block)?; - - // chain_id MUST be the same as for the previous block (except for genesis). - if current_block.0 .0 != previous_block.0 .0 { - return Err(anyhow::anyhow!( - "Module: Immutable ledger, Message: Chain_id MUST be NOT the same as for the previous block {:?} {:?}", - current_block.0 .0, - previous_block.0 .0 - )); - }; - - // height MUST be incremented by 1 from the previous block height value (except for - // genesis and final block). Genesis block MUST have 0 value. Final block MUST hash be - // incremented by 1 from the previous block height and changed the sign to negative. - // E.g. previous block height is 9 and the Final block height is -10. - if current_block.0 .1 .0 != previous_block.0 .1 .0 + 1 { - return Err(anyhow::anyhow!( - "Module: Immutable ledger, Message: height validation failed: {:?} {:?}", - current_block.0 .1 .0, - previous_block.0 .1 .0 - )); - } - - // timestamp MUST be greater or equals than the timestamp of the previous block (except - // for genesis) - if current_block.0 .2 .0 <= previous_block.0 .2 .0 { - return Err(anyhow::anyhow!( - "Module: Immutable ledger, Message: timestamp validation failed: {:?} {:?}", - current_block.0 .2 .0, - previous_block.0 .2 .0 - )); - } - - // prev_block_id MUST be a hash of the previous block bytes (except for genesis). - if current_block.0 .3 .0 != hashed_previous_block { - return Err(anyhow::anyhow!( - "Module: Immutable ledger, Message: previous hash validation failed: {:?} {:?}", - current_block.0 .3 .0, - previous_block.0 .3 .0 - )); - } - - // ledger_type MUST be the same as for the previous block if present (except for genesis). - if current_block.0 .4 .0 != previous_block.0 .4 .0 { - return Err(anyhow::anyhow!( - "Module: Immutable ledger, Message: ledger type validation failed: {:?} {:?}", - current_block.0 .4 .0, - previous_block.0 .4 .0 - )); - } - - // purpose_id MUST be the same as for the previous block if present (except for genesis). - if current_block.0 .5 .0 != previous_block.0 .5 .0 { - return Err(anyhow::anyhow!( - "Module: Immutable ledger, Message: purpose id validation failed: {:?} {:?}", - current_block.0 .5 .0, - previous_block.0 .5 .0 - )); - } - - // validator MUST be the same as for the previous block if present (except for genesis) - if current_block.0 .6 .0 != previous_block.0 .6 .0 { - return Err(anyhow::anyhow!( - "Module: Immutable ledger, Message: validator validation failed: {:?} {:?}", - current_block.0 .6 .0, - previous_block.0 .6 .0 - )); - } - - Ok(()) -} - -/// Validate genesis block -/// ## Errors -/// -/// Genesis validation -pub fn genesis_validation( - genesis: &EncodedGenesisBlock, hasher: &HashFunction, -) -> anyhow::Result<()> { - /// Genesis block MUST have 0 value - const BLOCK_HEIGHT: u32 = 0; - - let genesis_block = decode_genesis_block(genesis.clone())?; - - // Genesis block MUST have 0 value - if genesis_block.1 .0 != BLOCK_HEIGHT { - return Err(anyhow::anyhow!( - "Module: Immutable ledger, Message: Validate genesis failed {:?}", - genesis_block.1 - )); - }; - - // last N bytes of encoding are the hash of the contents - let genesis_block_contents = genesis_block.8 .0; - - let hashed_contents = match hasher { - HashFunction::Blake3 => blake3(&genesis_block_contents)?.to_vec(), - HashFunction::Blake2b => blake2b_512(&genesis_block_contents)?.to_vec(), - }; - - if genesis_block.3 .0 != hashed_contents { - return Err(anyhow::anyhow!( - "Module: Immutable ledger, Message: Validate genesis failed {:?}", - genesis_block.3 - )); - }; - - Ok(()) -} - -#[cfg(test)] -mod tests { - - use ed25519_dalek::SECRET_KEY_LENGTH; - use ulid::Ulid; - use uuid::Uuid; - - use super::{block_validation, genesis_validation}; - use crate::serialize::{ - blake2b_512, encode_block, encode_genesis, BlockHeader, BlockTimeStamp, ChainId, - EncodedBlockData, HashFunction::Blake2b, Height, Kid, LedgerType, Metadata, - PreviousBlockHash, PurposeId, Validator, ValidatorKeys, - }; - - #[test] - #[allow(clippy::zero_prefixed_literal)] - fn validate_block_test() { - // PREVIOUS BLOCK - // - // - let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - - let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - - let chain_id = ChainId(Ulid::new()); - let block_height = Height(5); - let block_ts = BlockTimeStamp(1_728_474_515); - let prev_block_hash = PreviousBlockHash(vec![0; 64]); - let ledger_type = LedgerType(Uuid::new_v4()); - let purpose_id = PurposeId(Ulid::new()); - let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); - let metadata = Some(Metadata(vec![1; 128])); - - let block_hdr = BlockHeader( - chain_id, - block_height, - block_ts, - prev_block_hash.clone(), - ledger_type.clone(), - purpose_id.clone(), - validators.clone(), - metadata.clone(), - ); - - // validators - let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, - 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, - ]; - - let out: Vec = Vec::new(); - let mut block_data = minicbor::Encoder::new(out); - - let block_data_bytes = &[ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, - 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, - 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, - 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, - 157, - ]; - - block_data.bytes(block_data_bytes).unwrap(); - let encoded_block_data = block_data.writer().clone(); - - let previous_block = encode_block( - block_hdr, - &EncodedBlockData(encoded_block_data.clone()), - &ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), - &Blake2b, - ) - .unwrap(); - - // CURRENT BLOCK - // - - let block_height = Height(6); - let block_ts = BlockTimeStamp(1_728_474_518); - let prev_block_hash = PreviousBlockHash(blake2b_512(&previous_block).unwrap().to_vec()); - let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); - let metadata = Some(Metadata(vec![1; 128])); - - let block_hdr = BlockHeader( - chain_id, - block_height, - block_ts, - prev_block_hash.clone(), - ledger_type.clone(), - purpose_id.clone(), - validators.clone(), - metadata.clone(), - ); - - block_data.bytes(block_data_bytes).unwrap(); - - let current_block = encode_block( - block_hdr, - &EncodedBlockData(encoded_block_data.clone()), - &ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), - &Blake2b, - ) - .unwrap(); - - // VALIDATE BLOCK - // - - assert!(block_validation(¤t_block, &previous_block, &Blake2b).is_ok()); - } - - #[test] - fn validate_genesis_test() { - let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - - let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - - let chain_id = ChainId(Ulid::new()); - let block_ts = BlockTimeStamp(0); - let ledger_type = LedgerType(Uuid::new_v4()); - let purpose_id = PurposeId(Ulid::new()); - let validators = Validator(vec![Kid(kid_a), Kid(kid_b)]); - - let encoded_block_genesis = encode_genesis( - chain_id, - block_ts, - &ledger_type.clone(), - &purpose_id.clone(), - &validators.clone(), - &Blake2b, - ) - .unwrap(); - - assert!(genesis_validation(&encoded_block_genesis, &Blake2b).is_ok()); - } -} From 6474854052bd4dc0f449935a23ea9523b4c48df6 Mon Sep 17 00:00:00 2001 From: cong-or Date: Sun, 3 Nov 2024 20:23:39 +0000 Subject: [PATCH 40/53] refactor(add block types and cddl tests): verifcation and refactor --- rust/immutable-ledger/src/lib.rs | 3 - rust/immutable-ledger/src/ser.rs | 917 ------------------------- rust/immutable-ledger/src/serialize.rs | 222 +++--- 3 files changed, 145 insertions(+), 997 deletions(-) delete mode 100644 rust/immutable-ledger/src/ser.rs diff --git a/rust/immutable-ledger/src/lib.rs b/rust/immutable-ledger/src/lib.rs index 3c11fab089..0bae4b2ce8 100644 --- a/rust/immutable-ledger/src/lib.rs +++ b/rust/immutable-ledger/src/lib.rs @@ -6,6 +6,3 @@ /// Block encoding decoding and validation pub mod serialize; - -/// ac -pub mod ser; diff --git a/rust/immutable-ledger/src/ser.rs b/rust/immutable-ledger/src/ser.rs deleted file mode 100644 index e8d0b325ed..0000000000 --- a/rust/immutable-ledger/src/ser.rs +++ /dev/null @@ -1,917 +0,0 @@ -//! Block structure - -//! Block structure - -use anyhow::Ok; -use blake2b_simd::{self, Params}; -use ed25519_dalek::{ - ed25519::signature::SignerMut, Signature, SigningKey, SECRET_KEY_LENGTH, SIGNATURE_LENGTH, -}; -use ulid::Ulid; -use uuid::Uuid; - -/// Genesis block MUST have 0 value height. -const GENESIS_BLOCK: i64 = 0; - -/// Block header size -#[derive(Debug, Clone, PartialEq)] -pub struct BlockHeaderSize(usize); - -/// Decoded block header -pub type DecodedBlockHeader = BlockHeader; - -/// Signatures -#[derive(Debug, Clone, PartialEq)] -pub struct Signatures(Vec); - -/// Decoded block -pub type DecodedBlock = (DecodedBlockHeader, BlockData, Signatures); - -/// Encoded genesis Block contents as cbor, used for hash validation -#[derive(Debug, Clone, PartialEq)] -pub struct EncodedGenesisBlockContents(pub Vec); - -/// Choice of hash function: -/// must be the same as the hash of the previous block. -pub enum HashFunction { - /// BLAKE3 is based on an optimized instance of the established hash function BLAKE2 - /// and on the original Bao tree mode - Blake3, - /// BLAKE2b-512 produces digest side of 512 bits. - Blake2b, -} - -/// Kid (The key identifier) size in bytes -const KID_BYTES: usize = 16; - -/// Key ID - Blake2b-128 hash of the Role 0 Certificate defining the Session public key. -/// BLAKE2b-128 produces digest side of 16 bytes. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Kid(pub [u8; KID_BYTES]); - -/// Encoded whole block including block header, cbor encoded block data and signatures. -pub type EncodedBlock = Vec; - -/// Produce BLAKE3 hash -pub(crate) fn blake3(value: &[u8]) -> anyhow::Result<[u8; 32]> { - Ok(*blake3::hash(value).as_bytes()) -} - -/// BLAKE2b-512 produces digest side of 512 bits. -pub(crate) fn blake2b_512(value: &[u8]) -> anyhow::Result<[u8; 64]> { - let h = Params::new().hash_length(64).hash(value); - let b = h.as_bytes(); - b.try_into() - .map_err(|_| anyhow::anyhow!("Invalid length of blake2b_512, expected 64 got {}", b.len())) -} - -/// Block data -#[derive(Debug, Clone, PartialEq)] -pub struct BlockData(Vec); - -/// Validator's keys defined in the corresponding certificates referenced by the -/// validator. -pub struct ValidatorKeys(pub Vec<[u8; SECRET_KEY_LENGTH]>); - -/// CBOR tag for timestamp -const TIMESTAMP_CBOR_TAG: u64 = 1; - -/// CBOR tag for UUID -const UUID_CBOR_TAG: u64 = 37; - -/// CBOR tag for UUID -const ULID_CBOR_TAG: u64 = 32780; - -/// CBOR tags for BLAKE2 [2] and BLAKE3 [3] hash functions -/// `https://github.com/input-output-hk/catalyst-voices/blob/main/docs/src/catalyst-standards/cbor_tags/blake.md` - -/// CBOR tag for UUID -const BLAKE3_CBOR_TAG: u64 = 32781; - -/// CBOR tag for blake2b -const BLAKE_2B_CBOR_TAG: u64 = 32782; - -/// Block -pub struct Block { - /// Block header - pub block_header: BlockHeader, - /// cbor encoded block data - pub block_data: BlockData, - /// Validators - pub validator_keys: ValidatorKeys, - /// Hash function - pub hasher: HashFunction, -} - -impl Block { - /// New block - #[must_use] - pub fn new( - block_header: BlockHeader, block_data: BlockData, validator_keys: ValidatorKeys, - hasher: HashFunction, - ) -> Self { - Self { - block_header, - block_data, - validator_keys, - hasher, - } - } - - /// Encode block - /// ## Errors - /// - /// Returns an error if encoding fails. - pub fn to_bytes(&self) -> anyhow::Result { - // Enforce block data to be cbor encoded in the form of CBOR byte strings - // which are just (ordered) series of bytes without further interpretation - let _ = minicbor::Decoder::new(&self.block_data.0).bytes()?; - - // cbor encode block hdr - let encoded_block_hdr = self.block_header.to_bytes(&self.hasher)?; - - let hashed_block_header = match self.hasher { - HashFunction::Blake3 => blake3(&encoded_block_hdr)?.to_vec(), - HashFunction::Blake2b => blake2b_512(&encoded_block_hdr)?.to_vec(), - }; - - // validator_signature MUST be a signature of the hashed block_header bytes - // and the block_data bytes - let data_to_sign = [hashed_block_header, self.block_data.0.clone()].concat(); - - // if validator is only one id => validator_signature contains only 1 signature; - // if validator is array => validator_signature contains an array with the same length; - let signatures: Vec<[u8; 64]> = self - .validator_keys - .0 - .iter() - .map(|sk| { - let mut sk: SigningKey = SigningKey::from_bytes(sk); - sk.sign(&data_to_sign).to_bytes() - }) - .collect(); - - let out: Vec = Vec::new(); - let mut encoder = minicbor::Encoder::new(out); - encoder.array(signatures.len().try_into()?)?; - for sig in signatures { - encoder.bytes(&sig)?; - } - - Ok([ - [encoded_block_hdr, self.block_data.0.clone()].concat(), - encoder.writer().to_vec(), - ] - .concat()) - } - - /// Decode block - /// ## Errors - /// - /// Returns an error if decoding fails. - pub fn from_bytes(encoded_block: &[u8], hasher: &HashFunction) -> anyhow::Result { - // Decoded block hdr - let (block_hdr, block_hdr_size, _) = BlockHeader::from_bytes(encoded_block, hasher)?; - - // Init decoder - let mut cbor_decoder = minicbor::Decoder::new(encoded_block); - - // Decode remaining block, set position after block hdr data. - cbor_decoder.set_position(block_hdr_size.0); - - // Block data - let block_data = cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block data : {e}")))?; - - // Extract signatures - let number_of_sigs = cbor_decoder - .array()? - .ok_or(anyhow::anyhow!(format!("Invalid signature.")))?; - - let mut sigs = Vec::new(); - for _sig in 0..number_of_sigs { - let sig: [u8; SIGNATURE_LENGTH] = cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for signature : {e}")))? - .try_into()?; - - sigs.push(Signature::from_bytes(&sig)); - } - - Ok((block_hdr, BlockData(block_data.to_vec()), Signatures(sigs))) - } - - /// Validate block against previous block or validate itself if genesis block. - /// ## Errors - /// - /// Returns an error if validation fails. - pub fn validate(&self, previous_block: Option) -> anyhow::Result<()> { - if let Some(previous_block) = previous_block { - // Standard block - let hashed_previous_block = match self.hasher { - HashFunction::Blake3 => blake3(&previous_block.to_bytes()?)?.to_vec(), - HashFunction::Blake2b => blake2b_512(&previous_block.to_bytes()?)?.to_vec(), - }; - - // chain_id MUST be the same as for the previous block (except for genesis). - if self.block_header.chain_id != previous_block.block_header.chain_id { - return Err(anyhow::anyhow!( - "Module: Immutable ledger, Message: Chain_id MUST be the same as for the previous block {:?} {:?}", - self.block_header, - previous_block.block_header - )); - }; - - // height MUST be incremented by 1 from the previous block height value (except for - // genesis and final block). Genesis block MUST have 0 value. Final block MUST hash be - // incremented by 1 from the previous block height and changed the sign to negative. - // E.g. previous block height is 9 and the Final block height is -10. - if self.block_header.height != previous_block.block_header.height + 1 { - return Err(anyhow::anyhow!( - "Module: Immutable ledger, Message: height validation failed: {:?} {:?}", - self.block_header, - previous_block.block_header - )); - } - - // timestamp MUST be greater or equals than the timestamp of the previous block (except - // for genesis) - if self.block_header.block_time_stamp <= previous_block.block_header.block_time_stamp { - return Err(anyhow::anyhow!( - "Module: Immutable ledger, Message: timestamp validation failed: {:?} {:?}", - self.block_header, - previous_block.block_header - )); - } - - // prev_block_id MUST be a hash of the previous block bytes (except for genesis). - if self.block_header.previous_block_hash != hashed_previous_block { - return Err(anyhow::anyhow!( - "Module: Immutable ledger, Message: previous hash validation failed: {:?} {:?}", - self.block_header, - previous_block.block_header - )); - } - - // ledger_type MUST be the same as for the previous block if present (except for - // genesis). - if self.block_header.ledger_type != previous_block.block_header.ledger_type { - return Err(anyhow::anyhow!( - "Module: Immutable ledger, Message: ledger type validation failed: {:?} {:?}", - self.block_header, - previous_block.block_header - )); - } - - // purpose_id MUST be the same as for the previous block if present (except for - // genesis). - if self.block_header.purpose_id != previous_block.block_header.purpose_id { - return Err(anyhow::anyhow!( - "Module: Immutable ledger, Message: purpose id validation failed: {:?} {:?}", - self.block_header, - previous_block.block_header - )); - } - - // validator MUST be the same as for the previous block if present (except for genesis) - if self.block_header.validator != previous_block.block_header.validator { - return Err(anyhow::anyhow!( - "Module: Immutable ledger, Message: validator validation failed: {:?} {:?}", - self.block_header, - previous_block.block_header - )); - } - } else if self.block_header.height == GENESIS_BLOCK { - // Validate genesis block - { - let genesis_to_prev_hash = GenesisPreviousHash::new( - self.block_header.chain_id, - self.block_header.block_time_stamp, - self.block_header.ledger_type, - self.block_header.purpose_id, - self.block_header.validator.clone(), - ) - .hash(&self.hasher)?; - - if self.block_header.previous_block_hash != genesis_to_prev_hash { - return Err(anyhow::anyhow!( - "Module: Immutable ledger, Message: Genesis block prev hash is invalid {:?}", - self.block_header, - )); - } - } - } - - Ok(()) - } -} - -/// Block header -#[derive(Debug, Clone, PartialEq)] -pub struct BlockHeader { - /// Unique identifier of the chain. - pub chain_id: Ulid, - /// Block height. - pub height: i64, - /// Block epoch-based date/time. - pub block_time_stamp: i64, - /// Previous Block hash. - pub previous_block_hash: Vec, - /// unique identifier of the ledger type. - /// In general, this is the way to strictly bound and specify `block_data` of the - /// ledger for the specific `ledger_type`. - pub ledger_type: Uuid, - /// unique identifier of the purpose, each Ledger instance will have a strict time - /// boundaries, so each of them will run for different purposes. - pub purpose_id: Ulid, - /// Identifier or identifiers of the entity who was produced and processed a block. - pub validator: Vec, - /// Add arbitrary metadata to the block. - pub metadata: Vec, -} - -impl BlockHeader { - /// Create new block - #[must_use] - #[allow(clippy::too_many_arguments)] - pub fn new( - chain_id: Ulid, height: i64, block_time_stamp: i64, previous_block_hash: Vec, - ledger_type: Uuid, purpose_id: Ulid, validator: Vec, metadata: Vec, - ) -> Self { - Self { - chain_id, - height, - block_time_stamp, - previous_block_hash, - ledger_type, - purpose_id, - validator, - metadata, - } - } - - /// Encode block header - /// ## Errors - /// - /// Returns an error encoding fails - pub fn to_bytes(&self, hasher: &HashFunction) -> anyhow::Result> { - /// # of elements in block header - const BLOCK_HEADER_SIZE: u64 = 8; - - let out: Vec = Vec::new(); - let mut encoder = minicbor::Encoder::new(out); - - encoder.array(BLOCK_HEADER_SIZE)?; - - // Chain id - encoder.tag(minicbor::data::Tag::new(ULID_CBOR_TAG))?; - encoder.bytes(&self.chain_id.to_bytes())?; - - // Block height - encoder.int(self.height.into())?; - - // Block timestamp - encoder.tag(minicbor::data::Tag::new(TIMESTAMP_CBOR_TAG))?; - encoder.int(self.block_time_stamp.into())?; - - let cbor_hash_tag = match hasher { - HashFunction::Blake3 => BLAKE3_CBOR_TAG, - HashFunction::Blake2b => BLAKE_2B_CBOR_TAG, - }; - - // Prev block hash - encoder.tag(minicbor::data::Tag::new(cbor_hash_tag))?; - encoder.bytes(&self.previous_block_hash)?; - - // Ledger type - encoder.tag(minicbor::data::Tag::new(UUID_CBOR_TAG))?; - encoder.bytes(self.ledger_type.as_bytes())?; - - // Purpose id - encoder.tag(minicbor::data::Tag::new(ULID_CBOR_TAG))?; - encoder.bytes(&self.purpose_id.to_bytes())?; - - // Validators - encoder.array(self.validator.len().try_into()?)?; - for val in self.validator.clone() { - encoder.tag(minicbor::data::Tag::new(cbor_hash_tag))?; - encoder.bytes(&val.0)?; - } - - // Metadata - encoder.bytes(&self.metadata)?; - - Ok(encoder.writer().clone()) - } - - /// Decode block header - /// ## Errors - /// - /// Returns an error decoding fails - pub fn from_bytes( - block: &[u8], _hasher: &HashFunction, - ) -> anyhow::Result<( - BlockHeader, - BlockHeaderSize, - Option, - )> { - // Decode cbor to bytes - let mut cbor_decoder = minicbor::Decoder::new(block); - cbor_decoder.array()?; - - // Raw chain_id - cbor_decoder.tag()?; - let chain_id = Ulid::from_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for chain id : {e}")))? - .try_into()?, - ); - - // Raw Block height - let block_height: i64 = cbor_decoder.int()?.try_into()?; - - // Raw time stamp - cbor_decoder.tag()?; - let ts: i64 = cbor_decoder.int()?.try_into()?; - - // Raw prev block hash - cbor_decoder.tag()?; - let prev_block_hash = cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for prev block hash : {e}")))? - .to_vec(); - - // Raw ledger type - cbor_decoder.tag()?; - let ledger_type = Uuid::from_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for ledger type : {e}")))? - .try_into()?, - ); - - // Raw purpose id - cbor_decoder.tag()?; - let purpose_id = Ulid::from_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for purpose id : {e}")))? - .try_into()?, - ); - - // Validators - let mut validators = Vec::new(); - let number_of_validators = cbor_decoder.array()?.ok_or(anyhow::anyhow!(format!( - "Invalid amount of validators, should be at least two" - )))?; - - for _validator in 0..number_of_validators { - cbor_decoder.tag()?; - let validator_kid: [u8; 16] = cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for validators : {e}")))? - .try_into()?; - - validators.push(Kid(validator_kid)); - } - - let metadata = cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for metadata : {e}")))? - .try_into()?; - - let block_header = BlockHeader { - chain_id, - height: block_height, - block_time_stamp: ts, - previous_block_hash: prev_block_hash, - ledger_type, - purpose_id, - validator: validators, - metadata, - }; - - Ok((block_header, BlockHeaderSize(cbor_decoder.position()), None)) - } -} - -/// Genesis block previous identifier type i.e hash of itself -pub struct GenesisPreviousHash { - /// Unique identifier of the chain. - pub chain_id: Ulid, - /// Block epoch-based date/time. - pub block_time_stamp: i64, - /// unique identifier of the ledger type. - /// In general, this is the way to strictly bound and specify `block_data` of the - /// ledger for the specific `ledger_type`. - pub ledger_type: Uuid, - /// unique identifier of the purpose, each Ledger instance will have a strict time - /// boundaries, so each of them will run for different purposes. - pub purpose_id: Ulid, - /// Identifier or identifiers of the entity who was produced and processed a block. - pub validator: Vec, -} - -impl GenesisPreviousHash { - /// Create previous block id - #[must_use] - pub fn new( - chain_id: Ulid, block_time_stamp: i64, ledger_type: Uuid, purpose_id: Ulid, - validator: Vec, - ) -> Self { - Self { - chain_id, - block_time_stamp, - ledger_type, - purpose_id, - validator, - } - } - - /// Encode genesis previous hash to cbor - /// ## Errors - /// - /// Returns an error encoding fails - pub fn to_bytes(&self, hasher: &HashFunction) -> anyhow::Result> { - // # of elements in Genesis to previous block hash - // const GENESIS_TO_PREV_HASH_SIZE: u64 = 5; - let out: Vec = Vec::new(); - let mut encoder = minicbor::Encoder::new(out); - encoder.array(5)?; - - // Chain id - encoder.tag(minicbor::data::Tag::new(ULID_CBOR_TAG))?; - encoder.bytes(&self.chain_id.to_bytes())?; - - // Block timestamp - encoder.tag(minicbor::data::Tag::new(TIMESTAMP_CBOR_TAG))?; - encoder.int(self.block_time_stamp.into())?; - - let cbor_hash_tag = match hasher { - HashFunction::Blake3 => BLAKE3_CBOR_TAG, - HashFunction::Blake2b => BLAKE_2B_CBOR_TAG, - }; - - // Ledger type - encoder.tag(minicbor::data::Tag::new(UUID_CBOR_TAG))?; - encoder.bytes(self.ledger_type.as_bytes())?; - - // Purpose id - encoder.tag(minicbor::data::Tag::new(ULID_CBOR_TAG))?; - encoder.bytes(&self.purpose_id.to_bytes())?; - - // Validators - encoder.array(self.validator.len().try_into()?)?; - for val in self.validator.clone() { - encoder.tag(minicbor::data::Tag::new(cbor_hash_tag))?; - encoder.bytes(&val.0)?; - } - - Ok(encoder.writer().clone()) - } - - /// Generate hash of cbor encoded self - /// ## Errors - /// - /// Returns an error if hashing fails - pub fn hash(&self, hasher: &HashFunction) -> anyhow::Result> { - let encoding = self.to_bytes(hasher)?; - - // get hash of genesis_to_prev_hash - let genesis_prev_hash = match hasher { - HashFunction::Blake3 => blake3(&encoding)?.to_vec(), - HashFunction::Blake2b => blake2b_512(&encoding)?.to_vec(), - }; - - Ok(genesis_prev_hash) - } -} - -#[cfg(test)] -mod tests { - - use ed25519_dalek::{SigningKey, SECRET_KEY_LENGTH}; - use ulid::Ulid; - use uuid::Uuid; - - use super::{BlockHeader, Kid}; - use crate::ser::{ - blake2b_512, Block, BlockData, GenesisPreviousHash, HashFunction::Blake2b, ValidatorKeys, - }; - - #[test] - fn block_header_encoding() { - let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - - let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - - let block_hdr = BlockHeader::new( - Ulid::new(), - 5, - 1_728_474_515, - vec![0; 64], - Uuid::new_v4(), - Ulid::new(), - vec![Kid(kid_a), Kid(kid_b)], - vec![7; 356], - ); - - let encoded_block_hdr = block_hdr.to_bytes(&Blake2b).unwrap(); - - const CDDL: &str = include_str!("./cddl/block_header.cddl"); - - cddl::validate_cbor_from_slice(CDDL, &encoded_block_hdr, None).unwrap(); - - let (block_hdr_from_bytes, ..) = - BlockHeader::from_bytes(&encoded_block_hdr, &Blake2b).unwrap(); - assert_eq!(block_hdr_from_bytes.chain_id, block_hdr.chain_id); - assert_eq!(block_hdr_from_bytes.height, block_hdr.height); - assert_eq!( - block_hdr_from_bytes.block_time_stamp, - block_hdr.block_time_stamp - ); - assert_eq!( - block_hdr_from_bytes.previous_block_hash, - block_hdr.previous_block_hash - ); - assert_eq!(block_hdr_from_bytes.ledger_type, block_hdr.ledger_type); - assert_eq!(block_hdr_from_bytes.purpose_id, block_hdr.purpose_id); - assert_eq!(block_hdr_from_bytes.validator, block_hdr.validator); - assert_eq!(block_hdr_from_bytes.metadata, block_hdr.metadata); - } - - #[test] - fn block_encoding() { - // validators - let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, - 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, - ]; - - let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - - let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - - let block_hdr = BlockHeader::new( - Ulid::new(), - 5, - 1_728_474_515, - vec![0; 64], - Uuid::new_v4(), - Ulid::new(), - vec![Kid(kid_a), Kid(kid_b)], - vec![1; 128], - ); - - let out: Vec = Vec::new(); - let mut block_data = minicbor::Encoder::new(out); - - let block_data_bytes = &[ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, - 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, - 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, - 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, - 157, - ]; - - block_data.bytes(block_data_bytes).unwrap(); - let encoded_block_data = block_data.writer().clone(); - - let block = Block::new( - block_hdr.clone(), - BlockData(encoded_block_data.clone()), - ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), - Blake2b, - ); - - let encoded_block = block.to_bytes().unwrap(); - - const CDDL: &str = include_str!("./cddl/block.cddl"); - - cddl::validate_cbor_from_slice(CDDL, &encoded_block, None).unwrap(); - - let (block_header, block_data, sigs) = Block::from_bytes(&encoded_block, &Blake2b).unwrap(); - - assert_eq!(block_header, block_hdr); - - // signatures are over encoded block data - // block data is returned as plain bytes decoded from cbor - assert_eq!(block_data.0, block_data_bytes); - let data_to_sign = [ - blake2b_512(&block_hdr.to_bytes(&Blake2b).unwrap()) - .unwrap() - .to_vec(), - encoded_block_data, - ] - .concat(); - - let verifying_key = SigningKey::from_bytes(&validator_secret_key_bytes); - - for sig in sigs.0 { - verifying_key.verify_strict(&data_to_sign, &sig).unwrap(); - } - - // ENCODING SHOULD FAIL with block data that is NOT cbor encoded - let block = Block::new( - block_hdr.clone(), - BlockData(block_data_bytes.to_vec()), - ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), - Blake2b, - ); - - assert!(block.to_bytes().is_err()); - } - - #[test] - #[allow(clippy::zero_prefixed_literal)] - fn validate_block_test() { - // PREVIOUS BLOCK - // - // - // validators - let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, - 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, - ]; - - let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - - let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - - let chain_id = Ulid::new(); - let ledger_type = Uuid::new_v4(); - let purpose_id = Ulid::new(); - - let block_hdr = BlockHeader::new( - chain_id, - 5, - 1_728_474_515, - vec![0; 64], - ledger_type, - purpose_id, - vec![Kid(kid_a), Kid(kid_b)], - vec![1; 128], - ); - - let out: Vec = Vec::new(); - let mut block_data = minicbor::Encoder::new(out); - - let block_data_bytes = &[ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, - 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, - 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, - 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, - 157, - ]; - - block_data.bytes(block_data_bytes).unwrap(); - let encoded_block_data = block_data.writer().clone(); - - let previous_block = Block::new( - block_hdr.clone(), - BlockData(encoded_block_data.clone()), - ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), - Blake2b, - ); - - // CURRENT BLOCK - - let prev_block_hash = blake2b_512(&previous_block.to_bytes().unwrap()).unwrap(); - - let block_hdr = BlockHeader::new( - chain_id, - 6, - 1_728_474_516, - prev_block_hash.to_vec(), - ledger_type, - purpose_id, - vec![Kid(kid_a), Kid(kid_b)], - vec![1; 128], - ); - - let out: Vec = Vec::new(); - let mut block_data = minicbor::Encoder::new(out); - - block_data.bytes(block_data_bytes).unwrap(); - let encoded_block_data = block_data.writer().clone(); - - let current_block = Block::new( - block_hdr.clone(), - BlockData(encoded_block_data.clone()), - ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), - Blake2b, - ); - - assert!(current_block.validate(Some(previous_block)).is_ok()); - } - - #[test] - fn genesis_encoding_and_validation() { - // validators - let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, - 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, - ]; - - let chain_id = Ulid::new(); - let ledger_type = Uuid::new_v4(); - let purpose_id = Ulid::new(); - let block_time_stamp = 1_728_474_515; - - let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - - let kid_b: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") - .unwrap() - .try_into() - .unwrap(); - - let validator = vec![Kid(kid_a), Kid(kid_b)]; - - let genesis_to_prev_hash = GenesisPreviousHash::new( - chain_id, - block_time_stamp, - ledger_type, - purpose_id, - validator.clone(), - ); - - let block_hdr = BlockHeader::new( - chain_id, - 0, - block_time_stamp, - genesis_to_prev_hash.hash(&Blake2b).unwrap(), - ledger_type, - purpose_id, - validator.clone(), - vec![1; 128], - ); - - let out: Vec = Vec::new(); - let mut block_data = minicbor::Encoder::new(out); - - let block_data_bytes = &[ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, - 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, - 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, - 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, - 157, - ]; - - block_data.bytes(block_data_bytes).unwrap(); - let encoded_block_data = block_data.writer().clone(); - - let block = Block::new( - block_hdr.clone(), - BlockData(encoded_block_data.clone()), - ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), - Blake2b, - ); - - assert!(block.validate(None).is_ok()); - - // SHOULD FAIL as previous block hash for genesis is invalid, it should be a hash of - // itself like above. - let block_hdr = BlockHeader::new( - chain_id, - 0, - block_time_stamp, - vec![1; 128], - ledger_type, - purpose_id, - validator, - vec![1; 128], - ); - - let block = Block::new( - block_hdr.clone(), - BlockData(encoded_block_data.clone()), - ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), - Blake2b, - ); - - assert!(block.validate(None).is_err()); - } -} diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index a2faa2c91f..282ab2a049 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -1,6 +1,6 @@ //! Block structure -use core::result::Result::Ok as ResultOk; +//! Block structure use anyhow::Ok; use blake2b_simd::{self, Params}; @@ -73,6 +73,24 @@ pub struct BlockData(Vec); /// validator. pub struct ValidatorKeys(pub Vec<[u8; SECRET_KEY_LENGTH]>); +/// CBOR tag for timestamp +const TIMESTAMP_CBOR_TAG: u64 = 1; + +/// CBOR tag for UUID +const UUID_CBOR_TAG: u64 = 37; + +/// CBOR tag for UUID +const ULID_CBOR_TAG: u64 = 32780; + +/// CBOR tags for BLAKE2 [2] and BLAKE3 [3] hash functions +/// `https://github.com/input-output-hk/catalyst-voices/blob/main/docs/src/catalyst-standards/cbor_tags/blake.md` + +/// CBOR tag for UUID +const BLAKE3_CBOR_TAG: u64 = 32781; + +/// CBOR tag for blake2b +const BLAKE_2B_CBOR_TAG: u64 = 32782; + /// Block pub struct Block { /// Block header @@ -135,18 +153,16 @@ impl Block { let out: Vec = Vec::new(); let mut encoder = minicbor::Encoder::new(out); - - encoder.bytes(&self.block_data.0)?; - - for sig in &signatures { - encoder.bytes(sig)?; + encoder.array(signatures.len().try_into()?)?; + for sig in signatures { + encoder.bytes(&sig)?; } - let block_data_with_sigs = encoder.writer().clone(); - // block hdr + block data + sigs - let encoded_block = [encoded_block_hdr, block_data_with_sigs].concat(); - - Ok(encoded_block) + Ok([ + [encoded_block_hdr, self.block_data.0.clone()].concat(), + encoder.writer().to_vec(), + ] + .concat()) } /// Decode block @@ -168,9 +184,13 @@ impl Block { .bytes() .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block data : {e}")))?; - // Extract signatures, block hdr indicates how many validators. + // Extract signatures + let number_of_sigs = cbor_decoder + .array()? + .ok_or(anyhow::anyhow!(format!("Invalid signature.")))?; + let mut sigs = Vec::new(); - for _sig in 0..block_hdr.validator.len() { + for _sig in 0..number_of_sigs { let sig: [u8; SIGNATURE_LENGTH] = cbor_decoder .bytes() .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for signature : {e}")))? @@ -307,8 +327,8 @@ pub struct BlockHeader { pub purpose_id: Ulid, /// Identifier or identifiers of the entity who was produced and processed a block. pub validator: Vec, - /// Optional field, to add some arbitrary metadata to the block. - pub metadata: Option>, + /// Add arbitrary metadata to the block. + pub metadata: Vec, } impl BlockHeader { @@ -317,7 +337,7 @@ impl BlockHeader { #[allow(clippy::too_many_arguments)] pub fn new( chain_id: Ulid, height: i64, block_time_stamp: i64, previous_block_hash: Vec, - ledger_type: Uuid, purpose_id: Ulid, validator: Vec, metadata: Option>, + ledger_type: Uuid, purpose_id: Ulid, validator: Vec, metadata: Vec, ) -> Self { Self { chain_id, @@ -335,26 +355,52 @@ impl BlockHeader { /// ## Errors /// /// Returns an error encoding fails - pub fn to_bytes(&self, _hasher: &HashFunction) -> anyhow::Result> { + pub fn to_bytes(&self, hasher: &HashFunction) -> anyhow::Result> { + /// # of elements in block header + const BLOCK_HEADER_SIZE: u64 = 8; + let out: Vec = Vec::new(); let mut encoder = minicbor::Encoder::new(out); + encoder.array(BLOCK_HEADER_SIZE)?; + + // Chain id + encoder.tag(minicbor::data::Tag::new(ULID_CBOR_TAG))?; encoder.bytes(&self.chain_id.to_bytes())?; - encoder.bytes(&self.height.to_be_bytes())?; - encoder.bytes(&self.block_time_stamp.to_be_bytes())?; - encoder.bytes(self.previous_block_hash.as_slice())?; + + // Block height + encoder.int(self.height.into())?; + + // Block timestamp + encoder.tag(minicbor::data::Tag::new(TIMESTAMP_CBOR_TAG))?; + encoder.int(self.block_time_stamp.into())?; + + let cbor_hash_tag = match hasher { + HashFunction::Blake3 => BLAKE3_CBOR_TAG, + HashFunction::Blake2b => BLAKE_2B_CBOR_TAG, + }; + + // Prev block hash + encoder.tag(minicbor::data::Tag::new(cbor_hash_tag))?; + encoder.bytes(&self.previous_block_hash)?; + + // Ledger type + encoder.tag(minicbor::data::Tag::new(UUID_CBOR_TAG))?; encoder.bytes(self.ledger_type.as_bytes())?; + + // Purpose id + encoder.tag(minicbor::data::Tag::new(ULID_CBOR_TAG))?; encoder.bytes(&self.purpose_id.to_bytes())?; - // marks how many validators for decoding side. - encoder.bytes(&self.validator.len().to_be_bytes())?; - for validator in self.validator.clone() { - encoder.bytes(&validator.0)?; + // Validators + encoder.array(self.validator.len().try_into()?)?; + for val in self.validator.clone() { + encoder.tag(minicbor::data::Tag::new(cbor_hash_tag))?; + encoder.bytes(&val.0)?; } - if let Some(meta) = &self.metadata { - encoder.bytes(meta)?; - } + // Metadata + encoder.bytes(&self.metadata)?; Ok(encoder.writer().clone()) } @@ -372,8 +418,10 @@ impl BlockHeader { )> { // Decode cbor to bytes let mut cbor_decoder = minicbor::Decoder::new(block); + cbor_decoder.array()?; // Raw chain_id + cbor_decoder.tag()?; let chain_id = Ulid::from_bytes( cbor_decoder .bytes() @@ -382,28 +430,21 @@ impl BlockHeader { ); // Raw Block height - let block_height = i64::from_be_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for block height : {e}")))? - .try_into()?, - ); + let block_height: i64 = cbor_decoder.int()?.try_into()?; // Raw time stamp - let ts = i64::from_be_bytes( - cbor_decoder - .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for timestamp : {e}")))? - .try_into()?, - ); + cbor_decoder.tag()?; + let ts: i64 = cbor_decoder.int()?.try_into()?; // Raw prev block hash + cbor_decoder.tag()?; let prev_block_hash = cbor_decoder .bytes() .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for prev block hash : {e}")))? .to_vec(); // Raw ledger type + cbor_decoder.tag()?; let ledger_type = Uuid::from_bytes( cbor_decoder .bytes() @@ -412,6 +453,7 @@ impl BlockHeader { ); // Raw purpose id + cbor_decoder.tag()?; let purpose_id = Ulid::from_bytes( cbor_decoder .bytes() @@ -419,19 +461,14 @@ impl BlockHeader { .try_into()?, ); - // Number of validators - let number_of_validators = usize::from_be_bytes( - cbor_decoder - .bytes() - .map_err(|e| { - anyhow::anyhow!(format!("Invalid cbor for number of validators : {e}")) - })? - .try_into()?, - ); - - // Extract validators + // Validators let mut validators = Vec::new(); + let number_of_validators = cbor_decoder.array()?.ok_or(anyhow::anyhow!(format!( + "Invalid amount of validators, should be at least two" + )))?; + for _validator in 0..number_of_validators { + cbor_decoder.tag()?; let validator_kid: [u8; 16] = cbor_decoder .bytes() .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for validators : {e}")))? @@ -440,10 +477,10 @@ impl BlockHeader { validators.push(Kid(validator_kid)); } - let metadata = match cbor_decoder.bytes() { - ResultOk(meta) => Some(meta.to_vec()), - Err(_) => None, - }; + let metadata = cbor_decoder + .bytes() + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for metadata : {e}")))? + .try_into()?; let block_header = BlockHeader { chain_id, @@ -497,19 +534,39 @@ impl GenesisPreviousHash { /// ## Errors /// /// Returns an error encoding fails - pub fn to_bytes(&self) -> anyhow::Result> { + pub fn to_bytes(&self, hasher: &HashFunction) -> anyhow::Result> { + // # of elements in Genesis to previous block hash + // const GENESIS_TO_PREV_HASH_SIZE: u64 = 5; let out: Vec = Vec::new(); let mut encoder = minicbor::Encoder::new(out); + encoder.array(5)?; + // Chain id + encoder.tag(minicbor::data::Tag::new(ULID_CBOR_TAG))?; encoder.bytes(&self.chain_id.to_bytes())?; - encoder.bytes(&self.block_time_stamp.to_be_bytes())?; + + // Block timestamp + encoder.tag(minicbor::data::Tag::new(TIMESTAMP_CBOR_TAG))?; + encoder.int(self.block_time_stamp.into())?; + + let cbor_hash_tag = match hasher { + HashFunction::Blake3 => BLAKE3_CBOR_TAG, + HashFunction::Blake2b => BLAKE_2B_CBOR_TAG, + }; + + // Ledger type + encoder.tag(minicbor::data::Tag::new(UUID_CBOR_TAG))?; encoder.bytes(self.ledger_type.as_bytes())?; + + // Purpose id + encoder.tag(minicbor::data::Tag::new(ULID_CBOR_TAG))?; encoder.bytes(&self.purpose_id.to_bytes())?; - // marks how many validators for decoding side. - encoder.bytes(&self.validator.len().to_be_bytes())?; - for validator in self.validator.clone() { - encoder.bytes(&validator.0)?; + // Validators + encoder.array(self.validator.len().try_into()?)?; + for val in self.validator.clone() { + encoder.tag(minicbor::data::Tag::new(cbor_hash_tag))?; + encoder.bytes(&val.0)?; } Ok(encoder.writer().clone()) @@ -520,7 +577,7 @@ impl GenesisPreviousHash { /// /// Returns an error if hashing fails pub fn hash(&self, hasher: &HashFunction) -> anyhow::Result> { - let encoding = self.to_bytes()?; + let encoding = self.to_bytes(hasher)?; // get hash of genesis_to_prev_hash let genesis_prev_hash = match hasher { @@ -539,8 +596,10 @@ mod tests { use ulid::Ulid; use uuid::Uuid; - use super::{Block, BlockData, BlockHeader, Kid, ValidatorKeys}; - use crate::serialize::{blake2b_512, GenesisPreviousHash, HashFunction::Blake2b}; + use super::{BlockHeader, Kid}; + use crate::serialize::{ + blake2b_512, Block, BlockData, GenesisPreviousHash, HashFunction::Blake2b, ValidatorKeys, + }; #[test] fn block_header_encoding() { @@ -562,11 +621,15 @@ mod tests { Uuid::new_v4(), Ulid::new(), vec![Kid(kid_a), Kid(kid_b)], - Some(vec![1; 128]), + vec![7; 356], ); let encoded_block_hdr = block_hdr.to_bytes(&Blake2b).unwrap(); + const CDDL: &str = include_str!("./cddl/block_header.cddl"); + + cddl::validate_cbor_from_slice(CDDL, &encoded_block_hdr, None).unwrap(); + let (block_hdr_from_bytes, ..) = BlockHeader::from_bytes(&encoded_block_hdr, &Blake2b).unwrap(); assert_eq!(block_hdr_from_bytes.chain_id, block_hdr.chain_id); @@ -582,10 +645,7 @@ mod tests { assert_eq!(block_hdr_from_bytes.ledger_type, block_hdr.ledger_type); assert_eq!(block_hdr_from_bytes.purpose_id, block_hdr.purpose_id); assert_eq!(block_hdr_from_bytes.validator, block_hdr.validator); - assert_eq!( - block_hdr_from_bytes.metadata, - Some(block_hdr.metadata.unwrap()) - ); + assert_eq!(block_hdr_from_bytes.metadata, block_hdr.metadata); } #[test] @@ -614,7 +674,7 @@ mod tests { Uuid::new_v4(), Ulid::new(), vec![Kid(kid_a), Kid(kid_b)], - Some(vec![1; 128]), + vec![1; 128], ); let out: Vec = Vec::new(); @@ -640,20 +700,28 @@ mod tests { let encoded_block = block.to_bytes().unwrap(); - let decoded_block = Block::from_bytes(&encoded_block.clone(), &Blake2b).unwrap(); - assert_eq!(decoded_block.0, block_hdr); + const CDDL: &str = include_str!("./cddl/block.cddl"); + + cddl::validate_cbor_from_slice(CDDL, &encoded_block, None).unwrap(); + let (block_header, block_data, sigs) = Block::from_bytes(&encoded_block, &Blake2b).unwrap(); + + assert_eq!(block_header, block_hdr); + + // signatures are over encoded block data + // block data is returned as plain bytes decoded from cbor + assert_eq!(block_data.0, block_data_bytes); let data_to_sign = [ blake2b_512(&block_hdr.to_bytes(&Blake2b).unwrap()) .unwrap() .to_vec(), - encoded_block_data.clone(), + encoded_block_data, ] .concat(); let verifying_key = SigningKey::from_bytes(&validator_secret_key_bytes); - for sig in decoded_block.2 .0 { + for sig in sigs.0 { verifying_key.verify_strict(&data_to_sign, &sig).unwrap(); } @@ -702,7 +770,7 @@ mod tests { ledger_type, purpose_id, vec![Kid(kid_a), Kid(kid_b)], - Some(vec![1; 128]), + vec![1; 128], ); let out: Vec = Vec::new(); @@ -738,7 +806,7 @@ mod tests { ledger_type, purpose_id, vec![Kid(kid_a), Kid(kid_b)], - Some(vec![1; 128]), + vec![1; 128], ); let out: Vec = Vec::new(); @@ -798,7 +866,7 @@ mod tests { ledger_type, purpose_id, validator.clone(), - Some(vec![1; 128]), + vec![1; 128], ); let out: Vec = Vec::new(); @@ -834,7 +902,7 @@ mod tests { ledger_type, purpose_id, validator, - Some(vec![1; 128]), + vec![1; 128], ); let block = Block::new( From e61bdd4fa96473b77be2bedcd21aa0e0db5f5a49 Mon Sep 17 00:00:00 2001 From: cong-or Date: Sun, 3 Nov 2024 20:28:07 +0000 Subject: [PATCH 41/53] refactor(add block types and cddl tests): verifcation and refactor --- rust/immutable-ledger/src/serialize.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index 282ab2a049..069033adc1 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -158,11 +158,15 @@ impl Block { encoder.bytes(&sig)?; } - Ok([ + let signatures = encoder.writer().clone(); + + let block_encoding = [ [encoded_block_hdr, self.block_data.0.clone()].concat(), - encoder.writer().to_vec(), + signatures, ] - .concat()) + .concat(); + + Ok(block_encoding) } /// Decode block @@ -480,7 +484,7 @@ impl BlockHeader { let metadata = cbor_decoder .bytes() .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for metadata : {e}")))? - .try_into()?; + .into(); let block_header = BlockHeader { chain_id, @@ -590,6 +594,8 @@ impl GenesisPreviousHash { } #[cfg(test)] +#[allow(clippy::zero_prefixed_literal)] +#[allow(clippy::items_after_statements)] mod tests { use ed25519_dalek::{SigningKey, SECRET_KEY_LENGTH}; From 8ba86cceb48e28a6c31b4b8c471d563f902cd3e9 Mon Sep 17 00:00:00 2001 From: cong-or Date: Sun, 3 Nov 2024 21:08:15 +0000 Subject: [PATCH 42/53] refactor(rm redundant test): test --- rust/immutable-ledger/src/cddl/block.cddl | 33 ----------------------- rust/immutable-ledger/src/serialize.rs | 4 --- 2 files changed, 37 deletions(-) delete mode 100644 rust/immutable-ledger/src/cddl/block.cddl diff --git a/rust/immutable-ledger/src/cddl/block.cddl b/rust/immutable-ledger/src/cddl/block.cddl deleted file mode 100644 index f380b6a020..0000000000 --- a/rust/immutable-ledger/src/cddl/block.cddl +++ /dev/null @@ -1,33 +0,0 @@ -block = [ - block_header, - block_data: metadata, - validator_signature:metadata, -] - -block_header = [ - chain_id: ULID, - height: int, - timestamp: #6.1(uint .ge 1722470400), ; Epoch-based date/time - prev_block_id: hash_bytes, ; hash of the previous block - ledger_type: UUID, - purpose_id: ULID / UUID, - validator: validator, - metadata: metadata, -] - -ULID = #6.32780(bytes) ; ULID type -UUID = #6.37(bytes) ; UUID type - -BLAKE_3 = #6.32781(bytes) ; Blake3 hash -BLAKE_2B = #6.32782(bytes) ; Blake2b hash -BLAKE_2S = #6.32783(bytes) ; Blake2s hash - -hash_bytes = BLAKE_2B / BLAKE_3 / BLAKE_2S - -kid = hash_bytes ; hash of the x509/c509 certificate - -validator = (kid / [2* kid]) - -metadata = [ *any ] - -validator_signature = (bytes / [2* bytes]) diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index 069033adc1..1b42627386 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -706,10 +706,6 @@ mod tests { let encoded_block = block.to_bytes().unwrap(); - const CDDL: &str = include_str!("./cddl/block.cddl"); - - cddl::validate_cbor_from_slice(CDDL, &encoded_block, None).unwrap(); - let (block_header, block_data, sigs) = Block::from_bytes(&encoded_block, &Blake2b).unwrap(); assert_eq!(block_header, block_hdr); From 1f9507ea21b6d8693ee0b2ca9b4605446851e0d1 Mon Sep 17 00:00:00 2001 From: cong-or Date: Sun, 3 Nov 2024 21:21:25 +0000 Subject: [PATCH 43/53] refactor(deny.toml): rustsec --- rust/deny.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rust/deny.toml b/rust/deny.toml index 77f0259f18..1ba2aa5c4f 100644 --- a/rust/deny.toml +++ b/rust/deny.toml @@ -58,6 +58,10 @@ allow-git = [ "https://github.com/input-output-hk/catalyst-mithril.git", "https://github.com/bytecodealliance/wasmtime", "https://github.com/aldanor/hdf5-rust", + "https://github.com/txpipe/vrf", + "https://github.com/txpipe/kes", + "https://github.com/txpipe/curve25519-dalek", + "https://github.com/anweiss/cddl", ] [licenses] From d971069328c7c9daca536284cbd80b2bd8ba9771 Mon Sep 17 00:00:00 2001 From: cong-or Date: Sun, 3 Nov 2024 21:23:56 +0000 Subject: [PATCH 44/53] refactor(deny.toml): rustsec --- Earthfile | 4 ++-- docs/Earthfile | 2 +- rust/c509-certificate/Earthfile | 2 +- rust/cbork/Earthfile | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Earthfile b/Earthfile index a224708f32..8a891774a1 100644 --- a/Earthfile +++ b/Earthfile @@ -1,7 +1,7 @@ VERSION 0.8 -IMPORT github.com/input-output-hk/catalyst-ci/earthly/mdlint:v3.2.15 AS mdlint-ci -IMPORT github.com/input-output-hk/catalyst-ci/earthly/cspell:v3.2.15 AS cspell-ci +IMPORT github.com/input-output-hk/catalyst-ci/earthly/mdlint:feat/deny.toml AS mdlint-ci +IMPORT github.com/input-output-hk/catalyst-ci/earthly/cspell:feat/deny.toml AS cspell-ci FROM debian:stable-slim diff --git a/docs/Earthfile b/docs/Earthfile index b20624efbf..a5a418d088 100644 --- a/docs/Earthfile +++ b/docs/Earthfile @@ -1,6 +1,6 @@ VERSION 0.8 -IMPORT github.com/input-output-hk/catalyst-ci/earthly/docs:v3.2.15 AS docs-ci +IMPORT github.com/input-output-hk/catalyst-ci/earthly/docs:feat/deny.toml AS docs-ci IMPORT .. AS repo diff --git a/rust/c509-certificate/Earthfile b/rust/c509-certificate/Earthfile index 198a937952..4ae68ab57d 100644 --- a/rust/c509-certificate/Earthfile +++ b/rust/c509-certificate/Earthfile @@ -1,6 +1,6 @@ VERSION 0.8 -IMPORT github.com/input-output-hk/catalyst-ci/earthly/rust::v3.2.15 AS rust-ci +IMPORT github.com/input-output-hk/catalyst-ci/earthly/rust:feat/deny.toml AS rust-ci IMPORT .. AS rust-local IMPORT ../.. AS repo diff --git a/rust/cbork/Earthfile b/rust/cbork/Earthfile index ed2e6473f0..37461b9569 100644 --- a/rust/cbork/Earthfile +++ b/rust/cbork/Earthfile @@ -1,6 +1,6 @@ VERSION 0.8 -IMPORT github.com/input-output-hk/catalyst-ci/earthly/rust::v3.2.15 AS rust-ci +IMPORT github.com/input-output-hk/catalyst-ci/earthly/rust:feat/deny.toml AS rust-ci IMPORT .. AS rust-local From c75b706876700a52a14c461af958c680de3817c0 Mon Sep 17 00:00:00 2001 From: cong-or Date: Sun, 3 Nov 2024 21:29:18 +0000 Subject: [PATCH 45/53] refactor(deny.toml): rustsec --- rust/deny.toml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/rust/deny.toml b/rust/deny.toml index 1ba2aa5c4f..0e59c5bac9 100644 --- a/rust/deny.toml +++ b/rust/deny.toml @@ -16,11 +16,7 @@ targets = [ [advisories] version = 2 -ignore = [ - { id = "RUSTSEC-2020-0168", reason = "`mach` is used by wasmtime and we have no control over that." }, - { id = "RUSTSEC-2021-0145", reason = "we don't target windows, and don't use a custom global allocator." }, - { id = "RUSTSEC-2024-0370", reason = "`proc-macro-error` is used by crates we rely on, we can't control what they use."}, -] +ignore = [] [bans] multiple-versions = "warn" From 3f758953d4681aa54bbc2d9e93e39fff8cee5540 Mon Sep 17 00:00:00 2001 From: cong-or <60357579+cong-or@users.noreply.github.com> Date: Wed, 6 Nov 2024 21:38:15 +0000 Subject: [PATCH 46/53] Update rust/immutable-ledger/src/serialize.rs Co-authored-by: Alex Pozhylenkov --- rust/immutable-ledger/src/serialize.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index 1b42627386..1da1fc1ce2 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -173,7 +173,7 @@ impl Block { /// ## Errors /// /// Returns an error if decoding fails. - pub fn from_bytes(encoded_block: &[u8], hasher: &HashFunction) -> anyhow::Result { + pub fn from_bytes(encoded_block: &[u8], hasher: &HashFunction) -> anyhow::Result { // Decoded block hdr let (block_hdr, block_hdr_size, _) = BlockHeader::from_bytes(encoded_block, hasher)?; From c6f93e3aee6371fa4f18a7b885c1e8b4cc3a510a Mon Sep 17 00:00:00 2001 From: cong-or <60357579+cong-or@users.noreply.github.com> Date: Wed, 6 Nov 2024 21:38:24 +0000 Subject: [PATCH 47/53] Update rust/immutable-ledger/src/serialize.rs Co-authored-by: Alex Pozhylenkov --- rust/immutable-ledger/src/serialize.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index 1da1fc1ce2..492c0bb9b8 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -122,7 +122,7 @@ impl Block { /// ## Errors /// /// Returns an error if encoding fails. - pub fn to_bytes(&self) -> anyhow::Result { + pub fn to_bytes(&self) -> anyhow::Result> { // Enforce block data to be cbor encoded in the form of CBOR byte strings // which are just (ordered) series of bytes without further interpretation let _ = minicbor::Decoder::new(&self.block_data.0).bytes()?; From abb68037113c222814f43a4a92c97cdc6fa77953 Mon Sep 17 00:00:00 2001 From: cong-or Date: Sun, 10 Nov 2024 20:50:52 +0000 Subject: [PATCH 48/53] refactor(agnostic signing algo and proptest): generic --- rust/immutable-ledger/Cargo.toml | 7 +- .../src/cddl/block_header.cddl | 26 -- rust/immutable-ledger/src/serialize.rs | 289 ++++++++---------- 3 files changed, 140 insertions(+), 182 deletions(-) delete mode 100644 rust/immutable-ledger/src/cddl/block_header.cddl diff --git a/rust/immutable-ledger/Cargo.toml b/rust/immutable-ledger/Cargo.toml index ce7792da67..aef695bde3 100644 --- a/rust/immutable-ledger/Cargo.toml +++ b/rust/immutable-ledger/Cargo.toml @@ -16,9 +16,12 @@ ulid = { version = "1.1.3", features = ["serde", "uuid"] } hex = "0.4.3" blake2b_simd = "1.0.2" blake3 = "=0.1.3" -cddl = "0.9.4" - +proptest = { version = "1.5.0" } [lints] workspace = true + + +[dev-dependencies] +test-strategy = "0.4.0" \ No newline at end of file diff --git a/rust/immutable-ledger/src/cddl/block_header.cddl b/rust/immutable-ledger/src/cddl/block_header.cddl deleted file mode 100644 index b4a57de778..0000000000 --- a/rust/immutable-ledger/src/cddl/block_header.cddl +++ /dev/null @@ -1,26 +0,0 @@ -block_header = [ - chain_id: ULID, - height: int, - timestamp: #6.1(uint .ge 1722470400), ; Epoch-based date/time - prev_block_id: hash_bytes, ; hash of the previous block - ledger_type: UUID, - purpose_id: ULID / UUID, - validator: validator, - metadata: metadata, -] - -ULID = #6.32780(bytes) ; ULID type -UUID = #6.37(bytes) ; UUID type - -BLAKE_3 = #6.32781(bytes) ; Blake3 hash -BLAKE_2B = #6.32782(bytes) ; Blake2b hash -BLAKE_2S = #6.32783(bytes) ; Blake2s hash - -hash_bytes = BLAKE_2B / BLAKE_3 / BLAKE_2S - -kid = hash_bytes ; hash of the x509/c509 certificate - -validator = (kid / [2* kid]) - -metadata = [ *any ] - diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index 492c0bb9b8..f58130ca76 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -2,11 +2,8 @@ //! Block structure -use anyhow::Ok; +use anyhow::{bail, Ok}; use blake2b_simd::{self, Params}; -use ed25519_dalek::{ - ed25519::signature::SignerMut, Signature, SigningKey, SECRET_KEY_LENGTH, SIGNATURE_LENGTH, -}; use ulid::Ulid; use uuid::Uuid; @@ -17,15 +14,12 @@ const GENESIS_BLOCK: i64 = 0; #[derive(Debug, Clone, PartialEq)] pub struct BlockHeaderSize(usize); -/// Decoded block header -pub type DecodedBlockHeader = BlockHeader; - /// Signatures #[derive(Debug, Clone, PartialEq)] -pub struct Signatures(Vec); +pub struct Signatures(Vec>); /// Decoded block -pub type DecodedBlock = (DecodedBlockHeader, BlockData, Signatures); +pub type DecodedBlock = (BlockHeader, BlockData, Signatures); /// Encoded genesis Block contents as cbor, used for hash validation #[derive(Debug, Clone, PartialEq)] @@ -33,6 +27,7 @@ pub struct EncodedGenesisBlockContents(pub Vec); /// Choice of hash function: /// must be the same as the hash of the previous block. +#[derive(Debug, Clone, PartialEq)] pub enum HashFunction { /// BLAKE3 is based on an optimized instance of the established hash function BLAKE2 /// and on the original Bao tree mode @@ -69,10 +64,6 @@ pub(crate) fn blake2b_512(value: &[u8]) -> anyhow::Result<[u8; 64]> { #[derive(Debug, Clone, PartialEq)] pub struct BlockData(Vec); -/// Validator's keys defined in the corresponding certificates referenced by the -/// validator. -pub struct ValidatorKeys(pub Vec<[u8; SECRET_KEY_LENGTH]>); - /// CBOR tag for timestamp const TIMESTAMP_CBOR_TAG: u64 = 1; @@ -97,24 +88,20 @@ pub struct Block { pub block_header: BlockHeader, /// cbor encoded block data pub block_data: BlockData, - /// Validators - pub validator_keys: ValidatorKeys, - /// Hash function - pub hasher: HashFunction, + /// Validators signatures + pub validator_sigs: Signatures, } impl Block { /// New block #[must_use] pub fn new( - block_header: BlockHeader, block_data: BlockData, validator_keys: ValidatorKeys, - hasher: HashFunction, + block_header: BlockHeader, block_data: BlockData, validator_sigs: Signatures, ) -> Self { Self { block_header, block_data, - validator_keys, - hasher, + validator_sigs, } } @@ -128,33 +115,13 @@ impl Block { let _ = minicbor::Decoder::new(&self.block_data.0).bytes()?; // cbor encode block hdr - let encoded_block_hdr = self.block_header.to_bytes(&self.hasher)?; - - let hashed_block_header = match self.hasher { - HashFunction::Blake3 => blake3(&encoded_block_hdr)?.to_vec(), - HashFunction::Blake2b => blake2b_512(&encoded_block_hdr)?.to_vec(), - }; - - // validator_signature MUST be a signature of the hashed block_header bytes - // and the block_data bytes - let data_to_sign = [hashed_block_header, self.block_data.0.clone()].concat(); - - // if validator is only one id => validator_signature contains only 1 signature; - // if validator is array => validator_signature contains an array with the same length; - let signatures: Vec<[u8; 64]> = self - .validator_keys - .0 - .iter() - .map(|sk| { - let mut sk: SigningKey = SigningKey::from_bytes(sk); - sk.sign(&data_to_sign).to_bytes() - }) - .collect(); + let encoded_block_hdr = self.block_header.to_bytes()?; let out: Vec = Vec::new(); let mut encoder = minicbor::Encoder::new(out); - encoder.array(signatures.len().try_into()?)?; - for sig in signatures { + let signatures = &self.validator_sigs; + encoder.array(signatures.0.len().try_into()?)?; + for sig in signatures.0.clone() { encoder.bytes(&sig)?; } @@ -173,9 +140,11 @@ impl Block { /// ## Errors /// /// Returns an error if decoding fails. - pub fn from_bytes(encoded_block: &[u8], hasher: &HashFunction) -> anyhow::Result { + pub fn from_bytes( + encoded_block: &[u8], + ) -> anyhow::Result<(BlockHeader, BlockData, Signatures)> { // Decoded block hdr - let (block_hdr, block_hdr_size, _) = BlockHeader::from_bytes(encoded_block, hasher)?; + let (block_hdr, block_hdr_size, _) = BlockHeader::from_bytes(encoded_block)?; // Init decoder let mut cbor_decoder = minicbor::Decoder::new(encoded_block); @@ -195,12 +164,10 @@ impl Block { let mut sigs = Vec::new(); for _sig in 0..number_of_sigs { - let sig: [u8; SIGNATURE_LENGTH] = cbor_decoder + let sig = cbor_decoder .bytes() - .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for signature : {e}")))? - .try_into()?; - - sigs.push(Signature::from_bytes(&sig)); + .map_err(|e| anyhow::anyhow!(format!("Invalid cbor signature : {e}")))?; + sigs.push(sig.to_owned()); } Ok((block_hdr, BlockData(block_data.to_vec()), Signatures(sigs))) @@ -213,9 +180,15 @@ impl Block { pub fn validate(&self, previous_block: Option) -> anyhow::Result<()> { if let Some(previous_block) = previous_block { // Standard block - let hashed_previous_block = match self.hasher { - HashFunction::Blake3 => blake3(&previous_block.to_bytes()?)?.to_vec(), - HashFunction::Blake2b => blake2b_512(&previous_block.to_bytes()?)?.to_vec(), + let hashed_previous_block = match self.block_header.previous_block_hash.0 { + HashFunction::Blake3 => ( + HashFunction::Blake3, + blake3(&previous_block.to_bytes()?)?.to_vec(), + ), + HashFunction::Blake2b => ( + HashFunction::Blake2b, + blake2b_512(&previous_block.to_bytes()?)?.to_vec(), + ), }; // chain_id MUST be the same as for the previous block (except for genesis). @@ -250,7 +223,7 @@ impl Block { } // prev_block_id MUST be a hash of the previous block bytes (except for genesis). - if self.block_header.previous_block_hash != hashed_previous_block { + if self.block_header.previous_block_hash != (hashed_previous_block) { return Err(anyhow::anyhow!( "Module: Immutable ledger, Message: previous hash validation failed: {:?} {:?}", self.block_header, @@ -296,9 +269,9 @@ impl Block { self.block_header.purpose_id, self.block_header.validator.clone(), ) - .hash(&self.hasher)?; + .hash(&self.block_header.previous_block_hash.0)?; - if self.block_header.previous_block_hash != genesis_to_prev_hash { + if self.block_header.previous_block_hash.1 != genesis_to_prev_hash { return Err(anyhow::anyhow!( "Module: Immutable ledger, Message: Genesis block prev hash is invalid {:?}", self.block_header, @@ -321,7 +294,7 @@ pub struct BlockHeader { /// Block epoch-based date/time. pub block_time_stamp: i64, /// Previous Block hash. - pub previous_block_hash: Vec, + pub previous_block_hash: (HashFunction, Vec), /// unique identifier of the ledger type. /// In general, this is the way to strictly bound and specify `block_data` of the /// ledger for the specific `ledger_type`. @@ -340,8 +313,9 @@ impl BlockHeader { #[must_use] #[allow(clippy::too_many_arguments)] pub fn new( - chain_id: Ulid, height: i64, block_time_stamp: i64, previous_block_hash: Vec, - ledger_type: Uuid, purpose_id: Ulid, validator: Vec, metadata: Vec, + chain_id: Ulid, height: i64, block_time_stamp: i64, + previous_block_hash: (HashFunction, Vec), ledger_type: Uuid, purpose_id: Ulid, + validator: Vec, metadata: Vec, ) -> Self { Self { chain_id, @@ -359,7 +333,7 @@ impl BlockHeader { /// ## Errors /// /// Returns an error encoding fails - pub fn to_bytes(&self, hasher: &HashFunction) -> anyhow::Result> { + pub fn to_bytes(&self) -> anyhow::Result> { /// # of elements in block header const BLOCK_HEADER_SIZE: u64 = 8; @@ -379,14 +353,15 @@ impl BlockHeader { encoder.tag(minicbor::data::Tag::new(TIMESTAMP_CBOR_TAG))?; encoder.int(self.block_time_stamp.into())?; - let cbor_hash_tag = match hasher { + let hash_function = self.previous_block_hash.0.clone(); + let cbor_hash_tag = match hash_function { HashFunction::Blake3 => BLAKE3_CBOR_TAG, HashFunction::Blake2b => BLAKE_2B_CBOR_TAG, }; // Prev block hash encoder.tag(minicbor::data::Tag::new(cbor_hash_tag))?; - encoder.bytes(&self.previous_block_hash)?; + encoder.bytes(&self.previous_block_hash.1)?; // Ledger type encoder.tag(minicbor::data::Tag::new(UUID_CBOR_TAG))?; @@ -414,7 +389,7 @@ impl BlockHeader { /// /// Returns an error decoding fails pub fn from_bytes( - block: &[u8], _hasher: &HashFunction, + block: &[u8], ) -> anyhow::Result<( BlockHeader, BlockHeaderSize, @@ -441,7 +416,13 @@ impl BlockHeader { let ts: i64 = cbor_decoder.int()?.try_into()?; // Raw prev block hash - cbor_decoder.tag()?; + let hash_function = cbor_decoder.tag()?; + let prev_block_hash_type = match hash_function.as_u64() { + BLAKE3_CBOR_TAG => HashFunction::Blake3, + BLAKE_2B_CBOR_TAG => HashFunction::Blake2b, + _ => bail!(format!("Invalid hash function type {:?}", hash_function)), + }; + let prev_block_hash = cbor_decoder .bytes() .map_err(|e| anyhow::anyhow!(format!("Invalid cbor for prev block hash : {e}")))? @@ -490,7 +471,7 @@ impl BlockHeader { chain_id, height: block_height, block_time_stamp: ts, - previous_block_hash: prev_block_hash, + previous_block_hash: (prev_block_hash_type, prev_block_hash), ledger_type, purpose_id, validator: validators, @@ -539,11 +520,12 @@ impl GenesisPreviousHash { /// /// Returns an error encoding fails pub fn to_bytes(&self, hasher: &HashFunction) -> anyhow::Result> { - // # of elements in Genesis to previous block hash - // const GENESIS_TO_PREV_HASH_SIZE: u64 = 5; + /// # of elements in genesis to prev hash + const GENESIS_TO_PREV_HASH_SIZE: u64 = 5; + let out: Vec = Vec::new(); let mut encoder = minicbor::Encoder::new(out); - encoder.array(5)?; + encoder.array(GENESIS_TO_PREV_HASH_SIZE)?; // Chain id encoder.tag(minicbor::data::Tag::new(ULID_CBOR_TAG))?; @@ -598,17 +580,20 @@ impl GenesisPreviousHash { #[allow(clippy::items_after_statements)] mod tests { - use ed25519_dalek::{SigningKey, SECRET_KEY_LENGTH}; + use ed25519_dalek::{Signature, Signer, SigningKey, SECRET_KEY_LENGTH}; + use test_strategy::proptest; use ulid::Ulid; use uuid::Uuid; use super::{BlockHeader, Kid}; use crate::serialize::{ - blake2b_512, Block, BlockData, GenesisPreviousHash, HashFunction::Blake2b, ValidatorKeys, + blake2b_512, Block, BlockData, GenesisPreviousHash, HashFunction::Blake2b, Signatures, }; - #[test] - fn block_header_encoding() { + #[proptest] + fn block_header_encoding( + prev_block_hash: Vec, metadata: Vec, block_height: i64, block_timestamp: i64, + ) { let kid_a: [u8; 16] = hex::decode("00112233445566778899aabbccddeeff") .unwrap() .try_into() @@ -621,23 +606,18 @@ mod tests { let block_hdr = BlockHeader::new( Ulid::new(), - 5, - 1_728_474_515, - vec![0; 64], + block_height, + block_timestamp, + (Blake2b, prev_block_hash), Uuid::new_v4(), Ulid::new(), vec![Kid(kid_a), Kid(kid_b)], - vec![7; 356], + metadata, ); - let encoded_block_hdr = block_hdr.to_bytes(&Blake2b).unwrap(); - - const CDDL: &str = include_str!("./cddl/block_header.cddl"); + let encoded_block_hdr = block_hdr.to_bytes().unwrap(); - cddl::validate_cbor_from_slice(CDDL, &encoded_block_hdr, None).unwrap(); - - let (block_hdr_from_bytes, ..) = - BlockHeader::from_bytes(&encoded_block_hdr, &Blake2b).unwrap(); + let (block_hdr_from_bytes, ..) = BlockHeader::from_bytes(&encoded_block_hdr).unwrap(); assert_eq!(block_hdr_from_bytes.chain_id, block_hdr.chain_id); assert_eq!(block_hdr_from_bytes.height, block_hdr.height); assert_eq!( @@ -654,8 +634,11 @@ mod tests { assert_eq!(block_hdr_from_bytes.metadata, block_hdr.metadata); } - #[test] - fn block_encoding() { + #[proptest] + fn block_encoding( + prev_block_hash: Vec, metadata: Vec, block_height: i64, block_timestamp: i64, + block_data_bytes: Vec, + ) { // validators let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, @@ -674,73 +657,77 @@ mod tests { let block_hdr = BlockHeader::new( Ulid::new(), - 5, - 1_728_474_515, - vec![0; 64], + block_height, + block_timestamp, + (Blake2b, prev_block_hash), Uuid::new_v4(), Ulid::new(), vec![Kid(kid_a), Kid(kid_b)], - vec![1; 128], + metadata, ); let out: Vec = Vec::new(); let mut block_data = minicbor::Encoder::new(out); - let block_data_bytes = &[ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, - 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, - 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, - 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, - 157, - ]; - - block_data.bytes(block_data_bytes).unwrap(); + block_data.bytes(&block_data_bytes).unwrap(); let encoded_block_data = block_data.writer().clone(); + // validator_signature MUST be a signature of the hashed block_header bytes + // and the block_data bytes + let hashed_block_header = blake2b_512(&block_hdr.to_bytes().unwrap()) + .unwrap() + .to_vec(); + + let data_to_sign = [hashed_block_header, block_data_bytes.clone()].concat(); + + // sign data with keys, block type is signature agnostic, test case uses ed25519 + let sk: SigningKey = SigningKey::from_bytes(&validator_secret_key_bytes); + let signature_a = sk.sign(&data_to_sign).to_bytes(); + let signature_b = sk.sign(&data_to_sign).to_bytes(); + let block = Block::new( block_hdr.clone(), BlockData(encoded_block_data.clone()), - ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), - Blake2b, + Signatures(vec![signature_a.to_vec(), signature_b.to_vec()]), ); let encoded_block = block.to_bytes().unwrap(); - let (block_header, block_data, sigs) = Block::from_bytes(&encoded_block, &Blake2b).unwrap(); + // DECODE RAW BYTES BACK INTO BLOCK TYPE + let (block_header, block_data, sigs) = Block::from_bytes(&encoded_block).unwrap(); assert_eq!(block_header, block_hdr); // signatures are over encoded block data // block data is returned as plain bytes decoded from cbor assert_eq!(block_data.0, block_data_bytes); - let data_to_sign = [ - blake2b_512(&block_hdr.to_bytes(&Blake2b).unwrap()) - .unwrap() - .to_vec(), - encoded_block_data, - ] - .concat(); let verifying_key = SigningKey::from_bytes(&validator_secret_key_bytes); for sig in sigs.0 { - verifying_key.verify_strict(&data_to_sign, &sig).unwrap(); + let s: [u8; 64] = sig.try_into().unwrap(); + let signature = Signature::from_bytes(&s); + verifying_key + .verify_strict(&data_to_sign, &signature) + .unwrap(); } // ENCODING SHOULD FAIL with block data that is NOT cbor encoded let block = Block::new( block_hdr.clone(), - BlockData(block_data_bytes.to_vec()), - ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), - Blake2b, + BlockData(vec![7; 1024]), + Signatures(vec![ + validator_secret_key_bytes.to_vec(), + validator_secret_key_bytes.to_vec(), + ]), ); assert!(block.to_bytes().is_err()); } - #[test] + #[proptest] #[allow(clippy::zero_prefixed_literal)] - fn validate_block_test() { + fn validate_block_test(prev_block_hash: Vec, metadata: Vec, block_data_bytes: Vec) { // PREVIOUS BLOCK // // @@ -768,32 +755,26 @@ mod tests { chain_id, 5, 1_728_474_515, - vec![0; 64], + (Blake2b, prev_block_hash), ledger_type, purpose_id, vec![Kid(kid_a), Kid(kid_b)], - vec![1; 128], + metadata.clone(), ); let out: Vec = Vec::new(); let mut block_data = minicbor::Encoder::new(out); - let block_data_bytes = &[ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, - 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, - 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, - 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, - 157, - ]; - - block_data.bytes(block_data_bytes).unwrap(); + block_data.bytes(&block_data_bytes).unwrap(); let encoded_block_data = block_data.writer().clone(); let previous_block = Block::new( block_hdr.clone(), BlockData(encoded_block_data.clone()), - ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), - Blake2b, + Signatures(vec![ + validator_secret_key_bytes.to_vec(), + validator_secret_key_bytes.to_vec(), + ]), ); // CURRENT BLOCK @@ -804,31 +785,35 @@ mod tests { chain_id, 6, 1_728_474_516, - prev_block_hash.to_vec(), + (Blake2b, prev_block_hash.to_vec()), ledger_type, purpose_id, vec![Kid(kid_a), Kid(kid_b)], - vec![1; 128], + metadata, ); let out: Vec = Vec::new(); let mut block_data = minicbor::Encoder::new(out); - block_data.bytes(block_data_bytes).unwrap(); + block_data.bytes(&block_data_bytes).unwrap(); let encoded_block_data = block_data.writer().clone(); let current_block = Block::new( block_hdr.clone(), BlockData(encoded_block_data.clone()), - ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), - Blake2b, + Signatures(vec![ + validator_secret_key_bytes.to_vec(), + validator_secret_key_bytes.to_vec(), + ]), ); - assert!(current_block.validate(Some(previous_block)).is_ok()); + assert!(current_block.validate(Some(previous_block),).is_ok()); } - #[test] - fn genesis_encoding_and_validation() { + #[proptest] + fn genesis_encoding_and_validation( + invalid_prev_block_hash: Vec, metadata: Vec, block_data_bytes: Vec, + ) { // validators let validator_secret_key_bytes: [u8; SECRET_KEY_LENGTH] = [ 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, @@ -864,32 +849,26 @@ mod tests { chain_id, 0, block_time_stamp, - genesis_to_prev_hash.hash(&Blake2b).unwrap(), + (Blake2b, genesis_to_prev_hash.hash(&Blake2b).unwrap()), ledger_type, purpose_id, validator.clone(), - vec![1; 128], + metadata.clone(), ); let out: Vec = Vec::new(); let mut block_data = minicbor::Encoder::new(out); - let block_data_bytes = &[ - 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, - 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, - 186, 132, 074, 244, 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, - 146, 236, 044, 196, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, - 157, - ]; - - block_data.bytes(block_data_bytes).unwrap(); + block_data.bytes(&block_data_bytes).unwrap(); let encoded_block_data = block_data.writer().clone(); let block = Block::new( block_hdr.clone(), BlockData(encoded_block_data.clone()), - ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), - Blake2b, + Signatures(vec![ + validator_secret_key_bytes.to_vec(), + validator_secret_key_bytes.to_vec(), + ]), ); assert!(block.validate(None).is_ok()); @@ -900,18 +879,20 @@ mod tests { chain_id, 0, block_time_stamp, - vec![1; 128], + (Blake2b, invalid_prev_block_hash), ledger_type, purpose_id, validator, - vec![1; 128], + metadata, ); let block = Block::new( block_hdr.clone(), BlockData(encoded_block_data.clone()), - ValidatorKeys(vec![validator_secret_key_bytes, validator_secret_key_bytes]), - Blake2b, + Signatures(vec![ + validator_secret_key_bytes.to_vec(), + validator_secret_key_bytes.to_vec(), + ]), ); assert!(block.validate(None).is_err()); From 84998f0c0447a11a5a934ac072a8ce59553196e9 Mon Sep 17 00:00:00 2001 From: cong-or Date: Sun, 10 Nov 2024 20:58:26 +0000 Subject: [PATCH 49/53] refactor(agnostic signing algo and proptest): generic --- rust/immutable-ledger/src/serialize.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index f58130ca76..c1a7c133d7 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -181,14 +181,18 @@ impl Block { if let Some(previous_block) = previous_block { // Standard block let hashed_previous_block = match self.block_header.previous_block_hash.0 { - HashFunction::Blake3 => ( - HashFunction::Blake3, - blake3(&previous_block.to_bytes()?)?.to_vec(), - ), - HashFunction::Blake2b => ( - HashFunction::Blake2b, - blake2b_512(&previous_block.to_bytes()?)?.to_vec(), - ), + HashFunction::Blake3 => { + ( + HashFunction::Blake3, + blake3(&previous_block.to_bytes()?)?.to_vec(), + ) + }, + HashFunction::Blake2b => { + ( + HashFunction::Blake2b, + blake2b_512(&previous_block.to_bytes()?)?.to_vec(), + ) + }, }; // chain_id MUST be the same as for the previous block (except for genesis). From b143df5fa8803745586c86d7d0a0702fd0311090 Mon Sep 17 00:00:00 2001 From: cong-or Date: Sun, 10 Nov 2024 21:02:17 +0000 Subject: [PATCH 50/53] refactor(agnostic signing algo and proptest): generic --- rust/immutable-ledger/Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rust/immutable-ledger/Cargo.toml b/rust/immutable-ledger/Cargo.toml index aef695bde3..8213d6ab2a 100644 --- a/rust/immutable-ledger/Cargo.toml +++ b/rust/immutable-ledger/Cargo.toml @@ -24,4 +24,7 @@ workspace = true [dev-dependencies] -test-strategy = "0.4.0" \ No newline at end of file +test-strategy = "0.4.0" + +[package.metadata.cargo-machete] +ignored = ["proptest"] \ No newline at end of file From df6855ff6c5bc84f07336bde8102f63ab175662f Mon Sep 17 00:00:00 2001 From: cong-or Date: Sun, 10 Nov 2024 21:12:38 +0000 Subject: [PATCH 51/53] refactor(agnostic signing algo and proptest): generic --- rust/immutable-ledger/Cargo.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rust/immutable-ledger/Cargo.toml b/rust/immutable-ledger/Cargo.toml index 8213d6ab2a..b99354aa6c 100644 --- a/rust/immutable-ledger/Cargo.toml +++ b/rust/immutable-ledger/Cargo.toml @@ -18,6 +18,10 @@ blake2b_simd = "1.0.2" blake3 = "=0.1.3" proptest = { version = "1.5.0" } +[package.metadata.cargo-machete] +ignored = ["proptest"] + + [lints] workspace = true @@ -26,5 +30,4 @@ workspace = true [dev-dependencies] test-strategy = "0.4.0" -[package.metadata.cargo-machete] -ignored = ["proptest"] \ No newline at end of file + From 77f8bcfc7d13fdd50c18669e1ae7a8ba2c63d0fd Mon Sep 17 00:00:00 2001 From: cong-or Date: Sun, 10 Nov 2024 21:24:04 +0000 Subject: [PATCH 52/53] refactor(agnostic signing algo and proptest): generic --- rust/immutable-ledger/src/serialize.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/immutable-ledger/src/serialize.rs b/rust/immutable-ledger/src/serialize.rs index c1a7c133d7..f83efd2b6d 100644 --- a/rust/immutable-ledger/src/serialize.rs +++ b/rust/immutable-ledger/src/serialize.rs @@ -73,7 +73,7 @@ const UUID_CBOR_TAG: u64 = 37; /// CBOR tag for UUID const ULID_CBOR_TAG: u64 = 32780; -/// CBOR tags for BLAKE2 [2] and BLAKE3 [3] hash functions +/// CBOR tags for BLAKE2 and BLAKE3 hash functions /// `https://github.com/input-output-hk/catalyst-voices/blob/main/docs/src/catalyst-standards/cbor_tags/blake.md` /// CBOR tag for UUID From eefe293843007094912f35cc930211d74c200a2b Mon Sep 17 00:00:00 2001 From: cong-or Date: Thu, 14 Nov 2024 10:04:42 +0000 Subject: [PATCH 53/53] ci --- rust/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 0e7bf53889..498c5eff01 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -8,7 +8,7 @@ members = [ "cbork-abnf-parser", "cbork-cddl-parser", "catalyst-voting", - "catalyst-voting", "jormungandr-vote-tx", + "catalyst-voting", "immutable-ledger", "vote-tx-v1", "vote-tx-v2",