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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026 DeRec Alliance. All rights reserved.
use crateTransportProtocolExt as _;
use crate::;
use ;
use Message;
/// Produces an unpair request envelope asking the peer to drop all state
/// associated with this channel.
///
/// Either party — **Owner** or **Helper** — may call this to terminate a paired
/// channel. The envelope is symmetrically encrypted with the channel's
/// `shared_key`, so pairing must already be complete (i.e. the symmetric key
/// is established) for this primitive to be meaningful.
///
/// This function:
///
/// 1. Builds an [`derec_proto::UnpairRequestMessage`] carrying the current
/// timestamp and the application-supplied `memo`
/// 2. Serializes the inner message and encrypts it with `shared_key`
/// 3. Wraps the ciphertext into a plain outer [`derec_proto::DeRecMessage`]
/// envelope, copying the same timestamp
/// 4. Returns the serialized envelope bytes ready to send over the transport
///
/// # Arguments
///
/// * `channel_id` - Channel identifier for the paired peer.
/// * `memo` - Optional human-readable reason embedded in the request. Used
/// for logging / display only; the protocol attaches no semantics to it.
/// Pass an empty string when no reason is offered.
/// * `shared_key` - Previously established 32-byte symmetric channel key used
/// to encrypt the inner request.
///
/// # Returns
///
/// On success returns [`ProduceResult`] containing:
///
/// - `envelope`: serialized outer [`derec_proto::DeRecMessage`] bytes
/// carrying an encrypted inner [`derec_proto::UnpairRequestMessage`]
///
/// # Errors
///
/// Returns [`crate::Error`] if outer envelope construction or symmetric
/// encryption fails.
///
/// # Security Notes
///
/// - The outer envelope timestamp equals the inner request timestamp,
/// preserving the invariant `envelope.timestamp == request.timestamp`.
/// - The `memo` is sent in the clear inside the encrypted body; do not embed
/// secrets there.
///
/// # Example
///
/// ```
/// use derec_library::primitives::unpairing::request;
/// use derec_library::types::ChannelId;
///
/// let channel_id = ChannelId(42);
/// let shared_key = [7u8; 32];
///
/// let result = request::produce(channel_id, "no longer needed", &shared_key, None)
/// .expect("failed to build unpair request");
///
/// assert!(!result.envelope.is_empty());
/// ```
/// Decrypts and decodes an incoming [`derec_proto::UnpairRequestMessage`]
/// from an outer [`derec_proto::DeRecMessage`] envelope.
///
/// Call this on the **receiving** side after an unpair request envelope is
/// received over the transport. Once decrypted, the protocol layer can drop
/// its local state for the channel and reply with an unpair response.
///
/// This function:
///
/// 1. Decodes the outer [`derec_proto::DeRecMessage`] envelope from `envelope_bytes`
/// 2. Decrypts and decodes the inner [`derec_proto::UnpairRequestMessage`]
/// using `shared_key`
/// 3. Validates the invariant `envelope.timestamp == request.timestamp`
///
/// # Arguments
///
/// * `envelope_bytes` - Serialized outer [`derec_proto::DeRecMessage`] bytes
/// carrying an encrypted inner [`derec_proto::UnpairRequestMessage`], as
/// produced by [`produce`].
/// * `shared_key` - Previously established 32-byte symmetric channel key used to
/// decrypt the inner message.
///
/// # Returns
///
/// On success returns [`ExtractResult`] containing:
///
/// - `request`: the decrypted inner [`derec_proto::UnpairRequestMessage`]
///
/// # Errors
///
/// Returns [`crate::Error`] if:
///
/// - `envelope_bytes` cannot be decoded as a valid [`derec_proto::DeRecMessage`]
/// - decryption or inner-message decoding fails
/// - `envelope.timestamp != request.timestamp`
/// - the inner message is not a [`derec_proto::UnpairRequestMessage`]
///
/// # Security: no freshness or replay protection
///
/// The timestamp check enforced here only binds the envelope to the
/// inner body (`envelope.timestamp == body.timestamp`). It does NOT
/// enforce a freshness window against the receiver's clock and does
/// NOT detect replays of a previously-captured ciphertext. Because
/// the channel key is long-lived, a recorded envelope stays
/// decryptable indefinitely. Callers MUST add a freshness window
/// and per-channel anti-replay (monotonic counter or nonce log) on
/// top before driving any side-effecting state off the parsed body.
///
/// # Example
///
/// ```
/// use derec_library::primitives::unpairing::request;
/// use derec_library::types::ChannelId;
///
/// let channel_id = ChannelId(42);
/// let shared_key = [7u8; 32];
///
/// let request::ProduceResult { envelope } =
/// request::produce(channel_id, "no longer needed", &shared_key, None)
/// .expect("failed to build unpair request");
///
/// let request::ExtractResult { request } =
/// request::extract(&envelope, &shared_key).expect("failed to extract");
///
/// assert_eq!(request.memo, "no longer needed");
/// ```