458 lines
13 KiB
Rust
458 lines
13 KiB
Rust
use crate::header_store::*;
|
||
use crate::utils::*;
|
||
use serde_derive::{Deserialize, Serialize};
|
||
use solana_sdk::pubkey::Pubkey;
|
||
use std::{error, fmt};
|
||
|
||
pub type BitcoinTxHash = [u8; 32];
|
||
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||
pub struct BlockHeader {
|
||
// Bitcoin network version
|
||
pub version: u32,
|
||
// Previous block's hash/digest
|
||
pub parent: [u8; 32],
|
||
// merkle Root of the block, proofEntry side should be None
|
||
pub merkle_root: ProofEntry,
|
||
// the blocktime associate with the block
|
||
pub time: u32,
|
||
// An encoded version of the target threshold this block’s header hash must be less than or equal to.
|
||
pub nbits: [u8; 4],
|
||
// block header's nonce
|
||
pub nonce: [u8; 4],
|
||
// Block hash
|
||
pub blockhash: [u8; 32],
|
||
}
|
||
|
||
impl BlockHeader {
|
||
pub fn new(header: &[u8; 80], blockhash: &[u8; 32]) -> Result<BlockHeader, SpvError> {
|
||
let mut va: [u8; 4] = [0; 4];
|
||
va.copy_from_slice(&header[0..4]);
|
||
let version = u32::from_le_bytes(va);
|
||
|
||
let mut ph: [u8; 32] = [0; 32];
|
||
ph.copy_from_slice(&header[4..36]);
|
||
let parent = ph;
|
||
// extract merkle root in internal byte order
|
||
let mut mrr: [u8; 32] = [0; 32];
|
||
mrr.copy_from_slice(&header[36..68]);
|
||
let merkle_root = ProofEntry {
|
||
hash: mrr,
|
||
side: EntrySide::Root,
|
||
};
|
||
// timestamp associate with the block
|
||
let mut bt: [u8; 4] = [0; 4];
|
||
bt.copy_from_slice(&header[68..72]);
|
||
let time = u32::from_le_bytes(bt);
|
||
|
||
// nbits field is an encoded version of the
|
||
let mut nb: [u8; 4] = [0; 4];
|
||
nb.copy_from_slice(&header[72..76]);
|
||
let nbits = nb;
|
||
|
||
let mut nn: [u8; 4] = [0; 4];
|
||
nn.copy_from_slice(&header[76..80]);
|
||
let nonce = nn;
|
||
|
||
let bh = BlockHeader {
|
||
version,
|
||
parent,
|
||
merkle_root,
|
||
time,
|
||
nbits,
|
||
nonce,
|
||
blockhash: *blockhash,
|
||
};
|
||
Ok(bh)
|
||
}
|
||
|
||
pub fn hexnew(header: &str, blockhash: &str) -> Result<BlockHeader, SpvError> {
|
||
if header.len() != 160 || blockhash.len() != 64 {
|
||
return Err(SpvError::InvalidBlockHeader);
|
||
}
|
||
|
||
match hex::decode(header) {
|
||
Ok(header) => {
|
||
let bhbytes = hex::decode(blockhash)?;
|
||
const SIZE: usize = 80;
|
||
let mut hh = [0; SIZE];
|
||
hh.copy_from_slice(&header[..header.len()]);
|
||
|
||
let mut bhb: [u8; 32] = [0; 32];
|
||
bhb.copy_from_slice(&bhbytes[..bhbytes.len()]);
|
||
|
||
Ok(BlockHeader::new(&hh, &bhb).unwrap())
|
||
}
|
||
Err(e) => Err(SpvError::InvalidBlockHeader),
|
||
}
|
||
}
|
||
|
||
pub fn difficulty(mut self) -> u32 {
|
||
// calculates difficulty from nbits
|
||
let standin: u32 = 123_456_789;
|
||
standin
|
||
}
|
||
}
|
||
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||
pub struct BitcoinTransaction {
|
||
pub inputs: Vec<Input>,
|
||
|
||
pub inputs_num: u64,
|
||
//input utxos
|
||
pub outputs: Vec<Output>,
|
||
|
||
pub outputs_num: u64,
|
||
//output utxos
|
||
pub version: u32,
|
||
//bitcoin network version
|
||
pub lock_time: u32,
|
||
|
||
pub bytes_len: usize,
|
||
}
|
||
|
||
impl BitcoinTransaction {
|
||
pub fn new(txbytes: Vec<u8>) -> Self {
|
||
let mut ver: [u8; 4] = [0; 4];
|
||
ver.copy_from_slice(&txbytes[..4]);
|
||
let version = u32::from_le_bytes(ver);
|
||
|
||
let inputs_num: u64 = decode_variable_int(&txbytes[4..13]).unwrap();
|
||
let vinlen: usize = measure_variable_int(&txbytes[4..13]).unwrap();
|
||
let mut inputstart: usize = 4 + vinlen;
|
||
let mut inputs = Vec::new();
|
||
|
||
if inputs_num > 0 {
|
||
for i in 0..inputs_num {
|
||
let mut input = Input::new(txbytes[inputstart..].to_vec());
|
||
inputstart += input.bytes_len;
|
||
inputs.push(input);
|
||
}
|
||
inputs.to_vec();
|
||
}
|
||
|
||
let outputs_num: u64 = decode_variable_int(&txbytes[inputstart..9 + inputstart]).unwrap();
|
||
let voutlen: usize = measure_variable_int(&txbytes[inputstart..9 + inputstart]).unwrap();
|
||
|
||
let mut outputstart: usize = inputstart + voutlen;
|
||
let mut outputs = Vec::new();
|
||
for i in 0..outputs_num {
|
||
let mut output = Output::new(txbytes[outputstart..].to_vec());
|
||
outputstart += output.bytes_len;
|
||
outputs.push(output);
|
||
}
|
||
|
||
let mut lt: [u8; 4] = [0; 4];
|
||
lt.copy_from_slice(&txbytes[outputstart..4 + outputstart]);
|
||
let lock_time = u32::from_le_bytes(lt);
|
||
|
||
assert_eq!(inputs.len(), inputs_num as usize);
|
||
assert_eq!(outputs.len(), outputs_num as usize);
|
||
|
||
BitcoinTransaction {
|
||
inputs,
|
||
inputs_num,
|
||
outputs,
|
||
outputs_num,
|
||
version,
|
||
lock_time,
|
||
bytes_len: 4 + outputstart,
|
||
}
|
||
}
|
||
pub fn hexnew(hex: String) -> Result<BitcoinTransaction, SpvError> {
|
||
match hex::decode(&hex) {
|
||
Ok(txbytes) => Ok(BitcoinTransaction::new(txbytes)),
|
||
Err(e) => Err(SpvError::ParseError),
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||
pub struct Input {
|
||
pub input_type: InputType,
|
||
// Type of the input
|
||
pub position: u32,
|
||
// position of the tx in its Block
|
||
pub txhash: BitcoinTxHash,
|
||
// hash of the transaction
|
||
pub script_length: u64,
|
||
// length of the spend script
|
||
pub script: Vec<u8>,
|
||
// script bytes
|
||
pub sequence: [u8; 4],
|
||
// length of the input in bytes
|
||
pub bytes_len: usize,
|
||
}
|
||
|
||
impl Input {
|
||
fn new(ibytes: Vec<u8>) -> Self {
|
||
let mut txhash: [u8; 32] = [0; 32];
|
||
txhash.copy_from_slice(&ibytes[..32]);
|
||
|
||
let mut tx_out_index: [u8; 4] = [0; 4];
|
||
tx_out_index.copy_from_slice(&ibytes[32..36]);
|
||
let position = u32::from_le_bytes(tx_out_index);
|
||
|
||
let script_length: u64 = decode_variable_int(&ibytes[36..45]).unwrap();
|
||
let script_length_len: usize = measure_variable_int(&ibytes[36..45]).unwrap();
|
||
let script_start = 36 + script_length_len; //checkc for correctness
|
||
let script_end = script_start + script_length as usize;
|
||
let input_end = script_end + 4;
|
||
|
||
let script: Vec<u8> = ibytes[script_start..script_length as usize].to_vec();
|
||
|
||
let mut sequence: [u8; 4] = [0; 4];
|
||
sequence.copy_from_slice(&ibytes[script_end..input_end]);
|
||
|
||
let input_type: InputType = InputType::NONE; // testing measure
|
||
|
||
Self {
|
||
input_type,
|
||
position,
|
||
txhash,
|
||
script_length,
|
||
script,
|
||
sequence,
|
||
bytes_len: input_end,
|
||
}
|
||
}
|
||
|
||
fn default() -> Self {
|
||
let txh: [u8; 32] = [0; 32];
|
||
let seq: [u8; 4] = [0; 4];
|
||
|
||
Self {
|
||
input_type: InputType::NONE,
|
||
position: 55,
|
||
txhash: txh,
|
||
script_length: 45,
|
||
script: txh.to_vec(),
|
||
sequence: seq,
|
||
bytes_len: 123,
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||
pub enum InputType {
|
||
LEGACY,
|
||
COMPATIBILITY,
|
||
WITNESS,
|
||
NONE,
|
||
}
|
||
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||
pub struct Output {
|
||
pub output_type: OutputType,
|
||
// type of the output
|
||
pub value: u64,
|
||
// amount of btc in sats
|
||
pub script: Vec<u8>,
|
||
|
||
pub script_length: u64,
|
||
|
||
pub bytes_len: usize,
|
||
// payload: Option<Vec<u8>>,
|
||
// // data sent with the transaction (Op return)
|
||
}
|
||
|
||
impl Output {
|
||
fn new(obytes: Vec<u8>) -> Self {
|
||
let mut val: [u8; 8] = [0; 8];
|
||
val.copy_from_slice(&obytes[..8]);
|
||
let value: u64 = u64::from_le_bytes(val);
|
||
|
||
let script_start: usize = 8 + measure_variable_int(&obytes[8..17]).unwrap();
|
||
let script_length = decode_variable_int(&obytes[8..script_start]).unwrap();
|
||
let script_end: usize = script_start + script_length as usize;
|
||
|
||
let script = obytes[script_start..script_end].to_vec();
|
||
|
||
let output_type = OutputType::WPKH; // temporary hardcode
|
||
|
||
Self {
|
||
output_type,
|
||
value,
|
||
script,
|
||
script_length,
|
||
bytes_len: script_end,
|
||
}
|
||
}
|
||
|
||
fn default() -> Self {
|
||
let transaction_hash: [u8; 32] = [0; 32];
|
||
|
||
Self {
|
||
output_type: OutputType::WPKH,
|
||
value: 55,
|
||
script: transaction_hash.to_vec(),
|
||
script_length: 45,
|
||
bytes_len: 123,
|
||
}
|
||
}
|
||
}
|
||
|
||
#[allow(non_camel_case_types)]
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||
pub enum OutputType {
|
||
WPKH,
|
||
WSH,
|
||
OP_RETURN,
|
||
PKH,
|
||
SH,
|
||
NONSTANDARD,
|
||
// https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md
|
||
}
|
||
|
||
pub type HeaderChain = Vec<BlockHeader>;
|
||
// a vector of BlockHeaders used as part of a Proof
|
||
// index 0 : the block header of the block prior to the proof Block
|
||
// index 1 : the block header of the proof block
|
||
// index 2-n* : the block headers for the confirmation chain
|
||
// (where n is the confirmations value from the proof request)
|
||
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||
pub struct ProofEntry {
|
||
// 32 byte merkle hashes
|
||
pub hash: [u8; 32],
|
||
// side of the merkle tree entry
|
||
pub side: EntrySide,
|
||
}
|
||
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||
pub enum EntrySide {
|
||
// Left side of the hash combination
|
||
Left,
|
||
// Right side of hash combination
|
||
Right,
|
||
// Root hash (neither side)
|
||
Root,
|
||
}
|
||
|
||
pub type MerkleProof = Vec<ProofEntry>;
|
||
// a vector of ProofEntries used as part of a Proof
|
||
// index 0 : a ProofEntry representing the txid
|
||
// indices 0-n : ProofEntries linking the txhash and the merkle root
|
||
// index n : a ProofEntry representing the merkel root for the block in question
|
||
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||
pub struct ClientRequestInfo {
|
||
// bitcoin transaction hash
|
||
pub txhash: BitcoinTxHash,
|
||
// confirmation count
|
||
pub confirmations: u8,
|
||
// fee paid for tx verification
|
||
pub fee: u64,
|
||
// required minimum difficulty for submitted blocks
|
||
pub difficulty: u64,
|
||
// expiration slot height
|
||
pub expiration: Option<u32>,
|
||
}
|
||
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||
pub struct ProofRequest {
|
||
pub owner: Pubkey,
|
||
// bitcoin transaction hash
|
||
pub txhash: BitcoinTxHash,
|
||
// confirmation count
|
||
pub confirmations: u8,
|
||
// fee paid for tx verification
|
||
pub fee: u64,
|
||
// minimum allowable difficulty
|
||
pub difficulty: u64,
|
||
// expiration slot height
|
||
pub expiration: u64,
|
||
}
|
||
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||
pub struct Proof {
|
||
// the pubkey who submitted the proof in question, entitled to fees from any corresponding proof requests
|
||
pub submitter: Pubkey,
|
||
// merkle branch connecting txhash to block header merkle root
|
||
pub proof: MerkleProof,
|
||
// chain of bitcoin headers provifing context for the proof
|
||
pub headers: HeaderChain,
|
||
// transaction associated with the Proof
|
||
pub transaction: BitcoinTransaction,
|
||
// public key of the request this proof corresponds to
|
||
pub request: Pubkey,
|
||
}
|
||
|
||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||
pub enum AccountState {
|
||
// Request Account
|
||
Request(ClientRequestInfo),
|
||
// Verified Proof
|
||
Verification(Proof),
|
||
// Account holds a HeaderStore structure
|
||
Headers(HeaderAccountInfo),
|
||
// Account's data is Unallocated
|
||
Unallocated,
|
||
// Invalid
|
||
Invalid,
|
||
}
|
||
|
||
impl Default for AccountState {
|
||
fn default() -> Self {
|
||
AccountState::Unallocated
|
||
}
|
||
}
|
||
|
||
///Errors
|
||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||
pub enum SpvError {
|
||
InvalidBlockHeader,
|
||
// blockheader is malformed or out of order
|
||
HeaderStoreError,
|
||
// header store write/read result is invalid
|
||
ParseError,
|
||
// other errors with parsing inputs
|
||
InvalidAccount,
|
||
}
|
||
|
||
impl error::Error for SpvError {
|
||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||
// temporary measure
|
||
None
|
||
}
|
||
}
|
||
|
||
impl From<HeaderStoreError> for SpvError {
|
||
fn from(e: HeaderStoreError) -> Self {
|
||
SpvError::HeaderStoreError
|
||
}
|
||
}
|
||
|
||
impl From<DecodeHexError> for SpvError {
|
||
fn from(e: DecodeHexError) -> Self {
|
||
SpvError::ParseError
|
||
}
|
||
}
|
||
|
||
impl From<hex::FromHexError> for SpvError {
|
||
fn from(e: hex::FromHexError) -> Self {
|
||
SpvError::ParseError
|
||
}
|
||
}
|
||
|
||
// impl fmt::Debug for SpvError {
|
||
// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result{
|
||
// match self {
|
||
// SpvError::InvalidBlockHeader => "BlockHeader is malformed or does not apply ".fmt(f),
|
||
// SpvError::HeaderStoreError => "Placeholder headerstore error debug text".fmt(f),
|
||
// SpvError::ParseError => "Error parsing blockheaders debug".fmt(f),
|
||
// }
|
||
// }
|
||
// }
|
||
|
||
impl fmt::Display for SpvError {
|
||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
match self {
|
||
SpvError::InvalidBlockHeader => "BlockHeader is malformed or does not apply ".fmt(f),
|
||
SpvError::HeaderStoreError => "Placeholder headerstore error text".fmt(f),
|
||
SpvError::ParseError => "Error parsing blockheaders placceholder text".fmt(f),
|
||
SpvError::InvalidAccount => "Provided account is not usable or does not exist".fmt(f),
|
||
}
|
||
}
|
||
}
|