1#![doc = include_str!("../README.md")]
2
3extern crate alloc;
27
28use alloc::{string::String, vec::Vec};
29
30use blake2::{Blake2s256, Digest};
31
32pub mod cbor;
33
34use cbor::Value;
35
36pub const AUM_HASH_LEN: usize = 32;
38
39const MAX_SIG_NESTING_DEPTH: usize = 16;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
51pub struct AumHash(pub [u8; AUM_HASH_LEN]);
52
53impl AumHash {
54 pub fn from_base32(text: &str) -> Option<AumHash> {
57 let decoded = base32_decode_nopad(text)?;
58 if decoded.len() != AUM_HASH_LEN {
59 return None;
60 }
61 let mut h = [0u8; AUM_HASH_LEN];
62 h.copy_from_slice(&decoded);
63 Some(AumHash(h))
64 }
65
66 pub fn to_base32(&self) -> String {
68 base32_encode_nopad(&self.0)
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75#[repr(u8)]
76pub enum AumKind {
77 Invalid = 0,
79 AddKey = 1,
81 RemoveKey = 2,
83 NoOp = 3,
85 UpdateKey = 4,
87 Checkpoint = 5,
89}
90
91impl AumKind {
92 pub fn from_u8(n: u8) -> Option<AumKind> {
96 Some(match n {
97 0 => AumKind::Invalid,
98 1 => AumKind::AddKey,
99 2 => AumKind::RemoveKey,
100 3 => AumKind::NoOp,
101 4 => AumKind::UpdateKey,
102 5 => AumKind::Checkpoint,
103 _ => return None,
104 })
105 }
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum KeyKind {
111 Ed25519,
113}
114
115#[derive(Debug, Clone, PartialEq, Eq)]
118pub struct Key {
119 pub kind: KeyKind,
121 pub votes: u32,
123 pub public: Vec<u8>,
125}
126
127impl Key {
128 pub fn id(&self) -> &[u8] {
130 &self.public
131 }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136#[repr(u8)]
137pub enum SigKind {
138 Invalid = 0,
140 Direct = 1,
142 Rotation = 2,
144 Credential = 3,
146}
147
148impl SigKind {
149 fn from_u8(n: u8) -> Option<SigKind> {
150 Some(match n {
151 0 => SigKind::Invalid,
152 1 => SigKind::Direct,
153 2 => SigKind::Rotation,
154 3 => SigKind::Credential,
155 _ => return None,
156 })
157 }
158}
159
160#[derive(Debug, Clone, PartialEq, Eq)]
163pub struct NodeKeySignature {
164 pub sig_kind: SigKind,
166 pub pubkey: Vec<u8>,
168 pub key_id: Vec<u8>,
170 pub signature: Vec<u8>,
172 pub nested: Option<alloc::boxed::Box<NodeKeySignature>>,
174 pub wrapping_pubkey: Vec<u8>,
176}
177
178#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
180pub enum TkaError {
181 #[error("TKA decode error: {0}")]
183 Decode(&'static str),
184 #[error("TKA signature verification failed")]
186 BadSignature,
187 #[error("TKA authorizing key is not trusted")]
189 UntrustedKey,
190 #[error("a credential signature cannot authorize a node")]
192 CredentialCannotAuthorize,
193 #[error("signature does not cover this node key")]
195 NodeKeyMismatch,
196}
197
198impl NodeKeySignature {
199 fn sig_hash(&self) -> [u8; AUM_HASH_LEN] {
203 let v = self.to_cbor(false);
204 blake2s_256(&v.to_vec())
205 }
206
207 fn to_cbor(&self, include_signature: bool) -> Value {
210 cbor::int_map([
211 (1, Some(Value::Uint(self.sig_kind as u8 as u64))),
212 (2, nonempty_bytes(&self.pubkey)),
213 (3, nonempty_bytes(&self.key_id)),
214 (
215 4,
216 if include_signature {
217 nonempty_bytes(&self.signature)
218 } else {
219 None
220 },
221 ),
222 (5, self.nested.as_ref().map(|n| n.to_cbor(true))),
223 (6, nonempty_bytes(&self.wrapping_pubkey)),
224 ])
225 }
226
227 fn authorizing_key_id(&self) -> Result<&[u8], TkaError> {
230 match self.sig_kind {
231 SigKind::Rotation => self
232 .nested
233 .as_ref()
234 .ok_or(TkaError::Decode("rotation signature missing nested"))?
235 .authorizing_key_id(),
236 SigKind::Direct | SigKind::Credential => Ok(&self.key_id),
237 SigKind::Invalid => Err(TkaError::Decode("invalid signature kind")),
238 }
239 }
240
241 fn verify_signature(&self, node_key: &[u8], verification_key: &Key) -> Result<(), TkaError> {
244 if self.sig_kind != SigKind::Credential && self.pubkey != node_key {
246 return Err(TkaError::NodeKeyMismatch);
247 }
248
249 let sig_hash = self.sig_hash();
250
251 match self.sig_kind {
252 SigKind::Rotation => {
253 let nested = self
254 .nested
255 .as_ref()
256 .ok_or(TkaError::Decode("rotation signature missing nested"))?;
257 let verify_pub = &nested.wrapping_pubkey;
260 if verify_pub.len() != 32 {
261 return Err(TkaError::Decode("wrapping pubkey wrong length"));
262 }
263 verify_ed25519_std(verify_pub, &sig_hash, &self.signature)?;
264 if nested.sig_kind == SigKind::Credential && nested.pubkey != *verify_pub {
270 return Err(TkaError::NodeKeyMismatch);
271 }
272 nested.verify_signature(verify_pub, verification_key)
274 }
275 SigKind::Direct | SigKind::Credential => {
276 if self.nested.is_some() {
277 return Err(TkaError::Decode("direct/credential signature has nested"));
278 }
279 if verification_key.kind != KeyKind::Ed25519 || verification_key.public.len() != 32
280 {
281 return Err(TkaError::Decode("verification key not ed25519"));
282 }
283 verify_ed25519_zip215(&verification_key.public, &sig_hash, &self.signature)
286 }
287 SigKind::Invalid => Err(TkaError::Decode("invalid signature kind")),
288 }
289 }
290}
291
292#[derive(Debug, Clone, Default, PartialEq, Eq)]
295pub struct State {
296 pub keys: Vec<Key>,
298}
299
300impl State {
301 pub fn get_key(&self, key_id: &[u8]) -> Option<&Key> {
303 self.keys.iter().find(|k| k.id() == key_id)
304 }
305}
306
307#[derive(Debug, Clone)]
311pub struct Authority {
312 head: AumHash,
313 state: State,
314}
315
316impl Authority {
317 pub fn from_state(head: AumHash, state: State) -> Authority {
320 Authority { head, state }
321 }
322
323 pub fn head(&self) -> AumHash {
325 self.head
326 }
327
328 pub fn state(&self) -> &State {
330 &self.state
331 }
332
333 pub fn head_matches(&self, head: &AumHash) -> bool {
336 &self.head == head
337 }
338
339 pub fn node_key_authorized(
353 &self,
354 node_key: &[u8],
355 signature_cbor: &[u8],
356 ) -> Result<(), TkaError> {
357 let sig = decode_node_key_signature(signature_cbor)?;
358 if sig.sig_kind == SigKind::Credential {
360 return Err(TkaError::CredentialCannotAuthorize);
361 }
362 let key_id = sig.authorizing_key_id()?;
363 let key = self.state.get_key(key_id).ok_or(TkaError::UntrustedKey)?;
364 sig.verify_signature(node_key, key)
365 }
366}
367
368pub fn aum_hash(canonical_cbor: &[u8]) -> AumHash {
371 AumHash(blake2s_256(canonical_cbor))
372}
373
374fn blake2s_256(data: &[u8]) -> [u8; AUM_HASH_LEN] {
375 let mut hasher = Blake2s256::new();
376 hasher.update(data);
377 let out = hasher.finalize();
378 let mut h = [0u8; AUM_HASH_LEN];
379 h.copy_from_slice(&out);
380 h
381}
382
383fn nonempty_bytes(b: &[u8]) -> Option<Value> {
385 if b.is_empty() {
386 None
387 } else {
388 Some(Value::Bytes(b.to_vec()))
389 }
390}
391
392fn verify_ed25519_std(public: &[u8], msg: &[u8], sig: &[u8]) -> Result<(), TkaError> {
394 use ed25519_dalek::{Signature, Verifier, VerifyingKey};
395 let pk: [u8; 32] = public
396 .try_into()
397 .map_err(|_| TkaError::Decode("bad pubkey len"))?;
398 let vk = VerifyingKey::from_bytes(&pk).map_err(|_| TkaError::Decode("bad ed25519 pubkey"))?;
399 let sig: [u8; 64] = sig
400 .try_into()
401 .map_err(|_| TkaError::Decode("bad sig len"))?;
402 vk.verify(msg, &Signature::from_bytes(&sig))
403 .map_err(|_| TkaError::BadSignature)
404}
405
406fn verify_ed25519_zip215(public: &[u8], msg: &[u8], sig: &[u8]) -> Result<(), TkaError> {
408 let pk: [u8; 32] = public
409 .try_into()
410 .map_err(|_| TkaError::Decode("bad pubkey len"))?;
411 let vk = ed25519_zebra::VerificationKey::try_from(pk)
412 .map_err(|_| TkaError::Decode("bad ed25519 pubkey"))?;
413 let sig_bytes: [u8; 64] = sig
414 .try_into()
415 .map_err(|_| TkaError::Decode("bad sig len"))?;
416 let sig = ed25519_zebra::Signature::from(sig_bytes);
417 vk.verify(&sig, msg).map_err(|_| TkaError::BadSignature)
418}
419
420fn decode_node_key_signature(buf: &[u8]) -> Result<NodeKeySignature, TkaError> {
423 let (val, rest) = decode_value(buf, 0)?;
424 if !rest.is_empty() {
425 return Err(TkaError::Decode("trailing bytes after signature"));
426 }
427 node_key_signature_from_value(val, 0)
428}
429
430fn node_key_signature_from_value(val: Value, depth: usize) -> Result<NodeKeySignature, TkaError> {
431 if depth > MAX_SIG_NESTING_DEPTH {
432 return Err(TkaError::Decode("nested signature too deep"));
433 }
434 let Value::IntMap(entries) = val else {
435 return Err(TkaError::Decode("signature is not an int-keyed map"));
436 };
437 let mut sig_kind = None;
438 let mut pubkey = Vec::new();
439 let mut key_id = Vec::new();
440 let mut signature = Vec::new();
441 let mut nested = None;
442 let mut wrapping_pubkey = Vec::new();
443
444 for (k, v) in entries {
445 match k {
446 1 => {
447 let Value::Uint(n) = v else {
448 return Err(TkaError::Decode("sig kind not uint"));
449 };
450 sig_kind = Some(
451 SigKind::from_u8(
452 u8::try_from(n).map_err(|_| TkaError::Decode("sig kind range"))?,
453 )
454 .ok_or(TkaError::Decode("unknown sig kind"))?,
455 );
456 }
457 2 => pubkey = expect_bytes(v)?,
458 3 => key_id = expect_bytes(v)?,
459 4 => signature = expect_bytes(v)?,
460 5 => {
461 nested = Some(alloc::boxed::Box::new(node_key_signature_from_value(
462 v,
463 depth + 1,
464 )?))
465 }
466 6 => wrapping_pubkey = expect_bytes(v)?,
467 _ => return Err(TkaError::Decode("unknown signature field")),
468 }
469 }
470
471 Ok(NodeKeySignature {
472 sig_kind: sig_kind.ok_or(TkaError::Decode("signature missing kind"))?,
473 pubkey,
474 key_id,
475 signature,
476 nested,
477 wrapping_pubkey,
478 })
479}
480
481fn expect_bytes(v: Value) -> Result<Vec<u8>, TkaError> {
482 match v {
483 Value::Bytes(b) => Ok(b),
484 _ => Err(TkaError::Decode("expected byte string")),
485 }
486}
487
488fn decode_value(buf: &[u8], depth: usize) -> Result<(Value, &[u8]), TkaError> {
491 if depth > MAX_SIG_NESTING_DEPTH {
494 return Err(TkaError::Decode("nested signature too deep"));
495 }
496 let (major, arg, rest) = decode_head(buf)?;
497 match major {
498 0 => Ok((Value::Uint(arg), rest)),
499 2 => {
500 let len = arg as usize;
501 if rest.len() < len {
502 return Err(TkaError::Decode("byte string truncated"));
503 }
504 Ok((Value::Bytes(rest[..len].to_vec()), &rest[len..]))
505 }
506 3 => {
507 let len = arg as usize;
508 if rest.len() < len {
509 return Err(TkaError::Decode("text string truncated"));
510 }
511 Ok((Value::Text(rest[..len].to_vec()), &rest[len..]))
512 }
513 4 => {
514 let mut items = Vec::new();
515 let mut cur = rest;
516 for _ in 0..arg {
517 let (v, next) = decode_value(cur, depth + 1)?;
518 items.push(v);
519 cur = next;
520 }
521 Ok((Value::Array(items), cur))
522 }
523 5 => {
524 let mut entries: Vec<(u64, Value)> = Vec::new();
525 let mut cur = rest;
526 for _ in 0..arg {
527 let (k, next) = decode_head(cur).and_then(|(m, a, r)| {
528 if m == 0 {
529 Ok((a, r))
530 } else {
531 Err(TkaError::Decode("map key not uint"))
532 }
533 })?;
534 if entries.iter().any(|(existing, _)| *existing == k) {
537 return Err(TkaError::Decode("duplicate map key"));
538 }
539 let (v, next2) = decode_value(next, depth + 1)?;
540 entries.push((k, v));
541 cur = next2;
542 }
543 Ok((Value::IntMap(entries), cur))
544 }
545 _ => Err(TkaError::Decode("unsupported CBOR major type")),
546 }
547}
548
549fn decode_head(buf: &[u8]) -> Result<(u8, u64, &[u8]), TkaError> {
551 let first = *buf.first().ok_or(TkaError::Decode("empty CBOR"))?;
552 let major = first >> 5;
553 let info = first & 0x1f;
554 let rest = &buf[1..];
555 let (arg, rest) = match info {
556 n @ 0..=23 => (n as u64, rest),
557 24 => {
558 let b = *rest.first().ok_or(TkaError::Decode("truncated u8"))?;
559 (b as u64, &rest[1..])
560 }
561 25 => {
562 if rest.len() < 2 {
563 return Err(TkaError::Decode("truncated u16"));
564 }
565 (u16::from_be_bytes([rest[0], rest[1]]) as u64, &rest[2..])
566 }
567 26 => {
568 if rest.len() < 4 {
569 return Err(TkaError::Decode("truncated u32"));
570 }
571 (
572 u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]) as u64,
573 &rest[4..],
574 )
575 }
576 27 => {
577 if rest.len() < 8 {
578 return Err(TkaError::Decode("truncated u64"));
579 }
580 let mut b = [0u8; 8];
581 b.copy_from_slice(&rest[..8]);
582 (u64::from_be_bytes(b), &rest[8..])
583 }
584 _ => return Err(TkaError::Decode("indefinite/reserved CBOR length")),
585 };
586 Ok((major, arg, rest))
587}
588
589const BASE32_ALPHABET: &[u8; 32] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
592
593fn base32_encode_nopad(data: &[u8]) -> String {
594 let mut out = String::new();
595 let mut buffer: u32 = 0;
596 let mut bits: u32 = 0;
597 for &b in data {
598 buffer = (buffer << 8) | b as u32;
599 bits += 8;
600 while bits >= 5 {
601 bits -= 5;
602 let idx = ((buffer >> bits) & 0x1f) as usize;
603 out.push(BASE32_ALPHABET[idx] as char);
604 }
605 }
606 if bits > 0 {
607 let idx = ((buffer << (5 - bits)) & 0x1f) as usize;
608 out.push(BASE32_ALPHABET[idx] as char);
609 }
610 out
611}
612
613fn base32_decode_nopad(text: &str) -> Option<Vec<u8>> {
614 let mut buffer: u32 = 0;
615 let mut bits: u32 = 0;
616 let mut out = Vec::new();
617 for c in text.chars() {
618 let val = match c {
619 'A'..='Z' => c as u32 - 'A' as u32,
620 '2'..='7' => c as u32 - '2' as u32 + 26,
621 _ => return None,
622 };
623 buffer = (buffer << 5) | val;
624 bits += 5;
625 if bits >= 8 {
626 bits -= 8;
627 out.push((buffer >> bits) as u8);
628 }
629 }
630 Some(out)
631}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636
637 #[test]
638 fn base32_roundtrip_32_bytes() {
639 let h = AumHash([0xABu8; 32]);
640 let text = h.to_base32();
641 let back = AumHash::from_base32(&text).unwrap();
642 assert_eq!(h, back);
643 }
644
645 #[test]
646 fn base32_rejects_wrong_length() {
647 assert!(AumHash::from_base32("AAAA").is_none());
649 assert!(AumHash::from_base32("aaaa").is_none());
651 }
652
653 #[test]
654 fn base32_matches_known_vector() {
655 assert_eq!(base32_encode_nopad(b"foobar"), "MZXW6YTBOI");
657 assert_eq!(base32_decode_nopad("MZXW6YTBOI").unwrap(), b"foobar");
658 }
659
660 #[test]
661 fn credential_signature_cannot_authorize() {
662 let auth = Authority::from_state(AumHash([0; 32]), State::default());
663 let sig = NodeKeySignature {
664 sig_kind: SigKind::Credential,
665 pubkey: alloc::vec![1, 2, 3],
666 key_id: alloc::vec![4, 5, 6],
667 signature: alloc::vec![0; 64],
668 nested: None,
669 wrapping_pubkey: Vec::new(),
670 };
671 let cbor = sig.to_cbor(true).to_vec();
672 let err = auth.node_key_authorized(&[1, 2, 3], &cbor).unwrap_err();
673 assert_eq!(err, TkaError::CredentialCannotAuthorize);
674 }
675
676 #[test]
677 fn untrusted_key_denied() {
678 let auth = Authority::from_state(AumHash([0; 32]), State::default());
680 let sig = NodeKeySignature {
681 sig_kind: SigKind::Direct,
682 pubkey: alloc::vec![9; 32],
683 key_id: alloc::vec![7; 32],
684 signature: alloc::vec![0; 64],
685 nested: None,
686 wrapping_pubkey: Vec::new(),
687 };
688 let cbor = sig.to_cbor(true).to_vec();
689 let err = auth.node_key_authorized(&[9; 32], &cbor).unwrap_err();
690 assert_eq!(err, TkaError::UntrustedKey);
691 }
692
693 #[test]
694 fn direct_signature_verifies_end_to_end() {
695 use ed25519_dalek::{Signer, SigningKey};
696
697 let signing = SigningKey::from_bytes(&[42u8; 32]);
699 let trusted_pub = signing.verifying_key().to_bytes().to_vec();
700 let node_key = alloc::vec![7u8; 32];
701
702 let trusted = Key {
703 kind: KeyKind::Ed25519,
704 votes: 1,
705 public: trusted_pub.clone(),
706 };
707 let auth = Authority::from_state(
708 AumHash([0; 32]),
709 State {
710 keys: alloc::vec![trusted],
711 },
712 );
713
714 let mut sig = NodeKeySignature {
716 sig_kind: SigKind::Direct,
717 pubkey: node_key.clone(),
718 key_id: trusted_pub.clone(),
719 signature: Vec::new(),
720 nested: None,
721 wrapping_pubkey: Vec::new(),
722 };
723 let sig_hash = sig.sig_hash();
724 sig.signature = signing.sign(&sig_hash).to_bytes().to_vec();
727
728 let cbor = sig.to_cbor(true).to_vec();
729 assert!(auth.node_key_authorized(&node_key, &cbor).is_ok());
730
731 let other = alloc::vec![8u8; 32];
733 assert_eq!(
734 auth.node_key_authorized(&other, &cbor).unwrap_err(),
735 TkaError::NodeKeyMismatch
736 );
737 }
738
739 #[test]
740 fn tampered_signature_denied() {
741 use ed25519_dalek::{Signer, SigningKey};
742
743 let signing = SigningKey::from_bytes(&[42u8; 32]);
744 let trusted_pub = signing.verifying_key().to_bytes().to_vec();
745 let node_key = alloc::vec![7u8; 32];
746 let auth = Authority::from_state(
747 AumHash([0; 32]),
748 State {
749 keys: alloc::vec![Key {
750 kind: KeyKind::Ed25519,
751 votes: 1,
752 public: trusted_pub.clone(),
753 }],
754 },
755 );
756 let mut sig = NodeKeySignature {
757 sig_kind: SigKind::Direct,
758 pubkey: node_key.clone(),
759 key_id: trusted_pub,
760 signature: Vec::new(),
761 nested: None,
762 wrapping_pubkey: Vec::new(),
763 };
764 let sig_hash = sig.sig_hash();
765 let mut sigbytes = signing.sign(&sig_hash).to_bytes();
766 sigbytes[0] ^= 0xff; sig.signature = sigbytes.to_vec();
768
769 let cbor = sig.to_cbor(true).to_vec();
770 assert_eq!(
771 auth.node_key_authorized(&node_key, &cbor).unwrap_err(),
772 TkaError::BadSignature
773 );
774 }
775
776 #[test]
777 fn head_matches_check() {
778 let h = AumHash([5u8; 32]);
779 let auth = Authority::from_state(h, State::default());
780 assert!(auth.head_matches(&h));
781 assert!(!auth.head_matches(&AumHash([6u8; 32])));
782 }
783
784 #[test]
787 fn deeply_nested_signature_rejected_without_overflow() {
788 let mut sig = NodeKeySignature {
792 sig_kind: SigKind::Direct,
793 pubkey: alloc::vec![1u8; 32],
794 key_id: alloc::vec![2u8; 32],
795 signature: alloc::vec![3u8; 64],
796 nested: None,
797 wrapping_pubkey: Vec::new(),
798 };
799 for _ in 0..(MAX_SIG_NESTING_DEPTH + 8) {
800 sig = NodeKeySignature {
801 sig_kind: SigKind::Rotation,
802 pubkey: alloc::vec![1u8; 32],
803 key_id: Vec::new(),
804 signature: alloc::vec![3u8; 64],
805 nested: Some(alloc::boxed::Box::new(sig)),
806 wrapping_pubkey: alloc::vec![1u8; 32],
807 };
808 }
809 let cbor = sig.to_cbor(true).to_vec();
810 let err = decode_node_key_signature(&cbor).unwrap_err();
811 assert_eq!(err, TkaError::Decode("nested signature too deep"));
812 }
813
814 #[test]
817 fn duplicate_map_key_rejected() {
818 let blob = [0xa2u8, 0x01, 0x00, 0x01, 0x01];
820 let err = decode_node_key_signature(&blob).unwrap_err();
821 assert_eq!(err, TkaError::Decode("duplicate map key"));
822 }
823
824 #[test]
838 fn rotation_chain_verifies_end_to_end() {
839 use ed25519_dalek::{Signer, SigningKey};
840
841 let trusted = SigningKey::from_bytes(&[7u8; 32]);
843 let trusted_pub = trusted.verifying_key().to_bytes().to_vec();
844
845 let wrapping = SigningKey::from_bytes(&[9u8; 32]);
848 let wrapping_pub = wrapping.verifying_key().to_bytes().to_vec();
849
850 let node_key = alloc::vec![5u8; 32];
851
852 let auth = Authority::from_state(
853 AumHash([0; 32]),
854 State {
855 keys: alloc::vec![Key {
856 kind: KeyKind::Ed25519,
857 votes: 1,
858 public: trusted_pub.clone(),
859 }],
860 },
861 );
862
863 let mut inner = NodeKeySignature {
866 sig_kind: SigKind::Direct,
867 pubkey: wrapping_pub.clone(),
868 key_id: trusted_pub.clone(),
869 signature: Vec::new(),
870 nested: None,
871 wrapping_pubkey: wrapping_pub.clone(),
872 };
873 let inner_hash = inner.sig_hash();
874 inner.signature = trusted.sign(&inner_hash).to_bytes().to_vec();
875
876 let mut outer = NodeKeySignature {
878 sig_kind: SigKind::Rotation,
879 pubkey: node_key.clone(),
880 key_id: Vec::new(),
881 signature: Vec::new(),
882 nested: Some(alloc::boxed::Box::new(inner)),
883 wrapping_pubkey: Vec::new(),
884 };
885 let outer_hash = outer.sig_hash();
886 outer.signature = wrapping.sign(&outer_hash).to_bytes().to_vec();
887
888 let cbor = outer.to_cbor(true).to_vec();
889 assert!(auth.node_key_authorized(&node_key, &cbor).is_ok());
890
891 let mut tampered = outer.clone();
893 let mut sb = tampered.signature.clone();
894 sb[0] ^= 0xff;
895 tampered.signature = sb;
896 let cbor_bad = tampered.to_cbor(true).to_vec();
897 assert_eq!(
898 auth.node_key_authorized(&node_key, &cbor_bad).unwrap_err(),
899 TkaError::BadSignature
900 );
901 }
902
903 #[test]
906 fn rotation_nested_credential_pubkey_bind() {
907 use ed25519_dalek::{Signer, SigningKey};
908
909 let trusted = SigningKey::from_bytes(&[11u8; 32]);
910 let trusted_pub = trusted.verifying_key().to_bytes().to_vec();
911 let wrapping = SigningKey::from_bytes(&[13u8; 32]);
912 let wrapping_pub = wrapping.verifying_key().to_bytes().to_vec();
913 let node_key = alloc::vec![6u8; 32];
914
915 let auth = Authority::from_state(
916 AumHash([0; 32]),
917 State {
918 keys: alloc::vec![Key {
919 kind: KeyKind::Ed25519,
920 votes: 1,
921 public: trusted_pub.clone(),
922 }],
923 },
924 );
925
926 let build = |cred_pubkey: Vec<u8>| -> Vec<u8> {
928 let mut inner = NodeKeySignature {
929 sig_kind: SigKind::Credential,
930 pubkey: cred_pubkey,
931 key_id: trusted_pub.clone(),
932 signature: Vec::new(),
933 nested: None,
934 wrapping_pubkey: wrapping_pub.clone(),
935 };
936 let inner_hash = inner.sig_hash();
937 inner.signature = trusted.sign(&inner_hash).to_bytes().to_vec();
938
939 let mut outer = NodeKeySignature {
940 sig_kind: SigKind::Rotation,
941 pubkey: node_key.clone(),
942 key_id: Vec::new(),
943 signature: Vec::new(),
944 nested: Some(alloc::boxed::Box::new(inner)),
945 wrapping_pubkey: Vec::new(),
946 };
947 let outer_hash = outer.sig_hash();
948 outer.signature = wrapping.sign(&outer_hash).to_bytes().to_vec();
949 outer.to_cbor(true).to_vec()
950 };
951
952 let cbor_ok = build(wrapping_pub.clone());
954 assert!(auth.node_key_authorized(&node_key, &cbor_ok).is_ok());
955
956 let cbor_bad = build(alloc::vec![0xaau8; 32]);
959 assert_eq!(
960 auth.node_key_authorized(&node_key, &cbor_bad).unwrap_err(),
961 TkaError::NodeKeyMismatch
962 );
963 }
964
965 fn hex(bytes: &[u8]) -> String {
969 let mut s = String::new();
970 for b in bytes {
971 s.push_str(&alloc::format!("{b:02x}"));
972 }
973 s
974 }
975
976 #[test]
994 fn node_key_signature_cbor_frozen_vector() {
995 let pubkey: Vec<u8> = (0u8..32).collect();
997 let key_id: Vec<u8> = (32u8..64).collect();
998 let signature: Vec<u8> = (64u8..128).collect();
999
1000 let sig = NodeKeySignature {
1001 sig_kind: SigKind::Direct,
1002 pubkey,
1003 key_id,
1004 signature,
1005 nested: None,
1006 wrapping_pubkey: Vec::new(), };
1008
1009 let full = sig.to_cbor(true).to_vec();
1011 const EXPECTED_FULL: &[u8] = &[
1012 0xa4, 0x01, 0x01, 0x02, 0x58, 0x20, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1013 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
1014 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x03, 0x58, 0x20, 0x20,
1015 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e,
1016 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
1017 0x3d, 0x3e, 0x3f, 0x04, 0x58, 0x40, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
1018 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
1019 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63,
1020 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71,
1021 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
1022 ];
1023 assert_eq!(
1024 full,
1025 EXPECTED_FULL,
1026 "full CBOR serialization changed (canonical-CBOR encoding drift). actual: {}",
1027 hex(&full)
1028 );
1029
1030 let preimage = sig.to_cbor(false).to_vec();
1032 const EXPECTED_PREIMAGE: &[u8] = &[
1033 0xa3, 0x01, 0x01, 0x02, 0x58, 0x20, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1034 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
1035 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x03, 0x58, 0x20, 0x20,
1036 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e,
1037 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
1038 0x3d, 0x3e, 0x3f,
1039 ];
1040 assert_eq!(
1041 preimage,
1042 EXPECTED_PREIMAGE,
1043 "SigHash preimage CBOR changed. actual: {}",
1044 hex(&preimage)
1045 );
1046
1047 let sig_hash = sig.sig_hash();
1049 const EXPECTED_SIG_HASH: [u8; AUM_HASH_LEN] = [
1050 0x22, 0x6f, 0x9c, 0xbc, 0x63, 0x73, 0x92, 0x75, 0x2e, 0x0e, 0xb1, 0x32, 0x9c, 0xc4,
1051 0x99, 0x07, 0x01, 0x4a, 0xb6, 0x4f, 0x8e, 0x5d, 0x82, 0x85, 0xc2, 0x91, 0x42, 0x62,
1052 0xf6, 0xa6, 0xa8, 0x33,
1053 ];
1054 assert_eq!(
1055 sig_hash,
1056 EXPECTED_SIG_HASH,
1057 "sig_hash (BLAKE2s-256 of preimage) changed. actual: {}",
1058 hex(&sig_hash)
1059 );
1060
1061 let aum = aum_hash(&full);
1064 const EXPECTED_AUM_HASH: [u8; AUM_HASH_LEN] = [
1065 0xa4, 0x40, 0x71, 0xa3, 0x7a, 0xbf, 0x80, 0x92, 0xd6, 0xff, 0x23, 0x84, 0xb2, 0xb0,
1066 0xa3, 0x50, 0xc7, 0xcb, 0x48, 0x41, 0xed, 0x68, 0x99, 0x62, 0x41, 0x7c, 0xd4, 0x23,
1067 0x68, 0xdc, 0x72, 0x49,
1068 ];
1069 assert_eq!(
1070 aum.0,
1071 EXPECTED_AUM_HASH,
1072 "aum_hash over full serialization changed. actual: {}",
1073 hex(&aum.0)
1074 );
1075 }
1076
1077 fn unhex(s: &str) -> Vec<u8> {
1081 assert!(s.len().is_multiple_of(2), "odd hex length");
1082 let nib = |c: u8| -> u8 {
1083 match c {
1084 b'0'..=b'9' => c - b'0',
1085 b'a'..=b'f' => c - b'a' + 10,
1086 b'A'..=b'F' => c - b'A' + 10,
1087 _ => panic!("bad hex nibble"),
1088 }
1089 };
1090 let b = s.as_bytes();
1091 let mut out = Vec::with_capacity(s.len() / 2);
1092 let mut i = 0;
1093 while i < b.len() {
1094 out.push((nib(b[i]) << 4) | nib(b[i + 1]));
1095 i += 2;
1096 }
1097 out
1098 }
1099
1100 const SPECCHECK_VECTORS: [(&str, &str, &str); 12] = [
1110 (
1112 "8c93255d71dcab10e8f379c26200f3c7bd5f09d9bc3068d3ef4edeb4853022b6",
1113 "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa",
1114 "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000",
1115 ),
1116 (
1118 "9bd9f44f4dcc75bd531b56b2cd280b0bb38fc1cd6d1230e14861d861de092e79",
1119 "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa",
1120 "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43a5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04",
1121 ),
1122 (
1124 "aebf3f2601a0c8c5d39cc7d8911642f740b78168218da8471772b35f9d35b9ab",
1125 "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43",
1126 "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa8c4bd45aecaca5b24fb97bc10ac27ac8751a7dfe1baff8b953ec9f5833ca260e",
1127 ),
1128 (
1130 "9bd9f44f4dcc75bd531b56b2cd280b0bb38fc1cd6d1230e14861d861de092e79",
1131 "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d",
1132 "9046a64750444938de19f227bb80485e92b83fdb4b6506c160484c016cc1852f87909e14428a7a1d62e9f22f3d3ad7802db02eb2e688b6c52fcd6648a98bd009",
1133 ),
1134 (
1136 "e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec4011eaccd55b53f56c",
1137 "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d",
1138 "160a1cb0dc9c0258cd0a7d23e94d8fa878bcb1925f2c64246b2dee1796bed5125ec6bc982a269b723e0668e540911a9a6a58921d6925e434ab10aa7940551a09",
1139 ),
1140 (
1142 "e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec4011eaccd55b53f56c",
1143 "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d",
1144 "21122a84e0b5fca4052f5b1235c80a537878b38f3142356b2c2384ebad4668b7e40bc836dac0f71076f9abe3a53f9c03c1ceeeddb658d0030494ace586687405",
1145 ),
1146 (
1148 "85e241a07d148b41e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec40",
1149 "442aad9f089ad9e14647b1ef9099a1ff4798d78589e66f28eca69c11f582a623",
1150 "e96f66be976d82e60150baecff9906684aebb1ef181f67a7189ac78ea23b6c0e547f7690a0e2ddcd04d87dbc3490dc19b3b3052f7ff0538cb68afb369ba3a514",
1151 ),
1152 (
1154 "85e241a07d148b41e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec40",
1155 "442aad9f089ad9e14647b1ef9099a1ff4798d78589e66f28eca69c11f582a623",
1156 "8ce5b96c8f26d0ab6c47958c9e68b937104cd36e13c33566acd2fe8d38aa19427e71f98a473474f2f13f06f97c20d58cc3f54b8bd0d272f42b695dd7e89a8c22",
1157 ),
1158 (
1160 "9bedc267423725d473888631ebf45988bad3db83851ee85c85e241a07d148b41",
1161 "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43",
1162 "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03be9678ac102edcd92b0210bb34d7428d12ffc5df5f37e359941266a4e35f0f",
1163 ),
1164 (
1166 "9bedc267423725d473888631ebf45988bad3db83851ee85c85e241a07d148b41",
1167 "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43",
1168 "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffca8c5b64cd208982aa38d4936621a4775aa233aa0505711d8fdcfdaa943d4908",
1169 ),
1170 (
1172 "e96b7021eb39c1a163b6da4e3093dcd3f21387da4cc4572be588fafae23c155b",
1173 "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
1174 "a9d55260f765261eb9b84e106f665e00b867287a761990d7135963ee0a7d59dca5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04",
1175 ),
1176 (
1178 "39a591f5321bbe07fd5a23dc2f39d025d74526615746727ceefd6e82ae65c06f",
1179 "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
1180 "a9d55260f765261eb9b84e106f665e00b867287a761990d7135963ee0a7d59dca5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04",
1181 ),
1182 ];
1183
1184 #[test]
1200 fn ed25519_speccheck_dual_verifier_kat() {
1201 const STD_EXPECT: [bool; 12] = [
1207 true, true, true, true, false, false, false, false, false, false, false, true,
1208 ];
1209 const ZIP215_EXPECT: [bool; 12] = [
1210 true, true, true, true, true, true, false, false, false, true, true, true,
1211 ];
1212
1213 for (i, (msg_hex, pk_hex, sig_hex)) in SPECCHECK_VECTORS.iter().enumerate() {
1214 let msg = unhex(msg_hex);
1215 let pk = unhex(pk_hex);
1216 let sig = unhex(sig_hex);
1217 assert_eq!(pk.len(), 32, "vector {i}: pubkey not 32 bytes");
1218 assert_eq!(sig.len(), 64, "vector {i}: signature not 64 bytes");
1219
1220 let std_ok = verify_ed25519_std(&pk, &msg, &sig).is_ok();
1221 let zip_ok = verify_ed25519_zip215(&pk, &msg, &sig).is_ok();
1222
1223 assert_eq!(
1224 std_ok, STD_EXPECT[i],
1225 "speccheck vector {i}: verify_ed25519_std accept={std_ok}, expected {}",
1226 STD_EXPECT[i]
1227 );
1228 assert_eq!(
1229 zip_ok, ZIP215_EXPECT[i],
1230 "speccheck vector {i}: verify_ed25519_zip215 accept={zip_ok}, expected {}",
1231 ZIP215_EXPECT[i]
1232 );
1233 }
1234
1235 for &i in &[6usize, 7usize] {
1239 let (msg_hex, pk_hex, sig_hex) = SPECCHECK_VECTORS[i];
1240 let (msg, pk, sig) = (unhex(msg_hex), unhex(pk_hex), unhex(sig_hex));
1241 assert!(
1242 verify_ed25519_std(&pk, &msg, &sig).is_err(),
1243 "SECURITY: verify_ed25519_std ACCEPTED S>=L malleability vector {i}"
1244 );
1245 }
1246
1247 {
1251 let (msg_hex, pk_hex, sig_hex) = SPECCHECK_VECTORS[4];
1252 let (msg, pk, sig) = (unhex(msg_hex), unhex(pk_hex), unhex(sig_hex));
1253 assert!(
1254 verify_ed25519_zip215(&pk, &msg, &sig).is_ok(),
1255 "vector 4: ZIP-215 (zebra) should ACCEPT the cofactored discriminator"
1256 );
1257 assert!(
1258 verify_ed25519_std(&pk, &msg, &sig).is_err(),
1259 "vector 4: standard (dalek) should REJECT the cofactored discriminator"
1260 );
1261 }
1262 }
1263
1264 #[test]
1283 fn tka_cbor_matches_go_golden() {
1284 let pubkey32 = unhex("a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf");
1286 let key_id32 = unhex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
1287 let sig64 = unhex(
1288 "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeffe0e1e2e3e4e5e6e7e8e9eaebecedeeefd0d1d2d3d4d5d6d7d8d9dadbdcdddedfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf",
1289 );
1290 let wrap32 = unhex("101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f");
1291 let rot_sig64 = unhex(
1292 "55565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f9091929394",
1293 );
1294
1295 {
1297 let sig = NodeKeySignature {
1298 sig_kind: SigKind::Direct,
1299 pubkey: pubkey32.clone(),
1300 key_id: key_id32.clone(),
1301 signature: sig64.clone(),
1302 nested: None,
1303 wrapping_pubkey: Vec::new(),
1304 };
1305 let full = sig.to_cbor(true).to_vec();
1306 let expected_full = unhex(
1307 "a40101025820a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf035820000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f045840f0f1f2f3f4f5f6f7f8f9fafbfcfdfeffe0e1e2e3e4e5e6e7e8e9eaebecedeeefd0d1d2d3d4d5d6d7d8d9dadbdcdddedfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf",
1308 );
1309 assert_eq!(
1310 full,
1311 expected_full,
1312 "GOLDEN 1 (Direct) full CBOR diverged from Go tka v1.100.0. actual: {}",
1313 hex(&full)
1314 );
1315 let expected_hash =
1316 unhex("7e9653c97d35485b37b9bf942b1861cd2f3cb0663b5bb154f1178cca72101e74");
1317 assert_eq!(
1318 sig.sig_hash().as_slice(),
1319 expected_hash.as_slice(),
1320 "GOLDEN 1 (Direct) sig_hash diverged from Go tka v1.100.0. actual: {}",
1321 hex(&sig.sig_hash())
1322 );
1323 }
1324
1325 {
1327 let sig = NodeKeySignature {
1328 sig_kind: SigKind::Credential,
1329 pubkey: pubkey32.clone(),
1330 key_id: key_id32.clone(),
1331 signature: sig64.clone(),
1332 nested: None,
1333 wrapping_pubkey: Vec::new(),
1334 };
1335 let full = sig.to_cbor(true).to_vec();
1336 let expected_full = unhex(
1337 "a40103025820a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf035820000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f045840f0f1f2f3f4f5f6f7f8f9fafbfcfdfeffe0e1e2e3e4e5e6e7e8e9eaebecedeeefd0d1d2d3d4d5d6d7d8d9dadbdcdddedfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf",
1338 );
1339 assert_eq!(
1340 full,
1341 expected_full,
1342 "GOLDEN 2 (Credential) full CBOR diverged from Go tka v1.100.0. actual: {}",
1343 hex(&full)
1344 );
1345 let expected_hash =
1346 unhex("b6070ea8bc7ae8989ef4293f5031bedaa4a499803ade99f9e2f34dc2898ac03f");
1347 assert_eq!(
1348 sig.sig_hash().as_slice(),
1349 expected_hash.as_slice(),
1350 "GOLDEN 2 (Credential) sig_hash diverged from Go tka v1.100.0. actual: {}",
1351 hex(&sig.sig_hash())
1352 );
1353 }
1354
1355 {
1365 let nested = NodeKeySignature {
1366 sig_kind: SigKind::Direct,
1367 pubkey: pubkey32.clone(),
1368 key_id: key_id32.clone(),
1369 signature: sig64.clone(),
1370 nested: None,
1371 wrapping_pubkey: wrap32.clone(),
1372 };
1373 let sig = NodeKeySignature {
1374 sig_kind: SigKind::Rotation,
1375 pubkey: wrap32.clone(),
1376 key_id: Vec::new(),
1377 signature: rot_sig64.clone(),
1378 nested: Some(alloc::boxed::Box::new(nested)),
1379 wrapping_pubkey: Vec::new(),
1380 };
1381 let full = sig.to_cbor(true).to_vec();
1382 let expected_full = unhex(
1383 "a40102025820101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f04584055565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939405a50101025820a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf035820000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f045840f0f1f2f3f4f5f6f7f8f9fafbfcfdfeffe0e1e2e3e4e5e6e7e8e9eaebecedeeefd0d1d2d3d4d5d6d7d8d9dadbdcdddedfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf065820101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f",
1384 );
1385 assert_eq!(
1386 full,
1387 expected_full,
1388 "GOLDEN 3 (Rotation) full CBOR diverged from Go tka v1.100.0. actual: {}",
1389 hex(&full)
1390 );
1391 let expected_hash =
1392 unhex("fac0a5a6781bb945369c28a0b3d3eea04e1648b60ec1a990a1ff68a9a566e6a7");
1393 assert_eq!(
1394 sig.sig_hash().as_slice(),
1395 expected_hash.as_slice(),
1396 "GOLDEN 3 (Rotation) sig_hash diverged from Go tka v1.100.0. actual: {}",
1397 hex(&sig.sig_hash())
1398 );
1399 }
1400 }
1401
1402 #[test]
1418 fn ed25519_dual_verifier_matches_go_verdicts() {
1419 const GO_STD_ACCEPT: [bool; 12] = [
1421 true, true, true, true, false, false, false, false, false, false, false, true,
1422 ];
1423 const GO_ZIP215_ACCEPT: [bool; 12] = [
1424 true, true, true, true, true, true, false, false, false, true, true, true,
1425 ];
1426
1427 for (i, (msg_hex, pk_hex, sig_hex)) in SPECCHECK_VECTORS.iter().enumerate() {
1428 let msg = unhex(msg_hex);
1429 let pk = unhex(pk_hex);
1430 let sig = unhex(sig_hex);
1431
1432 let std_ok = verify_ed25519_std(&pk, &msg, &sig).is_ok();
1433 let zip_ok = verify_ed25519_zip215(&pk, &msg, &sig).is_ok();
1434
1435 assert_eq!(
1436 std_ok, GO_STD_ACCEPT[i],
1437 "vector {i}: Rust verify_ed25519_std accept={std_ok} disagrees with Go \
1438 crypto/ed25519.Verify={}",
1439 GO_STD_ACCEPT[i]
1440 );
1441 assert_eq!(
1442 zip_ok, GO_ZIP215_ACCEPT[i],
1443 "vector {i}: Rust verify_ed25519_zip215 accept={zip_ok} disagrees with Go \
1444 ed25519consensus.Verify={}",
1445 GO_ZIP215_ACCEPT[i]
1446 );
1447 }
1448 }
1449}