diff --git a/src/delivery_group.rs b/src/delivery_group.rs index 7a1e764fcb..b98bc42f8d 100644 --- a/src/delivery_group.rs +++ b/src/delivery_group.rs @@ -406,7 +406,7 @@ mod tests { let elders0: Vec<_> = elders_info0.peers().copied().collect(); let elders_info0 = proven(&sk, elders_info0)?; - let mut section = Section::new(chain, elders_info0)?; + let mut section = Section::new(pk, chain, elders_info0)?; for peer in elders0 { let member_info = MemberInfo::joined(peer); @@ -434,7 +434,7 @@ mod tests { let (elders_info, _) = gen_elders_info(prefix0, ELDER_SIZE); let elders_info = proven(&sk, elders_info)?; - let section = Section::new(chain, elders_info)?; + let section = Section::new(pk, chain, elders_info)?; let network = Network::new(); let our_name = section.prefix().substituted_in(rand::random()); diff --git a/src/messages/mod.rs b/src/messages/mod.rs index 79f6ae3bd9..35251b5ac4 100644 --- a/src/messages/mod.rs +++ b/src/messages/mod.rs @@ -379,11 +379,21 @@ impl Message { new_first_key: &bls::PublicKey, full_chain: &SectionChain, ) -> Result { - if let Some(proof_chain) = &mut self.proof_chain { - *proof_chain = proof_chain.extend(new_first_key, full_chain)? - } else { - return Err(ExtendProofChainError::NoProofChain); - } + let proof_chain = self + .proof_chain + .as_mut() + .ok_or(ExtendProofChainError::NoProofChain)?; + + *proof_chain = match proof_chain.extend(new_first_key, full_chain) { + Ok(chain) => chain, + Err(SectionChainError::InvalidOperation) => { + // This means the tip of the proof chain is not reachable from `new_first_key`. + // Extend it from the root key of the full chain instead as that should be the + // genesis key which is implicitly trusted. + proof_chain.extend(full_chain.root_key(), full_chain)? + } + Err(error) => return Err(error.into()), + }; Ok(Self::new_signed( self.src, @@ -516,6 +526,7 @@ mod tests { let member_info = consensus::test_utils::proven(&sk1, member_info)?; let variant = Variant::NodeApproval { + genesis_key: pk0, elders_info, member_info, }; diff --git a/src/messages/variant.rs b/src/messages/variant.rs index c92295a4a7..e2a2cf29ae 100644 --- a/src/messages/variant.rs +++ b/src/messages/variant.rs @@ -43,6 +43,7 @@ pub(crate) enum Variant { /// Message sent to newly joined node containing the necessary info to become a member of our /// section. NodeApproval { + genesis_key: bls::PublicKey, elders_info: Proven, member_info: Proven, }, @@ -127,6 +128,7 @@ impl Variant { Self::NodeApproval { elders_info, member_info, + .. } => { let proof_chain = proof_chain.ok_or(Error::InvalidMessage)?; @@ -171,10 +173,12 @@ impl Debug for Variant { .finish(), Self::UserMessage(payload) => write!(f, "UserMessage({:10})", HexFmt(payload)), Self::NodeApproval { + genesis_key, elders_info, member_info, } => f .debug_struct("NodeApproval") + .field("genesis_key", genesis_key) .field("elders_info", elders_info) .field("member_info", member_info) .finish(), diff --git a/src/relocation.rs b/src/relocation.rs index fee6a78035..d21c1f4e56 100644 --- a/src/relocation.rs +++ b/src/relocation.rs @@ -345,7 +345,7 @@ mod tests { ); let elders_info = proven(&sk, elders_info)?; - let mut section = Section::new(SectionChain::new(pk), elders_info)?; + let mut section = Section::new(pk, SectionChain::new(pk), elders_info)?; for peer in &peers { let info = MemberInfo::joined(*peer); diff --git a/src/routing/approved.rs b/src/routing/approved.rs index 8d6acf850b..ec33dbe40f 100644 --- a/src/routing/approved.rs +++ b/src/routing/approved.rs @@ -824,7 +824,8 @@ impl Approved { .section .chain() .keys() - .chain(self.network.keys().map(|(_, key)| key)); + .chain(self.network.keys().map(|(_, key)| key)) + .chain(iter::once(self.section.genesis_key())); match msg.verify(known_keys) { Ok(VerifyStatus::Full) => Ok(true), @@ -1836,6 +1837,7 @@ impl Approved { )?; let variant = Variant::NodeApproval { + genesis_key: *self.section.genesis_key(), elders_info: self.section.proven_elders_info().clone(), member_info, }; diff --git a/src/routing/bootstrap.rs b/src/routing/bootstrap.rs index 5d5f90d6f8..d3aedb0d97 100644 --- a/src/routing/bootstrap.rs +++ b/src/routing/bootstrap.rs @@ -55,7 +55,7 @@ pub(crate) async fn initial( let state = State::new(node, send_tx, recv_rx); future::join( - state.run(vec![bootstrap_addr], None), + state.run(vec![bootstrap_addr], None, None), send_messages(send_rx, comm), ) .instrument(span) @@ -73,6 +73,7 @@ pub(crate) async fn relocate( comm: &Comm, recv_rx: mpsc::Receiver<(MessageType, SocketAddr)>, bootstrap_addrs: Vec, + genesis_key: bls::PublicKey, relocate_details: SignedRelocateDetails, ) -> Result<(Node, Section, Vec<(Message, SocketAddr)>)> { let (send_tx, send_rx) = mpsc::channel(1); @@ -81,7 +82,7 @@ pub(crate) async fn relocate( let state = State::new(node, send_tx, recv_rx); future::join( - state.run(bootstrap_addrs, Some(relocate_details)), + state.run(bootstrap_addrs, Some(genesis_key), Some(relocate_details)), send_messages(send_rx, comm), ) .await @@ -115,6 +116,7 @@ impl<'a> State<'a> { async fn run( mut self, bootstrap_addrs: Vec, + genesis_key: Option, relocate_details: Option, ) -> Result<(Node, Section, Vec<(Message, SocketAddr)>)> { let (prefix, section_key, elders) = self @@ -127,7 +129,8 @@ impl<'a> State<'a> { None }; - self.join(section_key, elders, relocate_payload).await + self.join(section_key, elders, genesis_key, relocate_payload) + .await } // Send a `GetSectionQuery` and waits for the response. If the response is `Redirect`, @@ -282,6 +285,7 @@ impl<'a> State<'a> { mut self, mut section_key: bls::PublicKey, elders: BTreeMap, + genesis_key: Option, relocate_payload: Option, ) -> Result<(Node, Section, Vec<(Message, SocketAddr)>)> { let join_request = JoinRequest { @@ -294,18 +298,19 @@ impl<'a> State<'a> { loop { let (response, sender) = self - .receive_join_response(relocate_payload.as_ref()) + .receive_join_response(genesis_key.as_ref(), relocate_payload.as_ref()) .await?; match response { JoinResponse::Approval { elders_info, age, + genesis_key, section_chain, } => { return Ok(( self.node.with_age(age), - Section::new(section_chain, elders_info)?, + Section::new(genesis_key, section_chain, elders_info)?, self.backlog.into_iter().collect(), )); } @@ -386,6 +391,7 @@ impl<'a> State<'a> { async fn receive_join_response( &mut self, + expected_genesis_key: Option<&bls::PublicKey>, relocate_payload: Option<&RelocatePayload>, ) -> Result<(JoinResponse, SocketAddr)> { while let Some((message, sender)) = self.recv_rx.next().await { @@ -441,6 +447,7 @@ impl<'a> State<'a> { )); } Variant::NodeApproval { + genesis_key, elders_info, member_info, } => { @@ -449,6 +456,13 @@ impl<'a> State<'a> { continue; } + if let Some(expected_genesis_key) = expected_genesis_key { + if expected_genesis_key != genesis_key { + error!("Unexpected Genesis key"); + continue; + } + } + let trusted_key = if let Some(payload) = relocate_payload { Some(&payload.relocate_details().destination_key) } else { @@ -459,7 +473,6 @@ impl<'a> State<'a> { continue; } - // Transition from Joining to Approved let section_chain = message.proof_chain()?.clone(); info!( @@ -471,6 +484,7 @@ impl<'a> State<'a> { JoinResponse::Approval { elders_info: elders_info.clone(), age: member_info.value.peer.age(), + genesis_key: *genesis_key, section_chain, }, sender, @@ -515,6 +529,7 @@ enum JoinResponse { Approval { elders_info: Proven, age: u8, + genesis_key: bls::PublicKey, section_chain: SectionChain, }, Retry { @@ -613,7 +628,7 @@ mod tests { // Create the bootstrap task, but don't run it yet. let bootstrap = async move { state - .run(vec![bootstrap_addr], None) + .run(vec![bootstrap_addr], None, None) .await .map_err(Error::from) }; @@ -667,6 +682,7 @@ mod tests { &bootstrap_node, DstLocation::Direct, Variant::NodeApproval { + genesis_key: pk, elders_info, member_info, }, @@ -906,7 +922,7 @@ mod tests { let elders = (0..ELDER_SIZE) .map(|_| (good_prefix.substituted_in(rand::random()), gen_addr())) .collect(); - let join_task = state.join(section_key, elders, None); + let join_task = state.join(section_key, elders, None, None); let test_task = async { let (message, _) = send_rx diff --git a/src/routing/lazy_messaging.rs b/src/routing/lazy_messaging.rs index 3ca326c741..68ed639d73 100644 --- a/src/routing/lazy_messaging.rs +++ b/src/routing/lazy_messaging.rs @@ -319,7 +319,8 @@ mod tests { let node = nodes.remove(0); let elders_info0 = proven(&our_sk, elders_info0)?; - let section = Section::new(chain, elders_info0).context("failed to create section")?; + let section = Section::new(*chain.root_key(), chain, elders_info0) + .context("failed to create section")?; let (elders_info1, _) = gen_elders_info(prefix1, ELDER_SIZE); let elders_info1 = proven(&our_sk, elders_info1)?; diff --git a/src/routing/split_barrier.rs b/src/routing/split_barrier.rs index fe5d1e0a9c..43486f8351 100644 --- a/src/routing/split_barrier.rs +++ b/src/routing/split_barrier.rs @@ -234,7 +234,7 @@ mod tests { let elders_info = EldersInfo::new(elders, Prefix::default()); let elders_info = proven(&sk, elders_info)?; - let mut section = Section::new(chain, elders_info)?; + let mut section = Section::new(pk, chain, elders_info)?; for peer in members0.iter().chain(&members1).copied() { let info = MemberInfo::joined(peer); diff --git a/src/routing/stage.rs b/src/routing/stage.rs index 1c4a75fcda..01b0b4000d 100644 --- a/src/routing/stage.rs +++ b/src/routing/stage.rs @@ -245,11 +245,21 @@ impl Stage { details: SignedRelocateDetails, message_rx: mpsc::Receiver<(MessageType, SocketAddr)>, ) -> Result> { - let node = self.state.lock().await.node().clone(); + let (genesis_key, node) = { + let state = self.state.lock().await; + (*state.section().genesis_key(), state.node().clone()) + }; let previous_name = node.name(); - let (node, section, backlog) = - bootstrap::relocate(node, &self.comm, message_rx, bootstrap_addrs, details).await?; + let (node, section, backlog) = bootstrap::relocate( + node, + &self.comm, + message_rx, + bootstrap_addrs, + genesis_key, + details, + ) + .await?; let mut state = self.state.lock().await; let event_tx = state.event_tx.clone(); diff --git a/src/routing/tests/mod.rs b/src/routing/tests/mod.rs index 2aea987094..b16ad2414e 100644 --- a/src/routing/tests/mod.rs +++ b/src/routing/tests/mod.rs @@ -462,7 +462,7 @@ async fn handle_consensus_on_online_of_elder_candidate() -> Result<()> { let elders_info = EldersInfo::new(nodes.iter().map(Node::peer), Prefix::default()); let proven_elders_info = proven(sk_set.secret_key(), elders_info.clone())?; - let mut section = Section::new(chain, proven_elders_info)?; + let mut section = Section::new(*chain.root_key(), chain, proven_elders_info)?; for peer in elders_info.elders.values() { let member_info = MemberInfo::joined(*peer); @@ -852,7 +852,7 @@ async fn handle_unknown_message(source: UnknownMessageSource) -> Result<()> { let chain = SectionChain::new(sk.public_key()); let proven_elders_info = proven(&sk, elders_info)?; - let section = Section::new(chain, proven_elders_info)?; + let section = Section::new(*chain.root_key(), chain, proven_elders_info)?; let node = create_node(); let state = Approved::new(node, section, None, mpsc::unbounded_channel().0); @@ -960,7 +960,7 @@ async fn handle_untrusted_message(source: UntrustedMessageSource) -> Result<()> }; let proven_elders_info = proven(&sk0, elders_info)?; - let section = Section::new(chain.clone(), proven_elders_info)?; + let section = Section::new(pk0, chain.clone(), proven_elders_info)?; let node = create_node(); let node_name = node.name(); @@ -1031,7 +1031,7 @@ async fn handle_bounced_unknown_message() -> Result<()> { let _ = section_chain.insert(&pk0, pk1, pk1_signature); let proven_elders_info = proven(sk1_set.secret_key(), elders_info)?; - let section = Section::new(section_chain, proven_elders_info)?; + let section = Section::new(pk0, section_chain, proven_elders_info)?; let section_key_share = create_section_key_share(&sk1_set, 0); let node = nodes.remove(0); @@ -1124,7 +1124,7 @@ async fn handle_bounced_untrusted_message() -> Result<()> { let _ = chain.insert(&pk0, pk1, pk1_signature); let proven_elders_info = proven(sk1_set.secret_key(), elders_info)?; - let section = Section::new(chain.clone(), proven_elders_info)?; + let section = Section::new(pk0, chain.clone(), proven_elders_info)?; let section_key_share = create_section_key_share(&sk1_set, 0); let node = nodes.remove(0); @@ -1214,7 +1214,7 @@ async fn handle_sync() -> Result<()> { let (old_elders_info, mut nodes) = create_elders_info(); let proven_old_elders_info = proven(sk1_set.secret_key(), old_elders_info.clone())?; - let old_section = Section::new(chain.clone(), proven_old_elders_info)?; + let old_section = Section::new(pk0, chain.clone(), proven_old_elders_info)?; // Create our node let (event_tx, mut event_rx) = mpsc::unbounded_channel(); @@ -1244,7 +1244,7 @@ async fn handle_sync() -> Result<()> { ); let new_elders: BTreeSet<_> = new_elders_info.elders.keys().copied().collect(); let proven_new_elders_info = proven(&sk2, new_elders_info)?; - let new_section = Section::new(chain, proven_new_elders_info)?; + let new_section = Section::new(pk0, chain, proven_new_elders_info)?; // Create the `Sync` message containing the new `Section`. let message = Message::single_src( @@ -1297,11 +1297,11 @@ async fn handle_untrusted_sync() -> Result<()> { let (old_elders_info, _) = create_elders_info(); let proven_old_elders_info = proven(&sk0, old_elders_info.clone())?; - let old_section = Section::new(SectionChain::new(pk0), proven_old_elders_info)?; + let old_section = Section::new(pk0, SectionChain::new(pk0), proven_old_elders_info)?; let (new_elders_info, _) = create_elders_info(); let proven_new_elders_info = proven(&sk2, new_elders_info.clone())?; - let new_section = Section::new(chain.truncate(2), proven_new_elders_info)?; + let new_section = Section::new(pk0, chain.truncate(2), proven_new_elders_info)?; let (event_tx, mut event_rx) = mpsc::unbounded_channel(); let node = create_node(); @@ -1377,7 +1377,7 @@ async fn handle_bounced_untrusted_sync() -> Result<()> { let (elders_info, mut nodes) = create_elders_info(); let proven_elders_info = proven(sk2, elders_info.clone())?; - let section_full = Section::new(chain, proven_elders_info)?; + let section_full = Section::new(pk0, chain, proven_elders_info)?; let section_trimmed = section_full.trimmed(2); let (event_tx, _) = mpsc::unbounded_channel(); @@ -1887,7 +1887,7 @@ fn create_section( let section_chain = SectionChain::new(sk_set.secret_key().public_key()); let proven_elders_info = proven(sk_set.secret_key(), elders_info.clone())?; - let mut section = Section::new(section_chain, proven_elders_info)?; + let mut section = Section::new(*section_chain.root_key(), section_chain, proven_elders_info)?; for peer in elders_info.elders.values().copied() { let member_info = MemberInfo::joined(peer); diff --git a/src/section/mod.rs b/src/section/mod.rs index efc278291b..aad9e51513 100644 --- a/src/section/mod.rs +++ b/src/section/mod.rs @@ -35,9 +35,10 @@ use xor_name::{Prefix, XorName}; #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub(crate) struct Section { - members: SectionPeers, - elders_info: Proven, + genesis_key: bls::PublicKey, chain: SectionChain, + elders_info: Proven, + members: SectionPeers, } impl Section { @@ -45,7 +46,11 @@ impl Section { /// (`elders_info`). /// /// Returns error if `elders_info` is not signed with the last key of `chain`. - pub fn new(chain: SectionChain, elders_info: Proven) -> Result { + pub fn new( + genesis_key: bls::PublicKey, + chain: SectionChain, + elders_info: Proven, + ) -> Result { if elders_info.proof.public_key != *chain.last_key() { error!("can't create section: elders_info signed with incorrect key"); // TODO: consider more specific error here. @@ -53,8 +58,9 @@ impl Section { } Ok(Self { - elders_info, + genesis_key, chain, + elders_info, members: SectionPeers::default(), }) } @@ -67,7 +73,11 @@ impl Section { let elders_info = create_first_elders_info(&public_key_set, &secret_key_share, peer)?; - let mut section = Self::new(SectionChain::new(elders_info.proof.public_key), elders_info)?; + let mut section = Self::new( + elders_info.proof.public_key, + SectionChain::new(elders_info.proof.public_key), + elders_info, + )?; for peer in section.elders_info.value.peers() { let member_info = MemberInfo::joined(*peer); @@ -87,6 +97,10 @@ impl Section { Ok((section, section_key_share)) } + pub fn genesis_key(&self) -> &bls::PublicKey { + &self.genesis_key + } + /// Try to merge this `Section` with `other`. Returns `InvalidMessage` if `other` is invalid or /// its chain is not compatible with the chain of `self`. pub fn merge(&mut self, other: Self) -> Result<()> { @@ -168,6 +182,7 @@ impl Section { // always contains the latest key). If `chain_len` is zero, it is silently replaced with one. pub fn trimmed(&self, chain_len: usize) -> Self { Self { + genesis_key: self.genesis_key, elders_info: self.elders_info.clone(), chain: self.chain.truncate(chain_len), members: SectionPeers::default(), @@ -184,9 +199,18 @@ impl Section { trusted_key: &bls::PublicKey, full_chain: &SectionChain, ) -> Result { - let chain = self.chain.extend(trusted_key, full_chain)?; + let chain = match self.chain.extend(trusted_key, full_chain) { + Ok(chain) => chain, + Err(SectionChainError::InvalidOperation) => { + // This means the tip of the chain is not reachable from `trusted_key`. + // Use the full chain instead as it is always trusted. + self.chain.clone() + } + Err(error) => return Err(error), + }; Ok(Self { + genesis_key: self.genesis_key, elders_info: self.elders_info.clone(), chain, members: self.members.clone(), diff --git a/src/section/section_chain.rs b/src/section/section_chain.rs index cca3cf23e5..23e264c9c2 100644 --- a/src/section/section_chain.rs +++ b/src/section/section_chain.rs @@ -117,21 +117,21 @@ impl SectionChain { { // Note: the returned chain is not always strictly minimal. Consider this chain: // - // 0->1->2->3 + // 0->1->3->4 // | - // +->4 + // +->2 // - // Then calling `minimize([1, 2])` currently returns + // Then calling `minimize([1, 3])` currently returns // - // 1->2 + // 1->3 // | - // +->4 + // +->2 // - // Even though the truly minimal chain containing 1 and 2 is just + // Even though the truly minimal chain containing 1 and 3 is just // - // 1->2 + // 1->3 // - // This is because 4 lies between 1 and 2 in the underlying `tree` vector and so is + // This is because 2 lies between 1 and 3 in the underlying `tree` vector and so is // currently included. // // TODO: make this function return the truly minimal chain in all cases. @@ -175,51 +175,30 @@ impl SectionChain { pub fn truncate(&self, count: usize) -> Self { let count = count.max(1); - // Indices of the blocks to include in the result, in reverse order. - let mut indices = Vec::with_capacity(count - 1); + let mut tree: Vec<_> = self.branch(self.tree.len()).take(count).cloned().collect(); - // Index of the currently processed block. - let mut index = self.len() - 1; - - // Walk the chain starting from the last key and following the parent links. - // Stop when count - 1 blocks are visited or we hit the root, whichever comes first. - for _ in 1..count { - if index == 0 { - break; - } - - indices.push(index); - index = self.tree[index - 1].parent_index; - } - - // Visit one more block - this will be the root block of the resulting chain. - let mut chain = Self::new(if index == 0 { - self.root + let root = if tree.len() >= count { + tree.pop().map(|block| block.key).unwrap_or(self.root) } else { - self.tree[index - 1].key - }); + self.root + }; - // Iterate the visited blocks in reverse order (as the indices are reversed, this results - // in the original order) and push them into the resulting chain, adjusting the parent - // indices. - while let Some(index) = indices.pop() { - let block = &self.tree[index - 1]; + tree.reverse(); - chain.tree.push(Block { - key: block.key, - signature: block.signature.clone(), - parent_index: chain.tree.len(), - }); + // Fix the parent indices. + for (index, block) in tree.iter_mut().enumerate() { + block.parent_index = index; } - chain + Self { root, tree } } /// Returns the smallest super-chain of `self` that would be trusted by a peer that trust - /// `trusted_key`. + /// `trusted_key`. Ensures that the last key of the resuling chain is the same as the last key + /// of `self`. /// - /// Returns `Error::KeyNotFound` if either `trusted_key` or `self.last_key()` is not present in - /// `super_chain`. + /// Returns `Error::KeyNotFound` if any of `trusted_key`, `self.root_key()` or `self.last_key()` + /// is not present in `super_chain`. /// /// Returns `Error::InvalidOperation` if `trusted_key` is not reachable from `self.last_key()`. pub fn extend(&self, trusted_key: &bls::PublicKey, super_chain: &Self) -> Result { @@ -230,6 +209,10 @@ impl SectionChain { .index_of(self.last_key()) .ok_or(Error::KeyNotFound)?; + if !super_chain.has_key(self.root_key()) { + return Err(Error::KeyNotFound); + } + if super_chain.is_ancestor(trusted_key_index, last_key_index) { super_chain.minimize(vec![trusted_key, self.last_key()]) } else { @@ -303,7 +286,10 @@ impl SectionChain { I: IntoIterator, { let trusted_keys: HashSet<_> = trusted_keys.into_iter().collect(); - self.main_branch().any(|key| trusted_keys.contains(key)) + self.branch(self.tree.len()) + .map(|block| &block.key) + .chain(iter::once(&self.root)) + .any(|key| trusted_keys.contains(key)) } /// Compare the two keys by their position in the chain. The key that is higher (closer to the @@ -329,7 +315,7 @@ impl SectionChain { /// /// NOTE: this is a `O(n)` operation. pub fn main_branch_len(&self) -> usize { - self.main_branch().count() + self.branch(self.tree.len()).count() + 1 } fn insert_block(&mut self, new_block: Block) -> usize { @@ -419,12 +405,10 @@ impl SectionChain { } } - // Iterator over the key on the main branch of the chain in reverse order. - fn main_branch(&self) -> Branch { - Branch { - chain: self, - index: Some(self.tree.len()), - } + // Iterator over the blocks on the branch that ends at `index` in reverse order. + // Does not include the root block. + fn branch(&self, index: usize) -> Branch { + Branch { chain: self, index } } } @@ -476,25 +460,23 @@ impl PartialOrd for Block { } } -// Iterator over the keys on a single branch of the chain in reverse order. +// Iterator over the blocks on a single branch of the chain in reverse order. +// Does not include the root block. struct Branch<'a> { chain: &'a SectionChain, - index: Option, + index: usize, } impl<'a> Iterator for Branch<'a> { - type Item = &'a bls::PublicKey; + type Item = &'a Block; fn next(&mut self) -> Option { - let index = self.index?; - - if index == 0 { - self.index = None; - Some(&self.chain.root) + if self.index == 0 { + None } else { - let block = self.chain.tree.get(index - 1)?; - self.index = Some(block.parent_index); - Some(&block.key) + let block = self.chain.tree.get(self.index - 1)?; + self.index = block.parent_index; + Some(block) } } } @@ -1047,6 +1029,15 @@ mod tests { chain.extend(&pk2, &main_chain), Err(Error::InvalidOperation) ); + + // in: X->Y->2 (forged chain) + // trusted: 1 + // out: Error + let (skx, pkx) = gen_keypair(); + let (sky, pky, sigy) = gen_signed_keypair(&skx); + let fake_sig2 = sign(&sky, &pk2); + let chain = make_chain(pkx, vec![(&pkx, pky, sigy), (&pky, pk2, fake_sig2)]); + assert_eq!(chain.extend(&pk1, &main_chain), Err(Error::KeyNotFound)); } #[test]