diff --git a/src/feed.rs b/src/feed.rs index c14b13ae..9973d5e9 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_compat(&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_compat(&self.public_key, &message, proof.signature())?; // Update the length if we grew the feed. let len = verified_by / 2; @@ -605,3 +608,17 @@ 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() +} + +/// 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), + } +} diff --git a/tests/compat.rs b/tests/compat.rs index f9bf47e5..3a1a778d 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; @@ -63,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", @@ -71,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; @@ -86,12 +96,32 @@ 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] +#[ignore] +fn compat_signatures_work() { + // Port from mafintosh/hypercore when the necessary features are implemented + unimplemented!(); +} + #[test] #[ignore] fn deterministic_signatures_after_replication() {