From ba851b9015aa9fed20143146fce9b404c0cab1dc Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Thu, 11 Jan 2024 15:27:24 -0700 Subject: [PATCH] [WIP] `CanonicalVoteExtension` signature support When present, computes a secondary signature over `Vote::extension`. Closes #831 #835 --- src/commands/ledger.rs | 2 +- src/keyring/signature.rs | 6 ++++++ src/privval.rs | 38 +++++++++++++++++++++++++++++++------- src/session.rs | 22 +++++++++++++++++----- tests/integration.rs | 6 +++--- 5 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/commands/ledger.rs b/src/commands/ledger.rs index 992008b3..ad6cb610 100644 --- a/src/commands/ledger.rs +++ b/src/commands/ledger.rs @@ -64,7 +64,7 @@ impl Runnable for InitCommand { println!("{vote:?}"); let sign_vote_req = SignableMsg::from(Vote::try_from(vote).unwrap()); let to_sign = sign_vote_req - .signable_bytes(config.validator[0].chain_id.clone()) + .canonical_bytes(config.validator[0].chain_id.clone()) .unwrap(); let _sig = chain.keyring.sign(None, &to_sign).unwrap(); diff --git a/src/keyring/signature.rs b/src/keyring/signature.rs index c101aa43..37cd5673 100644 --- a/src/keyring/signature.rs +++ b/src/keyring/signature.rs @@ -33,3 +33,9 @@ impl From for Signature { Self::Ed25519(sig) } } + +impl From for tendermint::Signature { + fn from(sig: Signature) -> tendermint::Signature { + sig.to_vec().try_into().expect("signature should be valid") + } +} diff --git a/src/privval.rs b/src/privval.rs index 1c731452..479573df 100644 --- a/src/privval.rs +++ b/src/privval.rs @@ -49,12 +49,12 @@ impl SignableMsg { /// Get the bytes representing a canonically encoded message over which a /// signature is computed over. - pub fn signable_bytes(&self, chain_id: chain::Id) -> Result { + pub fn canonical_bytes(&self, chain_id: chain::Id) -> Result { let mut bytes = BytesMut::new(); match self { Self::Proposal(proposal) => { - let cp = proto::types::CanonicalProposal { + let canonical = proto::types::CanonicalProposal { chain_id: chain_id.to_string(), r#type: SignedMsgType::Proposal.into(), height: proposal.height.into(), @@ -67,10 +67,10 @@ impl SignableMsg { timestamp: proposal.timestamp.map(Into::into), }; - cp.encode_length_delimited(&mut bytes)?; + canonical.encode_length_delimited(&mut bytes)?; } Self::Vote(vote) => { - let cv = proto::types::CanonicalVote { + let canonical = proto::types::CanonicalVote { r#type: vote.vote_type.into(), height: vote.height.into(), round: vote.round.value().into(), @@ -78,13 +78,37 @@ impl SignableMsg { timestamp: vote.timestamp.map(Into::into), chain_id: chain_id.to_string(), }; - cv.encode_length_delimited(&mut bytes)?; + canonical.encode_length_delimited(&mut bytes)?; } } Ok(bytes.into()) } + /// Get the bytes representing a vote extension if applicable. + pub fn extension_bytes(&self, chain_id: chain::Id) -> Result, EncodeError> { + match self { + Self::Proposal(_) => Ok(None), + Self::Vote(vote) => { + // Only sign extension if actually present + if vote.extension.is_empty() { + return Ok(None); + } + + let canonical = proto::types::CanonicalVoteExtension { + extension: vote.extension.clone(), + height: vote.height.into(), + round: vote.round.value().into(), + chain_id: chain_id.to_string(), + }; + + let mut bytes = BytesMut::new(); + canonical.encode_length_delimited(&mut bytes)?; + Ok(Some(bytes.into())) + } + } + } + /// Parse the consensus state from the request. pub fn consensus_state(&self) -> consensus::State { match self { @@ -275,7 +299,7 @@ mod tests { #[test] fn serialize_canonical_proposal() { let signable_msg = SignableMsg::try_from(example_proposal()).unwrap(); - let signable_bytes = signable_msg.signable_bytes(example_chain_id()).unwrap(); + let signable_bytes = signable_msg.canonical_bytes(example_chain_id()).unwrap(); assert_eq!( signable_bytes.as_ref(), &[ @@ -290,7 +314,7 @@ mod tests { #[test] fn serialize_canonical_vote() { let signable_msg = SignableMsg::try_from(example_vote()).unwrap(); - let signable_bytes = signable_msg.signable_bytes(example_chain_id()).unwrap(); + let signable_bytes = signable_msg.canonical_bytes(example_chain_id()).unwrap(); assert_eq!( signable_bytes.as_ref(), &[ diff --git a/src/session.rs b/src/session.rs index 31b0e8f0..09509a38 100644 --- a/src/session.rs +++ b/src/session.rs @@ -123,7 +123,7 @@ impl Session { } /// Perform a digital signature operation - fn sign(&mut self, signable_msg: SignableMsg) -> Result { + fn sign(&mut self, mut signable_msg: SignableMsg) -> Result { self.check_max_height(&signable_msg)?; let registry = chain::REGISTRY.get(); @@ -139,13 +139,25 @@ impl Session { return Ok(Response::error(signable_msg, remote_err)); } - let to_sign = signable_msg.signable_bytes(self.config.chain_id.clone())?; + // TODO(tarcieri): support for non-default public keys + let public_key = None; + let chain_id = self.config.chain_id.clone(); + + let canonical_msg = signable_msg.canonical_bytes(chain_id.clone())?; let started_at = Instant::now(); + let signature = chain.keyring.sign(public_key, &canonical_msg)?; + self.log_signing_request(&signable_msg, started_at).unwrap(); - // TODO(ismail): figure out which key to use here instead of taking the only key - let signature = chain.keyring.sign(None, &to_sign)?; + // Add extension signature if there are any extensions defined + if let Some(extension_msg) = signable_msg.extension_bytes(chain_id)? { + let extension_sig = chain.keyring.sign(public_key, &extension_msg)?; + + match &mut signable_msg { + SignableMsg::Vote(vote) => vote.extension_signature = Some(extension_sig.into()), + other => fail!(InvalidMessageError, "expected a vote type: {:?}", other), + } + } - self.log_signing_request(&signable_msg, started_at).unwrap(); Response::sign(signable_msg, signature) } diff --git a/tests/integration.rs b/tests/integration.rs index 6baa3f4e..4554fc8d 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -366,7 +366,7 @@ fn handle_and_sign_proposal(key_type: KeyType) { }; let signable_bytes = signable_msg - .signable_bytes(chain_id.parse().unwrap()) + .canonical_bytes(chain_id.parse().unwrap()) .unwrap(); let prop = response @@ -449,7 +449,7 @@ fn handle_and_sign_vote(key_type: KeyType) { }; let signable_bytes = signable_msg - .signable_bytes(chain_id.parse().unwrap()) + .canonical_bytes(chain_id.parse().unwrap()) .unwrap(); let vote_msg: proto::types::Vote = request @@ -536,7 +536,7 @@ fn exceed_max_height(key_type: KeyType) { }; let signable_bytes = signable_msg - .signable_bytes(chain_id.parse().unwrap()) + .canonical_bytes(chain_id.parse().unwrap()) .unwrap(); let vote_msg = response