Skip to content

Commit 70c633f

Browse files
committed
full KERI spec compliance: typed newtypes, spec-compliant event schemas/seals/receipts, multi-sig threshold verification, delegated events, first-seen policy, KAWA witness agreement, superseding recovery, and routed message types
1 parent 9a7a819 commit 70c633f

69 files changed

Lines changed: 6216 additions & 1220 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/auths-cli/src/commands/artifact/verify.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use serde::Serialize;
33
use std::fs;
44
use std::path::{Path, PathBuf};
55

6-
use auths_keri::witness::Receipt;
6+
use auths_keri::witness::SignedReceipt;
77
use auths_transparency::{
88
BundleVerificationReport, CheckpointStatus, DelegationStatus, InclusionStatus, NamespaceStatus,
99
OfflineBundle, SignatureStatus, TrustRoot, WitnessStatus,
@@ -344,7 +344,7 @@ async fn verify_witnesses(
344344

345345
let receipts_bytes = fs::read(receipts_path)
346346
.with_context(|| format!("Failed to read witness receipts: {:?}", receipts_path))?;
347-
let receipts: Vec<Receipt> =
347+
let receipts: Vec<SignedReceipt> =
348348
serde_json::from_slice(&receipts_bytes).context("Failed to parse witness receipts JSON")?;
349349

350350
let witness_keys = parse_witness_keys(witness_keys_raw)?;

crates/auths-cli/src/commands/device/verify_attestation.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::ux::format::is_json_mode;
22
use anyhow::{Context, Result, anyhow};
3-
use auths_keri::witness::Receipt;
3+
use auths_keri::witness::SignedReceipt;
44
use auths_sdk::trust::{PinnedIdentity, PinnedIdentityStore, RootsFile, TrustLevel, TrustPolicy};
55
use auths_verifier::Capability;
66
use auths_verifier::core::Attestation;
@@ -313,7 +313,7 @@ async fn run_verify(now: chrono::DateTime<Utc>, cmd: &VerifyCommand) -> Result<V
313313
let receipts_bytes = fs::read(receipts_path).with_context(|| {
314314
format!("Failed to read witness receipts: {:?}", receipts_path)
315315
})?;
316-
let receipts: Vec<Receipt> = serde_json::from_slice(&receipts_bytes)
316+
let receipts: Vec<SignedReceipt> = serde_json::from_slice(&receipts_bytes)
317317
.context("Failed to parse witness receipts JSON")?;
318318
let witness_keys = parse_witness_keys(&cmd.witness_keys)?;
319319

crates/auths-cli/src/commands/verify_commit.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::ux::format::is_json_mode;
22
use anyhow::{Context, Result, anyhow};
3-
use auths_keri::witness::Receipt;
3+
use auths_keri::witness::SignedReceipt;
44
use auths_verifier::witness::{WitnessQuorum, WitnessVerifyConfig};
55
use auths_verifier::{
66
Attestation, IdentityBundle, VerificationReport, verify_chain, verify_chain_with_witnesses,
@@ -502,7 +502,7 @@ async fn verify_witnesses(
502502
let receipts_bytes = fs::read(receipts_path)
503503
.with_context(|| format!("Failed to read witness receipts: {:?}", receipts_path))?;
504504

505-
let receipts: Vec<Receipt> =
505+
let receipts: Vec<SignedReceipt> =
506506
serde_json::from_slice(&receipts_bytes).context("Failed to parse witness receipts JSON")?;
507507

508508
let witness_keys = parse_witness_keys(&cmd.witness_keys)?;

crates/auths-core/src/witness/collector.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,13 +243,13 @@ impl ReceiptCollector {
243243
return None;
244244
}
245245

246-
let expected_said = &existing[0].a;
247-
if new.a != *expected_said {
246+
let expected_said = &existing[0].d;
247+
if new.d != *expected_said {
248248
Some(DuplicityEvidence {
249249
prefix: Prefix::default(),
250-
sequence: new.s,
250+
sequence: new.s.value(),
251251
event_a_said: expected_said.clone(),
252-
event_b_said: new.a.clone(),
252+
event_b_said: new.d.clone(),
253253
witness_reports: vec![],
254254
})
255255
} else {

crates/auths-core/src/witness/duplicity.rs

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ impl DuplicityDetector {
139139
/// Verify that a set of receipts are consistent (same event SAID).
140140
///
141141
/// This checks that all receipts are for the same event. If receipts
142-
/// have different `a` (event SAID) fields, this indicates duplicity.
142+
/// have different `d` (event SAID) fields, this indicates duplicity.
143143
///
144144
/// # Arguments
145145
///
@@ -155,21 +155,21 @@ impl DuplicityDetector {
155155
}
156156

157157
let first = &receipts[0];
158-
let expected_said = &first.a;
158+
let expected_said = &first.d;
159159

160160
for receipt in receipts.iter().skip(1) {
161-
if receipt.a != *expected_said {
161+
if receipt.d != *expected_said {
162162
// Different receipts claim different SAIDs
163163
return Err(DuplicityEvidence {
164164
prefix: Prefix::default(),
165-
sequence: first.s,
165+
sequence: first.s.value(),
166166
event_a_said: expected_said.clone(),
167-
event_b_said: receipt.a.clone(),
167+
event_b_said: receipt.d.clone(),
168168
witness_reports: receipts
169169
.iter()
170170
.map(|r| WitnessReport {
171-
witness_id: r.i.clone(),
172-
observed_said: r.a.clone(),
171+
witness_id: r.i.as_str().to_string(),
172+
observed_said: r.d.clone(),
173173
observed_at: None,
174174
})
175175
.collect(),
@@ -289,26 +289,23 @@ mod tests {
289289

290290
#[test]
291291
fn verify_receipts_consistent() {
292+
use auths_keri::{KeriSequence, VersionString};
292293
let detector = DuplicityDetector::new();
293294

294295
let receipts = vec![
295296
Receipt {
296-
v: "KERI".into(),
297+
v: VersionString::placeholder(),
297298
t: "rct".into(),
298-
d: Said::new_unchecked("ER1".into()),
299-
i: "W1".into(),
300-
s: 5,
301-
a: Said::new_unchecked("EEVENT_SAID".into()),
302-
sig: vec![0; 64],
299+
d: Said::new_unchecked("EEVENT_SAID".into()),
300+
i: Prefix::new_unchecked("W1".into()),
301+
s: KeriSequence::new(5),
303302
},
304303
Receipt {
305-
v: "KERI".into(),
304+
v: VersionString::placeholder(),
306305
t: "rct".into(),
307-
d: Said::new_unchecked("ER2".into()),
308-
i: "W2".into(),
309-
s: 5,
310-
a: Said::new_unchecked("EEVENT_SAID".into()),
311-
sig: vec![0; 64],
306+
d: Said::new_unchecked("EEVENT_SAID".into()),
307+
i: Prefix::new_unchecked("W2".into()),
308+
s: KeriSequence::new(5),
312309
},
313310
];
314311

@@ -317,26 +314,23 @@ mod tests {
317314

318315
#[test]
319316
fn verify_receipts_inconsistent() {
317+
use auths_keri::{KeriSequence, VersionString};
320318
let detector = DuplicityDetector::new();
321319

322320
let receipts = vec![
323321
Receipt {
324-
v: "KERI".into(),
322+
v: VersionString::placeholder(),
325323
t: "rct".into(),
326-
d: Said::new_unchecked("ER1".into()),
327-
i: "W1".into(),
328-
s: 5,
329-
a: Said::new_unchecked("ESAID_A".into()),
330-
sig: vec![0; 64],
324+
d: Said::new_unchecked("ESAID_A".into()),
325+
i: Prefix::new_unchecked("W1".into()),
326+
s: KeriSequence::new(5),
331327
},
332328
Receipt {
333-
v: "KERI".into(),
329+
v: VersionString::placeholder(),
334330
t: "rct".into(),
335-
d: Said::new_unchecked("ER2".into()),
336-
i: "W2".into(),
337-
s: 5,
338-
a: Said::new_unchecked("ESAID_B".into()),
339-
sig: vec![0; 64],
331+
d: Said::new_unchecked("ESAID_B".into()),
332+
i: Prefix::new_unchecked("W2".into()),
333+
s: KeriSequence::new(5),
340334
},
341335
];
342336

crates/auths-core/src/witness/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,10 @@ mod server;
8686
mod storage;
8787

8888
// Re-export KERI witness protocol types from auths-keri
89+
pub use auths_keri::KERI_VERSION_PREFIX;
8990
pub use auths_keri::witness::{
90-
AsyncWitnessProvider, DuplicityEvidence, EventHash, EventHashParseError, KERI_VERSION,
91-
NoOpAsyncWitness, RECEIPT_TYPE, Receipt, ReceiptBuilder, WitnessError, WitnessProvider,
91+
AsyncWitnessProvider, DuplicityEvidence, EventHash, EventHashParseError, NoOpAsyncWitness,
92+
RECEIPT_TYPE, Receipt, ReceiptBuilder, SignedReceipt, WitnessError, WitnessProvider,
9293
WitnessReport,
9394
};
9495
pub use noop::NoOpWitness;
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
#[allow(unused_imports)]
2-
pub use auths_keri::witness::{KERI_VERSION, RECEIPT_TYPE, Receipt};
2+
pub use auths_keri::KERI_VERSION_PREFIX;
3+
#[allow(unused_imports)]
4+
pub use auths_keri::witness::{RECEIPT_TYPE, Receipt, SignedReceipt};

crates/auths-core/src/witness/server.rs

Lines changed: 37 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ use chrono::{DateTime, Utc};
3131
use serde::{Deserialize, Serialize};
3232
use std::sync::Mutex;
3333

34+
use auths_keri::{KeriSequence, VersionString};
35+
3436
use super::error::{DuplicityEvidence, WitnessError};
35-
use super::receipt::{KERI_VERSION, RECEIPT_TYPE, Receipt};
37+
use super::receipt::{RECEIPT_TYPE, Receipt, SignedReceipt};
3638
use super::storage::WitnessStorage;
3739

3840
/// Shared server state.
@@ -41,6 +43,7 @@ pub struct WitnessServerState {
4143
inner: Arc<WitnessServerInner>,
4244
}
4345

46+
#[allow(dead_code)]
4447
struct WitnessServerInner {
4548
/// Witness identifier (DID)
4649
witness_did: DeviceDID,
@@ -141,6 +144,7 @@ pub struct ErrorResponse {
141144
pub duplicity: Option<DuplicityEvidence>,
142145
}
143146

147+
#[allow(dead_code)]
144148
impl WitnessServerState {
145149
/// Create a new server state.
146150
#[allow(clippy::disallowed_methods)] // Server constructor is a clock boundary
@@ -212,31 +216,35 @@ impl WitnessServerState {
212216
/// Create a receipt for an event.
213217
fn create_receipt(
214218
&self,
215-
_prefix: &Prefix,
219+
prefix: &Prefix,
216220
seq: u64,
217221
event_said: &Said,
218222
) -> Result<Receipt, WitnessError> {
219-
let mut receipt = Receipt {
220-
v: KERI_VERSION.into(),
223+
let receipt = Receipt {
224+
v: VersionString::placeholder(),
221225
t: RECEIPT_TYPE.into(),
222-
d: Said::default(),
223-
i: self.inner.witness_did.to_string(),
224-
s: seq,
225-
a: event_said.clone(),
226-
sig: vec![],
226+
d: event_said.clone(),
227+
i: prefix.clone(),
228+
s: KeriSequence::new(seq),
227229
};
228230

229-
let receipt_value = serde_json::to_value(&receipt)
230-
.map_err(|e| WitnessError::Serialization(e.to_string()))?;
231-
receipt.d = crate::crypto::said::compute_said(&receipt_value)
232-
.map_err(|e| WitnessError::Serialization(e.to_string()))?;
231+
Ok(receipt)
232+
}
233233

234-
let signing_payload = receipt
235-
.signing_payload()
236-
.map_err(|e| WitnessError::Serialization(e.to_string()))?;
237-
receipt.sig = self.sign_payload(&signing_payload)?;
234+
/// Create a signed receipt for an event.
235+
fn create_signed_receipt(
236+
&self,
237+
prefix: &Prefix,
238+
seq: u64,
239+
event_said: &Said,
240+
) -> Result<SignedReceipt, WitnessError> {
241+
let receipt = self.create_receipt(prefix, seq, event_said)?;
238242

239-
Ok(receipt)
243+
let signing_payload =
244+
serde_json::to_vec(&receipt).map_err(|e| WitnessError::Serialization(e.to_string()))?;
245+
let signature = self.sign_payload(&signing_payload)?;
246+
247+
Ok(SignedReceipt { receipt, signature })
240248
}
241249

242250
/// Sign a payload with the witness Ed25519 keypair.
@@ -930,19 +938,16 @@ mod tests {
930938
}
931939

932940
#[test]
933-
fn receipt_said_is_proper_blake3() {
941+
fn receipt_d_matches_event_said() {
934942
let state = test_state();
935943
let prefix = Prefix::new_unchecked("EPrefix".into());
936-
let receipt = state
937-
.create_receipt(&prefix, 0, &Said::new_unchecked("ESAID123".into()))
938-
.unwrap();
939-
// SAID should be 44 chars: 'E' + 43 base64url chars
940-
assert_eq!(receipt.d.as_str().len(), 44);
941-
assert!(receipt.d.as_str().starts_with('E'));
944+
let event_said = Said::new_unchecked("ESAID123".into());
945+
let receipt = state.create_receipt(&prefix, 0, &event_said).unwrap();
946+
assert_eq!(receipt.d, event_said);
942947
}
943948

944949
#[test]
945-
fn receipt_said_changes_with_inputs() {
950+
fn receipt_d_changes_with_event_said() {
946951
let state = test_state();
947952
let prefix = Prefix::new_unchecked("EPrefix".into());
948953
let receipt_a = state
@@ -952,29 +957,21 @@ mod tests {
952957
.create_receipt(&prefix, 0, &Said::new_unchecked("ESAID_B".into()))
953958
.unwrap();
954959
assert_ne!(receipt_a.d, receipt_b.d);
955-
956-
let receipt_c = state
957-
.create_receipt(&prefix, 0, &Said::new_unchecked("ESAID_A".into()))
958-
.unwrap();
959-
let receipt_d = state
960-
.create_receipt(&prefix, 1, &Said::new_unchecked("ESAID_A".into()))
961-
.unwrap();
962-
assert_ne!(receipt_c.d, receipt_d.d);
963960
}
964961

965962
#[test]
966-
fn receipt_signature_verifies_against_signing_payload() {
963+
fn signed_receipt_signature_verifies() {
967964
let state = test_state();
968965
let prefix = Prefix::new_unchecked("EPrefix".into());
969-
let receipt = state
970-
.create_receipt(&prefix, 0, &Said::new_unchecked("ESAID123".into()))
966+
let signed = state
967+
.create_signed_receipt(&prefix, 0, &Said::new_unchecked("ESAID123".into()))
971968
.unwrap();
972969
let public_key = state.public_key();
973-
let payload = receipt.signing_payload().unwrap();
970+
let payload = serde_json::to_vec(&signed.receipt).unwrap();
974971

975972
let pk = ring::signature::UnparsedPublicKey::new(&ring::signature::ED25519, &public_key);
976-
pk.verify(&payload, &receipt.sig)
977-
.expect("receipt signature should verify against signing_payload");
973+
pk.verify(&payload, &signed.signature)
974+
.expect("signed receipt signature should verify against serialized receipt");
978975
}
979976

980977
#[test]

crates/auths-core/src/witness/storage.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ impl WitnessStorage {
188188

189189
stmt.bind((1, prefix.as_str()))
190190
.map_err(|e| WitnessError::Storage(format!("failed to bind prefix: {}", e)))?;
191-
stmt.bind((2, receipt.a.as_str()))
191+
stmt.bind((2, receipt.d.as_str()))
192192
.map_err(|e| WitnessError::Storage(format!("failed to bind event_said: {}", e)))?;
193193
stmt.bind((3, json.as_str()))
194194
.map_err(|e| WitnessError::Storage(format!("failed to bind json: {}", e)))?;
@@ -309,14 +309,13 @@ mod tests {
309309
}
310310

311311
fn sample_receipt(event_said: &str) -> Receipt {
312+
use auths_keri::{KeriSequence, VersionString};
312313
Receipt {
313-
v: "KERI10JSON".into(),
314+
v: VersionString::placeholder(),
314315
t: "rct".into(),
315-
d: Said::new_unchecked("EReceipt".into()),
316-
i: "did:key:witness".into(),
317-
s: 5,
318-
a: Said::new_unchecked(event_said.into()),
319-
sig: vec![0; 64],
316+
d: Said::new_unchecked(event_said.into()),
317+
i: Prefix::new_unchecked("did:key:witness".into()),
318+
s: KeriSequence::new(5),
320319
}
321320
}
322321

@@ -416,7 +415,7 @@ mod tests {
416415
let result = storage.get_receipt(&p, &said("EEVENT_SAID")).unwrap();
417416
assert!(result.is_some());
418417
let retrieved = result.unwrap();
419-
assert_eq!(retrieved.a, "EEVENT_SAID");
418+
assert_eq!(retrieved.d.as_str(), "EEVENT_SAID");
420419
}
421420

422421
#[test]

crates/auths-core/tests/cases/witness.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,16 @@ async fn http_witness_submit_and_retrieve_receipt() {
7171
.await
7272
.unwrap();
7373

74-
assert_eq!(receipt.a, said);
75-
assert_eq!(receipt.s, 0);
74+
assert_eq!(receipt.d, said);
75+
assert_eq!(receipt.s, auths_keri::KeriSequence::new(0));
7676
assert_eq!(receipt.t, "rct");
7777

7878
// Retrieve receipt
7979
let retrieved = client.get_receipt("ETestPrefix", &said).await.unwrap();
8080

8181
assert!(retrieved.is_some());
8282
let retrieved = retrieved.unwrap();
83-
assert_eq!(retrieved.a, said);
83+
assert_eq!(retrieved.d, said);
8484
}
8585

8686
#[tokio::test]

0 commit comments

Comments
 (0)