use std::convert::{TryFrom, TryInto};
use std::fmt::Debug;
use bitcoin::{BlockHash, Txid};
#[derive(Copy, Clone, Debug, Display, PartialEq, Eq)]
#[display(doc_comments)]
pub enum Error {
BlockHeightOutOfRange,
InputIndexOutOfRange,
OutputIndexOutOfRange,
ChecksumOutOfRange,
DimensionRequired,
UpgradeImpossible,
DowngradeImpossible,
}
#[derive(
Wrapper, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug,
Display, From
)]
#[display("{0}", alt = "block_checksum:{0:#}")]
#[wrapper(FromStr, LowerHex, UpperHex, Octal)]
pub struct BlockChecksum(u8);
impl From<BlockHash> for BlockChecksum {
fn from(block_hash: BlockHash) -> Self {
let mut xor: u8 = 0;
for byte in &block_hash[..] {
xor ^= byte;
}
Self::from(xor)
}
}
#[derive(
Wrapper, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug,
Display, From
)]
#[display("{0}", alt = "tx_checksum:{0:#}")]
#[wrapper(FromStr, LowerHex, UpperHex, Octal)]
pub struct TxChecksum(u64);
impl From<Txid> for TxChecksum {
fn from(txid: Txid) -> Self {
let mut checksum: u64 = 0;
for (shift, byte) in txid.to_vec()[0..5].iter().enumerate() {
checksum ^= (*byte as u64) << (shift * 8);
}
Self::from(checksum)
}
}
#[derive(Copy, Clone, Debug)]
pub enum Descriptor {
OnchainBlock {
block_height: u32,
block_checksum: BlockChecksum,
},
OnchainTransaction {
block_height: u32,
block_checksum: BlockChecksum,
tx_index: u16,
},
OnchainTxInput {
block_height: u32,
block_checksum: BlockChecksum,
tx_index: u16,
input_index: u16,
},
OnchainTxOutput {
block_height: u32,
block_checksum: BlockChecksum,
tx_index: u16,
output_index: u16,
},
OffchainTransaction {
tx_checksum: TxChecksum,
},
OffchainTxInput {
tx_checksum: TxChecksum,
input_index: u16,
},
OffchainTxOutput {
tx_checksum: TxChecksum,
output_index: u16,
},
}
#[derive(Copy, Clone, Debug, Display, PartialEq, Eq)]
pub enum Dimension {
#[display("input")]
Input,
#[display("output")]
Output,
}
impl Default for Descriptor {
fn default() -> Self {
Descriptor::OnchainBlock {
block_height: 0,
block_checksum: BlockChecksum::default(),
}
}
}
impl Descriptor {
pub fn try_validity(&self) -> Result<(), Error> {
match *self {
Descriptor::OnchainTransaction { block_height, .. }
| Descriptor::OnchainTxInput { block_height, .. }
| Descriptor::OnchainTxOutput { block_height, .. }
if block_height >= (2u32 << 22) =>
{
Err(Error::BlockHeightOutOfRange)
}
Descriptor::OnchainTxInput { input_index, .. }
| Descriptor::OffchainTxInput { input_index, .. }
if input_index + 1 >= (2u16 << 14) =>
{
Err(Error::InputIndexOutOfRange)
}
Descriptor::OnchainTxOutput { output_index, .. }
| Descriptor::OffchainTxOutput { output_index, .. }
if output_index + 1 >= (2u16 << 14) =>
{
Err(Error::OutputIndexOutOfRange)
}
Descriptor::OffchainTransaction { tx_checksum, .. }
| Descriptor::OffchainTxInput { tx_checksum, .. }
| Descriptor::OffchainTxOutput { tx_checksum, .. }
if *tx_checksum >= (2u64 << 46) =>
{
Err(Error::ChecksumOutOfRange)
}
_ => Ok(()),
}
}
pub fn is_onchain(&self) -> bool {
matches!(
self,
Descriptor::OnchainBlock { .. }
| Descriptor::OnchainTransaction { .. }
| Descriptor::OnchainTxInput { .. }
| Descriptor::OnchainTxOutput { .. }
)
}
pub fn is_offchain(&self) -> bool { !self.is_onchain() }
pub fn upgraded(
&self,
index: u16,
dimension: Option<Dimension>,
) -> Result<Self, Error> {
use Dimension::*;
match (*self, dimension) {
(
Descriptor::OnchainBlock {
block_height,
block_checksum,
},
None,
) => Ok(Descriptor::OnchainTransaction {
block_height,
block_checksum,
tx_index: index,
}),
(
Descriptor::OnchainTransaction {
block_height,
block_checksum,
tx_index,
},
Some(dim),
) if dim == Input => Ok(Descriptor::OnchainTxInput {
block_height,
block_checksum,
tx_index,
input_index: index,
}),
(
Descriptor::OnchainTransaction {
block_height,
block_checksum,
tx_index,
},
Some(dim),
) if dim == Output => Ok(Descriptor::OnchainTxOutput {
block_height,
block_checksum,
tx_index,
output_index: index,
}),
(Descriptor::OffchainTransaction { tx_checksum }, Some(dim))
if dim == Input =>
{
Ok(Descriptor::OffchainTxInput {
tx_checksum,
input_index: index,
})
}
(Descriptor::OffchainTransaction { tx_checksum }, Some(dim))
if dim == Output =>
{
Ok(Descriptor::OffchainTxOutput {
tx_checksum,
output_index: index,
})
}
(Descriptor::OnchainTransaction { .. }, None)
| (Descriptor::OffchainTransaction { .. }, None) => {
Err(Error::DimensionRequired)
}
_ => Err(Error::UpgradeImpossible),
}
}
pub fn downgraded(self) -> Result<Self, Error> {
match self {
Descriptor::OnchainTransaction {
block_height,
block_checksum,
..
} => Ok(Descriptor::OnchainBlock {
block_height,
block_checksum,
}),
Descriptor::OnchainTxInput {
block_height,
block_checksum,
tx_index,
..
}
| Descriptor::OnchainTxOutput {
block_height,
block_checksum,
tx_index,
..
} => Ok(Descriptor::OnchainTransaction {
block_height,
block_checksum,
tx_index,
}),
Descriptor::OffchainTxInput { tx_checksum, .. }
| Descriptor::OffchainTxOutput { tx_checksum, .. } => {
Ok(Descriptor::OffchainTransaction { tx_checksum })
}
_ => Err(Error::DowngradeImpossible),
}
}
pub fn get_block_height(&self) -> Option<u32> {
match self {
Descriptor::OnchainBlock { block_height, .. }
| Descriptor::OnchainTransaction { block_height, .. }
| Descriptor::OnchainTxInput { block_height, .. }
| Descriptor::OnchainTxOutput { block_height, .. } => {
Some(*block_height)
}
_ => None,
}
}
pub fn get_block_checksum(&self) -> Option<u8> {
match self {
Descriptor::OnchainBlock { block_checksum, .. }
| Descriptor::OnchainTransaction { block_checksum, .. }
| Descriptor::OnchainTxInput { block_checksum, .. }
| Descriptor::OnchainTxOutput { block_checksum, .. } => {
Some(**block_checksum)
}
_ => None,
}
}
pub fn get_tx_checksum(&self) -> Option<u64> {
match self {
Descriptor::OffchainTransaction { tx_checksum, .. }
| Descriptor::OffchainTxInput { tx_checksum, .. }
| Descriptor::OffchainTxOutput { tx_checksum, .. } => {
Some(**tx_checksum)
}
_ => None,
}
}
pub fn get_tx_index(&self) -> Option<u16> {
match self {
Descriptor::OnchainTransaction { tx_index, .. }
| Descriptor::OnchainTxInput { tx_index, .. }
| Descriptor::OnchainTxOutput { tx_index, .. } => Some(*tx_index),
_ => None,
}
}
pub fn get_input_index(&self) -> Option<u16> {
match self {
Descriptor::OnchainTxInput { input_index, .. }
| Descriptor::OffchainTxInput { input_index, .. } => {
Some(*input_index)
}
_ => None,
}
}
pub fn get_output_index(&self) -> Option<u16> {
match self {
Descriptor::OnchainTxOutput { output_index, .. }
| Descriptor::OffchainTxOutput { output_index, .. } => {
Some(*output_index)
}
_ => None,
}
}
pub fn try_into_u64(self) -> Result<u64, Error> {
ShortId::try_from(self).map(ShortId::into_u64)
}
}
#[derive(
Copy,
Clone,
PartialOrd,
Ord,
PartialEq,
Eq,
Hash,
Debug,
Display,
StrictEncode,
StrictDecode
)]
#[display("{0:016X}")]
pub struct ShortId(u64);
impl ShortId {
pub const FLAG_OFFCHAIN: u64 = 0x8000_0000_0000_0000;
pub const MASK_BLOCK: u64 = 0x7FFF_FF00_0000_0000;
pub const MASK_BLOCKCHECK: u64 = 0x0000_00FF_0000_0000;
pub const MASK_TXIDX: u64 = 0x0000_0000_FFFF_0000;
pub const MASK_TXCHECK: u64 = 0x7FFF_FFFF_FFFF_0000;
pub const FLAG_INOUT: u64 = 0x0000_0000_0000_8000;
pub const MASK_INOUT: u64 = 0x0000_0000_0000_7FFF;
pub const SHIFT_BLOCK: u64 = 40;
pub const SHIFT_BLOCKCHECK: u64 = 32;
pub const SHIFT_TXIDX: u64 = 16;
pub fn is_onchain(&self) -> bool {
self.0 & Self::FLAG_OFFCHAIN != Self::FLAG_OFFCHAIN
}
pub fn is_offchain(&self) -> bool {
self.0 & Self::FLAG_OFFCHAIN == Self::FLAG_OFFCHAIN
}
pub fn get_descriptor(&self) -> Descriptor {
#[inline]
fn iconv<T>(val: u64) -> T
where
T: TryFrom<u64>,
<T as TryFrom<u64>>::Error: Debug,
{
val.try_into()
.expect("Conversion from existing ShortId can't fail")
}
let index: u16 = iconv(self.0 & Self::MASK_INOUT);
if self.is_onchain() {
let block_height: u32 =
iconv((self.0 & Self::MASK_BLOCK) >> Self::SHIFT_BLOCK);
let block_checksum = BlockChecksum::from(iconv::<u8>(
(self.0 & Self::MASK_BLOCKCHECK) >> Self::SHIFT_BLOCKCHECK,
));
if (self.0 & (!Self::MASK_BLOCK)) == 0 {
return Descriptor::OnchainBlock {
block_height,
block_checksum,
};
}
let tx_index: u16 =
iconv((self.0 & Self::MASK_TXIDX) >> Self::SHIFT_TXIDX);
if (self.0 & (!Self::MASK_INOUT)) == 0 {
return Descriptor::OnchainTransaction {
block_height,
block_checksum,
tx_index,
};
}
if (self.0 & Self::FLAG_INOUT) == 0 {
Descriptor::OnchainTxInput {
block_height,
block_checksum,
tx_index,
input_index: index - 1,
}
} else {
Descriptor::OnchainTxOutput {
block_height,
block_checksum,
tx_index,
output_index: index - 1,
}
}
} else {
let tx_checksum = TxChecksum::from(
(self.0 & Self::MASK_TXCHECK) >> Self::SHIFT_TXIDX,
);
if (self.0 & (!Self::MASK_INOUT)) == 0 {
return Descriptor::OffchainTransaction { tx_checksum };
}
if (self.0 & Self::FLAG_INOUT) == 0 {
Descriptor::OffchainTxInput {
tx_checksum,
input_index: index - 1,
}
} else {
Descriptor::OffchainTxOutput {
tx_checksum,
output_index: index - 1,
}
}
}
}
pub fn into_u64(self) -> u64 { self.into() }
}
impl From<ShortId> for Descriptor {
fn from(short_id: ShortId) -> Self { short_id.get_descriptor() }
}
impl TryFrom<Descriptor> for ShortId {
type Error = self::Error;
fn try_from(descriptor: Descriptor) -> Result<Self, Self::Error> {
use Descriptor::*;
descriptor.try_validity()?;
let block_height: u64 = match descriptor {
OnchainBlock { block_height, .. }
| OnchainTransaction { block_height, .. }
| OnchainTxInput { block_height, .. }
| OnchainTxOutput { block_height, .. } => block_height,
_ => 0,
} as u64;
let block_checksum = *match descriptor {
OnchainBlock { block_checksum, .. }
| OnchainTransaction { block_checksum, .. }
| OnchainTxInput { block_checksum, .. }
| OnchainTxOutput { block_checksum, .. } => block_checksum,
_ => BlockChecksum::default(),
} as u64;
let tx_index = match descriptor {
OnchainTransaction { tx_index, .. }
| OnchainTxInput { tx_index, .. }
| OnchainTxOutput { tx_index, .. } => tx_index,
_ => 0,
} as u64;
let tx_checksum = match descriptor {
OffchainTransaction { tx_checksum }
| OffchainTxInput { tx_checksum, .. }
| OffchainTxOutput { tx_checksum, .. } => tx_checksum,
_ => TxChecksum::default(),
};
let inout_index: u64 = match descriptor {
OnchainTxInput { input_index, .. }
| OffchainTxInput { input_index, .. } => input_index + 1,
OnchainTxOutput { output_index, .. }
| OffchainTxOutput { output_index, .. } => output_index + 1,
_ => 0,
} as u64;
let mut short_id = 0u64;
short_id |= inout_index;
if descriptor.is_offchain() {
short_id |= Self::FLAG_OFFCHAIN;
short_id |=
(*tx_checksum << Self::SHIFT_TXIDX) & Self::MASK_TXCHECK;
} else {
short_id |= (block_height << 40) & Self::MASK_BLOCK;
short_id |= (block_checksum << Self::SHIFT_BLOCKCHECK)
& Self::MASK_BLOCKCHECK;
short_id |= (tx_index << 16) & Self::MASK_TXIDX;
}
match descriptor {
OnchainTxOutput { .. } | OffchainTxOutput { .. } => {
short_id |= Self::FLAG_INOUT << Self::SHIFT_TXIDX
}
_ => (),
}
Ok(Self(short_id))
}
}
impl From<u64> for ShortId {
fn from(val: u64) -> Self { Self(val) }
}
impl From<ShortId> for u64 {
fn from(short_id: ShortId) -> Self { short_id.0 }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn short_id_is_onchain() {
let test_cases = vec![
0,
1,
100,
16,
32,
40,
0x7FFF_FF00_0000_0000,
0x0000_0000_0000_8000,
];
for c in &test_cases {
let sid = ShortId(*c);
assert!(sid.is_onchain());
}
}
#[test]
fn short_id_is_offchain() {
let test_cases = vec![
0x8000_0000_0000_0000,
0x8000_0000_0000_0001,
0x9000_0000_0000_0000,
0xFFFF_0000_0000_0000,
];
for c in &test_cases {
let sid = ShortId(*c);
assert!(sid.is_offchain());
}
}
#[test]
fn short_id_into() {
let test_cases = [0, 1];
for c in &test_cases {
let sid = ShortId(*c);
assert_eq!(sid.into_u64(), *c);
}
}
#[test]
fn short_id_get_descriptor_empty() {
let sid = ShortId(0);
let descriptor = sid.get_descriptor();
match descriptor.get_block_height() {
Some(h) => assert_eq!(h, 0),
None => {}
}
}
#[test]
fn short_id_get_descriptor_block_height_valid() {
let test_cases = [
[0x0000_0100_0000_0000, 1],
[0x0000_1000_0000_0000, 16],
[0x0001_0000_0000_0000, 256],
];
for c in &test_cases {
let sid = ShortId(c[0]);
match sid.get_descriptor().get_block_height() {
Some(h) => assert_eq!(u64::from(h), c[1]),
None => {}
}
}
}
#[test]
#[should_panic(expected = "attempt to subtract with overflow")]
fn short_id_get_descriptor_block_height_overflow() {
let sid = ShortId(0x0000_0000_1000_0000);
sid.get_descriptor().get_block_height();
}
}