use crate::primitives::Hash;
use crate::Result;
pub use crate::primitives::domain::DS_BIND;
pub fn compute_beta<H: Hash>(domain: &[u8], r: &[u8], op_hash: &[u8; 32]) -> [u8; 32] {
H::hash_slices(&[domain, r, op_hash])
}
pub fn compute_beta_from_canonical<H: Hash>(
domain: &[u8],
r: &[u8],
canonical_o: &[u8],
) -> [u8; 32] {
let op_hash = H::hash(canonical_o);
compute_beta::<H>(domain, r, &op_hash)
}
pub fn compute_beta_for_op<H: Hash>(
domain: &[u8],
r: &[u8],
op: &crate::Operation,
) -> Result<[u8; 32]> {
let canonical = op.canonical_bytes()?;
Ok(compute_beta_from_canonical::<H>(domain, r, &canonical))
}
pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
use subtle::ConstantTimeEq;
a.ct_eq(b).into()
}
#[cfg(all(test, feature = "std-primitives"))]
mod tests {
use super::*;
use crate::primitives::Sha256;
use crate::{Act, ActType, Bind, Operation, Valid};
fn op(target: &str) -> Operation {
Operation {
act: Act {
kind: ActType::Use,
target: target.into(),
scope: serde_json::json!({}),
},
bind: Bind {
redeemer: "T".into(),
recipient: None,
},
valid: Valid::single_use(0, None),
}
}
#[test]
fn beta_is_deterministic() {
let r = [0xAAu8; 16];
let o = op("x");
let a = compute_beta_for_op::<Sha256>(DS_BIND, &r, &o).unwrap();
let b = compute_beta_for_op::<Sha256>(DS_BIND, &r, &o).unwrap();
assert_eq!(a, b);
}
#[test]
fn beta_changes_with_op() {
let r = [0xAAu8; 16];
let a = compute_beta_for_op::<Sha256>(DS_BIND, &r, &op("x")).unwrap();
let b = compute_beta_for_op::<Sha256>(DS_BIND, &r, &op("y")).unwrap();
assert_ne!(a, b);
}
#[test]
fn beta_changes_with_r() {
let o = op("x");
let a = compute_beta_for_op::<Sha256>(DS_BIND, &[0u8; 16], &o).unwrap();
let b = compute_beta_for_op::<Sha256>(DS_BIND, &[1u8; 16], &o).unwrap();
assert_ne!(a, b);
}
#[test]
fn beta_changes_with_domain() {
let r = [0xAAu8; 16];
let o = op("x");
let a = compute_beta_for_op::<Sha256>(DS_BIND, &r, &o).unwrap();
let b = compute_beta_for_op::<Sha256>(b"profile/v1/other", &r, &o).unwrap();
assert_ne!(a, b);
}
#[test]
fn beta_matches_ts_authorizer_conformance_vector() {
let r = [0u8; 32];
let o = Operation {
act: Act {
kind: ActType::Use,
target: "env.api_key".into(),
scope: serde_json::json!({}),
},
bind: Bind {
redeemer: "custodian-id".into(),
recipient: None,
},
valid: Valid::single_use(1_700_000_000, None),
};
let canonical = o.canonical_bytes().unwrap();
let canonical_str = std::str::from_utf8(&canonical).unwrap();
assert_eq!(
canonical_str,
"{\"act\":{\"scope\":{},\"target\":\"env.api_key\",\"type\":\"use\"},\
\"bind\":{\"redeemer\":\"custodian-id\"},\
\"valid\":{\"iat\":1700000000,\"multiplicity\":\"one\"}}",
"canonical-encoder shape changed; TS conformance vector must change too"
);
let beta = compute_beta_for_op::<Sha256>(DS_BIND, &r, &o).unwrap();
let hex: String = beta.iter().map(|b| format!("{:02x}", b)).collect();
assert_eq!(
hex,
"6c43ba079b5316ac73e8f35e3ce59bfdefb9dee1fc964fcb39406c26169be954"
);
}
}