From fe237a4e38b0a33ae8939f1620bbeaf7e4168f56 Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Wed, 13 May 2020 12:22:00 +0200 Subject: [PATCH 1/4] Include length in signatures --- src/feed.rs | 18 +++++++++++++----- tests/compat.rs | 1 + 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/feed.rs b/src/feed.rs index c14b13ae..49ac8622 100644 --- a/src/feed.rs +++ b/src/feed.rs @@ -115,7 +115,8 @@ where let hash = Hash::from_roots(self.merkle.roots()); let index = self.length; - let signature = sign(&self.public_key, key, hash.as_bytes()); + let message = hash_with_length_as_bytes(hash, index + 1); + let signature = sign(&self.public_key, key, &message); self.storage.put_signature(index, signature).await?; for node in self.merkle.nodes() { @@ -399,10 +400,10 @@ where let roots = self.root_hashes(index).await?; let roots: Vec<_> = roots.into_iter().map(Arc::new).collect(); - let message = Hash::from_roots(&roots); - let message = message.as_bytes(); + let hash = Hash::from_roots(&roots); + let message = hash_with_length_as_bytes(hash, index + 1); - verify(&self.public_key, message, Some(signature))?; + verify(&self.public_key, &message, Some(signature))?; Ok(()) } @@ -485,7 +486,9 @@ where } let checksum = Hash::from_roots(&roots); - verify(&self.public_key, checksum.as_bytes(), proof.signature())?; + let length = verified_by / 2; + let message = hash_with_length_as_bytes(checksum, length); + verify(&self.public_key, &message, proof.signature())?; // Update the length if we grew the feed. let len = verified_by / 2; @@ -605,3 +608,8 @@ impl> + Debug + fn tree_index(index: u64) -> u64 { 2 * index } + +/// Extend a hash with a big-endian encoded length. +fn hash_with_length_as_bytes(hash: Hash, length: u64) -> Vec { + [hash.as_bytes(), &length.to_be_bytes()].concat().to_vec() +} diff --git a/tests/compat.rs b/tests/compat.rs index f9bf47e5..18e6e77e 100644 --- a/tests/compat.rs +++ b/tests/compat.rs @@ -56,6 +56,7 @@ fn deterministic_data_and_tree_after_replication() { } #[async_std::test] +#[ignore] async fn deterministic_signatures() { let key = hex_bytes("9718a1ff1c4ca79feac551c0c7212a65e4091278ec886b88be01ee4039682238"); let keypair_bytes = hex_bytes(concat!( From 2bc0460f6d1637b1f203cf884ee01dc3ef9d8b77 Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Wed, 13 May 2020 13:24:57 +0200 Subject: [PATCH 2/4] Accept hypercore v8 signatures (for backwards compatibility) --- src/feed.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/feed.rs b/src/feed.rs index 49ac8622..9973d5e9 100644 --- a/src/feed.rs +++ b/src/feed.rs @@ -403,7 +403,7 @@ where let hash = Hash::from_roots(&roots); let message = hash_with_length_as_bytes(hash, index + 1); - verify(&self.public_key, &message, Some(signature))?; + verify_compat(&self.public_key, &message, Some(signature))?; Ok(()) } @@ -488,7 +488,7 @@ where let checksum = Hash::from_roots(&roots); let length = verified_by / 2; let message = hash_with_length_as_bytes(checksum, length); - verify(&self.public_key, &message, proof.signature())?; + verify_compat(&self.public_key, &message, proof.signature())?; // Update the length if we grew the feed. let len = verified_by / 2; @@ -613,3 +613,12 @@ fn tree_index(index: u64) -> u64 { fn hash_with_length_as_bytes(hash: Hash, length: u64) -> Vec { [hash.as_bytes(), &length.to_be_bytes()].concat().to_vec() } + +/// Verify a signature. If it fails, remove the length suffix added in Hypercore v9 +/// and verify again (backwards compatibility, remove in later version). +pub fn verify_compat(public: &PublicKey, msg: &[u8], sig: Option<&Signature>) -> Result<()> { + match verify(public, msg, sig) { + Ok(_) => Ok(()), + Err(_) => verify(public, &msg[0..32], sig), + } +} From 82caad69fffa6b307c45ade8b6d277e2d796fb7c Mon Sep 17 00:00:00 2001 From: Bruno Tavares Date: Sun, 17 May 2020 12:08:55 -0300 Subject: [PATCH 3/4] Introduce test to verify compatibility and remove ignore - ported from JS PR --- tests/compat.rs | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/tests/compat.rs b/tests/compat.rs index 18e6e77e..df4f94e4 100644 --- a/tests/compat.rs +++ b/tests/compat.rs @@ -9,7 +9,7 @@ use std::io::Read; use std::path::{Path, PathBuf}; use data_encoding::HEXLOWER; -use ed25519_dalek::Keypair; +use ed25519_dalek::{Keypair, Signature}; use hypercore::Feed; use hypercore::{Storage, Store}; use random_access_disk::RandomAccessDisk; @@ -56,7 +56,6 @@ fn deterministic_data_and_tree_after_replication() { } #[async_std::test] -#[ignore] async fn deterministic_signatures() { let key = hex_bytes("9718a1ff1c4ca79feac551c0c7212a65e4091278ec886b88be01ee4039682238"); let keypair_bytes = hex_bytes(concat!( @@ -64,7 +63,7 @@ async fn deterministic_signatures() { "9718a1ff1c4ca79feac551c0c7212a65e4091278ec886b88be01ee4039682238" )); - let expected_signatures = hex_bytes(concat!( + let compat_v9_expected_signatures = hex_bytes(concat!( "050257010000400745643235353139000000000000000000000000000000000084684e8dd76c339", "d6f5754e813204906ee818e6c6cdc6a816a2ac785a3e0d926ac08641a904013194fe6121847b7da", "d4e361965d47715428eb0a0ededbdd5909d037ff3c3614fa0100ed9264a712d3b77cbe7a4f6eadd", @@ -72,6 +71,16 @@ async fn deterministic_signatures() { "dc0711057675ed57d445ce7ed4613881be37ebc56bb40556b822e431bb4dc3517421f9a5e3ed124", "eb5c4db8367386d9ce12b2408613b9fec2837022772a635ffd807", )); + let compat_signatures_len = compat_v9_expected_signatures.len(); + let compat_signature_struct = compat_v9_expected_signatures + .into_iter() + .skip(compat_signatures_len - 64) + .collect::>(); + + let expected_signatures = hex_bytes(concat!( + "42e057f2c225b4c5b97876a15959324931ad84646a8bf2e4d14487c0f117966a585edcdda54670d", + "d5def829ca85924ce44ae307835e57d5729aef8cd91678b06", + )); for _ in 0..5 { let (dir, storage) = mk_storage().await; @@ -87,12 +96,28 @@ async fn deterministic_signatures() { } assert_eq!(read_bytes(&dir, Store::Data), data); - assert_eq!(read_bytes(&dir, Store::Signatures), expected_signatures); + let actual_signatures = read_bytes(&dir, Store::Signatures); + let actual_signatures_len = actual_signatures.len(); + assert_eq!( + actual_signatures + .into_iter() + .skip(actual_signatures_len - 64) + .collect::>(), + expected_signatures + ); + + let compat_signature = Signature::from_bytes(&compat_signature_struct).unwrap(); + feed.verify(feed.len() - 1, &compat_signature) + .await + .expect("Could not verify compat signature of hypercore v9"); remove_dir_all(dir).unwrap() } } +#[test] +fn verify_older_signature_on_read() {} + #[test] #[ignore] fn deterministic_signatures_after_replication() { From 1e01e1d714c10e2f03dd320b39dfa9a7925bfdf2 Mon Sep 17 00:00:00 2001 From: Bruno Tavares Date: Sun, 17 May 2020 12:16:52 -0300 Subject: [PATCH 4/4] mark test as future work when replicate is ready --- tests/compat.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/compat.rs b/tests/compat.rs index df4f94e4..3a1a778d 100644 --- a/tests/compat.rs +++ b/tests/compat.rs @@ -116,7 +116,11 @@ async fn deterministic_signatures() { } #[test] -fn verify_older_signature_on_read() {} +#[ignore] +fn compat_signatures_work() { + // Port from mafintosh/hypercore when the necessary features are implemented + unimplemented!(); +} #[test] #[ignore]