├── .gitignore ├── .travis.yml ├── Cargo.toml ├── README.md ├── src ├── hex.rs ├── bin │ └── ots_info.rs ├── error.rs ├── op.rs ├── attestation.rs ├── timestamp.rs ├── ser.rs └── lib.rs └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | matrix: 7 | allow_failures: 8 | - rust: nightly 9 | 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "opentimestamps" 3 | version = "0.2.0" 4 | license = "MIT OR Apache-2.0" 5 | homepage = "https://github.com/opentimestamps/rust-opentimestamps/" 6 | repository = "https://github.com/opentimestamps/rust-opentimestamps/" 7 | documentation = "https://docs.rs/opentimestamps/" 8 | description = "Rust library for parsing, verifying, and serializing OpenTimestamps timestamps" 9 | 10 | keywords = [ "crypto", "bitcoin", "ots", "opentimestamps", "timestamping" ] 11 | 12 | [lib] 13 | name = "opentimestamps" 14 | path = "src/lib.rs" 15 | 16 | [[bin]] 17 | name = "ots-info" 18 | path = "src/bin/ots_info.rs" 19 | 20 | [dependencies] 21 | bitcoin_hashes = "0.12.0" 22 | env_logger = "0.10" 23 | log = "0.4" 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # OpenTimestamps Library for Rust 3 | 4 | [OpenTimestamps is a service which provides provable timestamping using the Bitcoin 5 | blockchain](https://petertodd.org/2016/opentimestamps-announcement) 6 | 7 | This library is in early stages. It currently supports parsing and serialization 8 | of `.ots` files, and can play them forward to compute the eventual hashes that 9 | actually wind up in the chain. 10 | 11 | There is lots of remaining work to do as far as documentation and examples. 12 | 13 | A timestamp viewer using this library is available at [wpsoftware.net](https://www.wpsoftware.net/ots/). 14 | Its [source code is here](https://github.com/apoelstra/ots-viewer). 15 | 16 | In `src/bin/ots_info.rs` there is a simple application that reads a `.ots` file and 17 | dumps its contents to stdout, as a basic usage example. It really just calls 18 | `fmt::Display::fmt` on the `DetachedTimestampFile` structure; in the absense of any 19 | other documentation, reading that function is a good starting point for seeing how 20 | the data structures work. You can execute it with `cargo run -- ` 21 | 22 | [Documentation](https://www.wpsoftware.net/rustdoc/opentimestamps/) 23 | 24 | -------------------------------------------------------------------------------- /src/hex.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) The OpenTimestamps developers 2 | // 3 | // This file is part of rust-opentimestamps. 4 | // 5 | // It is subject to the license terms in the LICENSE file found in the 6 | // top-level directory of this distribution. 7 | // 8 | // No part of rust-opentimestamps including this file, may be copied, modified, 9 | // propagated, or distributed except according to the terms contained in the 10 | // LICENSE file. 11 | 12 | //! # Hex 13 | //! 14 | //! Quick and dirty bytes-to-hex implementation 15 | //! 16 | 17 | use std::fmt::{self, Write}; 18 | 19 | /// Wrapper around a byteslice that allows formatting as hex 20 | pub struct Hexed<'a>(pub &'a [u8]); 21 | 22 | static CHARS: [char; 16] = [ 23 | '0', '1', '2', '3', '4', '5', '6', '7', 24 | '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' 25 | ]; 26 | 27 | impl<'a> fmt::Debug for Hexed<'a> { 28 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 29 | for byte in self.0 { 30 | f.write_char(CHARS[(*byte as usize) >> 4])?; 31 | f.write_char(CHARS[(*byte as usize) & 0x0f])?; 32 | } 33 | Ok(()) 34 | } 35 | } 36 | 37 | impl<'a> fmt::Display for Hexed<'a> { 38 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 39 | fmt::Debug::fmt(self, f) 40 | } 41 | } 42 | 43 | impl<'a> fmt::LowerHex for Hexed<'a> { 44 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 45 | fmt::Debug::fmt(self, f) 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/bin/ots_info.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) The OpenTimestamps developers 2 | // 3 | // This file is part of rust-opentimestamps. 4 | // 5 | // It is subject to the license terms in the LICENSE file found in the 6 | // top-level directory of this distribution. 7 | // 8 | // No part of rust-opentimestamps including this file, may be copied, modified, 9 | // propagated, or distributed except according to the terms contained in the 10 | // LICENSE file. 11 | 12 | //! # OpenTimestamps Viewer 13 | //! 14 | //! Simple application to open an OTS info file and dump its contents 15 | //! to stdout in a human-readable format 16 | //! 17 | 18 | // Coding conventions 19 | #![deny(non_upper_case_globals)] 20 | #![deny(non_camel_case_types)] 21 | #![deny(non_snake_case)] 22 | #![deny(unused_mut)] 23 | #![deny(missing_docs)] 24 | 25 | extern crate env_logger; 26 | extern crate opentimestamps as ots; 27 | 28 | use std::{env, fs, process}; 29 | 30 | fn main() { 31 | env_logger::init(); 32 | 33 | let args: Vec = env::args().collect(); 34 | if args.len() != 2 { 35 | println!("Usage: {} ", args[0]); 36 | process::exit(1); 37 | } 38 | 39 | let fh = match fs::File::open(&args[1]) { 40 | Ok(fh) => fh, 41 | Err(e) => { 42 | println!("Failed to open {}: {}", args[1], e); 43 | process::exit(1); 44 | } 45 | }; 46 | 47 | let ots = match ots::DetachedTimestampFile::from_reader(fh) { 48 | Ok(ots) => ots, 49 | Err(e) => { 50 | println!("Failed to parse {}: {}", args[1], e); 51 | process::exit(1); 52 | } 53 | }; 54 | 55 | println!("{}", ots); 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) The OpenTimestamps developers 2 | // 3 | // This file is part of rust-opentimestamps. 4 | // 5 | // It is subject to the license terms in the LICENSE file found in the 6 | // top-level directory of this distribution. 7 | // 8 | // No part of rust-opentimestamps including this file, may be copied, modified, 9 | // propagated, or distributed except according to the terms contained in the 10 | // LICENSE file. 11 | 12 | //! # Errors 13 | //! 14 | //! Library-wide error type and associated boilerplate 15 | //! 16 | 17 | use std::error; 18 | use std::{fmt, io}; 19 | use std::string::FromUtf8Error; 20 | 21 | /// Library-wide error structure 22 | #[allow(missing_docs)] 23 | #[derive(Debug)] 24 | pub enum Error { 25 | /// Recursed too deeply 26 | StackOverflow, 27 | /// A URI had a character we don't like 28 | InvalidUriChar(char), 29 | /// A digest type tag was not recognized 30 | BadDigestTag(u8), 31 | /// Decoded an op tag that we don't recognize 32 | BadOpTag(u8), 33 | /// OTS file began with invalid magic bytes 34 | BadMagic(Vec), 35 | /// OTS file has version we don't understand 36 | BadVersion(usize), 37 | /// A byte vector had an invalid length 38 | BadLength { min: usize, max: usize, val: usize }, 39 | /// Expected EOF but didn't get it 40 | TrailingBytes, 41 | /// UTF8 42 | Utf8(FromUtf8Error), 43 | /// I/O error 44 | Io(io::Error) 45 | } 46 | 47 | impl From for Error { 48 | fn from(e: FromUtf8Error) -> Error { 49 | Error::Utf8(e) 50 | } 51 | } 52 | 53 | impl From for Error { 54 | fn from(e: io::Error) -> Error { 55 | Error::Io(e) 56 | } 57 | } 58 | 59 | impl fmt::Display for Error { 60 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 61 | match *self { 62 | Error::StackOverflow => f.write_str("recursion limit reached"), 63 | Error::InvalidUriChar(c) => write!(f, "invalid character `{}` in URI", c), 64 | Error::BadDigestTag(t) => write!(f, "invalid digest tag 0x{:02x}", t), 65 | Error::BadOpTag(t) => write!(f, "invalid op tag 0x{:02x}", t), 66 | Error::BadMagic(ref x) => write!(f, "bad magic bytes `{:?}`, is this a timestamp file?", x), 67 | Error::BadVersion(v) => write!(f, "version {} timestamps not understood", v), 68 | Error::BadLength { min, max, val } => write!(f, "length {} should be between {} and {} inclusive", val, min, max), 69 | Error::TrailingBytes => f.write_str("expected eof not"), // lol 70 | Error::Utf8(ref e) => fmt::Display::fmt(e, f), 71 | Error::Io(ref e) => fmt::Display::fmt(e, f) 72 | } 73 | } 74 | } 75 | 76 | impl error::Error for Error { 77 | 78 | fn cause(&self) -> Option<&dyn error::Error> { 79 | match *self { 80 | Error::Utf8(ref e) => Some(e), 81 | Error::Io(ref e) => Some(e), 82 | _ => None 83 | } 84 | } 85 | } 86 | 87 | -------------------------------------------------------------------------------- /src/op.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) The OpenTimestamps developers 2 | // 3 | // This file is part of rust-opentimestamps. 4 | // 5 | // It is subject to the license terms in the LICENSE file found in the 6 | // top-level directory of this distribution. 7 | // 8 | // No part of rust-opentimestamps including this file, may be copied, modified, 9 | // propagated, or distributed except according to the terms contained in the 10 | // LICENSE file. 11 | 12 | //! # Operations 13 | //! 14 | //! Various operations that can be done when producing a timestamp. These 15 | //! include prepending data, appending data, and hashing. Importantly, every 16 | //! operation, when giving a commitment as input, yields a commitment as 17 | //! output. Without this property it would be possible to create fake 18 | //! timestamps. 19 | //! 20 | 21 | use std::fmt; 22 | use std::io::{Read, Write}; 23 | 24 | use bitcoin_hashes::{Hash, ripemd160, sha1, sha256}; 25 | use error::Error; 26 | use hex::Hexed; 27 | use ser; 28 | 29 | /// Maximum length of an op result 30 | const MAX_OP_LENGTH: usize = 4096; 31 | 32 | /// All the types of operations supported 33 | #[derive(Clone, PartialEq, Eq, Debug)] 34 | #[allow(missing_docs)] 35 | pub enum Op { 36 | // crypto (unary) ops 37 | Sha1, 38 | Sha256, 39 | Ripemd160, 40 | // unary ops 41 | Hexlify, 42 | Reverse, 43 | // binary ops 44 | Append(Vec), 45 | Prepend(Vec) 46 | } 47 | 48 | impl Op { 49 | /// Returns the 8-bit tag identifying the op 50 | pub fn tag(&self) -> u8 { 51 | match *self { 52 | Op::Sha1 => 0x02, 53 | Op::Sha256 => 0x08, 54 | Op::Ripemd160 => 0x03, 55 | Op::Hexlify => 0xf3, 56 | Op::Reverse => 0xf2, 57 | Op::Append(_) => 0xf0, 58 | Op::Prepend(_) => 0xf1 59 | } 60 | } 61 | 62 | /// Deserialize an arbitrary op 63 | pub fn deserialize(deser: &mut ser::Deserializer) -> Result { 64 | let tag = deser.read_byte()?; 65 | Op::deserialize_with_tag(deser, tag) 66 | } 67 | 68 | /// Deserialize an op with the designated tag 69 | pub fn deserialize_with_tag(deser: &mut ser::Deserializer, tag: u8) -> Result { 70 | match tag { 71 | // unary ops are trivial 72 | 0x02 => Ok(Op::Sha1), 73 | 0x08 => Ok(Op::Sha256), 74 | 0x03 => Ok(Op::Ripemd160), 75 | 0xf3 => Ok(Op::Hexlify), 76 | 0xf2 => Ok(Op::Reverse), 77 | // binary ops are almost trivial 78 | 0xf0 => Ok(Op::Append(deser.read_bytes(1, MAX_OP_LENGTH)?)), 79 | 0xf1 => Ok(Op::Prepend(deser.read_bytes(1, MAX_OP_LENGTH)?)), 80 | x => Err(Error::BadOpTag(x)) 81 | } 82 | } 83 | 84 | /// Serialize the op into a serializer 85 | pub fn serialize(&self, ser: &mut ser::Serializer) -> Result<(), Error> { 86 | ser.write_byte(self.tag())?; 87 | if let Op::Append(ref data) = *self { 88 | ser.write_bytes(data)?; 89 | } 90 | if let Op::Prepend(ref data) = *self { 91 | ser.write_bytes(data)?; 92 | } 93 | Ok(()) 94 | } 95 | 96 | /// Execute an op on the given data 97 | pub fn execute(&self, input: &[u8]) -> Vec { 98 | match *self { 99 | Op::Sha1 => { 100 | sha1::Hash::hash(&input).to_byte_array().to_vec() 101 | } 102 | Op::Sha256 => { 103 | sha256::Hash::hash(&input).to_byte_array().to_vec() 104 | } 105 | Op::Ripemd160 => { 106 | ripemd160::Hash::hash(&input).to_byte_array().to_vec() 107 | } 108 | Op::Hexlify => { 109 | format!("{}", Hexed(input)).into_bytes() 110 | } 111 | Op::Reverse => { 112 | input.iter().cloned().rev().collect() 113 | } 114 | Op::Append(ref data) => { 115 | let mut vec = input.to_vec(); 116 | vec.extend(data); 117 | vec 118 | } 119 | Op::Prepend(ref data) => { 120 | let mut vec = data.to_vec(); 121 | vec.extend(input); 122 | vec 123 | } 124 | } 125 | } 126 | } 127 | 128 | impl fmt::Display for Op { 129 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 130 | match *self { 131 | Op::Sha1 => f.write_str("SHA1()"), 132 | Op::Sha256 => f.write_str("SHA256()"), 133 | Op::Ripemd160 => f.write_str("RIPEMD16()"), 134 | Op::Hexlify => f.write_str("Hexlify()"), 135 | Op::Reverse => f.write_str("Reverse()"), 136 | Op::Append(ref data) => write!(f, "Append({})", Hexed(data)), 137 | Op::Prepend(ref data) => write!(f, "Prepend({})", Hexed(data)) 138 | } 139 | } 140 | } 141 | 142 | 143 | -------------------------------------------------------------------------------- /src/attestation.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) The OpenTimestamps developers 2 | // 3 | // This file is part of rust-opentimestamps. 4 | // 5 | // It is subject to the license terms in the LICENSE file found in the 6 | // top-level directory of this distribution. 7 | // 8 | // No part of rust-opentimestamps including this file, may be copied, modified, 9 | // propagated, or distributed except according to the terms contained in the 10 | // LICENSE file. 11 | 12 | //! # Attestations 13 | //! 14 | //! An attestation is a claim that some data existed at some time. It 15 | //! comes from some server or from a blockchain. 16 | //! 17 | 18 | use std::fmt; 19 | use std::io::{Read, Write}; 20 | 21 | use error::Error; 22 | use hex::Hexed; 23 | use ser; 24 | 25 | /// Size in bytes of the tag identifying the attestation type 26 | const TAG_SIZE: usize = 8; 27 | /// Maximum length of a URI in a "pending" attestation 28 | const MAX_URI_LEN: usize = 1000; 29 | 30 | /// Tag indicating a Bitcoin attestation 31 | const BITCOIN_TAG: &[u8] = b"\x05\x88\x96\x0d\x73\xd7\x19\x01"; 32 | /// Tag indicating a pending attestation 33 | const PENDING_TAG: &[u8] = b"\x83\xdf\xe3\x0d\x2e\xf9\x0c\x8e"; 34 | 35 | /// An attestation that some data existed at some time 36 | #[allow(missing_docs)] 37 | #[derive(Clone, PartialEq, Eq, Debug)] 38 | pub enum Attestation { 39 | /// An attestation from a Bitcoin blockheader. This consists of a blockheight 40 | /// and nothing more, it is expected that the current hash is equal to the 41 | /// Merkle root of the block at this height. 42 | Bitcoin { 43 | height: usize 44 | }, 45 | /// An attestation from some server. It is commented at length in Peter Todd's 46 | /// `python-opentimestamps` that the server should be expected to keep anything 47 | /// it attests to, forever, and therefore the only thing we store locally is a 48 | /// single simple URI with a very restricted charset. (The restricted charset 49 | /// seems mainly to be to avoid the software being used for nefarious purposes, 50 | /// as it will fetch this URI under some circumstances.) 51 | Pending { 52 | uri: String 53 | }, 54 | /// An unknown attestation that we just store straight 55 | Unknown { 56 | tag: Vec, 57 | data: Vec 58 | } 59 | } 60 | 61 | impl Attestation { 62 | /// Deserialize an arbitrary attestation 63 | pub fn deserialize(deser: &mut ser::Deserializer) -> Result { 64 | let tag = deser.read_fixed_bytes(TAG_SIZE)?; 65 | let len = deser.read_uint()?; 66 | 67 | if tag == BITCOIN_TAG { 68 | let height = deser.read_uint()?; 69 | Ok(Attestation::Bitcoin { 70 | height 71 | }) 72 | } else if tag == PENDING_TAG { 73 | // This validation logic copied from python-opentimestamps. Peter comments 74 | // that he is deliberately avoiding ?, &, @, etc., to "keep us out of trouble" 75 | let uri_bytes = deser.read_bytes(0, MAX_URI_LEN)?; 76 | let uri_string = String::from_utf8(uri_bytes)?; 77 | for ch in uri_string.chars() { 78 | match ch { 79 | 'a'..='z' => {} 80 | 'A'..='Z' => {} 81 | '0'..='9' => {} 82 | '.' | '-' | '_' | '/' | ':' => {}, 83 | x => return Err(Error::InvalidUriChar(x)) 84 | } 85 | } 86 | Ok(Attestation::Pending { 87 | uri: uri_string 88 | }) 89 | } else { 90 | Ok(Attestation::Unknown { 91 | tag, 92 | data: deser.read_fixed_bytes(len)? 93 | }) 94 | } 95 | } 96 | 97 | /// Serialize an attestation 98 | pub fn serialize(&self, ser: &mut ser::Serializer) -> Result<(), Error> { 99 | let mut byte_ser = ser::Serializer::new(vec![]); 100 | match *self { 101 | Attestation::Bitcoin { height } => { 102 | ser.write_fixed_bytes(BITCOIN_TAG)?; 103 | byte_ser.write_uint(height)?; 104 | ser.write_bytes(&byte_ser.into_inner()) 105 | } 106 | Attestation::Pending { ref uri } => { 107 | ser.write_fixed_bytes(PENDING_TAG)?; 108 | byte_ser.write_bytes(uri.as_bytes())?; 109 | ser.write_bytes(&byte_ser.into_inner()) 110 | } 111 | Attestation::Unknown { ref tag, ref data } => { 112 | ser.write_fixed_bytes(tag)?; 113 | ser.write_bytes(data) 114 | } 115 | } 116 | } 117 | } 118 | 119 | impl fmt::Display for Attestation { 120 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 121 | match *self { 122 | Attestation::Bitcoin { height } => write!(f, "Bitcoin block {}", height), 123 | Attestation::Pending { ref uri } => write!(f, "Pending: update URI {}", uri), 124 | Attestation::Unknown { ref tag, ref data } => write!(f, "unknown attestation type {}: {}", Hexed(tag), Hexed(data)), 125 | } 126 | } 127 | } 128 | 129 | -------------------------------------------------------------------------------- /src/timestamp.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) The OpenTimestamps developers 2 | // 3 | // This file is part of rust-opentimestamps. 4 | // 5 | // It is subject to the license terms in the LICENSE file found in the 6 | // top-level directory of this distribution. 7 | // 8 | // No part of rust-opentimestamps including this file, may be copied, modified, 9 | // propagated, or distributed except according to the terms contained in the 10 | // LICENSE file. 11 | 12 | //! # Timestamp 13 | //! 14 | 15 | use std::fmt; 16 | use std::io::{Read, Write}; 17 | 18 | use attestation::Attestation; 19 | use error::Error; 20 | use hex::Hexed; 21 | use op::Op; 22 | use ser; 23 | 24 | /// Anti-DoS 25 | const RECURSION_LIMIT: usize = 256; 26 | 27 | /// The actual contents of the execution step 28 | #[derive(Clone, PartialEq, Eq, Debug)] 29 | pub enum StepData { 30 | /// This step splits execution into multiple paths 31 | Fork, 32 | /// This step executes some concrete operation 33 | Op(Op), 34 | /// This step asserts an attestation of the current state by some timestamp service 35 | Attestation(Attestation) 36 | } 37 | 38 | /// An execution step in a timestamp verification 39 | #[derive(Clone, PartialEq, Eq, Debug)] 40 | pub struct Step { 41 | /// The contents of the step 42 | pub data: StepData, 43 | /// The output after execution 44 | pub output: Vec, 45 | /// A list of steps to execute after this one 46 | pub next: Vec 47 | } 48 | 49 | /// Main structure representing a timestamp 50 | #[derive(Clone, PartialEq, Eq, Debug)] 51 | pub struct Timestamp { 52 | /// The starting document digest 53 | pub start_digest: Vec, 54 | /// The first execution step in verifying it 55 | pub first_step: Step 56 | } 57 | 58 | impl Timestamp { 59 | /// Deserialize one step in a timestamp. 60 | fn deserialize_step_recurse(deser: &mut ser::Deserializer, input_digest: Vec, tag: Option, recursion_limit: usize) -> Result { 61 | if recursion_limit == 0 { 62 | return Err(Error::StackOverflow); 63 | } 64 | 65 | // Read next tag if we weren't given one 66 | let tag = match tag { 67 | Some(tag) => tag, 68 | None => deser.read_byte()? 69 | }; 70 | 71 | // A tag typically indicates an op to execute, but the two special values 72 | // 0xff (fork) and 0x00 (read attestation and terminate path) are used to 73 | // provide multiple attestations 74 | match tag { 75 | // Attestation 76 | 0x00 => { 77 | let attest = Attestation::deserialize(deser)?; 78 | trace!("[{:3}] Attestation: {}", recursion_limit, attest); 79 | Ok(Step { 80 | data: StepData::Attestation(attest), 81 | output: input_digest, 82 | next: vec![] 83 | }) 84 | } 85 | // Fork 86 | 0xff => { 87 | let mut forks = vec![]; 88 | let mut next_tag = 0xff; 89 | while next_tag == 0xff { 90 | trace!("[{:3}] Forking..", recursion_limit); 91 | forks.push(Timestamp::deserialize_step_recurse(deser, input_digest.clone(), None, recursion_limit - 1)?); 92 | next_tag = deser.read_byte()?; 93 | } 94 | forks.push(Timestamp::deserialize_step_recurse(deser, input_digest.clone(), Some(next_tag), recursion_limit - 1)?); 95 | Ok(Step { 96 | data: StepData::Fork, 97 | output: input_digest, 98 | next: forks 99 | }) 100 | } 101 | // An actual tag 102 | tag => { 103 | // parse tag 104 | let op = Op::deserialize_with_tag(deser, tag)?; 105 | let output_digest = op.execute(&input_digest); 106 | trace!("[{:3}] Tag {} maps {} to {}.", recursion_limit, op, Hexed(&input_digest), Hexed(&output_digest)); 107 | // recurse 108 | let next = vec![Timestamp::deserialize_step_recurse(deser, output_digest.clone(), None, recursion_limit - 1)?]; 109 | Ok(Step { 110 | data: StepData::Op(op), 111 | output: output_digest, 112 | next, 113 | }) 114 | } 115 | } 116 | } 117 | 118 | /// Deserialize a timestamp 119 | pub fn deserialize(deser: &mut ser::Deserializer, digest: Vec) -> Result { 120 | let first_step = Timestamp::deserialize_step_recurse(deser, digest.clone(), None, RECURSION_LIMIT)?; 121 | 122 | Ok(Timestamp { 123 | start_digest: digest, 124 | first_step, 125 | }) 126 | } 127 | 128 | fn serialize_step_recurse(ser: &mut ser::Serializer, step: &Step) -> Result<(), Error> { 129 | match step.data { 130 | StepData::Fork => { 131 | for i in 0..step.next.len() - 1 { 132 | ser.write_byte(0xff)?; 133 | Timestamp::serialize_step_recurse(ser, &step.next[i])?; 134 | } 135 | Timestamp::serialize_step_recurse(ser, &step.next[step.next.len() - 1]) 136 | } 137 | StepData::Op(ref op) => { 138 | op.serialize(ser)?; 139 | Timestamp::serialize_step_recurse(ser, &step.next[0]) 140 | } 141 | StepData::Attestation(ref attest) => { 142 | ser.write_byte(0x00)?; 143 | attest.serialize(ser) 144 | } 145 | } 146 | } 147 | 148 | /// Serialize a timestamp 149 | pub fn serialize(&self, ser: &mut ser::Serializer) -> Result<(), Error> { 150 | Timestamp::serialize_step_recurse(ser, &self.first_step) 151 | } 152 | } 153 | 154 | fn fmt_recurse(step: &Step, f: &mut fmt::Formatter, depth: usize, first_line: bool) -> fmt::Result { 155 | fn indent(f: &mut fmt::Formatter, depth: usize, first_line: bool) -> fmt::Result { 156 | if depth == 0 { 157 | return Ok(()); 158 | } 159 | 160 | for _ in 0..depth-1 { 161 | f.write_str(" ")?; 162 | } 163 | if first_line { 164 | f.write_str("--->")?; 165 | } else { 166 | f.write_str(" ")?; 167 | } 168 | Ok(()) 169 | } 170 | 171 | match step.data { 172 | StepData::Fork => { 173 | indent(f, depth, first_line)?; 174 | writeln!(f, "(fork {} ways)", step.next.len())?; 175 | for fork in &step.next { 176 | fmt_recurse(fork, f, depth + 1, true)?; 177 | } 178 | Ok(()) 179 | } 180 | StepData::Op(ref op) => { 181 | indent(f, depth, first_line)?; 182 | writeln!(f, "execute {}", op)?; 183 | indent(f, depth, false)?; 184 | writeln!(f, " result {}", Hexed(&step.output))?; 185 | fmt_recurse(&step.next[0], f, depth, false) 186 | } 187 | StepData::Attestation(ref attest) => { 188 | indent(f, depth, first_line)?; 189 | writeln!(f, "result attested by {}", attest) 190 | } 191 | } 192 | } 193 | 194 | impl fmt::Display for Timestamp { 195 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 196 | writeln!(f, "Starting digest: {}", Hexed(&self.start_digest))?; 197 | fmt_recurse(&self.first_step, f, 0, false) 198 | } 199 | } 200 | 201 | -------------------------------------------------------------------------------- /src/ser.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) The OpenTimestamps developers 2 | // 3 | // This file is part of rust-opentimestamps. 4 | // 5 | // It is subject to the license terms in the LICENSE file found in the 6 | // top-level directory of this distribution. 7 | // 8 | // No part of rust-opentimestamps including this file, may be copied, modified, 9 | // propagated, or distributed except according to the terms contained in the 10 | // LICENSE file. 11 | 12 | //! # Serialization 13 | //! 14 | //! Supports deserialization and serialization of OTS info files 15 | //! 16 | 17 | use std::fmt; 18 | use std::io::{Read, Write}; 19 | 20 | use error::Error; 21 | use hex::Hexed; 22 | use timestamp::Timestamp; 23 | 24 | /// Magic bytes that every proof must start with 25 | const MAGIC: &[u8] = b"\x00OpenTimestamps\x00\x00Proof\x00\xbf\x89\xe2\xe8\x84\xe8\x92\x94"; 26 | 27 | /// Major version of timestamp files we understand 28 | const VERSION: usize = 1; 29 | 30 | /// Structure representing an info file 31 | #[derive(Clone, PartialEq, Eq, Debug)] 32 | pub struct DetachedTimestampFile { 33 | /// The claimed hash function used to produce the document digest 34 | pub digest_type: DigestType, 35 | /// The actual timestamp data 36 | pub timestamp: Timestamp 37 | } 38 | 39 | impl DetachedTimestampFile { 40 | /// Deserialize a info file from a reader 41 | pub fn from_reader(reader: R) -> Result { 42 | trace!("Start deserializing timestampfile from reader."); 43 | let mut deser = Deserializer::new(reader); 44 | 45 | deser.read_magic()?; 46 | trace!("Magic ok."); 47 | deser.read_version()?; 48 | trace!("Version ok."); 49 | let digest_type = DigestType::from_tag(deser.read_byte()?)?; 50 | trace!("Digest type: {}", digest_type); 51 | let digest = deser.read_fixed_bytes(digest_type.digest_len())?; 52 | trace!("Digest: {}", Hexed(&digest)); 53 | let timestamp = Timestamp::deserialize(&mut deser, digest)?; 54 | 55 | deser.check_eof()?; 56 | 57 | Ok(DetachedTimestampFile { 58 | digest_type, 59 | timestamp, 60 | }) 61 | } 62 | 63 | /// Serialize the file into a reader 64 | pub fn to_writer(&self, writer: W) -> Result<(), Error> { 65 | let mut ser = Serializer::new(writer); 66 | ser.write_magic()?; 67 | ser.write_version()?; 68 | ser.write_byte(self.digest_type.to_tag())?; 69 | // We write timestamp.start_digest here and not in `Timestamp::serialize` 70 | // to copy the way that python-opentimestamps is structured, though it is 71 | // an abstraction violation 72 | ser.write_fixed_bytes(&self.timestamp.start_digest)?; 73 | self.timestamp.serialize(&mut ser) 74 | } 75 | } 76 | 77 | impl fmt::Display for DetachedTimestampFile { 78 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 79 | writeln!(f, "{} digest of some data.", self.digest_type)?; 80 | writeln!(f, "{}", self.timestamp) 81 | } 82 | } 83 | 84 | /// Type of hash used to produce the document digest 85 | #[allow(missing_docs)] 86 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 87 | pub enum DigestType { 88 | Sha1, 89 | Sha256, 90 | Ripemd160 91 | } 92 | 93 | impl DigestType { 94 | /// Intepret a one-byte tag as a digest type 95 | pub fn from_tag(tag: u8) -> Result { 96 | match tag { 97 | 0x02 => Ok(DigestType::Sha1), 98 | 0x03 => Ok(DigestType::Ripemd160), 99 | 0x08 => Ok(DigestType::Sha256), 100 | x => Err(Error::BadDigestTag(x)) 101 | } 102 | } 103 | 104 | /// Serialize a digest type by its tag 105 | pub fn to_tag(self) -> u8 { 106 | match self { 107 | DigestType::Sha1 => 0x02, 108 | DigestType::Sha256 => 0x08, 109 | DigestType::Ripemd160 => 0x03 110 | } 111 | } 112 | 113 | /// The length, in bytes, that a digest with this hash function will be 114 | pub fn digest_len(self) -> usize { 115 | match self { 116 | DigestType::Sha1 => 20, 117 | DigestType::Sha256 => 32, 118 | DigestType::Ripemd160 => 20 119 | } 120 | } 121 | } 122 | 123 | impl fmt::Display for DigestType { 124 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 125 | match *self { 126 | DigestType::Sha1 => f.write_str("SHA1"), 127 | DigestType::Sha256 => f.write_str("SHA256"), 128 | DigestType::Ripemd160 => f.write_str("RIPEMD160"), 129 | } 130 | } 131 | } 132 | 133 | 134 | // ** I/O stuff ** 135 | 136 | /// Standard deserializer for OTS info files 137 | pub struct Deserializer { 138 | reader: R 139 | } 140 | 141 | impl Deserializer { 142 | /// Constructs a new deserializer from a reader 143 | pub fn new(reader: R) -> Deserializer { 144 | Deserializer { 145 | reader, 146 | } 147 | } 148 | 149 | /// Extracts the underlying reader from the deserializer 150 | pub fn into_inner(self) -> R { 151 | self.reader 152 | } 153 | 154 | /// Reads the magic bytes and checks that they are what we expect 155 | pub fn read_magic(&mut self) -> Result<(), Error> { 156 | let recv_magic = self.read_fixed_bytes(MAGIC.len())?; 157 | if recv_magic == MAGIC { 158 | Ok(()) 159 | } else { 160 | Err(Error::BadMagic(recv_magic)) 161 | } 162 | } 163 | 164 | /// Reads the version and checks that it is what we expect 165 | pub fn read_version(&mut self) -> Result<(), Error> { 166 | let recv_version = self.read_uint()?; 167 | if recv_version == VERSION { 168 | Ok(()) 169 | } else { 170 | Err(Error::BadVersion(recv_version)) 171 | } 172 | } 173 | 174 | 175 | /// Reads a single byte from the reader 176 | pub fn read_byte(&mut self) -> Result { 177 | let mut byte = [0]; 178 | self.reader.read_exact(&mut byte)?; 179 | Ok(byte[0]) 180 | } 181 | 182 | // PT's implementation has a `read_bool` method but it is 183 | // never actually used 184 | 185 | /// Deserializes an unsigned integer 186 | pub fn read_uint(&mut self) -> Result { 187 | let mut ret = 0; 188 | let mut shift = 0; 189 | 190 | loop { 191 | // Bottom 7 bits are value bits 192 | let byte = self.read_byte()?; 193 | ret |= ((byte & 0x7f) as usize) << shift; 194 | // Top bit is a continue bit 195 | if byte & 0x80 == 0 { 196 | break; 197 | } 198 | shift += 7; 199 | } 200 | 201 | Ok(ret) 202 | } 203 | 204 | /// Deserializes a fixed number of bytes 205 | pub fn read_fixed_bytes(&mut self, n: usize) -> Result, Error> { 206 | let mut ret = vec![0; n]; 207 | self.reader.read_exact(&mut ret)?; 208 | Ok(ret) 209 | } 210 | 211 | /// Deserializes a variable number of bytes 212 | pub fn read_bytes(&mut self, min: usize, max: usize) -> Result, Error> { 213 | let n = self.read_uint()?; 214 | if n < min || n > max { 215 | return Err(Error::BadLength { min, max, val: n }); 216 | } 217 | self.read_fixed_bytes(n) 218 | } 219 | 220 | /// Check that there is no trailing data 221 | pub fn check_eof(&mut self) -> Result<(), Error> { 222 | if self.reader.by_ref().bytes().next().is_none() { 223 | Ok(()) 224 | } else { 225 | Err(Error::TrailingBytes) 226 | } 227 | } 228 | } 229 | 230 | 231 | /// Standard serializer for OTS info files 232 | pub struct Serializer { 233 | writer: W 234 | } 235 | 236 | impl Serializer { 237 | /// Constructs a new deserializer from a reader 238 | pub fn new(writer: W) -> Serializer { 239 | Serializer { 240 | writer, 241 | } 242 | } 243 | 244 | /// Extracts the underlying writer from the serializer 245 | pub fn into_inner(self) -> W { 246 | self.writer 247 | } 248 | 249 | /// Writes the magic bytes 250 | pub fn write_magic(&mut self) -> Result<(), Error> { 251 | self.write_fixed_bytes(MAGIC) 252 | } 253 | 254 | /// Writes the major version 255 | pub fn write_version(&mut self) -> Result<(), Error> { 256 | self.write_uint(VERSION) 257 | } 258 | 259 | /// Writes a single byte to the writer 260 | pub fn write_byte(&mut self, byte: u8) -> Result<(), Error> { 261 | self.writer.write_all(&[byte]).map_err(Error::Io) 262 | } 263 | 264 | /// Write an unsigned integer 265 | pub fn write_uint(&mut self, mut n: usize) -> Result<(), Error> { 266 | if n == 0 { 267 | self.write_byte(0x00) 268 | } else { 269 | while n > 0 { 270 | if n > 0x7f { 271 | self.write_byte((n as u8) | 0x80)?; 272 | } else { 273 | self.write_byte(n as u8)?; 274 | } 275 | n >>= 7; 276 | } 277 | Ok(()) 278 | } 279 | } 280 | 281 | /// Write a fixed number of bytes 282 | pub fn write_fixed_bytes(&mut self, data: &[u8]) -> Result<(), Error> { 283 | self.writer.write_all(data).map_err(Error::Io) 284 | } 285 | 286 | /// Write a variable number of bytes 287 | pub fn write_bytes(&mut self, data: &[u8]) -> Result<(), Error> { 288 | self.write_uint(data.len())?; 289 | self.write_fixed_bytes(data) 290 | } 291 | } 292 | 293 | #[cfg(test)] 294 | mod tests { 295 | use super::*; 296 | 297 | #[test] 298 | fn digest_len() { 299 | assert_eq!(DigestType::Sha1.digest_len(), 20); 300 | assert_eq!(DigestType::Sha256.digest_len(), 32); 301 | assert_eq!(DigestType::Ripemd160.digest_len(), 20); 302 | } 303 | } 304 | 305 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) The OpenTimestamps developers 2 | // 3 | // This file is part of rust-opentimestamps. 4 | // 5 | // It is subject to the license terms in the LICENSE file found in the 6 | // top-level directory of this distribution. 7 | // 8 | // No part of rust-opentimestamps including this file, may be copied, modified, 9 | // propagated, or distributed except according to the terms contained in the 10 | // LICENSE file. 11 | 12 | //! # OpenTimestamps 13 | //! 14 | //! This is a library to support Rust applications that interact with 15 | //! [OpenTimestamps](https://petertodd.org/2016/opentimestamps-announcement) 16 | //! timestamps and servers. It is written in pure Rust. 17 | //! 18 | 19 | // Coding conventions 20 | #![deny(non_upper_case_globals)] 21 | #![deny(non_camel_case_types)] 22 | #![deny(non_snake_case)] 23 | #![deny(unused_mut)] 24 | #![deny(missing_docs)] 25 | 26 | extern crate bitcoin_hashes; 27 | #[macro_use] extern crate log; 28 | 29 | pub mod attestation; 30 | pub mod error; 31 | pub mod hex; 32 | pub mod op; 33 | pub mod timestamp; 34 | pub mod ser; 35 | 36 | pub use ser::DetachedTimestampFile; 37 | pub use timestamp::Timestamp; 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use super::*; 42 | 43 | const SMALL_TEST: &'static [u8] = b"\ 44 | \x00\x4f\x70\x65\x6e\x54\x69\x6d\x65\x73\x74\x61\x6d\x70\x73\x00\x00\x50\x72\x6f\x6f\x66\x00\xbf\x89\xe2\xe8\x84\xe8\x92\ 45 | \x94\x01\x08\xa7\x0d\xfe\x69\xc5\xa0\xd6\x28\x16\x78\x1a\xbb\x6e\x17\x77\x85\x47\x18\x62\x4a\x0d\x19\x42\x31\xad\xb1\x4c\ 46 | \x32\xee\x54\x38\xa4\xf0\x10\x7a\x46\x05\xde\x0a\x5b\x37\xcb\x21\x17\x59\xc6\x81\x2b\xfe\x2e\x08\xff\xf0\x10\x24\x4b\x79\ 47 | \xd5\x78\xaa\x38\xe3\x4f\x42\x7b\x0f\x3e\xd2\x55\xa5\x08\xf1\x04\x58\xa4\xc2\x57\xf0\x08\xa1\xa9\x2c\x61\xd5\x41\x72\x06\ 48 | \x00\x83\xdf\xe3\x0d\x2e\xf9\x0c\x8e\x2c\x2b\x68\x74\x74\x70\x73\x3a\x2f\x2f\x62\x6f\x62\x2e\x62\x74\x63\x2e\x63\x61\x6c\ 49 | \x65\x6e\x64\x61\x72\x2e\x6f\x70\x65\x6e\x74\x69\x6d\x65\x73\x74\x61\x6d\x70\x73\x2e\x6f\x72\x67\xf0\x10\xe0\x27\x85\x91\ 50 | \xe2\x88\x68\x19\xba\x7b\x3d\xdd\x63\x2e\xd3\xfe\x08\xf1\x04\x58\xa4\xc2\x56\xf0\x08\x38\xf2\xc7\xf4\xba\xf4\xbc\xd7\x00\ 51 | \x83\xdf\xe3\x0d\x2e\xf9\x0c\x8e\x2e\x2d\x68\x74\x74\x70\x73\x3a\x2f\x2f\x61\x6c\x69\x63\x65\x2e\x62\x74\x63\x2e\x63\x61\ 52 | \x6c\x65\x6e\x64\x61\x72\x2e\x6f\x70\x65\x6e\x74\x69\x6d\x65\x73\x74\x61\x6d\x70\x73\x2e\x6f\x72\x67"; 53 | 54 | const LARGE_TEST: &'static [u8] = b"\ 55 | \x00\x4f\x70\x65\x6e\x54\x69\x6d\x65\x73\x74\x61\x6d\x70\x73\x00\x00\x50\x72\x6f\x6f\x66\x00\xbf\x89\xe2\xe8\x84\xe8\x92\ 56 | \x94\x01\x08\x6f\xd9\xc1\xc4\xf0\x96\xb7\x7e\x6d\x44\x57\xba\xc1\xc7\xf5\x10\x10\xd3\x18\xdb\x48\x3f\x28\x68\xd3\x79\x58\ 57 | \x43\xf0\x98\xd3\x78\xf0\x10\xe2\xe2\x24\x43\x9e\x7f\x0f\xdd\x8c\x1e\xea\xc7\x3e\xa7\x39\xdb\x08\xf1\x20\xa5\x74\x44\x4a\ 58 | \xa5\x00\x02\xb6\xfe\x5a\xf2\x46\x26\x70\x0a\x4b\xfc\x95\x0d\x61\xf8\x13\x7c\xc3\x9d\xa8\x2d\x53\x27\x6c\x9d\x66\x08\xf0\ 59 | \x20\x02\xf3\x1f\xd5\xa2\xf0\xff\x08\xf7\xe0\x73\x38\x4b\x4f\xf5\x2b\xc5\xa0\x26\xf6\xfe\x42\x4a\x3b\x6c\x83\x58\x0e\x76\ 60 | \x9e\x59\xd2\x08\xf0\x20\xe0\xea\x0a\x32\x87\xcc\xb1\x0f\x39\x1c\x62\xf6\x8e\xb5\xa2\xde\x1d\x13\xbc\x24\xc5\xc0\xb4\x0f\ 61 | \x6a\x03\xe3\x6b\xbb\xa7\xaa\xb0\x08\xf0\x20\xd9\xc3\xfa\x8a\x65\xbb\x0c\xcf\xb3\x38\x5c\xc2\x03\x42\x05\x94\xe2\xe5\xa9\ 62 | \x34\x41\xbf\xf8\x5c\xcc\x53\xd1\x63\x9b\x0f\x2c\x85\x08\xf0\x20\x2f\xc4\x1f\x43\xb7\xab\xb0\x51\xf2\xe9\xee\x08\x39\xb8\ 63 | \x61\x9a\xd8\xc7\xb0\xc4\x04\xcd\xfc\xcd\xd5\xd0\x90\xbb\x3b\x42\xa8\x89\x08\xf0\x20\x0b\xae\x5b\x64\x92\x16\x89\xf7\xb3\ 64 | \xee\x1f\x86\xb1\xae\x79\xea\x7e\xd3\xd8\x22\x08\x4f\x3a\x2c\xed\xb3\x75\xd1\xc2\x36\x05\x93\x08\xf1\x20\xe9\x31\xb8\x22\ 65 | \x28\xdb\x72\xb4\x9e\x9c\x33\x9c\x3f\xd8\xa2\x48\x16\x26\x48\xc3\x0e\x3c\x03\x1d\xb5\x40\x20\x76\xf4\xe1\x9d\x48\x08\xf1\ 66 | \x20\x37\xe1\x51\xfe\x09\x9e\x20\x8f\x90\xfe\x51\x11\x65\x0f\x81\x38\xdf\xd3\x2f\xa8\x5f\x21\x30\xf1\x6c\xd5\xe9\x91\xb4\ 67 | \xf9\x48\x1c\x08\xff\xf0\x10\x2c\x2b\xd1\x10\x61\x89\x89\xd9\xa4\xc6\xbf\x60\xa8\xde\xec\x50\x08\xf1\x04\x58\x83\xf1\x71\ 68 | \xf0\x08\x6d\x45\x80\xfc\x64\xdf\xa9\x79\xff\x00\x83\xdf\xe3\x0d\x2e\xf9\x0c\x8e\x2c\x2b\x68\x74\x74\x70\x73\x3a\x2f\x2f\ 69 | \x62\x6f\x62\x2e\x62\x74\x63\x2e\x63\x61\x6c\x65\x6e\x64\x61\x72\x2e\x6f\x70\x65\x6e\x74\x69\x6d\x65\x73\x74\x61\x6d\x70\ 70 | \x73\x2e\x6f\x72\x67\x08\xf1\xae\x01\x01\x00\x00\x00\x01\x7e\x85\x5c\xd0\x5c\xb2\x31\x1f\xea\x5f\xed\xde\xea\x21\xbe\x34\ 71 | \xa5\x98\x2e\xb3\xfb\xa9\xbd\xca\x1d\x9e\xf9\x8a\x80\x05\xe1\x22\x00\x00\x00\x00\x48\x47\x30\x44\x02\x20\x3d\x4d\xec\x68\ 72 | \x13\xb7\xe2\x87\x0e\xc5\x38\xb3\x88\x2c\xd0\x5e\x5d\xb5\x71\xd7\x51\x1b\x6e\x31\x98\x69\x46\x2b\x02\x9f\xf2\x5a\x02\x20\ 73 | \x3e\xeb\x26\x3b\x36\x1a\x2b\x48\x20\xe9\x9c\xed\xce\xa1\x47\x1a\xcd\x4b\xee\x47\x3a\x23\xa8\x2f\xaf\xcf\xf1\xbe\x13\x15\ 74 | \xb3\x45\x01\xfd\xff\xff\xff\x02\xe3\x14\x13\x00\x00\x00\x00\x00\x23\x21\x02\x76\x18\xa4\x61\xfd\x2d\x26\xc4\xba\x77\xf1\ 75 | \xf7\xcd\x8a\xc5\x57\x7e\xea\x66\x5f\xfb\xc9\xa8\xde\x3c\x2e\x55\x91\x1c\xf0\x9f\x73\xac\x00\x00\x00\x00\x00\x00\x00\x00\ 76 | \x22\x6a\x20\xf0\x04\x73\xdb\x06\x00\x08\x08\xf0\x20\xa3\xb9\x56\xff\xca\xc2\x63\xfb\xd6\x6b\x33\x1e\x9c\x06\xa4\xb0\x96\ 77 | \x34\x2c\xff\xa7\x5a\xc8\x09\x90\x50\xd8\xda\x1c\x14\x94\x10\x08\x08\xf1\x20\x6c\x3c\x90\x80\x96\x2b\x36\x5f\xc4\x3e\x1f\ 78 | \xc6\x10\xe6\x91\x23\x7e\x33\x3e\x59\x98\xf8\xa8\x5d\xe3\xac\xf5\x79\x3c\x7d\x7d\x96\x08\x08\xf1\x20\x13\x88\x3d\x43\x52\ 79 | \xa3\x8a\x7f\x1b\xe2\xf4\x3a\xe3\x8d\xc3\x8f\xd4\x75\x39\xe4\xf1\xb1\x43\x90\xbe\x7d\x27\x0b\xb3\xf8\x1d\x4e\x08\x08\xf1\ 80 | \x20\x86\xe1\xb5\x77\xf7\xc7\xa1\xfd\x34\x52\x92\x81\xba\xcd\xec\x29\x3d\xa4\xd8\xac\xe8\x62\x2a\x6c\x04\xd9\x99\x05\x7d\ 81 | \x8b\x8e\x62\x08\x08\xf0\x20\xbf\x6b\x64\xf8\x33\x89\x98\x5d\x0a\xf4\xf7\xb4\x75\x3b\xb6\x8e\x57\x09\xff\xf1\x00\xa3\xdb\ 82 | \x0c\xb6\x1e\x6e\x44\xff\x8c\xf6\xae\x08\x08\xf1\x20\xfa\x8b\x54\x69\x92\xb6\x1c\xe2\xf1\xa9\x2f\x82\xde\x54\x5d\xae\x0d\ 83 | \xa7\x03\xef\x93\x2b\x6e\x4b\xda\x52\x3f\x2a\xec\x61\x7e\x5f\x08\x08\xf0\x20\x25\x61\xe8\xf4\xc2\x4d\x32\xc2\x14\x1c\x74\ 84 | \x64\x6d\xb0\x67\x30\x7f\x6c\x6e\x17\x05\xa4\xf5\x05\xb8\xab\x81\xaf\x1c\x16\x54\xc2\x08\x08\xf1\x20\x51\x7a\x29\xcb\x81\ 85 | \x52\x6f\x3b\x28\x71\x6f\xff\xb2\x4d\x5c\x8b\x6d\x6c\xcc\xd4\xb9\x8e\xec\xc9\xaa\xf0\x00\x37\x08\xb4\x25\x22\x08\x08\x00\ 86 | \x05\x88\x96\x0d\x73\xd7\x19\x01\x03\xf7\xb6\x1b\xf0\x10\x75\x85\xd6\x34\x8e\x2c\x8a\x1c\x7e\xd0\xa6\x97\x7a\xe4\xd2\xad\ 87 | \x08\xf1\x04\x58\x83\xf1\x71\xf0\x08\x5d\xeb\x89\x67\x36\x2e\x06\xb6\xff\x00\x83\xdf\xe3\x0d\x2e\xf9\x0c\x8e\x2e\x2d\x68\ 88 | \x74\x74\x70\x73\x3a\x2f\x2f\x61\x6c\x69\x63\x65\x2e\x62\x74\x63\x2e\x63\x61\x6c\x65\x6e\x64\x61\x72\x2e\x6f\x70\x65\x6e\ 89 | \x74\x69\x6d\x65\x73\x74\x61\x6d\x70\x73\x2e\x6f\x72\x67\x08\xf0\x20\x41\x41\x13\x62\xbc\xe1\x8d\x16\xff\x66\x0b\x43\x1a\ 90 | \x64\x4b\xb6\xc4\xa1\xf0\x65\x55\x62\x7a\xe0\x07\x8c\x7b\xb7\x21\x48\x0e\x4b\x08\xf0\x20\xd8\x68\x49\x86\xc4\x82\x11\x22\ 91 | \xca\x9f\x66\x6c\x55\x07\xb8\x9d\x89\x6b\x81\x2b\xbe\xc9\xc1\x84\x72\x09\x96\x4d\x0c\x4f\x2e\xc3\x08\xf1\x20\xb1\x07\xd6\ 92 | \x20\x2e\x7f\x79\xca\x83\x99\x17\xda\xdb\xeb\x20\x5b\x76\x16\x83\xb4\x9d\x16\x9d\xe2\x30\x25\x45\x2b\xf5\x79\x6a\xe2\x08\ 93 | \xf0\x20\x74\xc4\x8c\x02\x9d\x2f\x8f\x5f\xd7\x40\x9e\x8f\xcf\x68\x4e\x42\xbe\xb7\x2e\xbd\x99\xfe\x6c\xef\xff\x09\xe4\x47\ 94 | \x29\x49\x06\xa7\x08\xf0\x20\x62\x9e\xe2\x17\x44\x93\x5b\x51\x8c\x36\x14\x8a\xd3\x0f\xc7\xfc\x08\x87\x89\xc2\xb0\x00\xb4\ 95 | \x69\xcb\xb5\x0a\xe6\x1a\x34\xf3\x01\x08\xf1\xae\x01\x01\x00\x00\x00\x01\xa2\xc7\x0a\xd9\x76\x8b\x47\x6e\xb8\x2e\x07\x04\ 96 | \x75\x60\x3c\xdc\xb3\x01\x41\x4f\x62\xd5\x58\x10\x06\x13\x72\x41\x2d\x91\xe1\xbf\x00\x00\x00\x00\x48\x47\x30\x44\x02\x20\ 97 | \x52\x52\xd2\x89\x09\x05\x5e\xff\x8f\xb3\xab\x68\xf9\xcc\x11\x15\x03\x2b\x75\xe6\xcc\xfb\xf3\x84\x4b\xd9\x16\x14\xdd\x73\ 98 | \x7c\xd6\x02\x20\x21\xad\xd2\xd0\xab\x18\x8f\x4d\xb5\x55\x06\x6b\x0c\x38\x22\xd4\xba\xb0\x13\x43\x91\x98\x57\xdb\xaa\x11\ 99 | \x11\x5d\xc1\x4a\xd2\x21\x01\xfd\xff\xff\xff\x02\xb4\x4d\x44\x00\x00\x00\x00\x00\x23\x21\x03\x00\x9a\x9a\x91\x2d\x43\x76\ 100 | \x26\x8e\xc1\x37\x7c\x12\xd3\xd9\x9b\xd5\x1d\xa4\xf1\xed\xd8\x2c\x22\x74\xfd\x45\xde\xe1\xe3\xac\xd1\xac\x00\x00\x00\x00\ 101 | \x00\x00\x00\x00\x22\x6a\x20\xf0\x04\x74\xdb\x06\x00\x08\x08\xf1\x20\x5a\xbb\xb3\xdc\xd1\x24\x9e\xeb\x6d\x9b\xa9\x97\x2a\ 102 | \x94\x6e\xef\x2c\xdc\x3f\x32\x50\x38\xc1\x9d\x25\x3f\x5c\xa6\xd6\x93\x83\x7b\x08\x08\xf1\x20\xe9\x89\x14\x1b\xe1\x09\xac\ 103 | \xba\x19\x78\x20\xe1\x8a\xd9\xc2\x50\x64\x5c\xc0\x9d\xa5\x32\x89\x5e\xd9\x8d\x19\x1f\xf6\xf4\x24\xd6\x08\x08\xf0\x20\x48\ 104 | \xdc\xfc\x2f\xe8\x9e\x46\x4e\xd5\x28\x31\x90\x16\x56\xa1\x3b\x9f\x8d\x78\x37\xd6\xba\xe3\xfc\xa1\x8f\x14\x4a\xe0\x03\x73\ 105 | \x50\x08\x08\xf1\x20\xb2\x42\x65\xa8\x06\x99\xfd\x93\x01\xd5\x94\xfd\x90\x25\x9b\xd0\xed\x3b\x86\x8a\xf1\xcd\x36\x42\x08\ 106 | \x84\x7e\x64\x80\xb8\xab\x57\x08\x08\xf1\x20\xd0\xa7\x95\x39\xe4\x40\xf9\x9e\xe6\x0d\xba\xdd\x27\xa0\x71\x62\x25\x52\x37\ 107 | \x14\x0e\x91\x1b\xd0\x1d\xfc\x5c\xde\xc6\xdc\xaf\xec\x08\x08\xf1\x20\xd5\x6d\xf3\x0e\x00\xef\x52\xc8\xd4\xc2\x7e\x95\xe7\ 108 | \x7e\x28\xe4\x2e\x8d\xb9\xdb\xf4\x93\x3b\xd3\xc1\xfa\x80\x3c\x79\x2c\x68\xfa\x08\x08\xf1\x20\x91\xe3\x57\x66\xb6\xcf\x6d\ 109 | \x60\xd4\xeb\x6f\xa7\x28\xc6\x87\x6e\xca\xbf\x99\x92\x81\xc8\x2e\xd2\x00\xb0\x5a\xb1\x18\x78\xab\x49\x08\x08\xf0\x20\xad\ 110 | \x0c\xbb\x07\xe6\xa6\xa3\x59\xf8\x0f\x69\xa8\x7d\xcb\xc9\xbc\x78\x04\x79\xea\x73\xd2\xbe\xb6\xf7\x3c\xd3\xb9\x25\xa2\x89\ 111 | \x41\x08\x08\xf1\x20\x4b\x38\x70\x93\xad\xcd\xe0\xb6\x91\x58\xcf\x5d\x08\xdf\xf0\xf6\x2a\xa9\x4c\x77\x41\x52\xad\xa3\x9f\ 112 | \xed\x89\x57\x63\xf6\xad\xb3\x08\x08\xf1\x20\xe1\xc1\xae\xc4\x3e\x4c\xba\x0c\xc7\x6a\xed\xf0\x74\x33\xc2\x45\xaf\x3f\x8a\ 113 | \xe2\xc0\x56\x45\xa1\x9c\x09\x09\x36\x4c\x3f\x30\x6e\x08\x08\x00\x05\x88\x96\x0d\x73\xd7\x19\x01\x03\xf5\xb6\x1b"; 114 | 115 | #[test] 116 | fn round_trip() { 117 | let mut rt1 = vec![]; 118 | let mut rt2 = vec![]; 119 | 120 | let ots = DetachedTimestampFile::from_reader(SMALL_TEST); 121 | assert!(ots.is_ok()); 122 | assert!(ots.unwrap().to_writer(&mut rt1).is_ok()); 123 | assert_eq!(rt1, SMALL_TEST); 124 | 125 | let ots = DetachedTimestampFile::from_reader(LARGE_TEST); 126 | ots.as_ref().unwrap(); 127 | assert!(ots.is_ok()); 128 | assert!(ots.unwrap().to_writer(&mut rt2).is_ok()); 129 | assert_eq!(rt2, LARGE_TEST); 130 | } 131 | } 132 | 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | rust-opentimestamps is dual-licensed under the Apache License, Version 2.0 2 | (reproduced below) or the MIT license (reproduced below) at your option. All 3 | files in the project carrying such notice may not be copied, modified, or 4 | distributed except according to those terms. 5 | 6 | 7 | 8 | Apache License 9 | Version 2.0, January 2004 10 | http://www.apache.org/licenses/ 11 | 12 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 13 | 14 | 1. Definitions. 15 | 16 | "License" shall mean the terms and conditions for use, reproduction, 17 | and distribution as defined by Sections 1 through 9 of this document. 18 | 19 | "Licensor" shall mean the copyright owner or entity authorized by 20 | the copyright owner that is granting the License. 21 | 22 | "Legal Entity" shall mean the union of the acting entity and all 23 | other entities that control, are controlled by, or are under common 24 | control with that entity. For the purposes of this definition, 25 | "control" means (i) the power, direct or indirect, to cause the 26 | direction or management of such entity, whether by contract or 27 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 28 | outstanding shares, or (iii) beneficial ownership of such entity. 29 | 30 | "You" (or "Your") shall mean an individual or Legal Entity 31 | exercising permissions granted by this License. 32 | 33 | "Source" form shall mean the preferred form for making modifications, 34 | including but not limited to software source code, documentation 35 | source, and configuration files. 36 | 37 | "Object" form shall mean any form resulting from mechanical 38 | transformation or translation of a Source form, including but 39 | not limited to compiled object code, generated documentation, 40 | and conversions to other media types. 41 | 42 | "Work" shall mean the work of authorship, whether in Source or 43 | Object form, made available under the License, as indicated by a 44 | copyright notice that is included in or attached to the work 45 | (an example is provided in the Appendix below). 46 | 47 | "Derivative Works" shall mean any work, whether in Source or Object 48 | form, that is based on (or derived from) the Work and for which the 49 | editorial revisions, annotations, elaborations, or other modifications 50 | represent, as a whole, an original work of authorship. For the purposes 51 | of this License, Derivative Works shall not include works that remain 52 | separable from, or merely link (or bind by name) to the interfaces of, 53 | the Work and Derivative Works thereof. 54 | 55 | "Contribution" shall mean any work of authorship, including 56 | the original version of the Work and any modifications or additions 57 | to that Work or Derivative Works thereof, that is intentionally 58 | submitted to Licensor for inclusion in the Work by the copyright owner 59 | or by an individual or Legal Entity authorized to submit on behalf of 60 | the copyright owner. For the purposes of this definition, "submitted" 61 | means any form of electronic, verbal, or written communication sent 62 | to the Licensor or its representatives, including but not limited to 63 | communication on electronic mailing lists, source code control systems, 64 | and issue tracking systems that are managed by, or on behalf of, the 65 | Licensor for the purpose of discussing and improving the Work, but 66 | excluding communication that is conspicuously marked or otherwise 67 | designated in writing by the copyright owner as "Not a Contribution." 68 | 69 | "Contributor" shall mean Licensor and any individual or Legal Entity 70 | on behalf of whom a Contribution has been received by Licensor and 71 | subsequently incorporated within the Work. 72 | 73 | 2. Grant of Copyright License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | copyright license to reproduce, prepare Derivative Works of, 77 | publicly display, publicly perform, sublicense, and distribute the 78 | Work and such Derivative Works in Source or Object form. 79 | 80 | 3. Grant of Patent License. Subject to the terms and conditions of 81 | this License, each Contributor hereby grants to You a perpetual, 82 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 83 | (except as stated in this section) patent license to make, have made, 84 | use, offer to sell, sell, import, and otherwise transfer the Work, 85 | where such license applies only to those patent claims licensable 86 | by such Contributor that are necessarily infringed by their 87 | Contribution(s) alone or by combination of their Contribution(s) 88 | with the Work to which such Contribution(s) was submitted. If You 89 | institute patent litigation against any entity (including a 90 | cross-claim or counterclaim in a lawsuit) alleging that the Work 91 | or a Contribution incorporated within the Work constitutes direct 92 | or contributory patent infringement, then any patent licenses 93 | granted to You under this License for that Work shall terminate 94 | as of the date such litigation is filed. 95 | 96 | 4. Redistribution. You may reproduce and distribute copies of the 97 | Work or Derivative Works thereof in any medium, with or without 98 | modifications, and in Source or Object form, provided that You 99 | meet the following conditions: 100 | 101 | (a) You must give any other recipients of the Work or 102 | Derivative Works a copy of this License; and 103 | 104 | (b) You must cause any modified files to carry prominent notices 105 | stating that You changed the files; and 106 | 107 | (c) You must retain, in the Source form of any Derivative Works 108 | that You distribute, all copyright, patent, trademark, and 109 | attribution notices from the Source form of the Work, 110 | excluding those notices that do not pertain to any part of 111 | the Derivative Works; and 112 | 113 | (d) If the Work includes a "NOTICE" text file as part of its 114 | distribution, then any Derivative Works that You distribute must 115 | include a readable copy of the attribution notices contained 116 | within such NOTICE file, excluding those notices that do not 117 | pertain to any part of the Derivative Works, in at least one 118 | of the following places: within a NOTICE text file distributed 119 | as part of the Derivative Works; within the Source form or 120 | documentation, if provided along with the Derivative Works; or, 121 | within a display generated by the Derivative Works, if and 122 | wherever such third-party notices normally appear. The contents 123 | of the NOTICE file are for informational purposes only and 124 | do not modify the License. You may add Your own attribution 125 | notices within Derivative Works that You distribute, alongside 126 | or as an addendum to the NOTICE text from the Work, provided 127 | that such additional attribution notices cannot be construed 128 | as modifying the License. 129 | 130 | You may add Your own copyright statement to Your modifications and 131 | may provide additional or different license terms and conditions 132 | for use, reproduction, or distribution of Your modifications, or 133 | for any such Derivative Works as a whole, provided Your use, 134 | reproduction, and distribution of the Work otherwise complies with 135 | the conditions stated in this License. 136 | 137 | 5. Submission of Contributions. Unless You explicitly state otherwise, 138 | any Contribution intentionally submitted for inclusion in the Work 139 | by You to the Licensor shall be under the terms and conditions of 140 | this License, without any additional terms or conditions. 141 | Notwithstanding the above, nothing herein shall supersede or modify 142 | the terms of any separate license agreement you may have executed 143 | with Licensor regarding such Contributions. 144 | 145 | 6. Trademarks. This License does not grant permission to use the trade 146 | names, trademarks, service marks, or product names of the Licensor, 147 | except as required for reasonable and customary use in describing the 148 | origin of the Work and reproducing the content of the NOTICE file. 149 | 150 | 7. Disclaimer of Warranty. Unless required by applicable law or 151 | agreed to in writing, Licensor provides the Work (and each 152 | Contributor provides its Contributions) on an "AS IS" BASIS, 153 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 154 | implied, including, without limitation, any warranties or conditions 155 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 156 | PARTICULAR PURPOSE. You are solely responsible for determining the 157 | appropriateness of using or redistributing the Work and assume any 158 | risks associated with Your exercise of permissions under this License. 159 | 160 | 8. Limitation of Liability. In no event and under no legal theory, 161 | whether in tort (including negligence), contract, or otherwise, 162 | unless required by applicable law (such as deliberate and grossly 163 | negligent acts) or agreed to in writing, shall any Contributor be 164 | liable to You for damages, including any direct, indirect, special, 165 | incidental, or consequential damages of any character arising as a 166 | result of this License or out of the use or inability to use the 167 | Work (including but not limited to damages for loss of goodwill, 168 | work stoppage, computer failure or malfunction, or any and all 169 | other commercial damages or losses), even if such Contributor 170 | has been advised of the possibility of such damages. 171 | 172 | 9. Accepting Warranty or Additional Liability. While redistributing 173 | the Work or Derivative Works thereof, You may choose to offer, 174 | and charge a fee for, acceptance of support, warranty, indemnity, 175 | or other liability obligations and/or rights consistent with this 176 | License. However, in accepting such obligations, You may act only 177 | on Your own behalf and on Your sole responsibility, not on behalf 178 | of any other Contributor, and only if You agree to indemnify, 179 | defend, and hold each Contributor harmless for any liability 180 | incurred by, or claims asserted against, such Contributor by reason 181 | of your accepting any such warranty or additional liability. 182 | 183 | END OF TERMS AND CONDITIONS 184 | 185 | APPENDIX: How to apply the Apache License to your work. 186 | 187 | To apply the Apache License to your work, attach the following 188 | boilerplate notice, with the fields enclosed by brackets "[]" 189 | replaced with your own identifying information. (Don't include 190 | the brackets!) The text should be enclosed in the appropriate 191 | comment syntax for the file format. We also recommend that a 192 | file or class name and description of purpose be included on the 193 | same "printed page" as the copyright notice for easier 194 | identification within third-party archives. 195 | 196 | Copyright [yyyy] [name of copyright owner] 197 | 198 | Licensed under the Apache License, Version 2.0 (the "License"); 199 | you may not use this file except in compliance with the License. 200 | You may obtain a copy of the License at 201 | 202 | http://www.apache.org/licenses/LICENSE-2.0 203 | 204 | Unless required by applicable law or agreed to in writing, software 205 | distributed under the License is distributed on an "AS IS" BASIS, 206 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 207 | See the License for the specific language governing permissions and 208 | limitations under the License. 209 | 210 | 211 | MIT License 212 | =========== 213 | 214 | Copyright (c) The OpenTimestamps Developers 215 | 216 | Permission is hereby granted, free of charge, to any 217 | person obtaining a copy of this software and associated 218 | documentation files (the "Software"), to deal in the 219 | Software without restriction, including without 220 | limitation the rights to use, copy, modify, merge, 221 | publish, distribute, sublicense, and/or sell copies of 222 | the Software, and to permit persons to whom the Software 223 | is furnished to do so, subject to the following 224 | conditions: 225 | 226 | The above copyright notice and this permission notice 227 | shall be included in all copies or substantial portions 228 | of the Software. 229 | 230 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 231 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 232 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 233 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 234 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 235 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 236 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 237 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 238 | DEALINGS IN THE SOFTWARE. 239 | --------------------------------------------------------------------------------