Skip to content
Snippets Groups Projects
Commit 5af21406 authored by nanocryk's avatar nanocryk
Browse files

Merge branch 'protocol-documents-types-traits' into 'dev'

Protocol documents types traits

See merge request !13
parents e6e4788d 8ab83871
No related branches found
No related tags found
1 merge request!13Protocol documents types traits
[[package]]
name = "aho-corasick"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "base58" name = "base58"
version = "0.1.0" version = "0.1.0"
...@@ -40,6 +48,19 @@ dependencies = [ ...@@ -40,6 +48,19 @@ dependencies = [
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "duniter-protocol"
version = "0.1.0"
dependencies = [
"base58 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"duniter-keys 0.3.0",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"linked-hash-map 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "duniter-wotb" name = "duniter-wotb"
version = "0.4.1" version = "0.4.1"
...@@ -68,11 +89,29 @@ name = "gcc" ...@@ -68,11 +89,29 @@ name = "gcc"
version = "0.3.54" version = "0.3.54"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.35" version = "0.2.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "linked-hash-map"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "memchr"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "0.3.15" version = "0.3.15"
...@@ -92,6 +131,23 @@ name = "redox_syscall" ...@@ -92,6 +131,23 @@ name = "redox_syscall"
version = "0.1.37" version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "regex"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "rust-crypto" name = "rust-crypto"
version = "0.2.36" version = "0.2.36"
...@@ -156,6 +212,15 @@ dependencies = [ ...@@ -156,6 +212,15 @@ dependencies = [
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "thread_local"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.39" version = "0.1.39"
...@@ -171,6 +236,24 @@ name = "unicode-xid" ...@@ -171,6 +236,24 @@ name = "unicode-xid"
version = "0.0.4" version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unreachable"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "utf8-ranges"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.3" version = "0.3.3"
...@@ -191,6 +274,7 @@ version = "0.3.2" ...@@ -191,6 +274,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata] [metadata]
"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
"checksum base58 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" "checksum base58 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83"
"checksum base64 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c4a342b450b268e1be8036311e2c613d7f8a7ed31214dff1cc3b60852a3168d" "checksum base64 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c4a342b450b268e1be8036311e2c613d7f8a7ed31214dff1cc3b60852a3168d"
"checksum bincode 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9a6301db0b49fb63551bc15b5ae348147101cdf323242b93ec7546d5002ff1af" "checksum bincode 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9a6301db0b49fb63551bc15b5ae348147101cdf323242b93ec7546d5002ff1af"
...@@ -199,10 +283,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" ...@@ -199,10 +283,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
"checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb" "checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb"
"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
"checksum libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)" = "96264e9b293e95d25bfcbbf8a88ffd1aedc85b754eba8b7d78012f638ba220eb" "checksum libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)" = "96264e9b293e95d25bfcbbf8a88ffd1aedc85b754eba8b7d78012f638ba220eb"
"checksum linked-hash-map 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2aab0478615bb586559b0114d94dd8eca4fdbb73b443adcb0d00b61692b4bf"
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
"checksum rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "512870020642bb8c221bf68baa1b2573da814f6ccfe5c9699b1c303047abe9b1" "checksum rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "512870020642bb8c221bf68baa1b2573da814f6ccfe5c9699b1c303047abe9b1"
"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" "checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
"checksum regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "744554e01ccbd98fff8c457c3b092cd67af62a555a43bfe97ae8a0451f7799fa"
"checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e"
"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" "checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a"
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
"checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
...@@ -211,8 +300,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" ...@@ -211,8 +300,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e03f1c9530c3fb0a0a5c9b826bdd9246a5921ae995d75f512ac917fc4dd55b5" "checksum serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e03f1c9530c3fb0a0a5c9b826bdd9246a5921ae995d75f512ac917fc4dd55b5"
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
"checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098" "checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098"
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b09fb3b6f248ea4cd42c9a65113a847d612e17505d6ebd1f7357ad68a8bf8693" "checksum winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b09fb3b6f248ea4cd42c9a65113a847d612e17505d6ebd1f7357ad68a8bf8693"
"checksum winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ec6667f60c23eca65c561e63a13d81b44234c2e38a6b6c959025ee907ec614cc" "checksum winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ec6667f60c23eca65c561e63a13d81b44234c2e38a6b6c959025ee907ec614cc"
"checksum winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98f12c52b2630cd05d2c3ffd8e008f7f48252c042b4871c72aed9dc733b96668" "checksum winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98f12c52b2630cd05d2c3ffd8e008f7f48252c042b4871c72aed9dc733b96668"
...@@ -2,4 +2,5 @@ ...@@ -2,4 +2,5 @@
members = [ members = [
"wotb", "wotb",
"keys", "keys",
"protocol",
] ]
# Introduction # keys
`keys` is a crate managing cryptographic keys for the Duniter project. `keys` is a crate managing cryptographic keys for the Duniter project.
[Duniter]: https://duniter.org/en/ [Duniter]: https://duniter.org/en/
# How to use it ? ## How to use it
You can add `duniter-keys` as a `cargo` dependency in your Rust project. You can add `duniter-keys` as a `cargo` dependency in your Rust project.
[package]
name = "duniter-protocol"
version = "0.1.0"
authors = ["nanocryk <nanocryk@duniter.org>"]
description = "Implements the Duniter Protocol"
repository = "https://git.duniter.org/nodes/rust/duniter-rs"
readme = "README.md"
keywords = ["duniter", "blockchain", "cryptocurrency", "document"]
license = "AGPL-3.0"
[lib]
path = "lib.rs"
[dependencies]
rust-crypto = "0.2.36"
linked-hash-map = "0.5.0"
base58 = "0.1.0"
base64 = "0.8.0"
lazy_static = "1.0.0"
regex = "0.2"
duniter-keys = { path = "../keys" }
# protocol
`protocol` is a crate implementing the [Duniter] Protocol in [version 10][version10].
[Duniter]: https://duniter.org/en/
[version10]: https://git.duniter.org/nodes/typescript/duniter/blob/master/doc/Protocol.md
## How to use it
You can add `duniter-keys` as a `cargo` dependency in your Rust project.
// Copyright (C) 2018 The Duniter Project Developers.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Provide wrappers around Duniter documents and events.
use std::fmt::Debug;
use duniter_keys::{PrivateKey, PublicKey};
pub mod v10;
/// List of blockchain protocol versions.
#[derive(Debug)]
pub enum BlockchainProtocol {
/// Version 10.
V10(v10::documents::V10Document),
/// Version 11. (not done yet, but defined for tests)
V11(),
}
/// trait providing commun methods for any documents of any protocol version.
///
/// # Design choice
///
/// Allow only ed25519 for protocol 10 and many differents
/// schemes for protocol 11 through a proxy type.
pub trait Document: Debug {
/// Type of the `PublicKey` used by the document.
type PublicKey: PublicKey;
/// Data type of the currency code used by the document.
type CurrencyType: ?Sized;
/// Get document version.
fn version(&self) -> u16;
/// Get document currency.
fn currency(&self) -> &Self::CurrencyType;
/// Iterate over document issuers.
fn issuers(&self) -> &Vec<Self::PublicKey>;
/// Iterate over document signatures.
fn signatures(&self) -> &Vec<<Self::PublicKey as PublicKey>::Signature>;
/// Get document as bytes for signature verification.
fn as_bytes(&self) -> &[u8];
/// Verify signatures of document content (as text format)
fn verify_signatures(&self) -> VerificationResult {
let issuers_count = self.issuers().len();
let signatures_count = self.signatures().len();
if issuers_count != signatures_count {
VerificationResult::IncompletePairs(issuers_count, signatures_count)
} else {
let issuers = self.issuers();
let signatures = self.signatures();
let mismatches: Vec<_> = issuers
.iter()
.zip(signatures.iter())
.enumerate()
.filter(|&(_, (key, signature))| !key.verify(self.as_bytes(), signature))
.map(|(i, _)| i)
.collect();
if mismatches.is_empty() {
VerificationResult::Valid()
} else {
VerificationResult::Invalid(mismatches)
}
}
}
}
/// List of possible results for signature verification.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VerificationResult {
/// All signatures are valid.
Valid(),
/// Not same amount of issuers and signatures.
/// (issuers count, signatures count)
IncompletePairs(usize, usize),
/// Signatures don't match.
/// List of mismatching pairs indexes.
Invalid(Vec<usize>),
}
/// Trait allowing access to the document through it's proper protocol version.
///
/// This trait is generic over `P` providing all supported protocol version variants.
///
/// A lifetime is specified to allow enum variants to hold references to the document.
pub trait IntoSpecializedDocument<P> {
/// Get a protocol-specific document wrapped in an enum variant.
fn into_specialized(self) -> P;
}
/// Trait helper for building new documents.
pub trait DocumentBuilder {
/// Type of the builded document.
type Document: Document;
/// Type of the private keys signing the documents.
type PrivateKey: PrivateKey<
Signature = <<Self::Document as Document>::PublicKey as PublicKey>::Signature,
>;
/// Build a document with provided signatures.
fn build_with_signature(
self,
signatures: Vec<<<Self::Document as Document>::PublicKey as PublicKey>::Signature>,
) -> Self::Document;
/// Build a document and sign it with the private key.
fn build_and_sign(self, private_keys: Vec<Self::PrivateKey>) -> Self::Document;
}
/// Trait for a document parser from a `S` source
/// format to a `D` document. Will return the
/// parsed document or an `E` error.
pub trait DocumentParser<S, D, E> {
/// Parse a source and return a document or an error.
fn parse(source: S) -> Result<D, E>;
}
#[cfg(test)]
mod tests {
use super::*;
use duniter_keys::{Signature, ed25519};
// simple text document for signature testing
#[derive(Debug, Clone)]
struct PlainTextDocument {
pub text: &'static str,
pub issuers: Vec<ed25519::PublicKey>,
pub signatures: Vec<ed25519::Signature>,
}
impl Document for PlainTextDocument {
type PublicKey = ed25519::PublicKey;
type CurrencyType = str;
fn version(&self) -> u16 {
unimplemented!()
}
fn currency(&self) -> &str {
unimplemented!()
}
fn issuers(&self) -> &Vec<ed25519::PublicKey> {
&self.issuers
}
fn signatures(&self) -> &Vec<ed25519::Signature> {
&self.signatures
}
fn as_bytes(&self) -> &[u8] {
self.text.as_bytes()
}
}
#[test]
fn verify_signatures() {
let text = "Version: 10
Type: Identity
Currency: duniter_unit_test_currency
Issuer: DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV
UniqueID: tic
Timestamp: 0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
";
// good pair
let issuer1 = ed25519::PublicKey::from_base58(
"DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV",
).unwrap();
let sig1 = ed25519::Signature::from_base64(
"1eubHHbuNfilHMM0G2bI30iZzebQ2cQ1PC7uPAw08FGMM\
mQCRerlF/3pc4sAcsnexsxBseA/3lY03KlONqJBAg==",
).unwrap();
// incorrect pair
let issuer2 = ed25519::PublicKey::from_base58(
"DNann1Lh55eZMEDXeYt32bzHbA3NJR46DeQYCS2qQdLV",
).unwrap();
let sig2 = ed25519::Signature::from_base64(
"1eubHHbuNfilHHH0G2bI30iZzebQ2cQ1PC7uPAw08FGMM\
mQCRerlF/3pc4sAcsnexsxBseA/3lY03KlONqJBAg==",
).unwrap();
{
let doc = PlainTextDocument {
text,
issuers: vec![issuer1],
signatures: vec![sig1],
};
assert_eq!(doc.verify_signatures(), VerificationResult::Valid());
}
{
let doc = PlainTextDocument {
text,
issuers: vec![issuer1],
signatures: vec![sig2],
};
assert_eq!(
doc.verify_signatures(),
VerificationResult::Invalid(vec![0])
);
}
{
let doc = PlainTextDocument {
text,
issuers: vec![issuer1, issuer2],
signatures: vec![sig1],
};
assert_eq!(
doc.verify_signatures(),
VerificationResult::IncompletePairs(2, 1)
);
}
{
let doc = PlainTextDocument {
text,
issuers: vec![issuer1],
signatures: vec![sig1, sig2],
};
assert_eq!(
doc.verify_signatures(),
VerificationResult::IncompletePairs(1, 2)
);
}
}
}
// Copyright (C) 2018 The Duniter Project Developers.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Wrappers around Identity documents.
use duniter_keys::{PublicKey, ed25519};
use regex::Regex;
use Blockstamp;
use blockchain::{BlockchainProtocol, Document, DocumentBuilder, IntoSpecializedDocument};
use blockchain::v10::documents::{StandardTextDocumentParser, TextDocument, TextDocumentBuilder,
V10Document, V10DocumentParsingError};
lazy_static! {
static ref IDENTITY_REGEX: Regex = Regex::new(
"^Issuer: (?P<issuer>[1-9A-Za-z][^OIl]{43,44})\nUniqueID: (?P<uid>[[:alnum:]_-]+)\nTimestamp: (?P<blockstamp>[0-9]+-[0-9A-F]{64})\n$"
).unwrap();
}
/// Wrap an Identity document.
///
/// Must be created by parsing a text document or using a builder.
#[derive(Debug, Clone)]
pub struct IdentityDocument {
/// Document as text.
///
/// Is used to check signatures, and other values
/// must be extracted from it.
text: String,
/// Currency.
currency: String,
/// Unique ID
unique_id: String,
/// Blockstamp
blockstamp: Blockstamp,
/// Document issuer (there should be only one).
issuers: Vec<ed25519::PublicKey>,
/// Document signature (there should be only one).
signatures: Vec<ed25519::Signature>,
}
impl Document for IdentityDocument {
type PublicKey = ed25519::PublicKey;
type CurrencyType = str;
fn version(&self) -> u16 {
10
}
fn currency(&self) -> &str {
&self.currency
}
fn issuers(&self) -> &Vec<ed25519::PublicKey> {
&self.issuers
}
fn signatures(&self) -> &Vec<ed25519::Signature> {
&self.signatures
}
fn as_bytes(&self) -> &[u8] {
self.as_text().as_bytes()
}
}
impl TextDocument for IdentityDocument {
fn as_text(&self) -> &str {
&self.text
}
}
impl IntoSpecializedDocument<BlockchainProtocol> for IdentityDocument {
fn into_specialized(self) -> BlockchainProtocol {
BlockchainProtocol::V10(V10Document::Identity(self))
}
}
/// Identity document builder.
#[derive(Debug, Copy, Clone)]
pub struct IdentityDocumentBuilder<'a> {
/// Document currency.
pub currency: &'a str,
/// Identity unique id.
pub unique_id: &'a str,
/// Reference blockstamp.
pub blockstamp: &'a Blockstamp,
/// Document/identity issuer.
pub issuer: &'a ed25519::PublicKey,
}
impl<'a> IdentityDocumentBuilder<'a> {
fn build_with_text_and_sigs(
self,
text: String,
signatures: Vec<ed25519::Signature>,
) -> IdentityDocument {
IdentityDocument {
text: text,
currency: self.currency.to_string(),
unique_id: self.unique_id.to_string(),
blockstamp: *self.blockstamp,
issuers: vec![*self.issuer],
signatures,
}
}
}
impl<'a> DocumentBuilder for IdentityDocumentBuilder<'a> {
type Document = IdentityDocument;
type PrivateKey = ed25519::PrivateKey;
fn build_with_signature(self, signatures: Vec<ed25519::Signature>) -> IdentityDocument {
self.build_with_text_and_sigs(self.generate_text(), signatures)
}
fn build_and_sign(self, private_keys: Vec<ed25519::PrivateKey>) -> IdentityDocument {
let (text, signatures) = self.build_signed_text(private_keys);
self.build_with_text_and_sigs(text, signatures)
}
}
impl<'a> TextDocumentBuilder for IdentityDocumentBuilder<'a> {
fn generate_text(&self) -> String {
format!(
"Version: 10
Type: Identity
Currency: {currency}
Issuer: {issuer}
UniqueID: {unique_id}
Timestamp: {blockstamp}
",
currency = self.currency,
issuer = self.issuer,
unique_id = self.unique_id,
blockstamp = self.blockstamp
)
}
}
/// Identity document parser
#[derive(Debug, Clone, Copy)]
pub struct IdentityDocumentParser;
impl StandardTextDocumentParser for IdentityDocumentParser {
fn parse_standard(
doc: &str,
body: &str,
currency: &str,
signatures: Vec<ed25519::Signature>,
) -> Result<V10Document, V10DocumentParsingError> {
if let Some(caps) = IDENTITY_REGEX.captures(body) {
let issuer = &caps["issuer"];
let uid = &caps["uid"];
let blockstamp = &caps["blockstamp"];
// Regex match so should not fail.
// TODO : Test it anyway
let issuer = ed25519::PublicKey::from_base58(issuer).unwrap();
let blockstamp = Blockstamp::from_string(blockstamp).unwrap();
Ok(V10Document::Identity(IdentityDocument {
text: doc.to_owned(),
currency: currency.to_owned(),
unique_id: uid.to_owned(),
blockstamp: blockstamp,
issuers: vec![issuer],
signatures,
}))
} else {
Err(V10DocumentParsingError::InvalidInnerFormat(
"Identity".to_string(),
))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use duniter_keys::{PrivateKey, PublicKey, Signature};
use blockchain::VerificationResult;
#[test]
fn generate_real_document() {
let pubkey = ed25519::PublicKey::from_base58(
"DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV",
).unwrap();
let prikey = ed25519::PrivateKey::from_base58(
"468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5G\
iERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7",
).unwrap();
let sig = ed25519::Signature::from_base64(
"1eubHHbuNfilHMM0G2bI30iZzebQ2cQ1PC7uPAw08FGM\
MmQCRerlF/3pc4sAcsnexsxBseA/3lY03KlONqJBAg==",
).unwrap();
let block = Blockstamp::from_string(
"0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
).unwrap();
{
let doc = IdentityDocumentBuilder {
currency: "duniter_unit_test_currency",
unique_id: "tic",
blockstamp: &block,
issuer: &pubkey,
}.build_with_signature(vec![sig]);
assert_eq!(doc.verify_signatures(), VerificationResult::Valid());
}
{
let doc = IdentityDocumentBuilder {
currency: "duniter_unit_test_currency",
unique_id: "tic",
blockstamp: &block,
issuer: &pubkey,
}.build_and_sign(vec![prikey]);
assert_eq!(doc.verify_signatures(), VerificationResult::Valid());
}
}
#[test]
fn identity_standard_regex() {
assert!(IDENTITY_REGEX.is_match(
"Issuer: DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo
UniqueID: toc
Timestamp: 0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
"
));
}
#[test]
fn parse_identity_document() {
let doc = "Version: 10
Type: Identity
Currency: duniter_unit_test_currency
Issuer: DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo
UniqueID: toc
Timestamp: 0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
lcekuS0eP2dpFL99imJcwvDAwx49diiDMkG8Lj7FLkC/6IJ0tgNjUzCIZgMGi7bL5tODRiWi9B49UMXb8b3MAw==";
let body = "Issuer: DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo
UniqueID: toc
Timestamp: 0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
";
let currency = "duniter_unit_test_currency";
let signatures = vec![Signature::from_base64(
"lcekuS0eP2dpFL99imJcwvDAwx49diiDMkG8Lj7FLkC/6IJ0tgNjUzCIZgMGi7bL5tODRiWi9B49UMXb8b3MAw=="
).unwrap(),];
let _ = IdentityDocumentParser::parse_standard(doc, body, currency, signatures).unwrap();
}
}
// Copyright (C) 2018 The Duniter Project Developers.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Provide wrappers around Duniter blockchain documents for protocol version 10.
use duniter_keys::{Signature, ed25519};
use regex::Regex;
use blockchain::{Document, DocumentBuilder, DocumentParser};
use blockchain::v10::documents::identity::IdentityDocumentParser;
pub mod identity;
pub use blockchain::v10::documents::identity::{IdentityDocument, IdentityDocumentBuilder};
// Use of lazy_static so the regex is only compiled at first use.
lazy_static! {
static ref DOCUMENT_REGEX: Regex = Regex::new(
"^(?P<doc>Version: 10\n\
Type: (?P<type>[[:alpha:]]+)\n\
Currency: (?P<currency>[[:alnum:] _-]+)\n\
(?P<body>(?:.*\n)+?))\
(?P<sigs>([[:alnum:]+/=]+\n)+)$"
).unwrap();
static ref SIGNATURES_REGEX: Regex = Regex::new(
"[[:alnum:]+/=]+\n"
).unwrap();
}
/// List of wrapped document types.
///
/// > TODO Add wrapped types in enum variants.
#[derive(Debug, Clone)]
pub enum V10Document {
/// Block document.
Block(),
/// Transaction document.
Transaction(),
/// Identity document.
Identity(IdentityDocument),
/// Membership document.
Membership(),
/// Certification document.
Certification(),
/// Revocation document.
Revocation(),
}
/// Trait for a V10 document.
pub trait TextDocument
: Document<PublicKey = ed25519::PublicKey, CurrencyType = str> {
/// Return document as text.
fn as_text(&self) -> &str;
/// Return document as text with leading signatures.
fn as_text_with_signatures(&self) -> String {
let mut text = self.as_text().to_string();
for sig in self.signatures() {
text = format!("{}{}\n", text, sig.to_base64());
}
text
}
}
/// Trait for a V10 document builder.
pub trait TextDocumentBuilder: DocumentBuilder {
/// Generate document text.
///
/// - Don't contains leading signatures
/// - Contains line breaks on all line.
fn generate_text(&self) -> String;
/// Generate final document with signatures, and also return them in an array.
///
/// Returns :
///
/// - Text without signatures
/// - Signatures
fn build_signed_text(
&self,
private_keys: Vec<ed25519::PrivateKey>,
) -> (String, Vec<ed25519::Signature>) {
use duniter_keys::PrivateKey;
let text = self.generate_text();
let signatures: Vec<_> = {
let text_bytes = text.as_bytes();
private_keys
.iter()
.map(|key| key.sign(text_bytes))
.collect()
};
(text, signatures)
}
}
/// List of possible errors while parsing.
#[derive(Debug, Clone)]
pub enum V10DocumentParsingError {
/// The given source don't have a valid document format.
InvalidWrapperFormat(),
/// The given source don't have a valid specific document format (document type).
InvalidInnerFormat(String),
/// Type fields contains an unknown document type.
UnknownDocumentType(String),
}
trait StandardTextDocumentParser {
fn parse_standard(
doc: &str,
body: &str,
currency: &str,
signatures: Vec<ed25519::Signature>,
) -> Result<V10Document, V10DocumentParsingError>;
}
trait CompactTextDocumentParser<D: TextDocument> {
fn parse_compact(
doc: &str,
body: &str,
currency: &str,
signatures: Vec<ed25519::Signature>,
) -> Result<D, V10DocumentParsingError>;
}
/// A V10 document parser.
#[derive(Debug, Clone, Copy)]
pub struct V10DocumentParser;
impl<'a> DocumentParser<&'a str, V10Document, V10DocumentParsingError> for V10DocumentParser {
fn parse(source: &'a str) -> Result<V10Document, V10DocumentParsingError> {
if let Some(caps) = DOCUMENT_REGEX.captures(source) {
let doctype = &caps["type"];
let doc = &caps["doc"];
let currency = &caps["currency"];
let body = &caps["body"];
let sigs = SIGNATURES_REGEX
.captures_iter(&caps["sigs"])
.map(|capture| ed25519::Signature::from_base64(&capture[0]).unwrap())
.collect::<Vec<_>>();
// TODO : Improve error handling of Signature::from_base64 failure
match doctype {
"Identity" => IdentityDocumentParser::parse_standard(doc, body, currency, sigs),
_ => Err(V10DocumentParsingError::UnknownDocumentType(
doctype.to_string(),
)),
}
} else {
Err(V10DocumentParsingError::InvalidWrapperFormat())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn document_regex() {
assert!(DOCUMENT_REGEX.is_match(
"Version: 10
Type: Transaction
Currency: beta_brousouf
Blockstamp: 204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B
Locktime: 0
Issuers:
HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY
CYYjHsNyg3HMRMpTHqCJAN9McjH5BwFLmDKGV3PmCuKp
9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB
Inputs:
40:2:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:2
70:2:T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:8
20:2:D:HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY:46
70:2:T:A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956:3
20:2:T:67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B:5
15:2:D:9WYHTavL1pmhunFCzUwiiq4pXwvgGG5ysjZnjz9H8yB:46
Unlocks:
0:SIG(0)
1:XHX(7665798292)
2:SIG(0)
3:SIG(0) SIG(2)
4:SIG(0) SIG(1) SIG(2)
5:SIG(2)
Outputs:
120:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)
146:2:SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)
49:2:(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i)\
|| XHX(3EB4702F2AC2FD3FA4FDC46A4FC05AE8CDEE1A85))
Comment: -----@@@----- (why not this comment?)
42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r
2D96KZwNUvVtcapQPq2mm7J9isFcDCfykwJpVEZwBc7tCgL4qPyu17BT5ePozAE9HS6Yvj51f62Mp4n9d9dkzJoX
2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk
"
));
assert!(DOCUMENT_REGEX.is_match(
"Version: 10
Type: Certification
Currency: beta_brousouf
Issuer: DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV
IdtyIssuer: HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd
IdtyUniqueID: lolcat
IdtyTimestamp: 32-DB30D958EE5CB75186972286ED3F4686B8A1C2CD
IdtySignature: J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUb\
GpCMjCqj9yXXqNq7dyZpDWA6BxipsiaMZhujMeBfCznzyci
CertTimestamp: 36-1076F10A7397715D2BEE82579861999EA1F274AC
SoKwoa8PFfCDJWZ6dNCv7XstezHcc2BbKiJgVDXv82R5zYR83nis9dShLgWJ5w48noVUHimdngzYQneNYSMV3rk
"
));
}
#[test]
fn signatures_regex() {
assert_eq!(
SIGNATURES_REGEX
.captures_iter(
"
42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r
2D96KZwNUvVtcapQPq2mm7J9isFcDCfykwJpVEZwBc7tCgL4qPyu17BT5ePozAE9HS6Yvj51f62Mp4n9d9dkzJoX
2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk
"
)
.count(),
3
);
assert_eq!(
SIGNATURES_REGEX
.captures_iter(
"
42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r
2XiBDpuUdu6zCPWGzHXXy8c4ATSscfFQG9DjmqMZUxDZVt1Dp4m2N5oHYVUfoPdrU9SLk4qxi65RNrfCVnvQtQJk
"
)
.count(),
2
);
}
}
// Copyright (C) 2018 The Duniter Project Developers.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Provide wrappers around Duniter V10 documents and events.
pub mod documents;
// Copyright (C) 2018 The Duniter Project Developers.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Implements the Duniter Protocol.
#![deny(missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts,
trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces,
unused_qualifications)]
extern crate base58;
extern crate base64;
extern crate crypto;
extern crate duniter_keys;
#[macro_use]
extern crate lazy_static;
extern crate linked_hash_map;
extern crate regex;
use std::fmt::{Debug, Display, Error, Formatter};
use duniter_keys::BaseConvertionError;
pub mod blockchain;
/// A block Id.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct BlockId(pub u32);
impl Display for BlockId {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}", self.0)
}
}
/// A hash wrapper.
///
/// A hash is often provided as string composed of 64 hexadecimal character (0 to 9 then A to F).
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Hash(pub [u8; 32]);
impl Display for Hash {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}", self.to_hex())
}
}
impl Debug for Hash {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "Hash({})", self)
}
}
impl Hash {
/// Convert a `Hash` to an hex string.
pub fn to_hex(&self) -> String {
let strings: Vec<String> = self.0.iter().map(|b| format!("{:02X}", b)).collect();
strings.join("")
}
/// Convert a hex string in a `Hash`.
///
/// The hex string must only contains hex characters
/// and produce a 32 bytes value.
pub fn from_hex(text: &str) -> Result<Hash, BaseConvertionError> {
if text.len() != 64 {
Err(BaseConvertionError::InvalidKeyLendth(text.len(), 64))
} else {
let mut hash = Hash([0u8; 32]);
let chars: Vec<char> = text.chars().collect();
for i in 0..64 {
if i % 2 != 0 {
continue;
}
let byte1 = chars[i].to_digit(16);
let byte2 = chars[i + 1].to_digit(16);
if byte1.is_none() {
return Err(BaseConvertionError::InvalidCharacter(chars[i], i));
} else if byte2.is_none() {
return Err(BaseConvertionError::InvalidCharacter(chars[i + 1], i + 1));
}
let byte1 = byte1.unwrap() as u8;
let byte2 = byte2.unwrap() as u8;
let byte = (byte1 << 4) | byte2;
hash.0[i / 2] = byte;
}
Ok(hash)
}
}
}
/// Wrapper of a block hash.
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct BlockHash(Hash);
impl Display for BlockHash {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}", self.0.to_hex())
}
}
impl Debug for BlockHash {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "BlockHash({})", self)
}
}
/// Type of errors for [`BlockUId`] parsing.
///
/// [`BlockUId`]: struct.BlockUId.html
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum BlockUIdParseError {
/// Given string have invalid format
InvalidFormat(),
/// [`BlockId`](struct.BlockHash.html) part is not a valid number.
InvalidBlockId(),
/// [`BlockHash`](struct.BlockHash.html) part is not a valid hex number.
InvalidBlockHash(),
}
/// A blockstamp (Unique ID).
///
/// It's composed of the [`BlockId`] and
/// the [`BlockHash`] of the block.
///
/// Thanks to blockchain immutability and frequent block production, it can
/// be used to date information.
///
/// [`BlockId`]: struct.BlockId.html
/// [`BlockHash`]: struct.BlockHash.html
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Blockstamp {
/// Block Id.
pub id: BlockId,
/// Block hash.
pub hash: BlockHash,
}
impl Display for Blockstamp {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "{}-{}", self.id, self.hash)
}
}
impl Debug for Blockstamp {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "BlockUId({})", self)
}
}
impl Blockstamp {
/// Create a `BlockUId` from a text.
pub fn from_string(src: &str) -> Result<Blockstamp, BlockUIdParseError> {
let mut split = src.split('-');
if split.clone().count() != 2 {
Err(BlockUIdParseError::InvalidFormat())
} else {
let id = split.next().unwrap().parse::<u32>();
let hash = Hash::from_hex(split.next().unwrap());
if id.is_err() {
Err(BlockUIdParseError::InvalidBlockId())
} else if hash.is_err() {
Err(BlockUIdParseError::InvalidBlockHash())
} else {
Ok(Blockstamp {
id: BlockId(id.unwrap()),
hash: BlockHash(hash.unwrap()),
})
}
}
}
/// Convert a `BlockUId` to its text format.
pub fn to_string(&self) -> String {
format!("{}", self)
}
}
# Introduction # wotb
`wotb` is a crate making "Web of Trust" computations for `wotb` is a crate making "Web of Trust" computations for
the [Duniter] project. the [Duniter] project.
[Duniter]: https://duniter.org/en/ [Duniter]: https://duniter.org/en/
# How to use it ? ## How to use it
You can add `duniter-wotb` as a `cargo` dependency in your Rust project. You can add `duniter-wotb` as a `cargo` dependency in your Rust project.
\ No newline at end of file
To use it in JavaScript, see `duniter-wotb-js` package. (not done yet)
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment