use crate::error::Error;
use bitcoin::consensus::encode::serialize;
use bitcoin::hashes::Hash;
use bitcoin::hashes::sha256::Hash as Sha256Hash;
use bitcoin::io::Read;
use bitcoin::{BlockHash, OutPoint, Witness};
use core::convert::TryFrom;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
pub type AssetID = Sha256Hash;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct SerializedKey {
pub bytes: [u8; 33],
}
impl Serialize for SerializedKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(&self.bytes)
}
}
impl<'de> Deserialize<'de> for SerializedKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct BytesVisitor;
impl<'de> de::Visitor<'de> for BytesVisitor {
type Value = SerializedKey;
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
formatter.write_str("33 bytes")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
if v.len() != 33 {
return Err(E::invalid_length(v.len(), &"33 bytes"));
}
let mut array = [0u8; 33];
array.copy_from_slice(v);
Ok(SerializedKey { bytes: array })
}
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
where
E: de::Error,
{
self.visit_bytes(&v)
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: de::SeqAccess<'de>,
{
let mut bytes = [0u8; 33];
for i in 0..33 {
bytes[i] = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(i, &"33 bytes"))?;
}
Ok(SerializedKey { bytes })
}
}
deserializer.deserialize_bytes(BytesVisitor)
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[repr(u8)]
pub enum AssetVersion {
V0 = 0,
V1 = 1,
}
impl AssetVersion {
pub(crate) fn from_u8(val: u8) -> Result<Self, Error> {
match val {
0 => Ok(AssetVersion::V0),
1 => Ok(AssetVersion::V1),
_ => Err(Error::InvalidTlvValue(
0,
format!("Unknown AssetVersion: {}", val),
)),
}
}
}
impl TryFrom<i32> for AssetVersion {
type Error = String;
fn try_from(value: i32) -> core::result::Result<Self, Self::Error> {
match value {
0 => Ok(AssetVersion::V0),
1 => Ok(AssetVersion::V1),
_ => Err(format!("Invalid AssetVersion value: {}", value)),
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum AssetType {
Normal,
Collectible,
}
impl TryFrom<i32> for AssetType {
type Error = String;
fn try_from(value: i32) -> core::result::Result<Self, Self::Error> {
match value {
0 => Ok(AssetType::Normal),
1 => Ok(AssetType::Collectible),
_ => Err(format!("Invalid AssetType value: {}", value)),
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum ScriptKeyType {
Unknown,
Bip86,
ScriptPathExternal,
Burn,
Tombstone,
Channel,
}
impl TryFrom<i32> for ScriptKeyType {
type Error = String;
fn try_from(value: i32) -> core::result::Result<Self, Self::Error> {
match value {
0 => Ok(ScriptKeyType::Unknown),
1 => Ok(ScriptKeyType::Bip86),
2 => Ok(ScriptKeyType::ScriptPathExternal),
3 => Ok(ScriptKeyType::Burn),
4 => Ok(ScriptKeyType::Tombstone),
5 => Ok(ScriptKeyType::Channel),
_ => Err(format!("Invalid ScriptKeyType value: {}", value)),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct GenesisInfo {
pub genesis_point: OutPoint,
pub name: String,
pub meta_hash: Sha256Hash,
pub asset_id: AssetID,
pub asset_type: AssetType,
pub output_index: u32,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct AssetGroup {
pub raw_group_key: Vec<u8>,
pub tweaked_group_key: Vec<u8>,
pub asset_witness: Vec<u8>,
pub tapscript_root: Option<Sha256Hash>,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct AnchorInfo {
pub anchor_tx: bitcoin::Transaction,
pub anchor_block_hash: BlockHash,
pub anchor_outpoint: OutPoint,
pub internal_key: Vec<u8>,
pub merkle_root: Sha256Hash,
pub tapscript_sibling: Vec<u8>,
pub block_height: u32,
pub block_timestamp: i64,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct PrevInputAsset {
pub anchor_point: OutPoint,
pub asset_id: AssetID,
pub script_key: Vec<u8>,
pub amount: u64,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct PrevId {
pub out_point: OutPoint,
pub asset_id: AssetID,
pub script_key: SerializedKey,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct DecimalDisplay {
pub decimal_display: u32,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct Asset {
pub version: AssetVersion,
pub asset_genesis: Option<GenesisInfo>,
pub amount: u64,
pub lock_time: i32,
pub relative_lock_time: i32,
pub script_version: i32,
pub script_key: Vec<u8>,
pub script_key_is_local: bool,
pub asset_group: Option<AssetGroup>,
pub chain_anchor: Option<AnchorInfo>,
pub prev_witnesses: Vec<PrevWitness>,
pub split_commitment_root: Option<crate::mssmt::MssmtNode>,
pub is_spent: bool,
pub lease_owner: Vec<u8>,
pub lease_expiry: i64,
pub is_burn: bool,
pub script_key_declared_known: bool,
pub script_key_has_script_path: bool,
pub decimal_display: Option<DecimalDisplay>,
pub script_key_type: ScriptKeyType,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum AssetMetaType {
Opaque,
Json,
}
impl TryFrom<i32> for AssetMetaType {
type Error = String;
fn try_from(value: i32) -> core::result::Result<Self, Self::Error> {
match value {
0 => Ok(AssetMetaType::Opaque),
1 => Ok(AssetMetaType::Json),
_ => Err(format!("Invalid AssetMetaType value: {}", value)),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct AssetMeta {
pub data: Vec<u8>,
pub meta_type: AssetMetaType,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct GenesisReveal {
pub genesis_base: Option<GenesisInfo>,
pub asset_type: AssetType,
pub amount: u64,
pub meta_reveal: Option<AssetMeta>,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum NonSpendLeafVersion {
OpReturn = 1,
Pedersen = 2,
}
impl NonSpendLeafVersion {
pub(crate) fn from_u8(value: u8) -> Result<Self, Error> {
match value {
1 => Ok(NonSpendLeafVersion::OpReturn),
2 => Ok(NonSpendLeafVersion::Pedersen),
_ => Err(Error::InvalidTlvValue(
0,
format!("Unknown NonSpendLeafVersion: {}", value),
)),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct GroupKeyReveal {
pub raw_group_key: SerializedKey,
pub tapscript_root: Option<Sha256Hash>,
pub version: Option<NonSpendLeafVersion>,
pub custom_subtree_root: Option<Sha256Hash>,
}
const GKR_VERSION_TYPE: crate::tlv::Type = crate::tlv::Type(0);
const GKR_INTERNAL_KEY_TYPE: crate::tlv::Type = crate::tlv::Type(2);
const GKR_TAPSCRIPT_ROOT_TYPE: crate::tlv::Type = crate::tlv::Type(4);
const GKR_CUSTOM_SUBTREE_ROOT_TYPE: crate::tlv::Type = crate::tlv::Type(7);
const GROUP_KEY_REVEAL_V0_MAX_LEN: u64 = 33 + 32;
const GROUP_KEY_REVEAL_INTERNAL_KEY_LEN: usize = 33;
const GROUP_KEY_REVEAL_TAPSCRIPT_ROOT_LEN: usize = 32;
pub(crate) fn decode_group_key_reveal(bytes: &[u8]) -> Result<GroupKeyReveal, Error> {
let len = bytes.len() as u64;
if len <= GROUP_KEY_REVEAL_V0_MAX_LEN {
return decode_group_key_reveal_v0(bytes);
}
decode_group_key_reveal_v1(bytes)
}
fn decode_group_key_reveal_v0(bytes: &[u8]) -> Result<GroupKeyReveal, Error> {
let len = bytes.len() as u64;
if len > GROUP_KEY_REVEAL_V0_MAX_LEN {
return Err(Error::InvalidTlvValue(
0,
format!("GroupKeyRevealV0 too large: {}", len),
));
}
if len < GROUP_KEY_REVEAL_INTERNAL_KEY_LEN as u64 {
return Err(Error::InvalidTlvValue(
0,
format!("GroupKeyRevealV0 too short: {}", len),
));
}
let mut cursor = bitcoin::io::Cursor::new(bytes);
let mut key_bytes = [0u8; GROUP_KEY_REVEAL_INTERNAL_KEY_LEN];
cursor.read_exact(&mut key_bytes).map_err(Error::Io)?;
let raw_key = SerializedKey { bytes: key_bytes };
let remaining = len - GROUP_KEY_REVEAL_INTERNAL_KEY_LEN as u64;
let tapscript_root_bytes = read_inline_var_bytes(&mut cursor, remaining)?;
let tapscript_root = match tapscript_root_bytes.len() {
0 => None,
GROUP_KEY_REVEAL_TAPSCRIPT_ROOT_LEN => {
let mut root_bytes = [0u8; GROUP_KEY_REVEAL_TAPSCRIPT_ROOT_LEN];
root_bytes.copy_from_slice(&tapscript_root_bytes);
Some(Sha256Hash::from_byte_array(root_bytes))
}
other => {
return Err(Error::InvalidTlvValue(
0,
format!("Invalid V0 tapscript root length: {}", other),
));
}
};
Ok(GroupKeyReveal {
raw_group_key: raw_key,
tapscript_root,
version: None,
custom_subtree_root: None,
})
}
fn decode_group_key_reveal_v1(bytes: &[u8]) -> Result<GroupKeyReveal, Error> {
let mut stream = crate::tlv::Stream::new(bitcoin::io::Cursor::new(bytes));
let mut version: Option<NonSpendLeafVersion> = None;
let mut internal_key: Option<SerializedKey> = None;
let mut tapscript_root: Option<Sha256Hash> = None;
let mut custom_subtree_root: Option<Sha256Hash> = None;
while let Some(record) = stream.next_record().map_err(Error::TlvStream)? {
match record.tlv_type() {
GKR_VERSION_TYPE => {
if record.value().len() != 1 {
return Err(Error::InvalidTlvValue(
GKR_VERSION_TYPE.0,
"Length must be 1 for group key reveal version".to_string(),
));
}
version = Some(NonSpendLeafVersion::from_u8(record.value()[0])?);
}
GKR_INTERNAL_KEY_TYPE => {
if record.value().len() != GROUP_KEY_REVEAL_INTERNAL_KEY_LEN {
return Err(Error::InvalidTlvValue(
GKR_INTERNAL_KEY_TYPE.0,
"Invalid internal key length".to_string(),
));
}
let mut key_bytes = [0u8; GROUP_KEY_REVEAL_INTERNAL_KEY_LEN];
key_bytes.copy_from_slice(record.value());
internal_key = Some(SerializedKey { bytes: key_bytes });
}
GKR_TAPSCRIPT_ROOT_TYPE => {
if record.value().len() != GROUP_KEY_REVEAL_TAPSCRIPT_ROOT_LEN {
return Err(Error::InvalidTlvValue(
GKR_TAPSCRIPT_ROOT_TYPE.0,
"Invalid tapscript root length".to_string(),
));
}
let mut root_bytes = [0u8; GROUP_KEY_REVEAL_TAPSCRIPT_ROOT_LEN];
root_bytes.copy_from_slice(record.value());
tapscript_root = Some(Sha256Hash::from_byte_array(root_bytes));
}
GKR_CUSTOM_SUBTREE_ROOT_TYPE => {
if record.value().len() != GROUP_KEY_REVEAL_TAPSCRIPT_ROOT_LEN {
return Err(Error::InvalidTlvValue(
GKR_CUSTOM_SUBTREE_ROOT_TYPE.0,
"Invalid custom subtree root length".to_string(),
));
}
let mut root_bytes = [0u8; GROUP_KEY_REVEAL_TAPSCRIPT_ROOT_LEN];
root_bytes.copy_from_slice(record.value());
custom_subtree_root = Some(Sha256Hash::from_byte_array(root_bytes));
}
type_val => {
if !type_val.is_odd() {
return Err(Error::UnknownTlvType(type_val.0));
}
}
}
}
Ok(GroupKeyReveal {
raw_group_key: internal_key.ok_or(Error::MissingTlvField(
"GroupKeyReveal.internal_key".to_string(),
))?,
tapscript_root: Some(tapscript_root.ok_or(Error::MissingTlvField(
"GroupKeyReveal.tapscript_root".to_string(),
))?),
version: Some(version.ok_or(Error::MissingTlvField("GroupKeyReveal.version".to_string()))?),
custom_subtree_root,
})
}
pub(crate) fn decode_alt_leaf(bytes: &[u8]) -> Result<Asset, Error> {
let mut prefixed = Vec::with_capacity(bytes.len() + 3);
prefixed.extend_from_slice(&[0u8, 1u8, 0u8]);
prefixed.extend_from_slice(bytes);
Asset::decode_tlv(bitcoin::io::Cursor::new(&prefixed))
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct SplitCommitment {
pub proof: crate::mssmt::MssmtProof,
pub root_asset: Box<Asset>,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct PrevWitness {
pub prev_id: Option<PrevId>,
pub tx_witness: Witness,
pub split_commitment: Option<SplitCommitment>,
}
const ASSET_LEAF_VERSION: crate::tlv::Type = crate::tlv::Type(0);
const ASSET_LEAF_GENESIS: crate::tlv::Type = crate::tlv::Type(2);
const ASSET_LEAF_AMOUNT: crate::tlv::Type = crate::tlv::Type(6);
const ASSET_LEAF_LOCK_TIME: crate::tlv::Type = crate::tlv::Type(7);
const ASSET_LEAF_RELATIVE_LOCK_TIME: crate::tlv::Type = crate::tlv::Type(9);
const ASSET_LEAF_PREV_WITNESS: crate::tlv::Type = crate::tlv::Type(11);
const ASSET_LEAF_SPLIT_COMMITMENT_ROOT: crate::tlv::Type = crate::tlv::Type(13);
const ASSET_LEAF_SCRIPT_VERSION: crate::tlv::Type = crate::tlv::Type(14);
const ASSET_LEAF_SCRIPT_KEY: crate::tlv::Type = crate::tlv::Type(16);
const ASSET_LEAF_GROUP_KEY: crate::tlv::Type = crate::tlv::Type(17);
const ASSET_LEAF_TYPE: crate::tlv::Type = crate::tlv::Type(4);
const WITNESS_PREV_ID: crate::tlv::Type = crate::tlv::Type(1);
const WITNESS_TX_WITNESS: crate::tlv::Type = crate::tlv::Type(3);
const WITNESS_SPLIT_COMMITMENT: crate::tlv::Type = crate::tlv::Type(5);
const MAX_ASSET_NAME_LENGTH: u64 = 64;
const MAX_WITNESS_STACK_ITEMS: u64 = u16::MAX as u64;
const MAX_WITNESS_ELEMENT_SIZE: u64 = u16::MAX as u64;
fn read_inline_var_bytes<R: bitcoin::io::Read>(
r: &mut R,
max_len: u64,
) -> Result<Vec<u8>, crate::error::Error> {
let len = Asset::read_varint(r)?;
if len > max_len {
return Err(crate::error::Error::InvalidTlvValue(
0,
format!("inline var bytes too large: {} (max: {})", len, max_len),
));
}
let mut bytes = alloc::vec![0u8; len as usize];
r.read_exact(&mut bytes).map_err(crate::error::Error::Io)?;
Ok(bytes)
}
fn decode_out_point<R: bitcoin::io::Read>(r: &mut R) -> Result<OutPoint, crate::error::Error> {
let mut hash_bytes = [0u8; 32];
r.read_exact(&mut hash_bytes)
.map_err(crate::error::Error::Io)?;
let mut index_bytes = [0u8; 4];
r.read_exact(&mut index_bytes)
.map_err(crate::error::Error::Io)?;
Ok(OutPoint {
txid: bitcoin::Txid::from_byte_array(hash_bytes),
vout: u32::from_be_bytes(index_bytes),
})
}
fn asset_type_byte(asset_type: AssetType) -> u8 {
match asset_type {
AssetType::Normal => 0,
AssetType::Collectible => 1,
}
}
fn compute_asset_id(genesis: &GenesisInfo) -> AssetID {
let outpoint_bytes = serialize(&genesis.genesis_point);
let tag_hash = Sha256Hash::hash(genesis.name.as_bytes());
let mut buf = Vec::with_capacity(outpoint_bytes.len() + 32 + 32 + 4 + 1);
buf.extend_from_slice(&outpoint_bytes);
buf.extend_from_slice(&tag_hash.to_byte_array());
buf.extend_from_slice(&genesis.meta_hash.to_byte_array());
buf.extend_from_slice(&genesis.output_index.to_be_bytes());
buf.push(asset_type_byte(genesis.asset_type));
Sha256Hash::hash(&buf)
}
pub(crate) fn decode_genesis_info<R: bitcoin::io::Read>(
mut r: R,
) -> Result<GenesisInfo, crate::error::Error> {
let genesis_point = decode_out_point(&mut r)?;
let tag_bytes = read_inline_var_bytes(&mut r, MAX_ASSET_NAME_LENGTH)?;
let name = String::from_utf8(tag_bytes).map_err(|_| {
crate::error::Error::InvalidTlvValue(ASSET_LEAF_GENESIS.0, "invalid UTF-8 tag".to_string())
})?;
let mut meta_hash_bytes = [0u8; 32];
r.read_exact(&mut meta_hash_bytes)
.map_err(crate::error::Error::Io)?;
let meta_hash = Sha256Hash::from_byte_array(meta_hash_bytes);
let mut output_index_bytes = [0u8; 4];
r.read_exact(&mut output_index_bytes)
.map_err(crate::error::Error::Io)?;
let output_index = u32::from_be_bytes(output_index_bytes);
let mut asset_type_byte = [0u8; 1];
r.read_exact(&mut asset_type_byte)
.map_err(crate::error::Error::Io)?;
let asset_type = match asset_type_byte[0] {
0 => AssetType::Normal,
1 => AssetType::Collectible,
other => {
return Err(crate::error::Error::InvalidTlvValue(
ASSET_LEAF_TYPE.0,
format!("Unknown asset type: {}", other),
));
}
};
let mut genesis = GenesisInfo {
genesis_point,
name,
meta_hash,
asset_id: AssetID::from_byte_array([0u8; 32]),
asset_type,
output_index,
};
genesis.asset_id = compute_asset_id(&genesis);
Ok(genesis)
}
fn decode_prev_id<R: bitcoin::io::Read>(mut r: R) -> Result<PrevId, crate::error::Error> {
let out_point = decode_out_point(&mut r)?;
let mut asset_id_bytes = [0u8; 32];
r.read_exact(&mut asset_id_bytes)
.map_err(crate::error::Error::Io)?;
let asset_id = AssetID::from_byte_array(asset_id_bytes);
let mut script_key_bytes = [0u8; 33];
r.read_exact(&mut script_key_bytes)
.map_err(crate::error::Error::Io)?;
Ok(PrevId {
out_point,
asset_id,
script_key: SerializedKey {
bytes: script_key_bytes,
},
})
}
fn decode_tx_witness<R: bitcoin::io::Read>(mut r: R) -> Result<Witness, crate::error::Error> {
let num_items = Asset::read_varint(&mut r)?;
if num_items > MAX_WITNESS_STACK_ITEMS {
return Err(crate::error::Error::InvalidTlvValue(
WITNESS_TX_WITNESS.0,
format!("too many witness items: {}", num_items),
));
}
let mut items = Vec::with_capacity(num_items as usize);
for _ in 0..num_items {
let item = read_inline_var_bytes(&mut r, MAX_WITNESS_ELEMENT_SIZE)?;
items.push(item);
}
Ok(Witness::from(items))
}
fn decode_split_commitment<R: bitcoin::io::Read>(
mut r: R,
max_len: u64,
) -> Result<SplitCommitment, crate::error::Error> {
let proof_bytes = read_inline_var_bytes(&mut r, max_len)?;
let proof = crate::mssmt::MssmtProof::decode_tlv(bitcoin::io::Cursor::new(&proof_bytes))?;
let root_asset_bytes = read_inline_var_bytes(&mut r, max_len)?;
let root_asset = Asset::decode_tlv(bitcoin::io::Cursor::new(&root_asset_bytes))?;
Ok(SplitCommitment {
proof,
root_asset: Box::new(root_asset),
})
}
fn decode_split_commitment_root<R: bitcoin::io::Read>(
mut r: R,
) -> Result<crate::mssmt::MssmtNode, crate::error::Error> {
let mut hash_bytes = [0u8; 32];
r.read_exact(&mut hash_bytes)
.map_err(crate::error::Error::Io)?;
let mut sum_bytes = [0u8; 8];
r.read_exact(&mut sum_bytes)
.map_err(crate::error::Error::Io)?;
Ok(crate::mssmt::MssmtNode {
hash: Sha256Hash::from_byte_array(hash_bytes),
sum: u64::from_be_bytes(sum_bytes),
})
}
fn decode_witness<R: bitcoin::io::Read>(r: R) -> Result<PrevWitness, crate::error::Error> {
let mut stream = crate::tlv::Stream::new(r);
let mut prev_id: Option<PrevId> = None;
let mut tx_witness: Option<Witness> = None;
let mut split_commitment: Option<SplitCommitment> = None;
while let Some(record) = stream
.next_record()
.map_err(crate::error::Error::TlvStream)?
{
match record.tlv_type() {
WITNESS_PREV_ID => {
prev_id = Some(decode_prev_id(record.value_reader())?);
}
WITNESS_TX_WITNESS => {
tx_witness = Some(decode_tx_witness(record.value_reader())?);
}
WITNESS_SPLIT_COMMITMENT => {
let max_len = record.value().len() as u64;
split_commitment = Some(decode_split_commitment(record.value_reader(), max_len)?);
}
type_val => {
if !type_val.is_odd() {
return Err(crate::error::Error::UnknownTlvType(type_val.0));
}
}
}
}
Ok(PrevWitness {
prev_id,
tx_witness: tx_witness.unwrap_or_default(),
split_commitment,
})
}
fn decode_prev_witnesses<R: bitcoin::io::Read>(
mut r: R,
) -> Result<Vec<PrevWitness>, crate::error::Error> {
let num_witnesses = Asset::read_varint(&mut r)?;
if num_witnesses > MAX_WITNESS_STACK_ITEMS {
return Err(crate::error::Error::InvalidTlvValue(
ASSET_LEAF_PREV_WITNESS.0,
format!("too many prev witnesses: {}", num_witnesses),
));
}
let mut witnesses = Vec::with_capacity(num_witnesses as usize);
for _ in 0..num_witnesses {
let witness_bytes = read_inline_var_bytes(&mut r, MAX_WITNESS_ELEMENT_SIZE)?;
let witness = decode_witness(bitcoin::io::Cursor::new(&witness_bytes))?;
witnesses.push(witness);
}
Ok(witnesses)
}
impl Asset {
pub fn decode_tlv<R: bitcoin::io::Read>(r: R) -> Result<Self, crate::error::Error> {
let mut stream = crate::tlv::Stream::new(r);
let mut version: Option<AssetVersion> = None;
let mut genesis: Option<GenesisInfo> = None;
let mut amount: Option<u64> = None;
let mut lock_time: Option<u64> = None;
let mut relative_lock_time: Option<u64> = None;
let mut prev_witnesses: Option<Vec<PrevWitness>> = None;
let mut script_version: Option<u16> = None;
let mut script_key: Option<Vec<u8>> = None;
let mut group_key: Option<AssetGroup> = None;
let mut unknown_odd_types = alloc::collections::BTreeMap::new();
let mut split_commitment_root: Option<crate::mssmt::MssmtNode> = None;
while let Some(record) = stream
.next_record()
.map_err(crate::error::Error::TlvStream)?
{
match record.tlv_type() {
ASSET_LEAF_VERSION => {
if record.value().len() != 1 {
return Err(crate::error::Error::InvalidTlvValue(
ASSET_LEAF_VERSION.0,
String::from("Length must be 1 for version"),
));
}
version = Some(AssetVersion::from_u8(record.value()[0])?);
}
ASSET_LEAF_GENESIS => {
genesis = Some(decode_genesis_info(record.value_reader())?);
}
ASSET_LEAF_AMOUNT => {
let mut cursor = bitcoin::io::Cursor::new(record.value());
amount = Some(Self::read_varint(&mut cursor)?);
}
ASSET_LEAF_LOCK_TIME => {
let mut cursor = bitcoin::io::Cursor::new(record.value());
let lock_time_val = Self::read_varint(&mut cursor)?;
lock_time = Some(lock_time_val);
}
ASSET_LEAF_RELATIVE_LOCK_TIME => {
let mut cursor = bitcoin::io::Cursor::new(record.value());
let relative_lock_time_val = Self::read_varint(&mut cursor)?;
relative_lock_time = Some(relative_lock_time_val);
}
ASSET_LEAF_PREV_WITNESS => {
prev_witnesses = Some(decode_prev_witnesses(record.value_reader())?);
}
ASSET_LEAF_SPLIT_COMMITMENT_ROOT => {
split_commitment_root =
Some(decode_split_commitment_root(record.value_reader())?);
}
ASSET_LEAF_SCRIPT_VERSION => {
if record.value().len() != 2 {
return Err(crate::error::Error::InvalidTlvValue(
ASSET_LEAF_SCRIPT_VERSION.0,
String::from("Length must be 2 for script version"),
));
}
script_version =
Some(u16::from_be_bytes([record.value()[0], record.value()[1]]));
}
ASSET_LEAF_SCRIPT_KEY => {
if record.value().len() != 33 {
return Err(crate::error::Error::InvalidTlvValue(
ASSET_LEAF_SCRIPT_KEY.0,
String::from("Length must be 33 for script key"),
));
}
script_key = Some(record.value().to_vec());
}
ASSET_LEAF_GROUP_KEY => {
if record.value().len() != 33 {
return Err(crate::error::Error::InvalidTlvValue(
ASSET_LEAF_GROUP_KEY.0,
String::from("Length must be 33 for group key"),
));
}
group_key = Some(AssetGroup {
raw_group_key: record.value().to_vec(),
tweaked_group_key: Vec::new(),
asset_witness: Vec::new(),
tapscript_root: None,
});
}
ASSET_LEAF_TYPE => {
if record.value().len() != 1 {
return Err(crate::error::Error::InvalidTlvValue(
ASSET_LEAF_TYPE.0,
String::from("Length must be 1 for asset type"),
));
}
let _asset_type = match record.value()[0] {
0 => AssetType::Normal,
1 => AssetType::Collectible,
_ => {
return Err(crate::error::Error::InvalidTlvValue(
ASSET_LEAF_TYPE.0,
format!("Unknown asset type: {}", record.value()[0]),
));
}
};
}
type_val => {
if type_val.is_odd() {
unknown_odd_types.insert(type_val.0, record.value().to_vec());
} else {
return Err(crate::error::Error::UnknownTlvType(type_val.0));
}
}
}
}
Ok(Asset {
version: version.ok_or(crate::error::Error::MissingTlvField(
"Asset.version".to_string(),
))?,
asset_genesis: genesis,
amount: amount.unwrap_or(0),
lock_time: lock_time.map(|v| v as i32).unwrap_or(0),
relative_lock_time: relative_lock_time.map(|v| v as i32).unwrap_or(0),
script_version: script_version.map(|v| v as i32).unwrap_or(0),
script_key: script_key.unwrap_or_default(),
script_key_is_local: false, asset_group: group_key,
chain_anchor: None, prev_witnesses: prev_witnesses.unwrap_or_default(),
split_commitment_root,
is_spent: false, lease_owner: Vec::new(), lease_expiry: 0, is_burn: false, script_key_declared_known: false, script_key_has_script_path: false, decimal_display: None, script_key_type: ScriptKeyType::Unknown, })
}
fn read_varint<R: bitcoin::io::Read>(r: &mut R) -> Result<u64, crate::error::Error> {
let mut first_byte = [0u8; 1];
r.read_exact(&mut first_byte)
.map_err(crate::error::Error::Io)?;
match first_byte[0] {
253 => {
let mut u16_bytes = [0u8; 2];
r.read_exact(&mut u16_bytes)
.map_err(crate::error::Error::Io)?;
Ok(u16::from_be_bytes(u16_bytes) as u64)
}
254 => {
let mut u32_bytes = [0u8; 4];
r.read_exact(&mut u32_bytes)
.map_err(crate::error::Error::Io)?;
Ok(u32::from_be_bytes(u32_bytes) as u64)
}
255 => {
let mut u64_bytes = [0u8; 8];
r.read_exact(&mut u64_bytes)
.map_err(crate::error::Error::Io)?;
Ok(u64::from_be_bytes(u64_bytes))
}
_ => Ok(first_byte[0] as u64),
}
}
}