Split format_iter into encode_to_buffer
What changed, and why it matters
This commit refactors base58 encoding so the same encoding logic can be reused with a fixed-size, no-allocation buffer (ArrayVec) as well as the existing heap buffer (Vec). It removes several `#[cfg(feature = "alloc")]` guards from shared constants and traits, and introduces a fallible `encode_to_buffer` helper. The change is a code-cleanup/refactoring step toward supporting no-alloc environments; it does not by itself fix a reported security bug.
No immediate action required. Treat as normal refactoring. If using the new `encode_to_buffer` directly in no-alloc code, ensure the destination buffer is sized using `encoded_reserve_len` / `encoded_check_reserve_len` to avoid capacity errors.
Security signals we found
Refactoring only; no direct vulnerability fix
Removes alloc feature gates from shared base58 encoding infrastructure
Introduces fallible encoding path suitable for fixed-size buffers
Existing `format_iter` caller uses `.expect()` on the result, preserving prior panic-on-OOM behavior for that path
Evidence from the diff
The patch splits format_iter into encode_to_buffer, which writes base58-encoded ASCII bytes into any Buffer (Vec or ArrayVec) and returns Result<(), T::Err>. format_iter now calls encode_to_buffer and then writes the resulting buffer to the fmt::Write sink. Several items (BASE58_CHARS, ArrayVec import, the Buffer trait, and the Buffer impl for ArrayVec) are no longer gated behind feature = "alloc", enabling no-alloc callers to use ArrayVec. The encoding loop now uses try_push and propagates capacity errors instead of panicking via Vec::push.
Changed components
base58/src/lib.rsbase58 encoding functions: format_iter, encode_to_buffer, encode_check_to_writerInspect captured patch +16 / −9
diff --git a/base58/src/lib.rs b/base58/src/lib.rs
index be0babea..44accf50 100644
--- a/base58/src/lib.rs
+++ b/base58/src/lib.rs
@@ -26,7 +26,6 @@ extern crate test;
#[cfg(feature = "std")]
extern crate std;
-#[cfg(feature = "alloc")]
static BASE58_CHARS: &[u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
#[cfg(feature = "alloc")]
@@ -45,7 +44,6 @@ pub use std::{string::String, vec::Vec};
use hashes::sha256d;
#[cfg(feature = "alloc")]
use internals::array::ArrayExt;
-#[cfg(feature = "alloc")]
use internals::array_vec::ArrayVec;
#[allow(unused)] // MSRV polyfill
#[cfg(feature = "alloc")]
@@ -220,7 +218,6 @@ const fn encoded_check_reserve_len(unencoded_len: usize) -> usize {
encoded_reserve_len(unencoded_len + 4)
}
-#[cfg(feature = "alloc")]
trait Buffer: Sized {
type Err: fmt::Debug;
@@ -246,7 +243,6 @@ impl Buffer for Vec<u8> {
fn slice_mut(&mut self) -> &mut [u8] { self }
}
-#[cfg(feature = "alloc")]
impl<const N: usize> Buffer for ArrayVec<u8, N> {
type Err = internals::array_vec::error::Error;
@@ -265,6 +261,16 @@ where
I: Iterator<Item = u8> + Clone,
W: fmt::Write,
{
+ // format_iter is only called from within encode_check_to_writer, and always has a sufficiently sized buffer.
+ encode_to_buffer(data, buf).expect("encode_to_buffer is infallible with well-sized buffer");
+
+ let str_slice =
+ core::str::from_utf8(buf.slice()).expect("encode_to_buffer writes ASCII bytes to buf");
+ writer.write_str(str_slice)
+}
+
+// Base58 encode the data in the iterator `data` to the buffer buf as ASCII bytes
+fn encode_to_buffer<I: Iterator<Item = u8>, T: Buffer>(data: I, buf: &mut T) -> Result<(), T::Err> {
let mut leading_zero_count = 0;
let mut leading_zeroes = true;
// Build string in little endian with 0-58 in place of characters...
@@ -283,18 +289,19 @@ where
}
while carry > 0 {
- buf.push((carry % 58) as u8); // cast loses data intentionally
+ buf.try_push((carry % 58) as u8)?; // cast loses data intentionally
carry /= 58;
}
}
- // ... then reverse it and convert to chars
+ // ... then reverse it and convert to ASCII
for _ in 0..leading_zero_count {
- buf.push(0);
+ buf.try_push(0)?;
}
- for ch in buf.slice().iter().rev() {
- writer.write_char(char::from(BASE58_CHARS[usize::from(*ch)]))?;
+ buf.slice_mut().reverse();
+ for ch in buf.slice_mut() {
+ *ch = BASE58_CHARS[usize::from(*ch)];
}
Ok(())
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.