1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//! Domain-authority publishing — the pure wire protocol shared by the signer
//! (CLI) and the verifier (registry).
//!
//! The signed-payload format and the header/algorithm names are load-bearing:
//! the CLI signs over exactly these bytes and the registry verifies over them.
//! They live here, in the foundational crate both sides already depend on, so a
//! change cannot drift one side out of agreement with the other. This module is
//! pure — no crypto, no I/O — so it carries no extra dependency weight.
/// The fixed path the proof manifest is served at on the publisher's own
/// domain. Distinct from a deployment's discovery manifest
/// (`memstead-authority.json`): the two make different claims.
pub const MANIFEST_PATH: &str = "/.well-known/memstead-publishing.json";
/// The only signature algorithm supported at launch. The prefix is part of
/// every key/signature string (`ed25519:<base64>`).
pub const ALG: &str = "ed25519";
/// Domain-separation prefix on the signed payload. Binds a signature to *this*
/// protocol and version so a signature made for some other purpose with the
/// same key can never be replayed as a Memstead publish authorisation. Bump the
/// version suffix if the payload shape ever changes.
pub const SIGNING_DOMAIN: &str = "memstead-domain-publish-v1";
/// Request headers carrying the per-publish signature. The publish handler
/// reads these for a `<domain>:<handle>` scope; the CLI sets them.
pub const HEADER_KEY: &str = "x-memstead-domain-key";
pub const HEADER_SIGNATURE: &str = "x-memstead-domain-signature";
pub const HEADER_TIMESTAMP: &str = "x-memstead-domain-timestamp";
/// The exact bytes a publish signature covers. Binds the signature to *this*
/// upload and target so a captured signature cannot be replayed onto a
/// different archive, scope, name, version, or far-off time.
///
/// The serialisation is a newline-joined, domain-separated concatenation:
///
/// ```text
/// memstead-domain-publish-v1\n
/// <content_sha256>\n (lowercase hex of the canonical archive bytes)
/// <scope>\n (canonical, e.g. acme.com:payments)
/// <name>\n
/// <version>\n
/// <timestamp> (unix seconds, decimal)
/// ```
///
/// The same layout, executable:
///
/// ```
/// use memstead_base::domain_authority_wire::signing_payload;
///
/// let bytes = signing_payload("aa11", "acme.com:payments", "billing", "1.2.0", 1755600000);
/// assert_eq!(
/// String::from_utf8(bytes).unwrap(),
/// "memstead-domain-publish-v1\naa11\nacme.com:payments\nbilling\n1.2.0\n1755600000",
/// );
/// ```
///
/// Every field is mandatory and both the signer and the verifier MUST produce
/// these bytes identically. None of the fields can contain a newline (hex,
/// scope, name, version, and a decimal integer are all newline-free), so the
/// join is unambiguous.