1use std::{
21 string::{String, ToString},
22 vec::Vec,
23};
24
25use bytes::BufMut;
26
27use crate::{
28 Error,
29 merkle::merkle_hash,
30 proto::{
31 prost::Message,
32 types::{
33 BlockId, CanonicalBlockId, CanonicalVote, CanonicalVoteExtension, Commit,
34 SignedMsgType, StateId, ValidatorSet, Vote, VoteExtension, VoteExtensionType,
35 },
36 },
37};
38
39const VOTE_REQUEST_ID_PREFIX: &str = "dpbvote";
40const VOTE_EXTENSION_REQUEST_ID_PREFIX: &str = "dpevote";
41
42pub trait Signable: Hashable {
44 #[deprecated = "replaced by calculate_sign_hash() to unify naming between core, platform and tenderdash"]
45 fn sign_digest(
46 &self,
47 chain_id: &str,
48 quorum_type: u8,
49 quorum_hash: &[u8; 32],
50 height: i64,
51 round: i32,
52 ) -> Result<Vec<u8>, Error> {
53 self.calculate_sign_hash(chain_id, quorum_type, quorum_hash, height, round)
54 }
55
56 fn calculate_sign_hash(
59 &self,
60 chain_id: &str,
61 quorum_type: u8,
62 quorum_hash: &[u8; 32],
63 height: i64,
64 round: i32,
65 ) -> Result<Vec<u8>, Error>;
66}
67
68impl Signable for Commit {
69 fn calculate_sign_hash(
70 &self,
71 chain_id: &str,
72 quorum_type: u8,
73 quorum_hash: &[u8; 32],
74
75 height: i64,
76 round: i32,
77 ) -> Result<Vec<u8>, Error> {
78 if self.quorum_hash.ne(quorum_hash) {
79 return Err(Error::Canonical("quorum hash mismatch".to_string()));
80 }
81
82 let request_id = sign_request_id(VOTE_REQUEST_ID_PREFIX, height, round);
83 let sign_bytes_hash = self.calculate_msg_hash(chain_id, height, round)?;
84
85 let digest = sign_hash(
86 quorum_type,
87 quorum_hash,
88 request_id[..]
89 .try_into()
90 .expect("invalid request ID length"),
91 &sign_bytes_hash,
92 );
93
94 tracing::trace!(
96 digest=hex::encode(&digest),
97 ?quorum_type,
98 quorum_hash=hex::encode(quorum_hash),
99 request_id=hex::encode(request_id),
100 commit=?self, "commit digest");
101
102 Ok(digest)
103 }
104}
105
106impl Signable for CanonicalVote {
107 fn calculate_sign_hash(
108 &self,
109 chain_id: &str,
110 quorum_type: u8,
111 quorum_hash: &[u8; 32],
112
113 height: i64,
114 round: i32,
115 ) -> Result<Vec<u8>, Error> {
116 let request_id = sign_request_id(VOTE_REQUEST_ID_PREFIX, height, round);
117 let sign_bytes_hash = self.calculate_msg_hash(chain_id, height, round)?;
118
119 let digest = sign_hash(
120 quorum_type,
121 quorum_hash,
122 request_id[..]
123 .try_into()
124 .expect("invalid request ID length"),
125 &sign_bytes_hash,
126 );
127
128 tracing::trace!(
130 digest=hex::encode(&digest),
131 ?quorum_type,
132 quorum_hash=hex::encode(quorum_hash),
133 request_id=hex::encode(request_id),
134 vote=?self, "canonical vote digest");
135
136 Ok(digest)
137 }
138}
139
140impl Signable for VoteExtension {
141 fn calculate_sign_hash(
142 &self,
143 chain_id: &str,
144 quorum_type: u8,
145 quorum_hash: &[u8; 32],
146 height: i64,
147 round: i32,
148 ) -> Result<Vec<u8>, Error> {
149 let (request_id, sign_bytes_hash) = match self.r#type() {
150 VoteExtensionType::ThresholdRecover => {
151 let request_id = sign_request_id(VOTE_EXTENSION_REQUEST_ID_PREFIX, height, round);
152 let sign_bytes_hash = self.calculate_msg_hash(chain_id, height, round)?;
153
154 (request_id, sign_bytes_hash)
155 },
156
157 VoteExtensionType::ThresholdRecoverRaw => {
158 let mut sign_bytes_hash = self.extension.clone();
159 sign_bytes_hash.reverse();
160
161 let request_id = self.sign_request_id.clone().unwrap_or_default();
162 let request_id = if request_id.is_empty() {
163 sign_request_id(VOTE_EXTENSION_REQUEST_ID_PREFIX, height, round)
164 } else {
165 let mut request_id = lhash::sha256(&lhash::sha256(&request_id));
167 request_id.reverse();
168 request_id.to_vec()
169 };
170
171 (request_id, sign_bytes_hash)
172 },
173
174 VoteExtensionType::Default => unimplemented!(
175 "vote extension of type {:?} cannot be signed",
176 self.r#type()
177 ),
178 };
179 let sign_hash = sign_hash(
180 quorum_type,
181 quorum_hash,
182 request_id[..]
183 .try_into()
184 .expect("invalid request ID length"),
185 &sign_bytes_hash,
186 );
187
188 tracing::trace!(
190 digest=hex::encode(&sign_hash),
191 ?quorum_type,
192 quorum_hash=hex::encode(quorum_hash),
193 request_id=hex::encode(request_id),
194 vote_extension=?self, "vote extension sign hash");
195
196 Ok(sign_hash)
197 }
198}
199
200fn sign_request_id(prefix: &str, height: i64, round: i32) -> Vec<u8> {
201 let mut buf: Vec<u8> = Vec::from(prefix.as_bytes());
202 buf.put_i64_le(height);
203 buf.put_i32_le(round);
204
205 lhash::sha256(&buf).to_vec()
206}
207
208fn sign_hash(
209 quorum_type: u8,
210 quorum_hash: &[u8; 32],
211 request_id: &[u8; 32],
212 sign_bytes_hash: &[u8],
213) -> Vec<u8> {
214 let mut quorum_hash = quorum_hash.to_vec();
215 quorum_hash.reverse();
216
217 let mut request_id = request_id.to_vec();
218 request_id.reverse();
219
220 let mut sign_bytes_hash = sign_bytes_hash.to_vec();
221 sign_bytes_hash.reverse();
222
223 let mut buf = Vec::<u8>::new();
224
225 buf.put_u8(quorum_type);
226 buf.append(&mut quorum_hash);
227 buf.append(&mut request_id);
228 buf.append(&mut sign_bytes_hash);
229
230 let hash = lhash::sha256(&buf);
231 lhash::sha256(&hash).to_vec()
234}
235
236pub trait Hashable {
239 fn calculate_msg_hash(&self, chain_id: &str, height: i64, round: i32)
241 -> Result<Vec<u8>, Error>;
242}
243
244impl<T: SignBytes> Hashable for T {
245 fn calculate_msg_hash(
249 &self,
250 chain_id: &str,
251 height: i64,
252 round: i32,
253 ) -> Result<Vec<u8>, Error> {
254 let sb = self.sign_bytes(chain_id, height, round)?;
255 let result = lhash::sha256(&sb);
256 Ok(Vec::from(result))
257 }
258}
259
260trait SignBytes {
264 fn sign_bytes(&self, chain_id: &str, height: i64, round: i32) -> Result<Vec<u8>, Error>;
269}
270
271impl SignBytes for StateId {
272 fn sign_bytes(&self, _chain_id: &str, _height: i64, _round: i32) -> Result<Vec<u8>, Error> {
273 let mut buf = Vec::new();
274 self.encode_length_delimited(&mut buf)
275 .map_err(Error::Encode)?;
276
277 Ok(buf.to_vec())
278 }
279}
280
281impl SignBytes for BlockId {
282 fn sign_bytes(&self, _chain_id: &str, _height: i64, _round: i32) -> Result<Vec<u8>, Error> {
283 if self.hash.is_empty()
285 && (self.part_set_header.is_none()
286 || self.part_set_header.as_ref().unwrap().hash.is_empty())
287 && self.state_id.is_empty()
288 {
289 return Ok(Vec::<u8>::new());
290 }
291
292 let part_set_header = self.part_set_header.clone().unwrap_or_default();
293
294 let block_id = CanonicalBlockId {
295 hash: self.hash.clone(),
296 part_set_header: Some(crate::proto::types::CanonicalPartSetHeader {
297 total: part_set_header.total,
298 hash: part_set_header.hash,
299 }),
300 };
301 let mut buf = Vec::new();
302 block_id
303 .encode_length_delimited(&mut buf)
304 .map_err(Error::Encode)?;
305
306 Ok(buf)
307 }
308}
309
310impl SignBytes for Vote {
311 fn sign_bytes(&self, chain_id: &str, height: i64, round: i32) -> Result<Vec<u8>, Error> {
312 if height != self.height || round != self.round {
313 return Err(Error::Canonical(String::from("vote height/round mismatch")));
314 }
315
316 let block_id = self
317 .block_id
318 .clone()
319 .ok_or(Error::Canonical(String::from("missing vote.block id")))?;
320
321 let block_id_hash = block_id.calculate_msg_hash(chain_id, height, round)?;
322 let state_id_hash = block_id.state_id;
323
324 let canonical = CanonicalVote {
325 block_id: block_id_hash,
326 state_id: state_id_hash,
327 chain_id: chain_id.to_string(),
328 height,
329 round: round as i64,
330 r#type: self.r#type,
331 };
332
333 canonical.sign_bytes(chain_id, height, round)
334 }
335}
336
337impl SignBytes for Commit {
338 fn sign_bytes(&self, chain_id: &str, height: i64, round: i32) -> Result<Vec<u8>, Error> {
339 if height != self.height || round != self.round {
340 return Err(Error::Canonical(String::from(
341 "commit height/round mismatch",
342 )));
343 }
344
345 let block_id = self
346 .block_id
347 .clone()
348 .ok_or(Error::Canonical(String::from("missing vote.block id")))?;
349
350 let state_id_hash = block_id.state_id.clone();
351 let block_id_hash = block_id.calculate_msg_hash(chain_id, height, round)?;
352
353 let canonical = CanonicalVote {
354 block_id: block_id_hash,
355 state_id: state_id_hash,
356 chain_id: chain_id.to_string(),
357 height,
358 round: round as i64,
359 r#type: SignedMsgType::Precommit.into(),
360 };
361
362 canonical.sign_bytes(chain_id, height, round)
363 }
364}
365
366impl SignBytes for CanonicalVote {
367 fn sign_bytes(&self, chain_id: &str, height: i64, round: i32) -> Result<Vec<u8>, Error> {
368 if height != self.height || (round as i64) != self.round {
369 return Err(Error::Canonical(String::from(
370 "commit height/round mismatch",
371 )));
372 }
373
374 let mut buf = Vec::with_capacity(100);
376
377 buf.put_i32_le(self.r#type().into()); buf.put_i64_le(height); buf.put_i64_le(round as i64); buf.extend(&self.block_id); buf.extend(&self.state_id); if buf.len() != 4 + 8 + 8 + 32 + 32 {
387 return Err(Error::Canonical(
388 "cannot encode sign bytes: length of input data is invalid".to_string(),
389 ));
390 }
391 buf.put(chain_id.as_bytes());
392
393 tracing::trace!(
395 sign_bytes=hex::encode(&buf),
396 height,round,
397 vote=?self, "vote/commit sign bytes calculated");
398
399 Ok(buf.to_vec())
400 }
401}
402
403impl SignBytes for VoteExtension {
404 fn sign_bytes(&self, chain_id: &str, height: i64, round: i32) -> Result<Vec<u8>, Error> {
405 match self.r#type() {
406 VoteExtensionType::ThresholdRecover => {
407 let ve = CanonicalVoteExtension {
408 chain_id: chain_id.to_string(),
409 extension: self.extension.clone(),
410 height,
411 round: round as i64,
412 r#type: self.r#type,
413 };
414
415 Ok(ve.encode_length_delimited_to_vec())
416 },
417 VoteExtensionType::ThresholdRecoverRaw => Ok(self.extension.to_vec()),
418 _ => Err(Error::Canonical(format!(
419 "unimplemented: vote extension of type {:?} cannot be signed",
420 self.r#type()
421 ))),
422 }
423 }
424}
425
426impl Hashable for ValidatorSet {
427 fn calculate_msg_hash(
452 &self,
453 _chain_id: &str,
454 _height: i64,
455 _round: i32,
456 ) -> Result<Vec<u8>, Error> {
457 use tenderdash_proto::crypto::public_key::Sum::*;
458 let threshold_public_key_enum = self
459 .threshold_public_key
460 .as_ref()
461 .and_then(|key| key.sum.as_ref())
462 .ok_or(Error::Canonical("missing threshold public key".to_string()))?;
463
464 let threshold_public_key = match &threshold_public_key_enum {
465 Bls12381(pk) => pk,
466 Ed25519(pk) => pk,
467 Secp256k1(pk) => pk,
468 };
469
470 let result = merkle_hash(&[threshold_public_key, &self.quorum_hash]);
471 Ok(result.to_vec())
472 }
473}
474
475#[cfg(test)]
476pub mod tests {
477 use std::{string::ToString, vec::Vec};
478
479 use super::SignBytes;
480 use crate::{
481 proto::types::{
482 Commit, PartSetHeader, SignedMsgType, Vote, VoteExtension, VoteExtensionType,
483 },
484 signatures::{Hashable, Signable},
485 };
486
487 #[test]
488 fn vote_sign_bytes() {
491 let h = [1u8, 2, 3, 4].repeat(8);
492
493 let state_id_hash =
494 hex::decode("d7509905b5407ee72dadd93b4ae70a24ad8a7755fc677acd2b215710a05cfc47")
495 .unwrap();
496 let expect_sign_bytes = hex::decode("0100000001000000000000000200000000000000fb\
497 7c89bf010a91d50f890455582b7fed0c346e53ab33df7da0bcd85c10fa92ead7509905b5407ee72dadd93b\
498 4ae70a24ad8a7755fc677acd2b215710a05cfc47736f6d652d636861696e")
499 .unwrap();
500
501 let vote = Vote {
502 r#type: SignedMsgType::Prevote as i32,
503 height: 1,
504 round: 2,
505 block_id: Some(crate::proto::types::BlockId {
506 hash: h.clone(),
507 part_set_header: Some(PartSetHeader {
508 total: 1,
509 hash: h.clone(),
510 }),
511 state_id: state_id_hash,
512 }),
513 ..Default::default()
514 };
515 let chain_id = "some-chain".to_string();
516 let height = vote.height;
517 let round = vote.round;
518
519 let actual = vote.sign_bytes(&chain_id, height, round).unwrap();
520
521 assert_eq!(expect_sign_bytes, actual);
522 }
523
524 #[test]
525 fn commit_sign_bytes() {
526 let h = [1u8, 2, 3, 4].repeat(8);
527
528 let state_id_hash =
529 hex::decode("d7509905b5407ee72dadd93b4ae70a24ad8a7755fc677acd2b215710a05cfc47")
530 .unwrap();
531 let expect_sign_bytes = hex::decode("0200000001000000000000000200000000000000fb7c89bf010a91d5\
532 0f890455582b7fed0c346e53ab33df7da0bcd85c10fa92ead7509905b5407ee72dadd93b4ae70a24ad8a7755fc677acd2b215710\
533 a05cfc47736f6d652d636861696e")
534 .unwrap();
535
536 let commit = Commit {
537 height: 1,
538 round: 2,
539 block_id: Some(crate::proto::types::BlockId {
540 hash: h.clone(),
541 part_set_header: Some(PartSetHeader {
542 total: 1,
543 hash: h.clone(),
544 }),
545 state_id: state_id_hash,
546 }),
547 ..Default::default()
548 };
549 let chain_id = "some-chain".to_string();
550 let height = commit.height;
551 let round = commit.round;
552
553 let actual = commit.sign_bytes(&chain_id, height, round).unwrap();
554
555 assert_eq!(expect_sign_bytes, actual);
556 }
557
558 #[test]
559 fn vote_extension_threshold_sign_bytes() {
560 let ve = VoteExtension {
561 extension: Vec::from([1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, 8u8]),
562 r#type: VoteExtensionType::ThresholdRecover.into(),
563 signature: Default::default(),
564 sign_request_id: None,
565 };
566
567 let chain_id = "some-chain".to_string();
568 let height = 1;
569 let round = 2;
570
571 let expect_sign_bytes = hex::decode(
572 "2a0a080102030405060708110100000000000000190200000000000000220a736f6d652d636861696e2801",
573 )
574 .unwrap();
575
576 let actual = ve.sign_bytes(&chain_id, height, round).unwrap();
577
578 assert_eq!(expect_sign_bytes, actual);
579 }
580
581 fn ve_threshold_raw() -> ([u8; 32], VoteExtension) {
585 let ve = VoteExtension {
586 extension: [1, 2, 3, 4, 5, 6, 7, 8].repeat(4),
587 r#type: VoteExtensionType::ThresholdRecoverRaw.into(),
588 signature: Default::default(),
589 sign_request_id: Some("dpevote-someSignRequestID".as_bytes().to_vec()),
590 };
591 let expected_sign_hash: [u8; 32] = [
592 0xe, 0x88, 0x8d, 0xa8, 0x97, 0xf1, 0xc0, 0xfd, 0x6a, 0xe8, 0x3b, 0x77, 0x9b, 0x5, 0xdd,
593 0x28, 0xc, 0xe2, 0x58, 0xf6, 0x4c, 0x86, 0x1, 0x34, 0xfa, 0x4, 0x27, 0xe1, 0xaa, 0xab,
594 0x1a, 0xde,
595 ];
596
597 (expected_sign_hash, ve)
598 }
599
600 #[test]
601 fn test_ve_threshold_raw_sign_bytes() {
602 let (_, ve) = ve_threshold_raw();
603 let expected_sign_bytes = ve.extension.clone();
604
605 let chain_id = String::new();
607 let height = -1;
608 let round = -1;
609
610 let actual = ve.sign_bytes(&chain_id, height, round).unwrap();
611
612 assert_eq!(expected_sign_bytes, actual);
613 }
614
615 #[test]
616 fn test_sign_digest() {
617 let quorum_hash: [u8; 32] =
618 hex::decode("6A12D9CF7091D69072E254B297AEF15997093E480FDE295E09A7DE73B31CEEDD")
619 .unwrap()
620 .try_into()
621 .unwrap();
622
623 let request_id = super::sign_request_id(super::VOTE_REQUEST_ID_PREFIX, 1001, 0);
624 let request_id = request_id[..].try_into().unwrap();
625
626 let sign_bytes_hash =
627 hex::decode("0CA3D5F42BDFED0C4FDE7E6DE0F046CC76CDA6CEE734D65E8B2EE0E375D4C57D")
628 .unwrap();
629
630 let expect_sign_hash =
631 hex::decode("DA25B746781DDF47B5D736F30B1D9D0CC86981EEC67CBE255265C4361DEF8C2E")
632 .unwrap();
633
634 let sign_hash = super::sign_hash(100, &quorum_hash, request_id, &sign_bytes_hash);
635 assert_eq!(expect_sign_hash, sign_hash); }
637
638 #[test]
639 fn test_ve_threshold_raw_sign_digest() {
640 const QUORUM_TYPE: u8 = 106;
641 let quorum_hash: [u8; 32] = [8u8, 7, 6, 5, 4, 3, 2, 1]
642 .repeat(4)
643 .try_into()
644 .expect("invalid quorum hash length");
645 let (expected_sign_hash, ve) = ve_threshold_raw();
646
647 let sign_hash = ve
649 .calculate_sign_hash("", QUORUM_TYPE, &quorum_hash, -1, -1)
650 .expect("sign digest failed");
651
652 assert_eq!(sign_hash, expected_sign_hash);
653 }
654
655 #[test]
656 fn test_validator_set_hash() {
657 use crate::proto::crypto::{PublicKey, public_key::Sum::Bls12381};
658
659 const QUORUM_HASH_HEX: &str =
660 "703ee5bfc78765cc9e151d8dd84e30e196ababa83ac6cbdee31a88a46bba81b9";
661 const THRESHOLD_PUB_KEY_HEX: &str = "830e45e45e6414d9d615473cc2814e6b171c508f9c77e8b16924b74594f61c9956a6fa16335e98467eac8d8bdb76d187";
662 const VALIDATORS_HASH_HEX: &str =
663 "81742F95E99EAE96ABC727FE792CECB4996205DE6BFC88AFEE1F60B96BC648B2";
664
665 let pubkey_vec = hex::decode(THRESHOLD_PUB_KEY_HEX).unwrap();
666 let quorum_hash = hex::decode(QUORUM_HASH_HEX).unwrap();
667 let expected_validators_hash = hex::decode(VALIDATORS_HASH_HEX).unwrap();
668
669 let threshold_public_key = PublicKey {
670 sum: Some(Bls12381(pubkey_vec)),
671 };
672
673 let vs = crate::proto::types::ValidatorSet {
674 threshold_public_key: Some(threshold_public_key),
675 quorum_hash,
676 ..Default::default()
677 };
678
679 let actual = vs.calculate_msg_hash("", 0, 0).unwrap();
681
682 assert_eq!(expected_validators_hash, actual,);
683 }
684}