use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use serde_json::{json, Map, Value};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Uncertainty {
pub relative_ppm: u32,
pub window_us: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CounterFiltering {
Unknown,
Off,
On,
}
impl CounterFiltering {
pub fn label(self) -> &'static str {
match self {
CounterFiltering::Unknown => "Unknown",
CounterFiltering::Off => "Off",
CounterFiltering::On => "On",
}
}
pub fn from_label(s: &str) -> Option<CounterFiltering> {
match s {
"Unknown" => Some(CounterFiltering::Unknown),
"Off" => Some(CounterFiltering::Off),
"On" => Some(CounterFiltering::On),
_ => None,
}
}
fn tag_byte(self) -> u8 {
match self {
CounterFiltering::Unknown => 0,
CounterFiltering::Off => 1,
CounterFiltering::On => 2,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EnergyClass {
ModelBased,
Estimator,
OnChipCounter { uncertainty: Uncertainty, filtering: CounterFiltering },
CalibratedInstrument { uncertainty: Uncertainty, calibration_ref: String },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ClassError {
NoUncertainty,
NoWindow,
NoCalibrationRef,
UnknownLabel,
UnexpectedEvidence,
LegacyLabel,
NoFigure,
SignerMismatch,
EntryMismatch,
BelowResolution,
}
impl core::fmt::Display for ClassError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(match self {
ClassError::NoUncertainty => "a hardware energy class must declare a non-zero relative uncertainty",
ClassError::NoWindow => "a declared uncertainty must state the integration window it holds over",
ClassError::NoCalibrationRef => "a calibrated-instrument figure must reference its calibration certificate",
ClassError::UnknownLabel => "unknown acquisition class",
ClassError::UnexpectedEvidence => "evidence fields attached to a class that does not carry them",
ClassError::LegacyLabel => "HwShunt is a legacy label and cannot label a receipt that never carried it",
ClassError::NoFigure => "a class beside a zero figure claims a measurement that did not occur",
ClassError::SignerMismatch => "only the key that signed a receipt may label it",
ClassError::EntryMismatch => "a labelled bill must give one class slot per entry and label at least one",
ClassError::BelowResolution => "the declared uncertainty is below the 0.5/√3 µJ (about 0.289 µJ) that rounding the figure to whole microjoules adds: joules_micro × relative_ppm must be at least 288 676",
})
}
}
impl std::error::Error for ClassError {}
impl EnergyClass {
pub fn label(&self) -> &'static str {
match self {
EnergyClass::ModelBased => "ModelBased",
EnergyClass::Estimator => "Estimator",
EnergyClass::OnChipCounter { .. } => "OnChipCounter",
EnergyClass::CalibratedInstrument { .. } => "CalibratedInstrument",
}
}
pub fn tag_byte(&self) -> u8 {
match self {
EnergyClass::ModelBased => 2,
EnergyClass::Estimator => 3,
EnergyClass::OnChipCounter { .. } => 4,
EnergyClass::CalibratedInstrument { .. } => 5,
}
}
pub fn uncertainty(&self) -> Option<Uncertainty> {
match self {
EnergyClass::OnChipCounter { uncertainty, .. }
| EnergyClass::CalibratedInstrument { uncertainty, .. } => Some(*uncertainty),
_ => None,
}
}
pub fn validate(&self) -> Result<(), ClassError> {
let check = |u: &Uncertainty| {
if u.relative_ppm == 0 {
Err(ClassError::NoUncertainty)
} else if u.window_us == 0 {
Err(ClassError::NoWindow)
} else {
Ok(())
}
};
match self {
EnergyClass::OnChipCounter { uncertainty, .. } => check(uncertainty),
EnergyClass::CalibratedInstrument { uncertainty, calibration_ref } => {
check(uncertainty)?;
if calibration_ref.trim().is_empty() {
return Err(ClassError::NoCalibrationRef);
}
Ok(())
}
_ => Ok(()),
}
}
pub fn write_signed(&self, o: &mut Vec<u8>) {
o.push(self.tag_byte());
match self {
EnergyClass::OnChipCounter { uncertainty, filtering } => {
o.extend_from_slice(&uncertainty.relative_ppm.to_be_bytes());
o.extend_from_slice(&uncertainty.window_us.to_be_bytes());
o.push(filtering.tag_byte());
}
EnergyClass::CalibratedInstrument { uncertainty, calibration_ref } => {
o.extend_from_slice(&uncertainty.relative_ppm.to_be_bytes());
o.extend_from_slice(&uncertainty.window_us.to_be_bytes());
o.extend_from_slice(&(calibration_ref.len() as u32).to_be_bytes());
o.extend_from_slice(calibration_ref.as_bytes());
}
_ => {}
}
}
pub fn from_parts(
label: &str,
uncertainty: Option<Uncertainty>,
filtering: Option<CounterFiltering>,
calibration_ref: Option<&str>,
) -> Result<EnergyClass, ClassError> {
let unrefined = |c: EnergyClass| {
if uncertainty.is_some() || filtering.is_some() || calibration_ref.is_some() {
Err(ClassError::UnexpectedEvidence)
} else {
Ok(c)
}
};
let c = match label {
"HwShunt" => return Err(ClassError::LegacyLabel),
"ModelBased" => unrefined(EnergyClass::ModelBased)?,
"Estimator" => unrefined(EnergyClass::Estimator)?,
"OnChipCounter" => {
if calibration_ref.is_some() {
return Err(ClassError::UnexpectedEvidence);
}
EnergyClass::OnChipCounter {
uncertainty: uncertainty.ok_or(ClassError::NoUncertainty)?,
filtering: filtering.unwrap_or(CounterFiltering::Unknown),
}
}
"CalibratedInstrument" => {
if filtering.is_some() {
return Err(ClassError::UnexpectedEvidence);
}
EnergyClass::CalibratedInstrument {
uncertainty: uncertainty.ok_or(ClassError::NoUncertainty)?,
calibration_ref: calibration_ref.ok_or(ClassError::NoCalibrationRef)?.to_owned(),
}
}
_ => return Err(ClassError::UnknownLabel),
};
c.validate()?;
Ok(c)
}
pub fn json_fields(&self) -> Vec<(&'static str, Value)> {
let mut f = vec![("energy_provenance", Value::from(self.label()))];
match self {
EnergyClass::OnChipCounter { uncertainty, filtering } => {
f.push(("energy_uncertainty", json!({"relative_ppm": uncertainty.relative_ppm, "window_us": uncertainty.window_us})));
f.push(("energy_counter_filtering", Value::from(filtering.label())));
}
EnergyClass::CalibratedInstrument { uncertainty, calibration_ref } => {
f.push(("energy_uncertainty", json!({"relative_ppm": uncertainty.relative_ppm, "window_us": uncertainty.window_us})));
f.push(("energy_calibration_ref", Value::from(calibration_ref.as_str())));
}
_ => {}
}
f
}
pub fn from_json_object(o: &Map<String, Value>) -> Result<Option<EnergyClass>, ClassError> {
let present = |k: &str| !matches!(o.get(k), None | Some(Value::Null));
let label = match o.get("energy_provenance") {
None | Some(Value::Null) => {
if present("energy_uncertainty") || present("energy_counter_filtering") || present("energy_calibration_ref") {
return Err(ClassError::UnexpectedEvidence);
}
return Ok(None);
}
Some(Value::String(s)) => s.as_str(),
Some(_) => return Err(ClassError::UnknownLabel),
};
let uncertainty = match o.get("energy_uncertainty") {
None | Some(Value::Null) => None,
Some(Value::Object(u)) => {
let ppm = u.get("relative_ppm").and_then(Value::as_u64).ok_or(ClassError::NoUncertainty)?;
let window_us = u.get("window_us").and_then(Value::as_u64).ok_or(ClassError::NoWindow)?;
Some(Uncertainty { relative_ppm: u32::try_from(ppm).map_err(|_| ClassError::NoUncertainty)?, window_us })
}
Some(_) => return Err(ClassError::NoUncertainty),
};
let filtering = match o.get("energy_counter_filtering") {
None | Some(Value::Null) => None,
Some(Value::String(s)) => Some(CounterFiltering::from_label(s).ok_or(ClassError::UnknownLabel)?),
Some(_) => return Err(ClassError::UnknownLabel),
};
let calibration_ref = match o.get("energy_calibration_ref") {
None | Some(Value::Null) => None,
Some(Value::String(s)) => Some(s.as_str()),
Some(_) => return Err(ClassError::NoCalibrationRef),
};
EnergyClass::from_parts(label, uncertainty, filtering, calibration_ref).map(Some)
}
}
pub const RESOLUTION_FLOOR_UJ_PPM: u64 = 288_676;
pub fn check_label(joules_micro: u64, class: &EnergyClass) -> Result<(), ClassError> {
if joules_micro == 0 {
return Err(ClassError::NoFigure);
}
class.validate()?;
match class.uncertainty() {
Some(u) if (joules_micro as u128) * (u.relative_ppm as u128) < RESOLUTION_FLOOR_UJ_PPM as u128 => {
Err(ClassError::BelowResolution)
}
_ => Ok(()),
}
}
pub(crate) fn labelled_domain(domain: &[u8], label: Option<&EnergyClass>) -> Vec<u8> {
let mut d = domain.to_vec();
if label.is_some() {
match d.last_mut() {
Some(v) if *v == 0x01 => *v = 0x02,
_ => panic!("a labelled signing domain must end in the legacy version byte 0x01"),
}
}
d
}
pub(crate) fn write_label(label: Option<&EnergyClass>, o: &mut Vec<u8>) {
if let Some(c) = label {
c.write_signed(o);
}
}
pub(crate) fn insert_sorted(obj: Map<String, Value>, mut fields: Vec<(&'static str, Value)>) -> Map<String, Value> {
fields.sort_by(|a, b| a.0.cmp(b.0));
let mut pending = fields.into_iter().peekable();
let mut out = Map::new();
for (i, (k, v)) in obj.into_iter().enumerate() {
if !(i == 0 && k == "kind") {
while let Some((nk, _)) = pending.peek() {
if *nk < k.as_str() {
let (nk, nv) = pending.next().unwrap();
out.insert(nk.to_string(), nv);
} else {
break;
}
}
}
out.insert(k, v);
}
for (nk, nv) in pending {
out.insert(nk.to_string(), nv);
}
out
}
fn hx(b: &[u8]) -> String {
b.iter().map(|x| format!("{x:02x}")).collect()
}
pub(crate) mod sealed {
pub trait Sealed {}
}
pub trait Labellable: sealed::Sealed + Clone + core::fmt::Debug + PartialEq + Sized {
#[doc(hidden)]
fn labelled_payload(&self, label: Option<&EnergyClass>) -> Vec<u8>;
#[doc(hidden)]
fn id_domain(&self) -> &'static [u8];
#[doc(hidden)]
fn figure(&self) -> u64;
#[doc(hidden)]
fn body_ok(&self) -> bool;
#[doc(hidden)]
fn signer_key(&self) -> [u8; 32];
#[doc(hidden)]
fn signature(&self) -> [u8; 64];
#[doc(hidden)]
fn set_signature(&mut self, sig: [u8; 64]);
#[doc(hidden)]
fn unlabelled_json(&self) -> String;
#[doc(hidden)]
fn parse_unlabelled_json(s: &str) -> Option<Self>;
#[doc(hidden)]
fn unlabelled_verify(&self) -> bool;
#[doc(hidden)]
fn unlabelled_receipt_hash(&self) -> [u8; 32];
}
macro_rules! labellable {
($t:ty, $id:expr) => {
impl $crate::quantum_energy::sealed::Sealed for $t {}
impl $crate::quantum_energy::Labellable for $t {
fn labelled_payload(&self, label: Option<&$crate::quantum_energy::EnergyClass>) -> Vec<u8> {
self.payload_with(label)
}
fn id_domain(&self) -> &'static [u8] {
$id
}
fn figure(&self) -> u64 {
self.joules_micro
}
fn body_ok(&self) -> bool {
self.body_verifies()
}
fn signer_key(&self) -> [u8; 32] {
self.signer_pubkey
}
fn signature(&self) -> [u8; 64] {
self.sig
}
fn set_signature(&mut self, sig: [u8; 64]) {
self.sig = sig;
}
fn unlabelled_json(&self) -> String {
self.to_json()
}
fn parse_unlabelled_json(s: &str) -> Option<Self> {
Self::from_json(s)
}
fn unlabelled_verify(&self) -> bool {
self.verify()
}
fn unlabelled_receipt_hash(&self) -> [u8; 32] {
self.receipt_hash()
}
}
};
}
pub(crate) use labellable;
#[derive(Clone, Debug, PartialEq)]
pub struct Labelled<R> {
pub receipt: R,
pub energy_class: EnergyClass,
}
impl<R: Labellable> Labelled<R> {
pub fn seal(mut receipt: R, energy_class: EnergyClass, signer: &SigningKey) -> Result<Labelled<R>, ClassError> {
if signer.verifying_key().to_bytes() != receipt.signer_key() {
return Err(ClassError::SignerMismatch);
}
check_label(receipt.figure(), &energy_class)?;
let sig = signer.sign(&receipt.labelled_payload(Some(&energy_class))).to_bytes();
receipt.set_signature(sig);
Ok(Labelled { receipt, energy_class })
}
pub fn verify(&self) -> bool {
if check_label(self.receipt.figure(), &self.energy_class).is_err() || !self.receipt.body_ok() {
return false;
}
let Ok(k) = VerifyingKey::from_bytes(&self.receipt.signer_key()) else {
return false;
};
k.verify(
&self.receipt.labelled_payload(Some(&self.energy_class)),
&Signature::from_bytes(&self.receipt.signature()),
)
.is_ok()
}
pub fn receipt_hash(&self) -> [u8; 32] {
let mut h = blake3::Hasher::new();
h.update(self.receipt.id_domain());
h.update(&self.receipt.labelled_payload(Some(&self.energy_class)));
h.update(&self.receipt.signature());
*h.finalize().as_bytes()
}
pub fn to_json(&self) -> String {
let v: Value = serde_json::from_str(&self.receipt.unlabelled_json()).expect("a receipt renders valid JSON");
let Value::Object(mut obj) = v else { panic!("a receipt renders a JSON object") };
if obj.contains_key("receipt_hash") {
obj.insert("receipt_hash".into(), Value::from(hx(&self.receipt_hash())));
}
Value::Object(insert_sorted(obj, self.energy_class.json_fields())).to_string()
}
pub fn from_json(s: &str) -> Option<Labelled<R>> {
let v: Value = serde_json::from_str(s).ok()?;
let energy_class = EnergyClass::from_json_object(v.as_object()?).ok()??;
let receipt = R::parse_unlabelled_json(s)?;
Some(Labelled { receipt, energy_class })
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum MaybeLabelled<R> {
Unlabelled(R),
Labelled(Labelled<R>),
}
impl<R: Labellable> MaybeLabelled<R> {
pub fn from_json(s: &str) -> Option<MaybeLabelled<R>> {
let v: Value = serde_json::from_str(s).ok()?;
match EnergyClass::from_json_object(v.as_object()?) {
Ok(None) => R::parse_unlabelled_json(s).map(MaybeLabelled::Unlabelled),
Ok(Some(_)) => Labelled::from_json(s).map(MaybeLabelled::Labelled),
Err(_) => None,
}
}
pub fn verify(&self) -> bool {
match self {
MaybeLabelled::Unlabelled(r) => r.unlabelled_verify(),
MaybeLabelled::Labelled(l) => l.verify(),
}
}
pub fn receipt(&self) -> &R {
match self {
MaybeLabelled::Unlabelled(r) => r,
MaybeLabelled::Labelled(l) => &l.receipt,
}
}
pub fn energy_class(&self) -> Option<&EnergyClass> {
match self {
MaybeLabelled::Unlabelled(_) => None,
MaybeLabelled::Labelled(l) => Some(&l.energy_class),
}
}
pub fn receipt_hash(&self) -> [u8; 32] {
match self {
MaybeLabelled::Unlabelled(r) => r.unlabelled_receipt_hash(),
MaybeLabelled::Labelled(l) => l.receipt_hash(),
}
}
pub fn to_json(&self) -> String {
match self {
MaybeLabelled::Unlabelled(r) => r.unlabelled_json(),
MaybeLabelled::Labelled(l) => l.to_json(),
}
}
}
impl<R> From<Labelled<R>> for MaybeLabelled<R> {
fn from(l: Labelled<R>) -> MaybeLabelled<R> {
MaybeLabelled::Labelled(l)
}
}
#[cfg(feature = "quantum_ops")]
impl MaybeLabelled<crate::quantum_ops::JobReceipt> {
pub fn verify_against_calibration(&self, cal: &MaybeLabelled<crate::quantum_ops::CalibrationReceipt>) -> bool {
self.receipt().calibration_ref == Some(cal.receipt_hash()) && cal.verify() && self.verify()
}
}
#[cfg(feature = "quantum_receipt")]
impl MaybeLabelled<crate::quantum_receipt::QuantumReceipt> {
pub fn verify_reconstruction(&self, circuit: &crate::quantum::Circuit) -> bool {
match self {
MaybeLabelled::Unlabelled(r) => r.verify_reconstruction(circuit),
MaybeLabelled::Labelled(l) => l.verify_reconstruction(circuit),
}
}
}
#[cfg(feature = "quantum_receipt")]
impl Labelled<crate::quantum_receipt::QuantumReceipt> {
pub fn verify_reconstruction(&self, circuit: &crate::quantum::Circuit) -> bool {
self.receipt.reconstruction_matches(circuit) && self.verify()
}
}
#[cfg(feature = "quantum_qbom")]
#[derive(Clone, Debug, PartialEq)]
pub struct LabelledQbom {
pub qbom: crate::quantum_qbom::Qbom,
pub classes: Vec<Option<EnergyClass>>,
}
#[cfg(feature = "quantum_qbom")]
impl LabelledQbom {
fn check(&self) -> Result<(), ClassError> {
if self.classes.len() != self.qbom.entries.len() || self.classes.iter().all(Option::is_none) {
return Err(ClassError::EntryMismatch);
}
for (e, c) in self.qbom.entries.iter().zip(&self.classes) {
if let Some(c) = c {
check_label(e.joules_micro, c)?;
}
}
Ok(())
}
pub fn seal(
mut qbom: crate::quantum_qbom::Qbom,
classes: Vec<Option<EnergyClass>>,
signer: &SigningKey,
) -> Result<LabelledQbom, ClassError> {
if signer.verifying_key().to_bytes() != qbom.signer_pubkey {
return Err(ClassError::SignerMismatch);
}
let probe = LabelledQbom { qbom: qbom.clone(), classes };
probe.check()?;
qbom.sig = signer.sign(&qbom.labelled_signing_payload(&probe.classes)).to_bytes();
Ok(LabelledQbom { qbom, classes: probe.classes })
}
pub fn verify(&self) -> bool {
if self.check().is_err() {
return false;
}
let Ok(k) = VerifyingKey::from_bytes(&self.qbom.signer_pubkey) else {
return false;
};
k.verify(&self.qbom.labelled_signing_payload(&self.classes), &Signature::from_bytes(&self.qbom.sig))
.is_ok()
}
pub fn provenance_root(&self) -> [u8; 32] {
*blake3::hash(&self.qbom.labelled_body(&self.classes)).as_bytes()
}
pub fn receipt_hash(&self) -> [u8; 32] {
let mut h = blake3::Hasher::new();
h.update(crate::quantum_qbom::DOMAIN_QBOM_ID);
h.update(&self.qbom.labelled_signing_payload(&self.classes));
h.update(&self.qbom.sig);
*h.finalize().as_bytes()
}
pub fn binds(&self, receipts: &[([u8; 32], bool, Option<&EnergyClass>)]) -> bool {
if !self.verify() || receipts.len() != self.qbom.entries.len() {
return false;
}
self.qbom.entries.iter().zip(&self.classes).all(|(e, c)| {
receipts.iter().any(|(h, ok, rc)| *h == e.receipt_hash && *ok && *rc == c.as_ref())
})
}
pub fn to_json(&self) -> String {
let v: Value = serde_json::from_str(&self.qbom.to_json()).expect("a qbom renders valid JSON");
let Value::Object(mut obj) = v else { panic!("a qbom renders a JSON object") };
if let Some(Value::Array(entries)) = obj.get_mut("entries") {
for (e, c) in entries.iter_mut().zip(&self.classes) {
if let (Value::Object(eo), Some(c)) = (e, c) {
let taken = std::mem::take(eo);
*eo = insert_sorted(taken, c.json_fields());
}
}
}
obj.insert("provenance_root".into(), Value::from(hx(&self.provenance_root())));
obj.insert("receipt_hash".into(), Value::from(hx(&self.receipt_hash())));
Value::Object(obj).to_string()
}
pub fn from_json(s: &str) -> Option<LabelledQbom> {
let v: Value = serde_json::from_str(s).ok()?;
let mut classes = Vec::new();
for e in v.as_object()?.get("entries")?.as_array()? {
classes.push(EnergyClass::from_json_object(e.as_object()?).ok()?);
}
if classes.iter().all(Option::is_none) {
return None;
}
let qbom = crate::quantum_qbom::Qbom::from_json(s)?;
Some(LabelledQbom { qbom, classes })
}
}
#[cfg(test)]
mod tests {
use super::*;
fn chip(ppm: u32) -> EnergyClass {
EnergyClass::OnChipCounter { uncertainty: Uncertainty { relative_ppm: ppm, window_us: 2_977_104 }, filtering: CounterFiltering::Unknown }
}
#[test]
fn encoding_matches_the_wai_vocabulary() {
let mut o = Vec::new();
chip(20_000).write_signed(&mut o);
let mut want = vec![4u8];
want.extend_from_slice(&20_000u32.to_be_bytes());
want.extend_from_slice(&2_977_104u64.to_be_bytes());
want.push(0);
assert_eq!(o, want);
let mut o = Vec::new();
EnergyClass::ModelBased.write_signed(&mut o);
EnergyClass::Estimator.write_signed(&mut o);
assert_eq!(o, vec![2, 3]);
let c = EnergyClass::CalibratedInstrument { uncertainty: Uncertainty { relative_ppm: 5_000, window_us: 60_000_000 }, calibration_ref: "cert-1".into() };
let mut o = Vec::new();
c.write_signed(&mut o);
assert_eq!(o[0], 5);
assert_eq!(&o[13..17], &6u32.to_be_bytes());
assert_eq!(&o[17..], b"cert-1");
}
#[test]
fn labels_are_refused_where_they_would_mislead() {
assert_eq!(check_label(0, &chip(1)), Err(ClassError::NoFigure));
assert_eq!(check_label(5, &chip(0)), Err(ClassError::NoUncertainty));
assert_eq!(check_label(1, &chip(288_675)), Err(ClassError::BelowResolution));
assert_eq!(check_label(1, &chip(288_676)), Ok(()));
assert_eq!(check_label(288_675, &chip(1)), Err(ClassError::BelowResolution));
assert_eq!(check_label(288_676, &chip(1)), Ok(()));
assert_eq!(check_label(u64::MAX, &chip(u32::MAX)), Ok(()), "no overflow");
assert_eq!(check_label(1, &EnergyClass::ModelBased), Ok(()), "no declaration, nothing to cover");
assert_eq!(EnergyClass::from_parts("HwShunt", None, None, None), Err(ClassError::LegacyLabel));
assert_eq!(EnergyClass::from_parts("OnChipCounter", None, None, None), Err(ClassError::NoUncertainty));
assert_eq!(
EnergyClass::from_parts("ModelBased", Some(Uncertainty { relative_ppm: 1, window_us: 1 }), None, None),
Err(ClassError::UnexpectedEvidence)
);
let parse = |s: &str| {
let v: Value = serde_json::from_str(s).unwrap();
EnergyClass::from_json_object(v.as_object().unwrap())
};
assert_eq!(parse(r#"{"joules_micro":5}"#), Ok(None));
assert_eq!(parse(r#"{"energy_counter_filtering":"Off"}"#), Err(ClassError::UnexpectedEvidence));
assert_eq!(parse(r#"{"calibration_ref":"d2d8"}"#), Ok(None));
assert_eq!(
parse(r#"{"energy_provenance":"OnChipCounter","energy_uncertainty":{"relative_ppm":7,"window_us":9}}"#),
Ok(Some(EnergyClass::OnChipCounter { uncertainty: Uncertainty { relative_ppm: 7, window_us: 9 }, filtering: CounterFiltering::Unknown }))
);
}
#[test]
fn a_labelled_domain_changes_only_the_version_byte() {
let d = b"wai:quantum-receipt\x01";
assert_eq!(labelled_domain(d, None), d.to_vec());
let l = labelled_domain(d, Some(&EnergyClass::Estimator));
assert_eq!(&l[..l.len() - 1], &d[..d.len() - 1]);
assert_eq!(l.last(), Some(&0x02));
}
}