#![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");
use core::str::FromStr;
use bitcoin::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, Network};
use bitcoin::blockdata::block::Header as BlockHeader;
use bitcoin::hash_types::FilterHeader;
#[cfg(feature = "use-serde")]
use serde::{Deserialize, Serialize};
pub mod filter;
pub mod proof;
#[cfg(all(feature = "source", feature = "std"))]
pub mod source;
pub mod spv;
pub mod util;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "use-serde", derive(Serialize, Deserialize))]
pub struct Attestation {
pub block_hash: BlockHash,
pub block_height: u32,
pub filter_header: FilterHeader,
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 {
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)
}
}
#[derive(Clone)]
#[cfg_attr(feature = "use-serde", derive(Serialize, Deserialize))]
pub struct SignedAttestation {
pub attestation: Attestation,
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 {
pub fn verify(&self, pubkey: &PublicKey, secp: &Secp256k1<All>) -> bool {
let xpubkey = secp256k1::XOnlyPublicKey::from(pubkey.clone());
let message = Message::from_digest(self.attestation.hash().to_byte_array());
secp.verify_schnorr(&self.signature, &message, &xpubkey)
.is_ok()
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "use-serde", derive(Serialize, Deserialize))]
pub struct OracleSetup {
pub network: Network,
pub start_block: u32,
pub public_key: PublicKey,
}
pub const CHECKPOINTS_BITCOIN: &[(u32, &str, &str, &str)] = &[
(
717500,
"0000000000000000000226bd443784c02a0ad04be78d5966ecccc727129454c8",
"5494f5e59cdb4816d69f9777d936a7dab5826e1beb8b1e1c3f88cb54b6ff49e4",
"006000200080f7688389afc62f5e659a5cc96f955e3766a883520100000000000000000077b008af850244de0974ed664f91788dff044815d59f40c8d1b885df9de0ce888c67d761ab980b173be525b5",
),
(
770000,
"00000000000000000004ea65f5ffe55bfc0adbc001d3a8e154cc9f19da959ba8",
"b9bfb0048201dc42c7a35c479570925959782be3736a1bc32d072e84e9bf16e4",
"00004020574e3006158b4eb0bbb58d9705026baa85f36229d5d6050000000000000000007bbc932b46eb980ad91425923b8548b2c698f70d17b84ed99d7e91899d7ea64b91a3b26390f5071788d1480e",
),
(
812000,
"00000000000000000000ebd093127365a54c5c9332362757426c8b9fac719e40",
"bb46d92c9b542c2e353072a49dc5d0f96cc49dfe18ff32b5389de1ebaab9c7a3",
"000020205491c41bff43317d8cc7e5969c820f5673934294e35f00000000000000000000b1c05152830de98123de79c4aac7301620f7e622d2c4c67236bb48ce85403d8bab4729650fe90417758d6ddc",
),
(
849700,
"000000000000000000018dd05c43695521b8adbb61e686644933fb4d08bf2ed6",
"b8eef088c0bb4684f6d12747d7816aee24017d20b67ae5c663dd45b64811c87f",
"00a0632521b2a87f158aef31a1b7f1336fa61c70473c90c2557801000000000000000000af6dc886c386d8bf479e1ff1013eeae5ab19cd595e276ffe284fceda9358b8eb64847d66255d0317c8ee3111",
)
];
pub const CHECKPOINTS_TESTNET: &[(u32, &str, &str, &str)] = &[
(
2425000,
"00000000898adbf7816acbb9bc46f58260c7e58a9a0018dd22a4bb20dae2b12f",
"0223b6153cc868f8a3bfa7aa17e8db28ee58300a1a7e584905bf834dec1348ca",
"0000002075212b88091673403f6d7efb9d43ec5004be49ad59a1b3ccf4e60000000000001f0989025687d3ce94fabd6837cc2da9c94122561be7813ea13968079813c6888d351664ffff001df6dc1b1f",
),
(
2469000,
"00000000000000a359f92219e8e3634ae3d0fd48f1978a4c58f8eb8af358f069",
"b95e23ecf2e90f58c430070452100818e3b2a923e7d010db14bcb1ddd192896b",
"000000205da9faffdc6ee5ad3a47579e3048770dcf3600558bd33acead0000000000000031d6ec4fc91ad4498f6dbf0406a7fd2fd4aca17d7d40ecdc2a5bcf32c42b6d3b78ecc2647bdd001a49755bd5",
),
(
2542800,
"00000000000037f642b41b676f2fbeb26758cb935e4748de64972b08c19ac265",
"16547889b0e0116531f807af394f7d4f230eb3789b25f8298772ff14a3f74359",
"0000e02022abb275f6de8b255f070ac6ffcb7c9e81a28b28605cf7861b1a000000000000dd7810a2fb2de9cf67747d16b44e5945e55deb04fffe7a234897138937c4705cc3bc7a65ffff001d65b009f3",
),
(
2577600,
"000000000000001077f67343afc9a58e5f8faeca3bf0c2b7497fe8c9c9c540a7",
"0cbc28a0b1ab22ea1e2728521e042ae2582ec3da7e706f60fd52317f44b34724",
"0000a020468ec3433406edd4e501bd3d9d548cd41b0cde1d2438c5c91c0000000000000011cad3107167cadb1c512182b953cd5a9c64202c152fe9d370589b42d994b85a1b4cc665b575211984b5c8e4",
),
(
2862000,
"00000000000003ab245d9f16d6add79ace94f2538e36d25705819fcb4b481e0a",
"1e1cbedecdba81ebaf4c2577e76304e0344471cbdddf1a5ea282ae375a08339c",
"0000a020917a8253a0c5c8b82c7825ce2650c94bfeb0267ea1193ba8591a39ef0000000099003d46370e4f1efe93b18a9e365cb5fe24e191b2f8220b7cd2bfa8bdaadffb39a27d66fcff031a04107bd9",
),
];
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_str(block_hash_hex).unwrap();
let filter_header = FilterHeader::from_str(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)
}
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() {
for checkpoint in CHECKPOINTS_BITCOIN {
decode_checkpoint(*checkpoint);
}
for checkpoint in CHECKPOINTS_TESTNET {
decode_checkpoint(*checkpoint);
}
}
}