Repository: BitVM/BitVM
Pull request #289: WOTS data format refactor
Author: login: uncomputable
Created: 2025-04-30T21:54:28Z | Merged: 2025-05-05T16:37:45Z

This PR documents the data formats used in the WOTS code. I also do some cheap refactors while I'm at it.

##  From messages to digits

In WOTS, we sign messages of bytes. These bytes are converted into digits, which reverses the byte order. Each byte is split into two nibbles (digits).

A message of the form `[0x12, 0x34, 0x56, 0x78, ... ]` is converted into digits `[..., 7, 8, 5, 6, 3, 4, 1, 2]`.

A checksum is appended to the end of the digit sequence.

This data format might raise some eyebrows, but a lot of code depends on it. In particular, many Bitcoin scripts expect this exact format. For now, it seems best to keep it and document as best as possible.

## From digits to signatures

A WOTS signature are the hashes of each digit, plus optionally the digit itself. The precise format is documented in the code.


Diff:
diff --git a/bitvm/src/bigint/bits.rs b/bitvm/src/bigint/bits.rs
index 9b29524c..998556a2 100644
--- a/bitvm/src/bigint/bits.rs
+++ b/bitvm/src/bigint/bits.rs
@@ -47,7 +47,7 @@ impl<const N_BITS: u32, const LIMB_SIZE: u32> BigIntImpl<N_BITS, LIMB_SIZE> {
     }
 
     pub fn limb_from_bytes() -> Script {
-        let bytes_per_limb = (LIMB_SIZE + 7) / 8;
+        let bytes_per_limb = LIMB_SIZE.div_ceil(8);
 
         assert!(LIMB_SIZE > 0, "LIMB_SIZE must not be 0");
         assert!(LIMB_SIZE < 33, "LIMB_SIZE must be less than 33");
diff --git a/bitvm/src/hash/blake3.rs b/bitvm/src/hash/blake3.rs
index 5f149cda..8b703276 100644
--- a/bitvm/src/hash/blake3.rs
+++ b/bitvm/src/hash/blake3.rs
@@ -214,7 +214,7 @@ fn chunk_message(message_bytes: &[u8]) -> Vec<[u8; 64]> {
     message_bytes
         .iter()
         .copied()
-        .chain(std::iter::repeat(0u8).take(needed_padding_bytes))
+        .chain(std::iter::repeat_n(0u8, needed_padding_bytes))
         .chunks(4) // reverse 4-byte chunks
         .into_iter()
         .flat_map(|chunk| chunk.collect::<Vec<u8>>().into_iter().rev())
diff --git a/bitvm/src/signatures/signing_winternitz.rs b/bitvm/src/signatures/signing_winternitz.rs
index ac207c67..ac555564 100644
--- a/bitvm/src/signatures/signing_winternitz.rs
+++ b/bitvm/src/signatures/signing_winternitz.rs
@@ -18,7 +18,7 @@ pub struct WinternitzSecret {
     parameters: Parameters,
 }
 
-// Bits per digit (block)
+/// Bits per digit.
 pub const LOG_D: u32 = 4;
 
 impl WinternitzSecret {
@@ -40,7 +40,7 @@ impl WinternitzSecret {
     pub fn from_string(secret: &str, parameters: &Parameters) -> Self {
         WinternitzSecret {
             secret_key: secret.as_bytes().to_lower_hex_string().into(),
-            parameters: parameters.clone(),
+            parameters: *parameters,
         }
     }
 }
@@ -55,7 +55,7 @@ impl From<&WinternitzSecret> for WinternitzPublicKey {
     fn from(secret: &WinternitzSecret) -> Self {
         WinternitzPublicKey {
             public_key: generate_public_key(&secret.parameters, &secret.secret_key),
-            parameters: secret.parameters.clone(),
+            parameters: secret.parameters,
         }
     }
 }
@@ -135,11 +135,7 @@ pub fn winternitz_message_checksig_verify(
 mod tests {
     use super::*;
     use super::{WinternitzPublicKey, WinternitzSecret};
-    use crate::{
-        bn254::g1::G1Affine,
-        execute_script,
-        signatures::{utils::digits_to_number, winternitz::generate_public_key},
-    };
+    use crate::{bn254::g1::G1Affine, execute_script, signatures::utils};
     use crate::{execute_script_with_inputs, ExecuteInfo};
     use ark_ff::UniformRand as _;
     use ark_std::test_rng;
@@ -199,7 +195,7 @@ mod tests {
           },
           ).to_vec() }
           { winternitz_message_checksig(&public_key) }
-          { digits_to_number::<{ 4 * 2}, { LOG_D as usize }>() }
+          { utils::digits_to_number::<{ 4 * 2}, { LOG_D as usize }>() }
           { start_time_block_number }
           OP_EQUAL
         };
@@ -227,7 +223,7 @@ mod tests {
         let public_key = WinternitzPublicKey::from(&secret);
         let reference_public_key = generate_public_key(&secret.parameters, &secret.secret_key);
 
-        for i in 0..secret.parameters.total_length() {
+        for i in 0..secret.parameters.total_digit_len() {
             assert_eq!(
                 public_key.public_key[i as usize],
                 reference_public_key[i as usize]
@@ -242,9 +238,9 @@ mod tests {
 
         assert_eq!(
             public_key.public_key.len(),
-            public_key.parameters.total_length() as usize
+            public_key.parameters.total_digit_len() as usize
         );
-        for i in 0..public_key.parameters.total_length() {
+        for i in 0..public_key.parameters.total_digit_len() {
             assert_eq!(
                 public_key.public_key[i as usize].len(),
                 20,
diff --git a/bitvm/src/signatures/utils.rs b/bitvm/src/signatures/utils.rs
index 2eb38fc6..30e6a80a 100644
--- a/bitvm/src/signatures/utils.rs
+++ b/bitvm/src/signatures/utils.rs
@@ -11,64 +11,90 @@ pub(super) const fn log_base_ceil(n: u32, base: u32) -> u32 {
     res
 }
 
-/// Converts the number to given base, smallest digit being at the start (returns `digit_count` smallest digits of it, and standard base representation of it if `digit_count` = -1)
-pub(super) fn to_digits(mut number: u32, base: u32, digit_count: i32) -> Vec<u32> {
-    let mut digits = Vec::new();
-    if digit_count == -1 {
-        while number > 0 {
-            let digit = number % base;
-            number = (number - digit) / base;
-            digits.push(digit);
-        }
-    } else {
-        digits.reserve(digit_count as usize);
-        for _ in 0..digit_count {
-            let digit = number % base;
-            number = (number - digit) / base;
-            digits.push(digit);
-        }
+/// Converts the given `checksum` into a vector of digits.
+///
+/// ## Output format
+///
+/// - sequence of `n_digits` many digits
+/// - each digit a `u32` value in range `0..base`
+/// - checksum converted into BE bytes, in turn converted into digits
+pub(super) fn checksum_to_digits(mut checksum: u32, base: u32, n_digits: u32) -> Vec<u32> {
+    debug_assert!((16..=256).contains(&base));
+    debug_assert!(
+        base.checked_pow(n_digits)
+            .map(|upper_limit| checksum < upper_limit)
+            .unwrap_or(true),
+        "Checksum is too large to fit into the given number of digits"
+    );
+
+    let mut digits = vec![0; n_digits as usize]; // cast safety: 32-bit machine or higher
+
+    for digit in digits.iter_mut().rev() {
+        *digit = checksum % base;
+        checksum = (checksum - *digit) / base;
     }
+
     digits
 }
 
-/// Converts the given bytes to 'len' `u32`'s, each consisting of given number of bits
-pub(crate) fn bytes_to_u32s(len: u32, bits_per_item: u32, bytes: &[u8]) -> Vec<u32> {
-    assert!(
-        bytes.len() as u32 * 8 <= len * bits_per_item,
-        "Message length is too large for the given length"
+/// Converts the given `message` into a vector of digits.
+///
+/// ## Output format
+///
+/// - sequence of `n_digits` many digits
+/// - each digit a `u32` value in range `0..2.pow(log2_base)`
+/// - message bytes are reversed (but not their nibbles!)
+pub(crate) fn message_to_digits(n_digits: u32, log2_base: u32, message: &[u8]) -> Vec<u32> {
+    debug_assert!((4..=8).contains(&log2_base));
+    debug_assert!(
+        message.len() as u32 * 8 <= n_digits * log2_base,
+        "Message is too long to fit into the given number of digits"
     );
-    let mut res = vec![0u32; len as usize];
-    let mut cur_index: u32 = 0;
-    let mut cur_bit: u32 = 0;
-    for byte in bytes {
-        let mut x: u8 = *byte;
+
+    let mut digits = vec![0u32; n_digits as usize]; // cast safety: 32-bit machine or higher
+    let mut digit_idx: u32 = 0;
+    let mut bit_idx: u32 = 0;
+
+    for mut byte in message.iter().copied() {
         for _ in 0..8 {
-            if cur_bit == bits_per_item {
-                cur_bit = 0;
-                cur_index += 1;
+            if bit_idx == log2_base {
+                bit_idx = 0;
+                digit_idx += 1;
             }
-            res[cur_index as usize] |= ((x & 1) as u32) << cur_bit;
-            x >>= 1;
-            cur_bit += 1;
+            digits[digit_idx as usize] |= ((byte & 1) as u32) << bit_idx; // cast safety: 32-bit machine or higher
+            byte >>= 1;
+            bit_idx += 1;
         }
     }
-    res
+
+    digits.reverse();
+    digits
 }
 
-/// Merges (binary concatenates) the `DIGIT_COUNT` stack elements (each consisting of `LOG_D` bits), most significant one being at the top
-pub fn digits_to_number<const DIGIT_COUNT: usize, const LOG_D: usize>() -> Script {
-    script!(
-        for _ in 0..DIGIT_COUNT - 1 {
+/// Returns a Bitcoin script that converts a message into a number.
+///
+/// ## Precondition
+///
+/// - message is at stack top
+/// - message is split into `N_DIGITS` digits
+/// - each digit is in range `0..2.pow(LOG2_BASE)`
+///
+/// ## Postcondition
+///
+/// - converted number is at stack top
+pub fn digits_to_number<const N_DIGITS: usize, const LOG2_BASE: usize>() -> Script {
+    script! {
+        for _ in 0..N_DIGITS - 1 {
           OP_TOALTSTACK
         }
-        for _ in 0..DIGIT_COUNT - 1 {
-            for _ in 0..LOG_D {
-                OP_DUP OP_ADD
+        for _ in 0..N_DIGITS - 1 {
+            for _ in 0..LOG2_BASE {
+                OP_DUP OP_ADD // simulating OP_MUL
             }
             OP_FROMALTSTACK
             OP_ADD
         }
-    )
+    }
 }
 
 /// Converts number to vector of bytes and removes trailing zeroes
@@ -82,7 +108,8 @@ pub fn u32_to_le_bytes_minimal(a: u32) -> Vec<u8> {
 
 #[cfg(test)]
 mod test {
-    use super::u32_to_le_bytes_minimal;
+    use super::*;
+    use crate::run;
 
     #[test]
     fn test_u32_to_bytes_minimal() {
@@ -91,4 +118,65 @@ mod test {
 
         assert_eq!(a_bytes, vec![0x00u8, 0xfeu8]);
     }
+
+    #[test]
+    fn checksum_to_digits_endianness() {
+        // Integer is encoded as BE digit sequence
+        assert_eq!(
+            checksum_to_digits(0x12345678, 16, 8),
+            vec![1, 2, 3, 4, 5, 6, 7, 8],
+        );
+    }
+
+    #[test]
+    fn message_to_digits_endianness() {
+        let message: u32 = 0x12345678;
+        // Bytes are reversed from BE to LE
+        // Digits stay BE
+        // The result is a weird mix of endianness
+        assert_eq!(
+            message_to_digits(8, 4, &message.to_be_bytes()),
+            vec![7, 8, 5, 6, 3, 4, 1, 2],
+        );
+        // Bytes are reversed from LE to BE
+        // Digits stay BE
+        // The result is a BE sequence
+        assert_eq!(
+            message_to_digits(8, 4, &message.to_le_bytes()),
+            vec![1, 2, 3, 4, 5, 6, 7, 8],
+        );
+    }
+
+    #[test]
+    fn digits_to_number_endianness() {
+        // LE input is glued together as LE
+        let script = script! {
+            { vec![0x07, 0x08, 0x05, 0x06, 0x03, 0x04, 0x01, 0x02] }
+            { digits_to_number::<8, 4>() }
+            { 0x78563412 }
+            OP_EQUAL
+        };
+        run(script);
+        // BE input is glued together as BE
+        let script = script! {
+            { vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] }
+            { digits_to_number::<8, 4>() }
+            { 0x12345678 }
+            OP_EQUAL
+        };
+        run(script);
+    }
+
+    #[test]
+    fn message_digits_roundtrip() {
+        let message: u32 = 0x12345678;
+        let digits = message_to_digits(8, 4, &message.to_le_bytes());
+        let script = script! {
+            { digits }
+            { digits_to_number::<8, 4>() }
+            { message }
+            OP_EQUAL
+        };
+        run(script);
+    }
 }
diff --git a/bitvm/src/signatures/winternitz.rs b/bitvm/src/signatures/winternitz.rs
index 44003eb4..81af2a54 100644
--- a/bitvm/src/signatures/winternitz.rs
+++ b/bitvm/src/signatures/winternitz.rs
@@ -11,68 +11,61 @@ type HashOut = [u8; 20];
 pub type PublicKey = Vec<HashOut>;
 pub type SecretKey = Vec<u8>;
 
-/// Contains the parameters to use with `Winternitz` struct
-#[derive(Serialize, Deserialize, Eq, PartialEq, Hash, Clone)]
+/// Parameters for the [`Winternitz`] struct.
+#[derive(Serialize, Deserialize, Eq, PartialEq, Hash, Clone, Copy)]
 pub struct Parameters {
-    /// Number of blocks of the actual message
-    message_length: u32,
-    /// Number of bits in one block
-    block_length: u32,
-    /// Number of blocks of the checksum part
-    checksum_length: u32,
+    /// Number of digits per message (including zero padding at the end).
+    message_digit_len: u32,
+    /// Number of bits per digit.
+    log2_base: u32,
+    /// Number of digits per checksum.
+    checksum_digit_len: u32,
 }
 
 impl Parameters {
-    /// Creates parameters with given message length (number of blocks in the message) and block length (number of bits in one block, in the closed range 4, 8)
-    pub const fn new(message_block_count: u32, block_length: u32) -> Self {
+    /// Creates parameters for messages of the given number of digits of the given base.
+    ///
+    /// The log2_base must be in the range `4..=8`.
+    pub const fn new(message_digit_len: u32, log2_base: u32) -> Self {
         assert!(
-            4 <= block_length && block_length <= 8,
-            "You can only choose block lengths in the range [4, 8]"
+            4 <= log2_base && log2_base <= 8,
+            "log2_base must be in the range 4..=8"
         );
         Parameters {
-            message_length: message_block_count,
-            block_length,
-            checksum_length: log_base_ceil(
-                ((1 << block_length) - 1) * message_block_count,
-                1 << block_length,
+            message_digit_len,
+            log2_base,
+            checksum_digit_len: log_base_ceil(
+                ((1 << log2_base) - 1) * message_digit_len,
+                1 << log2_base,
             ) + 1,
         }
     }
 
-    /// Creates parameters with given message_length (number of bits in the message) and block length (number of bits in one block, in the closed range 4, 8)
-    pub const fn new_by_bit_length(number_of_bits: u32, block_length: u32) -> Self {
-        assert!(
-            4 <= block_length && block_length <= 8,
-            "You can only choose block lengths in the range [4, 8]"
-        );
-        let message_block_count = number_of_bits.div_ceil(block_length);
-        Parameters {
-            message_length: message_block_count,
-            block_length,
-            checksum_length: log_base_ceil(
-                ((1 << block_length) - 1) * message_block_count,
-                1 << block_length,
-            ) + 1,
-        }
+    /// Creates parameters for messages of the given bit length and for digits of the given base.
+    ///
+    /// The log2_base must be in the range `4..=8`.
+    pub const fn new_by_bit_length(message_n_bits: u32, log2_base: u32) -> Self {
+        let message_digit_len = message_n_bits.div_ceil(log2_base);
+        Self::new(message_digit_len, log2_base)
     }
 
     /// Maximum value of a digit
-    pub const fn d(&self) -> u32 {
-        (1 << self.block_length) - 1
+    pub const fn max_digit(&self) -> u32 {
+        (1 << self.log2_base) - 1
     }
 
-    /// Number of bytes that can be represented at maximum with the parameters
-    pub const fn byte_message_length(&self) -> u32 {
-        (self.message_length * self.block_length + 7) / 8
+    /// Maximum byte length of supported messages.
+    pub const fn message_byte_len(&self) -> u32 {
+        (self.message_digit_len * self.log2_base).div_ceil(8)
     }
 
-    /// Total number of blocks, i.e. sum of the number of blocks in the actual message and the checksum
-    pub const fn total_length(&self) -> u32 {
-        self.message_length + self.checksum_length
+    /// Total number of digits (message plus checksum).
+    pub const fn total_digit_len(&self) -> u32 {
+        self.message_digit_len + self.checksum_digit_len
     }
 }
 
-/// Returns the signature of a given digit (block), requires the digit index to modify the secret key for each digit
+/// Returns the signature of a given digit, requires the digit index to modify the secret key for each digit
 pub fn digit_signature(secret_key: &SecretKey, digit_index: u32, message_digit: u32) -> HashOut {
     let mut secret_i = secret_key.clone();
     secret_i.push(digit_index as u8);
@@ -83,70 +76,105 @@ pub fn digit_signature(secret_key: &SecretKey, digit_index: u32, message_digit:
     *hash.as_byte_array()
 }
 
-/// Returns the public key of a given digit (block), requires the digit index to modify the secret key for each digit
+/// Returns the public key of a given digit, requires the digit index to modify the secret key for each digit
 fn public_key_for_digit(ps: &Parameters, secret_key: &SecretKey, digit_index: u32) -> HashOut {
-    digit_signature(secret_key, digit_index, ps.d())
+    digit_signature(secret_key, digit_index, ps.max_digit())
 }
 
 /// Returns the public key for the given secret key and the parameters
 pub fn generate_public_key(ps: &Parameters, secret_key: &SecretKey) -> PublicKey {
-    let mut public_key = PublicKey::with_capacity(ps.total_length() as usize);
-    for i in 0..ps.total_length() {
+    let mut public_key = PublicKey::with_capacity(ps.total_digit_len() as usize);
+    for i in 0..ps.total_digit_len() {
         public_key.push(public_key_for_digit(ps, secret_key, i));
     }
     public_key
 }
 
-/// Checksum of the message (negated sum of the digits)
-fn checksum(ps: &Parameters, digits: Vec<u32>) -> u32 {
-    let mut sum = 0;
-    for digit in digits {
-        sum += digit;
-    }
-    ps.d() * ps.message_length - sum
+/// Computes the checksum for the given message.
+fn checksum(ps: &Parameters, message_digits: &[u32]) -> u32 {
+    debug_assert_eq!(message_digits.len(), ps.message_digit_len as usize);
+
+    let sum: u32 = message_digits.iter().sum();
+    ps.max_digit() * ps.message_digit_len - sum
 }
 
-/// Appends checksum to the given message (digits), and reverses the whole message (with checksum) for the ease of usage in the bitcoin script
-fn add_message_checksum(ps: &Parameters, mut digits: Vec<u32>) -> Vec<u32> {
-    let mut checksum_digits = to_digits(
-        checksum(ps, digits.clone()),
-        ps.d() + 1,
-        ps.checksum_length as i32,
+/// Appends the checksum to the end of the message.
+fn add_message_checksum(ps: &Parameters, mut message_digits: Vec<u32>) -> Vec<u32> {
+    debug_assert_eq!(message_digits.len(), ps.message_digit_len as usize);
+
+    let checksum_digits = checksum_to_digits(
+        checksum(ps, &message_digits),
+        ps.max_digit() + 1,
+        ps.checksum_digit_len,
     );
-    checksum_digits.append(&mut digits);
-    checksum_digits.reverse();
-    checksum_digits
+    message_digits.extend(checksum_digits);
+    message_digits
 }
 
-/// This trait covers 3 verifiers for the signing and verifying phase of the signatures: `ListpickVerifier`, `BruteforceVerifier`, `BinarysearchVerifier`
+/// Creates and verifies Winternitz signatures.
 pub trait Verifier {
-    /// Default digit signatures for `ListpickVerifier` and `BinarysearchVerifier`, in the format: hash_{n - 1}, digit_{n - 1}, hash_{n - 2}, digit_{n - 2} ... hash_0, digit_0
-    fn sign_digits(ps: &Parameters, secret_key: &SecretKey, digits: Vec<u32>) -> Witness {
-        let digits = add_message_checksum(ps, digits);
+    /// Creates a Winternitz signature for the given `secret_key` and `digits`.
+    ///
+    /// ## Output format
+    ///
+    /// - `hash(digits[0])`
+    /// - `digits[0]`
+    /// - `hash(digits[1])`
+    /// - `digits[1]`
+    /// - ...
+    /// - `hash(digits[n + m - 1])`
+    /// - `digits[n + m - 1]`
+    ///
+    /// There are `n` message digits followed by `m` checksum digits.
+    /// The checksum is computed internally.
+    fn sign_digits(ps: &Parameters, secret_key: &SecretKey, message_digits: Vec<u32>) -> Witness {
+        let digits = add_message_checksum(ps, message_digits);
         let mut result = Witness::new();
-        for i in 0..ps.total_length() {
+        for i in 0..ps.total_digit_len() {
             let sig = digit_signature(secret_key, i, digits[i as usize]);
             result.push(sig);
             result.push(u32_to_le_bytes_minimal(digits[i as usize]));
         }
         result
     }
+
+    /// Returns a Bitcoin script that verifies a Winternitz signature for the given `public_key`.
+    ///
+    /// The checksum is verified by a separate Bitcoin script.
+    ///
+    /// ## Precondition
+    ///
+    /// - `hash(digits[0])`
+    /// - `digits[0]`
+    /// - `hash(digits[1])`
+    /// - `digits[1]`
+    /// - ...
+    /// - `hash(digits[n + m - 1])`
+    /// - `digits[n + m - 1]` (stack top)
+    ///
+    /// For `n` message digits followed by `m` checksum digits.
+    ///
+    /// ## Postcondition
+    ///
+    /// - `digits[n + m - 1]`
+    /// - `digits[n + m - 2]`
+    /// - ...
+    /// - `digits[0]` (alt stack top)
+    ///
+    /// The input is consumed from the stack.
     fn verify_digits(ps: &Parameters, public_key: &PublicKey) -> Script;
 }
 
-/// This trait covers 2 converters for converting the final message (which is left in the form of blocks): `VoidConverter`, `ToBytesConverter`
+/// Converts the message on the stack after signature verification.
 pub trait Converter {
-    /// Wrapper for converter functions
+    /// Returns a Bitcoin script that converts the message on the stack.
     fn get_script(ps: &Parameters) -> Script;
-    /// Length of the final message
+
+    /// Returns the number of stack elements of the conversion output.
     fn length_of_final_message(ps: &Parameters) -> u32;
 }
-/// Winternitz struct to used for its operations \
-/// Sample Usage:
-/// - Pick the algorithms you want to use, i.e. `BinarysearchVerifier` and `ToBytesConverter`
-/// - Construct your struct: `let o = Winternitz::<BinarysearchVerifier, ToBytesConverter>::new()`
-/// - Identify your message parameters:` let p = Parameters::new(message_block_count, block_length)` or `let p = Parameters::new_by_bit_length(number_of_bits_of_the_message, block_length)`
-/// - Use the methods for the necessary operations, for example: `o.sign(&p, ...)`, `o.checksig_verify(&p, ...)`, `o.checksig_verify_remove_message(&p, ...)`
+
+/// Marker for the set of used Winternitz algorithms.
 pub struct Winternitz<VERIFIER: Verifier, CONVERTER: Converter> {
     phantom0: PhantomData<VERIFIER>,
     phantom1: PhantomData<CONVERTER>,
@@ -158,8 +186,8 @@ impl<VERIFIER: Verifier, CONVERTER: Converter> Default for Winternitz<VERIFIER,
     }
 }
 
-/// Implementation of the default functions regardless of the algorithms that are chosen
 impl<VERIFIER: Verifier, CONVERTER: Converter> Winternitz<VERIFIER, CONVERTER> {
+    /// Creates a marker for the given [`Verifier`] and [`Converter`].
     pub const fn new() -> Self {
         Winternitz {
             phantom0: PhantomData,
@@ -167,7 +195,11 @@ impl<VERIFIER: Verifier, CONVERTER: Converter> Winternitz<VERIFIER, CONVERTER> {
         }
     }
 
-    /// Wrapper to sign the digits
+    /// Creates a Winternitz signature for the given `secret_key` and `digits`.
+    ///
+    /// ## See
+    ///
+    /// [`Verifier::sign_digits`]
     pub fn sign_digits(
         &self,
         ps: &Parameters,
@@ -177,16 +209,36 @@ impl<VERIFIER: Verifier, CONVERTER: Converter> Winternitz<VERIFIER, CONVERTER> {
         VERIFIER::sign_digits(ps, secret_key, digits)
     }
 
-    /// Wrapper to sign the message in bytes (converts to digits inside)
-    pub fn sign(&self, ps: &Parameters, secret_key: &SecretKey, message_bytes: &[u8]) -> Witness {
+    /// Creates a Winternitz signature for the given `secret_key` and `message`.
+    ///
+    /// The message is internally converted into digits.
+    ///
+    /// ## See
+    ///
+    /// [`Verifier::sign_digits`]
+    pub fn sign(&self, ps: &Parameters, secret_key: &SecretKey, message: &[u8]) -> Witness {
         VERIFIER::sign_digits(
             ps,
             secret_key,
-            bytes_to_u32s(ps.message_length, ps.block_length, message_bytes),
+            message_to_digits(ps.message_digit_len, ps.log2_base, message),
         )
     }
 
-    /// Expects the signature in the format of the used verification algorithm, start of the message being on the top and checksum being at the bottom, and verifies if the given signature is accurate leaving digits of the message on the stack start of it being on top
+    /// Returns a Bitcoin script that verifies a Winternitz signature for the given `public_key`.
+    ///
+    /// ## Precondition
+    ///
+    /// Signature (in the verifier's format) is at the stack top.
+    ///
+    /// ## Postcondition
+    ///
+    /// The converted message is on the stack top.
+    /// The checksum is consumed.
+    ///
+    /// ## See
+    ///
+    /// - [`Verifier::verify_digits`]
+    /// - [`Converter::get_script`]
     pub fn checksig_verify(&self, ps: &Parameters, public_key: &PublicKey) -> Script {
         script! {
             { VERIFIER::verify_digits(ps, public_key) }
@@ -195,7 +247,19 @@ impl<VERIFIER: Verifier, CONVERTER: Converter> Winternitz<VERIFIER, CONVERTER> {
         }
     }
 
-    /// Expects the signature in the format of the used verification algorithm, start of the message being on the top and checksum being at the bottom, and verifies if the given signature is accurate without leaving any trace on stack
+    /// Returns a Bitcoin script that verifies a Winternitz signature for the given `public_key`.
+    ///
+    /// ## Precondition
+    ///
+    /// Signature (in the verifier's format) is at the stack top.
+    ///
+    /// ## Postcondition
+    ///
+    /// The message and checksum are consumed.
+    ///
+    /// ## See
+    ///
+    /// [`Verifier::verify_digits`]
     pub fn checksig_verify_and_clear_stack(
         &self,
         ps: &Parameters,
@@ -204,28 +268,46 @@ impl<VERIFIER: Verifier, CONVERTER: Converter> Winternitz<VERIFIER, CONVERTER> {
         script! {
             { VERIFIER::verify_digits(ps, public_key) }
             { self.verify_checksum(ps) }
-            for _ in 0..(ps.message_length) / 2 {
+            for _ in 0..(ps.message_digit_len) / 2 {
                 OP_2DROP
             }
-            if ps.message_length % 2 == 1 {
+            if ps.message_digit_len % 2 == 1 {
                 OP_DROP
             }
         }
     }
 
-    /// Verifies the checksum after the whole message is verified, expects the digits of the whole message at the alt stack, the first element of the message being at the bottom. \
-    /// Removes the checksum digits and leaves the actual message on stack
+    /// Returns a Bitcoin script that verifies the message checksum.
+    ///
+    /// ## Precondition
+    ///
+    /// - `digits[n + m - 1]`
+    /// - `digits[n + m - 2]`
+    /// - ...
+    /// - `digits[0]` (alt stack top)
+    ///
+    /// On the alt stack, there are `m` checksum digits followed by `n` message digits.
+    /// The digits order is flipped.
+    ///
+    /// ## Postcondition
+    ///
+    /// - `digits[0]`
+    /// - `digits[1]`
+    /// - ...
+    /// - `digits[n - 1]` (stack top)
+    ///
+    /// The input is consumed from the alt stack.
     fn verify_checksum(&self, ps: &Parameters) -> Script {
         script! {
             OP_FROMALTSTACK OP_DUP OP_NEGATE
-            for _ in 1..ps.message_length {
+            for _ in 1..ps.message_digit_len {
                 OP_FROMALTSTACK OP_TUCK OP_SUB // sum the digits and tuck them before the sum so they are stored for later
             }
-            { ps.d() * ps.message_length }
+            { ps.max_digit() * ps.message_digit_len }
             OP_ADD
             OP_FROMALTSTACK
-            for _ in 0..ps.checksum_length - 1 {
-                for _ in 0..ps.block_length {
+            for _ in 0..ps.checksum_digit_len - 1 {
+                for _ in 0..ps.log2_base {
                     OP_DUP OP_ADD
                 }
                 OP_FROMALTSTACK
@@ -236,82 +318,146 @@ impl<VERIFIER: Verifier, CONVERTER: Converter> Winternitz<VERIFIER, CONVERTER> {
     }
 }
 
-/// - Verification Algorithm: Generates hashes for each possible value and then uses `OP_PICK` to get the corresponding one from the created list. \
-///   As a small improvement, it also divides the length of the list by 2 in the start
-/// - Signature format: hash_{n - 1}, digit_{n - 1}, hash_{n - 2}, digit_{n - 2} ... hash_0, digit_0 (With digits)
-/// - Approximate Max Stack Depth Used During Verification: 2 * `total_length()` + (2 ^ `block_length`)/2   
+/// Winternitz signature verification using lists and `OP_PICK`.
+///
+/// ## Verification Algorithm
+///
+/// Generates hashes for each possible value and then uses `OP_PICK` to get the corresponding one from the created list.
+/// As a small improvement, it also divides the length of the list by 2 in the start.
+///
+/// ## Signature format
+///
+/// - `hash(digits[0])`
+/// - `digits[0]`
+/// - `hash(digits[1])`
+/// - `digits[1]`
+/// - ...
+/// - `hash(digits[n + m - 1])`
+/// - `digits[n + m - 1]`
+///
+/// There are `n` message digits followed by `m` checksum digits.
+///
+/// ## Approximate Max Stack Depth Used During Verification
+///
+/// 2 * `total_digit_len` + (2 ^ `log2_base`) / 2
 pub struct ListpickVerifier {}
+
 impl Verifier for ListpickVerifier {
-    /// Expects the signature in the verifier's format, checks and verifies if the given signature is accurate with the given public key, leaving the message (with checksum) on the stack in given order
     fn verify_digits(ps: &Parameters, public_key: &PublicKey) -> Script {
         script! {
-            for digit_index in 0..ps.total_length() {
+            for digit_index in 0..ps.total_digit_len() {
                 // See https://github.com/BitVM/BitVM/issues/35
-                { ps.d() }
+                { ps.max_digit() }
                 OP_MIN
                 OP_DUP
                 OP_TOALTSTACK
-                { (ps.d() + 1) / 2 }
+                { ps.max_digit().div_ceil(2) }
                 OP_2DUP
                 OP_LESSTHAN
                 OP_IF
                     OP_DROP
                     OP_TOALTSTACK
-                    for _ in 0..(ps.d() + 1) / 2  {
+                    for _ in 0..ps.max_digit().div_ceil(2)  {
                         OP_HASH160
                     }
                 OP_ELSE
                     OP_SUB
                     OP_TOALTSTACK
                 OP_ENDIF
-                for _ in 0..ps.d()/2 {
+                for _ in 0..ps.max_digit()/2 {
                     OP_DUP OP_HASH160
                 }
                 OP_FROMALTSTACK
                 OP_PICK
-                { (public_key[ps.total_length() as usize - 1 - digit_index as usize]).to_vec() }
+                { (public_key[ps.total_digit_len() as usize - 1 - digit_index as usize]).to_vec() }
                 OP_EQUALVERIFY
-                for _ in 0..(ps.d() + 1) / 4 {
+                for _ in 0..(ps.max_digit() + 1) / 4 {
                     OP_2DROP
                 }
             }
         }
     }
 }
-/// - Verification Algorithm: Tries each possible digit value
-/// - Signature Format: hash_{n - 1}, hash_{n - 2} ... hash_0 (Without digits)
-/// - Approximate Max Stack Depth Used During Verification: `total_length()`
+
+/// Winternitz signature verification using brute force.
+///
+/// ## Verification Algorithm
+///
+/// Tries each possible digit value.
+///
+/// ## Signature Format
+///
+/// - `hash(digit[0])`
+/// - `hash(digit[1])`
+/// - ...
+/// - `hash(digit[n + m - 1])`
+///
+/// There are `n` message digits followed by `m` checksum digits.
+///
+/// ## Approximate Max Stack Depth Used During Verification
+///
+/// `total_length()`
 pub struct BruteforceVerifier {}
+
 impl Verifier for BruteforceVerifier {
-    /// Signature in the verifier's format
-    fn sign_digits(ps: &Parameters, secret_key: &SecretKey, digits: Vec<u32>) -> Witness {
-        let digits = add_message_checksum(ps, digits);
+    /// Creates a Winternitz signature for the given `secret_key` and `digits`.
+    ///
+    /// ## Signature Format
+    ///
+    /// - `hash(digit[0])`
+    /// - `hash(digit[1])`
+    /// - ...
+    /// - `hash(digit[n + m - 1])`
+    ///
+    /// There are `n` message digits followed by `m` checksum digits.
+    /// The checksum is computed internally.
+    fn sign_digits(ps: &Parameters, secret_key: &SecretKey, message_digits: Vec<u32>) -> Witness {
+        let digits = add_message_checksum(ps, message_digits);
         let mut result = Witness::new();
-        for i in 0..ps.total_length() {
+        for i in 0..ps.total_digit_len() {
             let sig = digit_signature(secret_key, i, digits[i as usize]);
             result.push(sig);
         }
         result
     }
-    /// Expects the signature in the verifier's format, checks and verifies if the given signature is accurate with the given public key, leaving the message (with checksum) on the stack in given order
+
+    /// Returns a Bitcoin script that verifies a Winternitz signature for the given `public_key`.
+    ///
+    /// ## Precondition
+    ///
+    /// - `hash(digits[0])`
+    /// - `hash(digits[1])`
+    /// - ...
+    /// - `hash(digits[n + m - 1])` (stack top)
+    ///
+    /// There are `n` message digits followed by `m` checksum digits.
+    ///
+    /// ## Postcondition
+    ///
+    /// - `digits[n - 1]`
+    /// - `digits[n - 2]`
+    /// - ...
+    /// - `digits[0]` (alt stack top)
+    ///
+    /// The input is consumed from the stack.
     fn verify_digits(ps: &Parameters, public_key: &PublicKey) -> Script {
         script! {
-            for digit_index in 0..ps.total_length() {
-                { public_key[(ps.total_length() - 1 - digit_index) as usize].to_vec() }
+            for digit_index in 0..ps.total_digit_len() {
+                { public_key[(ps.total_digit_len() - 1 - digit_index) as usize].to_vec() }
                 OP_SWAP
-                { -1 } OP_TOALTSTACK // To avoid illegal stack acces, same -1 is checked later
+                { -1 } OP_TOALTSTACK // To avoid illegal stack access, same -1 is checked later
                 OP_2DUP
                 OP_EQUAL
                 OP_IF
-                    {ps.d()}
+                    {ps.max_digit()}
                     OP_TOALTSTACK
                 OP_ENDIF
-                for i in 0..ps.d() {
+                for i in 0..ps.max_digit() {
                     OP_HASH160
                     OP_2DUP
                     OP_EQUAL
                     OP_IF
-                        { ps.d() - i - 1 }
+                        { ps.max_digit() - i - 1 }
                         OP_TOALTSTACK
                     OP_ENDIF
                 }
@@ -327,22 +473,40 @@ impl Verifier for BruteforceVerifier {
     }
 }
 
-/// - Verification Algorithm: Simulates a for loop of hashing using binary search on the digit
-/// - Signature Format: hash_{n - 1}, digit_{n - 1}, hash_{n - 2}, digit_{n - 2} ... hash_0, digit_0 (With digits)
-/// - Approximate Max Stack Depth Used During Verification: 2 * `total_length()`
+/// Winternitz signature verification using binary search.
+///
+/// ## Verification Algorithm
+///
+/// Simulates a for loop of hashing using binary search on the digit.
+///
+/// ## Signature Format
+///
+/// - `hash(digits[0])`
+/// - `digits[0]`
+/// - `hash(digits[1])`
+/// - `digits[1]`
+/// - ...
+/// - `hash(digits[n + m - 1])`
+/// - `digits[n + m - 1]`
+///
+/// There are `n` message digits followed by `m` checksum digits.
+///
+/// ## Approximate Max Stack Depth Used During Verification
+///
+/// 2 * `total_length()`
 pub struct BinarysearchVerifier {}
+
 impl Verifier for BinarysearchVerifier {
-    /// Expects the signature in the verifier's format, checks and verifies if the given signature is accurate with the given public key, leaving the message (with checksum) on the stack in given order
     fn verify_digits(ps: &Parameters, public_key: &PublicKey) -> Script {
         script! {
-            for digit_index in 0..ps.total_length() {
+            for digit_index in 0..ps.total_digit_len() {
                 //one can send digits out of the range, i.e. negative or bigger than D for it to act as in range, so inorder for checksum to not be decreased, a lower bound check is necessary and enough
                 OP_0
                 OP_MAX
                 OP_DUP
                 OP_TOALTSTACK
-                { ps.d() } OP_SWAP OP_SUB
-                for bit in (0..ps.block_length).rev() {
+                { ps.max_digit() } OP_SWAP OP_SUB
+                for bit in (0..ps.log2_base).rev() {
                     if bit != 0 {
                         {1 << bit}
                         OP_2DUP
@@ -363,49 +527,53 @@ impl Verifier for BinarysearchVerifier {
                         OP_ENDIF
                     }
                 }
-                { (public_key[(ps.total_length() - 1 - digit_index) as usize]).to_vec() }
+                { (public_key[(ps.total_digit_len() - 1 - digit_index) as usize]).to_vec() }
                 OP_EQUALVERIFY
             }
         }
     }
 }
 
-/// Does nothing, leaving each stack each element as a block from Winternitz
+/// Leaves the digits on the stack as they are.
 pub struct VoidConverter {}
+
 impl Converter for VoidConverter {
     fn length_of_final_message(ps: &Parameters) -> u32 {
-        ps.message_length
+        ps.message_digit_len
     }
+
     fn get_script(ps: &Parameters) -> Script {
         let _ = ps;
         script! {}
     }
 }
 
-/// Alters message (divides it into 8 bit pieces), leaving each stack each element as a byte
+/// Converts the digits into bytes and leaves them on the stack.
 pub struct ToBytesConverter {}
+
 impl Converter for ToBytesConverter {
     fn length_of_final_message(ps: &Parameters) -> u32 {
-        ps.byte_message_length()
+        ps.message_byte_len()
     }
+
     fn get_script(ps: &Parameters) -> Script {
-        if ps.block_length == 8 {
+        if ps.log2_base == 8 {
             //already bytes
             script! {}
-        } else if ps.block_length == 4 {
+        } else if ps.log2_base == 4 {
             script! {
-                for i in 0..ps.message_length / 2 {
+                for i in 0..ps.message_digit_len / 2 {
                     OP_SWAP
-                    for _ in 0..ps.block_length {
+                    for _ in 0..ps.log2_base {
                         OP_DUP OP_ADD
                     }
                     OP_ADD
-                    if i != (ps.message_length / 2) - 1 {
+                    if i != (ps.message_digit_len / 2) - 1 {
                         OP_TOALTSTACK
                     }
                 }
-                if ps.message_length > 1 {
-                    for _ in 0..ps.message_length / 2 - 1{
+                if ps.message_digit_len > 1 {
+                    for _ in 0..ps.message_digit_len / 2 - 1{
                         OP_FROMALTSTACK
                     }
                 }
@@ -413,20 +581,20 @@ impl Converter for ToBytesConverter {
         } else {
             let mut lens: Vec<u32> = vec![];
             let mut split_save = vec![];
-            for i in 0..ps.message_length {
-                let start = i * ps.block_length;
+            for i in 0..ps.message_digit_len {
+                let start = i * ps.log2_base;
                 let next_stop = start + 8 - (start % 8);
                 let split = next_stop - start;
                 split_save.push(split);
-                if split >= ps.block_length {
-                    lens.push(ps.block_length);
+                if split >= ps.log2_base {
+                    lens.push(ps.log2_base);
                 } else {
                     lens.push(split);
-                    lens.push(ps.block_length - split);
+                    lens.push(ps.log2_base - split);
                 }
             }
             lens.reverse();
-            let mut last_bytes_var = (8 - (ps.message_length * ps.block_length % 8)) % 8;
+            let mut last_bytes_var = (8 - (ps.message_digit_len * ps.log2_base % 8)) % 8;
             let mut is_last_zero_var = true;
             let mut last_bytes_save = vec![];
             let mut is_last_zero_save = vec![];
@@ -443,12 +611,12 @@ impl Converter for ToBytesConverter {
 
             script! {
                 for split in split_save {
-                    if split >= ps.block_length {
+                    if split >= ps.log2_base {
                         OP_TOALTSTACK
                     } else {
                         OP_0
-                        for j in (split..ps.block_length).rev() {
-                            if j != ps.block_length - 1 {
+                        for j in (split..ps.log2_base).rev() {
+                            if j != ps.log2_base - 1 {
                                 OP_DUP OP_ADD
                             }
                             OP_SWAP
@@ -510,7 +678,7 @@ mod test {
     // This test is not extensive and definitely misses corner cases
     fn try_malicious(ps: &Parameters, _message: &[u8], verifier: &str) -> Script {
         let mut rng = MALICIOUS_RNG.lock().unwrap();
-        let ind = rng.gen_range(0..ps.total_length());
+        let ind = rng.gen_range(0..ps.total_digit_len());
         if verifier == get_type_name::<BruteforceVerifier>() {
             script! {
                 for _ in 0..ind {
@@ -544,58 +712,67 @@ mod test {
         }
     }
 
-    macro_rules! test_script {
-        ($ps:expr, $s:expr, $message_checker:expr, $desired_outcome:expr) => {
-            println!(
-                "Winternitz signature size:\n \t{:?} bytes / {:?} bits \n\t{:?} bytes / bit\n",
-                $s.len(),
-                $ps.message_length * $ps.block_length,
-                $s.len() as f64 / ($ps.message_length * $ps.block_length) as f64
+    fn test_script(
+        ps: &Parameters,
+        standard_script: Script,
+        message_checker: Script,
+        desired_outcome: bool,
+    ) {
+        println!(
+            "Winternitz signature size:\n \t{:?} bytes / {:?} bits \n\t{:?} bytes / bit\n",
+            standard_script.len(),
+            ps.message_digit_len * ps.log2_base,
+            standard_script.len() as f64 / (ps.message_digit_len * ps.log2_base) as f64
+        );
+        if desired_outcome == true {
+            assert!(
+                execute_script(standard_script.push_script(message_checker.clone().compile()))
+                    .success
+                    == true
             );
-            if $desired_outcome == true {
-                assert!(
-                    execute_script($s.push_script($message_checker.clone().compile())).success
+        } else {
+            assert!(
+                execute_script(standard_script.clone()).success == false
+                    || execute_script(standard_script.push_script(message_checker.compile()))
+                        .success
                         == true
-                );
-            } else {
-                assert!(
-                    execute_script($s.clone()).success == false
-                        || execute_script($s.push_script($message_checker.clone().compile()))
-                            .success
-                            == true
-                );
-            }
-        };
+            );
+        }
     }
-    macro_rules! generate_winternitz_tests {
-        (
-            $ps:expr, $secret_key:expr, $public_key:expr, $message:expr, $message_checker:expr, $desired_outcome:expr;
-            $([$verifier:ty, $converter:ty]),*
-        ) => {
-            $(
-                {
-                    let o = Winternitz::<$verifier, $converter>::new();
-                    let standard_script = script! {
-                        { o.sign(&$ps, &$secret_key, &$message) }
-                        if $desired_outcome == false {
-                             { try_malicious(&$ps, &$message, &get_type_name::<$verifier>()) }
-                        }
-                        { o.checksig_verify(&$ps, &$public_key) }
-                    };
-
-                    println!("For message_length:{} and block_length:{}  {} with {} =>", $ps.message_length, $ps.block_length, get_type_name::<$verifier>(), get_type_name::<$converter>());
-                    test_script!($ps, standard_script, $message_checker, $desired_outcome);
-                    if $desired_outcome == true {
-                        let message_remove_script = script! {
-                            { o.sign(&$ps, &$secret_key, &$message) }
-                            { o.checksig_verify_and_clear_stack(&$ps, &$public_key) }
-                            OP_TRUE
-                        };
-                        assert!(execute_script(message_remove_script).success == true);
-                    }
-                }
-            )*
+
+    fn run_winternitz_test<VERIFIER: Verifier, CONVERTER: Converter>(
+        ps: &Parameters,
+        secret_key: &SecretKey,
+        public_key: &PublicKey,
+        message: &[u8],
+        message_checker: Script,
+        desired_outcome: bool,
+    ) {
+        let o = Winternitz::<VERIFIER, CONVERTER>::new();
+        let standard_script = script! {
+            { o.sign(ps, secret_key, message) }
+            if desired_outcome == false {
+                 { try_malicious(ps, message, &get_type_name::<VERIFIER>()) }
+            }
+            { o.checksig_verify(ps, &public_key) }
         };
+
+        println!(
+            "For message_digit_len: {} and log2_base: {} {} with {} =>",
+            ps.message_digit_len,
+            ps.log2_base,
+            get_type_name::<VERIFIER>(),
+            get_type_name::<CONVERTER>()
+        );
+        test_script(ps, standard_script, message_checker, desired_outcome);
+        if desired_outcome == true {
+            let message_remove_script = script! {
+                { o.sign(ps, secret_key, message) }
+                { o.checksig_verify_and_clear_stack(ps, public_key) }
+                OP_TRUE
+            };
+            assert!(execute_script(message_remove_script).success == true);
+        }
     }
 
     #[test]
@@ -622,7 +799,7 @@ mod test {
             // convert to number
             { digits_to_number::<8, 4>() }
 
-             { message }
+            { message }
 
             OP_EQUAL
 
@@ -639,46 +816,85 @@ mod test {
         let mut prng = ChaCha20Rng::seed_from_u64(37);
         for _ in 0..TEST_COUNT {
             let ps = Parameters::new(prng.gen_range(1..200), prng.gen_range(4..=8));
-            let message_byte_size = ps.message_length * ps.block_length / 8;
+            let message_byte_size = ps.message_digit_len * ps.log2_base / 8;
             let mut message = vec![0u8; message_byte_size as usize];
-            let mut return_message = vec![0; ps.byte_message_length() as usize];
+            let mut return_message = vec![0; ps.message_byte_len() as usize];
             for i in 0..message_byte_size {
                 message[i as usize] = prng.gen_range(0u8..=255);
                 return_message[i as usize] = message[i as usize];
             }
             let public_key = generate_public_key(&ps, &secret_key);
             let message_checker = script! {
-                for i in 0..ps.byte_message_length() {
+                for i in 0..ps.message_byte_len() {
                     {return_message[i as usize]}
-                    if i == ps.byte_message_length() - 1 {
+                    if i == ps.message_byte_len() - 1 {
                         OP_EQUAL
                     } else {
                         OP_EQUALVERIFY
                     }
                 }
             };
-            generate_winternitz_tests!(
-                ps, secret_key, public_key, message, message_checker, true;
-                [ListpickVerifier, ToBytesConverter],
-                [BruteforceVerifier, ToBytesConverter],
-                [BinarysearchVerifier, ToBytesConverter]
+
+            run_winternitz_test::<ListpickVerifier, ToBytesConverter>(
+                &ps,
+                &secret_key,
+                &public_key,
+                &message,
+                message_checker.clone(),
+                true,
             );
-            let message_digits = bytes_to_u32s(ps.message_length, ps.block_length, &message);
+            run_winternitz_test::<BruteforceVerifier, ToBytesConverter>(
+                &ps,
+                &secret_key,
+                &public_key,
+                &message,
+                message_checker.clone(),
+                true,
+            );
+            run_winternitz_test::<BinarysearchVerifier, ToBytesConverter>(
+                &ps,
+                &secret_key,
+                &public_key,
+                &message,
+                message_checker,
+                true,
+            );
+
+            let message_digits = message_to_digits(ps.message_digit_len, ps.log2_base, &message);
             let void_message_checker = script! {
-                for i in 0..ps.message_length {
+                for i in (0..ps.message_digit_len).rev() {
                     { message_digits[i as usize] }
-                    if i == ps.message_length - 1 {
+                    if i == 0 {
                         OP_EQUAL
                     } else {
                         OP_EQUALVERIFY
                     }
                 }
             };
-            generate_winternitz_tests!(
-                ps, secret_key, public_key, message, void_message_checker, true;
-                [ListpickVerifier, VoidConverter],
-                [BruteforceVerifier, VoidConverter],
-                [BinarysearchVerifier, VoidConverter]
+
+            run_winternitz_test::<ListpickVerifier, VoidConverter>(
+                &ps,
+                &secret_key,
+                &public_key,
+                &message,
+                void_message_checker.clone(),
+                true,
+            );
+            run_winternitz_test::<BruteforceVerifier, VoidConverter>(
+                &ps,
+                &secret_key,
+                &public_key,
+                &message,
+                void_message_checker.clone(),
+                true,
+            );
+            run_winternitz_test::<BinarysearchVerifier, VoidConverter>(
+                &ps,
+                &secret_key,
+                &public_key,
+                &message,
+                void_message_checker,
+                true,
             );
         }
     }
@@ -692,46 +908,85 @@ mod test {
         let mut prng = ChaCha20Rng::seed_from_u64(37);
         for _ in 0..TEST_COUNT {
             let ps = Parameters::new(prng.gen_range(1..200), prng.gen_range(4..=8));
-            let message_byte_size = ps.message_length * ps.block_length / 8;
+            let message_byte_size = ps.message_digit_len * ps.log2_base / 8;
             let mut message = vec![0u8; message_byte_size as usize];
-            let mut return_message = vec![0; ps.byte_message_length() as usize];
+            let mut return_message = vec![0; ps.message_byte_len() as usize];
             for i in 0..message_byte_size {
                 message[i as usize] = prng.gen_range(0u8..=255);
                 return_message[i as usize] = message[i as usize];
             }
             let public_key = generate_public_key(&ps, &secret_key);
             let message_checker = script! {
-                for i in 0..ps.byte_message_length() {
+                for i in 0..ps.message_byte_len() {
                     {return_message[i as usize]}
-                    if i == ps.byte_message_length() - 1 {
+                    if i == ps.message_byte_len() - 1 {
                         OP_EQUAL
                     } else {
                         OP_EQUALVERIFY
                     }
                 }
             };
-            generate_winternitz_tests!(
-                ps, secret_key, public_key, message, message_checker, false;
-                [ListpickVerifier, ToBytesConverter],
-                [BruteforceVerifier, ToBytesConverter],
-                [BinarysearchVerifier, ToBytesConverter]
+
+            run_winternitz_test::<ListpickVerifier, ToBytesConverter>(
+                &ps,
+                &secret_key,
+                &public_key,
+                &message,
+                message_checker.clone(),
+                false,
             );
-            let message_digits = bytes_to_u32s(ps.message_length, ps.block_length, &message);
+            run_winternitz_test::<BruteforceVerifier, ToBytesConverter>(
+                &ps,
+                &secret_key,
+                &public_key,
+                &message,
+                message_checker.clone(),
+                false,
+            );
+            run_winternitz_test::<BinarysearchVerifier, ToBytesConverter>(
+                &ps,
+                &secret_key,
+                &public_key,
+                &message,
+                message_checker,
+                false,
+            );
+
+            let message_digits = message_to_digits(ps.message_digit_len, ps.log2_base, &message);
             let void_message_checker = script! {
-                for i in 0..ps.message_length {
+                for i in (0..ps.message_digit_len).rev() {
                     { message_digits[i as usize] }
-                    if i == ps.message_length - 1 {
+                    if i == 0 {
                         OP_EQUAL
                     } else {
                         OP_EQUALVERIFY
                     }
                 }
             };
-            generate_winternitz_tests!(
-                ps, secret_key, public_key, message, void_message_checker, false;
-                [ListpickVerifier, VoidConverter],
-                [BruteforceVerifier, VoidConverter],
-                [BinarysearchVerifier, VoidConverter]
+
+            run_winternitz_test::<ListpickVerifier, VoidConverter>(
+                &ps,
+                &secret_key,
+                &public_key,
+                &message,
+                void_message_checker.clone(),
+                false,
+            );
+            run_winternitz_test::<BruteforceVerifier, VoidConverter>(
+                &ps,
+                &secret_key,
+                &public_key,
+                &message,
+                void_message_checker.clone(),
+                false,
+            );
+            run_winternitz_test::<BinarysearchVerifier, VoidConverter>(
+                &ps,
+                &secret_key,
+                &public_key,
+                &message,
+                void_message_checker,
+                false,
             );
         }
     }
diff --git a/bitvm/src/signatures/winternitz_hash.rs b/bitvm/src/signatures/winternitz_hash.rs
index 2705d99f..0f58148e 100644
--- a/bitvm/src/signatures/winternitz_hash.rs
+++ b/bitvm/src/signatures/winternitz_hash.rs
@@ -3,24 +3,25 @@ use bitcoin::Witness;
 use blake3::hash;
 
 const MESSAGE_HASH_LEN: u32 = 20;
-/// Winternitz parameters for the 20 byte blake3 variant with the block length 4
-pub static WINTERNITZ_HASH_PARAMETERS: Parameters =
+
+/// Winternitz parameters for the 20 byte blake3 variant
+pub const WINTERNITZ_HASH_PARAMETERS: Parameters =
     Parameters::new_by_bit_length(MESSAGE_HASH_LEN * 8, 4);
 
 /// Winternitz verifier for the 20 byte blake3 variant (can be used with other parameters), returns the message in bytes
-pub static WINTERNITZ_HASH_VERIFIER: Winternitz<ListpickVerifier, ToBytesConverter> =
+pub const WINTERNITZ_HASH_VERIFIER: Winternitz<ListpickVerifier, ToBytesConverter> =
     Winternitz::new();
 
-/// Winternitz verifier, returns the message in blocks
-pub static WINTERNITZ_MESSAGE_VERIFIER: Winternitz<ListpickVerifier, VoidConverter> =
+/// Winternitz verifier, returns the message in digits
+pub const WINTERNITZ_MESSAGE_VERIFIER: Winternitz<ListpickVerifier, VoidConverter> =
     Winternitz::new();
 
-/// Winternitz verifier, returns the message in in bytes
-pub static WINTERNITZ_VARIABLE_VERIFIER: Winternitz<ListpickVerifier, ToBytesConverter> =
+/// Winternitz verifier, returns the message in bytes
+pub const WINTERNITZ_VARIABLE_VERIFIER: Winternitz<ListpickVerifier, ToBytesConverter> =
     Winternitz::new();
 
-/// Winternitz verifier for compact signature representation, returns the message in in bytes
-pub static WINTERNITZ_MESSAGE_COMPACT_VERIFIER: Winternitz<BruteforceVerifier, VoidConverter> =
+/// Winternitz verifier for compact signature representation, returns the message in bytes
+pub const WINTERNITZ_MESSAGE_COMPACT_VERIFIER: Winternitz<BruteforceVerifier, VoidConverter> =
     Winternitz::new();
 
 /// Create a Winternitz signature for the blake3 hash of a given message
diff --git a/bitvm/src/signatures/wots_api.rs b/bitvm/src/signatures/wots_api.rs
index 1cb176b0..ee9e1255 100644
--- a/bitvm/src/signatures/wots_api.rs
+++ b/bitvm/src/signatures/wots_api.rs
@@ -5,7 +5,6 @@ use crate::signatures::{
 };
 use crate::treepp::Script;
 use bitcoin::Witness;
-use paste::paste;
 
 /// Trait for converting a signature into a Script.
 pub trait SignatureImpl {
@@ -19,157 +18,152 @@ pub trait SignatureImpl {
 /// - For 256-bit WOTS, use 32 bytes.
 macro_rules! impl_wots {
     ($mod_name:ident, $MSG_LEN:expr) => {
-        paste! {
-            pub mod $mod_name {
-                use super::*;
-                use bitcoin_script::script;
-                use bitcoin::hex::FromHex;
-
-                /// Message length in bytes.
-                pub const MSG_LEN: u32 = $MSG_LEN;
-                /// Necessary parameters for the algorithm
-                pub const PS: winternitz::Parameters = winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
-                /// Total number of "digits" in the signature.
-                pub const N_DIGITS: u32 = PS.total_length();
-
-                /// Public key is an array of 20-byte arrays.
-                pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
-                /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
-                pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
-
-                impl SignatureImpl for Signature {
-                    fn to_script(self) -> Script {
-                        script! {
-                            for (preimage, digit) in self {
-                                { preimage.to_vec() }
-                                { digit }
-                            }
+        pub mod $mod_name {
+            use super::*;
+            use bitcoin::hex::FromHex;
+            use bitcoin_script::script;
+
+            /// Message length in bytes.
+            pub const MSG_LEN: u32 = $MSG_LEN;
+            /// Necessary parameters for the algorithm
+            pub const PS: winternitz::Parameters =
+                winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
+            /// Total number of "digits" in the signature.
+            pub const N_DIGITS: u32 = PS.total_digit_len();
+
+            /// Public key is an array of 20-byte arrays.
+            pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
+            /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
+            pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
+
+            impl SignatureImpl for Signature {
+                fn to_script(self) -> Script {
+                    script! {
+                        for (preimage, digit) in self {
+                            { preimage.to_vec() }
+                            { digit }
                         }
                     }
+                }
 
-                    fn to_compact_script(self) -> Script {
-                        script! {
-                            for (preimage, _) in self {
-                                { preimage.to_vec() }
-                            }
+                fn to_compact_script(self) -> Script {
+                    script! {
+                        for (preimage, _) in self {
+                            { preimage.to_vec() }
                         }
                     }
                 }
+            }
+
+            /// Creates a verification script for a WOTS `public_key`.
+            pub fn checksig_verify(public_key: PublicKey) -> Script {
+                WINTERNITZ_MESSAGE_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
+            }
+
+            /// Changes the format of the `signature`, from bitcoin witness to array.
+            pub fn raw_witness_to_signature(signature: &Witness) -> Signature {
+                // Iterate over the signature pieces two at a time.
+                let mut sigs_vec: Vec<([u8; 20], u8)> = Vec::new();
+                for i in (0..signature.len()).step_by(2) {
+                    let preimage: [u8; 20] = if signature[i].len() == 0 {
+                        [0; 20]
+                    } else {
+                        signature[i].try_into().unwrap()
+                    };
+                    let digit_arr: [u8; 1] = if signature[i + 1].len() == 0 {
+                        [0]
+                    } else {
+                        signature[i + 1].try_into().unwrap()
+                    };
+                    sigs_vec.push((preimage, digit_arr[0]));
+                }
+                sigs_vec.try_into().unwrap()
+            }
+
+            /// Changes the format of the `signature`, from array to bitcoin witness
+            pub fn signature_to_raw_witness(signature: &Signature) -> Witness {
+                let mut w = Witness::new();
+                for (h, digit) in signature.iter() {
+                    w.push(h.to_vec());
+                    w.push(u32_to_le_bytes_minimal(*digit as u32));
+                }
+                w
+            }
+
+            /// Generates a signature for a `message` using the provided `secret`.
+            pub fn get_signature(secret: &str, message: &[u8]) -> Signature {
+                let secret_key = match Vec::<u8>::from_hex(secret) {
+                    Ok(bytes) => bytes,
+                    Err(_) => panic!("Invalid hex string for secret"),
+                };
 
-                /// Create a verification script for a WOTS public key.
+                let sigs = WINTERNITZ_MESSAGE_VERIFIER.sign(&PS, &secret_key, &message.to_vec());
+                assert_eq!(sigs.len(), 2 * N_DIGITS as usize);
+                raw_witness_to_signature(&sigs)
+            }
+
+            /// Generates a WOTS public key using the provided `secret`.
+            pub fn generate_public_key(secret: &str) -> PublicKey {
+                let secret_key = match Vec::<u8>::from_hex(secret) {
+                    Ok(bytes) => bytes,
+                    Err(_) => panic!("Invalid hex string for secret"),
+                };
+                let pubkey_vec = winternitz::generate_public_key(&PS, &secret_key);
+                pubkey_vec.try_into().unwrap()
+            }
+
+            /// A sub-module for the compact signature variant.
+            pub mod compact {
+                use super::*;
+
+                /// The compact signature is just the 20-byte preimages.
+                pub type Signature = [[u8; 20]; N_DIGITS as usize];
+
+                /// Creates a verification script for the compact WOTS `public_key`.
                 pub fn checksig_verify(public_key: PublicKey) -> Script {
-                    WINTERNITZ_MESSAGE_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
+                    WINTERNITZ_MESSAGE_COMPACT_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
                 }
 
-                /// Changes the format of the signature, from bitcoin witness to array
-                pub fn raw_witness_to_signature(sigs: &Witness) -> Signature {
+                /// Changes the format of the `signature`, from bitcoin witness to array.
+                pub fn raw_witness_to_signature(signature: &Witness) -> Signature {
                     // Iterate over the signature pieces two at a time.
-                    let mut sigs_vec: Vec<([u8; 20], u8)> = Vec::new();
-                    for i in (0..sigs.len()).step_by(2) {
-                        let preimage: [u8; 20] = if sigs[i].len() == 0 {
+                    let mut sigs_vec: Vec<[u8; 20]> = Vec::new();
+                    // Iterate over the signature pieces using step_by.
+                    for i in 0..signature.len() {
+                        let preimage: [u8; 20] = if signature[i].len() == 0 {
                             [0; 20]
                         } else {
-                            sigs[i].try_into().unwrap()
-                        };
-                        let digit_arr: [u8; 1] = if sigs[i + 1].len() == 0 {
-                            [0]
-                        } else {
-                            sigs[i + 1].try_into().unwrap()
+                            signature[i].try_into().unwrap()
                         };
-                        sigs_vec.push((preimage, digit_arr[0]));
+                        sigs_vec.push(preimage);
                     }
                     sigs_vec.try_into().unwrap()
                 }
 
-                /// Changes the format of the signature, from array to bitcoin witness
-                pub fn signature_to_raw_witness(sigs: &Signature) -> Witness {
+                /// Changes the format of the `signature`, from array to bitcoin witness.
+                pub fn signature_to_raw_witness(signature: &Signature) -> Witness {
                     let mut w = Witness::new();
-                    for (h, digit) in sigs.iter() {
+                    for h in signature.iter() {
                         w.push(h.to_vec());
-                        w.push(u32_to_le_bytes_minimal(*digit as u32));
                     }
                     w
                 }
 
-                /// Generate a signature for a message using the provided secret.
-                pub fn get_signature(secret: &str, msg_bytes: &[u8]) -> Signature {
+                /// Generates a compact signature for a `message`.
+                pub fn get_signature(secret: &str, message: &[u8]) -> Signature {
                     let secret_key = match Vec::<u8>::from_hex(secret) {
                         Ok(bytes) => bytes,
                         Err(_) => panic!("Invalid hex string for secret"),
                     };
 
-                    let sigs = WINTERNITZ_MESSAGE_VERIFIER.sign(
+                    let sigs = WINTERNITZ_MESSAGE_COMPACT_VERIFIER.sign(
                         &PS,
                         &secret_key,
-                        &msg_bytes.to_vec(),
+                        &message.to_vec(),
                     );
-                    assert_eq!(sigs.len(), 2 * N_DIGITS as usize);
+                    assert_eq!(sigs.len(), N_DIGITS as usize);
                     raw_witness_to_signature(&sigs)
                 }
-
-                /// Generate a WOTS public key using the provided secret.
-                pub fn generate_public_key(secret: &str) -> PublicKey {
-                    let secret_key = match Vec::<u8>::from_hex(secret) {
-                        Ok(bytes) => bytes,
-                        Err(_) => panic!("Invalid hex string for secret"),
-                    };
-                    let pubkey_vec = winternitz::generate_public_key(&PS, &secret_key);
-                    pubkey_vec.try_into().unwrap()
-                }
-
-                /// A sub-module for the compact signature variant.
-                pub mod compact {
-                    use super::*;
-
-                    /// The compact signature is just the 20-byte preimages.
-                    pub type Signature = [[u8; 20]; N_DIGITS as usize];
-
-                    /// Create a verification script for the compact WOTS public key.
-                    pub fn checksig_verify(public_key: PublicKey) -> Script {
-                        WINTERNITZ_MESSAGE_COMPACT_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
-                    }
-
-                    /// Changes the format of the signature, from bitcoin witness to array
-                    pub fn raw_witness_to_signature(sigs: &Witness) -> Signature {
-                        // Iterate over the signature pieces two at a time.
-                        let mut sigs_vec: Vec<[u8; 20]> = Vec::new();
-                        // Iterate over the signature pieces using step_by.
-                        for i in 0..sigs.len() {
-                            let preimage: [u8; 20] = if sigs[i].len() == 0 {
-                                [0; 20]
-                            } else {
-                                sigs[i].try_into().unwrap()
-                            };
-                            sigs_vec.push(preimage);
-                        }
-                        sigs_vec.try_into().unwrap()
-                    }
-
-                    /// Changes the format of the signature, from array to bitcoin witness
-                    pub fn signature_to_raw_witness(sigs: &Signature) -> Witness {
-                        let mut w = Witness::new();
-                        for h in sigs.iter() {
-                            w.push(h.to_vec());
-                        }
-                        w
-                    }
-
-                    /// Generate a compact signature for a message.
-                    pub fn get_signature(secret: &str, msg_bytes: &[u8]) -> Signature {
-                        let secret_key = match Vec::<u8>::from_hex(secret) {
-                            Ok(bytes) => bytes,
-                            Err(_) => panic!("Invalid hex string for secret"),
-                        };
-
-                        let sigs = WINTERNITZ_MESSAGE_COMPACT_VERIFIER.sign(
-                            &PS,
-                            &secret_key,
-                            &msg_bytes.to_vec(),
-                        );
-                        assert_eq!(sigs.len(), N_DIGITS as usize);
-                        raw_witness_to_signature(&sigs)
-                    }
-                }
             }
         }
     };
@@ -177,6 +171,5 @@ macro_rules! impl_wots {
 const BIGINT_LEN: u32 = 32;
 pub const HASH_LEN: u32 = 16; // bytes, can lower it to value like 16 or 13 lesser acceptable security
 
-// Expand the macro for the two variants.
 impl_wots!(wots_hash, HASH_LEN);
 impl_wots!(wots256, BIGINT_LEN);


Reviews:
user: login: AaronFeickert
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T23:10:00Z
user: login: AaronFeickert
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T23:10:38Z
user: login: uncomputable
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T08:55:57Z
user: login: storopoli
body: I'd love to see also some JSON test vectors somewhere so that we can have an official agnostic test vectors for BitVM WOTS API.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T13:00:37Z
user: login: uncomputable
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T13:51:38Z
user: login: uncomputable
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T16:03:50Z
user: login: uncomputable
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T16:05:36Z
user: login: uncomputable
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T16:11:23Z
user: login: storopoli
body: ACK ef11503e9d22d4b63c834c213f68a4604c69dbbb
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T16:24:24Z
user: login: AaronFeickert
body: As noted elsewhere, this PR would be a good opportunity to add JSON test vectors to aid other implementations.

Edit: Sounds like test vectors are planned as part of a follow-up PR.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T20:07:37Z

Review comments:
diff_hunk: @@ -11,64 +11,95 @@ pub(super) const fn log_base_ceil(n: u32, base: u32) -> u32 {
     res
 }
 
-/// Converts the number to given base, smallest digit being at the start (returns `digit_count` smallest digits of it, and standard base representation of it if `digit_count` = -1)
-pub(super) fn to_digits(mut number: u32, base: u32, digit_count: i32) -> Vec<u32> {
-    let mut digits = Vec::new();
-    if digit_count == -1 {
-        while number > 0 {
-            let digit = number % base;
-            number = (number - digit) / base;
-            digits.push(digit);
-        }
-    } else {
-        digits.reserve(digit_count as usize);
-        for _ in 0..digit_count {
-            let digit = number % base;
-            number = (number - digit) / base;
-            digits.push(digit);
-        }
+/// Converts the given `checksum` into a vector of digits.
+///
+/// There are `n_digits` many digits.
+/// Each digit is a `u32` value in the range `0..base`.
+/// Digits are in big endian order.
+///
+/// ## Panics
path: bitvm/src/signatures/utils.rs
user: login: AaronFeickert
body: Not in release mode.
created_at: 2025-04-30T23:10:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,64 +11,95 @@ pub(super) const fn log_base_ceil(n: u32, base: u32) -> u32 {
     res
 }
 
-/// Converts the number to given base, smallest digit being at the start (returns `digit_count` smallest digits of it, and standard base representation of it if `digit_count` = -1)
-pub(super) fn to_digits(mut number: u32, base: u32, digit_count: i32) -> Vec<u32> {
-    let mut digits = Vec::new();
-    if digit_count == -1 {
-        while number > 0 {
-            let digit = number % base;
-            number = (number - digit) / base;
-            digits.push(digit);
-        }
-    } else {
-        digits.reserve(digit_count as usize);
-        for _ in 0..digit_count {
-            let digit = number % base;
-            number = (number - digit) / base;
-            digits.push(digit);
-        }
+/// Converts the given `checksum` into a vector of digits.
+///
+/// There are `n_digits` many digits.
+/// Each digit is a `u32` value in the range `0..base`.
+/// Digits are in big endian order.
+///
+/// ## Panics
+///
+/// Checksum is larger than or equal to `base.pow(n_digits)`.
+pub(super) fn checksum_to_digits(mut checksum: u32, base: u32, n_digits: u32) -> Vec<u32> {
+    debug_assert!(
+        base.checked_pow(n_digits)
+            .map(|upper_limit| checksum < upper_limit)
+            .unwrap_or(true),
+        "Checksum is too large to fit into the given number of digits"
+    );
+
+    let mut digits = vec![0; n_digits as usize]; // cast safety: 32-bit machine or higher
+
+    for digit in digits.iter_mut().rev() {
+        *digit = checksum % base;
+        checksum = (checksum - *digit) / base;
     }
+
     digits
 }
 
-/// Converts the given bytes to 'len' `u32`'s, each consisting of given number of bits
-pub(crate) fn bytes_to_u32s(len: u32, bits_per_item: u32, bytes: &[u8]) -> Vec<u32> {
-    assert!(
-        bytes.len() as u32 * 8 <= len * bits_per_item,
-        "Message length is too large for the given length"
+/// Converts the given `message` into a vector of digits.
+///
+/// There are `n_digits` many digits.
+/// Each digit is a `u32` value in the range `0..2.pow(log2_base)`.
+/// Digits are in big endian order.
+///
+/// ## Panics
path: bitvm/src/signatures/utils.rs
user: login: AaronFeickert
body: Not in release mode.
created_at: 2025-04-30T23:10:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,64 +11,95 @@ pub(super) const fn log_base_ceil(n: u32, base: u32) -> u32 {
     res
 }
 
-/// Converts the number to given base, smallest digit being at the start (returns `digit_count` smallest digits of it, and standard base representation of it if `digit_count` = -1)
-pub(super) fn to_digits(mut number: u32, base: u32, digit_count: i32) -> Vec<u32> {
-    let mut digits = Vec::new();
-    if digit_count == -1 {
-        while number > 0 {
-            let digit = number % base;
-            number = (number - digit) / base;
-            digits.push(digit);
-        }
-    } else {
-        digits.reserve(digit_count as usize);
-        for _ in 0..digit_count {
-            let digit = number % base;
-            number = (number - digit) / base;
-            digits.push(digit);
-        }
+/// Converts the given `checksum` into a vector of digits.
+///
+/// There are `n_digits` many digits.
+/// Each digit is a `u32` value in the range `0..base`.
+/// Digits are in big endian order.
+///
+/// ## Panics
path: bitvm/src/signatures/utils.rs
user: login: uncomputable
body: I changed an `assert!` into a `debug_assert!`. Thanks for the headsup.
created_at: 2025-05-01T08:55:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -91,4 +118,65 @@ mod test {
 
         assert_eq!(a_bytes, vec![0x00u8, 0xfeu8]);
     }
+
+    #[test]
+    fn checksum_to_digits_endianness() {
+        // Integer is encoded as BE digit sequence
+        assert_eq!(
+            checksum_to_digits(0x12345678, 16, 8),
+            vec![1, 2, 3, 4, 5, 6, 7, 8],
+        );
+    }
+
+    #[test]
+    fn message_to_digits_endianness() {
+        let message: u32 = 0x12345678;
+        // Bytes are reversed from BE to LE
+        // Digits stay BE
+        // The result is a weird mix of endianness
+        assert_eq!(
+            message_to_digits(8, 4, &message.to_be_bytes()),
+            vec![7, 8, 5, 6, 3, 4, 1, 2],
+        );
+        // Bytes are reversed from LE to BE
+        // Digits stay BE
+        // The result is a BE sequence
+        assert_eq!(
+            message_to_digits(8, 4, &message.to_le_bytes()),
+            vec![1, 2, 3, 4, 5, 6, 7, 8],
+        );
+    }
+
+    #[test]
+    fn digits_to_number_endianness() {
+        // LE input is glued together as LE
+        let script = script! {
+            { vec![0x07, 0x08, 0x05, 0x06, 0x03, 0x04, 0x01, 0x02] }
+            { digits_to_number::<8, 4>() }
+            { 0x78563412 }
+            OP_EQUAL
+        };
+        run(script);
+        // BE input is glued together as BE
+        let script = script! {
+            { vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] }
+            { digits_to_number::<8, 4>() }
+            { 0x12345678 }
+            OP_EQUAL
+        };
+        run(script);
+    }
+
+    #[test]
+    fn message_digits_roundtrip() {
+        let message: u32 = 0x12345678;
+        let digits = message_to_digits(8, 4, &message.to_le_bytes());
+        let script = script! {
+            { digits }
+            { digits_to_number::<8, 4>() }
+            { message }
+            OP_EQUAL
+        };
+        run(script);
+    }
path: bitvm/src/signatures/utils.rs
user: login: storopoli
body: Not in the scope of this PR, but this could be a property testing with [`proptest`](https://lib.rs/crates/proptest) and adding it to the `dev-dependencies` (the default strategy is `1_000` cases which is fine for the tests in `signatures/utils.rs`)
created_at: 2025-05-02T12:46:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,68 +11,61 @@ type HashOut = [u8; 20];
 pub type PublicKey = Vec<HashOut>;
 pub type SecretKey = Vec<u8>;
 
-/// Contains the parameters to use with `Winternitz` struct
-#[derive(Serialize, Deserialize, Eq, PartialEq, Hash, Clone)]
+/// Parameters for the [`Winternitz`] struct.
+#[derive(Serialize, Deserialize, Eq, PartialEq, Hash, Clone, Copy)]
 pub struct Parameters {
-    /// Number of blocks of the actual message
-    message_length: u32,
-    /// Number of bits in one block
-    block_length: u32,
-    /// Number of blocks of the checksum part
-    checksum_length: u32,
+    /// Number of digits per message (including zero padding at the end).
+    message_digit_len: u32,
+    /// Number of bits per digit.
+    log2_base: u32,
+    /// Number of digits per checksum.
+    checksum_digit_len: u32,
 }
 
 impl Parameters {
-    /// Creates parameters with given message length (number of blocks in the message) and block length (number of bits in one block, in the closed range 4, 8)
-    pub const fn new(message_block_count: u32, block_length: u32) -> Self {
+    /// Creates parameters for messages of the given number of digits of the given base.
+    ///
+    /// The base must be in the range `16..=256`.
+    pub const fn new(message_digit_len: u32, log2_base: u32) -> Self {
         assert!(
-            4 <= block_length && block_length <= 8,
-            "You can only choose block lengths in the range [4, 8]"
+            4 <= log2_base && log2_base <= 8,
+            "log2_base must be in the range 4..=8"
path: bitvm/src/signatures/winternitz.rs
user: login: storopoli
body: This is not accurate description in the docstrings with respect to the base. If you follow the instructions and input 256 you get a panic. The docstring should be like:

```
/// The base must be in the range `16..=256` in log2, i.e. `4..=8`.
```

created_at: 2025-05-02T12:49:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,68 +11,61 @@ type HashOut = [u8; 20];
 pub type PublicKey = Vec<HashOut>;
 pub type SecretKey = Vec<u8>;
 
-/// Contains the parameters to use with `Winternitz` struct
-#[derive(Serialize, Deserialize, Eq, PartialEq, Hash, Clone)]
+/// Parameters for the [`Winternitz`] struct.
+#[derive(Serialize, Deserialize, Eq, PartialEq, Hash, Clone, Copy)]
 pub struct Parameters {
-    /// Number of blocks of the actual message
-    message_length: u32,
-    /// Number of bits in one block
-    block_length: u32,
-    /// Number of blocks of the checksum part
-    checksum_length: u32,
+    /// Number of digits per message (including zero padding at the end).
+    message_digit_len: u32,
+    /// Number of bits per digit.
+    log2_base: u32,
+    /// Number of digits per checksum.
+    checksum_digit_len: u32,
 }
 
 impl Parameters {
-    /// Creates parameters with given message length (number of blocks in the message) and block length (number of bits in one block, in the closed range 4, 8)
-    pub const fn new(message_block_count: u32, block_length: u32) -> Self {
+    /// Creates parameters for messages of the given number of digits of the given base.
+    ///
+    /// The base must be in the range `16..=256`.
+    pub const fn new(message_digit_len: u32, log2_base: u32) -> Self {
         assert!(
-            4 <= block_length && block_length <= 8,
-            "You can only choose block lengths in the range [4, 8]"
+            4 <= log2_base && log2_base <= 8,
+            "log2_base must be in the range 4..=8"
         );
         Parameters {
-            message_length: message_block_count,
-            block_length,
-            checksum_length: log_base_ceil(
-                ((1 << block_length) - 1) * message_block_count,
-                1 << block_length,
+            message_digit_len,
+            log2_base,
+            checksum_digit_len: log_base_ceil(
+                ((1 << log2_base) - 1) * message_digit_len,
+                1 << log2_base,
             ) + 1,
         }
     }
 
-    /// Creates parameters with given message_length (number of bits in the message) and block length (number of bits in one block, in the closed range 4, 8)
-    pub const fn new_by_bit_length(number_of_bits: u32, block_length: u32) -> Self {
-        assert!(
-            4 <= block_length && block_length <= 8,
-            "You can only choose block lengths in the range [4, 8]"
-        );
-        let message_block_count = number_of_bits.div_ceil(block_length);
-        Parameters {
-            message_length: message_block_count,
-            block_length,
-            checksum_length: log_base_ceil(
-                ((1 << block_length) - 1) * message_block_count,
-                1 << block_length,
-            ) + 1,
-        }
+    /// Creates parameters for messages of the given bit length and for digits of the given base.
+    ///
+    /// The base must be in the range `16..=256`.
path: bitvm/src/signatures/winternitz.rs
user: login: storopoli
body: same as above
created_at: 2025-05-02T12:49:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -83,70 +76,105 @@ pub fn digit_signature(secret_key: &SecretKey, digit_index: u32, message_digit:
     *hash.as_byte_array()
 }
 
-/// Returns the public key of a given digit (block), requires the digit index to modify the secret key for each digit
+/// Returns the public key of a given digit, requires the digit index to modify the secret key for each digit
 fn public_key_for_digit(ps: &Parameters, secret_key: &SecretKey, digit_index: u32) -> HashOut {
-    digit_signature(secret_key, digit_index, ps.d())
+    digit_signature(secret_key, digit_index, ps.max_digit())
 }
 
 /// Returns the public key for the given secret key and the parameters
 pub fn generate_public_key(ps: &Parameters, secret_key: &SecretKey) -> PublicKey {
-    let mut public_key = PublicKey::with_capacity(ps.total_length() as usize);
-    for i in 0..ps.total_length() {
+    let mut public_key = PublicKey::with_capacity(ps.total_digit_len() as usize);
+    for i in 0..ps.total_digit_len() {
         public_key.push(public_key_for_digit(ps, secret_key, i));
     }
     public_key
 }
 
 /// Checksum of the message (negated sum of the digits)
path: bitvm/src/signatures/winternitz.rs
user: login: storopoli
body: what is "negated sum of the digits"? Can we update this docstring as well? Also add a `.` dot in the end like all other docstrings
created_at: 2025-05-02T12:50:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -83,70 +76,105 @@ pub fn digit_signature(secret_key: &SecretKey, digit_index: u32, message_digit:
     *hash.as_byte_array()
 }
 
-/// Returns the public key of a given digit (block), requires the digit index to modify the secret key for each digit
+/// Returns the public key of a given digit, requires the digit index to modify the secret key for each digit
 fn public_key_for_digit(ps: &Parameters, secret_key: &SecretKey, digit_index: u32) -> HashOut {
-    digit_signature(secret_key, digit_index, ps.d())
+    digit_signature(secret_key, digit_index, ps.max_digit())
 }
 
 /// Returns the public key for the given secret key and the parameters
 pub fn generate_public_key(ps: &Parameters, secret_key: &SecretKey) -> PublicKey {
-    let mut public_key = PublicKey::with_capacity(ps.total_length() as usize);
-    for i in 0..ps.total_length() {
+    let mut public_key = PublicKey::with_capacity(ps.total_digit_len() as usize);
+    for i in 0..ps.total_digit_len() {
         public_key.push(public_key_for_digit(ps, secret_key, i));
     }
     public_key
 }
 
 /// Checksum of the message (negated sum of the digits)
-fn checksum(ps: &Parameters, digits: Vec<u32>) -> u32 {
-    let mut sum = 0;
-    for digit in digits {
-        sum += digit;
-    }
-    ps.d() * ps.message_length - sum
+fn checksum(ps: &Parameters, message_digits: &[u32]) -> u32 {
+    debug_assert_eq!(message_digits.len(), ps.message_digit_len as usize);
+
+    let sum: u32 = message_digits.iter().sum();
+    ps.max_digit() * ps.message_digit_len - sum
 }
 
-/// Appends checksum to the given message (digits), and reverses the whole message (with checksum) for the ease of usage in the bitcoin script
-fn add_message_checksum(ps: &Parameters, mut digits: Vec<u32>) -> Vec<u32> {
-    let mut checksum_digits = to_digits(
-        checksum(ps, digits.clone()),
-        ps.d() + 1,
-        ps.checksum_length as i32,
+/// Appends the checksum to the end of the message.
+fn add_message_checksum(ps: &Parameters, mut message_digits: Vec<u32>) -> Vec<u32> {
+    debug_assert_eq!(message_digits.len(), ps.message_digit_len as usize);
+
+    let checksum_digits = checksum_to_digits(
+        checksum(ps, &message_digits),
+        ps.max_digit() + 1,
+        ps.checksum_digit_len,
     );
-    checksum_digits.append(&mut digits);
-    checksum_digits.reverse();
-    checksum_digits
+    message_digits.extend(checksum_digits);
+    message_digits
 }
 
-/// This trait covers 3 verifiers for the signing and verifying phase of the signatures: `ListpickVerifier`, `BruteforceVerifier`, `BinarysearchVerifier`
+/// Trait for creating and for verifying Winternitz signatures.
path: bitvm/src/signatures/winternitz.rs
user: login: storopoli
body: I mean it is a trait... Do we need do add "Trait" in the docstring? It's like adding "Function" in all functions' docstrings...
created_at: 2025-05-02T12:51:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -83,70 +76,105 @@ pub fn digit_signature(secret_key: &SecretKey, digit_index: u32, message_digit:
     *hash.as_byte_array()
 }
 
-/// Returns the public key of a given digit (block), requires the digit index to modify the secret key for each digit
+/// Returns the public key of a given digit, requires the digit index to modify the secret key for each digit
 fn public_key_for_digit(ps: &Parameters, secret_key: &SecretKey, digit_index: u32) -> HashOut {
-    digit_signature(secret_key, digit_index, ps.d())
+    digit_signature(secret_key, digit_index, ps.max_digit())
 }
 
 /// Returns the public key for the given secret key and the parameters
 pub fn generate_public_key(ps: &Parameters, secret_key: &SecretKey) -> PublicKey {
-    let mut public_key = PublicKey::with_capacity(ps.total_length() as usize);
-    for i in 0..ps.total_length() {
+    let mut public_key = PublicKey::with_capacity(ps.total_digit_len() as usize);
+    for i in 0..ps.total_digit_len() {
         public_key.push(public_key_for_digit(ps, secret_key, i));
     }
     public_key
 }
 
 /// Checksum of the message (negated sum of the digits)
-fn checksum(ps: &Parameters, digits: Vec<u32>) -> u32 {
-    let mut sum = 0;
-    for digit in digits {
-        sum += digit;
-    }
-    ps.d() * ps.message_length - sum
+fn checksum(ps: &Parameters, message_digits: &[u32]) -> u32 {
+    debug_assert_eq!(message_digits.len(), ps.message_digit_len as usize);
+
+    let sum: u32 = message_digits.iter().sum();
+    ps.max_digit() * ps.message_digit_len - sum
 }
 
-/// Appends checksum to the given message (digits), and reverses the whole message (with checksum) for the ease of usage in the bitcoin script
-fn add_message_checksum(ps: &Parameters, mut digits: Vec<u32>) -> Vec<u32> {
-    let mut checksum_digits = to_digits(
-        checksum(ps, digits.clone()),
-        ps.d() + 1,
-        ps.checksum_length as i32,
+/// Appends the checksum to the end of the message.
+fn add_message_checksum(ps: &Parameters, mut message_digits: Vec<u32>) -> Vec<u32> {
+    debug_assert_eq!(message_digits.len(), ps.message_digit_len as usize);
+
+    let checksum_digits = checksum_to_digits(
+        checksum(ps, &message_digits),
+        ps.max_digit() + 1,
+        ps.checksum_digit_len,
     );
-    checksum_digits.append(&mut digits);
-    checksum_digits.reverse();
-    checksum_digits
+    message_digits.extend(checksum_digits);
+    message_digits
 }
 
-/// This trait covers 3 verifiers for the signing and verifying phase of the signatures: `ListpickVerifier`, `BruteforceVerifier`, `BinarysearchVerifier`
+/// Trait for creating and for verifying Winternitz signatures.
 pub trait Verifier {
-    /// Default digit signatures for `ListpickVerifier` and `BinarysearchVerifier`, in the format: hash_{n - 1}, digit_{n - 1}, hash_{n - 2}, digit_{n - 2} ... hash_0, digit_0
-    fn sign_digits(ps: &Parameters, secret_key: &SecretKey, digits: Vec<u32>) -> Witness {
-        let digits = add_message_checksum(ps, digits);
+    /// Creates a Winternitz signature for the given `secret_key` and `digits`.
+    ///
+    /// ## Output format
+    ///
+    /// - `hash(digits[0])`
+    /// - `digits[0]`
+    /// - `hash(digits[1])`
+    /// - `digits[1]`
+    /// - ...
+    /// - `hash(digits[n + m - 1])`
+    /// - `digits[n + m - 1]`
+    ///
+    /// There are `n` message digits followed by `m` checksum digits.
+    /// The checksum is computed internally.
+    fn sign_digits(ps: &Parameters, secret_key: &SecretKey, message_digits: Vec<u32>) -> Witness {
+        let digits = add_message_checksum(ps, message_digits);
         let mut result = Witness::new();
-        for i in 0..ps.total_length() {
+        for i in 0..ps.total_digit_len() {
             let sig = digit_signature(secret_key, i, digits[i as usize]);
             result.push(sig);
             result.push(u32_to_le_bytes_minimal(digits[i as usize]));
         }
         result
     }
+
+    /// Returns a Bitcoin script that verifies a Winternitz signature for the given `public_key`.
+    ///
+    /// The checksum is verified by a separate Bitcoin script.
+    ///
+    /// ## Precondition
+    ///
+    /// - `hash(digits[0])`
+    /// - `digits[0]`
+    /// - `hash(digits[1])`
+    /// - `digits[1]`
+    /// - ...
+    /// - `hash(digits[n + m - 1])`
+    /// - `digits[n + m - 1]` (stack top)
+    ///
+    /// For `n` message digits followed by `m` checksum digits.
+    ///
+    /// ## Postcondition
+    ///
+    /// - `digits[n + m - 1]`
+    /// - `digits[n + m - 2]`
+    /// - ...
+    /// - `digits[0]` (alt stack top)
+    ///
+    /// The input is consumed from the stack.
     fn verify_digits(ps: &Parameters, public_key: &PublicKey) -> Script;
 }
 
-/// This trait covers 2 converters for converting the final message (which is left in the form of blocks): `VoidConverter`, `ToBytesConverter`
+/// Trait for converting the message on the stack after signature verification.
path: bitvm/src/signatures/winternitz.rs
user: login: storopoli
body: same as above
created_at: 2025-05-02T12:52:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -158,7 +186,6 @@ impl<VERIFIER: Verifier, CONVERTER: Converter> Default for Winternitz<VERIFIER,
     }
 }
 
-/// Implementation of the default functions regardless of the algorithms that are chosen
 impl<VERIFIER: Verifier, CONVERTER: Converter> Winternitz<VERIFIER, CONVERTER> {
     pub const fn new() -> Self {
path: bitvm/src/signatures/winternitz.rs
user: login: storopoli
body: missing docstring in a public function
created_at: 2025-05-02T12:52:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -177,16 +208,37 @@ impl<VERIFIER: Verifier, CONVERTER: Converter> Winternitz<VERIFIER, CONVERTER> {
         VERIFIER::sign_digits(ps, secret_key, digits)
     }
 
-    /// Wrapper to sign the message in bytes (converts to digits inside)
-    pub fn sign(&self, ps: &Parameters, secret_key: &SecretKey, message_bytes: &[u8]) -> Witness {
+    /// Creates a Winternitz signature for the given `secret_key` and `message`.
+    ///
+    /// The message is internally converted into digits.
+    ///
+    /// ## See
+    ///
+    /// [`Verifier::sign_digits`]
+    pub fn sign(&self, ps: &Parameters, secret_key: &SecretKey, message: &[u8]) -> Witness {
         VERIFIER::sign_digits(
             ps,
             secret_key,
-            bytes_to_u32s(ps.message_length, ps.block_length, message_bytes),
+            message_to_digits(ps.message_digit_len, ps.log2_base, message),
         )
     }
 
-    /// Expects the signature in the format of the used verification algorithm, start of the message being on the top and checksum being at the bottom, and verifies if the given signature is accurate leaving digits of the message on the stack start of it being on top
+    /// Returns a Bitcoin script that verifies a Winternitz signature for the given `public_key`.
+    ///
+    /// ## Precondition
+    ///
+    /// Signature (in the verifier's format) is at the stack top.
+    ///
+    /// ## Postcondition
+    ///
+    /// The converted message is on the stack top.
+    /// The checksum is consumed.
+    ///
+    /// ## See
+    ///
+    /// [`Verifier::verify_digits`]
+    ///
+    /// [`Converter::get_script`]
path: bitvm/src/signatures/winternitz.rs
user: login: storopoli
body: These should be a list.
created_at: 2025-05-02T12:53:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -236,82 +318,142 @@ impl<VERIFIER: Verifier, CONVERTER: Converter> Winternitz<VERIFIER, CONVERTER> {
     }
 }
 
-/// - Verification Algorithm: Generates hashes for each possible value and then uses `OP_PICK` to get the corresponding one from the created list. \
-///   As a small improvement, it also divides the length of the list by 2 in the start
-/// - Signature format: hash_{n - 1}, digit_{n - 1}, hash_{n - 2}, digit_{n - 2} ... hash_0, digit_0 (With digits)
-/// - Approximate Max Stack Depth Used During Verification: 2 * `total_length()` + (2 ^ `block_length`)/2   
+/// ## Verification Algorithm
path: bitvm/src/signatures/winternitz.rs
user: login: storopoli
body: This should be a H1 not H2
or maybe add a introduction text before the H2
created_at: 2025-05-02T12:54:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -236,82 +318,142 @@ impl<VERIFIER: Verifier, CONVERTER: Converter> Winternitz<VERIFIER, CONVERTER> {
     }
 }
 
-/// - Verification Algorithm: Generates hashes for each possible value and then uses `OP_PICK` to get the corresponding one from the created list. \
-///   As a small improvement, it also divides the length of the list by 2 in the start
-/// - Signature format: hash_{n - 1}, digit_{n - 1}, hash_{n - 2}, digit_{n - 2} ... hash_0, digit_0 (With digits)
-/// - Approximate Max Stack Depth Used During Verification: 2 * `total_length()` + (2 ^ `block_length`)/2   
+/// ## Verification Algorithm
+///
+/// Generates hashes for each possible value and then uses `OP_PICK` to get the corresponding one from the created list.
+/// As a small improvement, it also divides the length of the list by 2 in the start.
+///
+/// ## Signature format
+///
+/// - `hash(digits[0])`
+/// - `digits[0]`
+/// - `hash(digits[1])`
+/// - `digits[1]`
+/// - ...
+/// - `hash(digits[n + m - 1])`
+/// - `digits[n + m - 1]`
+///
+/// There are `n` message digits followed by `m` checksum digits.
+///
+/// ## Approximate Max Stack Depth Used During Verification
+///
+/// 2 * `total_digit_len` + (2 ^ `log2_base`) / 2
 pub struct ListpickVerifier {}
+
 impl Verifier for ListpickVerifier {
-    /// Expects the signature in the verifier's format, checks and verifies if the given signature is accurate with the given public key, leaving the message (with checksum) on the stack in given order
     fn verify_digits(ps: &Parameters, public_key: &PublicKey) -> Script {
         script! {
-            for digit_index in 0..ps.total_length() {
+            for digit_index in 0..ps.total_digit_len() {
                 // See https://github.com/BitVM/BitVM/issues/35
-                { ps.d() }
+                { ps.max_digit() }
                 OP_MIN
                 OP_DUP
                 OP_TOALTSTACK
-                { (ps.d() + 1) / 2 }
+                { ps.max_digit().div_ceil(2) }
                 OP_2DUP
                 OP_LESSTHAN
                 OP_IF
                     OP_DROP
                     OP_TOALTSTACK
-                    for _ in 0..(ps.d() + 1) / 2  {
+                    for _ in 0..ps.max_digit().div_ceil(2)  {
                         OP_HASH160
                     }
                 OP_ELSE
                     OP_SUB
                     OP_TOALTSTACK
                 OP_ENDIF
-                for _ in 0..ps.d()/2 {
+                for _ in 0..ps.max_digit()/2 {
                     OP_DUP OP_HASH160
                 }
                 OP_FROMALTSTACK
                 OP_PICK
-                { (public_key[ps.total_length() as usize - 1 - digit_index as usize]).to_vec() }
+                { (public_key[ps.total_digit_len() as usize - 1 - digit_index as usize]).to_vec() }
                 OP_EQUALVERIFY
-                for _ in 0..(ps.d() + 1) / 4 {
+                for _ in 0..(ps.max_digit() + 1) / 4 {
                     OP_2DROP
                 }
             }
         }
     }
 }
-/// - Verification Algorithm: Tries each possible digit value
-/// - Signature Format: hash_{n - 1}, hash_{n - 2} ... hash_0 (Without digits)
-/// - Approximate Max Stack Depth Used During Verification: `total_length()`
+
+/// ## Verification Algorithm
path: bitvm/src/signatures/winternitz.rs
user: login: storopoli
body: This should be a H1 not H2
or maybe add a introduction text before the H2
created_at: 2025-05-02T12:54:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -236,82 +318,142 @@ impl<VERIFIER: Verifier, CONVERTER: Converter> Winternitz<VERIFIER, CONVERTER> {
     }
 }
 
-/// - Verification Algorithm: Generates hashes for each possible value and then uses `OP_PICK` to get the corresponding one from the created list. \
-///   As a small improvement, it also divides the length of the list by 2 in the start
-/// - Signature format: hash_{n - 1}, digit_{n - 1}, hash_{n - 2}, digit_{n - 2} ... hash_0, digit_0 (With digits)
-/// - Approximate Max Stack Depth Used During Verification: 2 * `total_length()` + (2 ^ `block_length`)/2   
+/// ## Verification Algorithm
+///
+/// Generates hashes for each possible value and then uses `OP_PICK` to get the corresponding one from the created list.
+/// As a small improvement, it also divides the length of the list by 2 in the start.
+///
+/// ## Signature format
+///
+/// - `hash(digits[0])`
+/// - `digits[0]`
+/// - `hash(digits[1])`
+/// - `digits[1]`
+/// - ...
+/// - `hash(digits[n + m - 1])`
+/// - `digits[n + m - 1]`
+///
+/// There are `n` message digits followed by `m` checksum digits.
+///
+/// ## Approximate Max Stack Depth Used During Verification
+///
+/// 2 * `total_digit_len` + (2 ^ `log2_base`) / 2
 pub struct ListpickVerifier {}
+
 impl Verifier for ListpickVerifier {
-    /// Expects the signature in the verifier's format, checks and verifies if the given signature is accurate with the given public key, leaving the message (with checksum) on the stack in given order
     fn verify_digits(ps: &Parameters, public_key: &PublicKey) -> Script {
         script! {
-            for digit_index in 0..ps.total_length() {
+            for digit_index in 0..ps.total_digit_len() {
                 // See https://github.com/BitVM/BitVM/issues/35
-                { ps.d() }
+                { ps.max_digit() }
                 OP_MIN
                 OP_DUP
                 OP_TOALTSTACK
-                { (ps.d() + 1) / 2 }
+                { ps.max_digit().div_ceil(2) }
                 OP_2DUP
                 OP_LESSTHAN
                 OP_IF
                     OP_DROP
                     OP_TOALTSTACK
-                    for _ in 0..(ps.d() + 1) / 2  {
+                    for _ in 0..ps.max_digit().div_ceil(2)  {
                         OP_HASH160
                     }
                 OP_ELSE
                     OP_SUB
                     OP_TOALTSTACK
                 OP_ENDIF
-                for _ in 0..ps.d()/2 {
+                for _ in 0..ps.max_digit()/2 {
                     OP_DUP OP_HASH160
                 }
                 OP_FROMALTSTACK
                 OP_PICK
-                { (public_key[ps.total_length() as usize - 1 - digit_index as usize]).to_vec() }
+                { (public_key[ps.total_digit_len() as usize - 1 - digit_index as usize]).to_vec() }
                 OP_EQUALVERIFY
-                for _ in 0..(ps.d() + 1) / 4 {
+                for _ in 0..(ps.max_digit() + 1) / 4 {
                     OP_2DROP
                 }
             }
         }
     }
 }
-/// - Verification Algorithm: Tries each possible digit value
-/// - Signature Format: hash_{n - 1}, hash_{n - 2} ... hash_0 (Without digits)
-/// - Approximate Max Stack Depth Used During Verification: `total_length()`
+
+/// ## Verification Algorithm
+///
+/// Tries each possible digit value.
+///
+/// ## Signature Format
+///
+/// - `hash(digit[0])`
+/// - `hash(digit[1])`
+/// - ...
+/// - `hash(digit[n + m - 1])`
+///
+/// There are `n` message digits followed by `m` checksum digits.
+///
+/// ## Approximate Max Stack Depth Used During Verification
+///
+/// `total_length()`
 pub struct BruteforceVerifier {}
+
 impl Verifier for BruteforceVerifier {
-    /// Signature in the verifier's format
-    fn sign_digits(ps: &Parameters, secret_key: &SecretKey, digits: Vec<u32>) -> Witness {
-        let digits = add_message_checksum(ps, digits);
+    /// Create a Winternitz signature for the given `secret_key` and `digits`.
path: bitvm/src/signatures/winternitz.rs
user: login: storopoli
body: ```suggestion
    /// Creates a Winternitz signature for the given `secret_key` and `digits`.
```

Consistency with other docstrings.
created_at: 2025-05-02T12:55:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -327,22 +469,38 @@ impl Verifier for BruteforceVerifier {
     }
 }
 
-/// - Verification Algorithm: Simulates a for loop of hashing using binary search on the digit
-/// - Signature Format: hash_{n - 1}, digit_{n - 1}, hash_{n - 2}, digit_{n - 2} ... hash_0, digit_0 (With digits)
-/// - Approximate Max Stack Depth Used During Verification: 2 * `total_length()`
+/// ## Verification Algorithm
path: bitvm/src/signatures/winternitz.rs
user: login: storopoli
body: This should be a H1 not H2
or maybe add a introduction text before the H2
created_at: 2025-05-02T12:55:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -19,157 +18,152 @@ pub trait SignatureImpl {
 /// - For 256-bit WOTS, use 32 bytes.
 macro_rules! impl_wots {
     ($mod_name:ident, $MSG_LEN:expr) => {
-        paste! {
-            pub mod $mod_name {
-                use super::*;
-                use bitcoin_script::script;
-                use bitcoin::hex::FromHex;
-
-                /// Message length in bytes.
-                pub const MSG_LEN: u32 = $MSG_LEN;
-                /// Necessary parameters for the algorithm
-                pub const PS: winternitz::Parameters = winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
-                /// Total number of "digits" in the signature.
-                pub const N_DIGITS: u32 = PS.total_length();
-
-                /// Public key is an array of 20-byte arrays.
-                pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
-                /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
-                pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
-
-                impl SignatureImpl for Signature {
-                    fn to_script(self) -> Script {
-                        script! {
-                            for (preimage, digit) in self {
-                                { preimage.to_vec() }
-                                { digit }
-                            }
+        pub mod $mod_name {
+            use super::*;
+            use bitcoin::hex::FromHex;
+            use bitcoin_script::script;
+
+            /// Message length in bytes.
+            pub const MSG_LEN: u32 = $MSG_LEN;
+            /// Necessary parameters for the algorithm
+            pub const PS: winternitz::Parameters =
+                winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
+            /// Total number of "digits" in the signature.
+            pub const N_DIGITS: u32 = PS.total_digit_len();
+
+            /// Public key is an array of 20-byte arrays.
+            pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
+            /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
+            pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
+
+            impl SignatureImpl for Signature {
+                fn to_script(self) -> Script {
+                    script! {
+                        for (preimage, digit) in self {
+                            { preimage.to_vec() }
+                            { digit }
                         }
                     }
+                }
 
-                    fn to_compact_script(self) -> Script {
-                        script! {
-                            for (preimage, _) in self {
-                                { preimage.to_vec() }
-                            }
+                fn to_compact_script(self) -> Script {
+                    script! {
+                        for (preimage, _) in self {
+                            { preimage.to_vec() }
                         }
                     }
                 }
+            }
+
+            /// Create a verification script for a WOTS public key.
path: bitvm/src/signatures/wots_api.rs
user: login: storopoli
body: ```suggestion
            /// Creates a verification script for a WOTS `public_key`.
```
created_at: 2025-05-02T12:56:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -19,157 +18,152 @@ pub trait SignatureImpl {
 /// - For 256-bit WOTS, use 32 bytes.
 macro_rules! impl_wots {
     ($mod_name:ident, $MSG_LEN:expr) => {
-        paste! {
-            pub mod $mod_name {
-                use super::*;
-                use bitcoin_script::script;
-                use bitcoin::hex::FromHex;
-
-                /// Message length in bytes.
-                pub const MSG_LEN: u32 = $MSG_LEN;
-                /// Necessary parameters for the algorithm
-                pub const PS: winternitz::Parameters = winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
-                /// Total number of "digits" in the signature.
-                pub const N_DIGITS: u32 = PS.total_length();
-
-                /// Public key is an array of 20-byte arrays.
-                pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
-                /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
-                pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
-
-                impl SignatureImpl for Signature {
-                    fn to_script(self) -> Script {
-                        script! {
-                            for (preimage, digit) in self {
-                                { preimage.to_vec() }
-                                { digit }
-                            }
+        pub mod $mod_name {
+            use super::*;
+            use bitcoin::hex::FromHex;
+            use bitcoin_script::script;
+
+            /// Message length in bytes.
+            pub const MSG_LEN: u32 = $MSG_LEN;
+            /// Necessary parameters for the algorithm
+            pub const PS: winternitz::Parameters =
+                winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
+            /// Total number of "digits" in the signature.
+            pub const N_DIGITS: u32 = PS.total_digit_len();
+
+            /// Public key is an array of 20-byte arrays.
+            pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
+            /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
+            pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
+
+            impl SignatureImpl for Signature {
+                fn to_script(self) -> Script {
+                    script! {
+                        for (preimage, digit) in self {
+                            { preimage.to_vec() }
+                            { digit }
                         }
                     }
+                }
 
-                    fn to_compact_script(self) -> Script {
-                        script! {
-                            for (preimage, _) in self {
-                                { preimage.to_vec() }
-                            }
+                fn to_compact_script(self) -> Script {
+                    script! {
+                        for (preimage, _) in self {
+                            { preimage.to_vec() }
                         }
                     }
                 }
+            }
+
+            /// Create a verification script for a WOTS public key.
+            pub fn checksig_verify(public_key: PublicKey) -> Script {
+                WINTERNITZ_MESSAGE_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
+            }
+
+            /// Changes the format of the signature, from bitcoin witness to array
+            pub fn raw_witness_to_signature(sigs: &Witness) -> Signature {
+                // Iterate over the signature pieces two at a time.
+                let mut sigs_vec: Vec<([u8; 20], u8)> = Vec::new();
+                for i in (0..sigs.len()).step_by(2) {
+                    let preimage: [u8; 20] = if sigs[i].len() == 0 {
+                        [0; 20]
+                    } else {
+                        sigs[i].try_into().unwrap()
+                    };
+                    let digit_arr: [u8; 1] = if sigs[i + 1].len() == 0 {
+                        [0]
+                    } else {
+                        sigs[i + 1].try_into().unwrap()
+                    };
+                    sigs_vec.push((preimage, digit_arr[0]));
+                }
+                sigs_vec.try_into().unwrap()
+            }
+
+            /// Changes the format of the signature, from array to bitcoin witness
+            pub fn signature_to_raw_witness(sigs: &Signature) -> Witness {
+                let mut w = Witness::new();
+                for (h, digit) in sigs.iter() {
+                    w.push(h.to_vec());
+                    w.push(u32_to_le_bytes_minimal(*digit as u32));
+                }
+                w
+            }
+
+            /// Generate a signature for a message using the provided secret.
path: bitvm/src/signatures/wots_api.rs
user: login: storopoli
body: ```suggestion
            /// Generates a signature for a message using the provided `secret`.
```
created_at: 2025-05-02T12:57:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -19,157 +18,152 @@ pub trait SignatureImpl {
 /// - For 256-bit WOTS, use 32 bytes.
 macro_rules! impl_wots {
     ($mod_name:ident, $MSG_LEN:expr) => {
-        paste! {
-            pub mod $mod_name {
-                use super::*;
-                use bitcoin_script::script;
-                use bitcoin::hex::FromHex;
-
-                /// Message length in bytes.
-                pub const MSG_LEN: u32 = $MSG_LEN;
-                /// Necessary parameters for the algorithm
-                pub const PS: winternitz::Parameters = winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
-                /// Total number of "digits" in the signature.
-                pub const N_DIGITS: u32 = PS.total_length();
-
-                /// Public key is an array of 20-byte arrays.
-                pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
-                /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
-                pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
-
-                impl SignatureImpl for Signature {
-                    fn to_script(self) -> Script {
-                        script! {
-                            for (preimage, digit) in self {
-                                { preimage.to_vec() }
-                                { digit }
-                            }
+        pub mod $mod_name {
+            use super::*;
+            use bitcoin::hex::FromHex;
+            use bitcoin_script::script;
+
+            /// Message length in bytes.
+            pub const MSG_LEN: u32 = $MSG_LEN;
+            /// Necessary parameters for the algorithm
+            pub const PS: winternitz::Parameters =
+                winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
+            /// Total number of "digits" in the signature.
+            pub const N_DIGITS: u32 = PS.total_digit_len();
+
+            /// Public key is an array of 20-byte arrays.
+            pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
+            /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
+            pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
+
+            impl SignatureImpl for Signature {
+                fn to_script(self) -> Script {
+                    script! {
+                        for (preimage, digit) in self {
+                            { preimage.to_vec() }
+                            { digit }
                         }
                     }
+                }
 
-                    fn to_compact_script(self) -> Script {
-                        script! {
-                            for (preimage, _) in self {
-                                { preimage.to_vec() }
-                            }
+                fn to_compact_script(self) -> Script {
+                    script! {
+                        for (preimage, _) in self {
+                            { preimage.to_vec() }
                         }
                     }
                 }
+            }
+
+            /// Create a verification script for a WOTS public key.
+            pub fn checksig_verify(public_key: PublicKey) -> Script {
+                WINTERNITZ_MESSAGE_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
+            }
+
+            /// Changes the format of the signature, from bitcoin witness to array
+            pub fn raw_witness_to_signature(sigs: &Witness) -> Signature {
+                // Iterate over the signature pieces two at a time.
+                let mut sigs_vec: Vec<([u8; 20], u8)> = Vec::new();
+                for i in (0..sigs.len()).step_by(2) {
+                    let preimage: [u8; 20] = if sigs[i].len() == 0 {
+                        [0; 20]
+                    } else {
+                        sigs[i].try_into().unwrap()
+                    };
+                    let digit_arr: [u8; 1] = if sigs[i + 1].len() == 0 {
+                        [0]
+                    } else {
+                        sigs[i + 1].try_into().unwrap()
+                    };
+                    sigs_vec.push((preimage, digit_arr[0]));
+                }
+                sigs_vec.try_into().unwrap()
+            }
+
+            /// Changes the format of the signature, from array to bitcoin witness
+            pub fn signature_to_raw_witness(sigs: &Signature) -> Witness {
+                let mut w = Witness::new();
+                for (h, digit) in sigs.iter() {
+                    w.push(h.to_vec());
+                    w.push(u32_to_le_bytes_minimal(*digit as u32));
+                }
+                w
+            }
+
+            /// Generate a signature for a message using the provided secret.
+            pub fn get_signature(secret: &str, msg_bytes: &[u8]) -> Signature {
+                let secret_key = match Vec::<u8>::from_hex(secret) {
+                    Ok(bytes) => bytes,
+                    Err(_) => panic!("Invalid hex string for secret"),
+                };
 
-                /// Create a verification script for a WOTS public key.
+                let sigs = WINTERNITZ_MESSAGE_VERIFIER.sign(&PS, &secret_key, &msg_bytes.to_vec());
+                assert_eq!(sigs.len(), 2 * N_DIGITS as usize);
+                raw_witness_to_signature(&sigs)
+            }
+
+            /// Generate a WOTS public key using the provided secret.
path: bitvm/src/signatures/wots_api.rs
user: login: storopoli
body: ```suggestion
            /// Generates a WOTS public key using the provided `secret`.
```
created_at: 2025-05-02T12:57:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -19,157 +18,152 @@ pub trait SignatureImpl {
 /// - For 256-bit WOTS, use 32 bytes.
 macro_rules! impl_wots {
     ($mod_name:ident, $MSG_LEN:expr) => {
-        paste! {
-            pub mod $mod_name {
-                use super::*;
-                use bitcoin_script::script;
-                use bitcoin::hex::FromHex;
-
-                /// Message length in bytes.
-                pub const MSG_LEN: u32 = $MSG_LEN;
-                /// Necessary parameters for the algorithm
-                pub const PS: winternitz::Parameters = winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
-                /// Total number of "digits" in the signature.
-                pub const N_DIGITS: u32 = PS.total_length();
-
-                /// Public key is an array of 20-byte arrays.
-                pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
-                /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
-                pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
-
-                impl SignatureImpl for Signature {
-                    fn to_script(self) -> Script {
-                        script! {
-                            for (preimage, digit) in self {
-                                { preimage.to_vec() }
-                                { digit }
-                            }
+        pub mod $mod_name {
+            use super::*;
+            use bitcoin::hex::FromHex;
+            use bitcoin_script::script;
+
+            /// Message length in bytes.
+            pub const MSG_LEN: u32 = $MSG_LEN;
+            /// Necessary parameters for the algorithm
+            pub const PS: winternitz::Parameters =
+                winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
+            /// Total number of "digits" in the signature.
+            pub const N_DIGITS: u32 = PS.total_digit_len();
+
+            /// Public key is an array of 20-byte arrays.
+            pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
+            /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
+            pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
+
+            impl SignatureImpl for Signature {
+                fn to_script(self) -> Script {
+                    script! {
+                        for (preimage, digit) in self {
+                            { preimage.to_vec() }
+                            { digit }
                         }
                     }
+                }
 
-                    fn to_compact_script(self) -> Script {
-                        script! {
-                            for (preimage, _) in self {
-                                { preimage.to_vec() }
-                            }
+                fn to_compact_script(self) -> Script {
+                    script! {
+                        for (preimage, _) in self {
+                            { preimage.to_vec() }
                         }
                     }
                 }
+            }
+
+            /// Create a verification script for a WOTS public key.
+            pub fn checksig_verify(public_key: PublicKey) -> Script {
+                WINTERNITZ_MESSAGE_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
+            }
+
+            /// Changes the format of the signature, from bitcoin witness to array
+            pub fn raw_witness_to_signature(sigs: &Witness) -> Signature {
+                // Iterate over the signature pieces two at a time.
+                let mut sigs_vec: Vec<([u8; 20], u8)> = Vec::new();
+                for i in (0..sigs.len()).step_by(2) {
+                    let preimage: [u8; 20] = if sigs[i].len() == 0 {
+                        [0; 20]
+                    } else {
+                        sigs[i].try_into().unwrap()
+                    };
+                    let digit_arr: [u8; 1] = if sigs[i + 1].len() == 0 {
+                        [0]
+                    } else {
+                        sigs[i + 1].try_into().unwrap()
+                    };
+                    sigs_vec.push((preimage, digit_arr[0]));
+                }
+                sigs_vec.try_into().unwrap()
+            }
+
+            /// Changes the format of the signature, from array to bitcoin witness
+            pub fn signature_to_raw_witness(sigs: &Signature) -> Witness {
+                let mut w = Witness::new();
+                for (h, digit) in sigs.iter() {
+                    w.push(h.to_vec());
+                    w.push(u32_to_le_bytes_minimal(*digit as u32));
+                }
+                w
+            }
+
+            /// Generate a signature for a message using the provided secret.
+            pub fn get_signature(secret: &str, msg_bytes: &[u8]) -> Signature {
+                let secret_key = match Vec::<u8>::from_hex(secret) {
+                    Ok(bytes) => bytes,
+                    Err(_) => panic!("Invalid hex string for secret"),
+                };
 
-                /// Create a verification script for a WOTS public key.
+                let sigs = WINTERNITZ_MESSAGE_VERIFIER.sign(&PS, &secret_key, &msg_bytes.to_vec());
+                assert_eq!(sigs.len(), 2 * N_DIGITS as usize);
+                raw_witness_to_signature(&sigs)
+            }
+
+            /// Generate a WOTS public key using the provided secret.
+            pub fn generate_public_key(secret: &str) -> PublicKey {
+                let secret_key = match Vec::<u8>::from_hex(secret) {
+                    Ok(bytes) => bytes,
+                    Err(_) => panic!("Invalid hex string for secret"),
+                };
+                let pubkey_vec = winternitz::generate_public_key(&PS, &secret_key);
+                pubkey_vec.try_into().unwrap()
+            }
+
+            /// A sub-module for the compact signature variant.
+            pub mod compact {
+                use super::*;
+
+                /// The compact signature is just the 20-byte preimages.
+                pub type Signature = [[u8; 20]; N_DIGITS as usize];
+
+                /// Create a verification script for the compact WOTS public key.
path: bitvm/src/signatures/wots_api.rs
user: login: storopoli
body: ```suggestion
                /// Creates a verification script for the compact WOTS `public_key`.
```
created_at: 2025-05-02T12:58:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -19,157 +18,152 @@ pub trait SignatureImpl {
 /// - For 256-bit WOTS, use 32 bytes.
 macro_rules! impl_wots {
     ($mod_name:ident, $MSG_LEN:expr) => {
-        paste! {
-            pub mod $mod_name {
-                use super::*;
-                use bitcoin_script::script;
-                use bitcoin::hex::FromHex;
-
-                /// Message length in bytes.
-                pub const MSG_LEN: u32 = $MSG_LEN;
-                /// Necessary parameters for the algorithm
-                pub const PS: winternitz::Parameters = winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
-                /// Total number of "digits" in the signature.
-                pub const N_DIGITS: u32 = PS.total_length();
-
-                /// Public key is an array of 20-byte arrays.
-                pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
-                /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
-                pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
-
-                impl SignatureImpl for Signature {
-                    fn to_script(self) -> Script {
-                        script! {
-                            for (preimage, digit) in self {
-                                { preimage.to_vec() }
-                                { digit }
-                            }
+        pub mod $mod_name {
+            use super::*;
+            use bitcoin::hex::FromHex;
+            use bitcoin_script::script;
+
+            /// Message length in bytes.
+            pub const MSG_LEN: u32 = $MSG_LEN;
+            /// Necessary parameters for the algorithm
+            pub const PS: winternitz::Parameters =
+                winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
+            /// Total number of "digits" in the signature.
+            pub const N_DIGITS: u32 = PS.total_digit_len();
+
+            /// Public key is an array of 20-byte arrays.
+            pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
+            /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
+            pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
+
+            impl SignatureImpl for Signature {
+                fn to_script(self) -> Script {
+                    script! {
+                        for (preimage, digit) in self {
+                            { preimage.to_vec() }
+                            { digit }
                         }
                     }
+                }
 
-                    fn to_compact_script(self) -> Script {
-                        script! {
-                            for (preimage, _) in self {
-                                { preimage.to_vec() }
-                            }
+                fn to_compact_script(self) -> Script {
+                    script! {
+                        for (preimage, _) in self {
+                            { preimage.to_vec() }
                         }
                     }
                 }
+            }
+
+            /// Create a verification script for a WOTS public key.
+            pub fn checksig_verify(public_key: PublicKey) -> Script {
+                WINTERNITZ_MESSAGE_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
+            }
+
+            /// Changes the format of the signature, from bitcoin witness to array
+            pub fn raw_witness_to_signature(sigs: &Witness) -> Signature {
+                // Iterate over the signature pieces two at a time.
+                let mut sigs_vec: Vec<([u8; 20], u8)> = Vec::new();
+                for i in (0..sigs.len()).step_by(2) {
+                    let preimage: [u8; 20] = if sigs[i].len() == 0 {
+                        [0; 20]
+                    } else {
+                        sigs[i].try_into().unwrap()
+                    };
+                    let digit_arr: [u8; 1] = if sigs[i + 1].len() == 0 {
+                        [0]
+                    } else {
+                        sigs[i + 1].try_into().unwrap()
+                    };
+                    sigs_vec.push((preimage, digit_arr[0]));
+                }
+                sigs_vec.try_into().unwrap()
+            }
+
+            /// Changes the format of the signature, from array to bitcoin witness
+            pub fn signature_to_raw_witness(sigs: &Signature) -> Witness {
+                let mut w = Witness::new();
+                for (h, digit) in sigs.iter() {
+                    w.push(h.to_vec());
+                    w.push(u32_to_le_bytes_minimal(*digit as u32));
+                }
+                w
+            }
+
+            /// Generate a signature for a message using the provided secret.
+            pub fn get_signature(secret: &str, msg_bytes: &[u8]) -> Signature {
+                let secret_key = match Vec::<u8>::from_hex(secret) {
+                    Ok(bytes) => bytes,
+                    Err(_) => panic!("Invalid hex string for secret"),
+                };
 
-                /// Create a verification script for a WOTS public key.
+                let sigs = WINTERNITZ_MESSAGE_VERIFIER.sign(&PS, &secret_key, &msg_bytes.to_vec());
+                assert_eq!(sigs.len(), 2 * N_DIGITS as usize);
+                raw_witness_to_signature(&sigs)
+            }
+
+            /// Generate a WOTS public key using the provided secret.
+            pub fn generate_public_key(secret: &str) -> PublicKey {
+                let secret_key = match Vec::<u8>::from_hex(secret) {
+                    Ok(bytes) => bytes,
+                    Err(_) => panic!("Invalid hex string for secret"),
+                };
+                let pubkey_vec = winternitz::generate_public_key(&PS, &secret_key);
+                pubkey_vec.try_into().unwrap()
+            }
+
+            /// A sub-module for the compact signature variant.
+            pub mod compact {
+                use super::*;
+
+                /// The compact signature is just the 20-byte preimages.
+                pub type Signature = [[u8; 20]; N_DIGITS as usize];
+
+                /// Create a verification script for the compact WOTS public key.
                 pub fn checksig_verify(public_key: PublicKey) -> Script {
-                    WINTERNITZ_MESSAGE_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
+                    WINTERNITZ_MESSAGE_COMPACT_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
                 }
 
                 /// Changes the format of the signature, from bitcoin witness to array
                 pub fn raw_witness_to_signature(sigs: &Witness) -> Signature {
                     // Iterate over the signature pieces two at a time.
path: bitvm/src/signatures/wots_api.rs
user: login: storopoli
body: Is this a single signature?
Then the function signature would be
```suggestion
                /// Changes the format of the signature, from bitcoin witness to an arrayo
                pub fn raw_witness_to_signature(sig: &Witness) -> Signature {
                    // Iterate over the signature pieces two at a time.
```

Or maybe even

```suggestion
                /// Changes the format of the `signature`, from bitcoin witness to an array.
                pub fn raw_witness_to_signature(signature: &Witness) -> Signature {
                    // Iterate over the signature pieces two at a time.
```
created_at: 2025-05-02T12:59:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -19,157 +18,152 @@ pub trait SignatureImpl {
 /// - For 256-bit WOTS, use 32 bytes.
 macro_rules! impl_wots {
     ($mod_name:ident, $MSG_LEN:expr) => {
-        paste! {
-            pub mod $mod_name {
-                use super::*;
-                use bitcoin_script::script;
-                use bitcoin::hex::FromHex;
-
-                /// Message length in bytes.
-                pub const MSG_LEN: u32 = $MSG_LEN;
-                /// Necessary parameters for the algorithm
-                pub const PS: winternitz::Parameters = winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
-                /// Total number of "digits" in the signature.
-                pub const N_DIGITS: u32 = PS.total_length();
-
-                /// Public key is an array of 20-byte arrays.
-                pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
-                /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
-                pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
-
-                impl SignatureImpl for Signature {
-                    fn to_script(self) -> Script {
-                        script! {
-                            for (preimage, digit) in self {
-                                { preimage.to_vec() }
-                                { digit }
-                            }
+        pub mod $mod_name {
+            use super::*;
+            use bitcoin::hex::FromHex;
+            use bitcoin_script::script;
+
+            /// Message length in bytes.
+            pub const MSG_LEN: u32 = $MSG_LEN;
+            /// Necessary parameters for the algorithm
+            pub const PS: winternitz::Parameters =
+                winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
+            /// Total number of "digits" in the signature.
+            pub const N_DIGITS: u32 = PS.total_digit_len();
+
+            /// Public key is an array of 20-byte arrays.
+            pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
+            /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
+            pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
+
+            impl SignatureImpl for Signature {
+                fn to_script(self) -> Script {
+                    script! {
+                        for (preimage, digit) in self {
+                            { preimage.to_vec() }
+                            { digit }
                         }
                     }
+                }
 
-                    fn to_compact_script(self) -> Script {
-                        script! {
-                            for (preimage, _) in self {
-                                { preimage.to_vec() }
-                            }
+                fn to_compact_script(self) -> Script {
+                    script! {
+                        for (preimage, _) in self {
+                            { preimage.to_vec() }
                         }
                     }
                 }
+            }
+
+            /// Create a verification script for a WOTS public key.
+            pub fn checksig_verify(public_key: PublicKey) -> Script {
+                WINTERNITZ_MESSAGE_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
+            }
+
+            /// Changes the format of the signature, from bitcoin witness to array
+            pub fn raw_witness_to_signature(sigs: &Witness) -> Signature {
+                // Iterate over the signature pieces two at a time.
+                let mut sigs_vec: Vec<([u8; 20], u8)> = Vec::new();
+                for i in (0..sigs.len()).step_by(2) {
+                    let preimage: [u8; 20] = if sigs[i].len() == 0 {
+                        [0; 20]
+                    } else {
+                        sigs[i].try_into().unwrap()
+                    };
+                    let digit_arr: [u8; 1] = if sigs[i + 1].len() == 0 {
+                        [0]
+                    } else {
+                        sigs[i + 1].try_into().unwrap()
+                    };
+                    sigs_vec.push((preimage, digit_arr[0]));
+                }
+                sigs_vec.try_into().unwrap()
+            }
+
+            /// Changes the format of the signature, from array to bitcoin witness
+            pub fn signature_to_raw_witness(sigs: &Signature) -> Witness {
+                let mut w = Witness::new();
+                for (h, digit) in sigs.iter() {
+                    w.push(h.to_vec());
+                    w.push(u32_to_le_bytes_minimal(*digit as u32));
+                }
+                w
+            }
+
+            /// Generate a signature for a message using the provided secret.
+            pub fn get_signature(secret: &str, msg_bytes: &[u8]) -> Signature {
+                let secret_key = match Vec::<u8>::from_hex(secret) {
+                    Ok(bytes) => bytes,
+                    Err(_) => panic!("Invalid hex string for secret"),
+                };
 
-                /// Create a verification script for a WOTS public key.
+                let sigs = WINTERNITZ_MESSAGE_VERIFIER.sign(&PS, &secret_key, &msg_bytes.to_vec());
+                assert_eq!(sigs.len(), 2 * N_DIGITS as usize);
+                raw_witness_to_signature(&sigs)
+            }
+
+            /// Generate a WOTS public key using the provided secret.
+            pub fn generate_public_key(secret: &str) -> PublicKey {
+                let secret_key = match Vec::<u8>::from_hex(secret) {
+                    Ok(bytes) => bytes,
+                    Err(_) => panic!("Invalid hex string for secret"),
+                };
+                let pubkey_vec = winternitz::generate_public_key(&PS, &secret_key);
+                pubkey_vec.try_into().unwrap()
+            }
+
+            /// A sub-module for the compact signature variant.
+            pub mod compact {
+                use super::*;
+
+                /// The compact signature is just the 20-byte preimages.
+                pub type Signature = [[u8; 20]; N_DIGITS as usize];
+
+                /// Create a verification script for the compact WOTS public key.
                 pub fn checksig_verify(public_key: PublicKey) -> Script {
-                    WINTERNITZ_MESSAGE_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
+                    WINTERNITZ_MESSAGE_COMPACT_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
                 }
 
                 /// Changes the format of the signature, from bitcoin witness to array
                 pub fn raw_witness_to_signature(sigs: &Witness) -> Signature {
                     // Iterate over the signature pieces two at a time.
-                    let mut sigs_vec: Vec<([u8; 20], u8)> = Vec::new();
-                    for i in (0..sigs.len()).step_by(2) {
+                    let mut sigs_vec: Vec<[u8; 20]> = Vec::new();
+                    // Iterate over the signature pieces using step_by.
+                    for i in 0..sigs.len() {
                         let preimage: [u8; 20] = if sigs[i].len() == 0 {
                             [0; 20]
                         } else {
                             sigs[i].try_into().unwrap()
                         };
-                        let digit_arr: [u8; 1] = if sigs[i + 1].len() == 0 {
-                            [0]
-                        } else {
-                            sigs[i + 1].try_into().unwrap()
-                        };
-                        sigs_vec.push((preimage, digit_arr[0]));
+                        sigs_vec.push(preimage);
                     }
                     sigs_vec.try_into().unwrap()
                 }
 
                 /// Changes the format of the signature, from array to bitcoin witness
                 pub fn signature_to_raw_witness(sigs: &Signature) -> Witness {
path: bitvm/src/signatures/wots_api.rs
user: login: storopoli
body: same as above
created_at: 2025-05-02T12:59:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -19,157 +18,152 @@ pub trait SignatureImpl {
 /// - For 256-bit WOTS, use 32 bytes.
 macro_rules! impl_wots {
     ($mod_name:ident, $MSG_LEN:expr) => {
-        paste! {
-            pub mod $mod_name {
-                use super::*;
-                use bitcoin_script::script;
-                use bitcoin::hex::FromHex;
-
-                /// Message length in bytes.
-                pub const MSG_LEN: u32 = $MSG_LEN;
-                /// Necessary parameters for the algorithm
-                pub const PS: winternitz::Parameters = winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
-                /// Total number of "digits" in the signature.
-                pub const N_DIGITS: u32 = PS.total_length();
-
-                /// Public key is an array of 20-byte arrays.
-                pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
-                /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
-                pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
-
-                impl SignatureImpl for Signature {
-                    fn to_script(self) -> Script {
-                        script! {
-                            for (preimage, digit) in self {
-                                { preimage.to_vec() }
-                                { digit }
-                            }
+        pub mod $mod_name {
+            use super::*;
+            use bitcoin::hex::FromHex;
+            use bitcoin_script::script;
+
+            /// Message length in bytes.
+            pub const MSG_LEN: u32 = $MSG_LEN;
+            /// Necessary parameters for the algorithm
+            pub const PS: winternitz::Parameters =
+                winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
+            /// Total number of "digits" in the signature.
+            pub const N_DIGITS: u32 = PS.total_digit_len();
+
+            /// Public key is an array of 20-byte arrays.
+            pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
+            /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
+            pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
+
+            impl SignatureImpl for Signature {
+                fn to_script(self) -> Script {
+                    script! {
+                        for (preimage, digit) in self {
+                            { preimage.to_vec() }
+                            { digit }
                         }
                     }
+                }
 
-                    fn to_compact_script(self) -> Script {
-                        script! {
-                            for (preimage, _) in self {
-                                { preimage.to_vec() }
-                            }
+                fn to_compact_script(self) -> Script {
+                    script! {
+                        for (preimage, _) in self {
+                            { preimage.to_vec() }
                         }
                     }
                 }
+            }
+
+            /// Create a verification script for a WOTS public key.
+            pub fn checksig_verify(public_key: PublicKey) -> Script {
+                WINTERNITZ_MESSAGE_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
+            }
+
+            /// Changes the format of the signature, from bitcoin witness to array
+            pub fn raw_witness_to_signature(sigs: &Witness) -> Signature {
+                // Iterate over the signature pieces two at a time.
+                let mut sigs_vec: Vec<([u8; 20], u8)> = Vec::new();
+                for i in (0..sigs.len()).step_by(2) {
+                    let preimage: [u8; 20] = if sigs[i].len() == 0 {
+                        [0; 20]
+                    } else {
+                        sigs[i].try_into().unwrap()
+                    };
+                    let digit_arr: [u8; 1] = if sigs[i + 1].len() == 0 {
+                        [0]
+                    } else {
+                        sigs[i + 1].try_into().unwrap()
+                    };
+                    sigs_vec.push((preimage, digit_arr[0]));
+                }
+                sigs_vec.try_into().unwrap()
+            }
+
+            /// Changes the format of the signature, from array to bitcoin witness
+            pub fn signature_to_raw_witness(sigs: &Signature) -> Witness {
+                let mut w = Witness::new();
+                for (h, digit) in sigs.iter() {
+                    w.push(h.to_vec());
+                    w.push(u32_to_le_bytes_minimal(*digit as u32));
+                }
+                w
+            }
+
+            /// Generate a signature for a message using the provided secret.
+            pub fn get_signature(secret: &str, msg_bytes: &[u8]) -> Signature {
+                let secret_key = match Vec::<u8>::from_hex(secret) {
+                    Ok(bytes) => bytes,
+                    Err(_) => panic!("Invalid hex string for secret"),
+                };
 
-                /// Create a verification script for a WOTS public key.
+                let sigs = WINTERNITZ_MESSAGE_VERIFIER.sign(&PS, &secret_key, &msg_bytes.to_vec());
+                assert_eq!(sigs.len(), 2 * N_DIGITS as usize);
+                raw_witness_to_signature(&sigs)
+            }
+
+            /// Generate a WOTS public key using the provided secret.
+            pub fn generate_public_key(secret: &str) -> PublicKey {
+                let secret_key = match Vec::<u8>::from_hex(secret) {
+                    Ok(bytes) => bytes,
+                    Err(_) => panic!("Invalid hex string for secret"),
+                };
+                let pubkey_vec = winternitz::generate_public_key(&PS, &secret_key);
+                pubkey_vec.try_into().unwrap()
+            }
+
+            /// A sub-module for the compact signature variant.
+            pub mod compact {
+                use super::*;
+
+                /// The compact signature is just the 20-byte preimages.
+                pub type Signature = [[u8; 20]; N_DIGITS as usize];
+
+                /// Create a verification script for the compact WOTS public key.
                 pub fn checksig_verify(public_key: PublicKey) -> Script {
-                    WINTERNITZ_MESSAGE_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
+                    WINTERNITZ_MESSAGE_COMPACT_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
                 }
 
                 /// Changes the format of the signature, from bitcoin witness to array
                 pub fn raw_witness_to_signature(sigs: &Witness) -> Signature {
                     // Iterate over the signature pieces two at a time.
-                    let mut sigs_vec: Vec<([u8; 20], u8)> = Vec::new();
-                    for i in (0..sigs.len()).step_by(2) {
+                    let mut sigs_vec: Vec<[u8; 20]> = Vec::new();
+                    // Iterate over the signature pieces using step_by.
+                    for i in 0..sigs.len() {
                         let preimage: [u8; 20] = if sigs[i].len() == 0 {
                             [0; 20]
                         } else {
                             sigs[i].try_into().unwrap()
                         };
-                        let digit_arr: [u8; 1] = if sigs[i + 1].len() == 0 {
-                            [0]
-                        } else {
-                            sigs[i + 1].try_into().unwrap()
-                        };
-                        sigs_vec.push((preimage, digit_arr[0]));
+                        sigs_vec.push(preimage);
                     }
                     sigs_vec.try_into().unwrap()
                 }
 
                 /// Changes the format of the signature, from array to bitcoin witness
                 pub fn signature_to_raw_witness(sigs: &Signature) -> Witness {
                     let mut w = Witness::new();
-                    for (h, digit) in sigs.iter() {
+                    for h in sigs.iter() {
                         w.push(h.to_vec());
-                        w.push(u32_to_le_bytes_minimal(*digit as u32));
                     }
                     w
                 }
 
-                /// Generate a signature for a message using the provided secret.
+                /// Generate a compact signature for a message.
path: bitvm/src/signatures/wots_api.rs
user: login: storopoli
body: ```suggestion
                /// Generates a compact signature for a message.
```
created_at: 2025-05-02T12:59:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,68 +11,61 @@ type HashOut = [u8; 20];
 pub type PublicKey = Vec<HashOut>;
 pub type SecretKey = Vec<u8>;
 
-/// Contains the parameters to use with `Winternitz` struct
-#[derive(Serialize, Deserialize, Eq, PartialEq, Hash, Clone)]
+/// Parameters for the [`Winternitz`] struct.
+#[derive(Serialize, Deserialize, Eq, PartialEq, Hash, Clone, Copy)]
 pub struct Parameters {
-    /// Number of blocks of the actual message
-    message_length: u32,
-    /// Number of bits in one block
-    block_length: u32,
-    /// Number of blocks of the checksum part
-    checksum_length: u32,
+    /// Number of digits per message (including zero padding at the end).
+    message_digit_len: u32,
+    /// Number of bits per digit.
+    log2_base: u32,
+    /// Number of digits per checksum.
+    checksum_digit_len: u32,
 }
 
 impl Parameters {
-    /// Creates parameters with given message length (number of blocks in the message) and block length (number of bits in one block, in the closed range 4, 8)
-    pub const fn new(message_block_count: u32, block_length: u32) -> Self {
+    /// Creates parameters for messages of the given number of digits of the given base.
+    ///
+    /// The base must be in the range `16..=256`.
+    pub const fn new(message_digit_len: u32, log2_base: u32) -> Self {
         assert!(
-            4 <= block_length && block_length <= 8,
-            "You can only choose block lengths in the range [4, 8]"
+            4 <= log2_base && log2_base <= 8,
+            "log2_base must be in the range 4..=8"
path: bitvm/src/signatures/winternitz.rs
user: login: uncomputable
body: If the base is in the range `16..=256`, then log2(base) is in the range `4..=8`. So, technically, the docs are accurate. What is confusing is the difference between base and log2(base). Some methods take the former, some take the latter.

I think we should consistently use one over the other. The compiler should collapse `2.pow(log2_base)`, or `base.log2()` at compile time. We could also use min const generics.

What do you like better? base or log2_base?
created_at: 2025-05-02T13:51:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -83,70 +76,105 @@ pub fn digit_signature(secret_key: &SecretKey, digit_index: u32, message_digit:
     *hash.as_byte_array()
 }
 
-/// Returns the public key of a given digit (block), requires the digit index to modify the secret key for each digit
+/// Returns the public key of a given digit, requires the digit index to modify the secret key for each digit
 fn public_key_for_digit(ps: &Parameters, secret_key: &SecretKey, digit_index: u32) -> HashOut {
-    digit_signature(secret_key, digit_index, ps.d())
+    digit_signature(secret_key, digit_index, ps.max_digit())
 }
 
 /// Returns the public key for the given secret key and the parameters
 pub fn generate_public_key(ps: &Parameters, secret_key: &SecretKey) -> PublicKey {
-    let mut public_key = PublicKey::with_capacity(ps.total_length() as usize);
-    for i in 0..ps.total_length() {
+    let mut public_key = PublicKey::with_capacity(ps.total_digit_len() as usize);
+    for i in 0..ps.total_digit_len() {
         public_key.push(public_key_for_digit(ps, secret_key, i));
     }
     public_key
 }
 
 /// Checksum of the message (negated sum of the digits)
path: bitvm/src/signatures/winternitz.rs
user: login: uncomputable
body: I removed "negated sum of digits" from the docstring. How the checksum is computed is an implementation detail, and the function is private anyway.
created_at: 2025-05-02T16:03:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -158,7 +186,6 @@ impl<VERIFIER: Verifier, CONVERTER: Converter> Default for Winternitz<VERIFIER,
     }
 }
 
-/// Implementation of the default functions regardless of the algorithms that are chosen
 impl<VERIFIER: Verifier, CONVERTER: Converter> Winternitz<VERIFIER, CONVERTER> {
     pub const fn new() -> Self {
path: bitvm/src/signatures/winternitz.rs
user: login: uncomputable
body: I plan to make this function private in a follow-up PR.
created_at: 2025-05-02T16:05:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -19,157 +18,152 @@ pub trait SignatureImpl {
 /// - For 256-bit WOTS, use 32 bytes.
 macro_rules! impl_wots {
     ($mod_name:ident, $MSG_LEN:expr) => {
-        paste! {
-            pub mod $mod_name {
-                use super::*;
-                use bitcoin_script::script;
-                use bitcoin::hex::FromHex;
-
-                /// Message length in bytes.
-                pub const MSG_LEN: u32 = $MSG_LEN;
-                /// Necessary parameters for the algorithm
-                pub const PS: winternitz::Parameters = winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
-                /// Total number of "digits" in the signature.
-                pub const N_DIGITS: u32 = PS.total_length();
-
-                /// Public key is an array of 20-byte arrays.
-                pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
-                /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
-                pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
-
-                impl SignatureImpl for Signature {
-                    fn to_script(self) -> Script {
-                        script! {
-                            for (preimage, digit) in self {
-                                { preimage.to_vec() }
-                                { digit }
-                            }
+        pub mod $mod_name {
+            use super::*;
+            use bitcoin::hex::FromHex;
+            use bitcoin_script::script;
+
+            /// Message length in bytes.
+            pub const MSG_LEN: u32 = $MSG_LEN;
+            /// Necessary parameters for the algorithm
+            pub const PS: winternitz::Parameters =
+                winternitz::Parameters::new_by_bit_length(MSG_LEN * 8, 4);
+            /// Total number of "digits" in the signature.
+            pub const N_DIGITS: u32 = PS.total_digit_len();
+
+            /// Public key is an array of 20-byte arrays.
+            pub type PublicKey = [[u8; 20]; N_DIGITS as usize];
+            /// Signature consists of pairs: (20-byte preimage, 1-byte digit).
+            pub type Signature = [([u8; 20], u8); N_DIGITS as usize];
+
+            impl SignatureImpl for Signature {
+                fn to_script(self) -> Script {
+                    script! {
+                        for (preimage, digit) in self {
+                            { preimage.to_vec() }
+                            { digit }
                         }
                     }
+                }
 
-                    fn to_compact_script(self) -> Script {
-                        script! {
-                            for (preimage, _) in self {
-                                { preimage.to_vec() }
-                            }
+                fn to_compact_script(self) -> Script {
+                    script! {
+                        for (preimage, _) in self {
+                            { preimage.to_vec() }
                         }
                     }
                 }
+            }
+
+            /// Create a verification script for a WOTS public key.
+            pub fn checksig_verify(public_key: PublicKey) -> Script {
+                WINTERNITZ_MESSAGE_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
+            }
+
+            /// Changes the format of the signature, from bitcoin witness to array
+            pub fn raw_witness_to_signature(sigs: &Witness) -> Signature {
+                // Iterate over the signature pieces two at a time.
+                let mut sigs_vec: Vec<([u8; 20], u8)> = Vec::new();
+                for i in (0..sigs.len()).step_by(2) {
+                    let preimage: [u8; 20] = if sigs[i].len() == 0 {
+                        [0; 20]
+                    } else {
+                        sigs[i].try_into().unwrap()
+                    };
+                    let digit_arr: [u8; 1] = if sigs[i + 1].len() == 0 {
+                        [0]
+                    } else {
+                        sigs[i + 1].try_into().unwrap()
+                    };
+                    sigs_vec.push((preimage, digit_arr[0]));
+                }
+                sigs_vec.try_into().unwrap()
+            }
+
+            /// Changes the format of the signature, from array to bitcoin witness
+            pub fn signature_to_raw_witness(sigs: &Signature) -> Witness {
+                let mut w = Witness::new();
+                for (h, digit) in sigs.iter() {
+                    w.push(h.to_vec());
+                    w.push(u32_to_le_bytes_minimal(*digit as u32));
+                }
+                w
+            }
+
+            /// Generate a signature for a message using the provided secret.
+            pub fn get_signature(secret: &str, msg_bytes: &[u8]) -> Signature {
+                let secret_key = match Vec::<u8>::from_hex(secret) {
+                    Ok(bytes) => bytes,
+                    Err(_) => panic!("Invalid hex string for secret"),
+                };
 
-                /// Create a verification script for a WOTS public key.
+                let sigs = WINTERNITZ_MESSAGE_VERIFIER.sign(&PS, &secret_key, &msg_bytes.to_vec());
+                assert_eq!(sigs.len(), 2 * N_DIGITS as usize);
+                raw_witness_to_signature(&sigs)
+            }
+
+            /// Generate a WOTS public key using the provided secret.
+            pub fn generate_public_key(secret: &str) -> PublicKey {
+                let secret_key = match Vec::<u8>::from_hex(secret) {
+                    Ok(bytes) => bytes,
+                    Err(_) => panic!("Invalid hex string for secret"),
+                };
+                let pubkey_vec = winternitz::generate_public_key(&PS, &secret_key);
+                pubkey_vec.try_into().unwrap()
+            }
+
+            /// A sub-module for the compact signature variant.
+            pub mod compact {
+                use super::*;
+
+                /// The compact signature is just the 20-byte preimages.
+                pub type Signature = [[u8; 20]; N_DIGITS as usize];
+
+                /// Create a verification script for the compact WOTS public key.
                 pub fn checksig_verify(public_key: PublicKey) -> Script {
-                    WINTERNITZ_MESSAGE_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
+                    WINTERNITZ_MESSAGE_COMPACT_VERIFIER.checksig_verify(&PS, &public_key.to_vec())
                 }
 
                 /// Changes the format of the signature, from bitcoin witness to array
                 pub fn raw_witness_to_signature(sigs: &Witness) -> Signature {
                     // Iterate over the signature pieces two at a time.
path: bitvm/src/signatures/wots_api.rs
user: login: uncomputable
body: The witness data may include multiple Winternitz signatures, so `sigs` is accurate. I plan to address this function is more detail in a follow-up PR.

Edit: At least in theory, a Bitcoin `Witness` may include multiple signatures. This is not what this function seems to assume. Weird.
created_at: 2025-05-02T16:11:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,64 +11,90 @@ pub(super) const fn log_base_ceil(n: u32, base: u32) -> u32 {
     res
 }
 
-/// Converts the number to given base, smallest digit being at the start (returns `digit_count` smallest digits of it, and standard base representation of it if `digit_count` = -1)
-pub(super) fn to_digits(mut number: u32, base: u32, digit_count: i32) -> Vec<u32> {
-    let mut digits = Vec::new();
-    if digit_count == -1 {
-        while number > 0 {
-            let digit = number % base;
-            number = (number - digit) / base;
-            digits.push(digit);
-        }
-    } else {
-        digits.reserve(digit_count as usize);
-        for _ in 0..digit_count {
-            let digit = number % base;
-            number = (number - digit) / base;
-            digits.push(digit);
-        }
+/// Converts the given `checksum` into a vector of digits.
+///
+/// ## Output format
+///
+/// - sequence of `n_digits` many digits
+/// - each digit a `u32` value in range `0..2.pow(log2_base)`
path: bitvm/src/signatures/utils.rs
user: login: AaronFeickert
body: The value `log2_base` is not defined as part of the function signature.
created_at: 2025-05-02T20:03:54Z
author_association: CONTRIBUTOR

PR comments:
user: login: uncomputable
created_at: 2025-05-01T21:34:34Z
body: I documented most methods, focusing on the used byte formats and script inputs / outputs. The last commit renames "blocks" to "digits" for term consistency. Feel free to voice your opinion if this change is too noisy.

The WOTS API is still very general, as digits can be 5 bits, 6 bits or 7 bits long. This makes it hard to write documentation, because these corner cases are weird. I plan a follow-up PR that makes these general WOTS implementations private.
author_association: CONTRIBUTOR
user: login: uncomputable
created_at: 2025-05-02T13:54:52Z
body: This PR focuses on the generic WOTS implementation. I plan to add test vectors in a follow-up PR for the 3 specific WOTS implementations that we use in BitVM.
author_association: CONTRIBUTOR