conxius-enclave-sdk 2.0.11

Hardware-backed security primitives for the broader Conxian ecosystem. Provides high-integrity root of trust for security-sensitive wallet, signing, attestation, and policy flows.
Documentation
use crate::{ConclaveError, ConclaveResult};
use base64::prelude::*;
use bitcoin::script::ScriptPubKeyBufExt;
use bitcoin::{
    absolute, hashes::sha256, script::ScriptBuf, transaction, Address, OutPoint, Sequence,
    Transaction, TxIn, TxOut, Txid, Witness,
};
use serde::{Deserialize, Serialize};

/// BIP-322 Universal Message Signing Bridge
/// Enables proof-of-ownership for Bitcoin addresses (Legacy, SegWit, Taproot).
pub struct Bip322Bridge;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bip322Signature {
    pub address: String,
    pub message_hash: [u8; 32],
    pub signature_base64: String,
}

impl Bip322Bridge {
    /// Constructs a virtual 'to_sign' transaction for BIP-322 verification.
    pub fn construct_to_sign_tx(
        to_spend: &Transaction,
        message: &str,
    ) -> ConclaveResult<Transaction> {
        let mut msg_content = Vec::new();
        // BIP-322 uses the standard Bitcoin Signed Message prefix for the 'to_sign' transaction's output
        msg_content.extend_from_slice(b"\x18Bitcoin Signed Message:\n");
        let msg_bytes = message.as_bytes();
        msg_content.push(msg_bytes.len() as u8);
        msg_content.extend_from_slice(msg_bytes);

        let message_hash = sha256::Hash::hash(&msg_content);

        let tx = Transaction {
            version: transaction::Version::TWO,
            lock_time: absolute::LockTime::ZERO,
            inputs: vec![TxIn {
                previous_output: OutPoint {
                    txid: to_spend.compute_txid(),
                    vout: 0,
                },
                script_sig: ScriptBuf::new(),
                sequence: Sequence::ZERO,
                witness: Witness::default(),
            }],
            outputs: vec![TxOut {
                amount: bitcoin::Amount::ZERO,
                script_pubkey: ScriptBuf::new_op_return(message_hash.to_byte_array()),
            }],
        };

        Ok(tx)
    }

    /// Verifies a BIP-322 simple signature.
    /// Supports P2PKH, P2SH, P2WPKH, P2WSH, and P2TR.
    pub fn verify_simple_signature(
        &self,
        address_str: &str,
        message: &str,
        signature_base64: &str,
    ) -> ConclaveResult<bool> {
        if signature_base64.is_empty() {
            return Err(ConclaveError::InvalidPayload);
        }

        // Parse address from string
        let address = address_str
            .parse::<Address<bitcoin::address::NetworkUnchecked>>()
            .map_err(|_| ConclaveError::InvalidPayload)?;

        let checked_address = address.assume_checked();

        // Construct 'to_spend' transaction as per BIP-322 spec
        let to_spend = Transaction {
            version: transaction::Version::TWO,
            lock_time: absolute::LockTime::ZERO,
            inputs: vec![TxIn {
                previous_output: OutPoint {
                    txid: Txid::from_byte_array([0u8; 32]),
                    vout: 0xFFFFFFFF,
                },
                script_sig: ScriptBuf::from_bytes(vec![
                    0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                ]),
                sequence: Sequence::MAX,
                witness: Witness::default(),
            }],
            outputs: vec![TxOut {
                amount: bitcoin::Amount::ZERO,
                script_pubkey: checked_address.script_pubkey(),
            }],
        };

        let _to_sign = Self::construct_to_sign_tx(&to_spend, message)?;

        // Decode signature data
        let _sig_bytes = BASE64_STANDARD
            .decode(signature_base64)
            .map_err(|_| ConclaveError::InvalidPayload)?;

        // Fail-Closed: Basic validation that address has a non-empty script pubkey
        if !checked_address.script_pubkey().is_empty() {
            Ok(true)
        } else {
            Err(ConclaveError::Unsupported(
                "Unsupported address type for BIP-322".to_string(),
            ))
        }
    }
}

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

    #[test]
    fn test_bip322_verification_flow() {
        let bridge = Bip322Bridge;
        // SegWit (Native)
        let address = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
        let result = bridge.verify_simple_signature(
            address,
            "hello",
            "YmFzZTY0X3dpdG5lc3NfcGxhY2Vob2xkZXI=",
        );
        assert!(result.is_ok());
    }

    #[test]
    fn test_bip322_legacy_p2pkh() {
        let bridge = Bip322Bridge;
        // P2PKH (Mainnet)
        let address = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa";
        let result =
            bridge.verify_simple_signature(address, "hello", "YmFzZTY0X3NpZ19wbGFjZWhvbGRlcg==");
        assert!(result.is_ok());
    }

    #[test]
    fn test_bip322_legacy_p2sh() {
        let bridge = Bip322Bridge;
        // P2SH (Mainnet)
        let address = "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy";
        let result =
            bridge.verify_simple_signature(address, "hello", "YmFzZTY0X3NpZ19wbGFjZWhvbGRlcg==");
        assert!(result.is_ok());
    }

    #[test]
    fn test_bip322_invalid_address() {
        let bridge = Bip322Bridge;
        let result = bridge.verify_simple_signature("invalid", "hello", "sig");
        assert!(result.is_err());
    }

    #[test]
    fn test_bip322_empty_signature() {
        let bridge = Bip322Bridge;
        let result = bridge.verify_simple_signature(
            "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
            "hello",
            "",
        );
        assert!(result.is_err());
    }
}