//! An oracle for the Bitcoin network that produces attestations to the chain tip and to the outputs spent in each block.

#![forbid(unsafe_code)]
#![warn(rustdoc::broken_intra_doc_links)]
#![warn(missing_docs)]
#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]

#[cfg(not(any(feature = "std", feature = "no-std")))]
compile_error!("at least one of the `std` or `no-std` features must be enabled");

#[cfg(not(feature = "std"))]
extern crate core2;

#[cfg(not(feature = "std"))]
use core2::io;
#[cfg(feature = "std")]
use std::io;

extern crate alloc;
extern crate core;

pub use bitcoin;

use alloc::vec::Vec;
use bitcoin::consensus::{Decodable, Encodable};
use bitcoin::hashes::hex::FromHex;
use bitcoin::hashes::sha256::Hash as Sha256Hash;
use bitcoin::hashes::{Hash, HashEngine};
use bitcoin::secp256k1::constants::SCHNORR_SIGNATURE_SIZE;
use bitcoin::secp256k1::schnorr::Signature;
use bitcoin::secp256k1::{All, Message, PublicKey, Secp256k1};
use bitcoin::{secp256k1, BlockHash, BlockHeader, FilterHeader, Network};
#[cfg(feature = "use-serde")]
use serde::{Deserialize, Serialize};

/// A GCS filter for spent outpoints in a block
pub mod filter;
/// Oracle proof that outpoints were spent or unspent in a block
pub mod proof;
/// Attestation source
#[cfg(all(feature = "source", feature = "std"))]
pub mod source;
/// SPV proof that transactions are included in a block
pub mod spv;
/// Utilities
pub mod util;

/// An attestation by the oracle to a certain chain tip and spend filter at a certain time
#[derive(Clone, Debug)]
#[cfg_attr(feature = "use-serde", derive(Serialize, Deserialize))]
pub struct Attestation {
    /// The block hash of the chain tip
    pub block_hash: BlockHash,
    /// The block height of the chain tip
    pub block_height: u32,
    /// The filter header of the chain tip (hash of the previous header and the GCS filter)
    pub filter_header: FilterHeader,
    /// The time the attestation was created, as seconds since the unix epoch
    pub time: u64,
}

impl Decodable for Attestation {
    fn consensus_decode<D: io::Read + ?Sized>(
        d: &mut D,
    ) -> Result<Self, bitcoin::consensus::encode::Error> {
        let block_hash = Decodable::consensus_decode(d)?;
        let block_height = Decodable::consensus_decode(d)?;
        let filter_header = Decodable::consensus_decode(d)?;
        let time = Decodable::consensus_decode(d)?;
        Ok(Attestation {
            block_hash,
            block_height,
            filter_header,
            time,
        })
    }
}

impl Encodable for Attestation {
    fn consensus_encode<S: io::Write + ?Sized>(&self, s: &mut S) -> Result<usize, io::Error> {
        let mut len = 0;
        len += self.block_hash.consensus_encode(s)?;
        len += self.block_height.consensus_encode(s)?;
        len += self.filter_header.consensus_encode(s)?;
        len += self.time.consensus_encode(s)?;
        Ok(len)
    }
}

impl Attestation {
    /// Signature hash for the attestation
    pub fn hash(&self) -> Sha256Hash {
        let mut engine = Sha256Hash::engine();
        engine.input(&self.block_hash[..]);
        engine.input(&self.block_height.to_le_bytes());
        engine.input(&self.filter_header[..]);
        engine.input(&self.time.to_le_bytes());
        Sha256Hash::from_engine(engine)
    }
}

/// A signed attestation
#[derive(Clone)]
#[cfg_attr(feature = "use-serde", derive(Serialize, Deserialize))]
pub struct SignedAttestation {
    /// The attestation
    pub attestation: Attestation,
    /// The schnorr signature over the attestation, serialized as TBD
    pub signature: Signature,
}

impl Encodable for SignedAttestation {
    fn consensus_encode<S: io::Write + ?Sized>(&self, s: &mut S) -> Result<usize, io::Error> {
        let mut len = 0;
        len += self.attestation.consensus_encode(s)?;
        s.write_all(&self.signature[..])?;
        len += SCHNORR_SIGNATURE_SIZE;
        Ok(len)
    }
}

impl Decodable for SignedAttestation {
    fn consensus_decode<D: io::Read + ?Sized>(
        d: &mut D,
    ) -> Result<Self, bitcoin::consensus::encode::Error> {
        let attestation = Decodable::consensus_decode(d)?;
        let mut signature = [0u8; SCHNORR_SIGNATURE_SIZE];
        d.read_exact(&mut signature)?;
        let signature = Signature::from_slice(&signature).expect("signature is valid");
        Ok(SignedAttestation {
            attestation,
            signature,
        })
    }
}

