1#![doc = include_str!("../README.md")]
2
3extern crate alloc;
30
31use alloc::{string::String, vec::Vec};
32
33use blake2::{Blake2s256, Digest};
34
35pub mod cbor;
36
37use cbor::Value;
38
39pub const AUM_HASH_LEN: usize = 32;
41
42const MAX_SIG_NESTING_DEPTH: usize = 16;
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54pub struct AumHash(pub [u8; AUM_HASH_LEN]);
55
56impl AumHash {
57 pub fn from_base32(text: &str) -> Option<AumHash> {
60 let decoded = base32_decode_nopad(text)?;
61 if decoded.len() != AUM_HASH_LEN {
62 return None;
63 }
64 let mut h = [0u8; AUM_HASH_LEN];
65 h.copy_from_slice(&decoded);
66 Some(AumHash(h))
67 }
68
69 pub fn to_base32(&self) -> String {
71 base32_encode_nopad(&self.0)
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78#[repr(u8)]
79pub enum AumKind {
80 Invalid = 0,
82 AddKey = 1,
84 RemoveKey = 2,
86 NoOp = 3,
88 UpdateKey = 4,
90 Checkpoint = 5,
92}
93
94impl AumKind {
95 pub fn from_u8(n: u8) -> Option<AumKind> {
99 Some(match n {
100 0 => AumKind::Invalid,
101 1 => AumKind::AddKey,
102 2 => AumKind::RemoveKey,
103 3 => AumKind::NoOp,
104 4 => AumKind::UpdateKey,
105 5 => AumKind::Checkpoint,
106 _ => return None,
107 })
108 }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub enum KeyKind {
114 Ed25519,
116}
117
118#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct Key {
122 pub kind: KeyKind,
124 pub votes: u32,
126 pub public: Vec<u8>,
128}
129
130impl Key {
131 pub fn id(&self) -> &[u8] {
133 &self.public
134 }
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139#[repr(u8)]
140pub enum SigKind {
141 Invalid = 0,
143 Direct = 1,
145 Rotation = 2,
147 Credential = 3,
149}
150
151impl SigKind {
152 fn from_u8(n: u8) -> Option<SigKind> {
153 Some(match n {
154 0 => SigKind::Invalid,
155 1 => SigKind::Direct,
156 2 => SigKind::Rotation,
157 3 => SigKind::Credential,
158 _ => return None,
159 })
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq)]
166pub struct NodeKeySignature {
167 pub sig_kind: SigKind,
169 pub pubkey: Vec<u8>,
171 pub key_id: Vec<u8>,
173 pub signature: Vec<u8>,
175 pub nested: Option<alloc::boxed::Box<NodeKeySignature>>,
177 pub wrapping_pubkey: Vec<u8>,
179}
180
181#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
183pub enum TkaError {
184 #[error("TKA decode error: {0}")]
186 Decode(&'static str),
187 #[error("TKA signature verification failed")]
189 BadSignature,
190 #[error("TKA authorizing key is not trusted")]
192 UntrustedKey,
193 #[error("a credential signature cannot authorize a node")]
195 CredentialCannotAuthorize,
196 #[error("signature does not cover this node key")]
198 NodeKeyMismatch,
199 #[error("AUM parent hash does not match the current chain head")]
202 BadParent,
203 #[error("AUM key-state update is invalid (key already exists, or no such key)")]
206 BadKeyState,
207 #[error("AUM chain is empty or has no valid genesis")]
210 BadChain,
211}
212
213impl NodeKeySignature {
214 fn sig_hash(&self) -> [u8; AUM_HASH_LEN] {
218 let v = self.to_cbor(false);
219 blake2s_256(&v.to_vec())
220 }
221
222 fn to_cbor(&self, include_signature: bool) -> Value {
225 cbor::int_map([
226 (1, Some(Value::Uint(self.sig_kind as u8 as u64))),
227 (2, nonempty_bytes(&self.pubkey)),
228 (3, nonempty_bytes(&self.key_id)),
229 (
230 4,
231 if include_signature {
232 nonempty_bytes(&self.signature)
233 } else {
234 None
235 },
236 ),
237 (5, self.nested.as_ref().map(|n| n.to_cbor(true))),
238 (6, nonempty_bytes(&self.wrapping_pubkey)),
239 ])
240 }
241
242 fn authorizing_key_id(&self) -> Result<&[u8], TkaError> {
245 match self.sig_kind {
246 SigKind::Rotation => self
247 .nested
248 .as_ref()
249 .ok_or(TkaError::Decode("rotation signature missing nested"))?
250 .authorizing_key_id(),
251 SigKind::Direct | SigKind::Credential => Ok(&self.key_id),
252 SigKind::Invalid => Err(TkaError::Decode("invalid signature kind")),
253 }
254 }
255
256 fn verify_signature(&self, node_key: &[u8], verification_key: &Key) -> Result<(), TkaError> {
259 if self.sig_kind != SigKind::Credential && self.pubkey != node_key {
261 return Err(TkaError::NodeKeyMismatch);
262 }
263
264 let sig_hash = self.sig_hash();
265
266 match self.sig_kind {
267 SigKind::Rotation => {
268 let nested = self
269 .nested
270 .as_ref()
271 .ok_or(TkaError::Decode("rotation signature missing nested"))?;
272 let verify_pub = &nested.wrapping_pubkey;
275 if verify_pub.len() != 32 {
276 return Err(TkaError::Decode("wrapping pubkey wrong length"));
277 }
278 verify_ed25519_std(verify_pub, &sig_hash, &self.signature)?;
279 if nested.sig_kind == SigKind::Credential && nested.pubkey != *verify_pub {
285 return Err(TkaError::NodeKeyMismatch);
286 }
287 nested.verify_signature(verify_pub, verification_key)
289 }
290 SigKind::Direct | SigKind::Credential => {
291 if self.nested.is_some() {
292 return Err(TkaError::Decode("direct/credential signature has nested"));
293 }
294 if verification_key.kind != KeyKind::Ed25519 || verification_key.public.len() != 32
295 {
296 return Err(TkaError::Decode("verification key not ed25519"));
297 }
298 verify_ed25519_zip215(&verification_key.public, &sig_hash, &self.signature)
301 }
302 SigKind::Invalid => Err(TkaError::Decode("invalid signature kind")),
303 }
304 }
305}
306
307#[derive(Debug, Clone, Default, PartialEq, Eq)]
310pub struct State {
311 pub keys: Vec<Key>,
313}
314
315impl State {
316 pub fn get_key(&self, key_id: &[u8]) -> Option<&Key> {
318 self.keys.iter().find(|k| k.id() == key_id)
319 }
320}
321
322#[derive(Debug, Clone)]
326pub struct Authority {
327 head: AumHash,
328 state: State,
329}
330
331impl Authority {
332 pub fn from_state(head: AumHash, state: State) -> Authority {
335 Authority { head, state }
336 }
337
338 pub fn head(&self) -> AumHash {
340 self.head
341 }
342
343 pub fn state(&self) -> &State {
345 &self.state
346 }
347
348 pub fn head_matches(&self, head: &AumHash) -> bool {
351 &self.head == head
352 }
353
354 pub fn node_key_authorized(
368 &self,
369 node_key: &[u8],
370 signature_cbor: &[u8],
371 ) -> Result<(), TkaError> {
372 let sig = decode_node_key_signature(signature_cbor)?;
373 if sig.sig_kind == SigKind::Credential {
375 return Err(TkaError::CredentialCannotAuthorize);
376 }
377 let key_id = sig.authorizing_key_id()?;
378 let key = self.state.get_key(key_id).ok_or(TkaError::UntrustedKey)?;
379 sig.verify_signature(node_key, key)
380 }
381}
382
383pub fn aum_hash(canonical_cbor: &[u8]) -> AumHash {
386 AumHash(blake2s_256(canonical_cbor))
387}
388
389#[derive(Debug, Clone, PartialEq, Eq)]
395pub struct AumKey {
396 pub kind: KeyKind,
398 pub votes: u32,
400 pub public: Vec<u8>,
402 pub meta: Vec<(alloc::string::String, alloc::string::String)>,
404}
405
406impl AumKey {
407 pub fn id(&self) -> &[u8] {
409 &self.public
410 }
411
412 pub fn to_key(&self) -> Key {
415 Key {
416 kind: self.kind,
417 votes: self.votes,
418 public: self.public.clone(),
419 }
420 }
421
422 fn kind_u8(&self) -> u8 {
423 match self.kind {
424 KeyKind::Ed25519 => 1,
425 }
426 }
427
428 fn to_cbor(&self) -> Value {
429 cbor::int_map([
430 (1, Some(Value::Uint(self.kind_u8() as u64))),
431 (2, Some(Value::Uint(self.votes as u64))),
432 (3, Some(Value::Bytes(self.public.clone()))),
433 (12, meta_to_cbor(&self.meta)),
434 ])
435 }
436}
437
438#[derive(Debug, Clone, PartialEq, Eq, Default)]
444pub struct AumState {
445 pub last_aum_hash: Option<AumHash>,
447 pub disablement_values: Vec<Vec<u8>>,
449 pub keys: Vec<AumKey>,
451 pub state_id1: u64,
453 pub state_id2: u64,
455}
456
457impl AumState {
458 fn to_cbor(&self) -> Value {
459 cbor::int_map([
460 (
461 1,
462 Some(match &self.last_aum_hash {
463 Some(h) => Value::Bytes(h.0.to_vec()),
464 None => Value::Null,
465 }),
466 ),
467 (
468 2,
469 Some(Value::Array(
470 self.disablement_values
471 .iter()
472 .map(|d| Value::Bytes(d.clone()))
473 .collect(),
474 )),
475 ),
476 (
477 3,
478 Some(Value::Array(
479 self.keys.iter().map(AumKey::to_cbor).collect(),
480 )),
481 ),
482 (
483 4,
484 (self.state_id1 != 0).then_some(Value::Uint(self.state_id1)),
485 ),
486 (
487 5,
488 (self.state_id2 != 0).then_some(Value::Uint(self.state_id2)),
489 ),
490 ])
491 }
492}
493
494#[derive(Debug, Clone, PartialEq, Eq)]
497pub struct AumSignature {
498 pub key_id: Vec<u8>,
500 pub signature: Vec<u8>,
502}
503
504impl AumSignature {
505 fn to_cbor(&self) -> Value {
506 cbor::int_map([
510 (1, Some(bytes_or_null(&self.key_id))),
511 (2, Some(bytes_or_null(&self.signature))),
512 ])
513 }
514}
515
516#[derive(Debug, Clone, PartialEq, Eq)]
526pub struct Aum {
527 pub message_kind: AumKind,
529 pub prev_aum_hash: Option<AumHash>,
531 pub key: Option<AumKey>,
533 pub key_id: Vec<u8>,
535 pub state: Option<AumState>,
537 pub votes: Option<u32>,
539 pub meta: Vec<(alloc::string::String, alloc::string::String)>,
541 pub signatures: Vec<AumSignature>,
543}
544
545impl Aum {
546 fn message_kind_u8(&self) -> u8 {
547 self.message_kind as u8
548 }
549
550 fn to_cbor(&self, include_signatures: bool) -> Value {
553 let signatures = if include_signatures && !self.signatures.is_empty() {
554 Some(Value::Array(
555 self.signatures.iter().map(AumSignature::to_cbor).collect(),
556 ))
557 } else {
558 None
559 };
560 cbor::int_map([
561 (1, Some(Value::Uint(self.message_kind_u8() as u64))),
563 (
565 2,
566 Some(match &self.prev_aum_hash {
567 Some(h) => Value::Bytes(h.0.to_vec()),
568 None => Value::Null,
569 }),
570 ),
571 (3, self.key.as_ref().map(AumKey::to_cbor)),
572 (4, nonempty_bytes(&self.key_id)),
573 (5, self.state.as_ref().map(AumState::to_cbor)),
574 (6, self.votes.map(|v| Value::Uint(v as u64))),
575 (7, meta_to_cbor(&self.meta)),
576 (23, signatures),
577 ])
578 }
579
580 pub fn serialize(&self) -> Vec<u8> {
582 self.to_cbor(true).to_vec()
583 }
584
585 pub fn hash(&self) -> AumHash {
587 AumHash(blake2s_256(&self.serialize()))
588 }
589
590 pub fn sig_hash(&self) -> [u8; AUM_HASH_LEN] {
593 blake2s_256(&self.to_cbor(false).to_vec())
594 }
595}
596
597fn meta_to_cbor(meta: &[(alloc::string::String, alloc::string::String)]) -> Option<Value> {
600 if meta.is_empty() {
601 return None;
602 }
603 Some(Value::TextMap(
604 meta.iter()
605 .map(|(k, v)| (k.as_bytes().to_vec(), Value::Text(v.as_bytes().to_vec())))
606 .collect(),
607 ))
608}
609
610fn blake2s_256(data: &[u8]) -> [u8; AUM_HASH_LEN] {
611 let mut hasher = Blake2s256::new();
612 hasher.update(data);
613 let out = hasher.finalize();
614 let mut h = [0u8; AUM_HASH_LEN];
615 h.copy_from_slice(&out);
616 h
617}
618
619fn nonempty_bytes(b: &[u8]) -> Option<Value> {
621 if b.is_empty() {
622 None
623 } else {
624 Some(Value::Bytes(b.to_vec()))
625 }
626}
627
628fn bytes_or_null(b: &[u8]) -> Value {
632 if b.is_empty() {
633 Value::Null
634 } else {
635 Value::Bytes(b.to_vec())
636 }
637}
638
639#[derive(Debug, Clone, Default)]
649struct ReplayState {
650 keys: Vec<AumKey>,
651 last_aum_hash: Option<AumHash>,
652 state_id: Option<(u64, u64)>,
656}
657
658impl ReplayState {
659 fn get_key(&self, key_id: &[u8]) -> Option<&AumKey> {
660 self.keys.iter().find(|k| k.id() == key_id)
661 }
662
663 fn find_key_index(&self, key_id: &[u8]) -> Option<usize> {
664 self.keys.iter().position(|k| k.id() == key_id)
665 }
666
667 fn weight(&self, aum: &Aum) -> u64 {
671 let mut seen: Vec<&[u8]> = Vec::new();
672 let mut weight: u64 = 0;
673 for sig in &aum.signatures {
674 let id = sig.key_id.as_slice();
675 if seen.contains(&id) {
676 continue;
677 }
678 if let Some(key) = self.get_key(id) {
679 weight += key.votes as u64;
680 seen.push(id);
681 }
682 }
683 weight
684 }
685
686 fn apply_verified_aum(&mut self, aum: &Aum) -> Result<(), TkaError> {
697 match &self.last_aum_hash {
698 Some(head) => match &aum.prev_aum_hash {
700 Some(prev) if prev == head => {}
701 _ => return Err(TkaError::BadParent),
702 },
703 None => {
705 if aum.prev_aum_hash.is_some() {
706 return Err(TkaError::BadParent);
707 }
708 if !matches!(
709 aum.message_kind,
710 AumKind::NoOp | AumKind::AddKey | AumKind::Checkpoint
711 ) {
712 return Err(TkaError::BadChain);
713 }
714 }
715 }
716
717 match aum.message_kind {
718 AumKind::NoOp | AumKind::Invalid => {
719 }
722 AumKind::Checkpoint => {
723 let state = aum
728 .state
729 .as_ref()
730 .ok_or(TkaError::Decode("checkpoint AUM missing state"))?;
731 let incoming = (state.state_id1, state.state_id2);
732 match self.state_id {
733 Some(existing) if existing != incoming => {
734 return Err(TkaError::BadKeyState);
735 }
736 _ => self.state_id = Some(incoming),
737 }
738 self.keys = state.keys.clone();
739 }
740 AumKind::AddKey => {
741 let key = aum
742 .key
743 .as_ref()
744 .ok_or(TkaError::Decode("AddKey AUM missing key"))?;
745 if self.get_key(key.id()).is_some() {
746 return Err(TkaError::BadKeyState);
747 }
748 self.keys.push(key.clone());
749 }
750 AumKind::UpdateKey => {
751 let idx = self
752 .find_key_index(&aum.key_id)
753 .ok_or(TkaError::BadKeyState)?;
754 if let Some(votes) = aum.votes {
755 self.keys[idx].votes = votes;
756 }
757 if !aum.meta.is_empty() {
758 self.keys[idx].meta = aum.meta.clone();
759 }
760 }
761 AumKind::RemoveKey => {
762 let idx = self
763 .find_key_index(&aum.key_id)
764 .ok_or(TkaError::BadKeyState)?;
765 self.keys.remove(idx);
766 }
767 }
768
769 self.last_aum_hash = Some(aum.hash());
770 Ok(())
771 }
772
773 fn to_state(&self) -> State {
775 State {
776 keys: self.keys.iter().map(AumKey::to_key).collect(),
777 }
778 }
779}
780
781fn pick_next_aum<'a>(state: &ReplayState, candidates: &'a [Aum]) -> &'a Aum {
792 debug_assert!(!candidates.is_empty(), "pick_next_aum needs candidates");
793 let mut best = &candidates[0];
794 let mut best_weight = state.weight(best);
795 let mut best_hash = best.hash();
796 for cand in &candidates[1..] {
797 let w = state.weight(cand);
798 let h = cand.hash();
799 let better = if w != best_weight {
801 w > best_weight
802 } else if (cand.message_kind == AumKind::RemoveKey)
803 != (best.message_kind == AumKind::RemoveKey)
804 {
805 cand.message_kind == AumKind::RemoveKey
807 } else {
808 h.0 < best_hash.0
810 };
811 if better {
812 best = cand;
813 best_weight = w;
814 best_hash = h;
815 }
816 }
817 best
818}
819
820impl Authority {
821 pub fn from_chain(aums: &[Aum]) -> Result<Authority, TkaError> {
840 let last = aums.last().ok_or(TkaError::BadChain)?;
841 let head = last.hash();
842 let mut state = ReplayState::default();
843 for aum in aums {
844 state.apply_verified_aum(aum)?;
845 }
846 Ok(Authority {
847 head,
848 state: state.to_state(),
849 })
850 }
851
852 pub fn from_forked_chain(prefix: &[Aum], branches: &[&[Aum]]) -> Result<Authority, TkaError> {
873 if branches.is_empty() || branches.iter().any(|b| b.len() != 1) {
876 return Err(TkaError::BadChain);
877 }
878 let mut state = ReplayState::default();
879 for aum in prefix {
880 state.apply_verified_aum(aum)?;
881 }
882 let heads: Vec<Aum> = branches.iter().map(|b| b[0].clone()).collect();
885 let winner_head = pick_next_aum(&state, &heads).hash();
886 let winner = branches
887 .iter()
888 .find(|b| b[0].hash() == winner_head)
889 .ok_or(TkaError::BadChain)?;
890 state.apply_verified_aum(&winner[0])?;
891 Ok(Authority {
892 head: winner[0].hash(),
893 state: state.to_state(),
894 })
895 }
896}
897
898fn verify_ed25519_std(public: &[u8], msg: &[u8], sig: &[u8]) -> Result<(), TkaError> {
900 use ed25519_dalek::{Signature, Verifier, VerifyingKey};
901 let pk: [u8; 32] = public
902 .try_into()
903 .map_err(|_| TkaError::Decode("bad pubkey len"))?;
904 let vk = VerifyingKey::from_bytes(&pk).map_err(|_| TkaError::Decode("bad ed25519 pubkey"))?;
905 let sig: [u8; 64] = sig
906 .try_into()
907 .map_err(|_| TkaError::Decode("bad sig len"))?;
908 vk.verify(msg, &Signature::from_bytes(&sig))
909 .map_err(|_| TkaError::BadSignature)
910}
911
912fn verify_ed25519_zip215(public: &[u8], msg: &[u8], sig: &[u8]) -> Result<(), TkaError> {
914 let pk: [u8; 32] = public
915 .try_into()
916 .map_err(|_| TkaError::Decode("bad pubkey len"))?;
917 let vk = ed25519_zebra::VerificationKey::try_from(pk)
918 .map_err(|_| TkaError::Decode("bad ed25519 pubkey"))?;
919 let sig_bytes: [u8; 64] = sig
920 .try_into()
921 .map_err(|_| TkaError::Decode("bad sig len"))?;
922 let sig = ed25519_zebra::Signature::from(sig_bytes);
923 vk.verify(&sig, msg).map_err(|_| TkaError::BadSignature)
924}
925
926fn decode_node_key_signature(buf: &[u8]) -> Result<NodeKeySignature, TkaError> {
929 let (val, rest) = decode_value(buf, 0)?;
930 if !rest.is_empty() {
931 return Err(TkaError::Decode("trailing bytes after signature"));
932 }
933 node_key_signature_from_value(val, 0)
934}
935
936fn node_key_signature_from_value(val: Value, depth: usize) -> Result<NodeKeySignature, TkaError> {
937 if depth > MAX_SIG_NESTING_DEPTH {
938 return Err(TkaError::Decode("nested signature too deep"));
939 }
940 let Value::IntMap(entries) = val else {
941 return Err(TkaError::Decode("signature is not an int-keyed map"));
942 };
943 let mut sig_kind = None;
944 let mut pubkey = Vec::new();
945 let mut key_id = Vec::new();
946 let mut signature = Vec::new();
947 let mut nested = None;
948 let mut wrapping_pubkey = Vec::new();
949
950 for (k, v) in entries {
951 match k {
952 1 => {
953 let Value::Uint(n) = v else {
954 return Err(TkaError::Decode("sig kind not uint"));
955 };
956 sig_kind = Some(
957 SigKind::from_u8(
958 u8::try_from(n).map_err(|_| TkaError::Decode("sig kind range"))?,
959 )
960 .ok_or(TkaError::Decode("unknown sig kind"))?,
961 );
962 }
963 2 => pubkey = expect_bytes(v)?,
964 3 => key_id = expect_bytes(v)?,
965 4 => signature = expect_bytes(v)?,
966 5 => {
967 nested = Some(alloc::boxed::Box::new(node_key_signature_from_value(
968 v,
969 depth + 1,
970 )?))
971 }
972 6 => wrapping_pubkey = expect_bytes(v)?,
973 _ => return Err(TkaError::Decode("unknown signature field")),
974 }
975 }
976
977 Ok(NodeKeySignature {
978 sig_kind: sig_kind.ok_or(TkaError::Decode("signature missing kind"))?,
979 pubkey,
980 key_id,
981 signature,
982 nested,
983 wrapping_pubkey,
984 })
985}
986
987fn expect_bytes(v: Value) -> Result<Vec<u8>, TkaError> {
988 match v {
989 Value::Bytes(b) => Ok(b),
990 _ => Err(TkaError::Decode("expected byte string")),
991 }
992}
993
994fn decode_value(buf: &[u8], depth: usize) -> Result<(Value, &[u8]), TkaError> {
997 if depth > MAX_SIG_NESTING_DEPTH {
1000 return Err(TkaError::Decode("nested signature too deep"));
1001 }
1002 let (major, arg, rest) = decode_head(buf)?;
1003 match major {
1004 0 => Ok((Value::Uint(arg), rest)),
1005 2 => {
1006 let len = arg as usize;
1007 if rest.len() < len {
1008 return Err(TkaError::Decode("byte string truncated"));
1009 }
1010 Ok((Value::Bytes(rest[..len].to_vec()), &rest[len..]))
1011 }
1012 3 => {
1013 let len = arg as usize;
1014 if rest.len() < len {
1015 return Err(TkaError::Decode("text string truncated"));
1016 }
1017 Ok((Value::Text(rest[..len].to_vec()), &rest[len..]))
1018 }
1019 4 => {
1020 let mut items = Vec::new();
1021 let mut cur = rest;
1022 for _ in 0..arg {
1023 let (v, next) = decode_value(cur, depth + 1)?;
1024 items.push(v);
1025 cur = next;
1026 }
1027 Ok((Value::Array(items), cur))
1028 }
1029 5 => {
1030 let mut entries: Vec<(u64, Value)> = Vec::new();
1031 let mut cur = rest;
1032 for _ in 0..arg {
1033 let (k, next) = decode_head(cur).and_then(|(m, a, r)| {
1034 if m == 0 {
1035 Ok((a, r))
1036 } else {
1037 Err(TkaError::Decode("map key not uint"))
1038 }
1039 })?;
1040 if entries.iter().any(|(existing, _)| *existing == k) {
1043 return Err(TkaError::Decode("duplicate map key"));
1044 }
1045 let (v, next2) = decode_value(next, depth + 1)?;
1046 entries.push((k, v));
1047 cur = next2;
1048 }
1049 Ok((Value::IntMap(entries), cur))
1050 }
1051 _ => Err(TkaError::Decode("unsupported CBOR major type")),
1052 }
1053}
1054
1055fn decode_head(buf: &[u8]) -> Result<(u8, u64, &[u8]), TkaError> {
1057 let first = *buf.first().ok_or(TkaError::Decode("empty CBOR"))?;
1058 let major = first >> 5;
1059 let info = first & 0x1f;
1060 let rest = &buf[1..];
1061 let (arg, rest) = match info {
1062 n @ 0..=23 => (n as u64, rest),
1063 24 => {
1064 let b = *rest.first().ok_or(TkaError::Decode("truncated u8"))?;
1065 (b as u64, &rest[1..])
1066 }
1067 25 => {
1068 if rest.len() < 2 {
1069 return Err(TkaError::Decode("truncated u16"));
1070 }
1071 (u16::from_be_bytes([rest[0], rest[1]]) as u64, &rest[2..])
1072 }
1073 26 => {
1074 if rest.len() < 4 {
1075 return Err(TkaError::Decode("truncated u32"));
1076 }
1077 (
1078 u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]) as u64,
1079 &rest[4..],
1080 )
1081 }
1082 27 => {
1083 if rest.len() < 8 {
1084 return Err(TkaError::Decode("truncated u64"));
1085 }
1086 let mut b = [0u8; 8];
1087 b.copy_from_slice(&rest[..8]);
1088 (u64::from_be_bytes(b), &rest[8..])
1089 }
1090 _ => return Err(TkaError::Decode("indefinite/reserved CBOR length")),
1091 };
1092 Ok((major, arg, rest))
1093}
1094
1095const BASE32_ALPHABET: &[u8; 32] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
1098
1099fn base32_encode_nopad(data: &[u8]) -> String {
1100 let mut out = String::new();
1101 let mut buffer: u32 = 0;
1102 let mut bits: u32 = 0;
1103 for &b in data {
1104 buffer = (buffer << 8) | b as u32;
1105 bits += 8;
1106 while bits >= 5 {
1107 bits -= 5;
1108 let idx = ((buffer >> bits) & 0x1f) as usize;
1109 out.push(BASE32_ALPHABET[idx] as char);
1110 }
1111 }
1112 if bits > 0 {
1113 let idx = ((buffer << (5 - bits)) & 0x1f) as usize;
1114 out.push(BASE32_ALPHABET[idx] as char);
1115 }
1116 out
1117}
1118
1119fn base32_decode_nopad(text: &str) -> Option<Vec<u8>> {
1120 let mut buffer: u32 = 0;
1121 let mut bits: u32 = 0;
1122 let mut out = Vec::new();
1123 for c in text.chars() {
1124 let val = match c {
1125 'A'..='Z' => c as u32 - 'A' as u32,
1126 '2'..='7' => c as u32 - '2' as u32 + 26,
1127 _ => return None,
1128 };
1129 buffer = (buffer << 5) | val;
1130 bits += 5;
1131 if bits >= 8 {
1132 bits -= 8;
1133 out.push((buffer >> bits) as u8);
1134 }
1135 }
1136 Some(out)
1137}
1138
1139#[cfg(test)]
1140mod tests {
1141 use super::*;
1142
1143 #[test]
1144 fn base32_roundtrip_32_bytes() {
1145 let h = AumHash([0xABu8; 32]);
1146 let text = h.to_base32();
1147 let back = AumHash::from_base32(&text).unwrap();
1148 assert_eq!(h, back);
1149 }
1150
1151 #[test]
1152 fn base32_rejects_wrong_length() {
1153 assert!(AumHash::from_base32("AAAA").is_none());
1155 assert!(AumHash::from_base32("aaaa").is_none());
1157 }
1158
1159 #[test]
1160 fn base32_matches_known_vector() {
1161 assert_eq!(base32_encode_nopad(b"foobar"), "MZXW6YTBOI");
1163 assert_eq!(base32_decode_nopad("MZXW6YTBOI").unwrap(), b"foobar");
1164 }
1165
1166 #[test]
1167 fn credential_signature_cannot_authorize() {
1168 let auth = Authority::from_state(AumHash([0; 32]), State::default());
1169 let sig = NodeKeySignature {
1170 sig_kind: SigKind::Credential,
1171 pubkey: alloc::vec![1, 2, 3],
1172 key_id: alloc::vec![4, 5, 6],
1173 signature: alloc::vec![0; 64],
1174 nested: None,
1175 wrapping_pubkey: Vec::new(),
1176 };
1177 let cbor = sig.to_cbor(true).to_vec();
1178 let err = auth.node_key_authorized(&[1, 2, 3], &cbor).unwrap_err();
1179 assert_eq!(err, TkaError::CredentialCannotAuthorize);
1180 }
1181
1182 #[test]
1183 fn untrusted_key_denied() {
1184 let auth = Authority::from_state(AumHash([0; 32]), State::default());
1186 let sig = NodeKeySignature {
1187 sig_kind: SigKind::Direct,
1188 pubkey: alloc::vec![9; 32],
1189 key_id: alloc::vec![7; 32],
1190 signature: alloc::vec![0; 64],
1191 nested: None,
1192 wrapping_pubkey: Vec::new(),
1193 };
1194 let cbor = sig.to_cbor(true).to_vec();
1195 let err = auth.node_key_authorized(&[9; 32], &cbor).unwrap_err();
1196 assert_eq!(err, TkaError::UntrustedKey);
1197 }
1198
1199 #[test]
1200 fn direct_signature_verifies_end_to_end() {
1201 use ed25519_dalek::{Signer, SigningKey};
1202
1203 let signing = SigningKey::from_bytes(&[42u8; 32]);
1205 let trusted_pub = signing.verifying_key().to_bytes().to_vec();
1206 let node_key = alloc::vec![7u8; 32];
1207
1208 let trusted = Key {
1209 kind: KeyKind::Ed25519,
1210 votes: 1,
1211 public: trusted_pub.clone(),
1212 };
1213 let auth = Authority::from_state(
1214 AumHash([0; 32]),
1215 State {
1216 keys: alloc::vec![trusted],
1217 },
1218 );
1219
1220 let mut sig = NodeKeySignature {
1222 sig_kind: SigKind::Direct,
1223 pubkey: node_key.clone(),
1224 key_id: trusted_pub.clone(),
1225 signature: Vec::new(),
1226 nested: None,
1227 wrapping_pubkey: Vec::new(),
1228 };
1229 let sig_hash = sig.sig_hash();
1230 sig.signature = signing.sign(&sig_hash).to_bytes().to_vec();
1233
1234 let cbor = sig.to_cbor(true).to_vec();
1235 assert!(auth.node_key_authorized(&node_key, &cbor).is_ok());
1236
1237 let other = alloc::vec![8u8; 32];
1239 assert_eq!(
1240 auth.node_key_authorized(&other, &cbor).unwrap_err(),
1241 TkaError::NodeKeyMismatch
1242 );
1243 }
1244
1245 #[test]
1246 fn tampered_signature_denied() {
1247 use ed25519_dalek::{Signer, SigningKey};
1248
1249 let signing = SigningKey::from_bytes(&[42u8; 32]);
1250 let trusted_pub = signing.verifying_key().to_bytes().to_vec();
1251 let node_key = alloc::vec![7u8; 32];
1252 let auth = Authority::from_state(
1253 AumHash([0; 32]),
1254 State {
1255 keys: alloc::vec![Key {
1256 kind: KeyKind::Ed25519,
1257 votes: 1,
1258 public: trusted_pub.clone(),
1259 }],
1260 },
1261 );
1262 let mut sig = NodeKeySignature {
1263 sig_kind: SigKind::Direct,
1264 pubkey: node_key.clone(),
1265 key_id: trusted_pub,
1266 signature: Vec::new(),
1267 nested: None,
1268 wrapping_pubkey: Vec::new(),
1269 };
1270 let sig_hash = sig.sig_hash();
1271 let mut sigbytes = signing.sign(&sig_hash).to_bytes();
1272 sigbytes[0] ^= 0xff; sig.signature = sigbytes.to_vec();
1274
1275 let cbor = sig.to_cbor(true).to_vec();
1276 assert_eq!(
1277 auth.node_key_authorized(&node_key, &cbor).unwrap_err(),
1278 TkaError::BadSignature
1279 );
1280 }
1281
1282 #[test]
1283 fn head_matches_check() {
1284 let h = AumHash([5u8; 32]);
1285 let auth = Authority::from_state(h, State::default());
1286 assert!(auth.head_matches(&h));
1287 assert!(!auth.head_matches(&AumHash([6u8; 32])));
1288 }
1289
1290 #[test]
1293 fn deeply_nested_signature_rejected_without_overflow() {
1294 let mut sig = NodeKeySignature {
1298 sig_kind: SigKind::Direct,
1299 pubkey: alloc::vec![1u8; 32],
1300 key_id: alloc::vec![2u8; 32],
1301 signature: alloc::vec![3u8; 64],
1302 nested: None,
1303 wrapping_pubkey: Vec::new(),
1304 };
1305 for _ in 0..(MAX_SIG_NESTING_DEPTH + 8) {
1306 sig = NodeKeySignature {
1307 sig_kind: SigKind::Rotation,
1308 pubkey: alloc::vec![1u8; 32],
1309 key_id: Vec::new(),
1310 signature: alloc::vec![3u8; 64],
1311 nested: Some(alloc::boxed::Box::new(sig)),
1312 wrapping_pubkey: alloc::vec![1u8; 32],
1313 };
1314 }
1315 let cbor = sig.to_cbor(true).to_vec();
1316 let err = decode_node_key_signature(&cbor).unwrap_err();
1317 assert_eq!(err, TkaError::Decode("nested signature too deep"));
1318 }
1319
1320 #[test]
1323 fn duplicate_map_key_rejected() {
1324 let blob = [0xa2u8, 0x01, 0x00, 0x01, 0x01];
1326 let err = decode_node_key_signature(&blob).unwrap_err();
1327 assert_eq!(err, TkaError::Decode("duplicate map key"));
1328 }
1329
1330 #[test]
1344 fn rotation_chain_verifies_end_to_end() {
1345 use ed25519_dalek::{Signer, SigningKey};
1346
1347 let trusted = SigningKey::from_bytes(&[7u8; 32]);
1349 let trusted_pub = trusted.verifying_key().to_bytes().to_vec();
1350
1351 let wrapping = SigningKey::from_bytes(&[9u8; 32]);
1354 let wrapping_pub = wrapping.verifying_key().to_bytes().to_vec();
1355
1356 let node_key = alloc::vec![5u8; 32];
1357
1358 let auth = Authority::from_state(
1359 AumHash([0; 32]),
1360 State {
1361 keys: alloc::vec![Key {
1362 kind: KeyKind::Ed25519,
1363 votes: 1,
1364 public: trusted_pub.clone(),
1365 }],
1366 },
1367 );
1368
1369 let mut inner = NodeKeySignature {
1372 sig_kind: SigKind::Direct,
1373 pubkey: wrapping_pub.clone(),
1374 key_id: trusted_pub.clone(),
1375 signature: Vec::new(),
1376 nested: None,
1377 wrapping_pubkey: wrapping_pub.clone(),
1378 };
1379 let inner_hash = inner.sig_hash();
1380 inner.signature = trusted.sign(&inner_hash).to_bytes().to_vec();
1381
1382 let mut outer = NodeKeySignature {
1384 sig_kind: SigKind::Rotation,
1385 pubkey: node_key.clone(),
1386 key_id: Vec::new(),
1387 signature: Vec::new(),
1388 nested: Some(alloc::boxed::Box::new(inner)),
1389 wrapping_pubkey: Vec::new(),
1390 };
1391 let outer_hash = outer.sig_hash();
1392 outer.signature = wrapping.sign(&outer_hash).to_bytes().to_vec();
1393
1394 let cbor = outer.to_cbor(true).to_vec();
1395 assert!(auth.node_key_authorized(&node_key, &cbor).is_ok());
1396
1397 let mut tampered = outer.clone();
1399 let mut sb = tampered.signature.clone();
1400 sb[0] ^= 0xff;
1401 tampered.signature = sb;
1402 let cbor_bad = tampered.to_cbor(true).to_vec();
1403 assert_eq!(
1404 auth.node_key_authorized(&node_key, &cbor_bad).unwrap_err(),
1405 TkaError::BadSignature
1406 );
1407 }
1408
1409 #[test]
1412 fn rotation_nested_credential_pubkey_bind() {
1413 use ed25519_dalek::{Signer, SigningKey};
1414
1415 let trusted = SigningKey::from_bytes(&[11u8; 32]);
1416 let trusted_pub = trusted.verifying_key().to_bytes().to_vec();
1417 let wrapping = SigningKey::from_bytes(&[13u8; 32]);
1418 let wrapping_pub = wrapping.verifying_key().to_bytes().to_vec();
1419 let node_key = alloc::vec![6u8; 32];
1420
1421 let auth = Authority::from_state(
1422 AumHash([0; 32]),
1423 State {
1424 keys: alloc::vec![Key {
1425 kind: KeyKind::Ed25519,
1426 votes: 1,
1427 public: trusted_pub.clone(),
1428 }],
1429 },
1430 );
1431
1432 let build = |cred_pubkey: Vec<u8>| -> Vec<u8> {
1434 let mut inner = NodeKeySignature {
1435 sig_kind: SigKind::Credential,
1436 pubkey: cred_pubkey,
1437 key_id: trusted_pub.clone(),
1438 signature: Vec::new(),
1439 nested: None,
1440 wrapping_pubkey: wrapping_pub.clone(),
1441 };
1442 let inner_hash = inner.sig_hash();
1443 inner.signature = trusted.sign(&inner_hash).to_bytes().to_vec();
1444
1445 let mut outer = NodeKeySignature {
1446 sig_kind: SigKind::Rotation,
1447 pubkey: node_key.clone(),
1448 key_id: Vec::new(),
1449 signature: Vec::new(),
1450 nested: Some(alloc::boxed::Box::new(inner)),
1451 wrapping_pubkey: Vec::new(),
1452 };
1453 let outer_hash = outer.sig_hash();
1454 outer.signature = wrapping.sign(&outer_hash).to_bytes().to_vec();
1455 outer.to_cbor(true).to_vec()
1456 };
1457
1458 let cbor_ok = build(wrapping_pub.clone());
1460 assert!(auth.node_key_authorized(&node_key, &cbor_ok).is_ok());
1461
1462 let cbor_bad = build(alloc::vec![0xaau8; 32]);
1465 assert_eq!(
1466 auth.node_key_authorized(&node_key, &cbor_bad).unwrap_err(),
1467 TkaError::NodeKeyMismatch
1468 );
1469 }
1470
1471 fn hex(bytes: &[u8]) -> String {
1475 let mut s = String::new();
1476 for b in bytes {
1477 s.push_str(&alloc::format!("{b:02x}"));
1478 }
1479 s
1480 }
1481
1482 #[test]
1500 fn node_key_signature_cbor_frozen_vector() {
1501 let pubkey: Vec<u8> = (0u8..32).collect();
1503 let key_id: Vec<u8> = (32u8..64).collect();
1504 let signature: Vec<u8> = (64u8..128).collect();
1505
1506 let sig = NodeKeySignature {
1507 sig_kind: SigKind::Direct,
1508 pubkey,
1509 key_id,
1510 signature,
1511 nested: None,
1512 wrapping_pubkey: Vec::new(), };
1514
1515 let full = sig.to_cbor(true).to_vec();
1517 const EXPECTED_FULL: &[u8] = &[
1518 0xa4, 0x01, 0x01, 0x02, 0x58, 0x20, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1519 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
1520 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x03, 0x58, 0x20, 0x20,
1521 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e,
1522 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
1523 0x3d, 0x3e, 0x3f, 0x04, 0x58, 0x40, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
1524 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
1525 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63,
1526 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71,
1527 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
1528 ];
1529 assert_eq!(
1530 full,
1531 EXPECTED_FULL,
1532 "full CBOR serialization changed (canonical-CBOR encoding drift). actual: {}",
1533 hex(&full)
1534 );
1535
1536 let preimage = sig.to_cbor(false).to_vec();
1538 const EXPECTED_PREIMAGE: &[u8] = &[
1539 0xa3, 0x01, 0x01, 0x02, 0x58, 0x20, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1540 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
1541 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x03, 0x58, 0x20, 0x20,
1542 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e,
1543 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
1544 0x3d, 0x3e, 0x3f,
1545 ];
1546 assert_eq!(
1547 preimage,
1548 EXPECTED_PREIMAGE,
1549 "SigHash preimage CBOR changed. actual: {}",
1550 hex(&preimage)
1551 );
1552
1553 let sig_hash = sig.sig_hash();
1555 const EXPECTED_SIG_HASH: [u8; AUM_HASH_LEN] = [
1556 0x22, 0x6f, 0x9c, 0xbc, 0x63, 0x73, 0x92, 0x75, 0x2e, 0x0e, 0xb1, 0x32, 0x9c, 0xc4,
1557 0x99, 0x07, 0x01, 0x4a, 0xb6, 0x4f, 0x8e, 0x5d, 0x82, 0x85, 0xc2, 0x91, 0x42, 0x62,
1558 0xf6, 0xa6, 0xa8, 0x33,
1559 ];
1560 assert_eq!(
1561 sig_hash,
1562 EXPECTED_SIG_HASH,
1563 "sig_hash (BLAKE2s-256 of preimage) changed. actual: {}",
1564 hex(&sig_hash)
1565 );
1566
1567 let aum = aum_hash(&full);
1570 const EXPECTED_AUM_HASH: [u8; AUM_HASH_LEN] = [
1571 0xa4, 0x40, 0x71, 0xa3, 0x7a, 0xbf, 0x80, 0x92, 0xd6, 0xff, 0x23, 0x84, 0xb2, 0xb0,
1572 0xa3, 0x50, 0xc7, 0xcb, 0x48, 0x41, 0xed, 0x68, 0x99, 0x62, 0x41, 0x7c, 0xd4, 0x23,
1573 0x68, 0xdc, 0x72, 0x49,
1574 ];
1575 assert_eq!(
1576 aum.0,
1577 EXPECTED_AUM_HASH,
1578 "aum_hash over full serialization changed. actual: {}",
1579 hex(&aum.0)
1580 );
1581 }
1582
1583 fn unhex(s: &str) -> Vec<u8> {
1587 assert!(s.len().is_multiple_of(2), "odd hex length");
1588 let nib = |c: u8| -> u8 {
1589 match c {
1590 b'0'..=b'9' => c - b'0',
1591 b'a'..=b'f' => c - b'a' + 10,
1592 b'A'..=b'F' => c - b'A' + 10,
1593 _ => panic!("bad hex nibble"),
1594 }
1595 };
1596 let b = s.as_bytes();
1597 let mut out = Vec::with_capacity(s.len() / 2);
1598 let mut i = 0;
1599 while i < b.len() {
1600 out.push((nib(b[i]) << 4) | nib(b[i + 1]));
1601 i += 2;
1602 }
1603 out
1604 }
1605
1606 const SPECCHECK_VECTORS: [(&str, &str, &str); 12] = [
1616 (
1618 "8c93255d71dcab10e8f379c26200f3c7bd5f09d9bc3068d3ef4edeb4853022b6",
1619 "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa",
1620 "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000",
1621 ),
1622 (
1624 "9bd9f44f4dcc75bd531b56b2cd280b0bb38fc1cd6d1230e14861d861de092e79",
1625 "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa",
1626 "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43a5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04",
1627 ),
1628 (
1630 "aebf3f2601a0c8c5d39cc7d8911642f740b78168218da8471772b35f9d35b9ab",
1631 "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43",
1632 "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa8c4bd45aecaca5b24fb97bc10ac27ac8751a7dfe1baff8b953ec9f5833ca260e",
1633 ),
1634 (
1636 "9bd9f44f4dcc75bd531b56b2cd280b0bb38fc1cd6d1230e14861d861de092e79",
1637 "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d",
1638 "9046a64750444938de19f227bb80485e92b83fdb4b6506c160484c016cc1852f87909e14428a7a1d62e9f22f3d3ad7802db02eb2e688b6c52fcd6648a98bd009",
1639 ),
1640 (
1642 "e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec4011eaccd55b53f56c",
1643 "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d",
1644 "160a1cb0dc9c0258cd0a7d23e94d8fa878bcb1925f2c64246b2dee1796bed5125ec6bc982a269b723e0668e540911a9a6a58921d6925e434ab10aa7940551a09",
1645 ),
1646 (
1648 "e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec4011eaccd55b53f56c",
1649 "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d",
1650 "21122a84e0b5fca4052f5b1235c80a537878b38f3142356b2c2384ebad4668b7e40bc836dac0f71076f9abe3a53f9c03c1ceeeddb658d0030494ace586687405",
1651 ),
1652 (
1654 "85e241a07d148b41e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec40",
1655 "442aad9f089ad9e14647b1ef9099a1ff4798d78589e66f28eca69c11f582a623",
1656 "e96f66be976d82e60150baecff9906684aebb1ef181f67a7189ac78ea23b6c0e547f7690a0e2ddcd04d87dbc3490dc19b3b3052f7ff0538cb68afb369ba3a514",
1657 ),
1658 (
1660 "85e241a07d148b41e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec40",
1661 "442aad9f089ad9e14647b1ef9099a1ff4798d78589e66f28eca69c11f582a623",
1662 "8ce5b96c8f26d0ab6c47958c9e68b937104cd36e13c33566acd2fe8d38aa19427e71f98a473474f2f13f06f97c20d58cc3f54b8bd0d272f42b695dd7e89a8c22",
1663 ),
1664 (
1666 "9bedc267423725d473888631ebf45988bad3db83851ee85c85e241a07d148b41",
1667 "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43",
1668 "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03be9678ac102edcd92b0210bb34d7428d12ffc5df5f37e359941266a4e35f0f",
1669 ),
1670 (
1672 "9bedc267423725d473888631ebf45988bad3db83851ee85c85e241a07d148b41",
1673 "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43",
1674 "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffca8c5b64cd208982aa38d4936621a4775aa233aa0505711d8fdcfdaa943d4908",
1675 ),
1676 (
1678 "e96b7021eb39c1a163b6da4e3093dcd3f21387da4cc4572be588fafae23c155b",
1679 "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
1680 "a9d55260f765261eb9b84e106f665e00b867287a761990d7135963ee0a7d59dca5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04",
1681 ),
1682 (
1684 "39a591f5321bbe07fd5a23dc2f39d025d74526615746727ceefd6e82ae65c06f",
1685 "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
1686 "a9d55260f765261eb9b84e106f665e00b867287a761990d7135963ee0a7d59dca5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04",
1687 ),
1688 ];
1689
1690 #[test]
1706 fn ed25519_speccheck_dual_verifier_kat() {
1707 const STD_EXPECT: [bool; 12] = [
1713 true, true, true, true, false, false, false, false, false, false, false, true,
1714 ];
1715 const ZIP215_EXPECT: [bool; 12] = [
1716 true, true, true, true, true, true, false, false, false, true, true, true,
1717 ];
1718
1719 for (i, (msg_hex, pk_hex, sig_hex)) in SPECCHECK_VECTORS.iter().enumerate() {
1720 let msg = unhex(msg_hex);
1721 let pk = unhex(pk_hex);
1722 let sig = unhex(sig_hex);
1723 assert_eq!(pk.len(), 32, "vector {i}: pubkey not 32 bytes");
1724 assert_eq!(sig.len(), 64, "vector {i}: signature not 64 bytes");
1725
1726 let std_ok = verify_ed25519_std(&pk, &msg, &sig).is_ok();
1727 let zip_ok = verify_ed25519_zip215(&pk, &msg, &sig).is_ok();
1728
1729 assert_eq!(
1730 std_ok, STD_EXPECT[i],
1731 "speccheck vector {i}: verify_ed25519_std accept={std_ok}, expected {}",
1732 STD_EXPECT[i]
1733 );
1734 assert_eq!(
1735 zip_ok, ZIP215_EXPECT[i],
1736 "speccheck vector {i}: verify_ed25519_zip215 accept={zip_ok}, expected {}",
1737 ZIP215_EXPECT[i]
1738 );
1739 }
1740
1741 for &i in &[6usize, 7usize] {
1745 let (msg_hex, pk_hex, sig_hex) = SPECCHECK_VECTORS[i];
1746 let (msg, pk, sig) = (unhex(msg_hex), unhex(pk_hex), unhex(sig_hex));
1747 assert!(
1748 verify_ed25519_std(&pk, &msg, &sig).is_err(),
1749 "SECURITY: verify_ed25519_std ACCEPTED S>=L malleability vector {i}"
1750 );
1751 }
1752
1753 {
1757 let (msg_hex, pk_hex, sig_hex) = SPECCHECK_VECTORS[4];
1758 let (msg, pk, sig) = (unhex(msg_hex), unhex(pk_hex), unhex(sig_hex));
1759 assert!(
1760 verify_ed25519_zip215(&pk, &msg, &sig).is_ok(),
1761 "vector 4: ZIP-215 (zebra) should ACCEPT the cofactored discriminator"
1762 );
1763 assert!(
1764 verify_ed25519_std(&pk, &msg, &sig).is_err(),
1765 "vector 4: standard (dalek) should REJECT the cofactored discriminator"
1766 );
1767 }
1768 }
1769
1770 #[test]
1789 fn tka_cbor_matches_go_golden() {
1790 let pubkey32 = unhex("a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf");
1792 let key_id32 = unhex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
1793 let sig64 = unhex(
1794 "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeffe0e1e2e3e4e5e6e7e8e9eaebecedeeefd0d1d2d3d4d5d6d7d8d9dadbdcdddedfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf",
1795 );
1796 let wrap32 = unhex("101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f");
1797 let rot_sig64 = unhex(
1798 "55565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f9091929394",
1799 );
1800
1801 {
1803 let sig = NodeKeySignature {
1804 sig_kind: SigKind::Direct,
1805 pubkey: pubkey32.clone(),
1806 key_id: key_id32.clone(),
1807 signature: sig64.clone(),
1808 nested: None,
1809 wrapping_pubkey: Vec::new(),
1810 };
1811 let full = sig.to_cbor(true).to_vec();
1812 let expected_full = unhex(
1813 "a40101025820a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf035820000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f045840f0f1f2f3f4f5f6f7f8f9fafbfcfdfeffe0e1e2e3e4e5e6e7e8e9eaebecedeeefd0d1d2d3d4d5d6d7d8d9dadbdcdddedfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf",
1814 );
1815 assert_eq!(
1816 full,
1817 expected_full,
1818 "GOLDEN 1 (Direct) full CBOR diverged from Go tka v1.100.0. actual: {}",
1819 hex(&full)
1820 );
1821 let expected_hash =
1822 unhex("7e9653c97d35485b37b9bf942b1861cd2f3cb0663b5bb154f1178cca72101e74");
1823 assert_eq!(
1824 sig.sig_hash().as_slice(),
1825 expected_hash.as_slice(),
1826 "GOLDEN 1 (Direct) sig_hash diverged from Go tka v1.100.0. actual: {}",
1827 hex(&sig.sig_hash())
1828 );
1829 }
1830
1831 {
1833 let sig = NodeKeySignature {
1834 sig_kind: SigKind::Credential,
1835 pubkey: pubkey32.clone(),
1836 key_id: key_id32.clone(),
1837 signature: sig64.clone(),
1838 nested: None,
1839 wrapping_pubkey: Vec::new(),
1840 };
1841 let full = sig.to_cbor(true).to_vec();
1842 let expected_full = unhex(
1843 "a40103025820a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf035820000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f045840f0f1f2f3f4f5f6f7f8f9fafbfcfdfeffe0e1e2e3e4e5e6e7e8e9eaebecedeeefd0d1d2d3d4d5d6d7d8d9dadbdcdddedfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf",
1844 );
1845 assert_eq!(
1846 full,
1847 expected_full,
1848 "GOLDEN 2 (Credential) full CBOR diverged from Go tka v1.100.0. actual: {}",
1849 hex(&full)
1850 );
1851 let expected_hash =
1852 unhex("b6070ea8bc7ae8989ef4293f5031bedaa4a499803ade99f9e2f34dc2898ac03f");
1853 assert_eq!(
1854 sig.sig_hash().as_slice(),
1855 expected_hash.as_slice(),
1856 "GOLDEN 2 (Credential) sig_hash diverged from Go tka v1.100.0. actual: {}",
1857 hex(&sig.sig_hash())
1858 );
1859 }
1860
1861 {
1871 let nested = NodeKeySignature {
1872 sig_kind: SigKind::Direct,
1873 pubkey: pubkey32.clone(),
1874 key_id: key_id32.clone(),
1875 signature: sig64.clone(),
1876 nested: None,
1877 wrapping_pubkey: wrap32.clone(),
1878 };
1879 let sig = NodeKeySignature {
1880 sig_kind: SigKind::Rotation,
1881 pubkey: wrap32.clone(),
1882 key_id: Vec::new(),
1883 signature: rot_sig64.clone(),
1884 nested: Some(alloc::boxed::Box::new(nested)),
1885 wrapping_pubkey: Vec::new(),
1886 };
1887 let full = sig.to_cbor(true).to_vec();
1888 let expected_full = unhex(
1889 "a40102025820101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f04584055565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939405a50101025820a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf035820000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f045840f0f1f2f3f4f5f6f7f8f9fafbfcfdfeffe0e1e2e3e4e5e6e7e8e9eaebecedeeefd0d1d2d3d4d5d6d7d8d9dadbdcdddedfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf065820101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f",
1890 );
1891 assert_eq!(
1892 full,
1893 expected_full,
1894 "GOLDEN 3 (Rotation) full CBOR diverged from Go tka v1.100.0. actual: {}",
1895 hex(&full)
1896 );
1897 let expected_hash =
1898 unhex("fac0a5a6781bb945369c28a0b3d3eea04e1648b60ec1a990a1ff68a9a566e6a7");
1899 assert_eq!(
1900 sig.sig_hash().as_slice(),
1901 expected_hash.as_slice(),
1902 "GOLDEN 3 (Rotation) sig_hash diverged from Go tka v1.100.0. actual: {}",
1903 hex(&sig.sig_hash())
1904 );
1905 }
1906 }
1907
1908 #[test]
1924 fn ed25519_dual_verifier_matches_go_verdicts() {
1925 const GO_STD_ACCEPT: [bool; 12] = [
1927 true, true, true, true, false, false, false, false, false, false, false, true,
1928 ];
1929 const GO_ZIP215_ACCEPT: [bool; 12] = [
1930 true, true, true, true, true, true, false, false, false, true, true, true,
1931 ];
1932
1933 for (i, (msg_hex, pk_hex, sig_hex)) in SPECCHECK_VECTORS.iter().enumerate() {
1934 let msg = unhex(msg_hex);
1935 let pk = unhex(pk_hex);
1936 let sig = unhex(sig_hex);
1937
1938 let std_ok = verify_ed25519_std(&pk, &msg, &sig).is_ok();
1939 let zip_ok = verify_ed25519_zip215(&pk, &msg, &sig).is_ok();
1940
1941 assert_eq!(
1942 std_ok, GO_STD_ACCEPT[i],
1943 "vector {i}: Rust verify_ed25519_std accept={std_ok} disagrees with Go \
1944 crypto/ed25519.Verify={}",
1945 GO_STD_ACCEPT[i]
1946 );
1947 assert_eq!(
1948 zip_ok, GO_ZIP215_ACCEPT[i],
1949 "vector {i}: Rust verify_ed25519_zip215 accept={zip_ok} disagrees with Go \
1950 ed25519consensus.Verify={}",
1951 GO_ZIP215_ACCEPT[i]
1952 );
1953 }
1954 }
1955
1956 #[test]
1962 fn aum_serialize_matches_go_test_serialization_vectors() {
1963 let add_key_inner_zero_key = cbor::Value::IntMap(alloc::vec![
1969 (1, cbor::Value::Uint(0)), (2, cbor::Value::Uint(0)), (3, cbor::Value::Null), ]);
1973 assert_eq!(
1974 add_key_inner_zero_key.to_vec(),
1975 alloc::vec![0xa3, 0x01, 0x00, 0x02, 0x00, 0x03, 0xf6],
1976 "Go's zero Key{{}} encodes as map(3){{kind=0, votes=0, public=null}}"
1977 );
1978
1979 let remove_key = Aum {
1981 message_kind: AumKind::RemoveKey,
1982 prev_aum_hash: None,
1983 key: None,
1984 key_id: alloc::vec![1, 2],
1985 state: None,
1986 votes: None,
1987 meta: Vec::new(),
1988 signatures: Vec::new(),
1989 };
1990 assert_eq!(
1991 remove_key.serialize(),
1992 alloc::vec![0xa3, 0x01, 0x02, 0x02, 0xf6, 0x04, 0x42, 0x01, 0x02],
1994 "RemoveKey AUM serialization must match Go TestSerialization byte-for-byte"
1995 );
1996
1997 let update_key = Aum {
2000 message_kind: AumKind::UpdateKey,
2001 prev_aum_hash: None,
2002 key: None,
2003 key_id: alloc::vec![1, 2],
2004 state: None,
2005 votes: Some(2),
2006 meta: alloc::vec![("a".into(), "b".into())],
2007 signatures: Vec::new(),
2008 };
2009 assert_eq!(
2010 update_key.serialize(),
2011 alloc::vec![
2014 0xa5, 0x01, 0x04, 0x02, 0xf6, 0x04, 0x42, 0x01, 0x02, 0x06, 0x02, 0x07, 0xa1, 0x61,
2015 0x61, 0x61, 0x62
2016 ],
2017 "UpdateKey AUM serialization must match Go TestSerialization byte-for-byte"
2018 );
2019
2020 let with_sig = Aum {
2022 message_kind: AumKind::AddKey,
2023 prev_aum_hash: None,
2024 key: None,
2025 key_id: Vec::new(),
2026 state: None,
2027 votes: None,
2028 meta: Vec::new(),
2029 signatures: alloc::vec![AumSignature {
2030 key_id: alloc::vec![1],
2031 signature: Vec::new(),
2032 }],
2033 };
2034 assert_eq!(
2035 with_sig.serialize(),
2036 alloc::vec![
2039 0xa3, 0x01, 0x01, 0x02, 0xf6, 0x17, 0x81, 0xa2, 0x01, 0x41, 0x01, 0x02, 0xf6
2040 ],
2041 "Signature AUM serialization must match Go TestSerialization (key 23 + nil sig = null)"
2042 );
2043
2044 let no_sig = Aum {
2047 signatures: Vec::new(),
2048 ..with_sig.clone()
2049 };
2050 assert_eq!(
2051 with_sig.sig_hash(),
2052 blake2s_256(&no_sig.serialize()),
2053 "SigHash preimage must omit key 23 (Signatures), matching Go AUM.SigHash"
2054 );
2055 assert_ne!(
2057 with_sig.hash().0,
2058 with_sig.sig_hash(),
2059 "Hash (incl. signatures) must differ from SigHash (excl.) when signatures are present"
2060 );
2061 }
2062
2063 #[test]
2067 fn aum_checkpoint_state_serialization() {
2068 let checkpoint = Aum {
2069 message_kind: AumKind::Checkpoint,
2070 prev_aum_hash: Some(AumHash([0u8; AUM_HASH_LEN])),
2071 key: None,
2072 key_id: Vec::new(),
2073 state: Some(AumState {
2074 last_aum_hash: Some(AumHash([0u8; AUM_HASH_LEN])),
2075 disablement_values: Vec::new(),
2076 keys: alloc::vec![AumKey {
2077 kind: KeyKind::Ed25519,
2078 votes: 1,
2079 public: alloc::vec![5, 6],
2080 meta: Vec::new(),
2081 }],
2082 state_id1: 0,
2083 state_id2: 0,
2084 }),
2085 votes: None,
2086 meta: Vec::new(),
2087 signatures: Vec::new(),
2088 };
2089 let bytes = checkpoint.serialize();
2090 assert_eq!(
2094 &bytes[0..3],
2095 &[0xa3, 0x01, 0x05],
2096 "map(3), MessageKind=Checkpoint(5)"
2097 );
2098 assert_eq!(
2099 &bytes[3..6],
2100 &[0x02, 0x58, 0x20],
2101 "key2 prev = 32-byte byte string head"
2102 );
2103 assert_eq!(bytes[38], 0x05, "key 5 = State");
2107 assert_eq!(
2109 &bytes[39..42],
2110 &[0xa3, 0x01, 0x58],
2111 "State map(3), key1 LastAUMHash bytes"
2112 );
2113 let tail = &bytes[bytes.len() - 4..];
2115 assert_eq!(
2116 tail,
2117 &[0x03, 0x42, 0x05, 0x06],
2118 "Key.Public (key 3) = bytes{{5,6}}"
2119 );
2120 assert_eq!(checkpoint.hash(), checkpoint.hash());
2122 }
2123
2124 fn test_aum_key(seed: u8, votes: u32) -> AumKey {
2128 use ed25519_dalek::SigningKey;
2129 let pubk = SigningKey::from_bytes(&[seed; 32])
2130 .verifying_key()
2131 .to_bytes()
2132 .to_vec();
2133 AumKey {
2134 kind: KeyKind::Ed25519,
2135 votes,
2136 public: pubk,
2137 meta: Vec::new(),
2138 }
2139 }
2140
2141 fn genesis_add(key: AumKey) -> Aum {
2143 Aum {
2144 message_kind: AumKind::AddKey,
2145 prev_aum_hash: None,
2146 key: Some(key),
2147 key_id: Vec::new(),
2148 state: None,
2149 votes: None,
2150 meta: Vec::new(),
2151 signatures: Vec::new(),
2152 }
2153 }
2154
2155 fn child(parent: &Aum, kind: AumKind, key: Option<AumKey>, key_id: Vec<u8>) -> Aum {
2157 Aum {
2158 message_kind: kind,
2159 prev_aum_hash: Some(parent.hash()),
2160 key,
2161 key_id,
2162 state: None,
2163 votes: None,
2164 meta: Vec::new(),
2165 signatures: Vec::new(),
2166 }
2167 }
2168
2169 #[test]
2172 fn replay_linear_chain_folds_all_kinds() {
2173 let k0 = test_aum_key(1, 1);
2174 let k1 = test_aum_key(2, 1);
2175
2176 let a0 = genesis_add(k0.clone());
2177 let a1 = child(&a0, AumKind::AddKey, Some(k1.clone()), Vec::new());
2178 let mut a2 = child(&a1, AumKind::UpdateKey, None, k1.public.clone());
2179 a2.votes = Some(5);
2180 let a3 = child(&a2, AumKind::RemoveKey, None, k0.public.clone());
2181
2182 let auth = Authority::from_chain(&[a0, a1, a2, a3.clone()]).unwrap();
2183
2184 assert_eq!(auth.state().keys.len(), 1, "k0 removed, k1 remains");
2186 let remaining = &auth.state().keys[0];
2187 assert_eq!(remaining.public, k1.public, "k1 is the surviving key");
2188 assert_eq!(remaining.votes, 5, "UpdateKey raised k1's votes to 5");
2189 assert_eq!(auth.head(), a3.hash(), "head = last AUM hash");
2191 }
2192
2193 #[test]
2195 fn replay_rejects_broken_parent_link() {
2196 let k0 = test_aum_key(1, 1);
2197 let k1 = test_aum_key(2, 1);
2198 let a0 = genesis_add(k0);
2199 let mut a1 = child(&a0, AumKind::AddKey, Some(k1), Vec::new());
2201 a1.prev_aum_hash = Some(AumHash([0xab; 32]));
2202 assert_eq!(
2203 Authority::from_chain(&[a0, a1]).unwrap_err(),
2204 TkaError::BadParent
2205 );
2206 }
2207
2208 #[test]
2210 fn replay_rejects_bad_key_state() {
2211 let k0 = test_aum_key(1, 1);
2212 let a0 = genesis_add(k0.clone());
2213 let dup = child(&a0, AumKind::AddKey, Some(k0.clone()), Vec::new());
2215 assert_eq!(
2216 Authority::from_chain(&[a0.clone(), dup]).unwrap_err(),
2217 TkaError::BadKeyState
2218 );
2219 let absent = test_aum_key(9, 1);
2221 let rm = child(&a0, AumKind::RemoveKey, None, absent.public.clone());
2222 assert_eq!(
2223 Authority::from_chain(&[a0, rm]).unwrap_err(),
2224 TkaError::BadKeyState
2225 );
2226 }
2227
2228 #[test]
2230 fn replay_empty_chain_is_bad_chain() {
2231 assert_eq!(Authority::from_chain(&[]).unwrap_err(), TkaError::BadChain);
2232 }
2233
2234 #[test]
2237 fn replay_weight_dedups_and_ignores_unknown() {
2238 let k0 = test_aum_key(1, 2);
2239 let k1 = test_aum_key(2, 3);
2240 let state = ReplayState {
2241 keys: alloc::vec![k0.clone(), k1.clone()],
2242 last_aum_hash: None,
2243 state_id: None,
2244 };
2245
2246 let mut aum = genesis_add(test_aum_key(5, 1));
2248 assert_eq!(state.weight(&aum), 0);
2249
2250 aum.signatures = alloc::vec![AumSignature {
2252 key_id: k0.public.clone(),
2253 signature: Vec::new()
2254 }];
2255 assert_eq!(state.weight(&aum), 2);
2256
2257 aum.signatures = alloc::vec![
2259 AumSignature {
2260 key_id: k0.public.clone(),
2261 signature: Vec::new()
2262 },
2263 AumSignature {
2264 key_id: k1.public.clone(),
2265 signature: Vec::new()
2266 },
2267 ];
2268 assert_eq!(state.weight(&aum), 5);
2269
2270 aum.signatures = alloc::vec![
2272 AumSignature {
2273 key_id: k0.public.clone(),
2274 signature: Vec::new()
2275 },
2276 AumSignature {
2277 key_id: k0.public.clone(),
2278 signature: Vec::new()
2279 },
2280 ];
2281 assert_eq!(state.weight(&aum), 2, "a key signing twice counts once");
2282
2283 aum.signatures = alloc::vec![AumSignature {
2285 key_id: alloc::vec![0xff; 32],
2286 signature: Vec::new()
2287 }];
2288 assert_eq!(
2289 state.weight(&aum),
2290 0,
2291 "an untrusted signing key contributes no weight"
2292 );
2293 }
2294
2295 #[test]
2299 fn pick_next_aum_lowest_hash_tiebreak_is_order_independent() {
2300 let k = test_aum_key(1, 1);
2301 let a0 = genesis_add(k);
2302 let c1 = child(&a0, AumKind::NoOp, None, alloc::vec![1]);
2304 let c2 = child(&a0, AumKind::NoOp, None, alloc::vec![2]);
2305 let state = ReplayState::default();
2306
2307 let lower = if c1.hash().0 < c2.hash().0 {
2308 c1.hash()
2309 } else {
2310 c2.hash()
2311 };
2312 let ab = [c1.clone(), c2.clone()];
2313 let ba = [c2, c1];
2314 let pick_ab = pick_next_aum(&state, &ab).hash();
2315 let pick_ba = pick_next_aum(&state, &ba).hash();
2316 assert_eq!(pick_ab, lower, "lowest hash wins");
2317 assert_eq!(
2318 pick_ab, pick_ba,
2319 "selection is independent of candidate order"
2320 );
2321 }
2322
2323 #[test]
2326 fn pick_next_aum_weight_beats_hash() {
2327 use ed25519_dalek::SigningKey;
2328 let signer_seed = 3u8;
2329 let signer_pub = SigningKey::from_bytes(&[signer_seed; 32])
2330 .verifying_key()
2331 .to_bytes()
2332 .to_vec();
2333 let state = ReplayState {
2334 keys: alloc::vec![AumKey {
2335 kind: KeyKind::Ed25519,
2336 votes: 4,
2337 public: signer_pub.clone(),
2338 meta: Vec::new(),
2339 }],
2340 last_aum_hash: None,
2341 state_id: None,
2342 };
2343
2344 let a0 = genesis_add(test_aum_key(1, 1));
2345 let unsigned = child(&a0, AumKind::NoOp, None, alloc::vec![1]);
2346 let mut signed = child(&a0, AumKind::NoOp, None, alloc::vec![2]);
2347 signed.signatures = alloc::vec![AumSignature {
2348 key_id: signer_pub,
2349 signature: Vec::new(),
2350 }];
2351
2352 let candidates = [unsigned.clone(), signed.clone()];
2354 let winner = pick_next_aum(&state, &candidates);
2355 assert_eq!(
2356 winner.hash(),
2357 signed.hash(),
2358 "higher weight wins over lower hash"
2359 );
2360 }
2361
2362 #[test]
2365 fn forked_chain_prefers_removekey_branch() {
2366 let k0 = test_aum_key(1, 1);
2367 let k1 = test_aum_key(2, 1);
2368 let a0 = genesis_add(k0.clone());
2370 let a1 = child(&a0, AumKind::AddKey, Some(k1.clone()), Vec::new());
2371 let branch_remove = child(&a1, AumKind::RemoveKey, None, k0.public.clone());
2373 let branch_noop = child(&a1, AumKind::NoOp, None, alloc::vec![9]);
2374
2375 let noop_branch = [branch_noop.clone()];
2376 let remove_branch = [branch_remove.clone()];
2377 let auth = Authority::from_forked_chain(&[a0, a1], &[&noop_branch[..], &remove_branch[..]])
2378 .unwrap();
2379
2380 assert_eq!(auth.state().keys.len(), 1);
2382 assert_eq!(auth.state().keys[0].public, k1.public);
2383 assert_eq!(
2384 auth.head(),
2385 branch_remove.hash(),
2386 "active head = RemoveKey branch"
2387 );
2388 }
2389
2390 #[test]
2394 fn replayed_authority_authorizes_node_end_to_end() {
2395 use ed25519_dalek::{Signer, SigningKey};
2396
2397 let signing = SigningKey::from_bytes(&[77u8; 32]);
2398 let trusted_pub = signing.verifying_key().to_bytes().to_vec();
2399 let trusted = AumKey {
2400 kind: KeyKind::Ed25519,
2401 votes: 1,
2402 public: trusted_pub.clone(),
2403 meta: Vec::new(),
2404 };
2405 let revoked_signing = SigningKey::from_bytes(&[88u8; 32]);
2407 let revoked_pub = revoked_signing.verifying_key().to_bytes().to_vec();
2408 let revoked = AumKey {
2409 kind: KeyKind::Ed25519,
2410 votes: 1,
2411 public: revoked_pub.clone(),
2412 meta: Vec::new(),
2413 };
2414
2415 let a0 = genesis_add(trusted);
2416 let a1 = child(&a0, AumKind::AddKey, Some(revoked), Vec::new());
2417 let a2 = child(&a1, AumKind::RemoveKey, None, revoked_pub.clone());
2418 let auth = Authority::from_chain(&[a0, a1, a2]).unwrap();
2419
2420 let node_key = alloc::vec![7u8; 32];
2421 let mut sig = NodeKeySignature {
2423 sig_kind: SigKind::Direct,
2424 pubkey: node_key.clone(),
2425 key_id: trusted_pub.clone(),
2426 signature: Vec::new(),
2427 nested: None,
2428 wrapping_pubkey: Vec::new(),
2429 };
2430 sig.signature = signing.sign(&sig.sig_hash()).to_bytes().to_vec();
2431 assert!(
2432 auth.node_key_authorized(&node_key, &sig.to_cbor(true).to_vec())
2433 .is_ok(),
2434 "the replayed authority must authorize a node signed by a still-trusted key"
2435 );
2436
2437 let mut bad = NodeKeySignature {
2439 sig_kind: SigKind::Direct,
2440 pubkey: node_key.clone(),
2441 key_id: revoked_pub.clone(),
2442 signature: Vec::new(),
2443 nested: None,
2444 wrapping_pubkey: Vec::new(),
2445 };
2446 bad.signature = revoked_signing.sign(&bad.sig_hash()).to_bytes().to_vec();
2447 assert_eq!(
2448 auth.node_key_authorized(&node_key, &bad.to_cbor(true).to_vec())
2449 .unwrap_err(),
2450 TkaError::UntrustedKey,
2451 "a key the chain removed must not authorize"
2452 );
2453 }
2454
2455 #[test]
2458 fn replay_rejects_invalid_genesis_kind() {
2459 let mut g = genesis_add(test_aum_key(1, 1));
2462 g.message_kind = AumKind::UpdateKey;
2463 g.key = None;
2464 g.key_id = test_aum_key(1, 1).public.clone();
2465 assert_eq!(
2466 Authority::from_chain(&[g]).unwrap_err(),
2467 TkaError::BadChain,
2468 "an UpdateKey cannot be a genesis AUM"
2469 );
2470 }
2471
2472 #[test]
2475 fn replay_rejects_genesis_with_parent() {
2476 let mut g = genesis_add(test_aum_key(1, 1));
2477 g.prev_aum_hash = Some(AumHash([0x11; 32])); assert_eq!(
2479 Authority::from_chain(&[g]).unwrap_err(),
2480 TkaError::BadParent,
2481 "a genesis AUM that names a parent must be rejected (not treated as genesis)"
2482 );
2483 }
2484
2485 #[test]
2488 fn replay_rejects_checkpoint_stateid_mismatch() {
2489 let k = test_aum_key(1, 1);
2490 let genesis = Aum {
2492 message_kind: AumKind::Checkpoint,
2493 prev_aum_hash: None,
2494 key: None,
2495 key_id: Vec::new(),
2496 state: Some(AumState {
2497 last_aum_hash: None,
2498 disablement_values: Vec::new(),
2499 keys: alloc::vec![k.clone()],
2500 state_id1: 7,
2501 state_id2: 0,
2502 }),
2503 votes: None,
2504 meta: Vec::new(),
2505 signatures: Vec::new(),
2506 };
2507 let bad = Aum {
2509 message_kind: AumKind::Checkpoint,
2510 prev_aum_hash: Some(genesis.hash()),
2511 key: None,
2512 key_id: Vec::new(),
2513 state: Some(AumState {
2514 last_aum_hash: Some(genesis.hash()),
2515 disablement_values: Vec::new(),
2516 keys: alloc::vec![k.clone()],
2517 state_id1: 8, state_id2: 0,
2519 }),
2520 votes: None,
2521 meta: Vec::new(),
2522 signatures: Vec::new(),
2523 };
2524 assert_eq!(
2525 Authority::from_chain(&[genesis.clone(), bad]).unwrap_err(),
2526 TkaError::BadKeyState,
2527 "a checkpoint with a foreign StateID belongs to another authority and must be rejected"
2528 );
2529 let ok = Aum {
2531 message_kind: AumKind::Checkpoint,
2532 prev_aum_hash: Some(genesis.hash()),
2533 key: None,
2534 key_id: Vec::new(),
2535 state: Some(AumState {
2536 last_aum_hash: Some(genesis.hash()),
2537 disablement_values: Vec::new(),
2538 keys: alloc::vec![k],
2539 state_id1: 7,
2540 state_id2: 0,
2541 }),
2542 votes: None,
2543 meta: Vec::new(),
2544 signatures: Vec::new(),
2545 };
2546 assert!(Authority::from_chain(&[genesis, ok]).is_ok());
2547 }
2548
2549 #[test]
2552 fn forked_chain_rejects_multistep_branch() {
2553 let k0 = test_aum_key(1, 1);
2554 let a0 = genesis_add(k0.clone());
2555 let b1 = child(&a0, AumKind::NoOp, None, alloc::vec![1]);
2556 let b2 = child(&b1, AumKind::NoOp, None, alloc::vec![2]);
2558 let single = [child(&a0, AumKind::NoOp, None, alloc::vec![3])];
2559 let multi = [b1, b2];
2560 assert_eq!(
2561 Authority::from_forked_chain(&[a0], &[&single[..], &multi[..]]).unwrap_err(),
2562 TkaError::BadChain,
2563 "a multi-step branch must be rejected, not judged by its first AUM"
2564 );
2565 }
2566}