diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..5397910 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,16 @@ +name: Rust + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Run installation + run: make install + - name: Run CI + run: make ci + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6985cf1..2eb9fd3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,11 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ +# Rust +target +.cache/.cargo/* -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock +# C +contracts/c/build/* -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb +# others +build/* +!.gitkeep +.tmp/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3e6e763 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "rust-analyzer.cargo.target": "riscv64imac-unknown-none-elf" +} + diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7fdeefa --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,109 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "blake2b-ref" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "294d17c72e0ba59fad763caa112368d0672083779cdebbb97164f4bb4c1e339a" + +[[package]] +name = "buddy-alloc" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f0d2da64a6a895d5a7e0724882825d50f83c13396b1b9f1878e19a024bab395" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ckb-auth-rs" +version = "0.1.0" +source = "git+https://github.com/nervosnetwork/ckb-auth.git?rev=df6e9ef#df6e9ef0e7ef3c83b6165e1ba180bf5a84a882ad" +dependencies = [ + "ckb-std", + "hex", + "log", +] + +[[package]] +name = "ckb-standalone-types" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5c776d70eb4f60a22a3180857646d77b2da8d33c0c4a063ad9f6610fc94609f" +dependencies = [ + "blake2b-ref", + "cfg-if", + "molecule", +] + +[[package]] +name = "ckb-std" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a08518aa0fd4ce069d3ec80b63dcd3d6543ad3805ad1c0b4e1d8e4d38f8a9fc" +dependencies = [ + "buddy-alloc", + "cc", + "ckb-standalone-types", +] + +[[package]] +name = "ckb-typed-message" +version = "0.1.0" +dependencies = [ + "blake2b-ref", + "ckb-std", + "molecule", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "molecule" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd9767ab5e5f2ea40f71ff4c8bdb633c50509052e093c2fdd0e390a749dfa3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "typed-message-lock-demo" +version = "0.1.0" +dependencies = [ + "blake2b-ref", + "ckb-auth-rs", + "ckb-std", + "ckb-typed-message", + "molecule", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6a79e91 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[workspace] +members = ["ckb-typed-message", "contracts/typed-message-lock-demo"] +exclude = ["tests"] + +[profile.release] +overflow-checks = true +opt-level = 3 +panic = 'abort' +strip = true +lto = true +debug-assertions = true + +[profile.dev] +strip = true +opt-level = 1 +debug = false +panic = 'abort' +debug-assertions = true diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 0000000..278f31b --- /dev/null +++ b/Cross.toml @@ -0,0 +1,5 @@ +[build] +default-target = "riscv64imac-unknown-none-elf" + +[target.riscv64imac-unknown-none-elf] +image = "nervos/ckb-riscv-gnu-toolchain:focal-20230214" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..74915ea --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ + + +all: + capsule build --release + +mol: + moleculec --language rust --schema-file schemas/basic.mol > ckb-typed-message/src/schemas/basic.rs + moleculec --language rust --schema-file schemas/top_level.mol > ckb-typed-message/src/schemas/top_level.rs + cargo fmt + +install: + rustup target add riscv64imac-unknown-none-elf + cargo install cross --git https://github.com/cross-rs/cross + wget 'https://github.com/nervosnetwork/capsule/releases/download/v0.10.2/capsule_v0.10.2_x86_64-linux.tar.gz' + tar zxvf capsule_v0.10.2_x86_64-linux.tar.gz + mv capsule_v0.10.2_x86_64-linux/capsule ~/.cargo/bin + cargo install moleculec@0.7.5 --locked + +ci: + capsule build --release + cd tests && cargo test && cd .. diff --git a/README.md b/README.md new file mode 100644 index 0000000..82c28c0 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# ckb-typed-message(PoC) +This project is a proof of concept that aims to demonstrate how to adopt typed +messages (similar to EIP-712) in scripts. It also includes extended witnesses to +simplify signing and DApp interoperability. + + +## Build +Build contracts: + +``` sh +capsule build +``` + +Run tests: + +``` sh +capsule test +``` diff --git a/build/.gitkeep b/build/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/build/auth b/build/auth new file mode 100755 index 0000000..493f0a1 Binary files /dev/null and b/build/auth differ diff --git a/capsule.toml b/capsule.toml new file mode 100644 index 0000000..969e0fd --- /dev/null +++ b/capsule.toml @@ -0,0 +1,13 @@ +# [rust] +# # path of rust contracts workspace directory, +# # a `Cargo.toml` file is expected under the directory. +# workspace_dir = "." + +# capsule version +version = "0.10.2 5e49142" +# path of deployment config file +deployment = "deployment.toml" + +[[contracts]] +name = "typed-message-lock-demo" +template_type = "Rust" diff --git a/ckb-typed-message/Cargo.toml b/ckb-typed-message/Cargo.toml new file mode 100644 index 0000000..8ab5b26 --- /dev/null +++ b/ckb-typed-message/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ckb-typed-message" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +blake2b-ref = "0.3.1" +ckb-std = { version = "0.14.3", default-features = false, features = ["ckb-types", "calc-hash"] } +molecule = { version = "0.7.5", default-features = false } diff --git a/ckb-typed-message/README.md b/ckb-typed-message/README.md new file mode 100644 index 0000000..0706e7e --- /dev/null +++ b/ckb-typed-message/README.md @@ -0,0 +1,5 @@ + +This is a library used for writing scripts that are intended to support typed +messages on CKB. + + diff --git a/ckb-typed-message/src/blake2b.rs b/ckb-typed-message/src/blake2b.rs new file mode 100644 index 0000000..dbaf067 --- /dev/null +++ b/ckb-typed-message/src/blake2b.rs @@ -0,0 +1,18 @@ +pub use blake2b_ref::{Blake2b, Blake2bBuilder}; + +pub const CKB_PERSONALIZATION: &[u8] = b"ckb-default-hash"; + +pub fn new_blake2b() -> Blake2b { + Blake2bBuilder::new(32) + .personal(CKB_PERSONALIZATION) + .build() +} + +pub fn hash(bytes: &[u8]) -> [u8; 32] { + let mut hasher = new_blake2b(); + hasher.update(bytes); + + let mut hash = [0u8; 32]; + hasher.finalize(&mut hash); + hash +} diff --git a/ckb-typed-message/src/lib.rs b/ckb-typed-message/src/lib.rs new file mode 100644 index 0000000..47dd782 --- /dev/null +++ b/ckb-typed-message/src/lib.rs @@ -0,0 +1,229 @@ +#![no_std] +extern crate alloc; +pub mod blake2b; +pub mod schemas; + +use alloc::vec::Vec; +use blake2b::new_blake2b; +use ckb_std::{ + ckb_constants::Source, + error::SysError, + high_level::{load_input_since, load_tx_hash, load_witness}, +}; +use molecule::{ + error::VerificationError, + prelude::{Entity, Reader}, +}; +use schemas::{ + basic::SighashWithAction, + top_level::{ + ExtendedWitness, ExtendedWitnessReader, ExtendedWitnessUnion, ExtendedWitnessUnionReader, + }, +}; + +#[derive(Eq, PartialEq, Debug, Clone, Copy)] +pub enum Error { + Sys(SysError), + DuplicateAction, + MoleculeEncoding, + WrongSighashWithAction, +} + +impl From for Error { + fn from(e: SysError) -> Self { + Error::Sys(e) + } +} + +impl From for Error { + fn from(_: VerificationError) -> Self { + Error::MoleculeEncoding + } +} +/// +/// A single transaction must have only one SighashWithAction +/// +pub fn check_sighash_with_action() -> Result<(), Error> { + let mut i = 0; + let mut found = false; + loop { + match load_witness(i, Source::Input) { + Ok(witness) => { + if let Ok(r) = ExtendedWitnessReader::from_slice(&witness) { + if let ExtendedWitnessUnionReader::SighashWithAction(_) = r.to_enum() { + if found { + return Err(Error::DuplicateAction); + } else { + found = true; + } + } + } + } + Err(SysError::IndexOutOfBound) => break, + Err(e) => return Err(e.into()), + } + i += 1; + } + if found { + return Ok(()); + } else { + return Err(Error::WrongSighashWithAction); + } +} + +/// +/// Fetch the corresponding ExtendedWitness( Sighash or SighashWithAction) +/// Used by lock script with typed message support +/// +pub fn fetch_sighash() -> Result { + match load_witness(0, Source::GroupInput) { + Ok(witness) => { + if let Ok(r) = ExtendedWitnessReader::from_slice(&witness) { + match r.to_enum() { + ExtendedWitnessUnionReader::SighashWithAction(_) + | ExtendedWitnessUnionReader::Sighash(_) => return Ok(r.to_entity()), + _ => { + return Err(Error::MoleculeEncoding); + } + } + } else { + return Err(Error::MoleculeEncoding); + } + } + Err(e) => return Err(e.into()), + } +} + +/// +/// Search the only SighashWithAction from all witnesses. +/// +pub fn search_sighash_with_action() -> Result { + let mut i = 0; + let mut result = None; + // Look for the first SighashWithAction witness + while result.is_none() { + match load_witness(i, Source::Input) { + Ok(witness) => { + if let Ok(r) = ExtendedWitnessReader::from_slice(&witness) { + if let ExtendedWitnessUnionReader::SighashWithAction(s) = r.to_enum() { + result = Some(s.to_entity()); + } + } + } + Err(SysError::IndexOutOfBound) => break, + Err(e) => return Err(e.into()), + }; + i += 1; + } + if result.is_some() { + return Ok(result.unwrap()); + } else { + return Err(Error::WrongSighashWithAction); + } +} + +// +// Rule for hashing: +// 1. Variable length data should hash the length. +// 2. Fixed length data don't need to hash the length. +// +pub fn generate_skeleton_hash() -> Result<[u8; 32], Error> { + let mut hasher = new_blake2b(); + hasher.update(&load_tx_hash()?); + + let mut i = calculate_inputs_len()?; + loop { + match load_witness(i, Source::Input) { + Ok(w) => { + hasher.update(&(w.len() as u64).to_le_bytes()); + hasher.update(&w); + } + Err(SysError::IndexOutOfBound) => { + break; + } + Err(e) => return Err(e.into()), + } + i += 1; + } + + let mut output = [0u8; 32]; + hasher.finalize(&mut output); + + Ok(output) +} + +pub fn generate_final_hash(skeleton_hash: &[u8; 32], typed_message: &[u8]) -> [u8; 32] { + let mut hasher = new_blake2b(); + hasher.update(&skeleton_hash[..]); + hasher.update(&(typed_message.len() as u64).to_le_bytes()); + hasher.update(typed_message); + let mut output = [0u8; 32]; + hasher.finalize(&mut output); + return output; +} + +// Translated from https://github.com/nervosnetwork/ckb-system-scripts/blob/a7b7c75662ed950c9bd024e15f83ce702a54996e/c/common.h#L32-L66 +fn calculate_inputs_len() -> Result { + let mut lo = 0; + let mut hi = 4; + + // The code below can't handle the scenario when input length is zero. + let first_available = load_input_since(0, Source::Input); + if first_available.is_err() { + return Ok(0); + } + + loop { + match load_input_since(hi, Source::Input) { + Ok(_) => { + lo = hi; + hi *= 2; + } + Err(SysError::IndexOutOfBound) => { + break; + } + Err(e) => return Err(e), + } + } + + while (lo + 1) != hi { + let i = (lo + hi) / 2; + match load_input_since(i, Source::Input) { + Ok(_) => { + lo = i; + } + Err(SysError::IndexOutOfBound) => { + hi = i; + } + Err(e) => return Err(e), + } + } + + Ok(hi) +} + +/// +/// parse transaction with typed message and return 2 values: +/// 1. digest message, 32 bytes message for signature verification +/// 2. lock, lock field in SighashWithAction or Sighash. Normally as signature. +/// +pub fn parse_typed_message() -> Result<([u8; 32], Vec), Error> { + // Ensure that a SighashWitAction is present throughout the entire transaction + check_sighash_with_action()?; + // There are 2 possible values: Sighash or SighashWithAction + let witness = fetch_sighash()?; + let (lock, typed_message) = match witness.to_enum() { + ExtendedWitnessUnion::SighashWithAction(s) => (s.lock(), s.message()), + ExtendedWitnessUnion::Sighash(s) => { + let sighash_with_action = search_sighash_with_action()?; + (s.lock(), sighash_with_action.message()) + } + _ => { + return Err(Error::WrongSighashWithAction); + } + }; + let skeleton_hash = generate_skeleton_hash()?; + let digest_message = generate_final_hash(&skeleton_hash, typed_message.as_slice()); + let lock = lock.as_slice(); + Ok((digest_message, lock.to_vec())) +} diff --git a/ckb-typed-message/src/schemas/basic.rs b/ckb-typed-message/src/schemas/basic.rs new file mode 100644 index 0000000..b6ae6af --- /dev/null +++ b/ckb-typed-message/src/schemas/basic.rs @@ -0,0 +1,3965 @@ +// Generated by Molecule 0.7.5 + +use super::blockchain::*; +use molecule::prelude::*; +#[derive(Clone)] +pub struct String(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for String { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for String { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for String { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + let raw_data = hex_string(&self.raw_data()); + write!(f, "{}(0x{})", Self::NAME, raw_data) + } +} +impl ::core::default::Default for String { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + String::new_unchecked(v) + } +} +impl String { + const DEFAULT_VALUE: [u8; 4] = [0, 0, 0, 0]; + pub const ITEM_SIZE: usize = 1; + pub fn total_size(&self) -> usize { + molecule::NUMBER_SIZE + Self::ITEM_SIZE * self.item_count() + } + pub fn item_count(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> Byte { + let start = molecule::NUMBER_SIZE + Self::ITEM_SIZE * idx; + let end = start + Self::ITEM_SIZE; + Byte::new_unchecked(self.0.slice(start..end)) + } + pub fn raw_data(&self) -> molecule::bytes::Bytes { + self.0.slice(molecule::NUMBER_SIZE..) + } + pub fn as_reader<'r>(&'r self) -> StringReader<'r> { + StringReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for String { + type Builder = StringBuilder; + const NAME: &'static str = "String"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + String(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + StringReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + StringReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().extend(self.into_iter()) + } +} +#[derive(Clone, Copy)] +pub struct StringReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for StringReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for StringReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for StringReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + let raw_data = hex_string(&self.raw_data()); + write!(f, "{}(0x{})", Self::NAME, raw_data) + } +} +impl<'r> StringReader<'r> { + pub const ITEM_SIZE: usize = 1; + pub fn total_size(&self) -> usize { + molecule::NUMBER_SIZE + Self::ITEM_SIZE * self.item_count() + } + pub fn item_count(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option> { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> ByteReader<'r> { + let start = molecule::NUMBER_SIZE + Self::ITEM_SIZE * idx; + let end = start + Self::ITEM_SIZE; + ByteReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn raw_data(&self) -> &'r [u8] { + &self.as_slice()[molecule::NUMBER_SIZE..] + } +} +impl<'r> molecule::prelude::Reader<'r> for StringReader<'r> { + type Entity = String; + const NAME: &'static str = "StringReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + StringReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let item_count = molecule::unpack_number(slice) as usize; + if item_count == 0 { + if slice_len != molecule::NUMBER_SIZE { + return ve!(Self, TotalSizeNotMatch, molecule::NUMBER_SIZE, slice_len); + } + return Ok(()); + } + let total_size = molecule::NUMBER_SIZE + Self::ITEM_SIZE * item_count; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct StringBuilder(pub(crate) Vec); +impl StringBuilder { + pub const ITEM_SIZE: usize = 1; + pub fn set(mut self, v: Vec) -> Self { + self.0 = v; + self + } + pub fn push(mut self, v: Byte) -> Self { + self.0.push(v); + self + } + pub fn extend>(mut self, iter: T) -> Self { + for elem in iter { + self.0.push(elem); + } + self + } + pub fn replace(&mut self, index: usize, v: Byte) -> Option { + self.0 + .get_mut(index) + .map(|item| ::core::mem::replace(item, v)) + } +} +impl molecule::prelude::Builder for StringBuilder { + type Entity = String; + const NAME: &'static str = "StringBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE + Self::ITEM_SIZE * self.0.len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + writer.write_all(&molecule::pack_number(self.0.len() as molecule::Number))?; + for inner in &self.0[..] { + writer.write_all(inner.as_slice())?; + } + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + String::new_unchecked(inner.into()) + } +} +pub struct StringIterator(String, usize, usize); +impl ::core::iter::Iterator for StringIterator { + type Item = Byte; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl ::core::iter::ExactSizeIterator for StringIterator { + fn len(&self) -> usize { + self.2 - self.1 + } +} +impl ::core::iter::IntoIterator for String { + type Item = Byte; + type IntoIter = StringIterator; + fn into_iter(self) -> Self::IntoIter { + let len = self.len(); + StringIterator(self, 0, len) + } +} +#[derive(Clone)] +pub struct Address(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Address { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Address { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Address { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}(", Self::NAME)?; + self.to_enum().display_inner(f)?; + write!(f, ")") + } +} +impl ::core::default::Default for Address { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Address::new_unchecked(v) + } +} +impl Address { + const DEFAULT_VALUE: [u8; 57] = [ + 0, 0, 0, 0, 53, 0, 0, 0, 16, 0, 0, 0, 48, 0, 0, 0, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + pub const ITEMS_COUNT: usize = 1; + pub fn item_id(&self) -> molecule::Number { + molecule::unpack_number(self.as_slice()) + } + pub fn to_enum(&self) -> AddressUnion { + let inner = self.0.slice(molecule::NUMBER_SIZE..); + match self.item_id() { + 0 => Script::new_unchecked(inner).into(), + _ => panic!("{}: invalid data", Self::NAME), + } + } + pub fn as_reader<'r>(&'r self) -> AddressReader<'r> { + AddressReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Address { + type Builder = AddressBuilder; + const NAME: &'static str = "Address"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Address(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + AddressReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + AddressReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().set(self.to_enum()) + } +} +#[derive(Clone, Copy)] +pub struct AddressReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for AddressReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for AddressReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for AddressReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}(", Self::NAME)?; + self.to_enum().display_inner(f)?; + write!(f, ")") + } +} +impl<'r> AddressReader<'r> { + pub const ITEMS_COUNT: usize = 1; + pub fn item_id(&self) -> molecule::Number { + molecule::unpack_number(self.as_slice()) + } + pub fn to_enum(&self) -> AddressUnionReader<'r> { + let inner = &self.as_slice()[molecule::NUMBER_SIZE..]; + match self.item_id() { + 0 => ScriptReader::new_unchecked(inner).into(), + _ => panic!("{}: invalid data", Self::NAME), + } + } +} +impl<'r> molecule::prelude::Reader<'r> for AddressReader<'r> { + type Entity = Address; + const NAME: &'static str = "AddressReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + AddressReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let item_id = molecule::unpack_number(slice); + let inner_slice = &slice[molecule::NUMBER_SIZE..]; + match item_id { + 0 => ScriptReader::verify(inner_slice, compatible), + _ => ve!(Self, UnknownItem, Self::ITEMS_COUNT, item_id), + }?; + Ok(()) + } +} +#[derive(Debug, Default)] +pub struct AddressBuilder(pub(crate) AddressUnion); +impl AddressBuilder { + pub const ITEMS_COUNT: usize = 1; + pub fn set(mut self, v: I) -> Self + where + I: ::core::convert::Into, + { + self.0 = v.into(); + self + } +} +impl molecule::prelude::Builder for AddressBuilder { + type Entity = Address; + const NAME: &'static str = "AddressBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE + self.0.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + writer.write_all(&molecule::pack_number(self.0.item_id()))?; + writer.write_all(self.0.as_slice()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Address::new_unchecked(inner.into()) + } +} +#[derive(Debug, Clone)] +pub enum AddressUnion { + Script(Script), +} +#[derive(Debug, Clone, Copy)] +pub enum AddressUnionReader<'r> { + Script(ScriptReader<'r>), +} +impl ::core::default::Default for AddressUnion { + fn default() -> Self { + AddressUnion::Script(::core::default::Default::default()) + } +} +impl ::core::fmt::Display for AddressUnion { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + AddressUnion::Script(ref item) => { + write!(f, "{}::{}({})", Self::NAME, Script::NAME, item) + } + } + } +} +impl<'r> ::core::fmt::Display for AddressUnionReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + AddressUnionReader::Script(ref item) => { + write!(f, "{}::{}({})", Self::NAME, Script::NAME, item) + } + } + } +} +impl AddressUnion { + pub(crate) fn display_inner(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + AddressUnion::Script(ref item) => write!(f, "{}", item), + } + } +} +impl<'r> AddressUnionReader<'r> { + pub(crate) fn display_inner(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + AddressUnionReader::Script(ref item) => write!(f, "{}", item), + } + } +} +impl ::core::convert::From