impl SignedAttestation {
    /// Verify the attestation signature
    pub fn verify(&self, pubkey: &PublicKey, secp: &Secp256k1<All>) -> bool {
        let xpubkey = secp256k1::XOnlyPublicKey::from(pubkey.clone());
        let message = Message::from_slice(&self.attestation.hash()).unwrap();
        secp.verify_schnorr(&self.signature, &message, &xpubkey)
            .is_ok()
    }
}

/// Basic information about an oracle
#[derive(Clone, Debug)]
#[cfg_attr(feature = "use-serde", derive(Serialize, Deserialize))]
pub struct OracleSetup {
    /// The network
    pub network: Network,
    /// The first block the oracle produced attestations for.
    /// Note that the `prev_filter_header` is defined as all zeros for this block.
    /// This parameter cannot be changed without invalidating all the oracle's attestations.
    pub start_block: u32,
    /// The oracle public key
    pub public_key: PublicKey,
}

/// Mainnet checkpoints.
/// (height, block_hash, filter_header, block_header)
/// Note that we start from the *first* checkpoint, not the last one.
pub const CHECKPOINTS_BITCOIN: &[(u32, &str, &str, &str)] = &[
    (
        717500,
        "0000000000000000000226bd443784c02a0ad04be78d5966ecccc727129454c8",
        "5494f5e59cdb4816d69f9777d936a7dab5826e1beb8b1e1c3f88cb54b6ff49e4",
        "006000200080f7688389afc62f5e659a5cc96f955e3766a883520100000000000000000077b008af850244de0974ed664f91788dff044815d59f40c8d1b885df9de0ce888c67d761ab980b173be525b5",
    ),
    (
        770000,
        "00000000000000000004ea65f5ffe55bfc0adbc001d3a8e154cc9f19da959ba8",
        "b9bfb0048201dc42c7a35c479570925959782be3736a1bc32d072e84e9bf16e4",
        "00004020574e3006158b4eb0bbb58d9705026baa85f36229d5d6050000000000000000007bbc932b46eb980ad91425923b8548b2c698f70d17b84ed99d7e91899d7ea64b91a3b26390f5071788d1480e",
    ),
];

/// Testnet checkpoints.
/// (height, block_hash, filter_header, block_header)
/// Note that --use-checkpoint starts from the *first* checkpoint, not the last one.
pub const CHECKPOINTS_TESTNET: &[(u32, &str, &str, &str)] = &[
    (
        2425000,
        "00000000898adbf7816acbb9bc46f58260c7e58a9a0018dd22a4bb20dae2b12f",
        "0223b6153cc868f8a3bfa7aa17e8db28ee58300a1a7e584905bf834dec1348ca",
        "0000002075212b88091673403f6d7efb9d43ec5004be49ad59a1b3ccf4e60000000000001f0989025687d3ce94fabd6837cc2da9c94122561be7813ea13968079813c6888d351664ffff001df6dc1b1f",
    ),
    (
        2469000,
        "00000000000000a359f92219e8e3634ae3d0fd48f1978a4c58f8eb8af358f069",
        "b95e23ecf2e90f58c430070452100818e3b2a923e7d010db14bcb1ddd192896b",
        "000000205da9faffdc6ee5ad3a47579e3048770dcf3600558bd33acead0000000000000031d6ec4fc91ad4498f6dbf0406a7fd2fd4aca17d7d40ecdc2a5bcf32c42b6d3b78ecc2647bdd001a49755bd5",
    ),
];

/// Decode a checkpoint tuple
pub fn decode_checkpoint(
    checkpoint: (u32, &str, &str, &str),
) -> (u32, BlockHash, FilterHeader, BlockHeader) {
    let (height, block_hash_hex, filter_header_hex, block_header_hex) = checkpoint;
    let block_hash = BlockHash::from_hex(block_hash_hex).unwrap();
    let filter_header = FilterHeader::from_hex(filter_header_hex).unwrap();
    let block_header_bytes = Vec::from_hex(block_header_hex).unwrap();
    let block_header = BlockHeader::consensus_decode(&mut block_header_bytes.as_slice()).unwrap();
    assert_eq!(block_header.block_hash(), block_hash);
    (height, block_hash, filter_header, block_header)
}

/// Get the latest checkpoint for the given network
pub fn get_latest_checkpoint(
    network: Network,
) -> Option<(u32, BlockHash, FilterHeader, BlockHeader)> {
    let checkpoints = match network {
        Network::Bitcoin => CHECKPOINTS_BITCOIN,
        Network::Testnet => CHECKPOINTS_TESTNET,
        _ => return None,
    };
    Some(decode_checkpoint(checkpoints[checkpoints.len() - 1]))
}

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

    #[test]
    fn checkpoints_valid_test() {
        // we depend on the assert inside decode_checkpoint
        for checkpoint in CHECKPOINTS_BITCOIN {
            decode_checkpoint(*checkpoint);
        }

        for checkpoint in CHECKPOINTS_TESTNET {
            decode_checkpoint(*checkpoint);
        }
    }
}
