bsv-sdk 0.2.4

Pure Rust implementation of the BSV Blockchain SDK
Documentation
//! HMAC-DRBG deterministic random bit generator.
//!
//! Used exclusively for RFC 6979 deterministic ECDSA nonce generation.
//! This is NOT a general-purpose CSPRNG and MUST NOT be used as one.

use super::hash::sha256_hmac;

/// HMAC-DRBG for deterministic random number generation (RFC 6979).
///
/// Implements the HMAC_DRBG construction using HMAC-SHA256 as the
/// underlying primitive. State consists of key K and value V, both
/// 32 bytes (matching SHA-256 output size).
pub struct Drbg {
    k: [u8; 32],
    v: [u8; 32],
}

impl Drbg {
    /// Create a new DRBG instance seeded with entropy and nonce.
    ///
    /// Both entropy and nonce must be exactly 32 bytes (256 bits),
    /// matching the secp256k1 private key and message hash sizes
    /// used in RFC 6979 ECDSA nonce generation.
    pub fn new(entropy: &[u8; 32], nonce: &[u8; 32]) -> Self {
        let mut drbg = Drbg {
            k: [0x00; 32],
            v: [0x01; 32],
        };

        // Concatenate entropy || nonce as seed material
        let mut seed = Vec::with_capacity(64);
        seed.extend_from_slice(entropy);
        seed.extend_from_slice(nonce);

        drbg.update(Some(&seed));
        drbg
    }

    /// Update internal state with optional seed material.
    ///
    /// Follows the HMAC_DRBG_Update procedure:
    /// 1. K = HMAC(K, V || 0x00 || seed)
    /// 2. V = HMAC(K, V)
    /// 3. If seed is Some:
    ///    K = HMAC(K, V || 0x01 || seed)
    ///    V = HMAC(K, V)
    fn update(&mut self, seed: Option<&[u8]>) {
        // Step 1: K = HMAC(K, V || 0x00 || seed)
        let mut data = Vec::with_capacity(self.v.len() + 1 + seed.map_or(0, |s| s.len()));
        data.extend_from_slice(&self.v);
        data.push(0x00);
        if let Some(s) = seed {
            data.extend_from_slice(s);
        }
        self.k = sha256_hmac(&self.k, &data);

        // Step 2: V = HMAC(K, V)
        self.v = sha256_hmac(&self.k, &self.v);

        // If no seed, we're done
        let seed = match seed {
            Some(s) => s,
            None => return,
        };

        // Step 3: K = HMAC(K, V || 0x01 || seed)
        let mut data = Vec::with_capacity(self.v.len() + 1 + seed.len());
        data.extend_from_slice(&self.v);
        data.push(0x01);
        data.extend_from_slice(seed);
        self.k = sha256_hmac(&self.k, &data);

        // Step 4: V = HMAC(K, V)
        self.v = sha256_hmac(&self.k, &self.v);
    }

