From cef22d47adcf53bad9c92c7c156bb9f2792847ce Mon Sep 17 00:00:00 2001 From: mamonet Date: Sat, 10 Jan 2026 20:17:55 +0100 Subject: [PATCH 01/25] Add Keeta block and operation types for DER parsing --- keetanetwork-block/Cargo.toml | 7 +- keetanetwork-block/src/lib.rs | 25 ++ keetanetwork-block/src/types.rs | 453 ++++++++++++++++++++++++++++++++ 3 files changed, 483 insertions(+), 2 deletions(-) create mode 100644 keetanetwork-block/src/types.rs diff --git a/keetanetwork-block/Cargo.toml b/keetanetwork-block/Cargo.toml index dbc58c8..c293a3a 100644 --- a/keetanetwork-block/Cargo.toml +++ b/keetanetwork-block/Cargo.toml @@ -8,9 +8,12 @@ repository.workspace = true homepage.workspace = true description = "Block structure and operations for Keetanetwork blockchain" +[features] +default = ["std"] +std = ["alloc"] +alloc = [] + [dependencies] -keetanetwork-error = { version = "0.1.0", path = "../keetanetwork-error" } -snafu = { workspace = true } [dev-dependencies] diff --git a/keetanetwork-block/src/lib.rs b/keetanetwork-block/src/lib.rs index 135bf02..489b623 100644 --- a/keetanetwork-block/src/lib.rs +++ b/keetanetwork-block/src/lib.rs @@ -1,3 +1,28 @@ //! # Keetanetwork Block //! //! This crate provides block structure and operations for the Keetanetwork blockchain. + +#![cfg_attr(not(feature = "std"), no_std)] + +mod types; + +// KeetaBlock requires alloc (uses Vec) +#[cfg(any(feature = "alloc", feature = "std"))] +pub use types::KeetaBlock; + +pub use types::{ + // Address validation + algo_prefix, is_valid_address, + // Block types + BlockHeader, BlockPurpose, BlockVersion, + // Operation enum + Operation, + // Operation structs + CancelSwapOp, CreateIdentifierOp, CreateIdentifierArgs, ManageCertificateOp, MatchSwapOp, + ModifyPermissionsOp, ReceiveOp, SendOp, SetInfoOp, SetRepOp, TokenAdminSupplyOp, + TokenModifyBalanceOp, + // Supporting types + FeeDetails, FeeDetailsWithRecipient, Permission, TokenValue, + // Enums + AdjustMethod, AdjustMethodRelative, +}; diff --git a/keetanetwork-block/src/types.rs b/keetanetwork-block/src/types.rs new file mode 100644 index 0000000..b1537b6 --- /dev/null +++ b/keetanetwork-block/src/types.rs @@ -0,0 +1,453 @@ +//! Keeta Network Block Types +//! +//! Type definitions for Keeta blockchain blocks and operations. +//! Zero-copy types with lifetime parameters for efficient parsing in `no_std` environments. +//! +//! ## Operation Tags +//! +//! | Tag | Operation | +//! |------|---------------------| +//! | [0] | Send | +//! | [1] | SetRep | +//! | [2] | SetInfo | +//! | [3] | ModifyPermissions | +//! | [4] | CreateIdentifier | +//! | [5] | TokenAdminSupply | +//! | [6] | TokenModifyBalance | +//! | [7] | Receive | +//! | [8] | ManageCertificate | +//! | [9] | MatchSwap | +//! | [10] | CancelSwap | + +// Use alloc for Vec when not using std +#[cfg(all(feature = "alloc", not(feature = "std")))] +extern crate alloc; + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::vec::Vec; + +#[cfg(feature = "std")] +use std::vec::Vec; + +// ============================================================================ +// Address/Key Validation +// ============================================================================ + +/// Algorithm prefix bytes for Keeta public keys +pub mod algo_prefix { + /// secp256k1 (Bitcoin-style): 33-byte compressed pubkey + pub const SECP256K1: u8 = 0x00; + /// Ed25519: 32-byte pubkey + pub const ED25519: u8 = 0x01; + /// secp256r1 (NIST P-256): 33-byte compressed pubkey + pub const SECP256R1: u8 = 0x02; +} + +/// Expected key sizes (prefix + pubkey) +const SECP256K1_KEY_SIZE: usize = 34; // 1 + 33 +const ED25519_KEY_SIZE: usize = 33; // 1 + 32 +const SECP256R1_KEY_SIZE: usize = 34; // 1 + 33 + +/// Validate address/public key size based on algorithm prefix. +/// +/// Keeta public keys have a 1-byte algorithm prefix: +/// - 0x00 (secp256k1): 1 + 33 = 34 bytes +/// - 0x01 (Ed25519): 1 + 32 = 33 bytes +/// - 0x02 (secp256r1): 1 + 33 = 34 bytes +/// +/// Returns `true` if the address has valid size for its algorithm prefix. +#[inline] +pub fn is_valid_address(addr: &[u8]) -> bool { + if addr.is_empty() { + return false; + } + match addr[0] { + algo_prefix::SECP256K1 => addr.len() == SECP256K1_KEY_SIZE, + algo_prefix::ED25519 => addr.len() == ED25519_KEY_SIZE, + algo_prefix::SECP256R1 => addr.len() == SECP256R1_KEY_SIZE, + _ => false, // Unknown algorithm prefix + } +} + +// ============================================================================ +// Block Types +// ============================================================================ + +/// Block version +/// +/// - V1: Plain SEQUENCE with internal version field = 0 +/// - V2: Wrapped in [1] EXPLICIT context tag +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum BlockVersion { + V1 = 1, + V2 = 2, +} + +impl BlockVersion { + /// V1 blocks have version field 0 internally, V2 blocks use [1] context tag + pub fn from_version_field(version: u8) -> Option { + match version { + 0 => Some(BlockVersion::V1), + _ => None, + } + } + + /// Create from context tag (for V2) + pub fn from_context_tag(tag: u8) -> Option { + match tag { + 1 => Some(BlockVersion::V2), + _ => None, + } + } +} + +/// Block purpose (V2 only) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum BlockPurpose { + Generic = 0, + Fee = 1, + Unknown(u8), +} + +impl From for BlockPurpose { + fn from(value: u8) -> Self { + match value { + 0 => BlockPurpose::Generic, + 1 => BlockPurpose::Fee, + n => BlockPurpose::Unknown(n), + } + } +} + +/// Block header information +#[derive(Debug, Clone)] +pub struct BlockHeader<'a> { + /// Network ID + pub network: u64, + /// Subnet ID (optional) + pub subnet: Option, + /// Idempotent key for deduplication (optional) + pub idempotent: Option<&'a [u8]>, + /// Date as GeneralizedTime raw bytes + pub date: &'a [u8], + /// Block purpose (V2 only, defaults to Generic for V1) + pub purpose: BlockPurpose, + /// Account public key with type prefix + pub account: &'a [u8], + /// Signer public key with type prefix (may be same as account) + pub signer: &'a [u8], + /// Previous block hash (32 bytes) + pub previous: &'a [u8], +} + +/// Parsed Keeta block (unified view for both V1 and V2) +/// +/// Requires the `alloc` or `std` feature (uses Vec for operations). +#[cfg(any(feature = "alloc", feature = "std"))] +#[derive(Debug, Clone)] +pub struct KeetaBlock<'a> { + /// Block version + pub version: BlockVersion, + /// Block header/metadata + pub header: BlockHeader<'a>, + /// Operations in the block + pub operations: Vec>, +} + +#[cfg(any(feature = "alloc", feature = "std"))] +impl<'a> KeetaBlock<'a> { + /// Create a new V1 block with the given header and operations + pub fn new(header: BlockHeader<'a>, operations: Vec>) -> Self { + Self { + version: BlockVersion::V1, + header, + operations, + } + } + + /// Create a new V2 block + pub fn new_v2(header: BlockHeader<'a>, operations: Vec>) -> Self { + Self { + version: BlockVersion::V2, + header, + operations, + } + } + + /// Get an iterator over operations + pub fn iter_operations(&self) -> impl Iterator> { + self.operations.iter() + } + + /// Get the number of operations + pub fn operation_count(&self) -> usize { + self.operations.len() + } +} + +// ============================================================================ +// Enum Types +// ============================================================================ + +/// Adjust method for supply/balance/permissions operations +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum AdjustMethod { + Add = 0, + Remove = 1, + Set = 2, + Unknown(u8), +} + +impl From for AdjustMethod { + fn from(value: u8) -> Self { + match value { + 0 => AdjustMethod::Add, + 1 => AdjustMethod::Remove, + 2 => AdjustMethod::Set, + n => AdjustMethod::Unknown(n), + } + } +} + +/// Adjust method for relative operations (add/remove only, no set) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum AdjustMethodRelative { + Add = 0, + Remove = 1, + Unknown(u8), +} + +impl From for AdjustMethodRelative { + fn from(value: u8) -> Self { + match value { + 0 => AdjustMethodRelative::Add, + 1 => AdjustMethodRelative::Remove, + n => AdjustMethodRelative::Unknown(n), + } + } +} + +// ============================================================================ +// Supporting Types +// ============================================================================ + +/// Token and value pair (used in swap operations) +#[derive(Debug, Clone, Copy)] +pub struct TokenValue<'a> { + /// Token public key + pub token: &'a [u8], + /// Value (rate or amount) + pub value: u64, +} + +/// Fee details for swap operations +#[derive(Debug, Clone, Copy)] +pub struct FeeDetails<'a> { + /// Fee token (None means use sell token) + pub token: Option<&'a [u8]>, + /// Fee value + pub value: u64, +} + +/// Fee details with recipient for match swap +#[derive(Debug, Clone, Copy)] +pub struct FeeDetailsWithRecipient<'a> { + /// Fee token (None means use sell token) + pub token: Option<&'a [u8]>, + /// Fee value + pub value: u64, + /// Fee recipient + pub recipient: &'a [u8], +} + +/// Permission value (base and external) +#[derive(Debug, Clone, Copy)] +pub struct Permission { + pub base: u64, + pub external: u64, +} + +// ============================================================================ +// Operation Structures +// ============================================================================ + +/// [0] SEND operation - Transfer tokens to another account +#[derive(Debug, Clone)] +pub struct SendOp<'a> { + /// Destination account + pub to: &'a [u8], + /// Amount to send + pub amount: u64, + /// Token ID to send + pub token: &'a [u8], + /// External reference (optional) + pub external: Option<&'a [u8]>, +} + +/// [1] SET_REP operation - Set representative for delegation +#[derive(Debug, Clone)] +pub struct SetRepOp<'a> { + /// Representative to delegate to + pub to: &'a [u8], +} + +/// [2] SET_INFO operation - Set account information +#[derive(Debug, Clone)] +pub struct SetInfoOp<'a> { + /// Account name + pub name: &'a [u8], + /// Account description + pub description: &'a [u8], + /// Account metadata + pub metadata: &'a [u8], + /// Default permission (optional) + pub default_permission: Option, +} + +/// [3] MODIFY_PERMISSIONS operation - Modify account permissions +#[derive(Debug, Clone)] +pub struct ModifyPermissionsOp<'a> { + /// Principal to modify permissions for + pub principal: &'a [u8], + /// Method to modify (add/remove/set) + pub method: AdjustMethod, + /// Permissions to modify (None = null/clear) + pub permissions: Option, + /// Target account (optional) + pub target: Option<&'a [u8]>, +} + +/// Identifier creation arguments +#[derive(Debug, Clone)] +pub enum CreateIdentifierArgs<'a> { + /// Multisig creation arguments [7] + Multisig { + signers: &'a [u8], // Raw sequence of octet strings + quorum: u64, + }, + /// Swap creation arguments [8] + Swap { + sell_token_rate: TokenValue<'a>, + buy_token_rate: TokenValue<'a>, + fee_token_rate: Option>, + quantity: u64, + }, +} + +/// [4] CREATE_IDENTIFIER operation - Create token, multisig, or swap +#[derive(Debug, Clone)] +pub struct CreateIdentifierOp<'a> { + /// Identifier to create + pub identifier: &'a [u8], + /// Creation arguments (optional, depends on identifier type) + pub create_arguments: Option>, +} + +/// [5] TOKEN_ADMIN_SUPPLY operation - Modify token supply +#[derive(Debug, Clone, Copy)] +pub struct TokenAdminSupplyOp { + /// Amount to modify + pub amount: u64, + /// Method (add/remove/set) + pub method: AdjustMethod, +} + +/// [6] TOKEN_MODIFY_BALANCE operation - Modify account token balance +#[derive(Debug, Clone)] +pub struct TokenModifyBalanceOp<'a> { + /// Token to modify balance of + pub token: &'a [u8], + /// Amount to modify + pub amount: u64, + /// Method (add/remove/set) + pub method: AdjustMethod, +} + +/// [7] RECEIVE operation - Receive tokens from another account +#[derive(Debug, Clone)] +pub struct ReceiveOp<'a> { + /// Amount to receive + pub amount: u64, + /// Token to receive + pub token: &'a [u8], + /// Sender account + pub from: &'a [u8], + /// Whether amount must match exactly + pub exact: bool, + /// Forward to another account (optional) + pub forward: Option<&'a [u8]>, +} + +/// [8] MANAGE_CERTIFICATE operation - Add or remove certificates +#[derive(Debug, Clone)] +pub struct ManageCertificateOp<'a> { + /// Method (add/remove) + pub method: AdjustMethodRelative, + /// Certificate (if adding) or certificate hash (if removing) + pub certificate_or_hash: &'a [u8], + /// Intermediate certificates (required if adding, must be None if removing) + pub intermediates: Option<&'a [u8]>, +} + +/// [9] MATCH_SWAP operation - Match two swap orders +#[derive(Debug, Clone)] +pub struct MatchSwapOp<'a> { + /// Swap account being used + pub swap: &'a [u8], + /// Other swap account to match against + pub other: &'a [u8], + /// Token being sold and value + pub sell: TokenValue<'a>, + /// Token being bought and value + pub buy: TokenValue<'a>, + /// Fee details with recipient (optional) + pub fee: Option>, +} + +/// [10] CANCEL_SWAP operation - Cancel a swap order +#[derive(Debug, Clone)] +pub struct CancelSwapOp<'a> { + /// Swap account to cancel + pub swap: &'a [u8], + /// Sell token and value being returned + pub sell: TokenValue<'a>, + /// Fee details (optional) + pub fee: Option>, +} + +// ============================================================================ +// Operation Enum +// ============================================================================ + +/// Keeta blockchain operation +#[derive(Debug, Clone)] +pub enum Operation<'a> { + /// [0] Send tokens + Send(SendOp<'a>), + /// [1] Set representative + SetRep(SetRepOp<'a>), + /// [2] Set account info + SetInfo(SetInfoOp<'a>), + /// [3] Modify permissions + ModifyPermissions(ModifyPermissionsOp<'a>), + /// [4] Create identifier (token, multisig, swap) + CreateIdentifier(CreateIdentifierOp<'a>), + /// [5] Token admin supply + TokenAdminSupply(TokenAdminSupplyOp), + /// [6] Token modify balance + TokenModifyBalance(TokenModifyBalanceOp<'a>), + /// [7] Receive tokens + Receive(ReceiveOp<'a>), + /// [8] Manage certificate + ManageCertificate(ManageCertificateOp<'a>), + /// [9] Match swap + MatchSwap(MatchSwapOp<'a>), + /// [10] Cancel swap + CancelSwap(CancelSwapOp<'a>), + /// Unknown operation type + Unknown(u32), +} From 788a2a48fa631a5677688e04d020cf433849df80 Mon Sep 17 00:00:00 2001 From: mamonet Date: Sat, 10 Jan 2026 23:19:25 +0100 Subject: [PATCH 02/25] Store DER integers as `&'a [u8]` --- keetanetwork-block/src/types.rs | 74 ++++++++------------------------- 1 file changed, 18 insertions(+), 56 deletions(-) diff --git a/keetanetwork-block/src/types.rs b/keetanetwork-block/src/types.rs index b1537b6..51bb310 100644 --- a/keetanetwork-block/src/types.rs +++ b/keetanetwork-block/src/types.rs @@ -29,46 +29,6 @@ use alloc::vec::Vec; #[cfg(feature = "std")] use std::vec::Vec; -// ============================================================================ -// Address/Key Validation -// ============================================================================ - -/// Algorithm prefix bytes for Keeta public keys -pub mod algo_prefix { - /// secp256k1 (Bitcoin-style): 33-byte compressed pubkey - pub const SECP256K1: u8 = 0x00; - /// Ed25519: 32-byte pubkey - pub const ED25519: u8 = 0x01; - /// secp256r1 (NIST P-256): 33-byte compressed pubkey - pub const SECP256R1: u8 = 0x02; -} - -/// Expected key sizes (prefix + pubkey) -const SECP256K1_KEY_SIZE: usize = 34; // 1 + 33 -const ED25519_KEY_SIZE: usize = 33; // 1 + 32 -const SECP256R1_KEY_SIZE: usize = 34; // 1 + 33 - -/// Validate address/public key size based on algorithm prefix. -/// -/// Keeta public keys have a 1-byte algorithm prefix: -/// - 0x00 (secp256k1): 1 + 33 = 34 bytes -/// - 0x01 (Ed25519): 1 + 32 = 33 bytes -/// - 0x02 (secp256r1): 1 + 33 = 34 bytes -/// -/// Returns `true` if the address has valid size for its algorithm prefix. -#[inline] -pub fn is_valid_address(addr: &[u8]) -> bool { - if addr.is_empty() { - return false; - } - match addr[0] { - algo_prefix::SECP256K1 => addr.len() == SECP256K1_KEY_SIZE, - algo_prefix::ED25519 => addr.len() == ED25519_KEY_SIZE, - algo_prefix::SECP256R1 => addr.len() == SECP256R1_KEY_SIZE, - _ => false, // Unknown algorithm prefix - } -} - // ============================================================================ // Block Types // ============================================================================ @@ -241,7 +201,7 @@ pub struct TokenValue<'a> { /// Token public key pub token: &'a [u8], /// Value (rate or amount) - pub value: u64, + pub value: &'a [u8], } /// Fee details for swap operations @@ -250,7 +210,7 @@ pub struct FeeDetails<'a> { /// Fee token (None means use sell token) pub token: Option<&'a [u8]>, /// Fee value - pub value: u64, + pub value: &'a [u8], } /// Fee details with recipient for match swap @@ -259,16 +219,18 @@ pub struct FeeDetailsWithRecipient<'a> { /// Fee token (None means use sell token) pub token: Option<&'a [u8]>, /// Fee value - pub value: u64, + pub value: &'a [u8], /// Fee recipient pub recipient: &'a [u8], } /// Permission value (base and external) #[derive(Debug, Clone, Copy)] -pub struct Permission { - pub base: u64, - pub external: u64, +pub struct Permission<'a> { + /// Base permissions + pub base: &'a [u8], + /// External permissions + pub external: &'a [u8], } // ============================================================================ @@ -281,7 +243,7 @@ pub struct SendOp<'a> { /// Destination account pub to: &'a [u8], /// Amount to send - pub amount: u64, + pub amount: &'a [u8], /// Token ID to send pub token: &'a [u8], /// External reference (optional) @@ -305,7 +267,7 @@ pub struct SetInfoOp<'a> { /// Account metadata pub metadata: &'a [u8], /// Default permission (optional) - pub default_permission: Option, + pub default_permission: Option>, } /// [3] MODIFY_PERMISSIONS operation - Modify account permissions @@ -316,7 +278,7 @@ pub struct ModifyPermissionsOp<'a> { /// Method to modify (add/remove/set) pub method: AdjustMethod, /// Permissions to modify (None = null/clear) - pub permissions: Option, + pub permissions: Option>, /// Target account (optional) pub target: Option<&'a [u8]>, } @@ -327,14 +289,14 @@ pub enum CreateIdentifierArgs<'a> { /// Multisig creation arguments [7] Multisig { signers: &'a [u8], // Raw sequence of octet strings - quorum: u64, + quorum: &'a [u8], }, /// Swap creation arguments [8] Swap { sell_token_rate: TokenValue<'a>, buy_token_rate: TokenValue<'a>, fee_token_rate: Option>, - quantity: u64, + quantity: &'a [u8], }, } @@ -349,9 +311,9 @@ pub struct CreateIdentifierOp<'a> { /// [5] TOKEN_ADMIN_SUPPLY operation - Modify token supply #[derive(Debug, Clone, Copy)] -pub struct TokenAdminSupplyOp { +pub struct TokenAdminSupplyOp<'a> { /// Amount to modify - pub amount: u64, + pub amount: &'a [u8], /// Method (add/remove/set) pub method: AdjustMethod, } @@ -362,7 +324,7 @@ pub struct TokenModifyBalanceOp<'a> { /// Token to modify balance of pub token: &'a [u8], /// Amount to modify - pub amount: u64, + pub amount: &'a [u8], /// Method (add/remove/set) pub method: AdjustMethod, } @@ -371,7 +333,7 @@ pub struct TokenModifyBalanceOp<'a> { #[derive(Debug, Clone)] pub struct ReceiveOp<'a> { /// Amount to receive - pub amount: u64, + pub amount: &'a [u8], /// Token to receive pub token: &'a [u8], /// Sender account @@ -437,7 +399,7 @@ pub enum Operation<'a> { /// [4] Create identifier (token, multisig, swap) CreateIdentifier(CreateIdentifierOp<'a>), /// [5] Token admin supply - TokenAdminSupply(TokenAdminSupplyOp), + TokenAdminSupply(TokenAdminSupplyOp<'a>), /// [6] Token modify balance TokenModifyBalance(TokenModifyBalanceOp<'a>), /// [7] Receive tokens From c7e1ce0f1f508d457b6a674db96c0eb6d7e7989d Mon Sep 17 00:00:00 2001 From: mamonet Date: Sat, 10 Jan 2026 23:22:39 +0100 Subject: [PATCH 03/25] Remove address validation exports from public API --- keetanetwork-block/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/keetanetwork-block/src/lib.rs b/keetanetwork-block/src/lib.rs index 489b623..b5e8785 100644 --- a/keetanetwork-block/src/lib.rs +++ b/keetanetwork-block/src/lib.rs @@ -11,8 +11,6 @@ mod types; pub use types::KeetaBlock; pub use types::{ - // Address validation - algo_prefix, is_valid_address, // Block types BlockHeader, BlockPurpose, BlockVersion, // Operation enum From 147ab9c1ea335958b5da2a6cffc7e0285b60ad75 Mon Sep 17 00:00:00 2001 From: mamonet Date: Sun, 11 Jan 2026 08:42:45 +0100 Subject: [PATCH 04/25] Remove `#[repr(u8)]` from enums in types.rs --- keetanetwork-block/src/types.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/keetanetwork-block/src/types.rs b/keetanetwork-block/src/types.rs index 51bb310..c364167 100644 --- a/keetanetwork-block/src/types.rs +++ b/keetanetwork-block/src/types.rs @@ -64,10 +64,9 @@ impl BlockVersion { /// Block purpose (V2 only) #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] pub enum BlockPurpose { - Generic = 0, - Fee = 1, + Generic, + Fee, Unknown(u8), } @@ -153,11 +152,10 @@ impl<'a> KeetaBlock<'a> { /// Adjust method for supply/balance/permissions operations #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] pub enum AdjustMethod { - Add = 0, - Remove = 1, - Set = 2, + Add, + Remove, + Set, Unknown(u8), } @@ -174,10 +172,9 @@ impl From for AdjustMethod { /// Adjust method for relative operations (add/remove only, no set) #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] pub enum AdjustMethodRelative { - Add = 0, - Remove = 1, + Add, + Remove, Unknown(u8), } From a17c01be88110cffe6b6355a404328cd10873ca3 Mon Sep 17 00:00:00 2001 From: mamonet Date: Sun, 11 Jan 2026 08:47:35 +0100 Subject: [PATCH 05/25] Fix formatting for block crate --- keetanetwork-block/src/lib.rs | 39 ++- keetanetwork-block/src/types.rs | 440 ++++++++++++++++---------------- 2 files changed, 243 insertions(+), 236 deletions(-) diff --git a/keetanetwork-block/src/lib.rs b/keetanetwork-block/src/lib.rs index b5e8785..69cb150 100644 --- a/keetanetwork-block/src/lib.rs +++ b/keetanetwork-block/src/lib.rs @@ -11,16 +11,31 @@ mod types; pub use types::KeetaBlock; pub use types::{ - // Block types - BlockHeader, BlockPurpose, BlockVersion, - // Operation enum - Operation, - // Operation structs - CancelSwapOp, CreateIdentifierOp, CreateIdentifierArgs, ManageCertificateOp, MatchSwapOp, - ModifyPermissionsOp, ReceiveOp, SendOp, SetInfoOp, SetRepOp, TokenAdminSupplyOp, - TokenModifyBalanceOp, - // Supporting types - FeeDetails, FeeDetailsWithRecipient, Permission, TokenValue, - // Enums - AdjustMethod, AdjustMethodRelative, + // Enums + AdjustMethod, + AdjustMethodRelative, + // Block types + BlockHeader, + BlockPurpose, + BlockVersion, + // Operation structs + CancelSwapOp, + CreateIdentifierArgs, + CreateIdentifierOp, + // Supporting types + FeeDetails, + FeeDetailsWithRecipient, + ManageCertificateOp, + MatchSwapOp, + ModifyPermissionsOp, + // Operation enum + Operation, + Permission, + ReceiveOp, + SendOp, + SetInfoOp, + SetRepOp, + TokenAdminSupplyOp, + TokenModifyBalanceOp, + TokenValue, }; diff --git a/keetanetwork-block/src/types.rs b/keetanetwork-block/src/types.rs index c364167..e88717d 100644 --- a/keetanetwork-block/src/types.rs +++ b/keetanetwork-block/src/types.rs @@ -40,65 +40,65 @@ use std::vec::Vec; #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum BlockVersion { - V1 = 1, - V2 = 2, + V1 = 1, + V2 = 2, } impl BlockVersion { - /// V1 blocks have version field 0 internally, V2 blocks use [1] context tag - pub fn from_version_field(version: u8) -> Option { - match version { - 0 => Some(BlockVersion::V1), - _ => None, - } - } - - /// Create from context tag (for V2) - pub fn from_context_tag(tag: u8) -> Option { - match tag { - 1 => Some(BlockVersion::V2), - _ => None, - } - } + /// V1 blocks have version field 0 internally, V2 blocks use [1] context tag + pub fn from_version_field(version: u8) -> Option { + match version { + 0 => Some(BlockVersion::V1), + _ => None, + } + } + + /// Create from context tag (for V2) + pub fn from_context_tag(tag: u8) -> Option { + match tag { + 1 => Some(BlockVersion::V2), + _ => None, + } + } } /// Block purpose (V2 only) #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BlockPurpose { - Generic, - Fee, - Unknown(u8), + Generic, + Fee, + Unknown(u8), } impl From for BlockPurpose { - fn from(value: u8) -> Self { - match value { - 0 => BlockPurpose::Generic, - 1 => BlockPurpose::Fee, - n => BlockPurpose::Unknown(n), - } - } + fn from(value: u8) -> Self { + match value { + 0 => BlockPurpose::Generic, + 1 => BlockPurpose::Fee, + n => BlockPurpose::Unknown(n), + } + } } /// Block header information #[derive(Debug, Clone)] pub struct BlockHeader<'a> { - /// Network ID - pub network: u64, - /// Subnet ID (optional) - pub subnet: Option, - /// Idempotent key for deduplication (optional) - pub idempotent: Option<&'a [u8]>, - /// Date as GeneralizedTime raw bytes - pub date: &'a [u8], - /// Block purpose (V2 only, defaults to Generic for V1) - pub purpose: BlockPurpose, - /// Account public key with type prefix - pub account: &'a [u8], - /// Signer public key with type prefix (may be same as account) - pub signer: &'a [u8], - /// Previous block hash (32 bytes) - pub previous: &'a [u8], + /// Network ID + pub network: u64, + /// Subnet ID (optional) + pub subnet: Option, + /// Idempotent key for deduplication (optional) + pub idempotent: Option<&'a [u8]>, + /// Date as GeneralizedTime raw bytes + pub date: &'a [u8], + /// Block purpose (V2 only, defaults to Generic for V1) + pub purpose: BlockPurpose, + /// Account public key with type prefix + pub account: &'a [u8], + /// Signer public key with type prefix (may be same as account) + pub signer: &'a [u8], + /// Previous block hash (32 bytes) + pub previous: &'a [u8], } /// Parsed Keeta block (unified view for both V1 and V2) @@ -107,43 +107,35 @@ pub struct BlockHeader<'a> { #[cfg(any(feature = "alloc", feature = "std"))] #[derive(Debug, Clone)] pub struct KeetaBlock<'a> { - /// Block version - pub version: BlockVersion, - /// Block header/metadata - pub header: BlockHeader<'a>, - /// Operations in the block - pub operations: Vec>, + /// Block version + pub version: BlockVersion, + /// Block header/metadata + pub header: BlockHeader<'a>, + /// Operations in the block + pub operations: Vec>, } #[cfg(any(feature = "alloc", feature = "std"))] impl<'a> KeetaBlock<'a> { - /// Create a new V1 block with the given header and operations - pub fn new(header: BlockHeader<'a>, operations: Vec>) -> Self { - Self { - version: BlockVersion::V1, - header, - operations, - } - } - - /// Create a new V2 block - pub fn new_v2(header: BlockHeader<'a>, operations: Vec>) -> Self { - Self { - version: BlockVersion::V2, - header, - operations, - } - } - - /// Get an iterator over operations - pub fn iter_operations(&self) -> impl Iterator> { - self.operations.iter() - } - - /// Get the number of operations - pub fn operation_count(&self) -> usize { - self.operations.len() - } + /// Create a new V1 block with the given header and operations + pub fn new(header: BlockHeader<'a>, operations: Vec>) -> Self { + Self { version: BlockVersion::V1, header, operations } + } + + /// Create a new V2 block + pub fn new_v2(header: BlockHeader<'a>, operations: Vec>) -> Self { + Self { version: BlockVersion::V2, header, operations } + } + + /// Get an iterator over operations + pub fn iter_operations(&self) -> impl Iterator> { + self.operations.iter() + } + + /// Get the number of operations + pub fn operation_count(&self) -> usize { + self.operations.len() + } } // ============================================================================ @@ -153,39 +145,39 @@ impl<'a> KeetaBlock<'a> { /// Adjust method for supply/balance/permissions operations #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AdjustMethod { - Add, - Remove, - Set, - Unknown(u8), + Add, + Remove, + Set, + Unknown(u8), } impl From for AdjustMethod { - fn from(value: u8) -> Self { - match value { - 0 => AdjustMethod::Add, - 1 => AdjustMethod::Remove, - 2 => AdjustMethod::Set, - n => AdjustMethod::Unknown(n), - } - } + fn from(value: u8) -> Self { + match value { + 0 => AdjustMethod::Add, + 1 => AdjustMethod::Remove, + 2 => AdjustMethod::Set, + n => AdjustMethod::Unknown(n), + } + } } /// Adjust method for relative operations (add/remove only, no set) #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AdjustMethodRelative { - Add, - Remove, - Unknown(u8), + Add, + Remove, + Unknown(u8), } impl From for AdjustMethodRelative { - fn from(value: u8) -> Self { - match value { - 0 => AdjustMethodRelative::Add, - 1 => AdjustMethodRelative::Remove, - n => AdjustMethodRelative::Unknown(n), - } - } + fn from(value: u8) -> Self { + match value { + 0 => AdjustMethodRelative::Add, + 1 => AdjustMethodRelative::Remove, + n => AdjustMethodRelative::Unknown(n), + } + } } // ============================================================================ @@ -195,39 +187,39 @@ impl From for AdjustMethodRelative { /// Token and value pair (used in swap operations) #[derive(Debug, Clone, Copy)] pub struct TokenValue<'a> { - /// Token public key - pub token: &'a [u8], - /// Value (rate or amount) - pub value: &'a [u8], + /// Token public key + pub token: &'a [u8], + /// Value (rate or amount) + pub value: &'a [u8], } /// Fee details for swap operations #[derive(Debug, Clone, Copy)] pub struct FeeDetails<'a> { - /// Fee token (None means use sell token) - pub token: Option<&'a [u8]>, - /// Fee value - pub value: &'a [u8], + /// Fee token (None means use sell token) + pub token: Option<&'a [u8]>, + /// Fee value + pub value: &'a [u8], } /// Fee details with recipient for match swap #[derive(Debug, Clone, Copy)] pub struct FeeDetailsWithRecipient<'a> { - /// Fee token (None means use sell token) - pub token: Option<&'a [u8]>, - /// Fee value - pub value: &'a [u8], - /// Fee recipient - pub recipient: &'a [u8], + /// Fee token (None means use sell token) + pub token: Option<&'a [u8]>, + /// Fee value + pub value: &'a [u8], + /// Fee recipient + pub recipient: &'a [u8], } /// Permission value (base and external) #[derive(Debug, Clone, Copy)] pub struct Permission<'a> { - /// Base permissions - pub base: &'a [u8], - /// External permissions - pub external: &'a [u8], + /// Base permissions + pub base: &'a [u8], + /// External permissions + pub external: &'a [u8], } // ============================================================================ @@ -237,145 +229,145 @@ pub struct Permission<'a> { /// [0] SEND operation - Transfer tokens to another account #[derive(Debug, Clone)] pub struct SendOp<'a> { - /// Destination account - pub to: &'a [u8], - /// Amount to send - pub amount: &'a [u8], - /// Token ID to send - pub token: &'a [u8], - /// External reference (optional) - pub external: Option<&'a [u8]>, + /// Destination account + pub to: &'a [u8], + /// Amount to send + pub amount: &'a [u8], + /// Token ID to send + pub token: &'a [u8], + /// External reference (optional) + pub external: Option<&'a [u8]>, } /// [1] SET_REP operation - Set representative for delegation #[derive(Debug, Clone)] pub struct SetRepOp<'a> { - /// Representative to delegate to - pub to: &'a [u8], + /// Representative to delegate to + pub to: &'a [u8], } /// [2] SET_INFO operation - Set account information #[derive(Debug, Clone)] pub struct SetInfoOp<'a> { - /// Account name - pub name: &'a [u8], - /// Account description - pub description: &'a [u8], - /// Account metadata - pub metadata: &'a [u8], - /// Default permission (optional) - pub default_permission: Option>, + /// Account name + pub name: &'a [u8], + /// Account description + pub description: &'a [u8], + /// Account metadata + pub metadata: &'a [u8], + /// Default permission (optional) + pub default_permission: Option>, } /// [3] MODIFY_PERMISSIONS operation - Modify account permissions #[derive(Debug, Clone)] pub struct ModifyPermissionsOp<'a> { - /// Principal to modify permissions for - pub principal: &'a [u8], - /// Method to modify (add/remove/set) - pub method: AdjustMethod, - /// Permissions to modify (None = null/clear) - pub permissions: Option>, - /// Target account (optional) - pub target: Option<&'a [u8]>, + /// Principal to modify permissions for + pub principal: &'a [u8], + /// Method to modify (add/remove/set) + pub method: AdjustMethod, + /// Permissions to modify (None = null/clear) + pub permissions: Option>, + /// Target account (optional) + pub target: Option<&'a [u8]>, } /// Identifier creation arguments #[derive(Debug, Clone)] pub enum CreateIdentifierArgs<'a> { - /// Multisig creation arguments [7] - Multisig { - signers: &'a [u8], // Raw sequence of octet strings - quorum: &'a [u8], - }, - /// Swap creation arguments [8] - Swap { - sell_token_rate: TokenValue<'a>, - buy_token_rate: TokenValue<'a>, - fee_token_rate: Option>, - quantity: &'a [u8], - }, + /// Multisig creation arguments [7] + Multisig { + signers: &'a [u8], // Raw sequence of octet strings + quorum: &'a [u8], + }, + /// Swap creation arguments [8] + Swap { + sell_token_rate: TokenValue<'a>, + buy_token_rate: TokenValue<'a>, + fee_token_rate: Option>, + quantity: &'a [u8], + }, } /// [4] CREATE_IDENTIFIER operation - Create token, multisig, or swap #[derive(Debug, Clone)] pub struct CreateIdentifierOp<'a> { - /// Identifier to create - pub identifier: &'a [u8], - /// Creation arguments (optional, depends on identifier type) - pub create_arguments: Option>, + /// Identifier to create + pub identifier: &'a [u8], + /// Creation arguments (optional, depends on identifier type) + pub create_arguments: Option>, } /// [5] TOKEN_ADMIN_SUPPLY operation - Modify token supply #[derive(Debug, Clone, Copy)] pub struct TokenAdminSupplyOp<'a> { - /// Amount to modify - pub amount: &'a [u8], - /// Method (add/remove/set) - pub method: AdjustMethod, + /// Amount to modify + pub amount: &'a [u8], + /// Method (add/remove/set) + pub method: AdjustMethod, } /// [6] TOKEN_MODIFY_BALANCE operation - Modify account token balance #[derive(Debug, Clone)] pub struct TokenModifyBalanceOp<'a> { - /// Token to modify balance of - pub token: &'a [u8], - /// Amount to modify - pub amount: &'a [u8], - /// Method (add/remove/set) - pub method: AdjustMethod, + /// Token to modify balance of + pub token: &'a [u8], + /// Amount to modify + pub amount: &'a [u8], + /// Method (add/remove/set) + pub method: AdjustMethod, } /// [7] RECEIVE operation - Receive tokens from another account #[derive(Debug, Clone)] pub struct ReceiveOp<'a> { - /// Amount to receive - pub amount: &'a [u8], - /// Token to receive - pub token: &'a [u8], - /// Sender account - pub from: &'a [u8], - /// Whether amount must match exactly - pub exact: bool, - /// Forward to another account (optional) - pub forward: Option<&'a [u8]>, + /// Amount to receive + pub amount: &'a [u8], + /// Token to receive + pub token: &'a [u8], + /// Sender account + pub from: &'a [u8], + /// Whether amount must match exactly + pub exact: bool, + /// Forward to another account (optional) + pub forward: Option<&'a [u8]>, } /// [8] MANAGE_CERTIFICATE operation - Add or remove certificates #[derive(Debug, Clone)] pub struct ManageCertificateOp<'a> { - /// Method (add/remove) - pub method: AdjustMethodRelative, - /// Certificate (if adding) or certificate hash (if removing) - pub certificate_or_hash: &'a [u8], - /// Intermediate certificates (required if adding, must be None if removing) - pub intermediates: Option<&'a [u8]>, + /// Method (add/remove) + pub method: AdjustMethodRelative, + /// Certificate (if adding) or certificate hash (if removing) + pub certificate_or_hash: &'a [u8], + /// Intermediate certificates (required if adding, must be None if removing) + pub intermediates: Option<&'a [u8]>, } /// [9] MATCH_SWAP operation - Match two swap orders #[derive(Debug, Clone)] pub struct MatchSwapOp<'a> { - /// Swap account being used - pub swap: &'a [u8], - /// Other swap account to match against - pub other: &'a [u8], - /// Token being sold and value - pub sell: TokenValue<'a>, - /// Token being bought and value - pub buy: TokenValue<'a>, - /// Fee details with recipient (optional) - pub fee: Option>, + /// Swap account being used + pub swap: &'a [u8], + /// Other swap account to match against + pub other: &'a [u8], + /// Token being sold and value + pub sell: TokenValue<'a>, + /// Token being bought and value + pub buy: TokenValue<'a>, + /// Fee details with recipient (optional) + pub fee: Option>, } /// [10] CANCEL_SWAP operation - Cancel a swap order #[derive(Debug, Clone)] pub struct CancelSwapOp<'a> { - /// Swap account to cancel - pub swap: &'a [u8], - /// Sell token and value being returned - pub sell: TokenValue<'a>, - /// Fee details (optional) - pub fee: Option>, + /// Swap account to cancel + pub swap: &'a [u8], + /// Sell token and value being returned + pub sell: TokenValue<'a>, + /// Fee details (optional) + pub fee: Option>, } // ============================================================================ @@ -385,28 +377,28 @@ pub struct CancelSwapOp<'a> { /// Keeta blockchain operation #[derive(Debug, Clone)] pub enum Operation<'a> { - /// [0] Send tokens - Send(SendOp<'a>), - /// [1] Set representative - SetRep(SetRepOp<'a>), - /// [2] Set account info - SetInfo(SetInfoOp<'a>), - /// [3] Modify permissions - ModifyPermissions(ModifyPermissionsOp<'a>), - /// [4] Create identifier (token, multisig, swap) - CreateIdentifier(CreateIdentifierOp<'a>), - /// [5] Token admin supply - TokenAdminSupply(TokenAdminSupplyOp<'a>), - /// [6] Token modify balance - TokenModifyBalance(TokenModifyBalanceOp<'a>), - /// [7] Receive tokens - Receive(ReceiveOp<'a>), - /// [8] Manage certificate - ManageCertificate(ManageCertificateOp<'a>), - /// [9] Match swap - MatchSwap(MatchSwapOp<'a>), - /// [10] Cancel swap - CancelSwap(CancelSwapOp<'a>), - /// Unknown operation type - Unknown(u32), + /// [0] Send tokens + Send(SendOp<'a>), + /// [1] Set representative + SetRep(SetRepOp<'a>), + /// [2] Set account info + SetInfo(SetInfoOp<'a>), + /// [3] Modify permissions + ModifyPermissions(ModifyPermissionsOp<'a>), + /// [4] Create identifier (token, multisig, swap) + CreateIdentifier(CreateIdentifierOp<'a>), + /// [5] Token admin supply + TokenAdminSupply(TokenAdminSupplyOp<'a>), + /// [6] Token modify balance + TokenModifyBalance(TokenModifyBalanceOp<'a>), + /// [7] Receive tokens + Receive(ReceiveOp<'a>), + /// [8] Manage certificate + ManageCertificate(ManageCertificateOp<'a>), + /// [9] Match swap + MatchSwap(MatchSwapOp<'a>), + /// [10] Cancel swap + CancelSwap(CancelSwapOp<'a>), + /// Unknown operation type + Unknown(u32), } From 8e550f2cf7623715a743b890e83e5b3744587cc6 Mon Sep 17 00:00:00 2001 From: mamonet Date: Tue, 13 Jan 2026 16:59:15 +0100 Subject: [PATCH 06/25] Use typed integers and fallible enum parsing in block types --- keetanetwork-block/Cargo.toml | 1 + keetanetwork-block/src/lib.rs | 2 +- keetanetwork-block/src/types.rs | 99 +++++++++++++++++---------------- 3 files changed, 53 insertions(+), 49 deletions(-) diff --git a/keetanetwork-block/Cargo.toml b/keetanetwork-block/Cargo.toml index c293a3a..e80efe6 100644 --- a/keetanetwork-block/Cargo.toml +++ b/keetanetwork-block/Cargo.toml @@ -14,6 +14,7 @@ std = ["alloc"] alloc = [] [dependencies] +crypto-bigint = { version = "0.5", default-features = false } [dev-dependencies] diff --git a/keetanetwork-block/src/lib.rs b/keetanetwork-block/src/lib.rs index 69cb150..49333c4 100644 --- a/keetanetwork-block/src/lib.rs +++ b/keetanetwork-block/src/lib.rs @@ -36,6 +36,6 @@ pub use types::{ SetInfoOp, SetRepOp, TokenAdminSupplyOp, - TokenModifyBalanceOp, + TokenAdminModifyBalanceOp, TokenValue, }; diff --git a/keetanetwork-block/src/types.rs b/keetanetwork-block/src/types.rs index e88717d..fe4928e 100644 --- a/keetanetwork-block/src/types.rs +++ b/keetanetwork-block/src/types.rs @@ -29,6 +29,8 @@ use alloc::vec::Vec; #[cfg(feature = "std")] use std::vec::Vec; +use crypto_bigint::U128; + // ============================================================================ // Block Types // ============================================================================ @@ -67,15 +69,16 @@ impl BlockVersion { pub enum BlockPurpose { Generic, Fee, - Unknown(u8), } -impl From for BlockPurpose { - fn from(value: u8) -> Self { +impl TryFrom for BlockPurpose { + type Error = u8; + + fn try_from(value: u8) -> Result { match value { - 0 => BlockPurpose::Generic, - 1 => BlockPurpose::Fee, - n => BlockPurpose::Unknown(n), + 0 => Ok(BlockPurpose::Generic), + 1 => Ok(BlockPurpose::Fee), + n => Err(n), } } } @@ -146,36 +149,38 @@ impl<'a> KeetaBlock<'a> { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AdjustMethod { Add, - Remove, + Subtract, Set, - Unknown(u8), } -impl From for AdjustMethod { - fn from(value: u8) -> Self { +impl TryFrom for AdjustMethod { + type Error = u8; + + fn try_from(value: u8) -> Result { match value { - 0 => AdjustMethod::Add, - 1 => AdjustMethod::Remove, - 2 => AdjustMethod::Set, - n => AdjustMethod::Unknown(n), + 0 => Ok(AdjustMethod::Add), + 1 => Ok(AdjustMethod::Subtract), + 2 => Ok(AdjustMethod::Set), + n => Err(n), } } } -/// Adjust method for relative operations (add/remove only, no set) +/// Adjust method for relative operations (add/subtract only, no set) #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AdjustMethodRelative { Add, - Remove, - Unknown(u8), + Subtract, } -impl From for AdjustMethodRelative { - fn from(value: u8) -> Self { +impl TryFrom for AdjustMethodRelative { + type Error = u8; + + fn try_from(value: u8) -> Result { match value { - 0 => AdjustMethodRelative::Add, - 1 => AdjustMethodRelative::Remove, - n => AdjustMethodRelative::Unknown(n), + 0 => Ok(AdjustMethodRelative::Add), + 1 => Ok(AdjustMethodRelative::Subtract), + n => Err(n), } } } @@ -190,7 +195,7 @@ pub struct TokenValue<'a> { /// Token public key pub token: &'a [u8], /// Value (rate or amount) - pub value: &'a [u8], + pub value: U128, } /// Fee details for swap operations @@ -199,7 +204,7 @@ pub struct FeeDetails<'a> { /// Fee token (None means use sell token) pub token: Option<&'a [u8]>, /// Fee value - pub value: &'a [u8], + pub value: U128, } /// Fee details with recipient for match swap @@ -208,18 +213,18 @@ pub struct FeeDetailsWithRecipient<'a> { /// Fee token (None means use sell token) pub token: Option<&'a [u8]>, /// Fee value - pub value: &'a [u8], + pub value: U128, /// Fee recipient pub recipient: &'a [u8], } /// Permission value (base and external) #[derive(Debug, Clone, Copy)] -pub struct Permission<'a> { +pub struct Permission { /// Base permissions - pub base: &'a [u8], + pub base: u64, /// External permissions - pub external: &'a [u8], + pub external: u64, } // ============================================================================ @@ -232,7 +237,7 @@ pub struct SendOp<'a> { /// Destination account pub to: &'a [u8], /// Amount to send - pub amount: &'a [u8], + pub amount: U128, /// Token ID to send pub token: &'a [u8], /// External reference (optional) @@ -256,7 +261,7 @@ pub struct SetInfoOp<'a> { /// Account metadata pub metadata: &'a [u8], /// Default permission (optional) - pub default_permission: Option>, + pub default_permission: Option, } /// [3] MODIFY_PERMISSIONS operation - Modify account permissions @@ -264,10 +269,10 @@ pub struct SetInfoOp<'a> { pub struct ModifyPermissionsOp<'a> { /// Principal to modify permissions for pub principal: &'a [u8], - /// Method to modify (add/remove/set) + /// Method to modify (add/subtract/set) pub method: AdjustMethod, /// Permissions to modify (None = null/clear) - pub permissions: Option>, + pub permissions: Option, /// Target account (optional) pub target: Option<&'a [u8]>, } @@ -278,14 +283,14 @@ pub enum CreateIdentifierArgs<'a> { /// Multisig creation arguments [7] Multisig { signers: &'a [u8], // Raw sequence of octet strings - quorum: &'a [u8], + quorum: u64, }, /// Swap creation arguments [8] Swap { sell_token_rate: TokenValue<'a>, buy_token_rate: TokenValue<'a>, fee_token_rate: Option>, - quantity: &'a [u8], + quantity: U128, }, } @@ -300,21 +305,21 @@ pub struct CreateIdentifierOp<'a> { /// [5] TOKEN_ADMIN_SUPPLY operation - Modify token supply #[derive(Debug, Clone, Copy)] -pub struct TokenAdminSupplyOp<'a> { +pub struct TokenAdminSupplyOp { /// Amount to modify - pub amount: &'a [u8], - /// Method (add/remove/set) + pub amount: U128, + /// Method (add/subtract/set) pub method: AdjustMethod, } -/// [6] TOKEN_MODIFY_BALANCE operation - Modify account token balance +/// [6] TOKEN_ADMIN_MODIFY_BALANCE operation - Modify account token balance #[derive(Debug, Clone)] -pub struct TokenModifyBalanceOp<'a> { +pub struct TokenAdminModifyBalanceOp<'a> { /// Token to modify balance of pub token: &'a [u8], /// Amount to modify - pub amount: &'a [u8], - /// Method (add/remove/set) + pub amount: U128, + /// Method (add/subtract/set) pub method: AdjustMethod, } @@ -322,7 +327,7 @@ pub struct TokenModifyBalanceOp<'a> { #[derive(Debug, Clone)] pub struct ReceiveOp<'a> { /// Amount to receive - pub amount: &'a [u8], + pub amount: U128, /// Token to receive pub token: &'a [u8], /// Sender account @@ -333,10 +338,10 @@ pub struct ReceiveOp<'a> { pub forward: Option<&'a [u8]>, } -/// [8] MANAGE_CERTIFICATE operation - Add or remove certificates +/// [8] MANAGE_CERTIFICATE operation - Add or subtract certificates #[derive(Debug, Clone)] pub struct ManageCertificateOp<'a> { - /// Method (add/remove) + /// Method (add/subtract) pub method: AdjustMethodRelative, /// Certificate (if adding) or certificate hash (if removing) pub certificate_or_hash: &'a [u8], @@ -388,9 +393,9 @@ pub enum Operation<'a> { /// [4] Create identifier (token, multisig, swap) CreateIdentifier(CreateIdentifierOp<'a>), /// [5] Token admin supply - TokenAdminSupply(TokenAdminSupplyOp<'a>), + TokenAdminSupply(TokenAdminSupplyOp), /// [6] Token modify balance - TokenModifyBalance(TokenModifyBalanceOp<'a>), + TokenModifyBalance(TokenAdminModifyBalanceOp<'a>), /// [7] Receive tokens Receive(ReceiveOp<'a>), /// [8] Manage certificate @@ -399,6 +404,4 @@ pub enum Operation<'a> { MatchSwap(MatchSwapOp<'a>), /// [10] Cancel swap CancelSwap(CancelSwapOp<'a>), - /// Unknown operation type - Unknown(u32), } From ce141dbbeb47758d5db68cf42f94e5c5d44a4cc6 Mon Sep 17 00:00:00 2001 From: mamonet Date: Wed, 14 Jan 2026 00:45:47 +0100 Subject: [PATCH 07/25] Add KeetaBlockBuilder and replace U128 with byte slices --- keetanetwork-block/Cargo.toml | 1 - keetanetwork-block/src/lib.rs | 6 ++-- keetanetwork-block/src/types.rs | 59 +++++++++++++++++++-------------- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/keetanetwork-block/Cargo.toml b/keetanetwork-block/Cargo.toml index e80efe6..c293a3a 100644 --- a/keetanetwork-block/Cargo.toml +++ b/keetanetwork-block/Cargo.toml @@ -14,7 +14,6 @@ std = ["alloc"] alloc = [] [dependencies] -crypto-bigint = { version = "0.5", default-features = false } [dev-dependencies] diff --git a/keetanetwork-block/src/lib.rs b/keetanetwork-block/src/lib.rs index 49333c4..6e96028 100644 --- a/keetanetwork-block/src/lib.rs +++ b/keetanetwork-block/src/lib.rs @@ -6,9 +6,9 @@ mod types; -// KeetaBlock requires alloc (uses Vec) +// KeetaBlock and builder require alloc (uses Vec) #[cfg(any(feature = "alloc", feature = "std"))] -pub use types::KeetaBlock; +pub use types::{KeetaBlock, KeetaBlockBuilder}; pub use types::{ // Enums @@ -35,7 +35,7 @@ pub use types::{ SendOp, SetInfoOp, SetRepOp, - TokenAdminSupplyOp, TokenAdminModifyBalanceOp, + TokenAdminSupplyOp, TokenValue, }; diff --git a/keetanetwork-block/src/types.rs b/keetanetwork-block/src/types.rs index fe4928e..605b754 100644 --- a/keetanetwork-block/src/types.rs +++ b/keetanetwork-block/src/types.rs @@ -29,8 +29,6 @@ use alloc::vec::Vec; #[cfg(feature = "std")] use std::vec::Vec; -use crypto_bigint::U128; - // ============================================================================ // Block Types // ============================================================================ @@ -118,26 +116,37 @@ pub struct KeetaBlock<'a> { pub operations: Vec>, } +/// Builder for constructing KeetaBlock instances +#[cfg(any(feature = "alloc", feature = "std"))] +#[derive(Debug, Clone)] +pub struct KeetaBlockBuilder<'a> { + version: BlockVersion, + header: BlockHeader<'a>, + operations: Vec>, +} + #[cfg(any(feature = "alloc", feature = "std"))] -impl<'a> KeetaBlock<'a> { - /// Create a new V1 block with the given header and operations - pub fn new(header: BlockHeader<'a>, operations: Vec>) -> Self { - Self { version: BlockVersion::V1, header, operations } +impl<'a> KeetaBlockBuilder<'a> { + /// Create a new builder with the specified block version and header + pub fn new(version: BlockVersion, header: BlockHeader<'a>) -> Self { + Self { version, header, operations: Vec::new() } } - /// Create a new V2 block - pub fn new_v2(header: BlockHeader<'a>, operations: Vec>) -> Self { - Self { version: BlockVersion::V2, header, operations } + /// Set the operations + pub fn operations(mut self, operations: Vec>) -> Self { + self.operations = operations; + self } - /// Get an iterator over operations - pub fn iter_operations(&self) -> impl Iterator> { - self.operations.iter() + /// Add a single operation + pub fn operation(mut self, operation: Operation<'a>) -> Self { + self.operations.push(operation); + self } - /// Get the number of operations - pub fn operation_count(&self) -> usize { - self.operations.len() + /// Build the KeetaBlock + pub fn build(self) -> KeetaBlock<'a> { + KeetaBlock { version: self.version, header: self.header, operations: self.operations } } } @@ -195,7 +204,7 @@ pub struct TokenValue<'a> { /// Token public key pub token: &'a [u8], /// Value (rate or amount) - pub value: U128, + pub value: &'a [u8], } /// Fee details for swap operations @@ -204,7 +213,7 @@ pub struct FeeDetails<'a> { /// Fee token (None means use sell token) pub token: Option<&'a [u8]>, /// Fee value - pub value: U128, + pub value: &'a [u8], } /// Fee details with recipient for match swap @@ -213,7 +222,7 @@ pub struct FeeDetailsWithRecipient<'a> { /// Fee token (None means use sell token) pub token: Option<&'a [u8]>, /// Fee value - pub value: U128, + pub value: &'a [u8], /// Fee recipient pub recipient: &'a [u8], } @@ -237,7 +246,7 @@ pub struct SendOp<'a> { /// Destination account pub to: &'a [u8], /// Amount to send - pub amount: U128, + pub amount: &'a [u8], /// Token ID to send pub token: &'a [u8], /// External reference (optional) @@ -290,7 +299,7 @@ pub enum CreateIdentifierArgs<'a> { sell_token_rate: TokenValue<'a>, buy_token_rate: TokenValue<'a>, fee_token_rate: Option>, - quantity: U128, + quantity: &'a [u8], }, } @@ -305,9 +314,9 @@ pub struct CreateIdentifierOp<'a> { /// [5] TOKEN_ADMIN_SUPPLY operation - Modify token supply #[derive(Debug, Clone, Copy)] -pub struct TokenAdminSupplyOp { +pub struct TokenAdminSupplyOp<'a> { /// Amount to modify - pub amount: U128, + pub amount: &'a [u8], /// Method (add/subtract/set) pub method: AdjustMethod, } @@ -318,7 +327,7 @@ pub struct TokenAdminModifyBalanceOp<'a> { /// Token to modify balance of pub token: &'a [u8], /// Amount to modify - pub amount: U128, + pub amount: &'a [u8], /// Method (add/subtract/set) pub method: AdjustMethod, } @@ -327,7 +336,7 @@ pub struct TokenAdminModifyBalanceOp<'a> { #[derive(Debug, Clone)] pub struct ReceiveOp<'a> { /// Amount to receive - pub amount: U128, + pub amount: &'a [u8], /// Token to receive pub token: &'a [u8], /// Sender account @@ -393,7 +402,7 @@ pub enum Operation<'a> { /// [4] Create identifier (token, multisig, swap) CreateIdentifier(CreateIdentifierOp<'a>), /// [5] Token admin supply - TokenAdminSupply(TokenAdminSupplyOp), + TokenAdminSupply(TokenAdminSupplyOp<'a>), /// [6] Token modify balance TokenModifyBalance(TokenAdminModifyBalanceOp<'a>), /// [7] Receive tokens From 0f2914b5eebdb7117c266736f4ec77eb7a0b36e6 Mon Sep 17 00:00:00 2001 From: mamonet Date: Wed, 14 Jan 2026 18:55:26 +0100 Subject: [PATCH 08/25] Add signatures to KeetaBlock --- keetanetwork-block/src/types.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/keetanetwork-block/src/types.rs b/keetanetwork-block/src/types.rs index 605b754..a85c511 100644 --- a/keetanetwork-block/src/types.rs +++ b/keetanetwork-block/src/types.rs @@ -114,6 +114,8 @@ pub struct KeetaBlock<'a> { pub header: BlockHeader<'a>, /// Operations in the block pub operations: Vec>, + /// Signatures (V1: single, V2: one or more) + pub signatures: Vec<&'a [u8]>, } /// Builder for constructing KeetaBlock instances @@ -123,13 +125,14 @@ pub struct KeetaBlockBuilder<'a> { version: BlockVersion, header: BlockHeader<'a>, operations: Vec>, + signatures: Vec<&'a [u8]>, } #[cfg(any(feature = "alloc", feature = "std"))] impl<'a> KeetaBlockBuilder<'a> { /// Create a new builder with the specified block version and header pub fn new(version: BlockVersion, header: BlockHeader<'a>) -> Self { - Self { version, header, operations: Vec::new() } + Self { version, header, operations: Vec::new(), signatures: Vec::new() } } /// Set the operations @@ -144,9 +147,26 @@ impl<'a> KeetaBlockBuilder<'a> { self } + /// Set the signatures + pub fn signatures(mut self, signatures: Vec<&'a [u8]>) -> Self { + self.signatures = signatures; + self + } + + /// Add a single signature + pub fn signature(mut self, signature: &'a [u8]) -> Self { + self.signatures.push(signature); + self + } + /// Build the KeetaBlock pub fn build(self) -> KeetaBlock<'a> { - KeetaBlock { version: self.version, header: self.header, operations: self.operations } + KeetaBlock { + version: self.version, + header: self.header, + operations: self.operations, + signatures: self.signatures, + } } } From 82e515f2d8774c3415e314d732fb4b139be32a22 Mon Sep 17 00:00:00 2001 From: mamonet Date: Wed, 14 Jan 2026 19:43:11 +0100 Subject: [PATCH 09/25] Add multisig signer types for V2 blocks --- keetanetwork-block/src/types.rs | 46 +++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/keetanetwork-block/src/types.rs b/keetanetwork-block/src/types.rs index a85c511..90beeb9 100644 --- a/keetanetwork-block/src/types.rs +++ b/keetanetwork-block/src/types.rs @@ -96,8 +96,8 @@ pub struct BlockHeader<'a> { pub purpose: BlockPurpose, /// Account public key with type prefix pub account: &'a [u8], - /// Signer public key with type prefix (may be same as account) - pub signer: &'a [u8], + /// Signer information (V1: always Single, V2: can be Single/Multisig/AccountIsSigner) + pub signer: SignerField<'a>, /// Previous block hash (32 bytes) pub previous: &'a [u8], } @@ -170,6 +170,48 @@ impl<'a> KeetaBlockBuilder<'a> { } } +// ============================================================================ +// Signer Types +// ============================================================================ + +/// Individual signer in a multisig (can be nested) +#[cfg(any(feature = "alloc", feature = "std"))] +#[derive(Debug, Clone)] +pub enum MultiSigSigner<'a> { + /// Nested multisig signer info + Nested(Box>), + /// Single key signer (public key with type prefix) + Key(&'a [u8]), +} + +/// Multisig signer information for V2 blocks +#[derive(Debug, Clone)] +#[cfg_attr(not(any(feature = "alloc", feature = "std")), derive(Copy))] +pub struct MultiSigSignerInfo<'a> { + /// Public key of the multisig account + pub multisig_pub_key: &'a [u8], + /// signers (can be nested multisig or single keys) + #[cfg(any(feature = "alloc", feature = "std"))] + pub signers: Vec>, + /// Raw DER bytes of the signers sequence + #[cfg(not(any(feature = "alloc", feature = "std")))] + pub signers_raw: &'a [u8], +} + +/// Signer field for blocks +/// +/// V1 blocks always use Single. V2 blocks can use any variant. +#[derive(Debug, Clone)] +#[cfg_attr(not(any(feature = "alloc", feature = "std")), derive(Copy))] +pub enum SignerField<'a> { + /// Single signer (public key with type prefix) + Single(&'a [u8]), + /// Multisig signer info (V2 only) + Multisig(MultiSigSignerInfo<'a>), + /// Account is signer (null case, V2 only) + AccountIsSigner, +} + // ============================================================================ // Enum Types // ============================================================================ From 880d5d2d4751f366f9490e1e52e00b29d5c219ea Mon Sep 17 00:00:00 2001 From: mamonet Date: Thu, 15 Jan 2026 15:48:27 +0100 Subject: [PATCH 10/25] Add vote types and separate rate/value swap types --- keetanetwork-block/src/types.rs | 182 +++++++++++++++++++++++++++----- 1 file changed, 157 insertions(+), 25 deletions(-) diff --git a/keetanetwork-block/src/types.rs b/keetanetwork-block/src/types.rs index 90beeb9..1764978 100644 --- a/keetanetwork-block/src/types.rs +++ b/keetanetwork-block/src/types.rs @@ -13,7 +13,7 @@ //! | [3] | ModifyPermissions | //! | [4] | CreateIdentifier | //! | [5] | TokenAdminSupply | -//! | [6] | TokenModifyBalance | +//! | [6] | TokenAdminModifyBalance | //! | [7] | Receive | //! | [8] | ManageCertificate | //! | [9] | MatchSwap | @@ -35,8 +35,8 @@ use std::vec::Vec; /// Block version /// -/// - V1: Plain SEQUENCE with internal version field = 0 -/// - V2: Wrapped in [1] EXPLICIT context tag +/// - V1: Unwrapped format with internal version field = 0 +/// - V2: Tagged format (explicit context tag 1) #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum BlockVersion { @@ -45,7 +45,7 @@ pub enum BlockVersion { } impl BlockVersion { - /// V1 blocks have version field 0 internally, V2 blocks use [1] context tag + /// V1 blocks have internal version field 0, V2 blocks use tag 1 pub fn from_version_field(version: u8) -> Option { match version { 0 => Some(BlockVersion::V1), @@ -53,7 +53,7 @@ impl BlockVersion { } } - /// Create from context tag (for V2) + /// Create from tag value (for V2 blocks) pub fn from_context_tag(tag: u8) -> Option { match tag { 1 => Some(BlockVersion::V2), @@ -90,7 +90,7 @@ pub struct BlockHeader<'a> { pub subnet: Option, /// Idempotent key for deduplication (optional) pub idempotent: Option<&'a [u8]>, - /// Date as GeneralizedTime raw bytes + /// Block timestamp as raw bytes pub date: &'a [u8], /// Block purpose (V2 only, defaults to Generic for V1) pub purpose: BlockPurpose, @@ -103,8 +103,6 @@ pub struct BlockHeader<'a> { } /// Parsed Keeta block (unified view for both V1 and V2) -/// -/// Requires the `alloc` or `std` feature (uses Vec for operations). #[cfg(any(feature = "alloc", feature = "std"))] #[derive(Debug, Clone)] pub struct KeetaBlock<'a> { @@ -260,27 +258,45 @@ impl TryFrom for AdjustMethodRelative { // Supporting Types // ============================================================================ -/// Token and value pair (used in swap operations) +/// Token and rate pair (used in CREATE_IDENTIFIER swap arguments) +#[derive(Debug, Clone, Copy)] +pub struct TokenRate<'a> { + /// Token public key + pub token: &'a [u8], + /// Rate + pub rate: &'a [u8], +} + +/// Fee rate for CREATE_IDENTIFIER swap arguments +#[derive(Debug, Clone, Copy)] +pub struct FeeRate<'a> { + /// Fee token (None means use sell token) + pub token: Option<&'a [u8]>, + /// Fee rate + pub rate: &'a [u8], +} + +/// Token and value pair (used in MATCH_SWAP/CANCEL_SWAP operations) #[derive(Debug, Clone, Copy)] pub struct TokenValue<'a> { /// Token public key pub token: &'a [u8], - /// Value (rate or amount) + /// Value pub value: &'a [u8], } -/// Fee details for swap operations +/// Fee value for CANCEL_SWAP operation #[derive(Debug, Clone, Copy)] -pub struct FeeDetails<'a> { +pub struct FeeValue<'a> { /// Fee token (None means use sell token) pub token: Option<&'a [u8]>, /// Fee value pub value: &'a [u8], } -/// Fee details with recipient for match swap +/// Fee value with recipient for MATCH_SWAP operation #[derive(Debug, Clone, Copy)] -pub struct FeeDetailsWithRecipient<'a> { +pub struct FeeValueWithRecipient<'a> { /// Fee token (None means use sell token) pub token: Option<&'a [u8]>, /// Fee value @@ -353,14 +369,16 @@ pub struct ModifyPermissionsOp<'a> { pub enum CreateIdentifierArgs<'a> { /// Multisig creation arguments [7] Multisig { - signers: &'a [u8], // Raw sequence of octet strings + /// Signer public keys (raw byte strings) + signers: &'a [u8], + /// Required number of signatures quorum: u64, }, /// Swap creation arguments [8] Swap { - sell_token_rate: TokenValue<'a>, - buy_token_rate: TokenValue<'a>, - fee_token_rate: Option>, + sell_token_rate: TokenRate<'a>, + buy_token_rate: TokenRate<'a>, + fee_token_rate: Option>, quantity: &'a [u8], }, } @@ -417,7 +435,7 @@ pub struct ManageCertificateOp<'a> { /// Certificate (if adding) or certificate hash (if removing) pub certificate_or_hash: &'a [u8], /// Intermediate certificates (required if adding, must be None if removing) - pub intermediates: Option<&'a [u8]>, + pub intermediate_certificates: Option<&'a [u8]>, } /// [9] MATCH_SWAP operation - Match two swap orders @@ -431,8 +449,8 @@ pub struct MatchSwapOp<'a> { pub sell: TokenValue<'a>, /// Token being bought and value pub buy: TokenValue<'a>, - /// Fee details with recipient (optional) - pub fee: Option>, + /// Fee value with recipient (optional) + pub fee: Option>, } /// [10] CANCEL_SWAP operation - Cancel a swap order @@ -442,8 +460,8 @@ pub struct CancelSwapOp<'a> { pub swap: &'a [u8], /// Sell token and value being returned pub sell: TokenValue<'a>, - /// Fee details (optional) - pub fee: Option>, + /// Fee value (optional) + pub fee: Option>, } // ============================================================================ @@ -465,8 +483,8 @@ pub enum Operation<'a> { CreateIdentifier(CreateIdentifierOp<'a>), /// [5] Token admin supply TokenAdminSupply(TokenAdminSupplyOp<'a>), - /// [6] Token modify balance - TokenModifyBalance(TokenAdminModifyBalanceOp<'a>), + /// [6] Token admin modify balance + TokenAdminModifyBalance(TokenAdminModifyBalanceOp<'a>), /// [7] Receive tokens Receive(ReceiveOp<'a>), /// [8] Manage certificate @@ -476,3 +494,117 @@ pub enum Operation<'a> { /// [10] Cancel swap CancelSwap(CancelSwapOp<'a>), } + +// ============================================================================ +// Vote Types (X.509 Certificate) +// ============================================================================ + +/// Algorithm identifier (used in certificates) +#[cfg(any(feature = "alloc", feature = "std"))] +#[derive(Debug, Clone, Copy)] +pub struct AlgorithmIdentifier<'a> { + /// Algorithm identifier + pub algorithm: &'a [u8], + /// Optional parameters (e.g., curve identifier for ECDSA) + pub parameters: Option<&'a [u8]>, +} + +/// Validity period for certificates +#[cfg(any(feature = "alloc", feature = "std"))] +#[derive(Debug, Clone, Copy)] +pub struct Validity<'a> { + /// Certificate validity start time + pub not_before: &'a str, + /// Certificate validity end time + pub not_after: &'a str, +} + +/// Subject public key info +#[cfg(any(feature = "alloc", feature = "std"))] +#[derive(Debug, Clone, Copy)] +pub struct SubjectPublicKeyInfo<'a> { + /// Algorithm identifier + pub algorithm: AlgorithmIdentifier<'a>, + /// Public key as raw bytes + pub public_key: &'a [u8], +} + +/// Certificate extension wrapper +#[cfg(any(feature = "alloc", feature = "std"))] +#[derive(Debug, Clone, Copy)] +pub struct CertificateExtensionWrapper<'a> { + /// Extension identifier + pub extension_id: &'a [u8], + /// Critical flag + pub critical: bool, + /// Extension data (contains HashData or FeeData) + pub data: &'a [u8], +} + +/// Hash data extension content (OID: 2.16.840.1.101.3.3.1.3) +#[cfg(any(feature = "alloc", feature = "std"))] +#[derive(Debug, Clone)] +pub struct HashData<'a> { + /// Hash algorithm identifier + pub hash_algorithm: &'a [u8], + /// Block hashes + pub hashes: Vec<&'a [u8]>, +} + +/// Fee data extension content (OID: 1.3.6.1.4.1.62675.0.1.0) +#[cfg(any(feature = "alloc", feature = "std"))] +#[derive(Debug, Clone, Copy)] +pub struct FeeData<'a> { + /// Whether this is a quote (`true`) or a vote (`false`) + pub quote: bool, + /// Amount + pub amount: &'a [u8], + /// Pay to account (optional) + pub pay_to: Option<&'a [u8]>, + /// Token account (optional) + pub token: Option<&'a [u8]>, +} + +/// TBS (To Be Signed) Certificate data +#[cfg(any(feature = "alloc", feature = "std"))] +#[derive(Debug, Clone)] +pub struct TbsCertificate<'a> { + /// Version (always 2 for v3 certificates) + pub version: u8, + /// Serial number + pub serial: &'a [u8], + /// Signature algorithm + pub signature_algorithm: AlgorithmIdentifier<'a>, + /// Issuer common name + pub issuer_cn: &'a str, + /// Validity period + pub validity: Validity<'a>, + /// Subject serial number + pub subject_serial: &'a str, + /// Subject public key info + pub subject_public_key_info: SubjectPublicKeyInfo<'a>, + /// Extensions + pub extensions: Vec>, +} + +/// Vote (X.509v3 Certificate for blockchain voting) +#[cfg(any(feature = "alloc", feature = "std"))] +#[derive(Debug, Clone)] +pub struct Vote<'a> { + /// TBS Certificate (data to be signed) + pub tbs_certificate: TbsCertificate<'a>, + /// Signature algorithm + pub signature_algorithm: AlgorithmIdentifier<'a>, + /// Signature as raw bytes + pub signature: &'a [u8], +} + +/// Vote staple - bundles blocks with their votes +#[cfg(any(feature = "alloc", feature = "std"))] +#[derive(Debug, Clone)] +pub struct VoteStaple<'a> { + /// Blocks + pub blocks: Vec>, + /// Votes (X.509 certificates) + pub votes: Vec>, +} From 711bbf73815d483f69732acbc409220059f94a13 Mon Sep 17 00:00:00 2001 From: mamonet Date: Thu, 15 Jan 2026 23:16:27 +0100 Subject: [PATCH 11/25] Add DER encoding/decoding support with derive macros --- keetanetwork-block/Cargo.toml | 1 + keetanetwork-block/src/lib.rs | 14 +- keetanetwork-block/src/types.rs | 357 +++++++++++++++++++++----------- 3 files changed, 252 insertions(+), 120 deletions(-) diff --git a/keetanetwork-block/Cargo.toml b/keetanetwork-block/Cargo.toml index c293a3a..45ea53e 100644 --- a/keetanetwork-block/Cargo.toml +++ b/keetanetwork-block/Cargo.toml @@ -14,6 +14,7 @@ std = ["alloc"] alloc = [] [dependencies] +der = { version = "0.7.10", default-features = false, features = ["derive"] } [dev-dependencies] diff --git a/keetanetwork-block/src/lib.rs b/keetanetwork-block/src/lib.rs index 6e96028..f593211 100644 --- a/keetanetwork-block/src/lib.rs +++ b/keetanetwork-block/src/lib.rs @@ -18,16 +18,23 @@ pub use types::{ BlockHeader, BlockPurpose, BlockVersion, + // Type aliases (maps to der types or raw bytes) + Bytes, // Operation structs CancelSwapOp, CreateIdentifierArgs, CreateIdentifierOp, // Supporting types - FeeDetails, - FeeDetailsWithRecipient, + FeeRate, + FeeValue, + FeeValueWithRecipient, + Int, ManageCertificateOp, MatchSwapOp, ModifyPermissionsOp, + MultisigArgs, + // Value-or-none wrapper (like Option but NULL has meaning) + NullOr, // Operation enum Operation, Permission, @@ -35,7 +42,10 @@ pub use types::{ SendOp, SetInfoOp, SetRepOp, + Str, + SwapArgs, TokenAdminModifyBalanceOp, TokenAdminSupplyOp, + TokenRate, TokenValue, }; diff --git a/keetanetwork-block/src/types.rs b/keetanetwork-block/src/types.rs index 1764978..3ee32e6 100644 --- a/keetanetwork-block/src/types.rs +++ b/keetanetwork-block/src/types.rs @@ -5,19 +5,19 @@ //! //! ## Operation Tags //! -//! | Tag | Operation | -//! |------|---------------------| -//! | [0] | Send | -//! | [1] | SetRep | -//! | [2] | SetInfo | -//! | [3] | ModifyPermissions | -//! | [4] | CreateIdentifier | -//! | [5] | TokenAdminSupply | +//! | Tag | Operation | +//! |------|-------------------------| +//! | [0] | Send | +//! | [1] | SetRep | +//! | [2] | SetInfo | +//! | [3] | ModifyPermissions | +//! | [4] | CreateIdentifier | +//! | [5] | TokenAdminSupply | //! | [6] | TokenAdminModifyBalance | -//! | [7] | Receive | -//! | [8] | ManageCertificate | -//! | [9] | MatchSwap | -//! | [10] | CancelSwap | +//! | [7] | Receive | +//! | [8] | ManageCertificate | +//! | [9] | MatchSwap | +//! | [10] | CancelSwap | // Use alloc for Vec when not using std #[cfg(all(feature = "alloc", not(feature = "std")))] @@ -29,6 +29,73 @@ use alloc::vec::Vec; #[cfg(feature = "std")] use std::vec::Vec; +// DER encoding/decoding support +use der::{ + asn1::{IntRef, Null, OctetStringRef, Utf8StringRef}, + Choice, Decode, DecodeValue, Encode, EncodeValue, Enumerated, Header, Length, Reader, Sequence, Tag, Writer, +}; + +// Type aliases for DER types +pub type Bytes<'a> = OctetStringRef<'a>; +pub type Int<'a> = IntRef<'a>; +pub type Str<'a> = Utf8StringRef<'a>; + +// ============================================================================ +// NullOr Type +// ============================================================================ + +/// Either a value of type `T` or an explicit "none" marker. +/// +/// Used for fields where `None` has a specific meaning (e.g., "use sell token +/// as fee token") rather than just being absent. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NullOr { + /// NULL - represents none/default + Null, + /// Value present + Value(T), +} + +impl NullOr { + /// Get the value if present, None if NULL + pub fn value(&self) -> Option<&T> { + match self { + NullOr::Null => None, + NullOr::Value(v) => Some(v), + } + } +} + +// Implement Decode for NullOr +impl<'a, T: Decode<'a>> Decode<'a> for NullOr { + fn decode>(reader: &mut R) -> der::Result { + let tag = reader.peek_tag()?; + if tag == Tag::Null { + let _: Null = reader.decode()?; + Ok(NullOr::Null) + } else { + Ok(NullOr::Value(T::decode(reader)?)) + } + } +} + +// Implement Encode for NullOr +impl Encode for NullOr { + fn encoded_len(&self) -> der::Result { + match self { + NullOr::Null => Null.encoded_len(), + NullOr::Value(v) => v.encoded_len(), + } + } + + fn encode(&self, writer: &mut impl Writer) -> der::Result<()> { + match self { + NullOr::Null => Null.encode(writer), + NullOr::Value(v) => v.encode(writer), + } + } +} + // ============================================================================ // Block Types // ============================================================================ @@ -215,43 +282,20 @@ pub enum SignerField<'a> { // ============================================================================ /// Adjust method for supply/balance/permissions operations -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Enumerated)] +#[repr(u8)] pub enum AdjustMethod { - Add, - Subtract, - Set, -} - -impl TryFrom for AdjustMethod { - type Error = u8; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(AdjustMethod::Add), - 1 => Ok(AdjustMethod::Subtract), - 2 => Ok(AdjustMethod::Set), - n => Err(n), - } - } + Add = 0, + Subtract = 1, + Set = 2, } /// Adjust method for relative operations (add/subtract only, no set) -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Enumerated)] +#[repr(u8)] pub enum AdjustMethodRelative { - Add, - Subtract, -} - -impl TryFrom for AdjustMethodRelative { - type Error = u8; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(AdjustMethodRelative::Add), - 1 => Ok(AdjustMethodRelative::Subtract), - n => Err(n), - } - } + Add = 0, + Subtract = 1, } // ============================================================================ @@ -259,54 +303,54 @@ impl TryFrom for AdjustMethodRelative { // ============================================================================ /// Token and rate pair (used in CREATE_IDENTIFIER swap arguments) -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Sequence)] pub struct TokenRate<'a> { /// Token public key - pub token: &'a [u8], + pub token: Bytes<'a>, /// Rate - pub rate: &'a [u8], + pub rate: Int<'a>, } /// Fee rate for CREATE_IDENTIFIER swap arguments -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Sequence)] pub struct FeeRate<'a> { - /// Fee token (None means use sell token) - pub token: Option<&'a [u8]>, + /// Fee token (NULL means use sell token) + pub token: NullOr>, /// Fee rate - pub rate: &'a [u8], + pub rate: Int<'a>, } /// Token and value pair (used in MATCH_SWAP/CANCEL_SWAP operations) -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Sequence)] pub struct TokenValue<'a> { /// Token public key - pub token: &'a [u8], + pub token: Bytes<'a>, /// Value - pub value: &'a [u8], + pub value: Int<'a>, } /// Fee value for CANCEL_SWAP operation -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Sequence)] pub struct FeeValue<'a> { - /// Fee token (None means use sell token) - pub token: Option<&'a [u8]>, + /// Fee token (NULL means use sell token) + pub token: NullOr>, /// Fee value - pub value: &'a [u8], + pub value: Int<'a>, } /// Fee value with recipient for MATCH_SWAP operation -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Sequence)] pub struct FeeValueWithRecipient<'a> { - /// Fee token (None means use sell token) - pub token: Option<&'a [u8]>, + /// Fee token (NULL means use sell token) + pub token: NullOr>, /// Fee value - pub value: &'a [u8], + pub value: Int<'a>, /// Fee recipient - pub recipient: &'a [u8], + pub recipient: Bytes<'a>, } /// Permission value (base and external) -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Sequence)] pub struct Permission { /// Base permissions pub base: u64, @@ -319,149 +363,215 @@ pub struct Permission { // ============================================================================ /// [0] SEND operation - Transfer tokens to another account -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Sequence)] pub struct SendOp<'a> { /// Destination account - pub to: &'a [u8], + pub to: Bytes<'a>, /// Amount to send - pub amount: &'a [u8], + pub amount: Int<'a>, /// Token ID to send - pub token: &'a [u8], + pub token: Bytes<'a>, /// External reference (optional) - pub external: Option<&'a [u8]>, + pub external: Option>, } /// [1] SET_REP operation - Set representative for delegation -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Sequence)] pub struct SetRepOp<'a> { /// Representative to delegate to - pub to: &'a [u8], + pub to: Bytes<'a>, } /// [2] SET_INFO operation - Set account information -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Sequence)] pub struct SetInfoOp<'a> { /// Account name - pub name: &'a [u8], + pub name: Str<'a>, /// Account description - pub description: &'a [u8], + pub description: Str<'a>, /// Account metadata - pub metadata: &'a [u8], + pub metadata: Str<'a>, /// Default permission (optional) + #[asn1(optional = "true")] pub default_permission: Option, } /// [3] MODIFY_PERMISSIONS operation - Modify account permissions -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Sequence)] pub struct ModifyPermissionsOp<'a> { /// Principal to modify permissions for - pub principal: &'a [u8], + pub principal: Bytes<'a>, /// Method to modify (add/subtract/set) pub method: AdjustMethod, - /// Permissions to modify (None = null/clear) - pub permissions: Option, + /// Permissions to modify (NULL = clear) + pub permissions: NullOr, /// Target account (optional) - pub target: Option<&'a [u8]>, + #[asn1(optional = "true")] + pub target: Option>, +} + +/// Multisig creation arguments +/// +/// Note: The `signers` field contains raw DER-encoded bytes representing +/// a sequence of public keys. For `no_std`/`no_alloc` environments, manual +/// parsing is required. +#[derive(Debug, Clone, Sequence)] +pub struct MultisigArgs<'a> { + /// Signer public keys (raw DER-encoded bytes) + pub signers: Bytes<'a>, + /// Required number of signatures + pub quorum: u64, +} + +/// Swap creation arguments +#[derive(Debug, Clone, Sequence)] +pub struct SwapArgs<'a> { + /// Token being sold and rate + pub sell_token_rate: TokenRate<'a>, + /// Token being bought and rate + pub buy_token_rate: TokenRate<'a>, + /// Fee token and rate (NULL = no fee) + pub fee_token_rate: NullOr>, + /// Quantity + pub quantity: Int<'a>, } /// Identifier creation arguments -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Choice)] pub enum CreateIdentifierArgs<'a> { /// Multisig creation arguments [7] - Multisig { - /// Signer public keys (raw byte strings) - signers: &'a [u8], - /// Required number of signatures - quorum: u64, - }, + #[asn1(context_specific = "7", tag_mode = "EXPLICIT", constructed = "true")] + Multisig(MultisigArgs<'a>), /// Swap creation arguments [8] - Swap { - sell_token_rate: TokenRate<'a>, - buy_token_rate: TokenRate<'a>, - fee_token_rate: Option>, - quantity: &'a [u8], - }, + #[asn1(context_specific = "8", tag_mode = "EXPLICIT", constructed = "true")] + Swap(SwapArgs<'a>), } /// [4] CREATE_IDENTIFIER operation - Create token, multisig, or swap -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Sequence)] pub struct CreateIdentifierOp<'a> { /// Identifier to create - pub identifier: &'a [u8], + pub identifier: Bytes<'a>, /// Creation arguments (optional, depends on identifier type) + #[asn1(optional = "true")] pub create_arguments: Option>, } /// [5] TOKEN_ADMIN_SUPPLY operation - Modify token supply -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Sequence)] pub struct TokenAdminSupplyOp<'a> { /// Amount to modify - pub amount: &'a [u8], + pub amount: Int<'a>, /// Method (add/subtract/set) pub method: AdjustMethod, } /// [6] TOKEN_ADMIN_MODIFY_BALANCE operation - Modify account token balance -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Sequence)] pub struct TokenAdminModifyBalanceOp<'a> { /// Token to modify balance of - pub token: &'a [u8], + pub token: Bytes<'a>, /// Amount to modify - pub amount: &'a [u8], + pub amount: Int<'a>, /// Method (add/subtract/set) pub method: AdjustMethod, } /// [7] RECEIVE operation - Receive tokens from another account -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Sequence)] pub struct ReceiveOp<'a> { /// Amount to receive - pub amount: &'a [u8], + pub amount: Int<'a>, /// Token to receive - pub token: &'a [u8], + pub token: Bytes<'a>, /// Sender account - pub from: &'a [u8], + pub from: Bytes<'a>, /// Whether amount must match exactly pub exact: bool, /// Forward to another account (optional) - pub forward: Option<&'a [u8]>, + #[asn1(optional = "true")] + pub forward: Option>, } /// [8] MANAGE_CERTIFICATE operation - Add or subtract certificates +/// +/// Implemented manually because `Option>` doesn't implement `Decode` +/// (the `Option` impl requires `T: FixedTag`, but `NullOr` has no fixed tag). #[derive(Debug, Clone)] pub struct ManageCertificateOp<'a> { /// Method (add/subtract) pub method: AdjustMethodRelative, /// Certificate (if adding) or certificate hash (if removing) - pub certificate_or_hash: &'a [u8], - /// Intermediate certificates (required if adding, must be None if removing) - pub intermediate_certificates: Option<&'a [u8]>, + pub certificate_or_hash: Bytes<'a>, + /// Intermediate certificates (either raw bytes or explicit none) + /// Required when adding a certificate, optional overall + pub intermediate_certificates: Option>>, +} + +impl<'a> DecodeValue<'a> for ManageCertificateOp<'a> { + fn decode_value>(reader: &mut R, _header: Header) -> der::Result { + let method = reader.decode()?; + let certificate_or_hash = reader.decode()?; + + // Handle optional field that can be either bytes or explicit none + let intermediate_certificates = if reader.is_finished() { + None + } else { + Some(reader.decode()?) + }; + + Ok(ManageCertificateOp { method, certificate_or_hash, intermediate_certificates }) + } } +impl EncodeValue for ManageCertificateOp<'_> { + fn value_len(&self) -> der::Result { + self.method.encoded_len()? + + self.certificate_or_hash.encoded_len()? + + self + .intermediate_certificates + .as_ref() + .map(|v| v.encoded_len()) + .transpose()? + .unwrap_or(Length::ZERO) + } + + fn encode_value(&self, writer: &mut impl Writer) -> der::Result<()> { + self.method.encode(writer)?; + self.certificate_or_hash.encode(writer)?; + if let Some(ref certs) = self.intermediate_certificates { + certs.encode(writer)?; + } + Ok(()) + } +} + +impl<'a> Sequence<'a> for ManageCertificateOp<'a> {} + /// [9] MATCH_SWAP operation - Match two swap orders -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Sequence)] pub struct MatchSwapOp<'a> { /// Swap account being used - pub swap: &'a [u8], + pub swap: Bytes<'a>, /// Other swap account to match against - pub other: &'a [u8], + pub other: Bytes<'a>, /// Token being sold and value pub sell: TokenValue<'a>, /// Token being bought and value pub buy: TokenValue<'a>, - /// Fee value with recipient (optional) - pub fee: Option>, + /// Fee value with recipient (NULL = no fee) + pub fee: NullOr>, } /// [10] CANCEL_SWAP operation - Cancel a swap order -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Sequence)] pub struct CancelSwapOp<'a> { /// Swap account to cancel - pub swap: &'a [u8], + pub swap: Bytes<'a>, /// Sell token and value being returned pub sell: TokenValue<'a>, - /// Fee value (optional) - pub fee: Option>, + /// Fee value (NULL = no fee) + pub fee: NullOr>, } // ============================================================================ @@ -469,29 +579,40 @@ pub struct CancelSwapOp<'a> { // ============================================================================ /// Keeta blockchain operation -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Choice)] pub enum Operation<'a> { /// [0] Send tokens + #[asn1(context_specific = "0", tag_mode = "EXPLICIT", constructed = "true")] Send(SendOp<'a>), /// [1] Set representative + #[asn1(context_specific = "1", tag_mode = "EXPLICIT", constructed = "true")] SetRep(SetRepOp<'a>), /// [2] Set account info + #[asn1(context_specific = "2", tag_mode = "EXPLICIT", constructed = "true")] SetInfo(SetInfoOp<'a>), /// [3] Modify permissions + #[asn1(context_specific = "3", tag_mode = "EXPLICIT", constructed = "true")] ModifyPermissions(ModifyPermissionsOp<'a>), /// [4] Create identifier (token, multisig, swap) + #[asn1(context_specific = "4", tag_mode = "EXPLICIT", constructed = "true")] CreateIdentifier(CreateIdentifierOp<'a>), /// [5] Token admin supply + #[asn1(context_specific = "5", tag_mode = "EXPLICIT", constructed = "true")] TokenAdminSupply(TokenAdminSupplyOp<'a>), /// [6] Token admin modify balance + #[asn1(context_specific = "6", tag_mode = "EXPLICIT", constructed = "true")] TokenAdminModifyBalance(TokenAdminModifyBalanceOp<'a>), /// [7] Receive tokens + #[asn1(context_specific = "7", tag_mode = "EXPLICIT", constructed = "true")] Receive(ReceiveOp<'a>), /// [8] Manage certificate + #[asn1(context_specific = "8", tag_mode = "EXPLICIT", constructed = "true")] ManageCertificate(ManageCertificateOp<'a>), /// [9] Match swap + #[asn1(context_specific = "9", tag_mode = "EXPLICIT", constructed = "true")] MatchSwap(MatchSwapOp<'a>), /// [10] Cancel swap + #[asn1(context_specific = "10", tag_mode = "EXPLICIT", constructed = "true")] CancelSwap(CancelSwapOp<'a>), } From 27650cfb154606b1f023a48fe42ff9a5c64c4c96 Mon Sep 17 00:00:00 2001 From: mamonet Date: Thu, 15 Jan 2026 23:30:19 +0100 Subject: [PATCH 12/25] Add test coverage --- keetanetwork-block/Cargo.toml | 2 +- keetanetwork-block/src/types.rs | 588 ++++++++++++++++++++++++++++++++ 2 files changed, 589 insertions(+), 1 deletion(-) diff --git a/keetanetwork-block/Cargo.toml b/keetanetwork-block/Cargo.toml index 45ea53e..215d6ff 100644 --- a/keetanetwork-block/Cargo.toml +++ b/keetanetwork-block/Cargo.toml @@ -11,7 +11,7 @@ description = "Block structure and operations for Keetanetwork blockchain" [features] default = ["std"] std = ["alloc"] -alloc = [] +alloc = ["der/alloc"] [dependencies] der = { version = "0.7.10", default-features = false, features = ["derive"] } diff --git a/keetanetwork-block/src/types.rs b/keetanetwork-block/src/types.rs index 3ee32e6..43daaf0 100644 --- a/keetanetwork-block/src/types.rs +++ b/keetanetwork-block/src/types.rs @@ -729,3 +729,591 @@ pub struct VoteStaple<'a> { /// Votes (X.509 certificates) pub votes: Vec>, } + +// ============================================================================ +// Tests +// ============================================================================ + +#[cfg(test)] +mod tests { + use super::*; + use der::{Decode, Encode}; + + // Helper to create test bytes (33-byte address with Ed25519 prefix) + fn test_address() -> [u8; 33] { + let mut addr = [0u8; 33]; + addr[0] = 0x01; // Ed25519 prefix + addr[1] = 0xAB; + addr[32] = 0xCD; + addr + } + + // Helper to create test token (33 bytes) + fn test_token() -> [u8; 33] { + let mut token = [0u8; 33]; + token[0] = 0x01; + token[1] = 0xDE; + token[32] = 0xAD; + token + } + + // ============================================================================ + // NullOr Tests + // ============================================================================ + + #[test] + fn nullor_null_roundtrip() { + let original: NullOr = NullOr::Null; + let encoded = original.to_der().unwrap(); + let decoded: NullOr = NullOr::from_der(&encoded).unwrap(); + assert_eq!(decoded, NullOr::Null); + } + + #[test] + fn nullor_value_roundtrip() { + let data = [1u8, 2, 3, 4]; + let original: NullOr = NullOr::Value(Bytes::new(&data).unwrap()); + let encoded = original.to_der().unwrap(); + let decoded: NullOr = NullOr::from_der(&encoded).unwrap(); + + match decoded { + NullOr::Value(bytes) => assert_eq!(bytes.as_bytes(), &data), + NullOr::Null => panic!("Expected Value, got Null"), + } + } + + #[test] + fn nullor_value_method() { + let data = [1u8, 2, 3]; + let null: NullOr = NullOr::Null; + let value: NullOr = NullOr::Value(Bytes::new(&data).unwrap()); + + assert!(null.value().is_none()); + assert_eq!(value.value().unwrap().as_bytes(), &data); + } + + // ============================================================================ + // Enum Tests + // ============================================================================ + + #[test] + fn adjust_method_roundtrip() { + for method in [AdjustMethod::Add, AdjustMethod::Subtract, AdjustMethod::Set] { + let encoded = method.to_der().unwrap(); + let decoded: AdjustMethod = AdjustMethod::from_der(&encoded).unwrap(); + assert_eq!(decoded, method); + } + } + + #[test] + fn adjust_method_relative_roundtrip() { + for method in [AdjustMethodRelative::Add, AdjustMethodRelative::Subtract] { + let encoded = method.to_der().unwrap(); + let decoded: AdjustMethodRelative = AdjustMethodRelative::from_der(&encoded).unwrap(); + assert_eq!(decoded, method); + } + } + + // ============================================================================ + // Supporting Type Tests + // ============================================================================ + + #[test] + fn permission_roundtrip() { + let original = Permission { base: 0x1234, external: 0x5678 }; + let encoded = original.to_der().unwrap(); + let decoded: Permission = Permission::from_der(&encoded).unwrap(); + assert_eq!(decoded.base, original.base); + assert_eq!(decoded.external, original.external); + } + + #[test] + fn token_rate_roundtrip() { + let token = test_token(); + let rate = [0x64]; // 100 (canonical form) + + let original = TokenRate { token: Bytes::new(&token).unwrap(), rate: Int::new(&rate).unwrap() }; + let encoded = original.to_der().unwrap(); + let decoded: TokenRate = TokenRate::from_der(&encoded).unwrap(); + + assert_eq!(decoded.token.as_bytes(), &token); + assert_eq!(decoded.rate.as_bytes(), &rate); + } + + #[test] + fn fee_rate_with_null_token_roundtrip() { + let rate = [0x0A]; // 10 + + let original = FeeRate { token: NullOr::Null, rate: Int::new(&rate).unwrap() }; + let encoded = original.to_der().unwrap(); + let decoded: FeeRate = FeeRate::from_der(&encoded).unwrap(); + + assert!(matches!(decoded.token, NullOr::Null)); + assert_eq!(decoded.rate.as_bytes(), &rate); + } + + #[test] + fn fee_rate_with_token_roundtrip() { + let token = test_token(); + let rate = [0x0A]; // 10 + + let original = FeeRate { token: NullOr::Value(Bytes::new(&token).unwrap()), rate: Int::new(&rate).unwrap() }; + let encoded = original.to_der().unwrap(); + let decoded: FeeRate = FeeRate::from_der(&encoded).unwrap(); + + match decoded.token { + NullOr::Value(t) => assert_eq!(t.as_bytes(), &token), + NullOr::Null => panic!("Expected token, got Null"), + } + } + + #[test] + fn token_value_roundtrip() { + let token = test_token(); + let value = [0x00, 0xFF]; // 255 + + let original = TokenValue { token: Bytes::new(&token).unwrap(), value: Int::new(&value).unwrap() }; + let encoded = original.to_der().unwrap(); + let decoded: TokenValue = TokenValue::from_der(&encoded).unwrap(); + + assert_eq!(decoded.token.as_bytes(), &token); + assert_eq!(decoded.value.as_bytes(), &value); + } + + #[test] + fn fee_value_roundtrip() { + let value = [0x64]; // 100 + + let original = FeeValue { token: NullOr::Null, value: Int::new(&value).unwrap() }; + let encoded = original.to_der().unwrap(); + let decoded: FeeValue = FeeValue::from_der(&encoded).unwrap(); + + assert!(matches!(decoded.token, NullOr::Null)); + assert_eq!(decoded.value.as_bytes(), &value); + } + + #[test] + fn fee_value_with_recipient_roundtrip() { + let token = test_token(); + let value = [0x64]; // 100 + let recipient = test_address(); + + let original = FeeValueWithRecipient { + token: NullOr::Value(Bytes::new(&token).unwrap()), + value: Int::new(&value).unwrap(), + recipient: Bytes::new(&recipient).unwrap(), + }; + let encoded = original.to_der().unwrap(); + let decoded: FeeValueWithRecipient = FeeValueWithRecipient::from_der(&encoded).unwrap(); + + match decoded.token { + NullOr::Value(t) => assert_eq!(t.as_bytes(), &token), + NullOr::Null => panic!("Expected token, got Null"), + } + assert_eq!(decoded.recipient.as_bytes(), &recipient); + } + + // ============================================================================ + // Operation Tests + // ============================================================================ + + #[test] + fn send_op_roundtrip() { + let to = test_address(); + let amount = [0x10]; // 16 (canonical form - no leading zeros) + let token = test_token(); + + let original = SendOp { + to: Bytes::new(&to).unwrap(), + amount: Int::new(&amount).unwrap(), + token: Bytes::new(&token).unwrap(), + external: None, + }; + let encoded = original.to_der().unwrap(); + let decoded: SendOp = SendOp::from_der(&encoded).unwrap(); + + assert_eq!(decoded.to.as_bytes(), &to); + assert_eq!(decoded.amount.as_bytes(), &amount); + assert_eq!(decoded.token.as_bytes(), &token); + assert!(decoded.external.is_none()); + } + + #[test] + fn send_op_with_external_roundtrip() { + let to = test_address(); + let amount = [0x10]; // 16 + let token = test_token(); + let external = "ref-123"; + + let original = SendOp { + to: Bytes::new(&to).unwrap(), + amount: Int::new(&amount).unwrap(), + token: Bytes::new(&token).unwrap(), + external: Some(Str::new(external).unwrap()), + }; + let encoded = original.to_der().unwrap(); + let decoded: SendOp = SendOp::from_der(&encoded).unwrap(); + + assert_eq!(decoded.external.unwrap().as_str(), external); + } + + #[test] + fn set_rep_op_roundtrip() { + let to = test_address(); + + let original = SetRepOp { to: Bytes::new(&to).unwrap() }; + let encoded = original.to_der().unwrap(); + let decoded: SetRepOp = SetRepOp::from_der(&encoded).unwrap(); + + assert_eq!(decoded.to.as_bytes(), &to); + } + + #[test] + fn set_info_op_roundtrip() { + let original = SetInfoOp { + name: Str::new("Test Account").unwrap(), + description: Str::new("A test account").unwrap(), + metadata: Str::new("{}").unwrap(), + default_permission: None, + }; + let encoded = original.to_der().unwrap(); + let decoded: SetInfoOp = SetInfoOp::from_der(&encoded).unwrap(); + + assert_eq!(decoded.name.as_str(), "Test Account"); + assert_eq!(decoded.description.as_str(), "A test account"); + assert_eq!(decoded.metadata.as_str(), "{}"); + } + + #[test] + fn set_info_op_with_permission_roundtrip() { + let original = SetInfoOp { + name: Str::new("Test").unwrap(), + description: Str::new("Desc").unwrap(), + metadata: Str::new("{}").unwrap(), + default_permission: Some(Permission { base: 0xFF, external: 0xAA }), + }; + let encoded = original.to_der().unwrap(); + let decoded: SetInfoOp = SetInfoOp::from_der(&encoded).unwrap(); + + let perm = decoded.default_permission.unwrap(); + assert_eq!(perm.base, 0xFF); + assert_eq!(perm.external, 0xAA); + } + + #[test] + fn modify_permissions_op_roundtrip() { + let principal = test_address(); + + let original = ModifyPermissionsOp { + principal: Bytes::new(&principal).unwrap(), + method: AdjustMethod::Add, + permissions: NullOr::Value(Permission { base: 0x10, external: 0x20 }), + target: None, + }; + let encoded = original.to_der().unwrap(); + let decoded: ModifyPermissionsOp = ModifyPermissionsOp::from_der(&encoded).unwrap(); + + assert_eq!(decoded.principal.as_bytes(), &principal); + assert_eq!(decoded.method, AdjustMethod::Add); + match decoded.permissions { + NullOr::Value(p) => { + assert_eq!(p.base, 0x10); + assert_eq!(p.external, 0x20); + } + NullOr::Null => panic!("Expected permissions"), + } + } + + #[test] + fn modify_permissions_op_clear_roundtrip() { + let principal = test_address(); + + let original = ModifyPermissionsOp { + principal: Bytes::new(&principal).unwrap(), + method: AdjustMethod::Set, + permissions: NullOr::Null, + target: None, + }; + let encoded = original.to_der().unwrap(); + let decoded: ModifyPermissionsOp = ModifyPermissionsOp::from_der(&encoded).unwrap(); + + assert!(matches!(decoded.permissions, NullOr::Null)); + } + + #[test] + fn token_admin_supply_op_roundtrip() { + let amount = [0x00, 0xFF, 0xFF]; // 65535 + + let original = TokenAdminSupplyOp { amount: Int::new(&amount).unwrap(), method: AdjustMethod::Add }; + let encoded = original.to_der().unwrap(); + let decoded: TokenAdminSupplyOp = TokenAdminSupplyOp::from_der(&encoded).unwrap(); + + assert_eq!(decoded.amount.as_bytes(), &amount); + assert_eq!(decoded.method, AdjustMethod::Add); + } + + #[test] + fn token_admin_modify_balance_op_roundtrip() { + let token = test_token(); + let amount = [0x64]; // 100 + + let original = TokenAdminModifyBalanceOp { + token: Bytes::new(&token).unwrap(), + amount: Int::new(&amount).unwrap(), + method: AdjustMethod::Subtract, + }; + let encoded = original.to_der().unwrap(); + let decoded: TokenAdminModifyBalanceOp = TokenAdminModifyBalanceOp::from_der(&encoded).unwrap(); + + assert_eq!(decoded.token.as_bytes(), &token); + assert_eq!(decoded.method, AdjustMethod::Subtract); + } + + #[test] + fn receive_op_roundtrip() { + let from = test_address(); + let token = test_token(); + let amount = [0x10]; // 16 + + let original = ReceiveOp { + amount: Int::new(&amount).unwrap(), + token: Bytes::new(&token).unwrap(), + from: Bytes::new(&from).unwrap(), + exact: true, + forward: None, + }; + let encoded = original.to_der().unwrap(); + let decoded: ReceiveOp = ReceiveOp::from_der(&encoded).unwrap(); + + assert_eq!(decoded.from.as_bytes(), &from); + assert!(decoded.exact); + assert!(decoded.forward.is_none()); + } + + #[test] + fn receive_op_with_forward_roundtrip() { + let from = test_address(); + let token = test_token(); + let amount = [0x10]; + let mut forward = [0u8; 33]; + forward[0] = 0x02; // Different address + + let original = ReceiveOp { + amount: Int::new(&amount).unwrap(), + token: Bytes::new(&token).unwrap(), + from: Bytes::new(&from).unwrap(), + exact: false, + forward: Some(Bytes::new(&forward).unwrap()), + }; + let encoded = original.to_der().unwrap(); + let decoded: ReceiveOp = ReceiveOp::from_der(&encoded).unwrap(); + + assert!(!decoded.exact); + assert_eq!(decoded.forward.unwrap().as_bytes(), &forward); + } + + #[test] + fn manage_certificate_op_add_roundtrip() { + let cert = [0x30, 0x82, 0x01, 0x00]; // Mock certificate + let intermediate = [0x30, 0x82, 0x02, 0x00]; // Mock intermediate + + let original = ManageCertificateOp { + method: AdjustMethodRelative::Add, + certificate_or_hash: Bytes::new(&cert).unwrap(), + intermediate_certificates: Some(NullOr::Value(Bytes::new(&intermediate).unwrap())), + }; + let encoded = original.to_der().unwrap(); + let decoded: ManageCertificateOp = ManageCertificateOp::from_der(&encoded).unwrap(); + + assert_eq!(decoded.method, AdjustMethodRelative::Add); + assert_eq!(decoded.certificate_or_hash.as_bytes(), &cert); + match decoded.intermediate_certificates { + Some(NullOr::Value(i)) => assert_eq!(i.as_bytes(), &intermediate), + _ => panic!("Expected intermediate certificates"), + } + } + + #[test] + fn manage_certificate_op_add_no_intermediate_roundtrip() { + let cert = [0x30, 0x82, 0x01, 0x00]; + + let original = ManageCertificateOp { + method: AdjustMethodRelative::Add, + certificate_or_hash: Bytes::new(&cert).unwrap(), + intermediate_certificates: Some(NullOr::Null), + }; + let encoded = original.to_der().unwrap(); + let decoded: ManageCertificateOp = ManageCertificateOp::from_der(&encoded).unwrap(); + + assert!(matches!(decoded.intermediate_certificates, Some(NullOr::Null))); + } + + #[test] + fn manage_certificate_op_subtract_roundtrip() { + let hash = [0xAB; 32]; // Certificate hash + + let original = ManageCertificateOp { + method: AdjustMethodRelative::Subtract, + certificate_or_hash: Bytes::new(&hash).unwrap(), + intermediate_certificates: None, + }; + let encoded = original.to_der().unwrap(); + let decoded: ManageCertificateOp = ManageCertificateOp::from_der(&encoded).unwrap(); + + assert_eq!(decoded.method, AdjustMethodRelative::Subtract); + assert!(decoded.intermediate_certificates.is_none()); + } + + #[test] + fn match_swap_op_roundtrip() { + let swap = test_address(); + let other = test_token(); + let sell_token = test_token(); + let buy_token = test_address(); + + let original = MatchSwapOp { + swap: Bytes::new(&swap).unwrap(), + other: Bytes::new(&other).unwrap(), + sell: TokenValue { token: Bytes::new(&sell_token).unwrap(), value: Int::new(&[0x64]).unwrap() }, + buy: TokenValue { token: Bytes::new(&buy_token).unwrap(), value: Int::new(&[0x32]).unwrap() }, + fee: NullOr::Null, + }; + let encoded = original.to_der().unwrap(); + let decoded: MatchSwapOp = MatchSwapOp::from_der(&encoded).unwrap(); + + assert_eq!(decoded.swap.as_bytes(), &swap); + assert!(matches!(decoded.fee, NullOr::Null)); + } + + #[test] + fn cancel_swap_op_roundtrip() { + let swap = test_address(); + let sell_token = test_token(); + + let original = CancelSwapOp { + swap: Bytes::new(&swap).unwrap(), + sell: TokenValue { token: Bytes::new(&sell_token).unwrap(), value: Int::new(&[0x10]).unwrap() }, + fee: NullOr::Null, + }; + let encoded = original.to_der().unwrap(); + let decoded: CancelSwapOp = CancelSwapOp::from_der(&encoded).unwrap(); + + assert_eq!(decoded.swap.as_bytes(), &swap); + } + + // ============================================================================ + // CreateIdentifier Tests + // ============================================================================ + + #[test] + fn create_identifier_token_roundtrip() { + let identifier = test_token(); + + let original = CreateIdentifierOp { identifier: Bytes::new(&identifier).unwrap(), create_arguments: None }; + let encoded = original.to_der().unwrap(); + let decoded: CreateIdentifierOp = CreateIdentifierOp::from_der(&encoded).unwrap(); + + assert_eq!(decoded.identifier.as_bytes(), &identifier); + assert!(decoded.create_arguments.is_none()); + } + + #[test] + fn create_identifier_multisig_roundtrip() { + let identifier = test_token(); + let signers_raw = [0x30, 0x04, 0x04, 0x02, 0xAB, 0xCD]; // Mock signers sequence + + let original = CreateIdentifierOp { + identifier: Bytes::new(&identifier).unwrap(), + create_arguments: Some(CreateIdentifierArgs::Multisig(MultisigArgs { + signers: Bytes::new(&signers_raw).unwrap(), + quorum: 2, + })), + }; + let encoded = original.to_der().unwrap(); + let decoded: CreateIdentifierOp = CreateIdentifierOp::from_der(&encoded).unwrap(); + + match decoded.create_arguments { + Some(CreateIdentifierArgs::Multisig(args)) => { + assert_eq!(args.signers.as_bytes(), &signers_raw); + assert_eq!(args.quorum, 2); + } + _ => panic!("Expected Multisig args"), + } + } + + #[test] + fn create_identifier_swap_roundtrip() { + let identifier = test_token(); + let sell_token = test_token(); + let buy_token = test_address(); + + let original = CreateIdentifierOp { + identifier: Bytes::new(&identifier).unwrap(), + create_arguments: Some(CreateIdentifierArgs::Swap(SwapArgs { + sell_token_rate: TokenRate { + token: Bytes::new(&sell_token).unwrap(), + rate: Int::new(&[0x64]).unwrap(), + }, + buy_token_rate: TokenRate { token: Bytes::new(&buy_token).unwrap(), rate: Int::new(&[0x32]).unwrap() }, + fee_token_rate: NullOr::Null, + quantity: Int::new(&[0x0A]).unwrap(), + })), + }; + let encoded = original.to_der().unwrap(); + let decoded: CreateIdentifierOp = CreateIdentifierOp::from_der(&encoded).unwrap(); + + match decoded.create_arguments { + Some(CreateIdentifierArgs::Swap(args)) => { + assert!(matches!(args.fee_token_rate, NullOr::Null)); + } + _ => panic!("Expected Swap args"), + } + } + + // ============================================================================ + // Operation Enum Tests + // ============================================================================ + + #[test] + fn operation_send_roundtrip() { + let to = test_address(); + let token = test_token(); + + let original = Operation::Send(SendOp { + to: Bytes::new(&to).unwrap(), + amount: Int::new(&[0x10]).unwrap(), + token: Bytes::new(&token).unwrap(), + external: None, + }); + let encoded = original.to_der().unwrap(); + let decoded: Operation = Operation::from_der(&encoded).unwrap(); + + assert!(matches!(decoded, Operation::Send(_))); + } + + #[test] + fn operation_set_rep_roundtrip() { + let to = test_address(); + + let original = Operation::SetRep(SetRepOp { to: Bytes::new(&to).unwrap() }); + let encoded = original.to_der().unwrap(); + let decoded: Operation = Operation::from_der(&encoded).unwrap(); + + assert!(matches!(decoded, Operation::SetRep(_))); + } + + #[test] + fn operation_manage_certificate_roundtrip() { + let cert = [0x30, 0x10]; + + let original = Operation::ManageCertificate(ManageCertificateOp { + method: AdjustMethodRelative::Add, + certificate_or_hash: Bytes::new(&cert).unwrap(), + intermediate_certificates: None, + }); + let encoded = original.to_der().unwrap(); + let decoded: Operation = Operation::from_der(&encoded).unwrap(); + + assert!(matches!(decoded, Operation::ManageCertificate(_))); + } +} From 032f569ff069ff9e19c307b64cd086351f529145 Mon Sep 17 00:00:00 2001 From: mamonet Date: Thu, 15 Jan 2026 23:39:11 +0100 Subject: [PATCH 13/25] Export vote and certificate types from lib.rs --- keetanetwork-block/src/lib.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/keetanetwork-block/src/lib.rs b/keetanetwork-block/src/lib.rs index f593211..1d6d902 100644 --- a/keetanetwork-block/src/lib.rs +++ b/keetanetwork-block/src/lib.rs @@ -6,9 +6,23 @@ mod types; -// KeetaBlock and builder require alloc (uses Vec) +// Types that require alloc (use Vec) #[cfg(any(feature = "alloc", feature = "std"))] -pub use types::{KeetaBlock, KeetaBlockBuilder}; +pub use types::{ + // Vote/Certificate types + AlgorithmIdentifier, + CertificateExtensionWrapper, + FeeData, + HashData, + // Block types + KeetaBlock, + KeetaBlockBuilder, + SubjectPublicKeyInfo, + TbsCertificate, + Validity, + Vote, + VoteStaple, +}; pub use types::{ // Enums From 4135c36a88073c4aed5d60cb748eb4e61bf063f3 Mon Sep 17 00:00:00 2001 From: mamonet Date: Sun, 18 Jan 2026 17:17:51 +0100 Subject: [PATCH 14/25] Add KeetaBlock DER encoding/decoding with SDK compatibility tests --- keetanetwork-block/Cargo.toml | 2 + keetanetwork-block/src/block.rs | 687 ++++++++++++++++++ keetanetwork-block/src/lib.rs | 3 + keetanetwork-block/src/types.rs | 188 ++--- .../tests/samples/CREATE_IDENTIFIER.hex | 5 + .../tests/samples/MANAGE_CERTIFICATE.hex | 5 + keetanetwork-block/tests/samples/RECEIVE.hex | 5 + keetanetwork-block/tests/samples/SEND.hex | 5 + keetanetwork-block/tests/samples/SET_INFO.hex | 5 + keetanetwork-block/tests/samples/SET_REP.hex | 5 + keetanetwork-block/tests/samples/SWAP.hex | 5 + .../tests/samples/TOKEN_ADMIN_SUPPLY.hex | 5 + keetanetwork-block/tests/sdk_compat.rs | 56 ++ 13 files changed, 891 insertions(+), 85 deletions(-) create mode 100644 keetanetwork-block/src/block.rs create mode 100644 keetanetwork-block/tests/samples/CREATE_IDENTIFIER.hex create mode 100644 keetanetwork-block/tests/samples/MANAGE_CERTIFICATE.hex create mode 100644 keetanetwork-block/tests/samples/RECEIVE.hex create mode 100644 keetanetwork-block/tests/samples/SEND.hex create mode 100644 keetanetwork-block/tests/samples/SET_INFO.hex create mode 100644 keetanetwork-block/tests/samples/SET_REP.hex create mode 100644 keetanetwork-block/tests/samples/SWAP.hex create mode 100644 keetanetwork-block/tests/samples/TOKEN_ADMIN_SUPPLY.hex create mode 100644 keetanetwork-block/tests/sdk_compat.rs diff --git a/keetanetwork-block/Cargo.toml b/keetanetwork-block/Cargo.toml index 215d6ff..9609e8c 100644 --- a/keetanetwork-block/Cargo.toml +++ b/keetanetwork-block/Cargo.toml @@ -17,6 +17,8 @@ alloc = ["der/alloc"] der = { version = "0.7.10", default-features = false, features = ["derive"] } [dev-dependencies] +der = "0.7.10" +hex = "0.4" [lib] name = "keetanetwork_block" diff --git a/keetanetwork-block/src/block.rs b/keetanetwork-block/src/block.rs new file mode 100644 index 0000000..39d2d3a --- /dev/null +++ b/keetanetwork-block/src/block.rs @@ -0,0 +1,687 @@ +//! KeetaBlock DER parsing and encoding +//! +//! This module provides `Decode` and `Encode` implementations for `KeetaBlock`, +//! enabling full block serialization and deserialization. +//! +//! ## Block Formats +//! +//! - **V1**: Plain SEQUENCE with version field = 0 +//! - **V2**: Wrapped in context tag [1] + +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::vec::Vec; + +#[cfg(feature = "std")] +use std::vec::Vec; + +use der::{asn1::OctetStringRef, Decode, Encode, Header, Length, Reader, Tag, TagNumber, Writer}; + +use crate::types::{ + BlockHeader, BlockPurpose, BlockVersion, KeetaBlock, MultiSigSigner, MultiSigSignerInfo, Operation, SignerField, +}; + +// ============================================================================ +// KeetaBlock Decode Implementation +// ============================================================================ + +#[cfg(any(feature = "alloc", feature = "std"))] +impl<'a> Decode<'a> for KeetaBlock<'a> { + fn decode>(reader: &mut R) -> der::Result { + // Peek at the first tag to determine block version + let tag = reader.peek_tag()?; + + if tag == Tag::Sequence { + // V1 block: plain SEQUENCE + decode_v1_block(reader) + } else if tag.is_context_specific() && tag.number() == TagNumber::new(1) { + // V2 block: context tag [1] + decode_v2_block(reader) + } else { + Err(Tag::Sequence.value_error()) + } + } +} + +/// Decode a V1 block +/// +/// V1 Structure: +/// ```text +/// SEQUENCE { +/// version INTEGER (= 0), +/// network INTEGER, +/// [subnet] NULL or INTEGER, +/// date GeneralizedTime, +/// account OCTET STRING, +/// [signer] NULL or OCTET STRING, +/// previous OCTET STRING, +/// operations SEQUENCE OF Operation, +/// signature OCTET STRING +/// } +/// ``` +#[cfg(any(feature = "alloc", feature = "std"))] +fn decode_v1_block<'a, R: Reader<'a>>(reader: &mut R) -> der::Result> { + reader.sequence(|seq| { + // version (must be 0 for V1) + let version: u8 = seq.decode()?; + if version != 0 { + return Err(Tag::Integer.value_error()); + } + + // network + let network: u64 = seq.decode()?; + + // subnet + let subnet = decode_null_or_integer(seq)?; + + // date + let date = read_generalized_time_bytes(seq)?; + + // account + let account: OctetStringRef = seq.decode()?; + + // signer + let signer = decode_v1_signer(seq)?; + + // previous + let previous: OctetStringRef = seq.decode()?; + + // operations + let operations = decode_operations(seq)?; + + // signature + let signature: OctetStringRef = seq.decode()?; + + Ok(KeetaBlock { + version: BlockVersion::V1, + header: BlockHeader { + network, + subnet, + idempotent: None, + date, + purpose: BlockPurpose::Generic, + account: account.as_bytes(), + signer, + previous: previous.as_bytes(), + }, + operations, + signatures: vec![signature.as_bytes()], + }) + }) +} + +/// Decode a V2 block +/// +/// V2 Structure: +/// ```text +/// [1] EXPLICIT { +/// SEQUENCE { +/// network INTEGER, +/// date GeneralizedTime, +/// purpose INTEGER, +/// account OCTET STRING, +/// signer NULL | OCTET STRING | SEQUENCE, +/// previous OCTET STRING, +/// operations SEQUENCE OF Operation, +/// signature OCTET STRING +/// } +/// } +/// ``` +#[cfg(any(feature = "alloc", feature = "std"))] +fn decode_v2_block<'a, R: Reader<'a>>(reader: &mut R) -> der::Result> { + // Read and verify context tag [1] + let header = Header::decode(reader)?; + if !header.tag.is_context_specific() || header.tag.number() != TagNumber::new(1) { + return Err(Tag::ContextSpecific { number: TagNumber::new(1), constructed: true }.value_error()); + } + + // Read the inner SEQUENCE + reader.sequence(|seq| { + // network + let network: u64 = seq.decode()?; + + // date + let date = read_generalized_time_bytes(seq)?; + + // purpose + let purpose_val: u8 = seq.decode()?; + let purpose = BlockPurpose::try_from(purpose_val).map_err(|_| Tag::Integer.value_error())?; + + // account + let account: OctetStringRef = seq.decode()?; + + // signer + let signer = decode_v2_signer(seq)?; + + // previous + let previous: OctetStringRef = seq.decode()?; + + // operations + let operations = decode_operations(seq)?; + + // signatures + let signatures = decode_signatures(seq)?; + + Ok(KeetaBlock { + version: BlockVersion::V2, + header: BlockHeader { + network, + subnet: None, + idempotent: None, + date, + purpose, + account: account.as_bytes(), + signer, + previous: previous.as_bytes(), + }, + operations, + signatures, + }) + }) +} + +// ============================================================================ +// Helper Functions for Decoding +// ============================================================================ + +/// Decodes `NULL` or `INTEGER`, returning `Option`. +fn decode_null_or_integer<'a, R: Reader<'a>>(reader: &mut R) -> der::Result> { + let tag = reader.peek_tag()?; + if tag == Tag::Null { + let _: der::asn1::Null = reader.decode()?; + Ok(None) + } else { + let value: u64 = reader.decode()?; + Ok(Some(value)) + } +} + +/// Reads `GeneralizedTime` as raw content bytes. +fn read_generalized_time_bytes<'a, R: Reader<'a>>(reader: &mut R) -> der::Result<&'a [u8]> { + let header = Header::decode(reader)?; + if header.tag != Tag::GeneralizedTime { + return Err(Tag::GeneralizedTime.value_error()); + } + reader.read_slice(header.length) +} + +/// Decodes V1 signer field. +#[cfg(any(feature = "alloc", feature = "std"))] +fn decode_v1_signer<'a, R: Reader<'a>>(reader: &mut R) -> der::Result> { + let tag = reader.peek_tag()?; + if tag == Tag::Null { + let _: der::asn1::Null = reader.decode()?; + Ok(SignerField::AccountIsSigner) + } else { + let signer: OctetStringRef = reader.decode()?; + Ok(SignerField::Single(signer.as_bytes())) + } +} + +/// Decodes V2 signer field. +#[cfg(any(feature = "alloc", feature = "std"))] +fn decode_v2_signer<'a, R: Reader<'a>>(reader: &mut R) -> der::Result> { + let tag = reader.peek_tag()?; + + if tag == Tag::Null { + let _: der::asn1::Null = reader.decode()?; + Ok(SignerField::AccountIsSigner) + } else if tag == Tag::OctetString { + let signer: OctetStringRef = reader.decode()?; + Ok(SignerField::Single(signer.as_bytes())) + } else if tag == Tag::Sequence { + // Multisig signer - read sequence content directly + let header = Header::decode(reader)?; + let content = reader.read_slice(header.length)?; + let multisig = decode_multisig_content(content)?; + Ok(SignerField::Multisig(multisig)) + } else { + Err(tag.value_error()) + } +} + +/// Decodes multisig signer info from `SEQUENCE` content. +#[cfg(any(feature = "alloc", feature = "std"))] +fn decode_multisig_content<'a>(content: &'a [u8]) -> der::Result> { + use der::SliceReader; + let mut reader = SliceReader::new(content)?; + + // multisig public key + let multisig_pub_key: OctetStringRef = reader.decode()?; + + // Read inner SEQUENCE header for signers + let inner_header = Header::decode(&mut reader)?; + if inner_header.tag != Tag::Sequence { + return Err(Tag::Sequence.value_error()); + } + let signers_content = reader.read_slice(inner_header.length)?; + + // Read signers from the inner content + let signers = decode_multisig_signers(signers_content)?; + + Ok(MultiSigSignerInfo { multisig_pub_key: multisig_pub_key.as_bytes(), signers }) +} + +/// Decodes a list of multisig signers from raw bytes. +#[cfg(any(feature = "alloc", feature = "std"))] +fn decode_multisig_signers<'a>(content: &'a [u8]) -> der::Result>> { + use der::SliceReader; + let mut reader = SliceReader::new(content)?; + let mut signers = Vec::new(); + + while !reader.is_finished() { + let tag = reader.peek_tag()?; + if tag == Tag::OctetString { + let key: OctetStringRef = reader.decode()?; + signers.push(MultiSigSigner::Key(key.as_bytes())); + } else if tag == Tag::Sequence { + // For nested multisig, read the content and recurse + let header = Header::decode(&mut reader)?; + let nested_content = reader.read_slice(header.length)?; + let nested = decode_multisig_content(nested_content)?; + signers.push(MultiSigSigner::Nested(Box::new(nested))); + } else { + return Err(tag.value_error()); + } + } + + Ok(signers) +} + +/// Decodes signatures from a single or multiple `OCTET STRING`. +#[cfg(any(feature = "alloc", feature = "std"))] +fn decode_signatures<'a, R: Reader<'a>>(reader: &mut R) -> der::Result> { + let tag = reader.peek_tag()?; + + if tag == Tag::OctetString { + // Single signature + let sig: OctetStringRef = reader.decode()?; + Ok(vec![sig.as_bytes()]) + } else if tag == Tag::Sequence { + // Multiple signatures wrapped in SEQUENCE + let header = Header::decode(reader)?; + let content = reader.read_slice(header.length)?; + + // Parse signatures from the sequence content + use der::SliceReader; + let mut sig_reader = SliceReader::new(content)?; + let mut signatures = Vec::new(); + + while !sig_reader.is_finished() { + let sig: OctetStringRef = sig_reader.decode()?; + signatures.push(sig.as_bytes()); + } + + Ok(signatures) + } else { + Err(Tag::OctetString.value_error()) + } +} + +/// Decodes operations sequence. +#[cfg(any(feature = "alloc", feature = "std"))] +fn decode_operations<'a, R: Reader<'a>>(reader: &mut R) -> der::Result>> { + let mut operations = Vec::new(); + reader.sequence(|seq| { + while !seq.is_finished() { + operations.push(Operation::decode(seq)?); + } + Ok(()) + })?; + Ok(operations) +} + +// ============================================================================ +// KeetaBlock Encode Implementation +// ============================================================================ + +#[cfg(any(feature = "alloc", feature = "std"))] +impl Encode for KeetaBlock<'_> { + fn encoded_len(&self) -> der::Result { + match self.version { + BlockVersion::V1 => self.v1_encoded_len(), + BlockVersion::V2 => self.v2_encoded_len(), + } + } + + fn encode(&self, writer: &mut impl Writer) -> der::Result<()> { + match self.version { + BlockVersion::V1 => self.encode_v1(writer), + BlockVersion::V2 => self.encode_v2(writer), + } + } +} + +#[cfg(any(feature = "alloc", feature = "std"))] +impl KeetaBlock<'_> { + /// Calculate encoded length for V1 block + fn v1_encoded_len(&self) -> der::Result { + let content_len = self.v1_content_len()?; + Header::new(Tag::Sequence, content_len)?.encoded_len() + content_len + } + + /// Calculate V1 content length + fn v1_content_len(&self) -> der::Result { + // version (0 for V1) + let version_len = 0u8.encoded_len()?; + + // network + let network_len = self.header.network.encoded_len()?; + + // subnet (NULL or INTEGER) + let subnet_len = match self.header.subnet { + Some(s) => s.encoded_len()?, + None => der::asn1::Null.encoded_len()?, + }; + + // date + let date_len = encode_generalized_time_len(self.header.date)?; + + // account + let account_len = OctetStringRef::new(self.header.account)?.encoded_len()?; + + // signer + let signer_len = encode_signer_len(&self.header.signer)?; + + // previous + let previous_len = OctetStringRef::new(self.header.previous)?.encoded_len()?; + + // operations + let ops_len = self.operations_encoded_len()?; + + // signature + let sig_len = if !self.signatures.is_empty() { + OctetStringRef::new(self.signatures[0])?.encoded_len()? + } else { + Length::ZERO + }; + + version_len + network_len + subnet_len + date_len + account_len + signer_len + previous_len + ops_len + sig_len + } + + /// Encode V1 block + fn encode_v1(&self, writer: &mut impl Writer) -> der::Result<()> { + let content_len = self.v1_content_len()?; + Header::new(Tag::Sequence, content_len)?.encode(writer)?; + + // version (0 for V1) + 0u8.encode(writer)?; + + // network + self.header.network.encode(writer)?; + + // subnet + match self.header.subnet { + Some(s) => s.encode(writer)?, + None => der::asn1::Null.encode(writer)?, + }; + + // date + encode_generalized_time(self.header.date, writer)?; + + // account + OctetStringRef::new(self.header.account)?.encode(writer)?; + + // signer + encode_signer(&self.header.signer, writer)?; + + // previous + OctetStringRef::new(self.header.previous)?.encode(writer)?; + + // operations + self.encode_operations(writer)?; + + // signature + if !self.signatures.is_empty() { + OctetStringRef::new(self.signatures[0])?.encode(writer)?; + } + + Ok(()) + } + + /// Calculate encoded length for V2 block + fn v2_encoded_len(&self) -> der::Result { + let inner_content_len = self.v2_content_len()?; + let inner_seq_len = (Header::new(Tag::Sequence, inner_content_len)?.encoded_len() + inner_content_len)?; + + // Context tag [1] wrapping + let ctx_header = + Header::new(Tag::ContextSpecific { number: TagNumber::new(1), constructed: true }, inner_seq_len)?; + ctx_header.encoded_len() + inner_seq_len + } + + /// Calculate V2 content length + fn v2_content_len(&self) -> der::Result { + // network + let network_len = self.header.network.encoded_len()?; + + // date + let date_len = encode_generalized_time_len(self.header.date)?; + + // purpose + let purpose_len = (self.header.purpose as u8).encoded_len()?; + + // account + let account_len = OctetStringRef::new(self.header.account)?.encoded_len()?; + + // signer + let signer_len = encode_signer_len(&self.header.signer)?; + + // previous + let previous_len = OctetStringRef::new(self.header.previous)?.encoded_len()?; + + // operations + let ops_len = self.operations_encoded_len()?; + + // signatures + let sig_len = self.signatures_encoded_len()?; + + network_len + date_len + purpose_len + account_len + signer_len + previous_len + ops_len + sig_len + } + + /// Encode V2 block + fn encode_v2(&self, writer: &mut impl Writer) -> der::Result<()> { + let inner_content_len = self.v2_content_len()?; + let inner_seq_len = (Header::new(Tag::Sequence, inner_content_len)?.encoded_len() + inner_content_len)?; + + // Write context tag [1] + Header::new(Tag::ContextSpecific { number: TagNumber::new(1), constructed: true }, inner_seq_len)? + .encode(writer)?; + + // Write inner SEQUENCE + Header::new(Tag::Sequence, inner_content_len)?.encode(writer)?; + + // network + self.header.network.encode(writer)?; + + // date + encode_generalized_time(self.header.date, writer)?; + + // purpose + (self.header.purpose as u8).encode(writer)?; + + // account + OctetStringRef::new(self.header.account)?.encode(writer)?; + + // signer + encode_signer(&self.header.signer, writer)?; + + // previous + OctetStringRef::new(self.header.previous)?.encode(writer)?; + + // operations + self.encode_operations(writer)?; + + // signature(s) + self.encode_signatures(writer)?; + + Ok(()) + } + + /// Calculate encoded length of signatures + fn signatures_encoded_len(&self) -> der::Result { + if self.signatures.is_empty() { + return Ok(Length::ZERO); + } + + if self.signatures.len() == 1 { + // Single signature: just OCTET STRING + OctetStringRef::new(self.signatures[0])?.encoded_len() + } else { + // Multiple signatures: SEQUENCE of OCTET STRINGs + let mut content_len = Length::ZERO; + for sig in &self.signatures { + content_len = (content_len + OctetStringRef::new(sig)?.encoded_len()?)?; + } + Header::new(Tag::Sequence, content_len)?.encoded_len() + content_len + } + } + + /// Encode signatures + fn encode_signatures(&self, writer: &mut impl Writer) -> der::Result<()> { + if self.signatures.is_empty() { + return Ok(()); + } + + if self.signatures.len() == 1 { + // Single signature: just OCTET STRING + OctetStringRef::new(self.signatures[0])?.encode(writer) + } else { + // Multiple signatures: SEQUENCE of OCTET STRINGs + let mut content_len = Length::ZERO; + for sig in &self.signatures { + content_len = (content_len + OctetStringRef::new(sig)?.encoded_len()?)?; + } + Header::new(Tag::Sequence, content_len)?.encode(writer)?; + for sig in &self.signatures { + OctetStringRef::new(sig)?.encode(writer)?; + } + Ok(()) + } + } + + /// Calculate encoded length of operations sequence + fn operations_encoded_len(&self) -> der::Result { + let mut content_len = Length::ZERO; + for op in &self.operations { + content_len = (content_len + op.encoded_len()?)?; + } + Header::new(Tag::Sequence, content_len)?.encoded_len() + content_len + } + + /// Encode operations sequence + fn encode_operations(&self, writer: &mut impl Writer) -> der::Result<()> { + let mut content_len = Length::ZERO; + for op in &self.operations { + content_len = (content_len + op.encoded_len()?)?; + } + Header::new(Tag::Sequence, content_len)?.encode(writer)?; + for op in &self.operations { + op.encode(writer)?; + } + Ok(()) + } +} + +// ============================================================================ +// Helper Functions for Encoding +// ============================================================================ + +/// Calculates signer field encoded length. +#[cfg(any(feature = "alloc", feature = "std"))] +fn encode_signer_len(signer: &SignerField) -> der::Result { + match signer { + SignerField::AccountIsSigner => der::asn1::Null.encoded_len(), + SignerField::Single(key) => OctetStringRef::new(key)?.encoded_len(), + SignerField::Multisig(info) => encode_multisig_len(info), + } +} + +/// Encodes signer field. +#[cfg(any(feature = "alloc", feature = "std"))] +fn encode_signer(signer: &SignerField, writer: &mut impl Writer) -> der::Result<()> { + match signer { + SignerField::AccountIsSigner => der::asn1::Null.encode(writer), + SignerField::Single(key) => OctetStringRef::new(key)?.encode(writer), + SignerField::Multisig(info) => encode_multisig(info, writer), + } +} + +/// Calculates multisig signer encoded length. +#[cfg(any(feature = "alloc", feature = "std"))] +fn encode_multisig_len(info: &MultiSigSignerInfo) -> der::Result { + let pubkey_len = OctetStringRef::new(info.multisig_pub_key)?.encoded_len()?; + + let mut signers_content_len = Length::ZERO; + for signer in &info.signers { + let signer_len = match signer { + MultiSigSigner::Key(key) => OctetStringRef::new(key)?.encoded_len()?, + MultiSigSigner::Nested(nested) => encode_multisig_len(nested)?, + }; + signers_content_len = (signers_content_len + signer_len)?; + } + let signers_seq_len = (Header::new(Tag::Sequence, signers_content_len)?.encoded_len() + signers_content_len)?; + + let content_len = (pubkey_len + signers_seq_len)?; + Header::new(Tag::Sequence, content_len)?.encoded_len() + content_len +} + +/// Encodes multisig signer. +#[cfg(any(feature = "alloc", feature = "std"))] +fn encode_multisig(info: &MultiSigSignerInfo, writer: &mut impl Writer) -> der::Result<()> { + let pubkey_len = OctetStringRef::new(info.multisig_pub_key)?.encoded_len()?; + + let mut signers_content_len = Length::ZERO; + for signer in &info.signers { + let signer_len = match signer { + MultiSigSigner::Key(key) => OctetStringRef::new(key)?.encoded_len()?, + MultiSigSigner::Nested(nested) => encode_multisig_len(nested)?, + }; + signers_content_len = (signers_content_len + signer_len)?; + } + let signers_seq_len = (Header::new(Tag::Sequence, signers_content_len)?.encoded_len() + signers_content_len)?; + + let content_len = (pubkey_len + signers_seq_len)?; + + // Write outer sequence + Header::new(Tag::Sequence, content_len)?.encode(writer)?; + + // Write pubkey + OctetStringRef::new(info.multisig_pub_key)?.encode(writer)?; + + // Write signers sequence + Header::new(Tag::Sequence, signers_content_len)?.encode(writer)?; + for signer in &info.signers { + match signer { + MultiSigSigner::Key(key) => OctetStringRef::new(key)?.encode(writer)?, + MultiSigSigner::Nested(nested) => encode_multisig(nested, writer)?, + } + } + + Ok(()) +} + +/// Calculates `GeneralizedTime` encoded length. +fn encode_generalized_time_len(date_bytes: &[u8]) -> der::Result { + let content_len = Length::try_from(date_bytes.len())?; + Header::new(Tag::GeneralizedTime, content_len)?.encoded_len() + content_len +} + +/// Encodes `GeneralizedTime` from raw bytes. +fn encode_generalized_time(date_bytes: &[u8], writer: &mut impl Writer) -> der::Result<()> { + let content_len = Length::try_from(date_bytes.len())?; + Header::new(Tag::GeneralizedTime, content_len)?.encode(writer)?; + writer.write(date_bytes) +} + +// ============================================================================ +// BlockPurpose Encode +// ============================================================================ + +impl From for u8 { + fn from(purpose: BlockPurpose) -> u8 { + match purpose { + BlockPurpose::Generic => 0, + BlockPurpose::Fee => 1, + } + } +} diff --git a/keetanetwork-block/src/lib.rs b/keetanetwork-block/src/lib.rs index 1d6d902..055cb43 100644 --- a/keetanetwork-block/src/lib.rs +++ b/keetanetwork-block/src/lib.rs @@ -6,6 +6,9 @@ mod types; +#[cfg(any(feature = "alloc", feature = "std"))] +mod block; + // Types that require alloc (use Vec) #[cfg(any(feature = "alloc", feature = "std"))] pub use types::{ diff --git a/keetanetwork-block/src/types.rs b/keetanetwork-block/src/types.rs index 43daaf0..74215c6 100644 --- a/keetanetwork-block/src/types.rs +++ b/keetanetwork-block/src/types.rs @@ -32,7 +32,7 @@ use std::vec::Vec; // DER encoding/decoding support use der::{ asn1::{IntRef, Null, OctetStringRef, Utf8StringRef}, - Choice, Decode, DecodeValue, Encode, EncodeValue, Enumerated, Header, Length, Reader, Sequence, Tag, Writer, + Choice, Decode, DecodeValue, Encode, EncodeValue, Header, Length, Reader, Sequence, Tag, Writer, }; // Type aliases for DER types @@ -164,6 +164,7 @@ pub struct BlockHeader<'a> { /// Account public key with type prefix pub account: &'a [u8], /// Signer information (V1: always Single, V2: can be Single/Multisig/AccountIsSigner) + #[cfg(any(feature = "alloc", feature = "std"))] pub signer: SignerField<'a>, /// Previous block hash (32 bytes) pub previous: &'a [u8], @@ -250,24 +251,20 @@ pub enum MultiSigSigner<'a> { } /// Multisig signer information for V2 blocks +#[cfg(any(feature = "alloc", feature = "std"))] #[derive(Debug, Clone)] -#[cfg_attr(not(any(feature = "alloc", feature = "std")), derive(Copy))] pub struct MultiSigSignerInfo<'a> { /// Public key of the multisig account pub multisig_pub_key: &'a [u8], - /// signers (can be nested multisig or single keys) - #[cfg(any(feature = "alloc", feature = "std"))] + /// Signers (can be nested multisig or single keys) pub signers: Vec>, - /// Raw DER bytes of the signers sequence - #[cfg(not(any(feature = "alloc", feature = "std")))] - pub signers_raw: &'a [u8], } /// Signer field for blocks /// /// V1 blocks always use Single. V2 blocks can use any variant. +#[cfg(any(feature = "alloc", feature = "std"))] #[derive(Debug, Clone)] -#[cfg_attr(not(any(feature = "alloc", feature = "std")), derive(Copy))] pub enum SignerField<'a> { /// Single signer (public key with type prefix) Single(&'a [u8]), @@ -281,8 +278,8 @@ pub enum SignerField<'a> { // Enum Types // ============================================================================ -/// Adjust method for supply/balance/permissions operations -#[derive(Debug, Clone, Copy, PartialEq, Eq, Enumerated)] +/// Adjust method for supply/balance/permissions operations. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum AdjustMethod { Add = 0, @@ -290,14 +287,57 @@ pub enum AdjustMethod { Set = 2, } -/// Adjust method for relative operations (add/subtract only, no set) -#[derive(Debug, Clone, Copy, PartialEq, Eq, Enumerated)] +impl<'a> Decode<'a> for AdjustMethod { + fn decode>(reader: &mut R) -> der::Result { + let value: u8 = reader.decode()?; + match value { + 0 => Ok(AdjustMethod::Add), + 1 => Ok(AdjustMethod::Subtract), + 2 => Ok(AdjustMethod::Set), + _ => Err(Tag::Integer.value_error()), + } + } +} + +impl Encode for AdjustMethod { + fn encoded_len(&self) -> der::Result { + (*self as u8).encoded_len() + } + + fn encode(&self, writer: &mut impl Writer) -> der::Result<()> { + (*self as u8).encode(writer) + } +} + +/// Adjust method for relative operations (add/subtract only, no set). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum AdjustMethodRelative { Add = 0, Subtract = 1, } +impl<'a> Decode<'a> for AdjustMethodRelative { + fn decode>(reader: &mut R) -> der::Result { + let value: u8 = reader.decode()?; + match value { + 0 => Ok(AdjustMethodRelative::Add), + 1 => Ok(AdjustMethodRelative::Subtract), + _ => Err(Tag::Integer.value_error()), + } + } +} + +impl Encode for AdjustMethodRelative { + fn encoded_len(&self) -> der::Result { + (*self as u8).encoded_len() + } + + fn encode(&self, writer: &mut impl Writer) -> der::Result<()> { + (*self as u8).encode(writer) + } +} + // ============================================================================ // Supporting Types // ============================================================================ @@ -493,31 +533,45 @@ pub struct ReceiveOp<'a> { pub forward: Option>, } -/// [8] MANAGE_CERTIFICATE operation - Add or subtract certificates +/// [8] MANAGE_CERTIFICATE operation - Add or subtract certificates. /// -/// Implemented manually because `Option>` doesn't implement `Decode` -/// (the `Option` impl requires `T: FixedTag`, but `NullOr` has no fixed tag). +/// Certificate data is stored as raw DER bytes (can be OCTET STRING or SEQUENCE). #[derive(Debug, Clone)] pub struct ManageCertificateOp<'a> { - /// Method (add/subtract) + /// Method (add/subtract). pub method: AdjustMethodRelative, - /// Certificate (if adding) or certificate hash (if removing) - pub certificate_or_hash: Bytes<'a>, - /// Intermediate certificates (either raw bytes or explicit none) - /// Required when adding a certificate, optional overall - pub intermediate_certificates: Option>>, + /// Certificate DER bytes (if adding) or certificate hash (if removing). + /// Stored with tag+length for roundtrip encoding. + pub certificate_or_hash: &'a [u8], + /// Intermediate certificates DER bytes, NULL, or absent. + pub intermediate_certificates: Option>, +} + +/// Reads raw TLV bytes (tag + length + content) from reader. +fn read_tlv_bytes<'a, R: Reader<'a>>(reader: &mut R) -> der::Result<&'a [u8]> { + let header = reader.peek_header()?; + let tlv_len = (header.encoded_len()? + header.length)?; + reader.read_slice(tlv_len) } impl<'a> DecodeValue<'a> for ManageCertificateOp<'a> { fn decode_value>(reader: &mut R, _header: Header) -> der::Result { let method = reader.decode()?; - let certificate_or_hash = reader.decode()?; - // Handle optional field that can be either bytes or explicit none + // Read certificate as raw TLV bytes (any tag type) + let certificate_or_hash = read_tlv_bytes(reader)?; + + // Handle optional intermediate_certificates field let intermediate_certificates = if reader.is_finished() { None } else { - Some(reader.decode()?) + let tag = reader.peek_tag()?; + if tag == Tag::Null { + let _: Null = reader.decode()?; + Some(NullOr::Null) + } else { + Some(NullOr::Value(read_tlv_bytes(reader)?)) + } }; Ok(ManageCertificateOp { method, certificate_or_hash, intermediate_certificates }) @@ -527,20 +581,26 @@ impl<'a> DecodeValue<'a> for ManageCertificateOp<'a> { impl EncodeValue for ManageCertificateOp<'_> { fn value_len(&self) -> der::Result { self.method.encoded_len()? - + self.certificate_or_hash.encoded_len()? + + Length::try_from(self.certificate_or_hash.len())? + self .intermediate_certificates .as_ref() - .map(|v| v.encoded_len()) + .map(|v| match v { + NullOr::Null => Null.encoded_len(), + NullOr::Value(bytes) => Length::try_from(bytes.len()), + }) .transpose()? .unwrap_or(Length::ZERO) } fn encode_value(&self, writer: &mut impl Writer) -> der::Result<()> { self.method.encode(writer)?; - self.certificate_or_hash.encode(writer)?; + writer.write(self.certificate_or_hash)?; if let Some(ref certs) = self.intermediate_certificates { - certs.encode(writer)?; + match certs { + NullOr::Null => Null.encode(writer)?, + NullOr::Value(bytes) => writer.write(bytes)?, + } } Ok(()) } @@ -1114,32 +1174,33 @@ mod tests { #[test] fn manage_certificate_op_add_roundtrip() { - let cert = [0x30, 0x82, 0x01, 0x00]; // Mock certificate - let intermediate = [0x30, 0x82, 0x02, 0x00]; // Mock intermediate + // Raw DER: SEQUENCE with some content (mock X.509 certificate) + let cert_der = [0x30, 0x03, 0x01, 0x01, 0xFF]; // SEQUENCE { BOOLEAN TRUE } + let intermediate_der = [0x30, 0x03, 0x02, 0x01, 0x00]; // SEQUENCE { INTEGER 0 } let original = ManageCertificateOp { method: AdjustMethodRelative::Add, - certificate_or_hash: Bytes::new(&cert).unwrap(), - intermediate_certificates: Some(NullOr::Value(Bytes::new(&intermediate).unwrap())), + certificate_or_hash: &cert_der, + intermediate_certificates: Some(NullOr::Value(&intermediate_der)), }; let encoded = original.to_der().unwrap(); let decoded: ManageCertificateOp = ManageCertificateOp::from_der(&encoded).unwrap(); assert_eq!(decoded.method, AdjustMethodRelative::Add); - assert_eq!(decoded.certificate_or_hash.as_bytes(), &cert); + assert_eq!(decoded.certificate_or_hash, &cert_der); match decoded.intermediate_certificates { - Some(NullOr::Value(i)) => assert_eq!(i.as_bytes(), &intermediate), + Some(NullOr::Value(i)) => assert_eq!(i, &intermediate_der), _ => panic!("Expected intermediate certificates"), } } #[test] fn manage_certificate_op_add_no_intermediate_roundtrip() { - let cert = [0x30, 0x82, 0x01, 0x00]; + let cert_der = [0x30, 0x03, 0x01, 0x01, 0xFF]; let original = ManageCertificateOp { method: AdjustMethodRelative::Add, - certificate_or_hash: Bytes::new(&cert).unwrap(), + certificate_or_hash: &cert_der, intermediate_certificates: Some(NullOr::Null), }; let encoded = original.to_der().unwrap(); @@ -1150,11 +1211,15 @@ mod tests { #[test] fn manage_certificate_op_subtract_roundtrip() { - let hash = [0xAB; 32]; // Certificate hash + // OCTET STRING containing 32-byte hash + let mut hash_der = [0u8; 34]; + hash_der[0] = 0x04; // OCTET STRING tag + hash_der[1] = 0x20; // length 32 + hash_der[2..].fill(0xAB); // hash bytes let original = ManageCertificateOp { method: AdjustMethodRelative::Subtract, - certificate_or_hash: Bytes::new(&hash).unwrap(), + certificate_or_hash: &hash_der, intermediate_certificates: None, }; let encoded = original.to_der().unwrap(); @@ -1269,51 +1334,4 @@ mod tests { _ => panic!("Expected Swap args"), } } - - // ============================================================================ - // Operation Enum Tests - // ============================================================================ - - #[test] - fn operation_send_roundtrip() { - let to = test_address(); - let token = test_token(); - - let original = Operation::Send(SendOp { - to: Bytes::new(&to).unwrap(), - amount: Int::new(&[0x10]).unwrap(), - token: Bytes::new(&token).unwrap(), - external: None, - }); - let encoded = original.to_der().unwrap(); - let decoded: Operation = Operation::from_der(&encoded).unwrap(); - - assert!(matches!(decoded, Operation::Send(_))); - } - - #[test] - fn operation_set_rep_roundtrip() { - let to = test_address(); - - let original = Operation::SetRep(SetRepOp { to: Bytes::new(&to).unwrap() }); - let encoded = original.to_der().unwrap(); - let decoded: Operation = Operation::from_der(&encoded).unwrap(); - - assert!(matches!(decoded, Operation::SetRep(_))); - } - - #[test] - fn operation_manage_certificate_roundtrip() { - let cert = [0x30, 0x10]; - - let original = Operation::ManageCertificate(ManageCertificateOp { - method: AdjustMethodRelative::Add, - certificate_or_hash: Bytes::new(&cert).unwrap(), - intermediate_certificates: None, - }); - let encoded = original.to_der().unwrap(); - let decoded: Operation = Operation::from_der(&encoded).unwrap(); - - assert!(matches!(decoded, Operation::ManageCertificate(_))); - } } diff --git a/keetanetwork-block/tests/samples/CREATE_IDENTIFIER.hex b/keetanetwork-block/tests/samples/CREATE_IDENTIFIER.hex new file mode 100644 index 0000000..f2ae5ac --- /dev/null +++ b/keetanetwork-block/tests/samples/CREATE_IDENTIFIER.hex @@ -0,0 +1,5 @@ +// CREATE_IDENTIFIER block from explorer +// Hash: 3A11F804843FC448E14F86C576824A9C3D1938DB7B68859799904532F7DF5DD6 +// Source: https://explorer.keeta.com/block/3A11F804843FC448E14F86C576824A9C3D1938DB7B68859799904532F7DF5DD6 + +3081d1020100020253820500181332303236303131363231323530372e3430325a042200036e2c89c42b41859f1386f4384cde7ec515ec201c84702f49f491f9130754805a05000420231ee41fb259e22daab123593683b9cfa9c8a3e014ec1470cdda90cea002aec23027a4253023042103b1dd74a8e92843a6b74dbf7afbb0983fa57c5994d921b0b0079703560adb325e0440ce1a7b115f10d2ff2f5aa6e136654b33bf1e8d82d810d4073aa2ced26d0277535df4c8e2ee5625846709783a152d2781942581efdab3dbcff2a68e549d1c5ea2 diff --git a/keetanetwork-block/tests/samples/MANAGE_CERTIFICATE.hex b/keetanetwork-block/tests/samples/MANAGE_CERTIFICATE.hex new file mode 100644 index 0000000..03c73cf --- /dev/null +++ b/keetanetwork-block/tests/samples/MANAGE_CERTIFICATE.hex @@ -0,0 +1,5 @@ +// MANAGE_CERTIFICATE block from explorer +// Hash: 02F1D8D6A4091979142AB1AFC13F1FC95121C17B3EF854F13A0824A220292321 +// Source: https://explorer.keeta.com/block/02F1D8D6A4091979142AB1AFC13F1FC95121C17B3EF854F13A0824A220292321 + +30821334020100020253820500181332303236303131363130323633322e3237355a04220002311db074927ac49fa1c3930cda10ec1030f4466efa3b6681977811c286bf46ae050004202260aaf4a832a1732aa1be40f2d2cf638988abd36e151ad6e1f9686a2ccd872c30821288a88212843082128002010004821101308210fd308210a2a0030201020210418811a2c0fde40f0af0a8bc45c94a41300b060960864801650304030a3018311630140603550403130d466f6f747072696e74204b5943301e170d3236303131363130323633305a170d3237303131363130323633305a3050314e304c060355040316456b656574615f6161626463686e716f736a68767265377568627a6764673263647762616d6875697a7870756f336771676c7871656f63713237756e6c7573797666376936613036301006072a8648ce3d020106052b8104000a03220002311db074927ac49fa1c3930cda10ec1030f4466efa3b6681977811c286bf46aea3820fb630820fb2300e0603551d0f0101ff0404030200c0301f0603551d23041830168014afce467da1b3582966dddb962f9682564797d740301d0603551d0e04160414f93a08d95023b16f5e1a8ffb8c44e4d8c179273630820f5e060a2b0601040183e953000004820f4e30820f4a30820146060a2b0601040183e953010081820136308201320201003081ad060960864801650304012e040c0ebab29e3a1927f1312740290481910431b4956c8f962386867217216823e5265640d5b18ce4735cdeed629b8c5343c8e6b46becb99dc36a65722f2196b63c94be40f6a573c2bf764f29ef856138630118c182be51496978e441c64a36b160c6f3e9104def1c9ad03e6781f5c729442f79ae480498c57a50f7437b14d315c68846ab4d07f153556794f392699dcce751fc6061b48ea0af99a41a04b75cedd2d2305f043004970d09c45586c7c2760f79d2c958a9d3da574b20e60e50247a0769d4fe43937b974549d7689a84073b24ead61e06ab060960864801650304020804206193fa61ebcb465cf0756314161d7754f99261329ac6a20bc93b0aee0d25ebf9041c40c47440ca4acd4e80b025ca91a05191839955a2d59cd885bc0214d030820142060b2b0601040183e953010001818201313082012d0201003081ad060960864801650304012e040cbe61f8923c0df64f1410887f04819104ede4d07ae133bb50df0390f5efe420ae8ff5858b634ce1f2ae288bfe42133ae22cea5c3428eb4b15b30e90e4e228a4fe1172a66c04caf4b59cc2b79378be799930cbd5b583a81163d416d5e1a8107a1a4d6324d95cf40d0fb3ceaa43f3632129e8cf8195e27bedd254f9b604bce9d8f591a6e37244268189a03ec5e1fc4dfdc74391ff26971744c1adf030136f2ea814305f04307f3eefdd2d105c987bca03711feae98051f67650f8ac4a4d40eeb1c6f387dcd65236abf48c080a2459907b9ffcdb6cae06096086480165030402080420e1fded8f77454a468ade5ffb3054d04e639be4d97dafe63c8efe770543319abb0417eace8fcafa00df22796595883866578b79b6f5c1a7418e30820141060b2b0601040183e953010002818201303082012c0201003081ad060960864801650304012e040cb8b2f3bcb4ee94de7d9d228904819104b0bf33fafe7c3d7a5c310cff648db129b4b1933694616e6c79a611fdd05d81789ac443a17d0da034a0ae8d8460a9fa319e13b939e107d82c1ff012cdc4217fd8af920d79d41b3685e990884c6695b928d39346f71ecb73bb3b0dd925a78337285702d25b9f067ebd9e5d7e708e19846dbd6d1490244a4c22f97aff425ad14cc8291cd69804cde887fb0f4385a7eb1d93305f04300e5c7f61dfc6bd9ce71353af13d987090a4961e118d4cf9e94c2537b5bb6864319b49c67cbad88dc6b016405ea3bb7fb060960864801650304020804206d863c8d12f36d1e20263666243e3294e24cc4fbc22533a0d91dc29fd547db0604166359f4cabe62562de8762a034f05a408804812a7fa1630820149060a2b0601040183e953010181820139308201350201003081ad060960864801650304012e040cf1d29e37f13f22e267026aad048191045ee124e44c10e7af8577a59f8e57ca4c81740c47d35e5e3355440d1615430aa658d6e21a6fc43f8f99c3c93dc0382f9ee29508ed14154a68af44a03a16f6ac87f43482c7106905b1a95e72d1229d7b261fadc9c74c6f8f075380a7e646ef946b8bb4bb60a997dc744042891d8463203a2340d9e3a40999853c1d2177406250ff61b5a1b01c6b8d558a3b71611a57b533305f04306a23280c102b33ac5392b908c97c38af7710732e446fc1a0d5613f3639f35853d11d1ddf4db4dcf9b93c9e585b4404a306096086480165030402080420327f7660a4bd119c602ec4ee167c7fda69eb94803c3346b5e2ec511f9b75e3c5041f5a96e94f5c8fff49c46b28f70c16782dd68b4b1d0b2af585b4a4e6e5dfdede3082014e060a2b0601040183e95301038182013e3082013a0201003081ad060960864801650304012e040c557535200b4d2f488721a40b0481910481453fcdbba246bf7d1f49d3f8874fd0978711b158613bebf269b6595842af240004182b3ed28b57b032af4f4151ab9db7e6cd34c0801f35a2221fbb5aff32c341b1699b6194bb91874932c791b20160d608d150a53581d55fcc6896b1167f8f0e6cecdedf06e689134446b856b10b7890b7b5482befc164d1ef69b2dbe9d708cc85c248f67814789ca41586f84e819e305f04306d82713e91cd14a693f2fc556dcba0c7cb127e619701d422ce6895859a84968b83406e5cbda4a685b496cb54f36b82da060960864801650304020804201a91e4c748d5d7a0c46619bbe9f17fa2f46be577039a3bde5db4ce8f896063fe04247bd135edf8a413b6bfb7a3f813267380dfb2602a2c7bbf89c4ba13d5b7ecf0827c18b16e308201b9060a2b0601040183e9530102818201a9308201a50201003081ad060960864801650304012e040c33185b23ccc5e222ad980f27048191044361899b6d12b9913b11d6bd4583a1595a1e21dcc83dac8db1161e8c9555f2433868698feea20d21774c4e93bb39b7729f800a3127f4d40c9f2e629d405641c4f45cd4f2d582e5a128a1f3f7a4042c43a14a1dc5faefce43d02cca76968d972d5ecec6d78c4055f40697eb63c93a7350f2a196707f1cb6449fc5ed687155bcc5afa6914fbd88280381152e1d90157206305f0430d4278aee91bf54f73675f063d0eef1d7be6d8d8cee3c9625c013bc54c15ba4d96e4cb921fadd62f6f2fefb2bb8525e7d06096086480165030402080420c88eaf5b6eea26a73d450f67e06889aaf42a99ca8653b4cf360103e8b696e8b304818e42b2dcd4527433534342fa6c3237405b98331d6117b147d974e0f213687b14506faa3a13f7f55b160e499c33babb74c58f5232a426560ebc695cca41eb7763f605762162c47de2de801cadabc1f2e8baca9736fe6e6f146bd912094510080ec500ecee3c034a1f71b21f52d6849e7f409a1d0012d1e6a2512ef5ad51e3d423d99dc544cf310dd445513f0558013330820325060b2b0601040183e953010b0281820314308203100201003081ad060960864801650304012e040ceeed6982dacc98aa78c8066d048191047f10851a53793079cd400ca20593f707c06298fc5cd53916b3eceb331969566c4870c73c08ea578cdce63874c7844ea273e57ee59d5426773151bd15beec5ba81c8649c6943863cf09a1f17101454a788c8da1d5f8724255f372578beaa9950a6941fb609d1c53764ad6be9cc10d56cfac469820c848c600501364f2f8f91a18e3b05e6b3648b48654fbe769df272bfd305f0430cb402beb5bc1e03cfdf912da8906695db05d1f483c5f42ccfcc644306a55760d9ddd7d4be3d87be44ad9b757e76f90f00609608648016503040208042022d385729e28131f4f823d0886ee485101f56d2ebb4a86f4ba7546f15558ace6048201f8110abfadfbc1b9b9638793aa79d1b49f21d12c5bdca6a9013e678dfe4b4ce4b1b10648796c8a6c239f2b0c89ba526fd7f8eaf43fd8f7329b5035ef7edb9931c3fb48c7a32da89fa1c0988e8761d98337ed8d05f0792374691a2e5e7b5ec495a0da16e03c3a2b71940dce07158846efff4f46cbe1eb68726bcda95ffb17c265a6b47607dfbe8fb5c04c3c834aec245479d45c8fdae4afd77de7a997be20fb38867edf14b60644cdf37265a10cf16601fd04ca4c73a761fdb47a2f8c52862fdbc7bc65d95dfe4231a5a1035ec482fe42eb1ed8b1a330ea81bb5ba6b56d94348543fba447a20d09271ddbec90998bc987afe6c9459f6d349a463286cf54976f48c3871f95815a7531251c29a0dd26d0c21e3e8f86da6dddad4befff267326f21baa8832ccc3c25073e6a6f0d4d859722f5295acb4eff59a7cbed91cc3030287e6fdb65d15589634364dd37bcd79308688d5f79dbbec61a3bb90b7a8c344662b853bc31bfe62d7a48aed9571e3eb1ff437fb9369be534b70858a69ad2a38455d185a779f778ebe819f7308964745953cc73234f704e6ca12da10c3b33ca512744e363f352dde9d13fe5d760dea82137ffcd884dde50abc85f8cb10ea0af261b0deebf6e11e58fbe9c78709ff648664fb3bed72e9fd0b60484f32b786cb3210e606df7a48c2fef451334dd190d881c3324c14e1f5bba8308f3e763082013e060a2b0601040183e953010a8182012e3082012a0201003081ad060960864801650304012e040c6bfaab6c7cf3ec5ac42782830481910433bfe1a0be8dd0616956f6937aeabad5a06bf4debeb0e01060f35a009fb2b1498e9099c3586a84281248afe031f90819d7579d9b54cbf71bf90e1efde17c1eba6874a191fcfe1873bf45182261414bdb31f6966a56c79579fd9ef37d0649077ce8749e9880e7dc586378fee73221993281576aa9db74e5b2152b5073ed0a6e6f18fe61b7e1f0ea1eac5268fb90dbac08305f04308388b437df389d7fa866bbf1871b65f79f8be399d8b500f69ddfeb513963a26ccc6adcfb6ecc6bd4dfbc71884581d140060960864801650304020804202fdb75a19811a24c6b9ffbfd6fb8e601f67d5d517c7e601fcc3817f12620b90604142ecc0fb9ade666b30886e09f51c30897c2fa389c3082015d060a2b0601040183e95301088182014d308201490201003081ad060960864801650304012e040c5eadc5fbdf33c0aabcf2f6a60481910463c6d537bbacfcd2d031b0403d91421f2ba627d0cb86b66b51b50a35cd66ff571e53a58d9519bedee5a1bfa766ddb156dac80c96494a843c30f9563591361d66fa22c02a59a9d26b40cccd05238113238a9828fc67ccd11c56812d146e1521f3f7798610dd354945c002663015b2e8958dfedb1e29ff168fdb005380f9e827cc3bf54a7ca1a9f27bccdb56d339c75371305f04306070d9f4ebd84d97b02d6e01e79401c04412740901621d3c43bc0a3c6723f30299f7e2bcfab1b57c04cf1bcccf07ad1806096086480165030402080420e0e84faf993a28b1c2f9d4f08d47b3c825b5e19e8b20e073d9ea3dd17fbb9eaf0433c4f359b86892db7a01d00691181fc1c4ee40d431e19b3528ed3e546312b1823eca6f19bf436a1e71aa173fb4142d5da114c3bc30820149060a2b0601040183e953010481820139308201350201003081ad060960864801650304012e040caa5cbc5d3df2da044d25f0f804819104be20c5346be430c435455e61e6807bdef2c1c9b0801326deea602f40e7ffacfc2df98483eddd1510fc0bba996bce73c07f747b4c5696955a75a38c04a0930254ca097b9f05e31e041e12c725ece9a4e7c712fe033690269cdae8d62e14b831cc551bbfdbb078d12c7ebf9fbb022b4cf60c7d9549a53d6f38687a7d40dde422e3fd18e538dfaab79218e3189388546845305f0430d7487f86a8c83f15d36c5c7581db9aed1687e34876e0cea681b41180f3732581b6aed67589c523044ff2117e9a41c60e06096086480165030402080420ea02e7154352b7518d9704c8e5f3f611a9c983a209a3e350f9fed09e60af9b41041fec53d59d416bcb6933fa87104458ada2d075fcf92994bd3ca6fe1c746a0823300b060960864801650304030a0348003045022100a91ef8652999023aa5c13364e2adccaa242705f1aae1fa4c9b80b78df33dec65022008046996f5d0a5d4ef6f66635dc758048789032fe5b15728e54e509df2b7ba0530820174048201703082016c30820112a003020102020102300b060960864801650304030a302431223020060355040313194b65657461204e6574776f726b204b594320526f6f74204341301e170d3235313032383033313134305a170d3237313032383033313134305a3018311630140603550403130d466f6f747072696e74204b59433036301006072a8648ce3d020106052b8104000a032200024227c41bd2deb2a2e1cab9b3013f29c2930e71bbb954a57ab667091bb991e9dca3633061300f0603551d130101ff040530030101ff300e0603551d0f0101ff0404030200c6301f0603551d230418301680143b0a9628b3b6408b6fc6b4e30c5314089f068dfa301d0603551d0e04160414afce467da1b3582966dddb962f9682564797d740300b060960864801650304030a03470030440220406b546206a914c030a959b665a3815d6268c88f9a164c5f194ea41c9064aebe02206271ee9a237fffbaf9de3e7676232250f35618f43e113a5a5cdc1fb9547d28c30440f158e88895ba97d82c10a3108294c1d5480dad8bb8a2962b8e447e75d92acc3062c64e1ad86642b9387f086ed938156a782f72cb369036ad74ca787da36f8aaa diff --git a/keetanetwork-block/tests/samples/RECEIVE.hex b/keetanetwork-block/tests/samples/RECEIVE.hex new file mode 100644 index 0000000..7f71c19 --- /dev/null +++ b/keetanetwork-block/tests/samples/RECEIVE.hex @@ -0,0 +1,5 @@ +// RECEIVE block from explorer +// Hash: BA917125248313E2817D11B09B1881A1E8E9DFE2E861A35C74E038B96AAEFBDA +// Source: https://explorer.keeta.com/block/BA917125248313E2817D11B09B1881A1E8E9DFE2E861A35C74E038B96AAEFBDA + +a18202053082020102025382181332303236303131363138323532332e3030375a02010004220002925bb66095636ab410ab4e1f4652163c45fa6ad8370881b1447bb0e7fdc3e37b05000420af60d78da8f071e8680aae85930b69ef2b0bd2ff6c3020806c2c1348de8215d530820157a05530530422000223241bfea9363b43a581fa2d383451c0692b39c6d419c15407d90035c44551f4020a010d30631fb52bb9530404210360342de0c8c8a1d38015bdd2a7f262e627ebd60c50b1aa1396615a922b4becf2a053305104210100000000000000000000000000000000000000000000000000000000000000000209056bc75e2d630fd8f0042103bbd871ca4d787885dfcff2bd9d15897dc71b657b9547f4f59eac52250ceb2f9ba0543052042200025f18c47c939ec74bc368aabcfae28ca2581e8b3975125a54d304b5782e52f42b02091dd0c885f9a0d80000042103bbd871ca4d787885dfcff2bd9d15897dc71b657b9547f4f59eac52250ceb2f9ba0533051042200025f18c47c939ec74bc368aabcfae28ca2581e8b3975125a54d304b5782e52f42b020800b1a2bc2ec5000004210360342de0c8c8a1d38015bdd2a7f262e627ebd60c50b1aa1396615a922b4becf20440a26a3550da8f8e8a1f0cff225ca9d4a626cbeae819a904e45b536faff407f24533afa00bc16cff1839154344654fc967bf0bada193bc4c268c72bbfa21234780 diff --git a/keetanetwork-block/tests/samples/SEND.hex b/keetanetwork-block/tests/samples/SEND.hex new file mode 100644 index 0000000..131fbad --- /dev/null +++ b/keetanetwork-block/tests/samples/SEND.hex @@ -0,0 +1,5 @@ +// SEND block from explorer +// Hash: 05248C7FA3585433D18099BDA283F12F675F8C434479C1F6A52BA98F98E6B79F +// Source: https://explorer.keeta.com/block/05248C7FA3585433D18099BDA283F12F675F8C434479C1F6A52BA98F98E6B79F + +a1820201308201fd02025382181332303236303131363231353135392e3137385a02010004220002925bb66095636ab410ab4e1f4652163c45fa6ad8370881b1447bb0e7fdc3e37b0500042043e4aa9ad6398fd1d42532b39ee2917faee20bf66b6934c6e12680c87f28c64c30820153a054305204220002afe7f6f95f62cff0bd728265069219ddb3f97647f028ab330bcae77c1448f1c402095dae70d23e7d87d659042103bbd871ca4d787885dfcff2bd9d15897dc71b657b9547f4f59eac52250ceb2f9ba0523050042103bbd871ca4d787885dfcff2bd9d15897dc71b657b9547f4f59eac52250ceb2f9b020804cfa5d7c4187972042103bbd871ca4d787885dfcff2bd9d15897dc71b657b9547f4f59eac52250ceb2f9ba0523050042200025f18c47c939ec74bc368aabcfae28ca2581e8b3975125a54d304b5782e52f42b0207426f8d094cc00004210360342de0c8c8a1d38015bdd2a7f262e627ebd60c50b1aa1396615a922b4becf2a0533051042200025f18c47c939ec74bc368aabcfae28ca2581e8b3975125a54d304b5782e52f42b020800b1a2bc2ec5000004210360342de0c8c8a1d38015bdd2a7f262e627ebd60c50b1aa1396615a922b4becf204408649d74f02569672dee5f8b7290773e8b084bf01893361863c2bf7a586f56ec64297f198407a9ca3e1545093d0c5a0a8a98463d17c5832ee1a18f1a704a66685 diff --git a/keetanetwork-block/tests/samples/SET_INFO.hex b/keetanetwork-block/tests/samples/SET_INFO.hex new file mode 100644 index 0000000..1b34c73 --- /dev/null +++ b/keetanetwork-block/tests/samples/SET_INFO.hex @@ -0,0 +1,5 @@ +// SET_INFO block from explorer +// Hash: AC1982A17272A36C4346B60587416799953CCB26277CD83443A98E8BE109FBE5 +// Source: https://explorer.keeta.com/block/AC1982A17272A36C4346B60587416799953CCB26277CD83443A98E8BE109FBE5 + +3082013e020100020253820500181332303236303131363231323530392e3039345a042200036e2c89c42b41859f1386f4384cde7ec515ec201c84702f49f491f9130754805a042103b1dd74a8e92843a6b74dbf7afbb0983fa57c5994d921b0b0079703560adb325e04203fe7a9f50684cfdeaece79285c8d1578f76b346eec9cac6f658a9908d2c4ebc53073a260305e0c075445535454574f0c0574657374320c4465794a6b5a574e7062574673637949364f5377695a47566a615731686246427359574e6c637949364f53776963336c74596d3973496a6f69564556545646525854794a393006020101020100a50f300d02080de0b6b3a76400000201000440b6d93c88db24a34387c58ec3d219e89124a75aab7e2b260be7ce1d14c7a0e4a458d7d56049726526d666a76dc6f1ab8fd69989ec11d4412db4fcd8d7de272eb3 diff --git a/keetanetwork-block/tests/samples/SET_REP.hex b/keetanetwork-block/tests/samples/SET_REP.hex new file mode 100644 index 0000000..06d921f --- /dev/null +++ b/keetanetwork-block/tests/samples/SET_REP.hex @@ -0,0 +1,5 @@ +// SET_REP block from explorer +// Hash: F16B80A3FD3DC60D390C7370D71C159A77484DE5A9C480D38407CC17EB095FE7 +// Source: https://explorer.keeta.com/block/F16B80A3FD3DC60D390C7370D71C159A77484DE5A9C480D38407CC17EB095FE7 + +3081d2020100020253820500181332303236303131363139343334372e3734305a04220002b6164ed249d17be9e805a342669b3888883b4f2b7e758612804dbfbe6653eb3d05000420614a2f8d130983c1811e8e88482f915e0455cdbb0f2c67e8eb5f13f10f2ffcb23028a126302404220003565af39d790ef8c12d48831ec5b3f78aa26b88cb200902d895017d7022e527d504407fb9b96926e341bd4ed778df4e836f9797bd6840451b1308649b90121e665d6e22cec3c0fed918652389ccfc9a0726333e31ad9de91ee94d0cb93dd0ff6510c8 diff --git a/keetanetwork-block/tests/samples/SWAP.hex b/keetanetwork-block/tests/samples/SWAP.hex new file mode 100644 index 0000000..92ceb59 --- /dev/null +++ b/keetanetwork-block/tests/samples/SWAP.hex @@ -0,0 +1,5 @@ +// SWAP block from explorer +// Hash: E741DFCABA783A290082696ACD911FFD9B5A27458176F27E412C1EF756E87B02 +// Source: https://explorer.keeta.com/block/E741DFCABA783A290082696ACD911FFD9B5A27458176F27E412C1EF756E87B02 + +30820100020100020253820500181332303236303131363231343935332e3835335a042200036f3e786c2aaf545ff1466c8dd947f589931a4b4311d7230557ef7364ee703b01050004208d2c6775251b5a690879b524a748ef959f1a64cf78e63c3652728a7392e4528d3056a054305204220003f10f895043d27a900233f75dd41814186d6b3ba9509f788f888c5b09f041a070020900affb28b8ab49880004210360342de0c8c8a1d38015bdd2a7f262e627ebd60c50b1aa1396615a922b4becf20440514e6dc434b7176cda808257ed7004c266605c37f093824a7f7016116e96b70e6927c514ca2a76ae6fc1067a3e7bdc2c509fc880c17c4f4e40462157eb0e0750 diff --git a/keetanetwork-block/tests/samples/TOKEN_ADMIN_SUPPLY.hex b/keetanetwork-block/tests/samples/TOKEN_ADMIN_SUPPLY.hex new file mode 100644 index 0000000..2e365ba --- /dev/null +++ b/keetanetwork-block/tests/samples/TOKEN_ADMIN_SUPPLY.hex @@ -0,0 +1,5 @@ +// TOKEN_ADMIN_SUPPLY block from explorer +// Hash: BCBCAA4BAD69472FED8A895CED6ED5EFCAB11902EC531961E2FC1B01613C02DA +// Source: https://explorer.keeta.com/block/BCBCAA4BAD69472FED8A895CED6ED5EFCAB11902EC531961E2FC1B01613C02DA + +a182022b3082022702025382181332303236303131363232333935352e3831335a02010004210360342de0c8c8a1d38015bdd2a7f262e627ebd60c50b1aa1396615a922b4becf2308191042107f7b2db86355f93f9dc5d27e94a7e74632ed9878581603103253c00788b074d26306c042200039f4cf0896341a71c016a39826a45f7dc1016cea1e1aa1d34295b6a1c3f7b4cb0042200035d779b71c36fa7120f73b4a3f70b5abf03db277ab6d489e2b66357eee3b97e3d042200025bdd5f4ff5195f24b76733b0dbe94b71176affaf88f4b5ac3ec51bcb2716cba0042028e270466961540fd09f1304d9c4b5881a7c2ff0e98c26271d5182570f15b79b3067a510300e020910d942ab903b064c00020100a05330510421041731817c8c8f3563a821c7ff771b60d2a13154e8f945bba5339b9846f45d88f6020910d942ab903b064c0004210360342de0c8c8a1d38015bdd2a7f262e627ebd60c50b1aa1396615a922b4becf23081c6044069b5f243e359f8c7153445cfc92c964a194e018587c6561631380deb6b84a6c70b0290babfc1a91888bd29d2147f64098774b9a1b393431b9bbfe53b4b63e982044099300795f999e89b2bc6cd499d925bd5073a2d84b4f85d228729317f1972b9db7b8d21b07c36852a0cd34a33d83cf6fa4577311a26e0cf60c9d10a0046af4380044059008a5072a4f266edfe2cbd2e6626806e4ffe04bd818d2873173c7f9fec50355be972447601515d609611228ce2d87f7cf8276a39ea5ecf218a45568e37e4b2 diff --git a/keetanetwork-block/tests/sdk_compat.rs b/keetanetwork-block/tests/sdk_compat.rs new file mode 100644 index 0000000..3169989 --- /dev/null +++ b/keetanetwork-block/tests/sdk_compat.rs @@ -0,0 +1,56 @@ +//! SDK Compatibility Tests +//! +//! These tests parse block DER bytes from .hex files and verify roundtrip encoding. +//! Test data is from mainnet blocks generated by @keetanetwork/keetanet-client JS SDK. + +use der::{Decode, Encode}; +use keetanetwork_block::KeetaBlock; +use std::fs; +use std::path::PathBuf; + +fn get_samples_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples") +} + +fn load_block_hex(name: &str) -> Vec { + let path = get_samples_dir().join(format!("{}.hex", name)); + let content = fs::read_to_string(&path).expect(&format!("Failed to read {}", path.display())); + + // Filter out comment lines and empty lines + let hex: String = content + .lines() + .filter(|line| !line.starts_with("//") && !line.trim().is_empty()) + .collect(); + + hex::decode(&hex).expect(&format!("Invalid hex in {}", path.display())) +} + +/// Block samples to test. +const SAMPLES: &[&str] = &[ + "SET_REP", + "SET_INFO", + "CREATE_IDENTIFIER", + "TOKEN_ADMIN_SUPPLY", + "SEND", + "SWAP", + "RECEIVE", + "MANAGE_CERTIFICATE", +]; + +#[test] +fn test_block_roundtrip_all_samples() { + for sample_name in SAMPLES { + let original_bytes = load_block_hex(sample_name); + + let block = KeetaBlock::from_der(&original_bytes).expect(&format!("Failed to parse {} block", sample_name)); + + assert!(!block.operations.is_empty(), "{} block should have operations", sample_name); + assert!(!block.signatures.is_empty(), "{} block should have signatures", sample_name); + + let encoded = block + .to_der() + .expect(&format!("Failed to encode {} block", sample_name)); + + assert_eq!(encoded, original_bytes, "Roundtrip encoding mismatch for {} block", sample_name); + } +} From 2bdcd258c363b4ba1a7ad8f218e4ccf50f8d0af1 Mon Sep 17 00:00:00 2001 From: mamonet Date: Sun, 18 Jan 2026 17:25:52 +0100 Subject: [PATCH 15/25] Fix clippy expect_fun_call warnings in SDK compat tests --- keetanetwork-block/tests/sdk_compat.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/keetanetwork-block/tests/sdk_compat.rs b/keetanetwork-block/tests/sdk_compat.rs index 3169989..9179b5d 100644 --- a/keetanetwork-block/tests/sdk_compat.rs +++ b/keetanetwork-block/tests/sdk_compat.rs @@ -14,7 +14,7 @@ fn get_samples_dir() -> PathBuf { fn load_block_hex(name: &str) -> Vec { let path = get_samples_dir().join(format!("{}.hex", name)); - let content = fs::read_to_string(&path).expect(&format!("Failed to read {}", path.display())); + let content = fs::read_to_string(&path).unwrap_or_else(|_| panic!("Failed to read {}", path.display())); // Filter out comment lines and empty lines let hex: String = content @@ -22,7 +22,7 @@ fn load_block_hex(name: &str) -> Vec { .filter(|line| !line.starts_with("//") && !line.trim().is_empty()) .collect(); - hex::decode(&hex).expect(&format!("Invalid hex in {}", path.display())) + hex::decode(&hex).unwrap_or_else(|_| panic!("Invalid hex in {}", path.display())) } /// Block samples to test. @@ -42,14 +42,15 @@ fn test_block_roundtrip_all_samples() { for sample_name in SAMPLES { let original_bytes = load_block_hex(sample_name); - let block = KeetaBlock::from_der(&original_bytes).expect(&format!("Failed to parse {} block", sample_name)); + let block = + KeetaBlock::from_der(&original_bytes).unwrap_or_else(|_| panic!("Failed to parse {} block", sample_name)); assert!(!block.operations.is_empty(), "{} block should have operations", sample_name); assert!(!block.signatures.is_empty(), "{} block should have signatures", sample_name); let encoded = block .to_der() - .expect(&format!("Failed to encode {} block", sample_name)); + .unwrap_or_else(|_| panic!("Failed to encode {} block", sample_name)); assert_eq!(encoded, original_bytes, "Roundtrip encoding mismatch for {} block", sample_name); } From d3e8baf679f3d8cf7cc2e34909ca184105e54c02 Mon Sep 17 00:00:00 2001 From: mamonet Date: Sun, 18 Jan 2026 23:16:21 +0100 Subject: [PATCH 16/25] Add multisig signer iteration support with SignersIter --- keetanetwork-block/src/types.rs | 219 +++++++++++++++++- .../samples/CREATE_IDENTIFIER_MULTISIG.hex | 5 + keetanetwork-block/tests/sdk_compat.rs | 7 +- 3 files changed, 217 insertions(+), 14 deletions(-) create mode 100644 keetanetwork-block/tests/samples/CREATE_IDENTIFIER_MULTISIG.hex diff --git a/keetanetwork-block/src/types.rs b/keetanetwork-block/src/types.rs index 74215c6..680d0c0 100644 --- a/keetanetwork-block/src/types.rs +++ b/keetanetwork-block/src/types.rs @@ -452,17 +452,156 @@ pub struct ModifyPermissionsOp<'a> { /// Multisig creation arguments /// -/// Note: The `signers` field contains raw DER-encoded bytes representing -/// a sequence of public keys. For `no_std`/`no_alloc` environments, manual -/// parsing is required. -#[derive(Debug, Clone, Sequence)] +/// The `signers` field contains raw DER-encoded bytes representing +/// SEQUENCE OF OCTET STRING. Use `iter_signers()` to iterate over +/// individual signer public keys without allocation. +#[derive(Debug, Clone)] pub struct MultisigArgs<'a> { - /// Signer public keys (raw DER-encoded bytes) - pub signers: Bytes<'a>, + /// Raw DER bytes of the signers SEQUENCE (tag 0x30 + length + content). + /// Contains SEQUENCE OF OCTET STRING where each OCTET STRING is a signer public key. + pub signers: &'a [u8], /// Required number of signatures pub quorum: u64, } +impl<'a> MultisigArgs<'a> { + /// Returns an iterator over signer public keys. + /// + /// Each item is a Result containing the raw bytes of one signer's + /// public key (the OCTET STRING content, without tag/length). + /// + /// # Example + /// ```ignore + /// for signer_result in multisig_args.iter_signers() { + /// let signer_pubkey = signer_result?; + /// // signer_pubkey is &[u8] containing the public key bytes + /// } + /// ``` + pub fn iter_signers(&self) -> SignersIter<'a> { + SignersIter::new(self.signers) + } + + /// Returns the number of signers. + /// + /// Returns an error if the signers data is malformed. + pub fn signer_count(&self) -> der::Result { + let mut count = 0; + for result in self.iter_signers() { + result?; + count += 1; + } + Ok(count) + } +} + +impl<'a> DecodeValue<'a> for MultisigArgs<'a> { + fn decode_value>(reader: &mut R, _header: Header) -> der::Result { + // Read signers as raw TLV bytes (SEQUENCE OF OCTET STRING) + let signers = read_tlv_bytes(reader)?; + let quorum: u64 = reader.decode()?; + Ok(MultisigArgs { signers, quorum }) + } +} + +impl EncodeValue for MultisigArgs<'_> { + fn value_len(&self) -> der::Result { + Length::try_from(self.signers.len())? + self.quorum.encoded_len()? + } + + fn encode_value(&self, writer: &mut impl Writer) -> der::Result<()> { + writer.write(self.signers)?; + self.quorum.encode(writer) + } +} + +impl<'a> Sequence<'a> for MultisigArgs<'a> {} + +/// Iterator that yields raw signer public key bytes (without DER tag/length). +#[derive(Debug, Clone)] +pub struct SignersIter<'a> { + remaining: &'a [u8], +} + +impl<'a> SignersIter<'a> { + /// Creates a new iterator from the raw signers SEQUENCE bytes. + fn new(signers_sequence: &'a [u8]) -> Self { + // Parse the SEQUENCE header to get to the content + if signers_sequence.is_empty() { + return SignersIter { remaining: &[] }; + } + + // Check for SEQUENCE tag (0x30) + if signers_sequence[0] != 0x30 { + return SignersIter { remaining: &[] }; + } + + if signers_sequence.len() < 2 { + return SignersIter { remaining: &[] }; + } + + // Parse length + let content_start = if signers_sequence[1] < 0x80 { + // Short form length + 2 + } else { + // Long form length + let num_len_bytes = (signers_sequence[1] & 0x7F) as usize; + if signers_sequence.len() < 2 + num_len_bytes { + return SignersIter { remaining: &[] }; + } + 2 + num_len_bytes + }; + + SignersIter { remaining: &signers_sequence[content_start..] } + } +} + +impl<'a> Iterator for SignersIter<'a> { + type Item = der::Result<&'a [u8]>; + + fn next(&mut self) -> Option { + if self.remaining.is_empty() { + return None; + } + + // Expect OCTET STRING tag (0x04) + if self.remaining[0] != 0x04 { + return Some(Err(Tag::OctetString.value_error())); + } + + if self.remaining.len() < 2 { + return Some(Err(Tag::OctetString.value_error())); + } + + // Parse length + let (content_start, content_len) = if self.remaining[1] < 0x80 { + // Short form length + (2usize, self.remaining[1] as usize) + } else { + // Long form length + let num_len_bytes = (self.remaining[1] & 0x7F) as usize; + if self.remaining.len() < 2 + num_len_bytes { + return Some(Err(Tag::OctetString.value_error())); + } + let mut len: usize = 0; + for i in 0..num_len_bytes { + len = (len << 8) | (self.remaining[2 + i] as usize); + } + (2 + num_len_bytes, len) + }; + + let total_len = content_start + content_len; + if self.remaining.len() < total_len { + return Some(Err(Tag::OctetString.value_error())); + } + + let content = &self.remaining[content_start..total_len]; + self.remaining = &self.remaining[total_len..]; + + Some(Ok(content)) + } +} + /// Swap creation arguments #[derive(Debug, Clone, Sequence)] pub struct SwapArgs<'a> { @@ -1285,27 +1424,83 @@ mod tests { #[test] fn create_identifier_multisig_roundtrip() { let identifier = test_token(); - let signers_raw = [0x30, 0x04, 0x04, 0x02, 0xAB, 0xCD]; // Mock signers sequence + // SEQUENCE OF OCTET STRING with 2 signers (each 3 bytes) + // SEQUENCE { OCTET STRING (AA BB CC), OCTET STRING (DD EE FF) } + let signers_raw = [ + 0x30, 0x0A, // SEQUENCE, length 10 + 0x04, 0x03, 0xAA, 0xBB, 0xCC, // OCTET STRING, length 3, content + 0x04, 0x03, 0xDD, 0xEE, 0xFF, // OCTET STRING, length 3, content + ]; let original = CreateIdentifierOp { identifier: Bytes::new(&identifier).unwrap(), - create_arguments: Some(CreateIdentifierArgs::Multisig(MultisigArgs { - signers: Bytes::new(&signers_raw).unwrap(), - quorum: 2, - })), + create_arguments: Some(CreateIdentifierArgs::Multisig(MultisigArgs { signers: &signers_raw, quorum: 2 })), }; let encoded = original.to_der().unwrap(); let decoded: CreateIdentifierOp = CreateIdentifierOp::from_der(&encoded).unwrap(); match decoded.create_arguments { Some(CreateIdentifierArgs::Multisig(args)) => { - assert_eq!(args.signers.as_bytes(), &signers_raw); + assert_eq!(args.signers, &signers_raw); assert_eq!(args.quorum, 2); + + // Test the iterator + let signers: Vec<&[u8]> = args.iter_signers().map(|r| r.unwrap()).collect(); + assert_eq!(signers.len(), 2); + assert_eq!(signers[0], &[0xAA, 0xBB, 0xCC]); + assert_eq!(signers[1], &[0xDD, 0xEE, 0xFF]); + + // Test signer_count + assert_eq!(args.signer_count().unwrap(), 2); } _ => panic!("Expected Multisig args"), } } + #[test] + fn multisig_args_iter_signers() { + // Test with realistic 33-byte public keys (Ed25519 prefix + 32 bytes) + let signer1 = test_address(); // 33 bytes + let signer2 = test_token(); // 33 bytes + + // Build SEQUENCE OF OCTET STRING manually + // Each signer: 04 21 <33 bytes> = 35 bytes + // Total content: 70 bytes + // SEQUENCE header: 30 46 (0x46 = 70) + let mut signers_raw = Vec::new(); + signers_raw.push(0x30); // SEQUENCE tag + signers_raw.push(0x46); // length 70 + signers_raw.push(0x04); // OCTET STRING tag + signers_raw.push(0x21); // length 33 + signers_raw.extend_from_slice(&signer1); + signers_raw.push(0x04); // OCTET STRING tag + signers_raw.push(0x21); // length 33 + signers_raw.extend_from_slice(&signer2); + + let args = MultisigArgs { signers: &signers_raw, quorum: 2 }; + + // Test iteration + let collected: Vec<&[u8]> = args.iter_signers().map(|r| r.unwrap()).collect(); + assert_eq!(collected.len(), 2); + assert_eq!(collected[0], &signer1[..]); + assert_eq!(collected[1], &signer2[..]); + + // Test signer_count + assert_eq!(args.signer_count().unwrap(), 2); + } + + #[test] + fn multisig_args_empty_signers() { + // Empty SEQUENCE + let signers_raw = [0x30, 0x00]; // SEQUENCE, length 0 + + let args = MultisigArgs { signers: &signers_raw, quorum: 0 }; + + let collected: Vec<&[u8]> = args.iter_signers().map(|r| r.unwrap()).collect(); + assert_eq!(collected.len(), 0); + assert_eq!(args.signer_count().unwrap(), 0); + } + #[test] fn create_identifier_swap_roundtrip() { let identifier = test_token(); diff --git a/keetanetwork-block/tests/samples/CREATE_IDENTIFIER_MULTISIG.hex b/keetanetwork-block/tests/samples/CREATE_IDENTIFIER_MULTISIG.hex new file mode 100644 index 0000000..87f6719 --- /dev/null +++ b/keetanetwork-block/tests/samples/CREATE_IDENTIFIER_MULTISIG.hex @@ -0,0 +1,5 @@ +// CREATE_IDENTIFIER with Multisig arguments (manually constructed) +// 3 signers, quorum of 2 +// Test data - includes correct MultisigArgs SEQUENCE wrapper + +3082014a020100020253820500181332303236303131383132303030302e3030305a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee05000420000000000000000000000000000000000000000000000000000000000000000030819fa4819c30819904220007dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddda7733071306c04220002aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa04220002bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb04220002cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc02010204405a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a diff --git a/keetanetwork-block/tests/sdk_compat.rs b/keetanetwork-block/tests/sdk_compat.rs index 9179b5d..8218213 100644 --- a/keetanetwork-block/tests/sdk_compat.rs +++ b/keetanetwork-block/tests/sdk_compat.rs @@ -30,6 +30,7 @@ const SAMPLES: &[&str] = &[ "SET_REP", "SET_INFO", "CREATE_IDENTIFIER", + "CREATE_IDENTIFIER_MULTISIG", "TOKEN_ADMIN_SUPPLY", "SEND", "SWAP", @@ -42,8 +43,10 @@ fn test_block_roundtrip_all_samples() { for sample_name in SAMPLES { let original_bytes = load_block_hex(sample_name); - let block = - KeetaBlock::from_der(&original_bytes).unwrap_or_else(|_| panic!("Failed to parse {} block", sample_name)); + let block = match KeetaBlock::from_der(&original_bytes) { + Ok(b) => b, + Err(e) => panic!("Failed to parse {} block: {:?}", sample_name, e), + }; assert!(!block.operations.is_empty(), "{} block should have operations", sample_name); assert!(!block.signatures.is_empty(), "{} block should have signatures", sample_name); From 0ca1272b9ed118de0cd07cb95217f2510acdfdb3 Mon Sep 17 00:00:00 2001 From: mamonet Date: Sun, 18 Jan 2026 23:29:08 +0100 Subject: [PATCH 17/25] Fix clippy vec_init_then_push warning in multisig test --- keetanetwork-block/src/types.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/keetanetwork-block/src/types.rs b/keetanetwork-block/src/types.rs index 680d0c0..524c796 100644 --- a/keetanetwork-block/src/types.rs +++ b/keetanetwork-block/src/types.rs @@ -641,8 +641,8 @@ pub struct CreateIdentifierOp<'a> { pub struct TokenAdminSupplyOp<'a> { /// Amount to modify pub amount: Int<'a>, - /// Method (add/subtract/set) - pub method: AdjustMethod, + /// Method (add/subtract only, set is not allowed) + pub method: AdjustMethodRelative, } /// [6] TOKEN_ADMIN_MODIFY_BALANCE operation - Modify account token balance @@ -1243,12 +1243,12 @@ mod tests { fn token_admin_supply_op_roundtrip() { let amount = [0x00, 0xFF, 0xFF]; // 65535 - let original = TokenAdminSupplyOp { amount: Int::new(&amount).unwrap(), method: AdjustMethod::Add }; + let original = TokenAdminSupplyOp { amount: Int::new(&amount).unwrap(), method: AdjustMethodRelative::Add }; let encoded = original.to_der().unwrap(); let decoded: TokenAdminSupplyOp = TokenAdminSupplyOp::from_der(&encoded).unwrap(); assert_eq!(decoded.amount.as_bytes(), &amount); - assert_eq!(decoded.method, AdjustMethod::Add); + assert_eq!(decoded.method, AdjustMethodRelative::Add); } #[test] @@ -1467,14 +1467,14 @@ mod tests { // Each signer: 04 21 <33 bytes> = 35 bytes // Total content: 70 bytes // SEQUENCE header: 30 46 (0x46 = 70) - let mut signers_raw = Vec::new(); - signers_raw.push(0x30); // SEQUENCE tag - signers_raw.push(0x46); // length 70 - signers_raw.push(0x04); // OCTET STRING tag - signers_raw.push(0x21); // length 33 + let mut signers_raw = vec![ + 0x30, // SEQUENCE tag + 0x46, // length 70 + 0x04, // OCTET STRING tag + 0x21, // length 33 + ]; signers_raw.extend_from_slice(&signer1); - signers_raw.push(0x04); // OCTET STRING tag - signers_raw.push(0x21); // length 33 + signers_raw.extend_from_slice(&[0x04, 0x21]); // OCTET STRING tag + length 33 signers_raw.extend_from_slice(&signer2); let args = MultisigArgs { signers: &signers_raw, quorum: 2 }; From c78763fba58eef98b1f481f19dbfd4991ebd33d9 Mon Sep 17 00:00:00 2001 From: mamonet Date: Mon, 19 Jan 2026 10:43:41 +0100 Subject: [PATCH 18/25] Embed test samples and add malformed input tests --- keetanetwork-block/Cargo.toml | 2 +- keetanetwork-block/src/block.rs | 2 - keetanetwork-block/src/types.rs | 4 +- keetanetwork-block/tests/samples.rs | 864 ++++++++++++++++++ .../tests/samples/CREATE_IDENTIFIER.hex | 5 - .../samples/CREATE_IDENTIFIER_MULTISIG.hex | 5 - .../tests/samples/MANAGE_CERTIFICATE.hex | 5 - keetanetwork-block/tests/samples/RECEIVE.hex | 5 - keetanetwork-block/tests/samples/SEND.hex | 5 - keetanetwork-block/tests/samples/SET_INFO.hex | 5 - keetanetwork-block/tests/samples/SET_REP.hex | 5 - keetanetwork-block/tests/samples/SWAP.hex | 5 - .../tests/samples/TOKEN_ADMIN_SUPPLY.hex | 5 - keetanetwork-block/tests/sdk_compat.rs | 128 ++- 14 files changed, 955 insertions(+), 90 deletions(-) create mode 100644 keetanetwork-block/tests/samples.rs delete mode 100644 keetanetwork-block/tests/samples/CREATE_IDENTIFIER.hex delete mode 100644 keetanetwork-block/tests/samples/CREATE_IDENTIFIER_MULTISIG.hex delete mode 100644 keetanetwork-block/tests/samples/MANAGE_CERTIFICATE.hex delete mode 100644 keetanetwork-block/tests/samples/RECEIVE.hex delete mode 100644 keetanetwork-block/tests/samples/SEND.hex delete mode 100644 keetanetwork-block/tests/samples/SET_INFO.hex delete mode 100644 keetanetwork-block/tests/samples/SET_REP.hex delete mode 100644 keetanetwork-block/tests/samples/SWAP.hex delete mode 100644 keetanetwork-block/tests/samples/TOKEN_ADMIN_SUPPLY.hex diff --git a/keetanetwork-block/Cargo.toml b/keetanetwork-block/Cargo.toml index 9609e8c..8dd170d 100644 --- a/keetanetwork-block/Cargo.toml +++ b/keetanetwork-block/Cargo.toml @@ -18,7 +18,7 @@ der = { version = "0.7.10", default-features = false, features = ["derive"] } [dev-dependencies] der = "0.7.10" -hex = "0.4" +hex-literal = "0.4" [lib] name = "keetanetwork_block" diff --git a/keetanetwork-block/src/block.rs b/keetanetwork-block/src/block.rs index 39d2d3a..0e15b11 100644 --- a/keetanetwork-block/src/block.rs +++ b/keetanetwork-block/src/block.rs @@ -96,7 +96,6 @@ fn decode_v1_block<'a, R: Reader<'a>>(reader: &mut R) -> der::Result>(reader: &mut R) -> der::Result for BlockPurpose { pub struct BlockHeader<'a> { /// Network ID pub network: u64, - /// Subnet ID (optional) + /// Subnet ID (optional, V1 only - ignored for V2 blocks) pub subnet: Option, - /// Idempotent key for deduplication (optional) - pub idempotent: Option<&'a [u8]>, /// Block timestamp as raw bytes pub date: &'a [u8], /// Block purpose (V2 only, defaults to Generic for V1) diff --git a/keetanetwork-block/tests/samples.rs b/keetanetwork-block/tests/samples.rs new file mode 100644 index 0000000..28330c2 --- /dev/null +++ b/keetanetwork-block/tests/samples.rs @@ -0,0 +1,864 @@ +//! Block test samples for SDK compatibility testing. +//! +//! These samples are DER-encoded blocks used to verify roundtrip encoding/decoding. + +use hex_literal::hex; + +// ============================================================================ +// From Explorer (Mainnet Blocks) +// ============================================================================ + +/// SetRep operation +/// Source: https://explorer.keeta.com/block/F16B80A3FD3DC60D390C7370D71C159A77484DE5A9C480D38407CC17EB095FE7 +pub const SET_REP: &[u8] = &hex!( + "3081d2020100020253820500181332303236303131363139343334372e373430" + "5a04220002b6164ed249d17be9e805a342669b3888883b4f2b7e758612804dbf" + "be6653eb3d05000420614a2f8d130983c1811e8e88482f915e0455cdbb0f2c67" + "e8eb5f13f10f2ffcb23028a126302404220003565af39d790ef8c12d48831ec5" + "b3f78aa26b88cb200902d895017d7022e527d504407fb9b96926e341bd4ed778" + "df4e836f9797bd6840451b1308649b90121e665d6e22cec3c0fed918652389cc" + "fc9a0726333e31ad9de91ee94d0cb93dd0ff6510c8" +); + +/// SetInfo operation +/// Source: https://explorer.keeta.com/block/AC1982A17272A36C4346B60587416799953CCB26277CD83443A98E8BE109FBE5 +pub const SET_INFO: &[u8] = &hex!( + "3082013e020100020253820500181332303236303131363231323530392e3039" + "345a042200036e2c89c42b41859f1386f4384cde7ec515ec201c84702f49f491" + "f9130754805a042103b1dd74a8e92843a6b74dbf7afbb0983fa57c5994d921b0" + "b0079703560adb325e04203fe7a9f50684cfdeaece79285c8d1578f76b346eec" + "9cac6f658a9908d2c4ebc53073a260305e0c075445535454574f0c0574657374" + "320c4465794a6b5a574e7062574673637949364f5377695a47566a6157316862" + "46427359574e6c637949364f53776963336c74596d3973496a6f695645565456" + "46525854794a393006020101020100a50f300d02080de0b6b3a7640000020100" + "0440b6d93c88db24a34387c58ec3d219e89124a75aab7e2b260be7ce1d14c7a0" + "e4a458d7d56049726526d666a76dc6f1ab8fd69989ec11d4412db4fcd8d7de27" + "2eb3" +); + +/// CreateIdentifier (token) operation +/// Source: https://explorer.keeta.com/block/3A11F804843FC448E14F86C576824A9C3D1938DB7B68859799904532F7DF5DD6 +pub const CREATE_IDENTIFIER: &[u8] = &hex!( + "3081d1020100020253820500181332303236303131363231323530372e343032" + "5a042200036e2c89c42b41859f1386f4384cde7ec515ec201c84702f49f491f9" + "130754805a05000420231ee41fb259e22daab123593683b9cfa9c8a3e014ec14" + "70cdda90cea002aec23027a4253023042103b1dd74a8e92843a6b74dbf7afbb0" + "983fa57c5994d921b0b0079703560adb325e0440ce1a7b115f10d2ff2f5aa6e1" + "36654b33bf1e8d82d810d4073aa2ced26d0277535df4c8e2ee5625846709783a" + "152d2781942581efdab3dbcff2a68e549d1c5ea2" +); + +/// TokenAdminSupply operation +/// Source: https://explorer.keeta.com/block/BCBCAA4BAD69472FED8A895CED6ED5EFCAB11902EC531961E2FC1B01613C02DA +pub const TOKEN_ADMIN_SUPPLY: &[u8] = &hex!( + "a182022b3082022702025382181332303236303131363232333935352e383133" + "5a02010004210360342de0c8c8a1d38015bdd2a7f262e627ebd60c50b1aa1396" + "615a922b4becf2308191042107f7b2db86355f93f9dc5d27e94a7e74632ed987" + "8581603103253c00788b074d26306c042200039f4cf0896341a71c016a39826a" + "45f7dc1016cea1e1aa1d34295b6a1c3f7b4cb0042200035d779b71c36fa7120f" + "73b4a3f70b5abf03db277ab6d489e2b66357eee3b97e3d042200025bdd5f4ff5" + "195f24b76733b0dbe94b71176affaf88f4b5ac3ec51bcb2716cba0042028e270" + "466961540fd09f1304d9c4b5881a7c2ff0e98c26271d5182570f15b79b3067a5" + "10300e020910d942ab903b064c00020100a05330510421041731817c8c8f3563" + "a821c7ff771b60d2a13154e8f945bba5339b9846f45d88f6020910d942ab903b" + "064c0004210360342de0c8c8a1d38015bdd2a7f262e627ebd60c50b1aa139661" + "5a922b4becf23081c6044069b5f243e359f8c7153445cfc92c964a194e018587" + "c6561631380deb6b84a6c70b0290babfc1a91888bd29d2147f64098774b9a1b3" + "93431b9bbfe53b4b63e982044099300795f999e89b2bc6cd499d925bd5073a2d" + "84b4f85d228729317f1972b9db7b8d21b07c36852a0cd34a33d83cf6fa457731" + "1a26e0cf60c9d10a0046af4380044059008a5072a4f266edfe2cbd2e6626806e" + "4ffe04bd818d2873173c7f9fec50355be972447601515d609611228ce2d87f7c" + "f8276a39ea5ecf218a45568e37e4b2" +); + +/// Send operation +/// Source: https://explorer.keeta.com/block/05248C7FA3585433D18099BDA283F12F675F8C434479C1F6A52BA98F98E6B79F +pub const SEND: &[u8] = &hex!( + "a1820201308201fd02025382181332303236303131363231353135392e313738" + "5a02010004220002925bb66095636ab410ab4e1f4652163c45fa6ad8370881b1" + "447bb0e7fdc3e37b0500042043e4aa9ad6398fd1d42532b39ee2917faee20bf6" + "6b6934c6e12680c87f28c64c30820153a054305204220002afe7f6f95f62cff0" + "bd728265069219ddb3f97647f028ab330bcae77c1448f1c402095dae70d23e7d" + "87d659042103bbd871ca4d787885dfcff2bd9d15897dc71b657b9547f4f59eac" + "52250ceb2f9ba0523050042103bbd871ca4d787885dfcff2bd9d15897dc71b65" + "7b9547f4f59eac52250ceb2f9b020804cfa5d7c4187972042103bbd871ca4d78" + "7885dfcff2bd9d15897dc71b657b9547f4f59eac52250ceb2f9ba05230500422" + "00025f18c47c939ec74bc368aabcfae28ca2581e8b3975125a54d304b5782e52" + "f42b0207426f8d094cc00004210360342de0c8c8a1d38015bdd2a7f262e627eb" + "d60c50b1aa1396615a922b4becf2a0533051042200025f18c47c939ec74bc368" + "aabcfae28ca2581e8b3975125a54d304b5782e52f42b020800b1a2bc2ec50000" + "04210360342de0c8c8a1d38015bdd2a7f262e627ebd60c50b1aa1396615a922b" + "4becf204408649d74f02569672dee5f8b7290773e8b084bf01893361863c2bf7" + "a586f56ec64297f198407a9ca3e1545093d0c5a0a8a98463d17c5832ee1a18f1" + "a704a66685" +); + +/// ManageCertificate (add) operation +/// Source: https://explorer.keeta.com/block/02F1D8D6A4091979142AB1AFC13F1FC95121C17B3EF854F13A0824A220292321 +pub const MANAGE_CERTIFICATE: &[u8] = &hex!( + "30821334020100020253820500181332303236303131363130323633322e3237" + "355a04220002311db074927ac49fa1c3930cda10ec1030f4466efa3b66819778" + "11c286bf46ae050004202260aaf4a832a1732aa1be40f2d2cf638988abd36e15" + "1ad6e1f9686a2ccd872c30821288a88212843082128002010004821101308210" + "fd308210a2a0030201020210418811a2c0fde40f0af0a8bc45c94a41300b0609" + "60864801650304030a3018311630140603550403130d466f6f747072696e7420" + "4b5943301e170d3236303131363130323633305a170d32373031313631303236" + "33305a3050314e304c060355040316456b656574615f6161626463686e716f73" + "6a68767265377568627a6764673263647762616d6875697a7870756f33677167" + "6c7871656f63713237756e6c7573797666376936613036301006072a8648ce3d" + "020106052b8104000a03220002311db074927ac49fa1c3930cda10ec1030f446" + "6efa3b6681977811c286bf46aea3820fb630820fb2300e0603551d0f0101ff04" + "04030200c0301f0603551d23041830168014afce467da1b3582966dddb962f96" + "82564797d740301d0603551d0e04160414f93a08d95023b16f5e1a8ffb8c44e4" + "d8c179273630820f5e060a2b0601040183e953000004820f4e30820f4a308201" + "46060a2b0601040183e953010081820136308201320201003081ad0609608648" + "01650304012e040c0ebab29e3a1927f1312740290481910431b4956c8f962386" + "867217216823e5265640d5b18ce4735cdeed629b8c5343c8e6b46becb99dc36a" + "65722f2196b63c94be40f6a573c2bf764f29ef856138630118c182be51496978" + "e441c64a36b160c6f3e9104def1c9ad03e6781f5c729442f79ae480498c57a50" + "f7437b14d315c68846ab4d07f153556794f392699dcce751fc6061b48ea0af99" + "a41a04b75cedd2d2305f043004970d09c45586c7c2760f79d2c958a9d3da574b" + "20e60e50247a0769d4fe43937b974549d7689a84073b24ead61e06ab06096086" + "4801650304020804206193fa61ebcb465cf0756314161d7754f99261329ac6a2" + "0bc93b0aee0d25ebf9041c40c47440ca4acd4e80b025ca91a05191839955a2d5" + "9cd885bc0214d030820142060b2b0601040183e953010001818201313082012d" + "0201003081ad060960864801650304012e040cbe61f8923c0df64f1410887f04" + "819104ede4d07ae133bb50df0390f5efe420ae8ff5858b634ce1f2ae288bfe42" + "133ae22cea5c3428eb4b15b30e90e4e228a4fe1172a66c04caf4b59cc2b79378" + "be799930cbd5b583a81163d416d5e1a8107a1a4d6324d95cf40d0fb3ceaa43f3" + "632129e8cf8195e27bedd254f9b604bce9d8f591a6e37244268189a03ec5e1fc" + "4dfdc74391ff26971744c1adf030136f2ea814305f04307f3eefdd2d105c987b" + "ca03711feae98051f67650f8ac4a4d40eeb1c6f387dcd65236abf48c080a2459" + "907b9ffcdb6cae06096086480165030402080420e1fded8f77454a468ade5ffb" + "3054d04e639be4d97dafe63c8efe770543319abb0417eace8fcafa00df227965" + "95883866578b79b6f5c1a7418e30820141060b2b0601040183e9530100028182" + "01303082012c0201003081ad060960864801650304012e040cb8b2f3bcb4ee94" + "de7d9d228904819104b0bf33fafe7c3d7a5c310cff648db129b4b1933694616e" + "6c79a611fdd05d81789ac443a17d0da034a0ae8d8460a9fa319e13b939e107d8" + "2c1ff012cdc4217fd8af920d79d41b3685e990884c6695b928d39346f71ecb73" + "bb3b0dd925a78337285702d25b9f067ebd9e5d7e708e19846dbd6d1490244a4c" + "22f97aff425ad14cc8291cd69804cde887fb0f4385a7eb1d93305f04300e5c7f" + "61dfc6bd9ce71353af13d987090a4961e118d4cf9e94c2537b5bb6864319b49c" + "67cbad88dc6b016405ea3bb7fb060960864801650304020804206d863c8d12f3" + "6d1e20263666243e3294e24cc4fbc22533a0d91dc29fd547db0604166359f4ca" + "be62562de8762a034f05a408804812a7fa1630820149060a2b0601040183e953" + "010181820139308201350201003081ad060960864801650304012e040cf1d29e" + "37f13f22e267026aad048191045ee124e44c10e7af8577a59f8e57ca4c81740c" + "47d35e5e3355440d1615430aa658d6e21a6fc43f8f99c3c93dc0382f9ee29508" + "ed14154a68af44a03a16f6ac87f43482c7106905b1a95e72d1229d7b261fadc9" + "c74c6f8f075380a7e646ef946b8bb4bb60a997dc744042891d8463203a2340d9" + "e3a40999853c1d2177406250ff61b5a1b01c6b8d558a3b71611a57b533305f04" + "306a23280c102b33ac5392b908c97c38af7710732e446fc1a0d5613f3639f358" + "53d11d1ddf4db4dcf9b93c9e585b4404a306096086480165030402080420327f" + "7660a4bd119c602ec4ee167c7fda69eb94803c3346b5e2ec511f9b75e3c5041f" + "5a96e94f5c8fff49c46b28f70c16782dd68b4b1d0b2af585b4a4e6e5dfdede30" + "82014e060a2b0601040183e95301038182013e3082013a0201003081ad060960" + "864801650304012e040c557535200b4d2f488721a40b0481910481453fcdbba2" + "46bf7d1f49d3f8874fd0978711b158613bebf269b6595842af240004182b3ed2" + "8b57b032af4f4151ab9db7e6cd34c0801f35a2221fbb5aff32c341b1699b6194" + "bb91874932c791b20160d608d150a53581d55fcc6896b1167f8f0e6cecdedf06" + "e689134446b856b10b7890b7b5482befc164d1ef69b2dbe9d708cc85c248f678" + "14789ca41586f84e819e305f04306d82713e91cd14a693f2fc556dcba0c7cb12" + "7e619701d422ce6895859a84968b83406e5cbda4a685b496cb54f36b82da0609" + "60864801650304020804201a91e4c748d5d7a0c46619bbe9f17fa2f46be57703" + "9a3bde5db4ce8f896063fe04247bd135edf8a413b6bfb7a3f813267380dfb260" + "2a2c7bbf89c4ba13d5b7ecf0827c18b16e308201b9060a2b0601040183e95301" + "02818201a9308201a50201003081ad060960864801650304012e040c33185b23" + "ccc5e222ad980f27048191044361899b6d12b9913b11d6bd4583a1595a1e21dc" + "c83dac8db1161e8c9555f2433868698feea20d21774c4e93bb39b7729f800a31" + "27f4d40c9f2e629d405641c4f45cd4f2d582e5a128a1f3f7a4042c43a14a1dc5" + "faefce43d02cca76968d972d5ecec6d78c4055f40697eb63c93a7350f2a19670" + "7f1cb6449fc5ed687155bcc5afa6914fbd88280381152e1d90157206305f0430" + "d4278aee91bf54f73675f063d0eef1d7be6d8d8cee3c9625c013bc54c15ba4d9" + "6e4cb921fadd62f6f2fefb2bb8525e7d06096086480165030402080420c88eaf" + "5b6eea26a73d450f67e06889aaf42a99ca8653b4cf360103e8b696e8b304818e" + "42b2dcd4527433534342fa6c3237405b98331d6117b147d974e0f213687b1450" + "6faa3a13f7f55b160e499c33babb74c58f5232a426560ebc695cca41eb7763f6" + "05762162c47de2de801cadabc1f2e8baca9736fe6e6f146bd912094510080ec5" + "00ecee3c034a1f71b21f52d6849e7f409a1d0012d1e6a2512ef5ad51e3d423d9" + "9dc544cf310dd445513f0558013330820325060b2b0601040183e953010b0281" + "820314308203100201003081ad060960864801650304012e040ceeed6982dacc" + "98aa78c8066d048191047f10851a53793079cd400ca20593f707c06298fc5cd5" + "3916b3eceb331969566c4870c73c08ea578cdce63874c7844ea273e57ee59d54" + "26773151bd15beec5ba81c8649c6943863cf09a1f17101454a788c8da1d5f872" + "4255f372578beaa9950a6941fb609d1c53764ad6be9cc10d56cfac469820c848" + "c600501364f2f8f91a18e3b05e6b3648b48654fbe769df272bfd305f0430cb40" + "2beb5bc1e03cfdf912da8906695db05d1f483c5f42ccfcc644306a55760d9ddd" + "7d4be3d87be44ad9b757e76f90f00609608648016503040208042022d385729e" + "28131f4f823d0886ee485101f56d2ebb4a86f4ba7546f15558ace6048201f811" + "0abfadfbc1b9b9638793aa79d1b49f21d12c5bdca6a9013e678dfe4b4ce4b1b1" + "0648796c8a6c239f2b0c89ba526fd7f8eaf43fd8f7329b5035ef7edb9931c3fb" + "48c7a32da89fa1c0988e8761d98337ed8d05f0792374691a2e5e7b5ec495a0da" + "16e03c3a2b71940dce07158846efff4f46cbe1eb68726bcda95ffb17c265a6b4" + "7607dfbe8fb5c04c3c834aec245479d45c8fdae4afd77de7a997be20fb38867e" + "df14b60644cdf37265a10cf16601fd04ca4c73a761fdb47a2f8c52862fdbc7bc" + "65d95dfe4231a5a1035ec482fe42eb1ed8b1a330ea81bb5ba6b56d94348543fb" + "a447a20d09271ddbec90998bc987afe6c9459f6d349a463286cf54976f48c387" + "1f95815a7531251c29a0dd26d0c21e3e8f86da6dddad4befff267326f21baa88" + "32ccc3c25073e6a6f0d4d859722f5295acb4eff59a7cbed91cc3030287e6fdb6" + "5d15589634364dd37bcd79308688d5f79dbbec61a3bb90b7a8c344662b853bc3" + "1bfe62d7a48aed9571e3eb1ff437fb9369be534b70858a69ad2a38455d185a77" + "9f778ebe819f7308964745953cc73234f704e6ca12da10c3b33ca512744e363f" + "352dde9d13fe5d760dea82137ffcd884dde50abc85f8cb10ea0af261b0deebf6" + "e11e58fbe9c78709ff648664fb3bed72e9fd0b60484f32b786cb3210e606df7a" + "48c2fef451334dd190d881c3324c14e1f5bba8308f3e763082013e060a2b0601" + "040183e953010a8182012e3082012a0201003081ad060960864801650304012e" + "040c6bfaab6c7cf3ec5ac42782830481910433bfe1a0be8dd0616956f6937aea" + "bad5a06bf4debeb0e01060f35a009fb2b1498e9099c3586a84281248afe031f9" + "0819d7579d9b54cbf71bf90e1efde17c1eba6874a191fcfe1873bf4518226141" + "4bdb31f6966a56c79579fd9ef37d0649077ce8749e9880e7dc586378fee73221" + "993281576aa9db74e5b2152b5073ed0a6e6f18fe61b7e1f0ea1eac5268fb90db" + "ac08305f04308388b437df389d7fa866bbf1871b65f79f8be399d8b500f69ddf" + "eb513963a26ccc6adcfb6ecc6bd4dfbc71884581d14006096086480165030402" + "0804202fdb75a19811a24c6b9ffbfd6fb8e601f67d5d517c7e601fcc3817f126" + "20b90604142ecc0fb9ade666b30886e09f51c30897c2fa389c3082015d060a2b" + "0601040183e95301088182014d308201490201003081ad060960864801650304" + "012e040c5eadc5fbdf33c0aabcf2f6a60481910463c6d537bbacfcd2d031b040" + "3d91421f2ba627d0cb86b66b51b50a35cd66ff571e53a58d9519bedee5a1bfa7" + "66ddb156dac80c96494a843c30f9563591361d66fa22c02a59a9d26b40cccd05" + "238113238a9828fc67ccd11c56812d146e1521f3f7798610dd354945c0026630" + "15b2e8958dfedb1e29ff168fdb005380f9e827cc3bf54a7ca1a9f27bccdb56d3" + "39c75371305f04306070d9f4ebd84d97b02d6e01e79401c04412740901621d3c" + "43bc0a3c6723f30299f7e2bcfab1b57c04cf1bcccf07ad180609608648016503" + "0402080420e0e84faf993a28b1c2f9d4f08d47b3c825b5e19e8b20e073d9ea3d" + "d17fbb9eaf0433c4f359b86892db7a01d00691181fc1c4ee40d431e19b3528ed" + "3e546312b1823eca6f19bf436a1e71aa173fb4142d5da114c3bc30820149060a" + "2b0601040183e953010481820139308201350201003081ad0609608648016503" + "04012e040caa5cbc5d3df2da044d25f0f804819104be20c5346be430c435455e" + "61e6807bdef2c1c9b0801326deea602f40e7ffacfc2df98483eddd1510fc0bba" + "996bce73c07f747b4c5696955a75a38c04a0930254ca097b9f05e31e041e12c7" + "25ece9a4e7c712fe033690269cdae8d62e14b831cc551bbfdbb078d12c7ebf9f" + "bb022b4cf60c7d9549a53d6f38687a7d40dde422e3fd18e538dfaab79218e318" + "9388546845305f0430d7487f86a8c83f15d36c5c7581db9aed1687e34876e0ce" + "a681b41180f3732581b6aed67589c523044ff2117e9a41c60e06096086480165" + "030402080420ea02e7154352b7518d9704c8e5f3f611a9c983a209a3e350f9fe" + "d09e60af9b41041fec53d59d416bcb6933fa87104458ada2d075fcf92994bd3c" + "a6fe1c746a0823300b060960864801650304030a0348003045022100a91ef865" + "2999023aa5c13364e2adccaa242705f1aae1fa4c9b80b78df33dec6502200804" + "6996f5d0a5d4ef6f66635dc758048789032fe5b15728e54e509df2b7ba053082" + "0174048201703082016c30820112a003020102020102300b0609608648016503" + "04030a302431223020060355040313194b65657461204e6574776f726b204b59" + "4320526f6f74204341301e170d3235313032383033313134305a170d32373130" + "32383033313134305a3018311630140603550403130d466f6f747072696e7420" + "4b59433036301006072a8648ce3d020106052b8104000a032200024227c41bd2" + "deb2a2e1cab9b3013f29c2930e71bbb954a57ab667091bb991e9dca363306130" + "0f0603551d130101ff040530030101ff300e0603551d0f0101ff0404030200c6" + "301f0603551d230418301680143b0a9628b3b6408b6fc6b4e30c5314089f068d" + "fa301d0603551d0e04160414afce467da1b3582966dddb962f9682564797d740" + "300b060960864801650304030a03470030440220406b546206a914c030a959b6" + "65a3815d6268c88f9a164c5f194ea41c9064aebe02206271ee9a237fffbaf9de" + "3e7676232250f35618f43e113a5a5cdc1fb9547d28c30440f158e88895ba97d8" + "2c10a3108294c1d5480dad8bb8a2962b8e447e75d92acc3062c64e1ad86642b9" + "387f086ed938156a782f72cb369036ad74ca787da36f8aaa" +); + +// ============================================================================ +// Manually Constructed (Basic Operations) +// ============================================================================ + +/// CreateIdentifier with Multisig arguments - 3 signers, quorum of 2 +pub const CREATE_IDENTIFIER_MULTISIG: &[u8] = &hex!( + "3082014a020100020253820500181332303236303131383132303030302e3030" + "305a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeee0500042000000000000000000000000000000000000000000000" + "0000000000000000000030819fa4819c30819904220007dddddddddddddddddd" + "dddddddddddddddddddddddddddddddddddddddddddddda7733071306c042200" + "02aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aa04220002bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + "bbbbbbbbbb04220002cccccccccccccccccccccccccccccccccccccccccccccc" + "cccccccccccccccccc02010204405a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// CreateIdentifier with SwapArgs - fee_token_rate=Null +pub const CREATE_SWAP: &[u8] = &hex!( + "3082012f020100020253820500181332303236303131383132303030302e3030" + "305a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeee0500042000000000000000000000000000000000000000000000" + "00000000000000000000308184a48181307f04220002aaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa8593057302704220002" + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + "020164302704220002cccccccccccccccccccccccccccccccccccccccccccccc" + "cccccccccccccccccc020132050002010a04405a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// Receive operation - basic (exact=true, no forward) +pub const RECEIVE: &[u8] = &hex!( + "3081fc020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee050004200000000000000000000000000000000000000000000000" + "0000000000000000003052a750304e02016404220002cccccccccccccccccccc" + "cccccccccccccccccccccccccccccccccccccccccccc04220002dddddddddddd" + "dddddddddddddddddddddddddddddddddddddddddddddddddddd0101ff04405a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// ModifyPermissions operation - method=Add, permissions={base:16, external:32} +pub const MODIFY_PERMISSIONS: &[u8] = &hex!( + "3081dd020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee050004200000000000000000000000000000000000000000000000" + "0000000000000000003033a331302f04220002aaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa02010030060201100201200440" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// TokenAdminModifyBalance operation - method=Add, amount=1000 +pub const TOKEN_ADMIN_MODIFY_BALANCE: &[u8] = &hex!( + "3081d9020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee050004200000000000000000000000000000000000000000000000" + "000000000000000000302fa62d302b04220002bbbbbbbbbbbbbbbbbbbbbbbbbb" + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb020203e802010004405a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// MatchSwap operation - fee=NULL +pub const MATCH_SWAP: &[u8] = &hex!( + "3082014d020100020253820500181332303236303131383132303030302e3030" + "305a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeee0500042000000000000000000000000000000000000000000000" + "000000000000000000003081a2a9819f30819c04220002aaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa04220002bbbbbbbbbb" + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb3027042200" + "02cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + "cc020164302704220002dddddddddddddddddddddddddddddddddddddddddddd" + "dddddddddddddddddddd020132050004405a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// CancelSwap operation - fee=NULL +pub const CANCEL_SWAP: &[u8] = &hex!( + "3081fd020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee050004200000000000000000000000000000000000000000000000" + "0000000000000000003053aa51304f04220002aaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa302704220002cccccccccccccc" + "cccccccccccccccccccccccccccccccccccccccccccccccccc02016405000440" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +// ============================================================================ +// Edge Cases (Optional Fields) +// ============================================================================ + +/// Send operation with external field +pub const SEND_WITH_EXTERNAL: &[u8] = &hex!( + "30820104020100020253820500181332303236303131383132303030302e3030" + "305a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeee0500042000000000000000000000000000000000000000000000" + "00000000000000000000305aa058305604220002aaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa02016404220002bbbbbbbbbb" + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0c09726566" + "2d313233343504405a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a" +); + +/// SetInfo operation with default_permission +pub const SET_INFO_WITH_PERMISSION: &[u8] = &hex!( + "3081da020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee050004200000000000000000000000000000000000000000000000" + "0000000000000000003030a22e302c0c0c54657374204163636f756e740c0e41" + "2074657374206163636f756e740c027b7d3008020200ff020200aa04405a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// Receive operation with forward field +pub const RECEIVE_WITH_FORWARD: &[u8] = &hex!( + "30820120020100020253820500181332303236303131383132303030302e3030" + "305a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeee0500042000000000000000000000000000000000000000000000" + "000000000000000000003076a774307202016404220002cccccccccccccccccc" + "cccccccccccccccccccccccccccccccccccccccccccccc04220002dddddddddd" + "dddddddddddddddddddddddddddddddddddddddddddddddddddddd0101ff0422" + "0002ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "ffff04405a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a" +); + +/// ModifyPermissions with permissions=Null (clear) +pub const MODIFY_PERMISSIONS_CLEAR: &[u8] = &hex!( + "3081d7020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee050004200000000000000000000000000000000000000000000000" + "000000000000000000302da32b302904220002aaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa020102050004405a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// ModifyPermissions with target field +pub const MODIFY_PERMISSIONS_WITH_TARGET: &[u8] = &hex!( + "30820101020100020253820500181332303236303131383132303030302e3030" + "305a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeee0500042000000000000000000000000000000000000000000000" + "000000000000000000003057a355305304220002aaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa020100300602011002012004" + "220002cccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + "cccccc04405a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a" +); + +/// MatchSwap with fee (FeeValueWithRecipient) +pub const MATCH_SWAP_WITH_FEE: &[u8] = &hex!( + "30820198020100020253820500181332303236303131383132303030302e3030" + "305a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeee0500042000000000000000000000000000000000000000000000" + "000000000000000000003081eda981ea3081e704220002aaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa04220002bbbbbbbbbb" + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb3027042200" + "02cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + "cc020164302704220002dddddddddddddddddddddddddddddddddddddddddddd" + "dddddddddddddddddddd020132304b04220002eeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee02010a04220002ffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffffffff04405a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// CancelSwap with fee (FeeValue) +pub const CANCEL_SWAP_WITH_FEE: &[u8] = &hex!( + "30820124020100020253820500181332303236303131383132303030302e3030" + "305a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeee0500042000000000000000000000000000000000000000000000" + "00000000000000000000307aaa78307604220002aaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa302704220002cccccccccccc" + "cccccccccccccccccccccccccccccccccccccccccccccccccccc020164302704" + "220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeee02010504405a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a" +); + +/// MatchSwap with fee.token=Null (use sell token as fee token) +pub const MATCH_SWAP_FEE_NULL_TOKEN: &[u8] = &hex!( + "30820176020100020253820500181332303236303131383132303030302e3030" + "305a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeee0500042000000000000000000000000000000000000000000000" + "000000000000000000003081cba981c83081c504220002aaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa04220002bbbbbbbbbb" + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb3027042200" + "02cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + "cc020164302704220002dddddddddddddddddddddddddddddddddddddddddddd" + "dddddddddddddddddddd0201323029050002010a04220002ffffffffffffffff" + "ffffffffffffffffffffffffffffffffffffffffffffffff04405a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// CancelSwap with fee.token=Null (use sell token as fee token) +pub const CANCEL_SWAP_FEE_NULL_TOKEN: &[u8] = &hex!( + "30820102020100020253820500181332303236303131383132303030302e3030" + "305a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeee0500042000000000000000000000000000000000000000000000" + "000000000000000000003058aa56305404220002aaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa302704220002cccccccccccc" + "cccccccccccccccccccccccccccccccccccccccccccccccccccc020164300505" + "0002010504405a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a" +); + +/// TokenAdminSupply with method=Subtract +pub const TOKEN_ADMIN_SUPPLY_SUBTRACT: &[u8] = &hex!( + "3081b5020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee050004200000000000000000000000000000000000000000000000" + "000000000000000000300ba5093007020201f402010104405a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// Receive with exact=false (no forward) +pub const RECEIVE_EXACT_FALSE: &[u8] = &hex!( + "3081fc020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee050004200000000000000000000000000000000000000000000000" + "0000000000000000003052a750304e02016404220002cccccccccccccccccccc" + "cccccccccccccccccccccccccccccccccccccccccccc04220002dddddddddddd" + "dddddddddddddddddddddddddddddddddddddddddddddddddddd01010004405a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// ManageCertificate with method=Subtract (remove by hash) +pub const MANAGE_CERTIFICATE_SUBTRACT: &[u8] = &hex!( + "3081d3020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee050004200000000000000000000000000000000000000000000000" + "0000000000000000003029a82730250201010420abababababababababababab" + "abababababababababababababababababababab04405a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// ManageCertificate with intermediate_certificates=Some(Null) +pub const MANAGE_CERTIFICATE_NULL_INTERMEDIATE: &[u8] = &hex!( + "3081bb020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee050004200000000000000000000000000000000000000000000000" + "0000000000000000003011a80f300d0201003006020101020102050004405a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +// ============================================================================ +// Block-Level Edge Cases +// ============================================================================ + +/// Block with multiple operations (SET_REP + SEND) +pub const BLOCK_MULTI_OPS: &[u8] = &hex!( + "30820121020100020253820500181332303236303131383132303030302e3030" + "305a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeee0500042000000000000000000000000000000000000000000000" + "000000000000000000003077a126302404220002aaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa04d304b04220002bbbbbbbb" + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb02016404" + "220002cccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + "cccccc04405a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a" +); + +/// Block with explicit signer key instead of AccountIsSigner (NULL) +pub const BLOCK_WITH_SIGNER_KEY: &[u8] = &hex!( + "3081f4020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee04220002ffffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffff0420000000000000000000000000000000000000000000" + "00000000000000000000003028a126302404220002aaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa04405a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// V1 block with explicit subnet value (subnet=5 instead of NULL) +/// Tests V1 header parsing with non-null subnet field +pub const BLOCK_V1_WITH_SUBNET: &[u8] = &hex!( + "3081d302010002025382020105181332303236303131383132303030302e3030" + "305a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeee0500042000000000000000000000000000000000000000000000" + "000000000000000000003028a126302404220002aaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa04405a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +// ============================================================================ +// Additional Method Variants +// ============================================================================ + +/// ModifyPermissions with method=Subtract +pub const MODIFY_PERMISSIONS_SUBTRACT: &[u8] = &hex!( + "3081dd020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee050004200000000000000000000000000000000000000000000000" + "0000000000000000003033a331302f04220002aaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa02010130060201100201200440" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// ModifyPermissions with method=Set and permissions=Value (not NULL) +/// This tests the combination of Set method with actual permission values +pub const MODIFY_PERMISSIONS_SET: &[u8] = &hex!( + "3081dd020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee050004200000000000000000000000000000000000000000000000" + "0000000000000000003033a331302f04220002aaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa02010230060201100201200440" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// TokenAdminModifyBalance with method=Subtract +pub const TOKEN_ADMIN_MODIFY_BALANCE_SUBTRACT: &[u8] = &hex!( + "3081d9020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee050004200000000000000000000000000000000000000000000000" + "000000000000000000302fa62d302b04220002bbbbbbbbbbbbbbbbbbbbbbbbbb" + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb020203e802010104405a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// TokenAdminModifyBalance with method=Set +pub const TOKEN_ADMIN_MODIFY_BALANCE_SET: &[u8] = &hex!( + "3081d9020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee050004200000000000000000000000000000000000000000000000" + "000000000000000000302fa62d302b04220002bbbbbbbbbbbbbbbbbbbbbbbbbb" + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb020203e802010204405a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +// ============================================================================ +// Permission Value Edge Cases +// ============================================================================ + +/// ModifyPermissions with zero permissions (base=0, external=0) +/// Tests minimum valid permission values +pub const MODIFY_PERMISSIONS_ZERO: &[u8] = &hex!( + "3081dd020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee050004200000000000000000000000000000000000000000000000" + "0000000000000000003033a331302f04220002aaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa02010030060201000201000440" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// ModifyPermissions at DER integer encoding boundary (base=127, external=128) +/// 127 = 0x7F (single byte), 128 = 0x80 (requires leading zero in DER) +pub const MODIFY_PERMISSIONS_BOUNDARY: &[u8] = &hex!( + "3081de020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee050004200000000000000000000000000000000000000000000000" + "0000000000000000003034a332303004220002aaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa020100300702017f0202008004" + "405a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a" +); + +/// ModifyPermissions with large multi-byte values (base=256, external=65535) +/// Tests DER encoding of larger integers requiring multiple bytes +pub const MODIFY_PERMISSIONS_LARGE: &[u8] = &hex!( + "3081e0020100020253820500181332303236303131383132303030302e303030" + "5a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeee050004200000000000000000000000000000000000000000000000" + "0000000000000000003036a334303204220002aaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa020100300902020100020300ff" + "ff04405a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a" +); + +// ============================================================================ +// CreateIdentifier SwapArgs Fee Variants +// ============================================================================ + +/// CreateIdentifier with SwapArgs - fee_token_rate=Value(FeeRate{token:Value, rate:5}) +pub const CREATE_SWAP_WITH_FEE: &[u8] = &hex!( + "30820158020100020253820500181332303236303131383132303030302e3030" + "305a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeee0500042000000000000000000000000000000000000000000000" + "000000000000000000003081ada481aa3081a704220002aaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa88180307e30270422" + "0002bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + "bbbb020164302704220002cccccccccccccccccccccccccccccccccccccccccc" + "cccccccccccccccccccccc020132302704220002dddddddddddddddddddddddd" + "dddddddddddddddddddddddddddddddddddddddd02010502010a04405a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// CreateIdentifier with SwapArgs - fee_token_rate=Value(FeeRate{token:Null, rate:5}) +pub const CREATE_SWAP_FEE_NULL_TOKEN: &[u8] = &hex!( + "30820135020100020253820500181332303236303131383132303030302e3030" + "305a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeee0500042000000000000000000000000000000000000000000000" + "0000000000000000000030818aa4818730818404220002aaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa85e305c3027042200" + "02bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + "bb020164302704220002cccccccccccccccccccccccccccccccccccccccccccc" + "cccccccccccccccccccc0201323005050002010502010a04405a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +// ============================================================================ +// V2 Block Samples +// ============================================================================ + +/// V2 block with purpose=Generic, single signer +pub const BLOCK_V2_BASIC: &[u8] = &hex!( + "a181f53081f202025382181332303236303131383132303030302e3030305a02" + "010004220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeee04220002ffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffff04200000000000000000000000000000000000000000" + "0000000000000000000000003028a126302404220002aaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa04405a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// V2 block with purpose=Fee +pub const BLOCK_V2_PURPOSE_FEE: &[u8] = &hex!( + "a181f53081f202025382181332303236303131383132303030302e3030305a02" + "010104220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeee04220002ffffffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffff04200000000000000000000000000000000000000000" + "0000000000000000000000003028a126302404220002aaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa04405a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// V2 block with signer=NULL (AccountIsSigner) +pub const BLOCK_V2_ACCOUNT_IS_SIGNER: &[u8] = &hex!( + "a181d33081d002025382181332303236303131383132303030302e3030305a02" + "010004220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeee0500042000000000000000000000000000000000000000000000" + "000000000000000000003028a126302404220002aaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa04405a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" +); + +/// V2 block with multisig signer (2 signers, 2 signatures) +pub const BLOCK_V2_MULTISIG_SIGNER: &[u8] = &hex!( + "a18201873082018302025382181332303236303131383132303030302e303030" + "5a02010004220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeeeeeee306e04220002dddddddddddddddddddddddddddddddddddd" + "dddddddddddddddddddddddddddd304804220002111111111111111111111111" + "1111111111111111111111111111111111111111042200022222222222222222" + "2222222222222222222222222222222222222222222222220420000000000000" + "00000000000000000000000000000000000000000000000000003028a1263024" + "04220002aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaa30818404405a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a04406b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b" + "6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b" + "6b6b6b6b6b6b6b6b6b6b6b" +); + +/// V2 block with multiple signatures (single signer, 2 signatures) +pub const BLOCK_V2_MULTI_SIGS: &[u8] = &hex!( + "a182013b3082013702025382181332303236303131383132303030302e303030" + "5a02010004220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeeeeeee04220002ffffffffffffffffffffffffffffffffffffffff" + "ffffffffffffffffffffffff0420000000000000000000000000000000000000" + "00000000000000000000000000003028a126302404220002aaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa30818404405a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a04406b" + "6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b" + "6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b" +); + +/// V2 block with nested multisig signers (outer + inner multisig, 3 signatures) +pub const BLOCK_V2_NESTED_MULTISIG: &[u8] = &hex!( + "a18202173082021302025382181332303236303131383132303030302e303030" + "5a02010004220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeeeeeee3081bb04220002cccccccccccccccccccccccccccccccccc" + "cccccccccccccccccccccccccccccc308194306e04220002dddddddddddddddd" + "dddddddddddddddddddddddddddddddddddddddddddddddd3048042200021111" + "1111111111111111111111111111111111111111111111111111111111110422" + "0002222222222222222222222222222222222222222222222222222222222222" + "2222042200023333333333333333333333333333333333333333333333333333" + "3333333333330420000000000000000000000000000000000000000000000000" + "00000000000000003028a126302404220002aaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa3081c604405a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a04406b6b6b6b6b6b6b" + "6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b" + "6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b04407c7c7c7c7c" + "7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c" + "7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c" +); + +/// V2 block with 3-level deep nested multisig (L1 -> L2 -> L3 -> leaf signers) +/// Structure: L1(key=AA, signers=[L2, 44]), L2(key=BB, signers=[L3, 33]), L3(key=CC, signers=[11, 22]) +/// This tests deeply nested signer structures with 4 leaf signers requiring 3 signatures +/// (one path through the multisig tree: L1->L2->L3->leaf1, then L3->leaf2, then L2->leaf3). +pub const BLOCK_V2_DEEP_NESTED_MULTISIG: &[u8] = &hex!( + "a1820266308202620202538218133230323630313138313230303030" + "2e3030305a02010004220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee3082010904220002aaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "3081e23081bb04220002bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + "bbbbbbbbbbbbbbbbbbbbbbbbbbbb308194306e04220002cccccccccc" + "cccccccccccccccccccccccccccccccccccccccccccccccccccccc30" + "48042200021111111111111111111111111111111111111111111111" + "11111111111111111104220002222222222222222222222222222222" + "22222222222222222222222222222222220422000233333333333333" + "33333333333333333333333333333333333333333333333333042200" + "02444444444444444444444444444444444444444444444444444444" + "44444444440420000000000000000000000000000000000000000000" + "00000000000000000000003028a126302404220002dddddddddddddd" + "dddddddddddddddddddddddddddddddddddddddddddddddddd3081c6" + "04405a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a" + "5a5a5a5a5a5a5a5a5a5a04405b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b" + "5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b" + "5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b5b04405c5c5c5c5c5c" + "5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c" + "5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c" + "5c5c" +); + +// ============================================================================ +// All Samples Array +// ============================================================================ + +/// All samples for iteration in tests +pub const ALL_SAMPLES: &[(&str, &[u8])] = &[ + // From explorer (mainnet blocks) + ("SET_REP", SET_REP), + ("SET_INFO", SET_INFO), + ("CREATE_IDENTIFIER", CREATE_IDENTIFIER), + ("TOKEN_ADMIN_SUPPLY", TOKEN_ADMIN_SUPPLY), + ("SEND", SEND), + ("MANAGE_CERTIFICATE", MANAGE_CERTIFICATE), + // Manually constructed (basic operations) + ("CREATE_IDENTIFIER_MULTISIG", CREATE_IDENTIFIER_MULTISIG), + ("CREATE_SWAP", CREATE_SWAP), + ("RECEIVE", RECEIVE), + ("MODIFY_PERMISSIONS", MODIFY_PERMISSIONS), + ("TOKEN_ADMIN_MODIFY_BALANCE", TOKEN_ADMIN_MODIFY_BALANCE), + ("MATCH_SWAP", MATCH_SWAP), + ("CANCEL_SWAP", CANCEL_SWAP), + // Edge cases (optional fields) + ("SEND_WITH_EXTERNAL", SEND_WITH_EXTERNAL), + ("SET_INFO_WITH_PERMISSION", SET_INFO_WITH_PERMISSION), + ("RECEIVE_WITH_FORWARD", RECEIVE_WITH_FORWARD), + ("MODIFY_PERMISSIONS_CLEAR", MODIFY_PERMISSIONS_CLEAR), + ("MODIFY_PERMISSIONS_WITH_TARGET", MODIFY_PERMISSIONS_WITH_TARGET), + ("MATCH_SWAP_WITH_FEE", MATCH_SWAP_WITH_FEE), + ("CANCEL_SWAP_WITH_FEE", CANCEL_SWAP_WITH_FEE), + ("MATCH_SWAP_FEE_NULL_TOKEN", MATCH_SWAP_FEE_NULL_TOKEN), + ("CANCEL_SWAP_FEE_NULL_TOKEN", CANCEL_SWAP_FEE_NULL_TOKEN), + ("TOKEN_ADMIN_SUPPLY_SUBTRACT", TOKEN_ADMIN_SUPPLY_SUBTRACT), + ("RECEIVE_EXACT_FALSE", RECEIVE_EXACT_FALSE), + ("MANAGE_CERTIFICATE_SUBTRACT", MANAGE_CERTIFICATE_SUBTRACT), + ("MANAGE_CERTIFICATE_NULL_INTERMEDIATE", MANAGE_CERTIFICATE_NULL_INTERMEDIATE), + // Block-level edge cases + ("BLOCK_MULTI_OPS", BLOCK_MULTI_OPS), + ("BLOCK_WITH_SIGNER_KEY", BLOCK_WITH_SIGNER_KEY), + ("BLOCK_V1_WITH_SUBNET", BLOCK_V1_WITH_SUBNET), + // Additional method variants + ("MODIFY_PERMISSIONS_SUBTRACT", MODIFY_PERMISSIONS_SUBTRACT), + ("MODIFY_PERMISSIONS_SET", MODIFY_PERMISSIONS_SET), + ("TOKEN_ADMIN_MODIFY_BALANCE_SUBTRACT", TOKEN_ADMIN_MODIFY_BALANCE_SUBTRACT), + ("TOKEN_ADMIN_MODIFY_BALANCE_SET", TOKEN_ADMIN_MODIFY_BALANCE_SET), + // Permission value edge cases + ("MODIFY_PERMISSIONS_ZERO", MODIFY_PERMISSIONS_ZERO), + ("MODIFY_PERMISSIONS_BOUNDARY", MODIFY_PERMISSIONS_BOUNDARY), + ("MODIFY_PERMISSIONS_LARGE", MODIFY_PERMISSIONS_LARGE), + // CreateIdentifier SwapArgs fee variants + ("CREATE_SWAP_WITH_FEE", CREATE_SWAP_WITH_FEE), + ("CREATE_SWAP_FEE_NULL_TOKEN", CREATE_SWAP_FEE_NULL_TOKEN), + // V2 block samples + ("BLOCK_V2_BASIC", BLOCK_V2_BASIC), + ("BLOCK_V2_PURPOSE_FEE", BLOCK_V2_PURPOSE_FEE), + ("BLOCK_V2_ACCOUNT_IS_SIGNER", BLOCK_V2_ACCOUNT_IS_SIGNER), + ("BLOCK_V2_MULTISIG_SIGNER", BLOCK_V2_MULTISIG_SIGNER), + ("BLOCK_V2_MULTI_SIGS", BLOCK_V2_MULTI_SIGS), + ("BLOCK_V2_NESTED_MULTISIG", BLOCK_V2_NESTED_MULTISIG), + ("BLOCK_V2_DEEP_NESTED_MULTISIG", BLOCK_V2_DEEP_NESTED_MULTISIG), +]; diff --git a/keetanetwork-block/tests/samples/CREATE_IDENTIFIER.hex b/keetanetwork-block/tests/samples/CREATE_IDENTIFIER.hex deleted file mode 100644 index f2ae5ac..0000000 --- a/keetanetwork-block/tests/samples/CREATE_IDENTIFIER.hex +++ /dev/null @@ -1,5 +0,0 @@ -// CREATE_IDENTIFIER block from explorer -// Hash: 3A11F804843FC448E14F86C576824A9C3D1938DB7B68859799904532F7DF5DD6 -// Source: https://explorer.keeta.com/block/3A11F804843FC448E14F86C576824A9C3D1938DB7B68859799904532F7DF5DD6 - -3081d1020100020253820500181332303236303131363231323530372e3430325a042200036e2c89c42b41859f1386f4384cde7ec515ec201c84702f49f491f9130754805a05000420231ee41fb259e22daab123593683b9cfa9c8a3e014ec1470cdda90cea002aec23027a4253023042103b1dd74a8e92843a6b74dbf7afbb0983fa57c5994d921b0b0079703560adb325e0440ce1a7b115f10d2ff2f5aa6e136654b33bf1e8d82d810d4073aa2ced26d0277535df4c8e2ee5625846709783a152d2781942581efdab3dbcff2a68e549d1c5ea2 diff --git a/keetanetwork-block/tests/samples/CREATE_IDENTIFIER_MULTISIG.hex b/keetanetwork-block/tests/samples/CREATE_IDENTIFIER_MULTISIG.hex deleted file mode 100644 index 87f6719..0000000 --- a/keetanetwork-block/tests/samples/CREATE_IDENTIFIER_MULTISIG.hex +++ /dev/null @@ -1,5 +0,0 @@ -// CREATE_IDENTIFIER with Multisig arguments (manually constructed) -// 3 signers, quorum of 2 -// Test data - includes correct MultisigArgs SEQUENCE wrapper - -3082014a020100020253820500181332303236303131383132303030302e3030305a04220002eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee05000420000000000000000000000000000000000000000000000000000000000000000030819fa4819c30819904220007dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddda7733071306c04220002aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa04220002bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb04220002cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc02010204405a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a diff --git a/keetanetwork-block/tests/samples/MANAGE_CERTIFICATE.hex b/keetanetwork-block/tests/samples/MANAGE_CERTIFICATE.hex deleted file mode 100644 index 03c73cf..0000000 --- a/keetanetwork-block/tests/samples/MANAGE_CERTIFICATE.hex +++ /dev/null @@ -1,5 +0,0 @@ -// MANAGE_CERTIFICATE block from explorer -// Hash: 02F1D8D6A4091979142AB1AFC13F1FC95121C17B3EF854F13A0824A220292321 -// Source: https://explorer.keeta.com/block/02F1D8D6A4091979142AB1AFC13F1FC95121C17B3EF854F13A0824A220292321 - -30821334020100020253820500181332303236303131363130323633322e3237355a04220002311db074927ac49fa1c3930cda10ec1030f4466efa3b6681977811c286bf46ae050004202260aaf4a832a1732aa1be40f2d2cf638988abd36e151ad6e1f9686a2ccd872c30821288a88212843082128002010004821101308210fd308210a2a0030201020210418811a2c0fde40f0af0a8bc45c94a41300b060960864801650304030a3018311630140603550403130d466f6f747072696e74204b5943301e170d3236303131363130323633305a170d3237303131363130323633305a3050314e304c060355040316456b656574615f6161626463686e716f736a68767265377568627a6764673263647762616d6875697a7870756f336771676c7871656f63713237756e6c7573797666376936613036301006072a8648ce3d020106052b8104000a03220002311db074927ac49fa1c3930cda10ec1030f4466efa3b6681977811c286bf46aea3820fb630820fb2300e0603551d0f0101ff0404030200c0301f0603551d23041830168014afce467da1b3582966dddb962f9682564797d740301d0603551d0e04160414f93a08d95023b16f5e1a8ffb8c44e4d8c179273630820f5e060a2b0601040183e953000004820f4e30820f4a30820146060a2b0601040183e953010081820136308201320201003081ad060960864801650304012e040c0ebab29e3a1927f1312740290481910431b4956c8f962386867217216823e5265640d5b18ce4735cdeed629b8c5343c8e6b46becb99dc36a65722f2196b63c94be40f6a573c2bf764f29ef856138630118c182be51496978e441c64a36b160c6f3e9104def1c9ad03e6781f5c729442f79ae480498c57a50f7437b14d315c68846ab4d07f153556794f392699dcce751fc6061b48ea0af99a41a04b75cedd2d2305f043004970d09c45586c7c2760f79d2c958a9d3da574b20e60e50247a0769d4fe43937b974549d7689a84073b24ead61e06ab060960864801650304020804206193fa61ebcb465cf0756314161d7754f99261329ac6a20bc93b0aee0d25ebf9041c40c47440ca4acd4e80b025ca91a05191839955a2d59cd885bc0214d030820142060b2b0601040183e953010001818201313082012d0201003081ad060960864801650304012e040cbe61f8923c0df64f1410887f04819104ede4d07ae133bb50df0390f5efe420ae8ff5858b634ce1f2ae288bfe42133ae22cea5c3428eb4b15b30e90e4e228a4fe1172a66c04caf4b59cc2b79378be799930cbd5b583a81163d416d5e1a8107a1a4d6324d95cf40d0fb3ceaa43f3632129e8cf8195e27bedd254f9b604bce9d8f591a6e37244268189a03ec5e1fc4dfdc74391ff26971744c1adf030136f2ea814305f04307f3eefdd2d105c987bca03711feae98051f67650f8ac4a4d40eeb1c6f387dcd65236abf48c080a2459907b9ffcdb6cae06096086480165030402080420e1fded8f77454a468ade5ffb3054d04e639be4d97dafe63c8efe770543319abb0417eace8fcafa00df22796595883866578b79b6f5c1a7418e30820141060b2b0601040183e953010002818201303082012c0201003081ad060960864801650304012e040cb8b2f3bcb4ee94de7d9d228904819104b0bf33fafe7c3d7a5c310cff648db129b4b1933694616e6c79a611fdd05d81789ac443a17d0da034a0ae8d8460a9fa319e13b939e107d82c1ff012cdc4217fd8af920d79d41b3685e990884c6695b928d39346f71ecb73bb3b0dd925a78337285702d25b9f067ebd9e5d7e708e19846dbd6d1490244a4c22f97aff425ad14cc8291cd69804cde887fb0f4385a7eb1d93305f04300e5c7f61dfc6bd9ce71353af13d987090a4961e118d4cf9e94c2537b5bb6864319b49c67cbad88dc6b016405ea3bb7fb060960864801650304020804206d863c8d12f36d1e20263666243e3294e24cc4fbc22533a0d91dc29fd547db0604166359f4cabe62562de8762a034f05a408804812a7fa1630820149060a2b0601040183e953010181820139308201350201003081ad060960864801650304012e040cf1d29e37f13f22e267026aad048191045ee124e44c10e7af8577a59f8e57ca4c81740c47d35e5e3355440d1615430aa658d6e21a6fc43f8f99c3c93dc0382f9ee29508ed14154a68af44a03a16f6ac87f43482c7106905b1a95e72d1229d7b261fadc9c74c6f8f075380a7e646ef946b8bb4bb60a997dc744042891d8463203a2340d9e3a40999853c1d2177406250ff61b5a1b01c6b8d558a3b71611a57b533305f04306a23280c102b33ac5392b908c97c38af7710732e446fc1a0d5613f3639f35853d11d1ddf4db4dcf9b93c9e585b4404a306096086480165030402080420327f7660a4bd119c602ec4ee167c7fda69eb94803c3346b5e2ec511f9b75e3c5041f5a96e94f5c8fff49c46b28f70c16782dd68b4b1d0b2af585b4a4e6e5dfdede3082014e060a2b0601040183e95301038182013e3082013a0201003081ad060960864801650304012e040c557535200b4d2f488721a40b0481910481453fcdbba246bf7d1f49d3f8874fd0978711b158613bebf269b6595842af240004182b3ed28b57b032af4f4151ab9db7e6cd34c0801f35a2221fbb5aff32c341b1699b6194bb91874932c791b20160d608d150a53581d55fcc6896b1167f8f0e6cecdedf06e689134446b856b10b7890b7b5482befc164d1ef69b2dbe9d708cc85c248f67814789ca41586f84e819e305f04306d82713e91cd14a693f2fc556dcba0c7cb127e619701d422ce6895859a84968b83406e5cbda4a685b496cb54f36b82da060960864801650304020804201a91e4c748d5d7a0c46619bbe9f17fa2f46be577039a3bde5db4ce8f896063fe04247bd135edf8a413b6bfb7a3f813267380dfb2602a2c7bbf89c4ba13d5b7ecf0827c18b16e308201b9060a2b0601040183e9530102818201a9308201a50201003081ad060960864801650304012e040c33185b23ccc5e222ad980f27048191044361899b6d12b9913b11d6bd4583a1595a1e21dcc83dac8db1161e8c9555f2433868698feea20d21774c4e93bb39b7729f800a3127f4d40c9f2e629d405641c4f45cd4f2d582e5a128a1f3f7a4042c43a14a1dc5faefce43d02cca76968d972d5ecec6d78c4055f40697eb63c93a7350f2a196707f1cb6449fc5ed687155bcc5afa6914fbd88280381152e1d90157206305f0430d4278aee91bf54f73675f063d0eef1d7be6d8d8cee3c9625c013bc54c15ba4d96e4cb921fadd62f6f2fefb2bb8525e7d06096086480165030402080420c88eaf5b6eea26a73d450f67e06889aaf42a99ca8653b4cf360103e8b696e8b304818e42b2dcd4527433534342fa6c3237405b98331d6117b147d974e0f213687b14506faa3a13f7f55b160e499c33babb74c58f5232a426560ebc695cca41eb7763f605762162c47de2de801cadabc1f2e8baca9736fe6e6f146bd912094510080ec500ecee3c034a1f71b21f52d6849e7f409a1d0012d1e6a2512ef5ad51e3d423d99dc544cf310dd445513f0558013330820325060b2b0601040183e953010b0281820314308203100201003081ad060960864801650304012e040ceeed6982dacc98aa78c8066d048191047f10851a53793079cd400ca20593f707c06298fc5cd53916b3eceb331969566c4870c73c08ea578cdce63874c7844ea273e57ee59d5426773151bd15beec5ba81c8649c6943863cf09a1f17101454a788c8da1d5f8724255f372578beaa9950a6941fb609d1c53764ad6be9cc10d56cfac469820c848c600501364f2f8f91a18e3b05e6b3648b48654fbe769df272bfd305f0430cb402beb5bc1e03cfdf912da8906695db05d1f483c5f42ccfcc644306a55760d9ddd7d4be3d87be44ad9b757e76f90f00609608648016503040208042022d385729e28131f4f823d0886ee485101f56d2ebb4a86f4ba7546f15558ace6048201f8110abfadfbc1b9b9638793aa79d1b49f21d12c5bdca6a9013e678dfe4b4ce4b1b10648796c8a6c239f2b0c89ba526fd7f8eaf43fd8f7329b5035ef7edb9931c3fb48c7a32da89fa1c0988e8761d98337ed8d05f0792374691a2e5e7b5ec495a0da16e03c3a2b71940dce07158846efff4f46cbe1eb68726bcda95ffb17c265a6b47607dfbe8fb5c04c3c834aec245479d45c8fdae4afd77de7a997be20fb38867edf14b60644cdf37265a10cf16601fd04ca4c73a761fdb47a2f8c52862fdbc7bc65d95dfe4231a5a1035ec482fe42eb1ed8b1a330ea81bb5ba6b56d94348543fba447a20d09271ddbec90998bc987afe6c9459f6d349a463286cf54976f48c3871f95815a7531251c29a0dd26d0c21e3e8f86da6dddad4befff267326f21baa8832ccc3c25073e6a6f0d4d859722f5295acb4eff59a7cbed91cc3030287e6fdb65d15589634364dd37bcd79308688d5f79dbbec61a3bb90b7a8c344662b853bc31bfe62d7a48aed9571e3eb1ff437fb9369be534b70858a69ad2a38455d185a779f778ebe819f7308964745953cc73234f704e6ca12da10c3b33ca512744e363f352dde9d13fe5d760dea82137ffcd884dde50abc85f8cb10ea0af261b0deebf6e11e58fbe9c78709ff648664fb3bed72e9fd0b60484f32b786cb3210e606df7a48c2fef451334dd190d881c3324c14e1f5bba8308f3e763082013e060a2b0601040183e953010a8182012e3082012a0201003081ad060960864801650304012e040c6bfaab6c7cf3ec5ac42782830481910433bfe1a0be8dd0616956f6937aeabad5a06bf4debeb0e01060f35a009fb2b1498e9099c3586a84281248afe031f90819d7579d9b54cbf71bf90e1efde17c1eba6874a191fcfe1873bf45182261414bdb31f6966a56c79579fd9ef37d0649077ce8749e9880e7dc586378fee73221993281576aa9db74e5b2152b5073ed0a6e6f18fe61b7e1f0ea1eac5268fb90dbac08305f04308388b437df389d7fa866bbf1871b65f79f8be399d8b500f69ddfeb513963a26ccc6adcfb6ecc6bd4dfbc71884581d140060960864801650304020804202fdb75a19811a24c6b9ffbfd6fb8e601f67d5d517c7e601fcc3817f12620b90604142ecc0fb9ade666b30886e09f51c30897c2fa389c3082015d060a2b0601040183e95301088182014d308201490201003081ad060960864801650304012e040c5eadc5fbdf33c0aabcf2f6a60481910463c6d537bbacfcd2d031b0403d91421f2ba627d0cb86b66b51b50a35cd66ff571e53a58d9519bedee5a1bfa766ddb156dac80c96494a843c30f9563591361d66fa22c02a59a9d26b40cccd05238113238a9828fc67ccd11c56812d146e1521f3f7798610dd354945c002663015b2e8958dfedb1e29ff168fdb005380f9e827cc3bf54a7ca1a9f27bccdb56d339c75371305f04306070d9f4ebd84d97b02d6e01e79401c04412740901621d3c43bc0a3c6723f30299f7e2bcfab1b57c04cf1bcccf07ad1806096086480165030402080420e0e84faf993a28b1c2f9d4f08d47b3c825b5e19e8b20e073d9ea3dd17fbb9eaf0433c4f359b86892db7a01d00691181fc1c4ee40d431e19b3528ed3e546312b1823eca6f19bf436a1e71aa173fb4142d5da114c3bc30820149060a2b0601040183e953010481820139308201350201003081ad060960864801650304012e040caa5cbc5d3df2da044d25f0f804819104be20c5346be430c435455e61e6807bdef2c1c9b0801326deea602f40e7ffacfc2df98483eddd1510fc0bba996bce73c07f747b4c5696955a75a38c04a0930254ca097b9f05e31e041e12c725ece9a4e7c712fe033690269cdae8d62e14b831cc551bbfdbb078d12c7ebf9fbb022b4cf60c7d9549a53d6f38687a7d40dde422e3fd18e538dfaab79218e3189388546845305f0430d7487f86a8c83f15d36c5c7581db9aed1687e34876e0cea681b41180f3732581b6aed67589c523044ff2117e9a41c60e06096086480165030402080420ea02e7154352b7518d9704c8e5f3f611a9c983a209a3e350f9fed09e60af9b41041fec53d59d416bcb6933fa87104458ada2d075fcf92994bd3ca6fe1c746a0823300b060960864801650304030a0348003045022100a91ef8652999023aa5c13364e2adccaa242705f1aae1fa4c9b80b78df33dec65022008046996f5d0a5d4ef6f66635dc758048789032fe5b15728e54e509df2b7ba0530820174048201703082016c30820112a003020102020102300b060960864801650304030a302431223020060355040313194b65657461204e6574776f726b204b594320526f6f74204341301e170d3235313032383033313134305a170d3237313032383033313134305a3018311630140603550403130d466f6f747072696e74204b59433036301006072a8648ce3d020106052b8104000a032200024227c41bd2deb2a2e1cab9b3013f29c2930e71bbb954a57ab667091bb991e9dca3633061300f0603551d130101ff040530030101ff300e0603551d0f0101ff0404030200c6301f0603551d230418301680143b0a9628b3b6408b6fc6b4e30c5314089f068dfa301d0603551d0e04160414afce467da1b3582966dddb962f9682564797d740300b060960864801650304030a03470030440220406b546206a914c030a959b665a3815d6268c88f9a164c5f194ea41c9064aebe02206271ee9a237fffbaf9de3e7676232250f35618f43e113a5a5cdc1fb9547d28c30440f158e88895ba97d82c10a3108294c1d5480dad8bb8a2962b8e447e75d92acc3062c64e1ad86642b9387f086ed938156a782f72cb369036ad74ca787da36f8aaa diff --git a/keetanetwork-block/tests/samples/RECEIVE.hex b/keetanetwork-block/tests/samples/RECEIVE.hex deleted file mode 100644 index 7f71c19..0000000 --- a/keetanetwork-block/tests/samples/RECEIVE.hex +++ /dev/null @@ -1,5 +0,0 @@ -// RECEIVE block from explorer -// Hash: BA917125248313E2817D11B09B1881A1E8E9DFE2E861A35C74E038B96AAEFBDA -// Source: https://explorer.keeta.com/block/BA917125248313E2817D11B09B1881A1E8E9DFE2E861A35C74E038B96AAEFBDA - -a18202053082020102025382181332303236303131363138323532332e3030375a02010004220002925bb66095636ab410ab4e1f4652163c45fa6ad8370881b1447bb0e7fdc3e37b05000420af60d78da8f071e8680aae85930b69ef2b0bd2ff6c3020806c2c1348de8215d530820157a05530530422000223241bfea9363b43a581fa2d383451c0692b39c6d419c15407d90035c44551f4020a010d30631fb52bb9530404210360342de0c8c8a1d38015bdd2a7f262e627ebd60c50b1aa1396615a922b4becf2a053305104210100000000000000000000000000000000000000000000000000000000000000000209056bc75e2d630fd8f0042103bbd871ca4d787885dfcff2bd9d15897dc71b657b9547f4f59eac52250ceb2f9ba0543052042200025f18c47c939ec74bc368aabcfae28ca2581e8b3975125a54d304b5782e52f42b02091dd0c885f9a0d80000042103bbd871ca4d787885dfcff2bd9d15897dc71b657b9547f4f59eac52250ceb2f9ba0533051042200025f18c47c939ec74bc368aabcfae28ca2581e8b3975125a54d304b5782e52f42b020800b1a2bc2ec5000004210360342de0c8c8a1d38015bdd2a7f262e627ebd60c50b1aa1396615a922b4becf20440a26a3550da8f8e8a1f0cff225ca9d4a626cbeae819a904e45b536faff407f24533afa00bc16cff1839154344654fc967bf0bada193bc4c268c72bbfa21234780 diff --git a/keetanetwork-block/tests/samples/SEND.hex b/keetanetwork-block/tests/samples/SEND.hex deleted file mode 100644 index 131fbad..0000000 --- a/keetanetwork-block/tests/samples/SEND.hex +++ /dev/null @@ -1,5 +0,0 @@ -// SEND block from explorer -// Hash: 05248C7FA3585433D18099BDA283F12F675F8C434479C1F6A52BA98F98E6B79F -// Source: https://explorer.keeta.com/block/05248C7FA3585433D18099BDA283F12F675F8C434479C1F6A52BA98F98E6B79F - -a1820201308201fd02025382181332303236303131363231353135392e3137385a02010004220002925bb66095636ab410ab4e1f4652163c45fa6ad8370881b1447bb0e7fdc3e37b0500042043e4aa9ad6398fd1d42532b39ee2917faee20bf66b6934c6e12680c87f28c64c30820153a054305204220002afe7f6f95f62cff0bd728265069219ddb3f97647f028ab330bcae77c1448f1c402095dae70d23e7d87d659042103bbd871ca4d787885dfcff2bd9d15897dc71b657b9547f4f59eac52250ceb2f9ba0523050042103bbd871ca4d787885dfcff2bd9d15897dc71b657b9547f4f59eac52250ceb2f9b020804cfa5d7c4187972042103bbd871ca4d787885dfcff2bd9d15897dc71b657b9547f4f59eac52250ceb2f9ba0523050042200025f18c47c939ec74bc368aabcfae28ca2581e8b3975125a54d304b5782e52f42b0207426f8d094cc00004210360342de0c8c8a1d38015bdd2a7f262e627ebd60c50b1aa1396615a922b4becf2a0533051042200025f18c47c939ec74bc368aabcfae28ca2581e8b3975125a54d304b5782e52f42b020800b1a2bc2ec5000004210360342de0c8c8a1d38015bdd2a7f262e627ebd60c50b1aa1396615a922b4becf204408649d74f02569672dee5f8b7290773e8b084bf01893361863c2bf7a586f56ec64297f198407a9ca3e1545093d0c5a0a8a98463d17c5832ee1a18f1a704a66685 diff --git a/keetanetwork-block/tests/samples/SET_INFO.hex b/keetanetwork-block/tests/samples/SET_INFO.hex deleted file mode 100644 index 1b34c73..0000000 --- a/keetanetwork-block/tests/samples/SET_INFO.hex +++ /dev/null @@ -1,5 +0,0 @@ -// SET_INFO block from explorer -// Hash: AC1982A17272A36C4346B60587416799953CCB26277CD83443A98E8BE109FBE5 -// Source: https://explorer.keeta.com/block/AC1982A17272A36C4346B60587416799953CCB26277CD83443A98E8BE109FBE5 - -3082013e020100020253820500181332303236303131363231323530392e3039345a042200036e2c89c42b41859f1386f4384cde7ec515ec201c84702f49f491f9130754805a042103b1dd74a8e92843a6b74dbf7afbb0983fa57c5994d921b0b0079703560adb325e04203fe7a9f50684cfdeaece79285c8d1578f76b346eec9cac6f658a9908d2c4ebc53073a260305e0c075445535454574f0c0574657374320c4465794a6b5a574e7062574673637949364f5377695a47566a615731686246427359574e6c637949364f53776963336c74596d3973496a6f69564556545646525854794a393006020101020100a50f300d02080de0b6b3a76400000201000440b6d93c88db24a34387c58ec3d219e89124a75aab7e2b260be7ce1d14c7a0e4a458d7d56049726526d666a76dc6f1ab8fd69989ec11d4412db4fcd8d7de272eb3 diff --git a/keetanetwork-block/tests/samples/SET_REP.hex b/keetanetwork-block/tests/samples/SET_REP.hex deleted file mode 100644 index 06d921f..0000000 --- a/keetanetwork-block/tests/samples/SET_REP.hex +++ /dev/null @@ -1,5 +0,0 @@ -// SET_REP block from explorer -// Hash: F16B80A3FD3DC60D390C7370D71C159A77484DE5A9C480D38407CC17EB095FE7 -// Source: https://explorer.keeta.com/block/F16B80A3FD3DC60D390C7370D71C159A77484DE5A9C480D38407CC17EB095FE7 - -3081d2020100020253820500181332303236303131363139343334372e3734305a04220002b6164ed249d17be9e805a342669b3888883b4f2b7e758612804dbfbe6653eb3d05000420614a2f8d130983c1811e8e88482f915e0455cdbb0f2c67e8eb5f13f10f2ffcb23028a126302404220003565af39d790ef8c12d48831ec5b3f78aa26b88cb200902d895017d7022e527d504407fb9b96926e341bd4ed778df4e836f9797bd6840451b1308649b90121e665d6e22cec3c0fed918652389ccfc9a0726333e31ad9de91ee94d0cb93dd0ff6510c8 diff --git a/keetanetwork-block/tests/samples/SWAP.hex b/keetanetwork-block/tests/samples/SWAP.hex deleted file mode 100644 index 92ceb59..0000000 --- a/keetanetwork-block/tests/samples/SWAP.hex +++ /dev/null @@ -1,5 +0,0 @@ -// SWAP block from explorer -// Hash: E741DFCABA783A290082696ACD911FFD9B5A27458176F27E412C1EF756E87B02 -// Source: https://explorer.keeta.com/block/E741DFCABA783A290082696ACD911FFD9B5A27458176F27E412C1EF756E87B02 - -30820100020100020253820500181332303236303131363231343935332e3835335a042200036f3e786c2aaf545ff1466c8dd947f589931a4b4311d7230557ef7364ee703b01050004208d2c6775251b5a690879b524a748ef959f1a64cf78e63c3652728a7392e4528d3056a054305204220003f10f895043d27a900233f75dd41814186d6b3ba9509f788f888c5b09f041a070020900affb28b8ab49880004210360342de0c8c8a1d38015bdd2a7f262e627ebd60c50b1aa1396615a922b4becf20440514e6dc434b7176cda808257ed7004c266605c37f093824a7f7016116e96b70e6927c514ca2a76ae6fc1067a3e7bdc2c509fc880c17c4f4e40462157eb0e0750 diff --git a/keetanetwork-block/tests/samples/TOKEN_ADMIN_SUPPLY.hex b/keetanetwork-block/tests/samples/TOKEN_ADMIN_SUPPLY.hex deleted file mode 100644 index 2e365ba..0000000 --- a/keetanetwork-block/tests/samples/TOKEN_ADMIN_SUPPLY.hex +++ /dev/null @@ -1,5 +0,0 @@ -// TOKEN_ADMIN_SUPPLY block from explorer -// Hash: BCBCAA4BAD69472FED8A895CED6ED5EFCAB11902EC531961E2FC1B01613C02DA -// Source: https://explorer.keeta.com/block/BCBCAA4BAD69472FED8A895CED6ED5EFCAB11902EC531961E2FC1B01613C02DA - -a182022b3082022702025382181332303236303131363232333935352e3831335a02010004210360342de0c8c8a1d38015bdd2a7f262e627ebd60c50b1aa1396615a922b4becf2308191042107f7b2db86355f93f9dc5d27e94a7e74632ed9878581603103253c00788b074d26306c042200039f4cf0896341a71c016a39826a45f7dc1016cea1e1aa1d34295b6a1c3f7b4cb0042200035d779b71c36fa7120f73b4a3f70b5abf03db277ab6d489e2b66357eee3b97e3d042200025bdd5f4ff5195f24b76733b0dbe94b71176affaf88f4b5ac3ec51bcb2716cba0042028e270466961540fd09f1304d9c4b5881a7c2ff0e98c26271d5182570f15b79b3067a510300e020910d942ab903b064c00020100a05330510421041731817c8c8f3563a821c7ff771b60d2a13154e8f945bba5339b9846f45d88f6020910d942ab903b064c0004210360342de0c8c8a1d38015bdd2a7f262e627ebd60c50b1aa1396615a922b4becf23081c6044069b5f243e359f8c7153445cfc92c964a194e018587c6561631380deb6b84a6c70b0290babfc1a91888bd29d2147f64098774b9a1b393431b9bbfe53b4b63e982044099300795f999e89b2bc6cd499d925bd5073a2d84b4f85d228729317f1972b9db7b8d21b07c36852a0cd34a33d83cf6fa4577311a26e0cf60c9d10a0046af4380044059008a5072a4f266edfe2cbd2e6626806e4ffe04bd818d2873173c7f9fec50355be972447601515d609611228ce2d87f7cf8276a39ea5ecf218a45568e37e4b2 diff --git a/keetanetwork-block/tests/sdk_compat.rs b/keetanetwork-block/tests/sdk_compat.rs index 8218213..79cafc2 100644 --- a/keetanetwork-block/tests/sdk_compat.rs +++ b/keetanetwork-block/tests/sdk_compat.rs @@ -1,49 +1,19 @@ //! SDK Compatibility Tests //! -//! These tests parse block DER bytes from .hex files and verify roundtrip encoding. -//! Test data is from mainnet blocks generated by @keetanetwork/keetanet-client JS SDK. +//! These tests parse block DER bytes and verify roundtrip encoding. +//! Test data includes: +//! - Mainnet blocks from the Keeta Network explorer +//! - Manually constructed test vectors for edge cases and operation variants + +mod samples; use der::{Decode, Encode}; use keetanetwork_block::KeetaBlock; -use std::fs; -use std::path::PathBuf; - -fn get_samples_dir() -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples") -} - -fn load_block_hex(name: &str) -> Vec { - let path = get_samples_dir().join(format!("{}.hex", name)); - let content = fs::read_to_string(&path).unwrap_or_else(|_| panic!("Failed to read {}", path.display())); - - // Filter out comment lines and empty lines - let hex: String = content - .lines() - .filter(|line| !line.starts_with("//") && !line.trim().is_empty()) - .collect(); - - hex::decode(&hex).unwrap_or_else(|_| panic!("Invalid hex in {}", path.display())) -} - -/// Block samples to test. -const SAMPLES: &[&str] = &[ - "SET_REP", - "SET_INFO", - "CREATE_IDENTIFIER", - "CREATE_IDENTIFIER_MULTISIG", - "TOKEN_ADMIN_SUPPLY", - "SEND", - "SWAP", - "RECEIVE", - "MANAGE_CERTIFICATE", -]; #[test] fn test_block_roundtrip_all_samples() { - for sample_name in SAMPLES { - let original_bytes = load_block_hex(sample_name); - - let block = match KeetaBlock::from_der(&original_bytes) { + for (sample_name, original_bytes) in samples::ALL_SAMPLES { + let block = match KeetaBlock::from_der(original_bytes) { Ok(b) => b, Err(e) => panic!("Failed to parse {} block: {:?}", sample_name, e), }; @@ -55,6 +25,86 @@ fn test_block_roundtrip_all_samples() { .to_der() .unwrap_or_else(|_| panic!("Failed to encode {} block", sample_name)); - assert_eq!(encoded, original_bytes, "Roundtrip encoding mismatch for {} block", sample_name); + assert_eq!(encoded, *original_bytes, "Roundtrip encoding mismatch for {} block", sample_name); } } + +// ============================================================================ +// Invalid/Malformed DER Input Tests +// ============================================================================ + +/// Test that empty input returns an error (not panic) +#[test] +fn test_malformed_empty_input() { + let result = KeetaBlock::from_der(&[]); + assert!(result.is_err(), "Empty input should fail to parse"); +} + +/// Test that truncated data returns an error +#[test] +fn test_malformed_truncated_data() { + // Take a valid sample and truncate it + let valid = samples::SET_REP; + let truncated = &valid[..valid.len() / 2]; + let result = KeetaBlock::from_der(truncated); + assert!(result.is_err(), "Truncated data should fail to parse"); +} + +/// Test that invalid outer tag returns an error +#[test] +fn test_malformed_invalid_tag() { + // Use OCTET STRING tag (0x04) instead of SEQUENCE (0x30) or [1] (0xa1) + let invalid = [0x04, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05]; + let result = KeetaBlock::from_der(&invalid); + assert!(result.is_err(), "Invalid outer tag should fail to parse"); +} + +/// Test that length exceeding data returns an error +#[test] +fn test_malformed_length_overflow() { + // SEQUENCE with length 0xFF but only 5 bytes of data + let invalid = [0x30, 0x81, 0xff, 0x01, 0x02, 0x03, 0x04, 0x05]; + let result = KeetaBlock::from_der(&invalid); + assert!(result.is_err(), "Length overflow should fail to parse"); +} + +/// Test that extra trailing bytes are handled +#[test] +fn test_malformed_trailing_data() { + // Take a valid sample and append extra bytes + let valid = samples::SET_REP; + let mut with_trailing = valid.to_vec(); + with_trailing.extend_from_slice(&[0xde, 0xad, 0xbe, 0xef]); + let result = KeetaBlock::from_der(&with_trailing); + // The der crate typically rejects trailing data + assert!(result.is_err(), "Trailing data should fail to parse"); +} + +/// Test that invalid nested structure returns an error +#[test] +fn test_malformed_invalid_nested_structure() { + // V1 block header start, but with invalid content inside + // SEQUENCE { INTEGER 0, ... garbage } + let invalid = [ + 0x30, 0x10, // SEQUENCE of 16 bytes + 0x02, 0x01, 0x00, // INTEGER 0 (version) + 0x02, 0x02, 0x53, 0x82, // INTEGER 21378 (parent) + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // garbage + ]; + let result = KeetaBlock::from_der(&invalid); + assert!(result.is_err(), "Invalid nested structure should fail to parse"); +} + +/// Test that zero-length required fields return an error +#[test] +fn test_malformed_zero_length_field() { + // SEQUENCE with empty OCTET STRING where account key is expected + let invalid = [ + 0x30, 0x08, // SEQUENCE of 8 bytes + 0x02, 0x01, 0x00, // INTEGER 0 (version) + 0x02, 0x01, 0x01, // INTEGER 1 (parent) + 0x04, 0x00, // Empty OCTET STRING (invalid account) + ]; + let result = KeetaBlock::from_der(&invalid); + assert!(result.is_err(), "Zero-length required field should fail to parse"); +} From 883d2cc42dd0da7beb5bc99896d1715631a7dfac Mon Sep 17 00:00:00 2001 From: mamonet Date: Wed, 21 Jan 2026 19:33:52 +0100 Subject: [PATCH 19/25] Fix clippy useless_vec warning in x509 utils test --- keetanetwork-x509/src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keetanetwork-x509/src/utils.rs b/keetanetwork-x509/src/utils.rs index 783946b..408a64c 100644 --- a/keetanetwork-x509/src/utils.rs +++ b/keetanetwork-x509/src/utils.rs @@ -978,7 +978,7 @@ mod tests { assert_eq!(multi_pairs.len(), 7); // Verify each mapping - let expected_mappings = vec![ + let expected_mappings = [ ("commonName", "example.com"), ("organizationName", "Example Organization"), ("organizationalUnitName", "IT Department"), From 8822860bdb34ce924a431ab130d91c3986af78fb Mon Sep 17 00:00:00 2001 From: mamonet Date: Wed, 21 Jan 2026 19:40:42 +0100 Subject: [PATCH 20/25] Fix rustdoc warnings for bracket notation in docs --- keetanetwork-block/src/block.rs | 2 +- keetanetwork-block/src/types.rs | 70 ++++++++++++++++----------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/keetanetwork-block/src/block.rs b/keetanetwork-block/src/block.rs index 0e15b11..4a50cac 100644 --- a/keetanetwork-block/src/block.rs +++ b/keetanetwork-block/src/block.rs @@ -6,7 +6,7 @@ //! ## Block Formats //! //! - **V1**: Plain SEQUENCE with version field = 0 -//! - **V2**: Wrapped in context tag [1] +//! - **V2**: Wrapped in context tag `1` #[cfg(all(feature = "alloc", not(feature = "std")))] use alloc::vec::Vec; diff --git a/keetanetwork-block/src/types.rs b/keetanetwork-block/src/types.rs index b0e92a9..b91863b 100644 --- a/keetanetwork-block/src/types.rs +++ b/keetanetwork-block/src/types.rs @@ -7,17 +7,17 @@ //! //! | Tag | Operation | //! |------|-------------------------| -//! | [0] | Send | -//! | [1] | SetRep | -//! | [2] | SetInfo | -//! | [3] | ModifyPermissions | -//! | [4] | CreateIdentifier | -//! | [5] | TokenAdminSupply | -//! | [6] | TokenAdminModifyBalance | -//! | [7] | Receive | -//! | [8] | ManageCertificate | -//! | [9] | MatchSwap | -//! | [10] | CancelSwap | +//! | `0` | Send | +//! | `1` | SetRep | +//! | `2` | SetInfo | +//! | `3` | ModifyPermissions | +//! | `4` | CreateIdentifier | +//! | `5` | TokenAdminSupply | +//! | `6` | TokenAdminModifyBalance | +//! | `7` | Receive | +//! | `8` | ManageCertificate | +//! | `9` | MatchSwap | +//! | `10` | CancelSwap | // Use alloc for Vec when not using std #[cfg(all(feature = "alloc", not(feature = "std")))] @@ -400,7 +400,7 @@ pub struct Permission { // Operation Structures // ============================================================================ -/// [0] SEND operation - Transfer tokens to another account +/// Tag `0`: SEND operation - Transfer tokens to another account #[derive(Debug, Clone, Sequence)] pub struct SendOp<'a> { /// Destination account @@ -413,14 +413,14 @@ pub struct SendOp<'a> { pub external: Option>, } -/// [1] SET_REP operation - Set representative for delegation +/// Tag `1`: SET_REP operation - Set representative for delegation #[derive(Debug, Clone, Sequence)] pub struct SetRepOp<'a> { /// Representative to delegate to pub to: Bytes<'a>, } -/// [2] SET_INFO operation - Set account information +/// Tag `2`: SET_INFO operation - Set account information #[derive(Debug, Clone, Sequence)] pub struct SetInfoOp<'a> { /// Account name @@ -434,7 +434,7 @@ pub struct SetInfoOp<'a> { pub default_permission: Option, } -/// [3] MODIFY_PERMISSIONS operation - Modify account permissions +/// Tag `3`: MODIFY_PERMISSIONS operation - Modify account permissions #[derive(Debug, Clone, Sequence)] pub struct ModifyPermissionsOp<'a> { /// Principal to modify permissions for @@ -616,15 +616,15 @@ pub struct SwapArgs<'a> { /// Identifier creation arguments #[derive(Debug, Clone, Choice)] pub enum CreateIdentifierArgs<'a> { - /// Multisig creation arguments [7] + /// Multisig creation arguments (tag `7`) #[asn1(context_specific = "7", tag_mode = "EXPLICIT", constructed = "true")] Multisig(MultisigArgs<'a>), - /// Swap creation arguments [8] + /// Swap creation arguments (tag `8`) #[asn1(context_specific = "8", tag_mode = "EXPLICIT", constructed = "true")] Swap(SwapArgs<'a>), } -/// [4] CREATE_IDENTIFIER operation - Create token, multisig, or swap +/// Tag `4`: CREATE_IDENTIFIER operation - Create token, multisig, or swap #[derive(Debug, Clone, Sequence)] pub struct CreateIdentifierOp<'a> { /// Identifier to create @@ -634,7 +634,7 @@ pub struct CreateIdentifierOp<'a> { pub create_arguments: Option>, } -/// [5] TOKEN_ADMIN_SUPPLY operation - Modify token supply +/// Tag `5`: TOKEN_ADMIN_SUPPLY operation - Modify token supply #[derive(Debug, Clone, Copy, Sequence)] pub struct TokenAdminSupplyOp<'a> { /// Amount to modify @@ -643,7 +643,7 @@ pub struct TokenAdminSupplyOp<'a> { pub method: AdjustMethodRelative, } -/// [6] TOKEN_ADMIN_MODIFY_BALANCE operation - Modify account token balance +/// Tag `6`: TOKEN_ADMIN_MODIFY_BALANCE operation - Modify account token balance #[derive(Debug, Clone, Sequence)] pub struct TokenAdminModifyBalanceOp<'a> { /// Token to modify balance of @@ -654,7 +654,7 @@ pub struct TokenAdminModifyBalanceOp<'a> { pub method: AdjustMethod, } -/// [7] RECEIVE operation - Receive tokens from another account +/// Tag `7`: RECEIVE operation - Receive tokens from another account #[derive(Debug, Clone, Sequence)] pub struct ReceiveOp<'a> { /// Amount to receive @@ -670,7 +670,7 @@ pub struct ReceiveOp<'a> { pub forward: Option>, } -/// [8] MANAGE_CERTIFICATE operation - Add or subtract certificates. +/// Tag `8`: MANAGE_CERTIFICATE operation - Add or subtract certificates. /// /// Certificate data is stored as raw DER bytes (can be OCTET STRING or SEQUENCE). #[derive(Debug, Clone)] @@ -745,7 +745,7 @@ impl EncodeValue for ManageCertificateOp<'_> { impl<'a> Sequence<'a> for ManageCertificateOp<'a> {} -/// [9] MATCH_SWAP operation - Match two swap orders +/// Tag `9`: MATCH_SWAP operation - Match two swap orders #[derive(Debug, Clone, Sequence)] pub struct MatchSwapOp<'a> { /// Swap account being used @@ -760,7 +760,7 @@ pub struct MatchSwapOp<'a> { pub fee: NullOr>, } -/// [10] CANCEL_SWAP operation - Cancel a swap order +/// Tag `10`: CANCEL_SWAP operation - Cancel a swap order #[derive(Debug, Clone, Sequence)] pub struct CancelSwapOp<'a> { /// Swap account to cancel @@ -778,37 +778,37 @@ pub struct CancelSwapOp<'a> { /// Keeta blockchain operation #[derive(Debug, Clone, Choice)] pub enum Operation<'a> { - /// [0] Send tokens + /// Tag `0`: Send tokens #[asn1(context_specific = "0", tag_mode = "EXPLICIT", constructed = "true")] Send(SendOp<'a>), - /// [1] Set representative + /// Tag `1`: Set representative #[asn1(context_specific = "1", tag_mode = "EXPLICIT", constructed = "true")] SetRep(SetRepOp<'a>), - /// [2] Set account info + /// Tag `2`: Set account info #[asn1(context_specific = "2", tag_mode = "EXPLICIT", constructed = "true")] SetInfo(SetInfoOp<'a>), - /// [3] Modify permissions + /// Tag `3`: Modify permissions #[asn1(context_specific = "3", tag_mode = "EXPLICIT", constructed = "true")] ModifyPermissions(ModifyPermissionsOp<'a>), - /// [4] Create identifier (token, multisig, swap) + /// Tag `4`: Create identifier (token, multisig, swap) #[asn1(context_specific = "4", tag_mode = "EXPLICIT", constructed = "true")] CreateIdentifier(CreateIdentifierOp<'a>), - /// [5] Token admin supply + /// Tag `5`: Token admin supply #[asn1(context_specific = "5", tag_mode = "EXPLICIT", constructed = "true")] TokenAdminSupply(TokenAdminSupplyOp<'a>), - /// [6] Token admin modify balance + /// Tag `6`: Token admin modify balance #[asn1(context_specific = "6", tag_mode = "EXPLICIT", constructed = "true")] TokenAdminModifyBalance(TokenAdminModifyBalanceOp<'a>), - /// [7] Receive tokens + /// Tag `7`: Receive tokens #[asn1(context_specific = "7", tag_mode = "EXPLICIT", constructed = "true")] Receive(ReceiveOp<'a>), - /// [8] Manage certificate + /// Tag `8`: Manage certificate #[asn1(context_specific = "8", tag_mode = "EXPLICIT", constructed = "true")] ManageCertificate(ManageCertificateOp<'a>), - /// [9] Match swap + /// Tag `9`: Match swap #[asn1(context_specific = "9", tag_mode = "EXPLICIT", constructed = "true")] MatchSwap(MatchSwapOp<'a>), - /// [10] Cancel swap + /// Tag `10`: Cancel swap #[asn1(context_specific = "10", tag_mode = "EXPLICIT", constructed = "true")] CancelSwap(CancelSwapOp<'a>), } From 2881b80cc4dee00f6389aa3d67395252ba9000c0 Mon Sep 17 00:00:00 2001 From: mamonet Date: Thu, 22 Jan 2026 15:24:01 +0100 Subject: [PATCH 21/25] Replace manual DER parsing with SliceReader in SignersIter --- keetanetwork-block/src/types.rs | 91 ++++++++++++--------------------- 1 file changed, 34 insertions(+), 57 deletions(-) diff --git a/keetanetwork-block/src/types.rs b/keetanetwork-block/src/types.rs index b91863b..4debb0f 100644 --- a/keetanetwork-block/src/types.rs +++ b/keetanetwork-block/src/types.rs @@ -32,7 +32,7 @@ use std::vec::Vec; // DER encoding/decoding support use der::{ asn1::{IntRef, Null, OctetStringRef, Utf8StringRef}, - Choice, Decode, DecodeValue, Encode, EncodeValue, Header, Length, Reader, Sequence, Tag, Writer, + Choice, Decode, DecodeValue, Encode, EncodeValue, Header, Length, Reader, Sequence, SliceReader, Tag, Writer, }; // Type aliases for DER types @@ -517,40 +517,47 @@ impl<'a> Sequence<'a> for MultisigArgs<'a> {} /// Iterator that yields raw signer public key bytes (without DER tag/length). #[derive(Debug, Clone)] pub struct SignersIter<'a> { - remaining: &'a [u8], + content: SliceReader<'a>, } impl<'a> SignersIter<'a> { /// Creates a new iterator from the raw signers SEQUENCE bytes. fn new(signers_sequence: &'a [u8]) -> Self { - // Parse the SEQUENCE header to get to the content if signers_sequence.is_empty() { - return SignersIter { remaining: &[] }; + return SignersIter { content: SliceReader::new(&[]).unwrap() }; } - // Check for SEQUENCE tag (0x30) - if signers_sequence[0] != 0x30 { - return SignersIter { remaining: &[] }; - } + // Parse the SEQUENCE header to get the content bytes + let outer_reader = match SliceReader::new(signers_sequence) { + Ok(r) => r, + Err(_) => return SignersIter { content: SliceReader::new(&[]).unwrap() }, + }; - if signers_sequence.len() < 2 { - return SignersIter { remaining: &[] }; + // Read the SEQUENCE header and get content length + let header = match outer_reader.peek_header() { + Ok(h) => h, + Err(_) => return SignersIter { content: SliceReader::new(&[]).unwrap() }, + }; + + if header.tag != Tag::Sequence { + return SignersIter { content: SliceReader::new(&[]).unwrap() }; } - // Parse length - let content_start = if signers_sequence[1] < 0x80 { - // Short form length - 2 - } else { - // Long form length - let num_len_bytes = (signers_sequence[1] & 0x7F) as usize; - if signers_sequence.len() < 2 + num_len_bytes { - return SignersIter { remaining: &[] }; - } - 2 + num_len_bytes + // Skip the header and create a reader for just the content + let header_len = match header.encoded_len() { + Ok(len) => len, + Err(_) => return SignersIter { content: SliceReader::new(&[]).unwrap() }, }; - SignersIter { remaining: &signers_sequence[content_start..] } + let content_len: usize = header.length.try_into().unwrap_or(0); + let header_bytes: usize = header_len.try_into().unwrap_or(0); + + if signers_sequence.len() < header_bytes + content_len { + return SignersIter { content: SliceReader::new(&[]).unwrap() }; + } + + let content = &signers_sequence[header_bytes..header_bytes + content_len]; + SignersIter { content: SliceReader::new(content).unwrap() } } } @@ -558,45 +565,15 @@ impl<'a> Iterator for SignersIter<'a> { type Item = der::Result<&'a [u8]>; fn next(&mut self) -> Option { - if self.remaining.is_empty() { + if self.content.is_finished() { return None; } - // Expect OCTET STRING tag (0x04) - if self.remaining[0] != 0x04 { - return Some(Err(Tag::OctetString.value_error())); - } - - if self.remaining.len() < 2 { - return Some(Err(Tag::OctetString.value_error())); - } - - // Parse length - let (content_start, content_len) = if self.remaining[1] < 0x80 { - // Short form length - (2usize, self.remaining[1] as usize) - } else { - // Long form length - let num_len_bytes = (self.remaining[1] & 0x7F) as usize; - if self.remaining.len() < 2 + num_len_bytes { - return Some(Err(Tag::OctetString.value_error())); - } - let mut len: usize = 0; - for i in 0..num_len_bytes { - len = (len << 8) | (self.remaining[2 + i] as usize); - } - (2 + num_len_bytes, len) - }; - - let total_len = content_start + content_len; - if self.remaining.len() < total_len { - return Some(Err(Tag::OctetString.value_error())); + // Decode the next OCTET STRING + match Bytes::decode(&mut self.content) { + Ok(octet_string) => Some(Ok(octet_string.as_bytes())), + Err(e) => Some(Err(e)), } - - let content = &self.remaining[content_start..total_len]; - self.remaining = &self.remaining[total_len..]; - - Some(Ok(content)) } } From 3a34af046eae8a17910a146a61d0b42b88aed074 Mon Sep 17 00:00:00 2001 From: mamonet Date: Wed, 4 Feb 2026 18:12:31 +0100 Subject: [PATCH 22/25] Add extract_operations_slice for block operations parsing --- keetanetwork-block/src/block.rs | 5 ++ keetanetwork-block/src/lib.rs | 3 + keetanetwork-block/src/parse.rs | 83 ++++++++++++++++++++++++++ keetanetwork-block/src/types.rs | 14 ++--- keetanetwork-block/tests/sdk_compat.rs | 53 +++++++++++++++- 5 files changed, 149 insertions(+), 9 deletions(-) create mode 100644 keetanetwork-block/src/parse.rs diff --git a/keetanetwork-block/src/block.rs b/keetanetwork-block/src/block.rs index 4a50cac..ff1195a 100644 --- a/keetanetwork-block/src/block.rs +++ b/keetanetwork-block/src/block.rs @@ -182,6 +182,7 @@ fn decode_v2_block<'a, R: Reader<'a>>(reader: &mut R) -> der::Result`. +#[cfg(any(feature = "alloc", feature = "std"))] fn decode_null_or_integer<'a, R: Reader<'a>>(reader: &mut R) -> der::Result> { let tag = reader.peek_tag()?; if tag == Tag::Null { @@ -194,6 +195,7 @@ fn decode_null_or_integer<'a, R: Reader<'a>>(reader: &mut R) -> der::Result>(reader: &mut R) -> der::Result<&'a [u8]> { let header = Header::decode(reader)?; if header.tag != Tag::GeneralizedTime { @@ -659,12 +661,14 @@ fn encode_multisig(info: &MultiSigSignerInfo, writer: &mut impl Writer) -> der:: } /// Calculates `GeneralizedTime` encoded length. +#[cfg(any(feature = "alloc", feature = "std"))] fn encode_generalized_time_len(date_bytes: &[u8]) -> der::Result { let content_len = Length::try_from(date_bytes.len())?; Header::new(Tag::GeneralizedTime, content_len)?.encoded_len() + content_len } /// Encodes `GeneralizedTime` from raw bytes. +#[cfg(any(feature = "alloc", feature = "std"))] fn encode_generalized_time(date_bytes: &[u8], writer: &mut impl Writer) -> der::Result<()> { let content_len = Length::try_from(date_bytes.len())?; Header::new(Tag::GeneralizedTime, content_len)?.encode(writer)?; @@ -675,6 +679,7 @@ fn encode_generalized_time(date_bytes: &[u8], writer: &mut impl Writer) -> der:: // BlockPurpose Encode // ============================================================================ +#[cfg(any(feature = "alloc", feature = "std"))] impl From for u8 { fn from(purpose: BlockPurpose) -> u8 { match purpose { diff --git a/keetanetwork-block/src/lib.rs b/keetanetwork-block/src/lib.rs index 055cb43..5ecd968 100644 --- a/keetanetwork-block/src/lib.rs +++ b/keetanetwork-block/src/lib.rs @@ -4,11 +4,14 @@ #![cfg_attr(not(feature = "std"), no_std)] +mod parse; mod types; #[cfg(any(feature = "alloc", feature = "std"))] mod block; +pub use parse::extract_operations_slice; + // Types that require alloc (use Vec) #[cfg(any(feature = "alloc", feature = "std"))] pub use types::{ diff --git a/keetanetwork-block/src/parse.rs b/keetanetwork-block/src/parse.rs new file mode 100644 index 0000000..22328a8 --- /dev/null +++ b/keetanetwork-block/src/parse.rs @@ -0,0 +1,83 @@ +//! DER utility functions for zero-copy parsing. + +use der::{Decode, Header, Reader, SliceReader, Tag, TagNumber}; + +/// Extracts the operations SEQUENCE content from raw block bytes. +/// +/// This function skips block header fields and returns a slice containing +/// the raw DER content of the operations SEQUENCE. +pub fn extract_operations_slice(data: &[u8]) -> Option<&[u8]> { + if data.is_empty() { + return None; + } + + let mut reader = SliceReader::new(data).ok()?; + let tag = reader.peek_tag().ok()?; + + if tag == Tag::Sequence { + // V1 block: plain SEQUENCE + extract_operations_v1(&mut reader) + } else if tag.is_context_specific() && tag.number() == TagNumber::new(1) { + // V2 block: context tag [1] + extract_operations_v2(&mut reader) + } else { + None + } +} + +/// Extract operations from V1 block. +fn extract_operations_v1<'a>(reader: &mut SliceReader<'a>) -> Option<&'a [u8]> { + let header = Header::decode(reader).ok()?; + if header.tag != Tag::Sequence { + return None; + } + let content = reader.read_slice(header.length).ok()?; + let mut inner = SliceReader::new(content).ok()?; + + // Skip V1 header fields: version, network, subnet, date, account, signer, previous + for _ in 0..7 { + skip_any(&mut inner).ok()?; + } + + read_sequence_content(&mut inner) +} + +/// Extract operations from V2 block. +fn extract_operations_v2<'a>(reader: &mut SliceReader<'a>) -> Option<&'a [u8]> { + let ctx_header = Header::decode(reader).ok()?; + if !ctx_header.tag.is_context_specific() || ctx_header.tag.number() != TagNumber::new(1) { + return None; + } + let inner_content = reader.read_slice(ctx_header.length).ok()?; + let mut inner = SliceReader::new(inner_content).ok()?; + + let seq_header = Header::decode(&mut inner).ok()?; + if seq_header.tag != Tag::Sequence { + return None; + } + let seq_content = inner.read_slice(seq_header.length).ok()?; + let mut seq_reader = SliceReader::new(seq_content).ok()?; + + // Skip V2 header fields: network, date, purpose, account, signer, previous + for _ in 0..6 { + skip_any(&mut seq_reader).ok()?; + } + + read_sequence_content(&mut seq_reader) +} + +/// Skip any DER element (tag + length + content). +pub(crate) fn skip_any<'a>(reader: &mut SliceReader<'a>) -> der::Result<()> { + let header = Header::decode(reader)?; + reader.read_slice(header.length)?; + Ok(()) +} + +/// Read a SEQUENCE and return its content bytes. +fn read_sequence_content<'a>(reader: &mut SliceReader<'a>) -> Option<&'a [u8]> { + let header = Header::decode(reader).ok()?; + if header.tag != Tag::Sequence { + return None; + } + reader.read_slice(header.length).ok() +} diff --git a/keetanetwork-block/src/types.rs b/keetanetwork-block/src/types.rs index 4debb0f..ccadb52 100644 --- a/keetanetwork-block/src/types.rs +++ b/keetanetwork-block/src/types.rs @@ -35,6 +35,13 @@ use der::{ Choice, Decode, DecodeValue, Encode, EncodeValue, Header, Length, Reader, Sequence, SliceReader, Tag, Writer, }; +/// Reads raw TLV bytes (tag + length + content) from reader. +fn read_tlv_bytes<'a, R: Reader<'a>>(reader: &mut R) -> der::Result<&'a [u8]> { + let header = reader.peek_header()?; + let tlv_len = (header.encoded_len()? + header.length)?; + reader.read_slice(tlv_len) +} + // Type aliases for DER types pub type Bytes<'a> = OctetStringRef<'a>; pub type Int<'a> = IntRef<'a>; @@ -661,13 +668,6 @@ pub struct ManageCertificateOp<'a> { pub intermediate_certificates: Option>, } -/// Reads raw TLV bytes (tag + length + content) from reader. -fn read_tlv_bytes<'a, R: Reader<'a>>(reader: &mut R) -> der::Result<&'a [u8]> { - let header = reader.peek_header()?; - let tlv_len = (header.encoded_len()? + header.length)?; - reader.read_slice(tlv_len) -} - impl<'a> DecodeValue<'a> for ManageCertificateOp<'a> { fn decode_value>(reader: &mut R, _header: Header) -> der::Result { let method = reader.decode()?; diff --git a/keetanetwork-block/tests/sdk_compat.rs b/keetanetwork-block/tests/sdk_compat.rs index 79cafc2..bb4d65c 100644 --- a/keetanetwork-block/tests/sdk_compat.rs +++ b/keetanetwork-block/tests/sdk_compat.rs @@ -7,8 +7,8 @@ mod samples; -use der::{Decode, Encode}; -use keetanetwork_block::KeetaBlock; +use der::{Decode, Encode, Reader, SliceReader}; +use keetanetwork_block::{extract_operations_slice, KeetaBlock, Operation}; #[test] fn test_block_roundtrip_all_samples() { @@ -108,3 +108,52 @@ fn test_malformed_zero_length_field() { let result = KeetaBlock::from_der(&invalid); assert!(result.is_err(), "Zero-length required field should fail to parse"); } + +// ============================================================================ +// extract_operations_slice Tests +// ============================================================================ + +/// Test that extract_operations_slice works with all sample blocks +#[test] +fn test_extract_operations_slice_all_samples() { + for (sample_name, original_bytes) in samples::ALL_SAMPLES { + // First parse using full KeetaBlock to get expected operations + let block = KeetaBlock::from_der(original_bytes) + .unwrap_or_else(|e| panic!("Failed to parse {} block: {:?}", sample_name, e)); + + let ops_slice = extract_operations_slice(original_bytes) + .unwrap_or_else(|| panic!("extract_operations_slice failed for {}", sample_name)); + + // Parse operations from the slice and verify count matches + let mut reader = SliceReader::new(ops_slice).expect("SliceReader failed"); + let mut op_count = 0; + while !reader.is_finished() { + let _op = Operation::decode(&mut reader) + .unwrap_or_else(|e| panic!("Failed to decode operation {} in {}: {:?}", op_count, sample_name, e)); + op_count += 1; + } + + assert_eq!( + op_count, + block.operations.len(), + "Operation count mismatch for {}: extracted {} vs block {}", + sample_name, + op_count, + block.operations.len() + ); + } +} + +/// Test that extract_operations_slice returns None for invalid input +#[test] +fn test_extract_operations_slice_invalid() { + // Empty input + assert!(extract_operations_slice(&[]).is_none()); + + // Invalid tag + assert!(extract_operations_slice(&[0x04, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05]).is_none()); + + // Truncated data + let valid = samples::SET_REP; + assert!(extract_operations_slice(&valid[..10]).is_none()); +} From 10a5c8c60cbae2cb4092694400fb2e7bef23f083 Mon Sep 17 00:00:00 2001 From: mamonet Date: Thu, 5 Feb 2026 01:14:29 +0100 Subject: [PATCH 23/25] Add keetanetwork-permissions crate with permission bit definitions --- keetanetwork-permissions/Cargo.toml | 13 ++++++ keetanetwork-permissions/src/lib.rs | 64 +++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 keetanetwork-permissions/Cargo.toml create mode 100644 keetanetwork-permissions/src/lib.rs diff --git a/keetanetwork-permissions/Cargo.toml b/keetanetwork-permissions/Cargo.toml new file mode 100644 index 0000000..f23f63f --- /dev/null +++ b/keetanetwork-permissions/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "keetanetwork-permissions" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +description = "Permission bit definitions for Keetanetwork blockchain" + +[lib] +name = "keetanetwork_permissions" +path = "src/lib.rs" diff --git a/keetanetwork-permissions/src/lib.rs b/keetanetwork-permissions/src/lib.rs new file mode 100644 index 0000000..2e4ef52 --- /dev/null +++ b/keetanetwork-permissions/src/lib.rs @@ -0,0 +1,64 @@ +//! # Keetanetwork Permissions +//! +//! Permission bit definitions for the Keetanetwork blockchain. + +#![no_std] + +// Base permission bit constants +pub const ACCESS: u64 = 1 << 0; +pub const OWNER: u64 = 1 << 1; +pub const ADMIN: u64 = 1 << 2; +pub const UPDATE_INFO: u64 = 1 << 3; +pub const SEND_ON_BEHALF: u64 = 1 << 4; +pub const TOKEN_CREATE: u64 = 1 << 5; +pub const TOKEN_SUPPLY: u64 = 1 << 6; +pub const TOKEN_BALANCE: u64 = 1 << 7; +pub const STORAGE_CREATE: u64 = 1 << 8; +pub const STORAGE_HOLD: u64 = 1 << 9; +pub const STORAGE_DEPOSIT: u64 = 1 << 10; +pub const PERM_ADD: u64 = 1 << 11; +pub const PERM_REMOVE: u64 = 1 << 12; +pub const MANAGE_CERT: u64 = 1 << 13; +pub const MULTISIG_SIGNER: u64 = 1 << 14; + +/// Lookup table mapping each base permission bit to its display name. +pub const BASE_PERMISSIONS: [(u64, &str); 15] = [ + (ACCESS, "ACCESS"), + (OWNER, "OWNER"), + (ADMIN, "ADMIN"), + (UPDATE_INFO, "INFO"), + (SEND_ON_BEHALF, "SEND"), + (TOKEN_CREATE, "T_CREATE"), + (TOKEN_SUPPLY, "T_SUPPLY"), + (TOKEN_BALANCE, "T_BALANCE"), + (STORAGE_CREATE, "S_CREATE"), + (STORAGE_HOLD, "S_HOLD"), + (STORAGE_DEPOSIT, "S_DEPOSIT"), + (PERM_ADD, "P_ADD"), + (PERM_REMOVE, "P_REMOVE"), + (MANAGE_CERT, "CERT"), + (MULTISIG_SIGNER, "MULTISIG"), +]; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn no_duplicate_bits() { + let mut combined = 0u64; + for &(mask, _) in &BASE_PERMISSIONS { + assert_eq!(combined & mask, 0, "duplicate bit in BASE_PERMISSIONS"); + combined |= mask; + } + } + + #[test] + fn all_bits_contiguous() { + let mut combined = 0u64; + for &(mask, _) in &BASE_PERMISSIONS { + combined |= mask; + } + assert_eq!(combined, (1 << 15) - 1); + } +} From 374bba1495cb3af18d508f5836dbe6ae4ed13c32 Mon Sep 17 00:00:00 2001 From: mamonet Date: Thu, 5 Feb 2026 01:15:48 +0100 Subject: [PATCH 24/25] Add keetanetwork-permissions to workspace members --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 3151285..961ed7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "keetanetwork-asn1", "keetanetwork-utils", "keetanetwork-block", + "keetanetwork-permissions", "keetanetwork-ledger", "keetanetwork-node", ] From 9d768871fa64149a596ddf8abd005bc56489259a Mon Sep 17 00:00:00 2001 From: mamonet Date: Thu, 5 Feb 2026 17:47:53 +0100 Subject: [PATCH 25/25] Add metadata module and consolidate permissions into keetanetwork-block --- Cargo.toml | 1 - keetanetwork-block/Cargo.toml | 3 + keetanetwork-block/src/lib.rs | 2 + keetanetwork-block/src/metadata.rs | 256 ++++++++++++++++++ .../src/permissions.rs | 7 +- keetanetwork-permissions/Cargo.toml | 13 - 6 files changed, 264 insertions(+), 18 deletions(-) create mode 100644 keetanetwork-block/src/metadata.rs rename keetanetwork-permissions/src/lib.rs => keetanetwork-block/src/permissions.rs (93%) delete mode 100644 keetanetwork-permissions/Cargo.toml diff --git a/Cargo.toml b/Cargo.toml index 961ed7b..3151285 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ members = [ "keetanetwork-asn1", "keetanetwork-utils", "keetanetwork-block", - "keetanetwork-permissions", "keetanetwork-ledger", "keetanetwork-node", ] diff --git a/keetanetwork-block/Cargo.toml b/keetanetwork-block/Cargo.toml index 8dd170d..9d6dc15 100644 --- a/keetanetwork-block/Cargo.toml +++ b/keetanetwork-block/Cargo.toml @@ -15,6 +15,9 @@ alloc = ["der/alloc"] [dependencies] der = { version = "0.7.10", default-features = false, features = ["derive"] } +serde = { version = "1.0", default-features = false, features = ["derive"] } +serde-json-core = { version = "0.6", default-features = false } +base64ct = { version = "1.8", default-features = false } [dev-dependencies] der = "0.7.10" diff --git a/keetanetwork-block/src/lib.rs b/keetanetwork-block/src/lib.rs index 5ecd968..8befacc 100644 --- a/keetanetwork-block/src/lib.rs +++ b/keetanetwork-block/src/lib.rs @@ -5,6 +5,8 @@ #![cfg_attr(not(feature = "std"), no_std)] mod parse; +pub mod metadata; +pub mod permissions; mod types; #[cfg(any(feature = "alloc", feature = "std"))] diff --git a/keetanetwork-block/src/metadata.rs b/keetanetwork-block/src/metadata.rs new file mode 100644 index 0000000..adf4407 --- /dev/null +++ b/keetanetwork-block/src/metadata.rs @@ -0,0 +1,256 @@ +//! Metadata parsing. +//! +//! Decodes base64 JSON metadata and extracts asset_id/authority/symbol. +//! This module is no_std compatible and does not allocate. + +use base64ct::{Base64, Encoding}; +use serde::Deserialize; + +const MAX_FIELD_LEN: usize = 64; + +/// Raw metadata structure for JSON deserialization. +#[derive(Deserialize)] +struct RawMetadata<'a> { + #[serde(default)] + asset_id: Option<&'a str>, + #[serde(default)] + authority: Option<&'a str>, + #[serde(default)] + symbol: Option<&'a str>, +} + +/// Decoded metadata with fixed-size buffers. +pub struct DecodedMetadata { + pub asset_id: [u8; MAX_FIELD_LEN], + pub asset_id_len: usize, + pub authority: [u8; MAX_FIELD_LEN], + pub authority_len: usize, + pub symbol: [u8; MAX_FIELD_LEN], + pub symbol_len: usize, +} + +impl DecodedMetadata { + /// Create a new empty metadata instance. + pub fn new() -> Self { + Self { + asset_id: [0u8; MAX_FIELD_LEN], + asset_id_len: 0, + authority: [0u8; MAX_FIELD_LEN], + authority_len: 0, + symbol: [0u8; MAX_FIELD_LEN], + symbol_len: 0, + } + } + + /// Get the asset_id as a string slice, if present. + pub fn asset_id_str(&self) -> Option<&str> { + if self.asset_id_len > 0 { + core::str::from_utf8(&self.asset_id[..self.asset_id_len]).ok() + } else { + None + } + } + + /// Get the authority as a string slice, if present. + pub fn authority_str(&self) -> Option<&str> { + if self.authority_len > 0 { + core::str::from_utf8(&self.authority[..self.authority_len]).ok() + } else { + None + } + } + + /// Get the symbol as a string slice, if present. + pub fn symbol_str(&self) -> Option<&str> { + if self.symbol_len > 0 { + core::str::from_utf8(&self.symbol[..self.symbol_len]).ok() + } else { + None + } + } +} + +impl Default for DecodedMetadata { + fn default() -> Self { + Self::new() + } +} + +/// Result of metadata decoding. +pub enum MetadataDisplay { + /// Successfully decoded metadata with at least one known field. + Decoded(DecodedMetadata), + /// Valid JSON but no known fields (asset_id, authority, symbol). + Unknown, + /// Invalid base64 encoding or malformed JSON. + Invalid, + /// Empty input string. + Empty, +} + +/// Decode base64 metadata and extract known fields. +/// +/// # Arguments +/// * `base64_input` - Base64-encoded JSON string +/// * `decode_buf` - Buffer for base64 decoding, must be >= input.len() * 3/4 +/// +/// # Returns +/// A `MetadataDisplay` variant indicating the result. +pub fn decode_metadata(base64_input: &str, decode_buf: &mut [u8]) -> MetadataDisplay { + if base64_input.is_empty() { + return MetadataDisplay::Empty; + } + + let decoded = match Base64::decode(base64_input.as_bytes(), decode_buf) { + Ok(bytes) => bytes, + Err(_) => return MetadataDisplay::Invalid, + }; + + let raw: RawMetadata = match serde_json_core::from_slice(decoded) { + Ok((meta, _)) => meta, + Err(_) => return MetadataDisplay::Invalid, + }; + + let mut result = DecodedMetadata::new(); + let mut found_any = false; + + if let Some(value) = raw.asset_id { + let bytes = value.as_bytes(); + let copy_len = bytes.len().min(MAX_FIELD_LEN); + result.asset_id[..copy_len].copy_from_slice(&bytes[..copy_len]); + result.asset_id_len = copy_len; + found_any = true; + } + + if let Some(value) = raw.authority { + let bytes = value.as_bytes(); + let copy_len = bytes.len().min(MAX_FIELD_LEN); + result.authority[..copy_len].copy_from_slice(&bytes[..copy_len]); + result.authority_len = copy_len; + found_any = true; + } + + if let Some(value) = raw.symbol { + let bytes = value.as_bytes(); + let copy_len = bytes.len().min(MAX_FIELD_LEN); + result.symbol[..copy_len].copy_from_slice(&bytes[..copy_len]); + result.symbol_len = copy_len; + found_any = true; + } + + if found_any { + MetadataDisplay::Decoded(result) + } else { + MetadataDisplay::Unknown + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_decode_metadata_full() { + let mut buf = [0u8; 512]; + + let json = r#"{"asset_id":"asset://1f0ccae9-5666/1","authority":"keeta_abc123def456","signature":"c2lnbmF0dXJl"}"#; + let b64 = base64_encode_for_test(json.as_bytes()); + + match decode_metadata(&b64, &mut buf) { + MetadataDisplay::Decoded(meta) => { + assert_eq!(meta.asset_id_str(), Some("asset://1f0ccae9-5666/1")); + assert_eq!(meta.authority_str(), Some("keeta_abc123def456")); + } + _ => panic!("Expected Decoded variant"), + } + } + + #[test] + fn test_decode_metadata_with_symbol() { + let mut buf = [0u8; 512]; + + let json = r#"{"asset_id":"asset://123","symbol":"KEETA"}"#; + let b64 = base64_encode_for_test(json.as_bytes()); + + match decode_metadata(&b64, &mut buf) { + MetadataDisplay::Decoded(meta) => { + assert_eq!(meta.asset_id_str(), Some("asset://123")); + assert_eq!(meta.symbol_str(), Some("KEETA")); + assert_eq!(meta.authority_str(), None); + } + _ => panic!("Expected Decoded variant"), + } + } + + #[test] + fn test_decode_metadata_empty() { + let mut buf = [0u8; 100]; + match decode_metadata("", &mut buf) { + MetadataDisplay::Empty => {} + _ => panic!("Expected Empty variant"), + } + } + + #[test] + fn test_decode_metadata_unknown_fields_only() { + let mut buf = [0u8; 512]; + + let json = r#"{"foo":"bar","baz":123}"#; + let b64 = base64_encode_for_test(json.as_bytes()); + + match decode_metadata(&b64, &mut buf) { + MetadataDisplay::Unknown => {} + _ => panic!("Expected Unknown variant"), + } + } + + #[test] + fn test_decode_metadata_invalid_base64() { + let mut buf = [0u8; 100]; + match decode_metadata("not-valid-base64!!!", &mut buf) { + MetadataDisplay::Invalid => {} + _ => panic!("Expected Invalid variant"), + } + } + + #[test] + fn test_decode_metadata_invalid_json() { + let mut buf = [0u8; 100]; + let b64 = base64_encode_for_test(b"not json at all"); + + match decode_metadata(&b64, &mut buf) { + MetadataDisplay::Invalid => {} + _ => panic!("Expected Invalid variant"), + } + } + + fn base64_encode_for_test(input: &[u8]) -> String { + const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + let mut result = String::new(); + + for chunk in input.chunks(3) { + let b0 = chunk[0] as u32; + let b1 = chunk.get(1).copied().unwrap_or(0) as u32; + let b2 = chunk.get(2).copied().unwrap_or(0) as u32; + + let n = (b0 << 16) | (b1 << 8) | b2; + + result.push(CHARS[((n >> 18) & 0x3F) as usize] as char); + result.push(CHARS[((n >> 12) & 0x3F) as usize] as char); + + if chunk.len() > 1 { + result.push(CHARS[((n >> 6) & 0x3F) as usize] as char); + } else { + result.push('='); + } + + if chunk.len() > 2 { + result.push(CHARS[(n & 0x3F) as usize] as char); + } else { + result.push('='); + } + } + + result + } +} diff --git a/keetanetwork-permissions/src/lib.rs b/keetanetwork-block/src/permissions.rs similarity index 93% rename from keetanetwork-permissions/src/lib.rs rename to keetanetwork-block/src/permissions.rs index 2e4ef52..a3a2b1e 100644 --- a/keetanetwork-permissions/src/lib.rs +++ b/keetanetwork-block/src/permissions.rs @@ -1,8 +1,7 @@ -//! # Keetanetwork Permissions -//! //! Permission bit definitions for the Keetanetwork blockchain. - -#![no_std] +//! +//! This module defines the semantic meaning of permission bits used in +//! [`Permission`](crate::Permission) values. // Base permission bit constants pub const ACCESS: u64 = 1 << 0; diff --git a/keetanetwork-permissions/Cargo.toml b/keetanetwork-permissions/Cargo.toml deleted file mode 100644 index f23f63f..0000000 --- a/keetanetwork-permissions/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "keetanetwork-permissions" -version.workspace = true -edition.workspace = true -authors.workspace = true -license.workspace = true -repository.workspace = true -homepage.workspace = true -description = "Permission bit definitions for Keetanetwork blockchain" - -[lib] -name = "keetanetwork_permissions" -path = "src/lib.rs"