veil-crypto 1.3.0

Pure Rust secp256k1 cryptography for Veil blockchain with WASM bindings
Documentation
//! ECDSA Signing for CT Inputs
//!
//! This module provides ECDSA signature creation for spending CT (Confidential Transaction)
//! outputs. CT outputs use standard Bitcoin-style scriptPubKey and are signed with ECDSA,
//! unlike RingCT outputs which use MLSAG ring signatures.

use k256::ecdsa::{
    signature::hazmat::PrehashSigner,
    Signature, SigningKey,
};
use crate::{Result, VeilCryptoError};

/// Sign a 32-byte message hash with ECDSA
///
/// This produces a DER-encoded ECDSA signature suitable for use in
/// Bitcoin-style scriptSig.
///
/// # Arguments
///
/// * `message_hash` - 32-byte hash to sign (e.g., sighash)
/// * `secret_key` - 32-byte private key
///
/// # Returns
///
/// DER-encoded signature bytes
///
/// # Example
///
/// ```ignore
/// let sighash = compute_sighash(&tx, input_index, &script_pubkey, &amount);
/// let signature = ecdsa_sign(&sighash, &private_key)?;
/// // Append SIGHASH_ALL byte
/// let mut script_sig = signature;
/// script_sig.push(0x01); // SIGHASH_ALL
/// ```
pub fn ecdsa_sign(message_hash: &[u8], secret_key: &[u8]) -> Result<Vec<u8>> {
    if message_hash.len() != 32 {
        return Err(VeilCryptoError::InvalidInput(
            "Message hash must be 32 bytes".to_string(),
        ));
    }
    if secret_key.len() != 32 {
        return Err(VeilCryptoError::InvalidSecretKey);
    }

    // Create signing key from secret
    let signing_key = SigningKey::from_bytes(secret_key.into())
        .map_err(|_| VeilCryptoError::InvalidSecretKey)?;

    // Sign the message hash (low-S normalized automatically by k256)
    let signature: Signature = signing_key
        .sign_prehash(message_hash)
        .map_err(|e| VeilCryptoError::SignatureError(format!("ECDSA signing failed: {}", e)))?;

    // Return DER-encoded signature
    Ok(signature.to_der().as_bytes().to_vec())
}

/// Sign and return the signature in compact (r || s) format
///
/// This returns a 64-byte signature (32 bytes r, 32 bytes s).
/// Useful for contexts that don't need DER encoding.
///
/// # Arguments
///
/// * `message_hash` - 32-byte hash to sign
/// * `secret_key` - 32-byte private key
///
/// # Returns
///
/// 64-byte compact signature (r || s)
pub fn ecdsa_sign_compact(message_hash: &[u8], secret_key: &[u8]) -> Result<Vec<u8>> {
    if message_hash.len() != 32 {
        return Err(VeilCryptoError::InvalidInput(
            "Message hash must be 32 bytes".to_string(),
        ));
    }
    if secret_key.len() != 32 {
        return Err(VeilCryptoError::InvalidSecretKey);
    }

    let signing_key = SigningKey::from_bytes(secret_key.into())
        .map_err(|_| VeilCryptoError::InvalidSecretKey)?;

    let signature: Signature = signing_key
        .sign_prehash(message_hash)
        .map_err(|e| VeilCryptoError::SignatureError(format!("ECDSA signing failed: {}", e)))?;

    // Return compact format (64 bytes)
    Ok(signature.to_bytes().to_vec())
}

/// Get the public key for a secret key (for verification)
///
/// # Arguments
///
/// * `secret_key` - 32-byte private key
///
/// # Returns
///
/// 33-byte compressed public key
pub fn ecdsa_get_public_key(secret_key: &[u8]) -> Result<Vec<u8>> {
    if secret_key.len() != 32 {
        return Err(VeilCryptoError::InvalidSecretKey);
    }

    let signing_key = SigningKey::from_bytes(secret_key.into())
        .map_err(|_| VeilCryptoError::InvalidSecretKey)?;

    let public_key = signing_key.verifying_key();
    let encoded = public_key.to_encoded_point(true); // compressed

    Ok(encoded.as_bytes().to_vec())
}

#[cfg(test)]
mod tests {
    use super::*;
    use k256::ecdsa::{signature::hazmat::PrehashVerifier, VerifyingKey};

    #[test]
    fn test_ecdsa_sign_and_verify() {
        // Test vector: random secret key
        let secret_key = hex::decode(
            "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"
        ).unwrap();

        // Message hash (could be a sighash)
        let message_hash = hex::decode(
            "9302bda273a887cb40c13e02a50b4071a31fd3aae3ae04021b0b843dd61ad18e"
        ).unwrap();

        // Sign
        let signature_der = ecdsa_sign(&message_hash, &secret_key).unwrap();

        // Verify the signature is valid DER
        assert!(signature_der.len() >= 68 && signature_der.len() <= 72);
        assert_eq!(signature_der[0], 0x30); // DER sequence tag

        // Get public key and verify
        let pubkey_bytes = ecdsa_get_public_key(&secret_key).unwrap();
        assert_eq!(pubkey_bytes.len(), 33);

        // Parse and verify signature
        let signature = Signature::from_der(&signature_der).unwrap();
        let verifying_key = VerifyingKey::from_sec1_bytes(&pubkey_bytes).unwrap();
        assert!(verifying_key.verify_prehash(&message_hash, &signature).is_ok());
    }

    #[test]
    fn test_ecdsa_sign_compact() {
        let secret_key = hex::decode(
            "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"
        ).unwrap();
        let message_hash = hex::decode(
            "9302bda273a887cb40c13e02a50b4071a31fd3aae3ae04021b0b843dd61ad18e"
        ).unwrap();

        let signature_compact = ecdsa_sign_compact(&message_hash, &secret_key).unwrap();

        // Compact signature is always 64 bytes
        assert_eq!(signature_compact.len(), 64);
    }

    #[test]
    fn test_ecdsa_invalid_inputs() {
        let valid_secret = [0x01u8; 32];
        let valid_hash = [0x02u8; 32];

        // Invalid message hash length
        assert!(ecdsa_sign(&[0u8; 31], &valid_secret).is_err());
        assert!(ecdsa_sign(&[0u8; 33], &valid_secret).is_err());

        // Invalid secret key length
        assert!(ecdsa_sign(&valid_hash, &[0u8; 31]).is_err());
        assert!(ecdsa_sign(&valid_hash, &[0u8; 33]).is_err());

        // Zero secret key (invalid)
        assert!(ecdsa_sign(&valid_hash, &[0u8; 32]).is_err());
    }

    #[test]
    fn test_low_s_normalization() {
        // k256 automatically produces low-S signatures (BIP 62)
        // This is important for Bitcoin compatibility
        let secret_key = hex::decode(
            "0000000000000000000000000000000000000000000000000000000000000001"
        ).unwrap();
        let message_hash = [0xffu8; 32];

        let signature_compact = ecdsa_sign_compact(&message_hash, &secret_key).unwrap();

        // Check S is in low range (first byte of S should be < 0x80 for low-S)
        // S is the second 32 bytes
        let s_first_byte = signature_compact[32];
        assert!(s_first_byte < 0x80, "Signature S value should be in low range");
    }
}