pub const FORMAT: &str = "amont-attest-v2";
pub const NOTES_REF: &str = "amont-attest";
pub const INPUTS_REF: &str = "amont-attest-inputs";
pub const LOCAL_PREFIX: &str = "attest-local/";
pub const NAMESPACE: &str = "amont-attest";
pub const MAX_BLOCKS: usize = 32;
pub(crate) const BEGIN: &str = "-----BEGIN SSH SIGNATURE-----";
pub(crate) const END: &str = "-----END SSH SIGNATURE-----";
pub fn platform() -> String {
format!("{}-{}", std::env::consts::ARCH, std::env::consts::OS)
}
#[derive(Debug, PartialEq)]
pub struct Block {
pub payload: String,
pub signature: String,
}
pub fn split_blocks(body: &str) -> (Vec<Block>, bool) {
enum State {
Between,
Payload,
WaitingForSignature,
Signature,
}
let mut blocks = Vec::new();
let mut state = State::Between;
let mut payload = String::new();
let mut signature = String::new();
let close = |blocks: &mut Vec<Block>, payload: &str, signature: &str| -> bool {
if blocks.len() == MAX_BLOCKS {
return true;
}
blocks.push(Block {
payload: payload.to_string(),
signature: signature.to_string(),
});
false
};
for line in body.split('\n') {
match state {
State::Between => {
if line.is_empty() {
continue;
}
payload.clear();
payload.push_str(line);
payload.push('\n');
state = State::Payload;
}
State::Payload => {
if line.is_empty() {
state = State::WaitingForSignature;
} else {
payload.push_str(line);
payload.push('\n');
}
}
State::WaitingForSignature => {
if line.is_empty() {
continue;
}
if line != BEGIN {
return (blocks, false);
}
signature.clear();
signature.push_str(line);
signature.push('\n');
state = State::Signature;
}
State::Signature => {
signature.push_str(line);
signature.push('\n');
if line == END {
if close(&mut blocks, &payload, &signature) {
return (blocks, true);
}
state = State::Between;
}
}
}
}
if let State::Signature = state {
if close(&mut blocks, &payload, &signature) {
return (blocks, true);
}
}
(blocks, false)
}
pub fn split_note(body: &str) -> Option<(String, String)> {
split_blocks(body)
.0
.into_iter()
.next()
.map(|b| (b.payload, b.signature))
}
pub fn platform_matches(want: Option<&str>, ran_on: &str) -> bool {
match want {
None => true,
Some(want) if want.contains('-') => want == ran_on,
Some(want) => ran_on.rsplit_once('-').is_some_and(|(_, os)| os == want),
}
}
pub fn field<'a>(payload: &'a str, name: &str) -> Option<&'a str> {
payload
.split('\n')
.find_map(|l| l.strip_prefix(name).and_then(|r| r.strip_prefix(' ')))
.map(|v| v.trim_start_matches(' '))
}
pub fn is_oid(s: &str) -> bool {
(s.len() == 40 || s.len() == 64)
&& s.bytes()
.all(|b| b.is_ascii_hexdigit() && !b.is_ascii_uppercase())
}
pub fn input_fp<'a>(payload: &'a str, gate: &str) -> Option<&'a str> {
payload.split('\n').find_map(|line| {
let f: Vec<&str> = line.split([' ', '\t']).filter(|t| !t.is_empty()).collect();
(f.len() == 3 && f[0] == "input" && f[1] == gate && is_oid(f[2])).then(|| f[2])
})
}
pub fn canon_claim(payload: &str) -> Option<&str> {
payload.split('\n').find_map(|line| {
let f: Vec<&str> = line.split([' ', '\t']).filter(|t| !t.is_empty()).collect();
(f.len() == 2 && f[0] == "canon" && is_oid(f[1])).then(|| f[1])
})
}
pub fn payload(
tree: &str,
gates: &str,
platform: &str,
inputs: &[(String, String)],
canon: Option<&str>,
producer: &str,
) -> String {
let mut out = format!("{FORMAT}\ntree {tree}\ngates {gates}\nplatform {platform}\n");
for (gate, fp) in inputs {
out.push_str(&format!("input {gate} {fp}\n"));
}
if let Some(c) = canon {
out.push_str(&format!("canon {c}\n"));
}
out.push_str(&format!("amont {producer}\n"));
out
}
pub fn block(payload: &str, signature: &str) -> String {
format!(
"{}\n\n{}",
payload.trim_end_matches('\n'),
signature.trim_end_matches('\n')
)
}