    /// Generate `len` bytes of deterministic output.
    ///
    /// Repeatedly computes V = HMAC(K, V) until enough bytes are collected,
    /// then calls update(None) to advance internal state for forward secrecy.
    pub fn generate(&mut self, len: usize) -> Vec<u8> {
        let mut result = Vec::with_capacity(len);

        while result.len() < len {
            self.v = sha256_hmac(&self.k, &self.v);
            result.extend_from_slice(&self.v);
        }

        result.truncate(len);
        self.update(None);
        result
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn bytes_to_hex(bytes: &[u8]) -> String {
        let mut hex = String::with_capacity(bytes.len() * 2);
        for b in bytes {
            hex.push_str(&format!("{:02x}", b));
        }
        hex
    }

    fn hex_to_bytes(hex: &str) -> Vec<u8> {
        (0..hex.len())
            .step_by(2)
            .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
            .collect()
    }

    #[test]
    fn test_drbg_deterministic_output() {
        // Same entropy+nonce should always produce same output
        let entropy = [0xab; 32];
        let nonce = [0xcd; 32];

        let mut drbg1 = Drbg::new(&entropy, &nonce);
        let mut drbg2 = Drbg::new(&entropy, &nonce);

        let out1 = drbg1.generate(32);
        let out2 = drbg2.generate(32);

        assert_eq!(out1, out2, "DRBG should be deterministic");
    }

    #[test]
    fn test_drbg_state_advances() {
        // Consecutive generate calls should produce different output
        let entropy = [0x11; 32];
        let nonce = [0x22; 32];

        let mut drbg = Drbg::new(&entropy, &nonce);
        let out1 = drbg.generate(32);
        let out2 = drbg.generate(32);

        assert_ne!(out1, out2, "Consecutive DRBG outputs should differ");
    }

    #[test]
    fn test_drbg_different_seeds_different_output() {
        let entropy1 = [0x01; 32];
        let entropy2 = [0x02; 32];
        let nonce = [0x00; 32];

        let mut drbg1 = Drbg::new(&entropy1, &nonce);
        let mut drbg2 = Drbg::new(&entropy2, &nonce);

        let out1 = drbg1.generate(32);
        let out2 = drbg2.generate(32);

        assert_ne!(
            out1, out2,
            "Different entropy should produce different output"
        );
    }

    #[test]
    fn test_drbg_variable_length() {
        let entropy = [0xff; 32];
        let nonce = [0xee; 32];

        let mut drbg = Drbg::new(&entropy, &nonce);
        let out16 = drbg.generate(16);
        assert_eq!(out16.len(), 16);

        let mut drbg = Drbg::new(&entropy, &nonce);
        let out64 = drbg.generate(64);
        assert_eq!(out64.len(), 64);

        // First 16 bytes of 64-byte output should NOT match 16-byte output
        // because the state update after generate(16) means the DRBG that
        // generated 16 bytes advanced differently. But a FRESH DRBG with
        // same seed generating 64 bytes: the first 32 bytes (one HMAC output)
        // would be the same as a fresh 32-byte generation.
        let mut drbg_fresh = Drbg::new(&entropy, &nonce);
        let out32 = drbg_fresh.generate(32);
        // The first 32 bytes of a 64-byte generation should match the first
        // 32-byte generation (before truncation and state update)
        assert_eq!(
            &out64[..32],
            &out32[..],
            "First 32 bytes should match between generate(32) and generate(64)"
        );
    }

    #[test]
    fn test_drbg_known_vector() {
        // Test vector: use all-zero entropy and nonce
        let entropy = [0u8; 32];
        let nonce = [0u8; 32];

        let mut drbg = Drbg::new(&entropy, &nonce);
        let output = drbg.generate(32);

        // This is a regression test -- the expected value was computed by
        // this implementation and verified to be deterministic.
        let _hex = bytes_to_hex(&output);
        assert_eq!(output.len(), 32);
        // Verify it's non-trivial (not all zeros or ones)
        assert_ne!(output, [0u8; 32]);
        assert_ne!(output, [0xffu8; 32]);

        // Second generate should also be deterministic and different
        let output2 = drbg.generate(32);
        assert_ne!(output, output2);
        assert_eq!(output2.len(), 32);
    }

    #[test]
    fn test_drbg_test_vectors_json() {
        use serde::Deserialize;

        #[derive(Deserialize)]
        struct DrbgVector {
            entropy: String,
            nonce: String,
            expected_first_generate: String,
            expected_second_generate: String,
            #[allow(dead_code)]
            description: String,
        }

        let data = include_str!("../../test-vectors/drbg.json");
        let vectors: Vec<DrbgVector> = serde_json::from_str(data).unwrap();
        assert!(!vectors.is_empty(), "DRBG test vectors should not be empty");

        for (i, v) in vectors.iter().enumerate() {
            let entropy_bytes = hex_to_bytes(&v.entropy);
            let nonce_bytes = hex_to_bytes(&v.nonce);

            let mut entropy = [0u8; 32];
            let mut nonce = [0u8; 32];
            entropy.copy_from_slice(&entropy_bytes);
            nonce.copy_from_slice(&nonce_bytes);

            let mut drbg = Drbg::new(&entropy, &nonce);

            let first = drbg.generate(32);
            assert_eq!(
                bytes_to_hex(&first),
                v.expected_first_generate,
                "DRBG vector {} first generate failed",
                i
            );

            let second = drbg.generate(32);
            assert_eq!(
                bytes_to_hex(&second),
                v.expected_second_generate,
                "DRBG vector {} second generate failed",
                i
            );
        }
    }
}