Skip to main content

base64_ng/
buffers.rs

1//! Stack-backed and owned buffer wrappers.
2
3use crate::{
4    DecodeError, EncodeError, STANDARD, constant_time_eq_public_len, wipe_bytes, wipe_tail,
5};
6#[cfg(feature = "alloc")]
7use crate::{wipe_vec_all, wipe_vec_spare_capacity};
8#[cfg(feature = "alloc")]
9use alloc::{string::String, vec::Vec};
10
11/// Stack-backed encoded Base64 output.
12///
13/// This type is intended for short values where heap allocation would be
14/// unnecessary but manually sizing and passing a separate output slice is
15/// noisy. Its visible bytes are produced by crate encoders, so [`Self::as_str`]
16/// can return `&str` without exposing a fallible UTF-8 conversion to callers.
17/// [`core::fmt::Display`] intentionally writes the full encoded text; use
18/// `SecretBuffer` for encoded secrets that may reach logs or error messages.
19///
20/// The backing array is cleared when the value is dropped. This is best-effort
21/// data-retention reduction and is not a formal zeroization guarantee.
22///
23/// On `wasm32` targets, the wipe barrier uses only a compiler fence. The wasm
24/// runtime JIT may still optimize or retain cleared bytes in ways this crate
25/// cannot control. `wasm32` builds fail closed by default; enable
26/// `allow-wasm32-best-effort-wipe` only when the deployment explicitly accepts
27/// this limitation and applies its own memory strategy around stack-backed
28/// buffers.
29pub struct EncodedBuffer<const CAP: usize> {
30    bytes: [u8; CAP],
31    len: usize,
32}
33
34/// Owned stack array extracted from [`EncodedBuffer`].
35///
36/// This wrapper keeps the extracted encoded bytes on the crate's best-effort
37/// drop-time cleanup path. Use
38/// [`Self::into_exposed_unprotected_array_caller_must_zeroize`] only when a
39/// bare array is unavoidable and the caller will handle cleanup.
40pub struct ExposedEncodedArray<const CAP: usize> {
41    bytes: [u8; CAP],
42    len: usize,
43}
44
45impl<const CAP: usize> ExposedEncodedArray<CAP> {
46    /// Wraps an encoded backing array and visible length.
47    ///
48    /// # Panics
49    ///
50    /// Panics if `len` is greater than `CAP`.
51    #[must_use]
52    pub const fn from_array(bytes: [u8; CAP], len: usize) -> Self {
53        assert!(len <= CAP, "visible length exceeds array capacity");
54        Self { bytes, len }
55    }
56
57    /// Returns the visible encoded bytes.
58    #[must_use]
59    pub fn as_bytes(&self) -> &[u8] {
60        &self.bytes[..self.len]
61    }
62
63    /// Returns the number of visible encoded bytes.
64    #[must_use]
65    pub const fn len(&self) -> usize {
66        self.len
67    }
68
69    /// Returns whether there are no visible encoded bytes.
70    #[must_use]
71    pub const fn is_empty(&self) -> bool {
72        self.len == 0
73    }
74
75    /// Returns the backing array capacity.
76    #[must_use]
77    pub const fn capacity(&self) -> usize {
78        CAP
79    }
80
81    /// Consumes the wrapper and returns a bare array plus visible length.
82    ///
83    /// This is an unprotected escape hatch. The returned array will not be
84    /// cleared by this crate on drop. Callers must clear it with their own
85    /// approved zeroization policy.
86    ///
87    /// # Security
88    ///
89    /// Treat this as a cleanup-boundary API. Failing to clear the returned
90    /// array leaves the encoded bytes in ordinary caller-owned memory until
91    /// overwritten by later stack or heap activity.
92    #[must_use = "caller must zeroize the returned array"]
93    pub fn into_exposed_unprotected_array_caller_must_zeroize(mut self) -> ([u8; CAP], usize) {
94        let len = self.len;
95        self.len = 0;
96        (core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
97    }
98}
99
100impl<const CAP: usize> Drop for ExposedEncodedArray<CAP> {
101    fn drop(&mut self) {
102        wipe_bytes(&mut self.bytes);
103        self.len = 0;
104    }
105}
106
107impl<const CAP: usize> core::fmt::Debug for ExposedEncodedArray<CAP> {
108    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
109        formatter
110            .debug_struct("ExposedEncodedArray")
111            .field("bytes", &"<redacted>")
112            .field("len", &self.len)
113            .field("capacity", &CAP)
114            .finish()
115    }
116}
117
118impl<const CAP: usize> EncodedBuffer<CAP> {
119    /// Creates an empty encoded buffer.
120    #[must_use]
121    pub const fn new() -> Self {
122        Self {
123            bytes: [0u8; CAP],
124            len: 0,
125        }
126    }
127
128    /// Returns the full backing array as an output slice for crate-internal
129    /// encode paths.
130    pub(crate) fn as_mut_capacity(&mut self) -> &mut [u8] {
131        &mut self.bytes
132    }
133
134    /// Sets the visible length after a crate-internal encode path succeeds.
135    pub(crate) fn set_filled(&mut self, written: usize) -> Result<(), EncodeError> {
136        debug_assert!(
137            written <= CAP,
138            "encoder wrote past stack-backed buffer capacity"
139        );
140        if written > CAP {
141            self.clear();
142            return Err(EncodeError::OutputTooSmall {
143                required: written,
144                available: CAP,
145            });
146        }
147        self.len = written;
148        Ok(())
149    }
150
151    /// Returns the number of visible encoded bytes.
152    #[must_use]
153    pub const fn len(&self) -> usize {
154        self.len
155    }
156
157    /// Returns whether the buffer has no visible encoded bytes.
158    #[must_use]
159    pub const fn is_empty(&self) -> bool {
160        self.len == 0
161    }
162
163    /// Returns whether the visible encoded bytes fill the stack backing array.
164    #[must_use]
165    pub const fn is_full(&self) -> bool {
166        self.len == CAP
167    }
168
169    /// Returns the stack capacity in bytes.
170    #[must_use]
171    pub const fn capacity(&self) -> usize {
172        CAP
173    }
174
175    /// Returns the number of unused bytes in the stack backing array.
176    #[must_use]
177    pub const fn remaining_capacity(&self) -> usize {
178        CAP - self.len
179    }
180
181    /// Returns the visible encoded bytes.
182    #[must_use]
183    pub fn as_bytes(&self) -> &[u8] {
184        &self.bytes[..self.len]
185    }
186
187    /// Returns the visible encoded bytes as UTF-8 text.
188    ///
189    /// Encoded Base64 output is produced as ASCII by this crate, so this
190    /// method should not fail unless an internal invariant has been broken.
191    /// It is provided for callers that prefer a fallible accessor over
192    /// [`Self::as_str`].
193    pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
194        core::str::from_utf8(self.as_bytes())
195    }
196
197    /// Returns the visible encoded bytes as UTF-8.
198    ///
199    /// # Panics
200    ///
201    /// Panics only if the crate's internal invariant is broken and the buffer
202    /// contains non-UTF-8 bytes.
203    #[must_use]
204    pub fn as_str(&self) -> &str {
205        match self.as_utf8() {
206            Ok(output) => output,
207            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
208        }
209    }
210
211    /// Compares this encoded output to `other` without short-circuiting on the
212    /// first differing byte.
213    ///
214    /// Length and the final equality result remain public. Different lengths
215    /// return `false` immediately; use this helper only when the compared
216    /// lengths are public protocol facts or have been normalized by the
217    /// caller. For equal-length inputs, this helper scans every byte before
218    /// returning. It is constant-time-oriented best effort, not a formal
219    /// cryptographic constant-time guarantee. This comparison is deliberately
220    /// explicit: redacted buffer types do not implement [`PartialEq`] because
221    /// `==` would make a best-effort helper look like a formal token/MAC
222    /// comparison primitive.
223    ///
224    /// Do not use this helper as the sole MAC, bearer-token, password-hash, or
225    /// authentication-secret comparison primitive in high-assurance systems.
226    /// Applications that can admit dependencies should use a reviewed
227    /// constant-time comparison primitive, such as `subtle`, at the protocol
228    /// boundary.
229    #[doc(alias = "constant_time_eq")]
230    #[must_use]
231    pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
232        constant_time_eq_public_len(self.as_bytes(), other)
233    }
234
235    /// Consumes the wrapper and returns the backing array plus visible length
236    /// inside a drop-wiping exposed wrapper.
237    ///
238    /// This is an explicit escape hatch for no-alloc interop with APIs that
239    /// require ownership of a fixed array. The returned
240    /// [`ExposedEncodedArray`] remains redacted by formatting and clears its
241    /// backing array on drop.
242    #[must_use]
243    pub fn into_exposed_array(mut self) -> ExposedEncodedArray<CAP> {
244        let len = self.len;
245        self.len = 0;
246        ExposedEncodedArray::from_array(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
247    }
248
249    /// Clears the visible bytes and the full backing array.
250    pub fn clear(&mut self) {
251        wipe_bytes(&mut self.bytes);
252        self.len = 0;
253    }
254
255    /// Clears bytes after the visible prefix.
256    pub fn clear_tail(&mut self) {
257        wipe_tail(&mut self.bytes, self.len);
258    }
259}
260
261impl<const CAP: usize> AsRef<[u8]> for EncodedBuffer<CAP> {
262    fn as_ref(&self) -> &[u8] {
263        self.as_bytes()
264    }
265}
266
267impl<const CAP: usize> Clone for EncodedBuffer<CAP> {
268    /// Clones the visible encoded bytes into a second stack-backed buffer.
269    ///
270    /// Security note: cloning duplicates the visible bytes in memory. Both the
271    /// original and the clone must be dropped or explicitly cleared before the
272    /// duplicated bytes are gone on the crate's best-effort cleanup path. The
273    /// compiler may also create temporary stack copies while performing the
274    /// copy; those intermediates are outside this crate's cleanup boundary.
275    /// Avoid cloning encoded secret material; use `SecretBuffer` when redacted
276    /// formatting and heap-owned secret handling are required.
277    fn clone(&self) -> Self {
278        let mut output = Self::new();
279        output.bytes[..self.len].copy_from_slice(self.as_bytes());
280        output.len = self.len;
281        output
282    }
283}
284
285impl<const CAP: usize> core::fmt::Debug for EncodedBuffer<CAP> {
286    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
287        formatter
288            .debug_struct("EncodedBuffer")
289            .field("bytes", &"<redacted>")
290            .field("len", &self.len)
291            .field("capacity", &CAP)
292            .finish()
293    }
294}
295
296impl<const CAP: usize> core::fmt::Display for EncodedBuffer<CAP> {
297    /// Writes the full Base64 text.
298    ///
299    /// Security note: this is intentionally not redacted. Do not use
300    /// `EncodedBuffer` for encoded secrets that may reach logs or error
301    /// messages; use `SecretBuffer` for redacted formatting.
302    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
303        formatter.write_str(self.as_str())
304    }
305}
306
307impl<const CAP: usize> Default for EncodedBuffer<CAP> {
308    fn default() -> Self {
309        Self::new()
310    }
311}
312
313impl<const CAP: usize> Drop for EncodedBuffer<CAP> {
314    fn drop(&mut self) {
315        self.clear();
316    }
317}
318
319impl<const CAP: usize> TryFrom<&[u8]> for EncodedBuffer<CAP> {
320    type Error = EncodeError;
321
322    /// Encodes bytes into strict standard padded Base64 in a stack-backed
323    /// buffer.
324    ///
325    /// Use [`crate::Engine::encode_buffer`] or [`crate::Profile::encode_buffer`] when a
326    /// different alphabet, padding mode, or line-wrapping profile is required.
327    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
328        STANDARD.encode_buffer(input)
329    }
330}
331
332impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for EncodedBuffer<CAP> {
333    type Error = EncodeError;
334
335    /// Encodes a byte array into strict standard padded Base64 in a
336    /// stack-backed buffer.
337    ///
338    /// Use [`crate::Engine::encode_buffer`] or [`crate::Profile::encode_buffer`] when a
339    /// different alphabet, padding mode, or line-wrapping profile is required.
340    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
341        Self::try_from(&input[..])
342    }
343}
344
345impl<const CAP: usize> TryFrom<&str> for EncodedBuffer<CAP> {
346    type Error = EncodeError;
347
348    /// Encodes UTF-8 text bytes into strict standard padded Base64 in a
349    /// stack-backed buffer.
350    ///
351    /// This treats the string as raw input bytes. Use
352    /// [`crate::Engine::encode_buffer`] or [`crate::Profile::encode_buffer`] when a
353    /// different alphabet, padding mode, or line-wrapping profile is required.
354    fn try_from(input: &str) -> Result<Self, Self::Error> {
355        Self::try_from(input.as_bytes())
356    }
357}
358
359/// Stack-backed decoded Base64 output.
360///
361/// This type is intended for short decoded values where heap allocation would
362/// be unnecessary but manually sizing and passing a separate output slice is
363/// noisy. Decoded data may be binary or secret-bearing, so formatting is
364/// redacted and contents are exposed only through explicit byte accessors.
365///
366/// The backing array is cleared when the value is dropped. This is best-effort
367/// data-retention reduction and is not a formal zeroization guarantee.
368///
369/// On `wasm32` targets, the wipe barrier uses only a compiler fence. The wasm
370/// runtime JIT may still optimize or retain cleared bytes in ways this crate
371/// cannot control. `wasm32` builds fail closed by default; enable
372/// `allow-wasm32-best-effort-wipe` only when the deployment explicitly accepts
373/// this limitation and applies its own memory strategy around stack-backed
374/// buffers.
375pub struct DecodedBuffer<const CAP: usize> {
376    bytes: [u8; CAP],
377    len: usize,
378}
379
380/// Owned stack array extracted from [`DecodedBuffer`].
381///
382/// This wrapper keeps the extracted decoded bytes on the crate's best-effort
383/// drop-time cleanup path. Use
384/// [`Self::into_exposed_unprotected_array_caller_must_zeroize`] only when a
385/// bare array is unavoidable and the caller will handle cleanup.
386pub struct ExposedDecodedArray<const CAP: usize> {
387    bytes: [u8; CAP],
388    len: usize,
389}
390
391impl<const CAP: usize> ExposedDecodedArray<CAP> {
392    /// Wraps a decoded backing array and visible length.
393    ///
394    /// # Panics
395    ///
396    /// Panics if `len` is greater than `CAP`.
397    #[must_use]
398    pub const fn from_array(bytes: [u8; CAP], len: usize) -> Self {
399        assert!(len <= CAP, "visible length exceeds array capacity");
400        Self { bytes, len }
401    }
402
403    /// Returns the visible decoded bytes.
404    #[must_use]
405    pub fn as_bytes(&self) -> &[u8] {
406        &self.bytes[..self.len]
407    }
408
409    /// Returns the number of visible decoded bytes.
410    #[must_use]
411    pub const fn len(&self) -> usize {
412        self.len
413    }
414
415    /// Returns whether there are no visible decoded bytes.
416    #[must_use]
417    pub const fn is_empty(&self) -> bool {
418        self.len == 0
419    }
420
421    /// Returns the backing array capacity.
422    #[must_use]
423    pub const fn capacity(&self) -> usize {
424        CAP
425    }
426
427    /// Consumes the wrapper and returns a bare array plus visible length.
428    ///
429    /// This is an unprotected escape hatch. The returned array will not be
430    /// cleared by this crate on drop. Callers must clear it with their own
431    /// approved zeroization policy.
432    ///
433    /// # Security
434    ///
435    /// Treat this as a cleanup-boundary API. Failing to clear the returned
436    /// array leaves decoded bytes, which may be secret-bearing, in ordinary
437    /// caller-owned memory until overwritten by later stack or heap activity.
438    #[must_use = "caller must zeroize the returned array"]
439    pub fn into_exposed_unprotected_array_caller_must_zeroize(mut self) -> ([u8; CAP], usize) {
440        let len = self.len;
441        self.len = 0;
442        (core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
443    }
444}
445
446impl<const CAP: usize> Drop for ExposedDecodedArray<CAP> {
447    fn drop(&mut self) {
448        wipe_bytes(&mut self.bytes);
449        self.len = 0;
450    }
451}
452
453impl<const CAP: usize> core::fmt::Debug for ExposedDecodedArray<CAP> {
454    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
455        formatter
456            .debug_struct("ExposedDecodedArray")
457            .field("bytes", &"<redacted>")
458            .field("len", &self.len)
459            .field("capacity", &CAP)
460            .finish()
461    }
462}
463
464impl<const CAP: usize> DecodedBuffer<CAP> {
465    /// Creates an empty decoded buffer.
466    #[must_use]
467    pub const fn new() -> Self {
468        Self {
469            bytes: [0u8; CAP],
470            len: 0,
471        }
472    }
473
474    /// Returns the full backing array as an output slice for crate-internal
475    /// decode paths.
476    pub(crate) fn as_mut_capacity(&mut self) -> &mut [u8] {
477        &mut self.bytes
478    }
479
480    /// Sets the visible length after a crate-internal decode path succeeds.
481    pub(crate) fn set_filled(&mut self, written: usize) -> Result<(), DecodeError> {
482        debug_assert!(
483            written <= CAP,
484            "decoder wrote past stack-backed buffer capacity"
485        );
486        if written > CAP {
487            self.clear();
488            return Err(DecodeError::OutputTooSmall {
489                required: written,
490                available: CAP,
491            });
492        }
493        self.len = written;
494        Ok(())
495    }
496
497    /// Returns the number of visible decoded bytes.
498    #[must_use]
499    pub const fn len(&self) -> usize {
500        self.len
501    }
502
503    /// Returns whether the buffer has no visible decoded bytes.
504    #[must_use]
505    pub const fn is_empty(&self) -> bool {
506        self.len == 0
507    }
508
509    /// Returns whether the visible decoded bytes fill the stack backing array.
510    #[must_use]
511    pub const fn is_full(&self) -> bool {
512        self.len == CAP
513    }
514
515    /// Returns the stack capacity in bytes.
516    #[must_use]
517    pub const fn capacity(&self) -> usize {
518        CAP
519    }
520
521    /// Returns the number of unused bytes in the stack backing array.
522    #[must_use]
523    pub const fn remaining_capacity(&self) -> usize {
524        CAP - self.len
525    }
526
527    /// Returns the visible decoded bytes.
528    #[must_use]
529    pub fn as_bytes(&self) -> &[u8] {
530        &self.bytes[..self.len]
531    }
532
533    /// Returns the visible decoded bytes as UTF-8 text.
534    ///
535    /// Decoded Base64 output is arbitrary bytes, so this method is fallible.
536    /// Use [`Self::as_bytes`] when the decoded payload is binary or when text
537    /// validation belongs to a higher protocol layer.
538    pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
539        core::str::from_utf8(self.as_bytes())
540    }
541
542    /// Compares this decoded output to `other` without short-circuiting on the
543    /// first differing byte.
544    ///
545    /// Length and the final equality result remain public. Different lengths
546    /// return `false` immediately; use this helper only when the compared
547    /// lengths are public protocol facts or have been normalized by the
548    /// caller. For equal-length inputs, this helper scans every byte before
549    /// returning. It is constant-time-oriented best effort, not a formal
550    /// cryptographic constant-time guarantee. This comparison is deliberately
551    /// explicit: redacted buffer types do not implement [`PartialEq`] because
552    /// `==` would make a best-effort helper look like a formal token/MAC
553    /// comparison primitive.
554    ///
555    /// Do not use this helper as the sole MAC, bearer-token, password-hash, or
556    /// authentication-secret comparison primitive in high-assurance systems.
557    /// Applications that can admit dependencies should use a reviewed
558    /// constant-time comparison primitive, such as `subtle`, at the protocol
559    /// boundary.
560    #[doc(alias = "constant_time_eq")]
561    #[must_use]
562    pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
563        constant_time_eq_public_len(self.as_bytes(), other)
564    }
565
566    /// Consumes the wrapper and returns the backing array plus visible length
567    /// inside a drop-wiping exposed wrapper.
568    ///
569    /// This is an explicit escape hatch for no-alloc interop with APIs that
570    /// require ownership of a fixed array. The returned
571    /// [`ExposedDecodedArray`] remains redacted by formatting and clears its
572    /// backing array on drop.
573    #[must_use]
574    pub fn into_exposed_array(mut self) -> ExposedDecodedArray<CAP> {
575        let len = self.len;
576        self.len = 0;
577        ExposedDecodedArray::from_array(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
578    }
579
580    /// Clears the visible bytes and the full backing array.
581    pub fn clear(&mut self) {
582        wipe_bytes(&mut self.bytes);
583        self.len = 0;
584    }
585
586    /// Clears bytes after the visible prefix.
587    pub fn clear_tail(&mut self) {
588        wipe_tail(&mut self.bytes, self.len);
589    }
590}
591
592impl<const CAP: usize> AsRef<[u8]> for DecodedBuffer<CAP> {
593    fn as_ref(&self) -> &[u8] {
594        self.as_bytes()
595    }
596}
597
598impl<const CAP: usize> Clone for DecodedBuffer<CAP> {
599    /// Clones the visible decoded bytes into a second stack-backed buffer.
600    ///
601    /// Security note: cloning duplicates decoded bytes in memory. Both the
602    /// original and the clone must be dropped or explicitly cleared before the
603    /// duplicated bytes are gone on the crate's best-effort cleanup path. The
604    /// compiler may also create temporary stack copies while performing the
605    /// copy; those intermediates are outside this crate's cleanup boundary. For
606    /// high-assurance applications, avoid cloning decoded key material and use
607    /// `SecretBuffer` for heap-owned secrets without a `Clone` implementation.
608    fn clone(&self) -> Self {
609        let mut output = Self::new();
610        output.bytes[..self.len].copy_from_slice(self.as_bytes());
611        output.len = self.len;
612        output
613    }
614}
615
616impl<const CAP: usize> core::fmt::Debug for DecodedBuffer<CAP> {
617    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
618        formatter
619            .debug_struct("DecodedBuffer")
620            .field("bytes", &"<redacted>")
621            .field("len", &self.len)
622            .field("capacity", &CAP)
623            .finish()
624    }
625}
626
627impl<const CAP: usize> Default for DecodedBuffer<CAP> {
628    fn default() -> Self {
629        Self::new()
630    }
631}
632
633impl<const CAP: usize> Drop for DecodedBuffer<CAP> {
634    fn drop(&mut self) {
635        self.clear();
636    }
637}
638
639impl<const CAP: usize> TryFrom<&[u8]> for DecodedBuffer<CAP> {
640    type Error = DecodeError;
641
642    /// Decodes strict standard padded Base64 into a stack-backed buffer.
643    ///
644    /// Use [`crate::Engine::decode_buffer`] or [`crate::Profile::decode_buffer`]
645    /// when a different alphabet, padding mode, or line-wrapping profile is
646    /// required. These conversions always use [`crate::STANDARD`]; URL-safe,
647    /// bcrypt, crypt, MIME, PEM, and custom alphabets must use an explicit
648    /// engine or profile.
649    ///
650    /// # Security
651    ///
652    /// This idiomatic conversion uses the strict standard decoder, not the
653    /// constant-time-oriented decoder. It may branch or return early on
654    /// malformed input and reports exact [`DecodeError`] positions. For
655    /// secret-bearing tokens or key material where malformed-input timing
656    /// matters, use [`crate::ct::CtEngine::decode_buffer`] through
657    /// [`crate::ct::STANDARD`] instead.
658    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
659        STANDARD.decode_buffer(input)
660    }
661}
662
663impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for DecodedBuffer<CAP> {
664    type Error = DecodeError;
665
666    /// Decodes a strict standard padded Base64 byte array into a stack-backed
667    /// buffer.
668    ///
669    /// Use [`crate::Engine::decode_buffer`] or [`crate::Profile::decode_buffer`]
670    /// when a different alphabet, padding mode, or line-wrapping profile is
671    /// required. These conversions always use [`crate::STANDARD`]; URL-safe,
672    /// bcrypt, crypt, MIME, PEM, and custom alphabets must use an explicit
673    /// engine or profile.
674    ///
675    /// # Security
676    ///
677    /// This idiomatic conversion uses the strict standard decoder, not the
678    /// constant-time-oriented decoder. It may branch or return early on
679    /// malformed input and reports exact [`DecodeError`] positions. For
680    /// secret-bearing tokens or key material where malformed-input timing
681    /// matters, use [`crate::ct::CtEngine::decode_buffer`] through
682    /// [`crate::ct::STANDARD`] instead.
683    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
684        Self::try_from(&input[..])
685    }
686}
687
688impl<const CAP: usize> TryFrom<&str> for DecodedBuffer<CAP> {
689    type Error = DecodeError;
690
691    /// Decodes strict standard padded Base64 text into a stack-backed buffer.
692    ///
693    /// Use [`crate::Engine::decode_buffer`] or [`crate::Profile::decode_buffer`]
694    /// when a different alphabet, padding mode, or line-wrapping profile is
695    /// required. These conversions always use [`crate::STANDARD`]; URL-safe,
696    /// bcrypt, crypt, MIME, PEM, and custom alphabets must use an explicit
697    /// engine or profile.
698    ///
699    /// # Security
700    ///
701    /// This idiomatic conversion uses the strict standard decoder, not the
702    /// constant-time-oriented decoder. It may branch or return early on
703    /// malformed input and reports exact [`DecodeError`] positions. For
704    /// secret-bearing tokens or key material where malformed-input timing
705    /// matters, use [`crate::ct::CtEngine::decode_buffer`] through
706    /// [`crate::ct::STANDARD`] instead.
707    fn try_from(input: &str) -> Result<Self, Self::Error> {
708        Self::try_from(input.as_bytes())
709    }
710}
711
712impl<const CAP: usize> core::str::FromStr for DecodedBuffer<CAP> {
713    type Err = DecodeError;
714
715    /// Decodes strict standard padded Base64 text into a stack-backed buffer.
716    ///
717    /// Use [`crate::Engine::decode_buffer`] or [`crate::Profile::decode_buffer`]
718    /// when a different alphabet, padding mode, or line-wrapping profile is
719    /// required. These conversions always use [`crate::STANDARD`]; URL-safe,
720    /// bcrypt, crypt, MIME, PEM, and custom alphabets must use an explicit
721    /// engine or profile.
722    ///
723    /// # Security
724    ///
725    /// This idiomatic conversion uses the strict standard decoder, not the
726    /// constant-time-oriented decoder. It may branch or return early on
727    /// malformed input and reports exact [`DecodeError`] positions. For
728    /// secret-bearing tokens or key material where malformed-input timing
729    /// matters, use [`crate::ct::CtEngine::decode_buffer`] through
730    /// [`crate::ct::STANDARD`] instead.
731    fn from_str(input: &str) -> Result<Self, Self::Err> {
732        Self::try_from(input)
733    }
734}
735
736/// Owned sensitive bytes with redacted formatting and drop-time cleanup.
737///
738/// `SecretBuffer` is available with the `alloc` feature. It is intended for
739/// decoded keys, tokens, and other values that should not be accidentally
740/// logged. The buffer exposes contents only through explicit reveal methods.
741///
742/// Spare vector capacity is cleared when wrapping owned bytes. On drop,
743/// initialized bytes and vector spare capacity are cleared with the crate's
744/// internal best-effort wipe helpers. This is data-retention reduction, not a
745/// formal zeroization guarantee, and it cannot make claims about allocator
746/// behavior or historical copies outside the wrapper.
747///
748/// # Platform Memory Controls
749///
750/// `SecretBuffer` does not lock its allocation into physical memory. The OS
751/// may page its contents to disk, include them in hibernation images, or expose
752/// them through crash dumps. High-assurance deployments must combine
753/// `SecretBuffer` with platform memory-locking where available, encrypted or
754/// disabled swap, crash-dump suppression, and allocator isolation appropriate
755/// for their environment.
756///
757/// On `wasm32` targets, the wipe barrier uses only a compiler fence. The wasm
758/// runtime JIT may still optimize or retain cleared bytes in ways this crate
759/// cannot control. `wasm32` builds fail closed by default; enable
760/// `allow-wasm32-best-effort-wipe` only when the deployment explicitly accepts
761/// this limitation and applies its own memory strategy around owned secret
762/// buffers.
763#[cfg(feature = "alloc")]
764pub struct SecretBuffer {
765    bytes: alloc::vec::Vec<u8>,
766}
767
768/// Owned secret bytes extracted from [`SecretBuffer`].
769///
770/// This wrapper keeps redacted formatting, best-effort spare-capacity clearing
771/// at construction time, and best-effort full wipe on drop after a
772/// [`SecretBuffer`] is consumed for owned interop. Use
773/// [`Self::into_exposed_unprotected_vec_caller_must_zeroize`] only when a raw
774/// `Vec<u8>` is unavoidable and the caller will handle cleanup.
775#[cfg(feature = "alloc")]
776pub struct ExposedSecretVec {
777    bytes: alloc::vec::Vec<u8>,
778}
779
780#[cfg(feature = "alloc")]
781impl ExposedSecretVec {
782    /// Wraps an owned vector as exposed secret material.
783    #[must_use]
784    pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
785        wipe_vec_spare_capacity(&mut bytes);
786        Self { bytes }
787    }
788
789    /// Returns the number of initialized secret bytes.
790    #[must_use]
791    pub fn len(&self) -> usize {
792        self.bytes.len()
793    }
794
795    /// Returns whether the buffer contains no initialized secret bytes.
796    #[must_use]
797    pub fn is_empty(&self) -> bool {
798        self.bytes.is_empty()
799    }
800
801    /// Reveals the secret bytes.
802    ///
803    /// This method is intentionally named to make secret access explicit at the
804    /// call site.
805    #[must_use]
806    pub fn expose_secret(&self) -> &[u8] {
807        &self.bytes
808    }
809
810    /// Reveals the secret bytes mutably.
811    ///
812    /// This method is intentionally named to make secret access explicit at the
813    /// call site.
814    #[must_use]
815    pub fn expose_secret_mut(&mut self) -> &mut [u8] {
816        &mut self.bytes
817    }
818
819    /// Consumes the wrapper and returns a raw `Vec<u8>`.
820    ///
821    /// This is an unprotected escape hatch. The returned vector is no longer
822    /// redacted by formatting and will not be cleared by this crate on drop.
823    /// Callers must clear it with their own approved zeroization policy.
824    #[must_use = "caller must zeroize the returned Vec"]
825    pub fn into_exposed_unprotected_vec_caller_must_zeroize(mut self) -> alloc::vec::Vec<u8> {
826        core::mem::take(&mut self.bytes)
827    }
828}
829
830#[cfg(feature = "alloc")]
831impl core::fmt::Debug for ExposedSecretVec {
832    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
833        formatter
834            .debug_struct("ExposedSecretVec")
835            .field("bytes", &"<redacted>")
836            .field("len", &self.len())
837            .finish()
838    }
839}
840
841#[cfg(feature = "alloc")]
842impl core::fmt::Display for ExposedSecretVec {
843    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
844        formatter.write_str("<redacted>")
845    }
846}
847
848#[cfg(feature = "alloc")]
849impl Drop for ExposedSecretVec {
850    fn drop(&mut self) {
851        wipe_vec_all(&mut self.bytes);
852    }
853}
854
855#[cfg(feature = "alloc")]
856struct WipeVecGuard {
857    bytes: alloc::vec::Vec<u8>,
858}
859
860#[cfg(feature = "alloc")]
861impl WipeVecGuard {
862    fn from_vec(bytes: alloc::vec::Vec<u8>) -> Self {
863        Self { bytes }
864    }
865
866    fn into_validated_secret_string(mut self) -> alloc::string::String {
867        wipe_vec_spare_capacity(&mut self.bytes);
868        let bytes = core::mem::take(&mut self.bytes);
869        string_from_validated_secret_bytes(bytes)
870    }
871}
872
873#[cfg(feature = "alloc")]
874impl Drop for WipeVecGuard {
875    fn drop(&mut self) {
876        wipe_vec_all(&mut self.bytes);
877    }
878}
879
880#[cfg(feature = "alloc")]
881impl AsRef<[u8]> for ExposedSecretVec {
882    fn as_ref(&self) -> &[u8] {
883        self.expose_secret()
884    }
885}
886
887#[cfg(feature = "alloc")]
888impl AsMut<[u8]> for ExposedSecretVec {
889    fn as_mut(&mut self) -> &mut [u8] {
890        self.expose_secret_mut()
891    }
892}
893
894/// Owned secret UTF-8 text extracted from [`SecretBuffer`].
895///
896/// This wrapper keeps redacted formatting, best-effort spare-capacity clearing
897/// at construction time, and best-effort full wipe on drop after a
898/// [`SecretBuffer`] is consumed for string interop. Use
899/// [`Self::into_exposed_unprotected_string_caller_must_zeroize`] only when a
900/// raw `String` is unavoidable and the caller will handle cleanup.
901#[cfg(feature = "alloc")]
902pub struct ExposedSecretString {
903    text: alloc::string::String,
904}
905
906#[cfg(feature = "alloc")]
907impl ExposedSecretString {
908    /// Wraps an owned UTF-8 string as exposed secret text.
909    #[must_use]
910    pub fn from_string(text: alloc::string::String) -> Self {
911        let mut bytes = text.into_bytes();
912        wipe_vec_spare_capacity(&mut bytes);
913        let text = string_from_validated_secret_bytes(bytes);
914        Self { text }
915    }
916
917    /// Returns the length of the secret text in bytes.
918    #[must_use]
919    pub fn len(&self) -> usize {
920        self.text.len()
921    }
922
923    /// Returns whether the secret text is empty.
924    #[must_use]
925    pub fn is_empty(&self) -> bool {
926        self.text.is_empty()
927    }
928
929    /// Reveals the secret text.
930    ///
931    /// This method is intentionally named to make secret access explicit at
932    /// the call site.
933    #[must_use]
934    pub fn expose_secret(&self) -> &str {
935        &self.text
936    }
937
938    /// Reveals the secret text as bytes.
939    ///
940    /// This method is intentionally named to make secret access explicit at
941    /// the call site.
942    #[must_use]
943    pub fn expose_secret_bytes(&self) -> &[u8] {
944        self.text.as_bytes()
945    }
946
947    /// Consumes the wrapper and returns a raw `String`.
948    ///
949    /// This is an unprotected escape hatch. The returned string is no longer
950    /// redacted by formatting and will not be cleared by this crate on drop.
951    /// Callers must clear it with their own approved zeroization policy.
952    #[must_use = "caller must zeroize the returned String"]
953    pub fn into_exposed_unprotected_string_caller_must_zeroize(mut self) -> alloc::string::String {
954        core::mem::take(&mut self.text)
955    }
956}
957
958#[cfg(feature = "alloc")]
959impl core::fmt::Debug for ExposedSecretString {
960    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
961        formatter
962            .debug_struct("ExposedSecretString")
963            .field("text", &"<redacted>")
964            .field("len", &self.len())
965            .finish()
966    }
967}
968
969#[cfg(feature = "alloc")]
970impl core::fmt::Display for ExposedSecretString {
971    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
972        formatter.write_str("<redacted>")
973    }
974}
975
976#[cfg(feature = "alloc")]
977impl Drop for ExposedSecretString {
978    fn drop(&mut self) {
979        let mut bytes = core::mem::take(&mut self.text).into_bytes();
980        wipe_vec_all(&mut bytes);
981    }
982}
983
984#[cfg(feature = "alloc")]
985impl AsRef<str> for ExposedSecretString {
986    fn as_ref(&self) -> &str {
987        self.expose_secret()
988    }
989}
990
991#[cfg(feature = "alloc")]
992impl SecretBuffer {
993    /// Wraps an existing vector as sensitive material.
994    #[must_use]
995    pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
996        wipe_vec_spare_capacity(&mut bytes);
997        Self { bytes }
998    }
999
1000    /// Copies a slice into an owned sensitive buffer.
1001    #[must_use]
1002    pub fn from_slice(bytes: &[u8]) -> Self {
1003        Self::from_vec(bytes.to_vec())
1004    }
1005
1006    /// Returns the number of initialized secret bytes.
1007    #[must_use]
1008    pub fn len(&self) -> usize {
1009        self.bytes.len()
1010    }
1011
1012    /// Returns whether the buffer contains no initialized secret bytes.
1013    #[must_use]
1014    pub fn is_empty(&self) -> bool {
1015        self.bytes.is_empty()
1016    }
1017
1018    /// Reveals the secret bytes.
1019    ///
1020    /// This method is intentionally named to make secret access explicit at the
1021    /// call site.
1022    #[must_use]
1023    pub fn expose_secret(&self) -> &[u8] {
1024        &self.bytes
1025    }
1026
1027    /// Reveals the secret bytes as UTF-8 text.
1028    ///
1029    /// This method is intentionally named to make secret access explicit at the
1030    /// call site. Secret material may be arbitrary binary data, so this method
1031    /// is fallible.
1032    pub fn expose_secret_utf8(&self) -> Result<&str, core::str::Utf8Error> {
1033        core::str::from_utf8(self.expose_secret())
1034    }
1035
1036    /// Reveals the secret bytes mutably.
1037    ///
1038    /// This method is intentionally named to make secret access explicit at the
1039    /// call site.
1040    #[must_use]
1041    pub fn expose_secret_mut(&mut self) -> &mut [u8] {
1042        &mut self.bytes
1043    }
1044
1045    /// Consumes the wrapper and returns owned secret bytes.
1046    ///
1047    /// This is an explicit escape hatch for interop with APIs that require an
1048    /// owned vector-like value. The returned [`ExposedSecretVec`] remains
1049    /// redacted by formatting and clears its vector on drop.
1050    #[must_use]
1051    pub fn into_exposed_vec(mut self) -> ExposedSecretVec {
1052        ExposedSecretVec::from_vec(core::mem::take(&mut self.bytes))
1053    }
1054
1055    /// Consumes the wrapper and returns the owned secret bytes as UTF-8 text.
1056    ///
1057    /// This is an explicit escape hatch for interop with APIs that require an
1058    /// owned string-like value. The returned [`ExposedSecretString`] remains
1059    /// redacted by formatting and clears its heap allocation on drop.
1060    ///
1061    /// If the secret bytes are not valid UTF-8, the original redacted wrapper
1062    /// is returned unchanged.
1063    #[must_use = "handle invalid UTF-8 errors and keep the returned wrapper protected"]
1064    pub fn try_into_exposed_string(self) -> Result<ExposedSecretString, Self> {
1065        if core::str::from_utf8(self.expose_secret()).is_err() {
1066            return Err(self);
1067        }
1068
1069        // Keep the bytes behind a wiping guard until the final infallible
1070        // ownership transfer into `String`.
1071        let mut exposed = self.into_exposed_vec();
1072        let guard = WipeVecGuard::from_vec(core::mem::take(&mut exposed.bytes));
1073        drop(exposed);
1074        Ok(ExposedSecretString::from_string(
1075            guard.into_validated_secret_string(),
1076        ))
1077    }
1078
1079    /// Compares this secret to `other` without short-circuiting on the first
1080    /// differing byte.
1081    ///
1082    /// Length and the final equality result remain public. Different lengths
1083    /// return `false` immediately; use this helper only when the compared
1084    /// lengths are public protocol facts or have been normalized by the
1085    /// caller. For equal-length inputs, this helper scans every byte before
1086    /// returning. It is constant-time-oriented best effort, not a formal
1087    /// cryptographic constant-time guarantee. This comparison is deliberately
1088    /// explicit: redacted buffer types do not implement [`PartialEq`] because
1089    /// `==` would make a best-effort helper look like a formal token/MAC
1090    /// comparison primitive.
1091    ///
1092    /// Do not use this helper as the sole MAC, bearer-token, password-hash, or
1093    /// authentication-secret comparison primitive in high-assurance systems.
1094    /// Applications that can admit dependencies should use a reviewed
1095    /// constant-time comparison primitive, such as `subtle`, at the protocol
1096    /// boundary.
1097    #[doc(alias = "constant_time_eq")]
1098    #[must_use]
1099    pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
1100        constant_time_eq_public_len(self.expose_secret(), other)
1101    }
1102
1103    /// Clears the initialized bytes and makes the buffer empty.
1104    pub fn clear(&mut self) {
1105        wipe_vec_all(&mut self.bytes);
1106        self.bytes.clear();
1107    }
1108}
1109
1110#[cfg(feature = "alloc")]
1111impl core::fmt::Debug for SecretBuffer {
1112    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1113        formatter
1114            .debug_struct("SecretBuffer")
1115            .field("bytes", &"<redacted>")
1116            .field("len", &self.len())
1117            .finish()
1118    }
1119}
1120
1121#[cfg(feature = "alloc")]
1122impl core::fmt::Display for SecretBuffer {
1123    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1124        formatter.write_str("<redacted>")
1125    }
1126}
1127
1128#[cfg(feature = "alloc")]
1129impl Drop for SecretBuffer {
1130    fn drop(&mut self) {
1131        wipe_vec_all(&mut self.bytes);
1132    }
1133}
1134
1135#[cfg(feature = "alloc")]
1136impl From<alloc::vec::Vec<u8>> for SecretBuffer {
1137    /// Wraps an owned vector as sensitive material.
1138    ///
1139    /// Spare capacity is cleared immediately before the vector is stored.
1140    /// Use [`SecretBuffer::from_slice`] when the source data is borrowed.
1141    fn from(bytes: alloc::vec::Vec<u8>) -> Self {
1142        Self::from_vec(bytes)
1143    }
1144}
1145
1146#[cfg(feature = "alloc")]
1147impl From<alloc::string::String> for SecretBuffer {
1148    /// Wraps an owned UTF-8 string as sensitive material.
1149    ///
1150    /// The string is consumed without copying its initialized bytes. Spare
1151    /// vector capacity is cleared immediately before the bytes are stored.
1152    fn from(text: alloc::string::String) -> Self {
1153        Self::from_vec(text.into_bytes())
1154    }
1155}
1156
1157#[cfg(feature = "alloc")]
1158impl<const CAP: usize> From<EncodedBuffer<CAP>> for SecretBuffer {
1159    /// Copies visible encoded bytes from a stack-backed buffer into an owned
1160    /// redacted buffer.
1161    ///
1162    /// The consumed stack-backed buffer clears its backing array when it is
1163    /// dropped at the end of the conversion.
1164    fn from(buffer: EncodedBuffer<CAP>) -> Self {
1165        Self::from_slice(buffer.as_bytes())
1166    }
1167}
1168
1169#[cfg(feature = "alloc")]
1170impl<const CAP: usize> From<DecodedBuffer<CAP>> for SecretBuffer {
1171    /// Copies visible decoded bytes from a stack-backed buffer into an owned
1172    /// redacted buffer.
1173    ///
1174    /// The consumed stack-backed buffer clears its backing array when it is
1175    /// dropped at the end of the conversion.
1176    fn from(buffer: DecodedBuffer<CAP>) -> Self {
1177        Self::from_slice(buffer.as_bytes())
1178    }
1179}
1180
1181#[cfg(feature = "alloc")]
1182impl TryFrom<&[u8]> for SecretBuffer {
1183    type Error = DecodeError;
1184
1185    /// Decodes strict standard padded Base64 into a redacted owned buffer.
1186    ///
1187    /// Use [`crate::Engine::decode_secret`] or [`crate::Profile::decode_secret`]
1188    /// when a different alphabet, padding mode, or line-wrapping profile is
1189    /// required. These conversions always use [`crate::STANDARD`]; URL-safe,
1190    /// bcrypt, crypt, MIME, PEM, and custom alphabets must use an explicit
1191    /// engine or profile.
1192    ///
1193    /// # Security
1194    ///
1195    /// This idiomatic conversion uses the strict standard decoder, not the
1196    /// constant-time-oriented decoder. It may branch or return early on
1197    /// malformed input and reports exact [`DecodeError`] positions. For
1198    /// secret-bearing tokens or key material where malformed-input timing
1199    /// matters, use [`crate::ct::CtEngine::decode_secret`] through
1200    /// [`crate::ct::STANDARD`], or use staged decode and then wrap the
1201    /// successful output in `SecretBuffer`.
1202    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
1203        STANDARD.decode_secret(input)
1204    }
1205}
1206
1207#[cfg(feature = "alloc")]
1208impl<const N: usize> TryFrom<&[u8; N]> for SecretBuffer {
1209    type Error = DecodeError;
1210
1211    /// Decodes a strict standard padded Base64 byte array into a redacted
1212    /// owned buffer.
1213    ///
1214    /// Use [`crate::Engine::decode_secret`] or [`crate::Profile::decode_secret`]
1215    /// when a different alphabet, padding mode, or line-wrapping profile is
1216    /// required. These conversions always use [`crate::STANDARD`]; URL-safe,
1217    /// bcrypt, crypt, MIME, PEM, and custom alphabets must use an explicit
1218    /// engine or profile.
1219    ///
1220    /// # Security
1221    ///
1222    /// This idiomatic conversion uses the strict standard decoder, not the
1223    /// constant-time-oriented decoder. It may branch or return early on
1224    /// malformed input and reports exact [`DecodeError`] positions. For
1225    /// secret-bearing tokens or key material where malformed-input timing
1226    /// matters, use [`crate::ct::CtEngine::decode_secret`] through
1227    /// [`crate::ct::STANDARD`], or use staged decode and then wrap the
1228    /// successful output in `SecretBuffer`.
1229    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
1230        Self::try_from(&input[..])
1231    }
1232}
1233
1234#[cfg(feature = "alloc")]
1235impl TryFrom<&str> for SecretBuffer {
1236    type Error = DecodeError;
1237
1238    /// Decodes strict standard padded Base64 text into a redacted owned buffer.
1239    ///
1240    /// Use [`crate::Engine::decode_secret`] or [`crate::Profile::decode_secret`]
1241    /// when a different alphabet, padding mode, or line-wrapping profile is
1242    /// required. These conversions always use [`crate::STANDARD`]; URL-safe,
1243    /// bcrypt, crypt, MIME, PEM, and custom alphabets must use an explicit
1244    /// engine or profile.
1245    ///
1246    /// # Security
1247    ///
1248    /// This idiomatic conversion uses the strict standard decoder, not the
1249    /// constant-time-oriented decoder. It may branch or return early on
1250    /// malformed input and reports exact [`DecodeError`] positions. For
1251    /// secret-bearing tokens or key material where malformed-input timing
1252    /// matters, use [`crate::ct::CtEngine::decode_secret`] through
1253    /// [`crate::ct::STANDARD`], or use staged decode and then wrap the
1254    /// successful output in `SecretBuffer`.
1255    fn try_from(input: &str) -> Result<Self, Self::Error> {
1256        Self::try_from(input.as_bytes())
1257    }
1258}
1259
1260#[cfg(feature = "alloc")]
1261impl core::str::FromStr for SecretBuffer {
1262    type Err = DecodeError;
1263
1264    /// Decodes strict standard padded Base64 text into a redacted owned buffer.
1265    ///
1266    /// Use [`crate::Engine::decode_secret`] or [`crate::Profile::decode_secret`]
1267    /// when a different alphabet, padding mode, or line-wrapping profile is
1268    /// required. These conversions always use [`crate::STANDARD`]; URL-safe,
1269    /// bcrypt, crypt, MIME, PEM, and custom alphabets must use an explicit
1270    /// engine or profile.
1271    ///
1272    /// # Security
1273    ///
1274    /// This idiomatic conversion uses the strict standard decoder, not the
1275    /// constant-time-oriented decoder. It may branch or return early on
1276    /// malformed input and reports exact [`DecodeError`] positions. For
1277    /// secret-bearing tokens or key material where malformed-input timing
1278    /// matters, use [`crate::ct::CtEngine::decode_secret`] through
1279    /// [`crate::ct::STANDARD`], or use staged decode and then wrap the
1280    /// successful output in `SecretBuffer`.
1281    fn from_str(input: &str) -> Result<Self, Self::Err> {
1282        Self::try_from(input)
1283    }
1284}
1285
1286#[cfg(feature = "alloc")]
1287fn string_from_validated_secret_bytes(bytes: Vec<u8>) -> String {
1288    match String::from_utf8(bytes) {
1289        Ok(string) => string,
1290        Err(error) => {
1291            let mut bytes = error.into_bytes();
1292            wipe_vec_all(&mut bytes);
1293            String::new()
1294        }
1295    }
1296}