From e8b04770f9da1a1ce98b3dd453ffb85c4d338040 Mon Sep 17 00:00:00 2001
From: Kris Nuttycombe <kris@nutty.land>
Date: Wed, 18 Dec 2024 15:04:55 -0700
Subject: [PATCH] Implement `no_std` support via a default-enabled `std`
 feature flag.

Add thumbv7em-none-eabihf as a no-default-features build target.
---
 .github/workflows/ci.yml            | 28 +++++++++++-
 CHANGELOG.md                        |  7 +++
 Cargo.lock                          | 38 +++++++---------
 Cargo.toml                          | 68 +++++++++++++++++++----------
 README.md                           |  9 +++-
 rust-toolchain.toml                 |  2 +-
 src/builder.rs                      | 43 +++++++++++++-----
 src/bundle.rs                       | 15 ++++---
 src/circuit.rs                      | 13 +++---
 src/circuit/constants.rs            |  1 +
 src/circuit/ecc.rs                  |  4 +-
 src/circuit/pedersen_hash.rs        |  1 +
 src/constants.rs                    |  4 ++
 src/keys.rs                         |  7 ++-
 src/lib.rs                          | 15 ++++++-
 src/note/nullifier.rs               |  5 ++-
 src/note_encryption.rs              |  9 ++--
 src/pczt.rs                         |  8 +++-
 src/pczt/io_finalizer.rs            |  1 +
 src/pczt/parse.rs                   |  4 +-
 src/pczt/updater.rs                 |  3 ++
 src/pedersen_hash.rs                | 17 +++++---
 src/pedersen_hash/test_vectors.rs   |  1 +
 src/prover.rs                       |  6 ++-
 src/test_vectors/note_encryption.rs |  2 +
 src/test_vectors/signatures.rs      |  2 +
 src/tree.rs                         | 22 ++++++----
 src/value/sums.rs                   |  1 +
 src/verifier/batch.rs               |  3 ++
 src/zip32.rs                        | 57 +++++++++++++-----------
 30 files changed, 261 insertions(+), 135 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8c57b34a..6ebcf063 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -41,12 +41,36 @@ jobs:
       matrix:
         target:
           - wasm32-wasi
+          - thumbv7em-none-eabihf
     steps:
       - uses: actions/checkout@v4
+        with:
+          path: crate_root
+      # We use a synthetic crate to ensure no dev-dependencies are enabled, which can
+      # be incompatible with some of these targets.
+      - name: Create synthetic crate for testing
+        run: cargo init --lib ci-build
+      - name: Copy Rust version into synthetic crate
+        run: cp crate_root/rust-toolchain.toml ci-build/
+      - name: Copy patch directives into synthetic crate
+        run: |
+          echo "[patch.crates-io]" >> ./ci-build/Cargo.toml
+          cat ./crate_root/Cargo.toml | sed "0,/.\+\(patch.crates.\+\)/d" >> ./ci-build/Cargo.toml
+      - name: Add no_std pragma to lib.rs
+        run: |
+          echo "#![no_std]" > ./ci-build/src/lib.rs
+      - name: Add sapling-crypto as a dependency of the synthetic crate
+        working-directory: ./ci-build
+        run: cargo add --no-default-features --path ../crate_root
+      - name: Add lazy_static with the spin_no_std feature
+        working-directory: ./ci-build
+        run: cargo add lazy_static --features "spin_no_std"
       - name: Add target
+        working-directory: ./ci-build
         run: rustup target add ${{ matrix.target }}
-      - name: Build crate
-        run: cargo build --no-default-features --verbose --target ${{ matrix.target }}
+      - name: Build for target
+        working-directory: ./ci-build
+        run: cargo build --verbose --target ${{ matrix.target }}
 
   bitrot:
     name: Bitrot check
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 53186af4..cc86cd76 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,13 @@ and this library adheres to Rust's notion of
 
 ### Added
 - `sapling_crypto::pczt::Zip32Derivation::extract_account_index`
+- `no_std` compatibility has been introduced by means of a default-enabled
+  `std` feature flag.
+- A default-enabled `circuit` is now provided to enable downstream users to
+  avoid the need to depend upon the `bellman` crate.
+
+### Changed
+- MSRV is now 1.66
 
 ## [0.4.0] - 2024-12-16
 
diff --git a/Cargo.lock b/Cargo.lock
index 2225ab87..b85b6470 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -342,6 +342,15 @@ version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6"
 
+[[package]]
+name = "core2"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "239fa3ae9b63c2dc74bd3fa852d4792b8b305ae64eeede946265b6af62f1fff3"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "cpp_demangle"
 version = "0.4.3"
@@ -815,9 +824,9 @@ dependencies = [
 
 [[package]]
 name = "memuse"
-version = "0.2.1"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2145869435ace5ea6ea3d35f59be559317ec9a0d04e1812d5f185a87b6d36f1a"
+checksum = "3d97bbf43eb4f088f8ca469930cde17fa036207c9a5e02ccc5107c4e8b17c964"
 
 [[package]]
 name = "miniz_oxide"
@@ -1202,12 +1211,10 @@ dependencies = [
 [[package]]
 name = "redjubjub"
 version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a60db2c3bc9c6fd1e8631fee75abc008841d27144be744951d6b9b75f9b569c"
+source = "git+https://github.com/nuttycom/redjubjub?rev=e413019904400f4caa3550df7c4040befadfbb14#e413019904400f4caa3550df7c4040befadfbb14"
 dependencies = [
  "rand_core",
  "reddsa",
- "serde",
  "thiserror",
  "zeroize",
 ]
@@ -1315,8 +1322,8 @@ dependencies = [
  "blake2b_simd",
  "blake2s_simd",
  "bls12_381",
- "byteorder",
  "chacha20poly1305",
+ "core2",
  "criterion",
  "document-features",
  "ff",
@@ -1504,29 +1511,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
 dependencies = [
  "pin-project-lite",
- "tracing-attributes",
  "tracing-core",
 ]
 
-[[package]]
-name = "tracing-attributes"
-version = "0.1.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
 [[package]]
 name = "tracing-core"
 version = "0.1.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
-dependencies = [
- "once_cell",
-]
 
 [[package]]
 name = "typenum"
@@ -1893,9 +1885,9 @@ dependencies = [
 
 [[package]]
 name = "zip32"
-version = "0.1.0"
+version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d724a63be4dfb50b7f3617e542984e22e4b4a5b8ca5de91f55613152885e6b22"
+checksum = "4226d0aee9c9407c27064dfeec9d7b281c917de3374e1e5a2e2cfad9e09de19e"
 dependencies = [
  "blake2b_simd",
  "memuse",
diff --git a/Cargo.toml b/Cargo.toml
index fa48cb33..f92ccd86 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,7 +7,7 @@ authors = [
     "Kris Nuttycombe <kris@electriccoin.co>",
 ]
 edition = "2021"
-rust-version = "1.65"
+rust-version = "1.66"
 description = "Cryptographic library for Zcash Sapling"
 homepage = "https://github.com/zcash/sapling-crypto"
 repository = "https://github.com/zcash/sapling-crypto"
@@ -18,48 +18,50 @@ features = ["test-dependencies"]
 rustdoc-args = ["--cfg", "docsrs"]
 
 [dependencies]
-ff = "0.13"
-group = { version = "0.13", features = ["wnaf-memuse"] }
+ff = { version = "0.13", default-features = false }
+group = "0.13"
 
-bls12_381 = "0.8"
-jubjub = "0.10"
-redjubjub = "0.7"
+bls12_381 = { version = "0.8", default-features = false, features = ["alloc"] }
+jubjub = { version = "0.10", default-features = false, features = ["alloc"] }
+redjubjub = { version = "0.7", default-features = false }
 zcash_spec = "0.1"
 
 # Boilerplate
 getset = "0.1"
 
+# No-std support
+core2 = { version = "0.3", default-features = false, features = ["alloc"] }
+
 # Circuits
-bellman = { version = "0.14", default-features = false, features = ["groth16"] }
+bellman = { version = "0.14", features = ["groth16"], optional = true }
 
 # CSPRNG
-rand = "0.8"
-rand_core = "0.6"
+rand = { version = "0.8", default-features = false }
+rand_core = { version = "0.6", default-features = false }
 
 # Digests
-blake2b_simd = "1"
-blake2s_simd = "1"
+blake2b_simd = { version = "1", default-features = false }
+blake2s_simd = { version = "1", default-features = false }
 
 # Documentation
-document-features = "0.2"
+document-features = { version = "0.2", optional = true }
 
 # Encodings
-byteorder = "1"
-hex = "0.4"
+hex = { version = "0.4", default-features = false, features = ["alloc"] }
 
 # Logging and metrics
-memuse = "0.2.1"
-tracing = "0.1"
+memuse = { version = "0.2.2", default-features = false }
+tracing = { version = "0.1", default-features = false }
 
 # Note Commitment Trees
-bitvec = "1"
-incrementalmerkletree = { version = "0.7", features = ["legacy-api"] }
+bitvec = { version = "1", default-features = false }
+incrementalmerkletree = { version = "0.7", default-features = false, features = ["legacy-api"] }
 
 # Note encryption
 zcash_note_encryption = { version = "0.4", features = ["pre-zip-212"] }
 
 # Secret management
-subtle = "2.2.3"
+subtle = { version = "2.2.3", default-features = false }
 
 # Static constants
 lazy_static = "1"
@@ -69,8 +71,9 @@ proptest = { version = "1", optional = true }
 
 # ZIP 32
 aes = "0.8"
-fpe = "0.6"
-zip32 = "0.1"
+fpe = { version = "0.6", default-features = false, features = ["alloc"] }
+zip32 = { version = "0.1.1", default-features = false }
+
 
 [dev-dependencies]
 chacha20poly1305 = "0.10"
@@ -83,10 +86,26 @@ rand_xorshift = "0.3"
 pprof = { version = "0.11", features = ["criterion", "flamegraph"] } # MSRV 1.56
 
 [features]
-default = ["multicore"]
+default = ["multicore", "std"]
+std = [
+  "core2/std",
+  "document-features",
+  "group/wnaf-memuse",
+  "redjubjub/std",
+  "circuit",
+]
+
+## Enables creation of Sapling proofs
+circuit = [
+  "bellman",
+  "bls12_381/bits",
+  "bls12_381/groups",
+  "bls12_381/pairings",
+  "jubjub/bits",
+]
 
 ## Enables multithreading support for creating proofs.
-multicore = ["bellman/multicore"]
+multicore = ["circuit", "bellman/multicore"]
 
 ### A temporary feature flag that exposes granular APIs needed by `zcashd`. These APIs
 ### should not be relied upon and will be removed in a future release.
@@ -105,3 +124,6 @@ harness = false
 [[bench]]
 name = "pedersen_hash"
 harness = false
+
+[patch.crates-io]
+redjubjub = { git = "https://github.com/nuttycom/redjubjub", rev = "e413019904400f4caa3550df7c4040befadfbb14" }
diff --git a/README.md b/README.md
index 6becc043..4f9c0df8 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,13 @@
 # sapling-crypto
 
-This repository contains a (work-in-progress) implementation of Zcash's "Sapling" cryptography.
+This repository contains an implementation of Zcash's "Sapling" cryptography.
+
+## `no_std` compatibility
+
+Downstream users of this crate must enable the `spin_no_std` feature of the
+`lazy_static` crate in order to take advantage of `no_std` builds; this is due
+to the fact that `--no-default-features` builds of `lazy_static` still rely on
+`std`.
 
 ## License
 
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
index ce699381..4a496752 100644
--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,3 +1,3 @@
 [toolchain]
-channel = "1.65.0"
+channel = "1.66.0"
 components = ["clippy", "rustfmt"]
diff --git a/src/builder.rs b/src/builder.rs
index 6665563f..4b08d6e1 100644
--- a/src/builder.rs
+++ b/src/builder.rs
@@ -1,7 +1,8 @@
 //! Types and functions for building Sapling transaction components.
 
-use core::fmt;
-use std::{collections::BTreeMap, iter, marker::PhantomData};
+use alloc::collections::BTreeMap;
+use alloc::vec::Vec;
+use core::{fmt, iter, marker::PhantomData};
 
 use group::ff::Field;
 use incrementalmerkletree::Position;
@@ -11,24 +12,27 @@ use redjubjub::{Binding, SpendAuth};
 use zcash_note_encryption::EphemeralKeyBytes;
 
 use crate::{
-    bundle::{
-        Authorization, Authorized, Bundle, GrothProofBytes, OutputDescription, SpendDescription,
-    },
-    circuit,
+    bundle::{Authorization, Authorized, Bundle, GrothProofBytes},
     keys::{
         EphemeralSecretKey, ExpandedSpendingKey, FullViewingKey, OutgoingViewingKey,
         SpendAuthorizingKey, SpendValidatingKey,
     },
     note::ExtractedNoteCommitment,
     note_encryption::{sapling_note_encryption, Zip212Enforcement},
-    prover::{OutputProver, SpendProver},
     util::generate_random_rseed_internal,
-    value::{
-        CommitmentSum, NoteValue, TrapdoorSum, ValueCommitTrapdoor, ValueCommitment, ValueSum,
-    },
+    value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum},
+    Anchor, Diversifier, MerklePath, Node, Note, Nullifier, PaymentAddress, SaplingIvk,
+    NOTE_COMMITMENT_TREE_DEPTH,
+};
+
+#[cfg(feature = "circuit")]
+use crate::{
+    bundle::{OutputDescription, SpendDescription},
+    circuit,
+    prover::{OutputProver, SpendProver},
+    value::{CommitmentSum, TrapdoorSum},
     zip32::ExtendedSpendingKey,
-    Anchor, Diversifier, MerklePath, Node, Note, Nullifier, PaymentAddress, ProofGenerationKey,
-    SaplingIvk, NOTE_COMMITMENT_TREE_DEPTH,
+    ProofGenerationKey,
 };
 
 /// If there are any shielded inputs, always have at least two shielded outputs, padding
@@ -268,6 +272,7 @@ impl PreparedSpendInfo {
         (cv, nullifier, rk, alpha)
     }
 
+    #[cfg(feature = "circuit")]
     fn build<Pr: SpendProver, R: RngCore>(
         self,
         proof_generation_key: Option<ProofGenerationKey>,
@@ -464,6 +469,7 @@ impl PreparedOutputInfo {
         )
     }
 
+    #[cfg(feature = "circuit")]
     fn build<Pr: OutputProver, R: RngCore>(
         self,
         rng: &mut R,
@@ -664,6 +670,7 @@ impl Builder {
     }
 
     /// Constructs the Sapling bundle from the builder's accumulated state.
+    #[cfg(feature = "circuit")]
     pub fn build<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
         self,
         extsks: &[ExtendedSpendingKey],
@@ -726,6 +733,7 @@ impl Builder {
 
 /// Constructs a new Sapling transaction bundle of the given type from the specified set of spends
 /// and outputs.
+#[cfg(feature = "circuit")]
 pub fn bundle<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
     rng: R,
     bundle_type: BundleType,
@@ -917,6 +925,7 @@ fn build_bundle<B, SP, OP, R: RngCore>(
 /// Type alias for an in-progress bundle that has no proofs or signatures.
 ///
 /// This is returned by [`Builder::build`].
+#[cfg(feature = "circuit")]
 pub type UnauthorizedBundle<V> = Bundle<InProgress<Unproven, Unsigned>, V>;
 
 /// Marker trait representing bundle proofs in the process of being created.
@@ -951,9 +960,11 @@ impl<P: InProgressProofs, S: InProgressSignatures> Authorization for InProgress<
 ///
 /// The [`SpendDescription`]s and [`OutputDescription`]s within the bundle contain the
 /// private data needed to create proofs.
+#[cfg(feature = "circuit")]
 #[derive(Clone, Copy, Debug)]
 pub struct Unproven;
 
+#[cfg(feature = "circuit")]
 impl InProgressProofs for Unproven {
     type SpendProof = circuit::Spend;
     type OutputProof = circuit::Output;
@@ -969,16 +980,19 @@ impl InProgressProofs for Proven {
 }
 
 /// Reports on the progress made towards creating proofs for a bundle.
+#[cfg(feature = "circuit")]
 pub trait ProverProgress {
     /// Updates the progress instance with the number of steps completed and the total
     /// number of steps.
     fn update(&mut self, cur: u32, end: u32);
 }
 
+#[cfg(feature = "circuit")]
 impl ProverProgress for () {
     fn update(&mut self, _: u32, _: u32) {}
 }
 
+#[cfg(feature = "circuit")]
 impl<U: From<(u32, u32)>> ProverProgress for std::sync::mpsc::Sender<U> {
     fn update(&mut self, cur: u32, end: u32) {
         // If the send fails, we should ignore the error, not crash.
@@ -986,12 +1000,14 @@ impl<U: From<(u32, u32)>> ProverProgress for std::sync::mpsc::Sender<U> {
     }
 }
 
+#[cfg(feature = "circuit")]
 impl<U: ProverProgress> ProverProgress for &mut U {
     fn update(&mut self, cur: u32, end: u32) {
         (*self).update(cur, end);
     }
 }
 
+#[cfg(feature = "circuit")]
 struct CreateProofs<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: ProverProgress> {
     spend_prover: &'a SP,
     output_prover: &'a OP,
@@ -1001,6 +1017,7 @@ struct CreateProofs<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: Prover
     progress: u32,
 }
 
+#[cfg(feature = "circuit")]
 impl<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: ProverProgress>
     CreateProofs<'a, SP, OP, R, U>
 {
@@ -1041,6 +1058,7 @@ impl<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: ProverProgress>
         OP::encode_proof(proof)
     }
 
+    #[cfg(feature = "circuit")]
     fn map_authorization<S: InProgressSignatures>(
         &mut self,
         a: InProgress<Unproven, S>,
@@ -1052,6 +1070,7 @@ impl<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: ProverProgress>
     }
 }
 
+#[cfg(feature = "circuit")]
 impl<S: InProgressSignatures, V> Bundle<InProgress<Unproven, S>, V> {
     /// Creates the proofs for this bundle.
     pub fn create_proofs<SP: SpendProver, OP: OutputProver>(
diff --git a/src/bundle.rs b/src/bundle.rs
index b015f133..f81353c3 100644
--- a/src/bundle.rs
+++ b/src/bundle.rs
@@ -1,3 +1,4 @@
+use alloc::vec::Vec;
 use core::fmt::Debug;
 
 use memuse::DynamicUsage;
@@ -9,7 +10,7 @@ use zcash_note_encryption::{
 };
 
 use crate::{
-    circuit::GROTH_PROOF_SIZE,
+    constants::GROTH_PROOF_SIZE,
     note::ExtractedNoteCommitment,
     note_encryption::{CompactOutputDescription, SaplingDomain},
     value::ValueCommitment,
@@ -220,8 +221,8 @@ pub struct SpendDescription<A: Authorization> {
     spend_auth_sig: A::AuthSig,
 }
 
-impl<A: Authorization> std::fmt::Debug for SpendDescription<A> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+impl<A: Authorization> core::fmt::Debug for SpendDescription<A> {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
         write!(
             f,
             "SpendDescription(cv = {:?}, anchor = {:?}, nullifier = {:?}, rk = {:?}, spend_auth_sig = {:?})",
@@ -431,8 +432,8 @@ impl<A> ShieldedOutput<SaplingDomain, ENC_CIPHERTEXT_SIZE> for OutputDescription
     }
 }
 
-impl<A> std::fmt::Debug for OutputDescription<A> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+impl<A> core::fmt::Debug for OutputDescription<A> {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
         write!(
             f,
             "OutputDescription(cv = {:?}, cmu = {:?}, ephemeral_key = {:?})",
@@ -498,7 +499,7 @@ impl<A> From<OutputDescription<A>> for CompactOutputDescription {
 #[cfg(any(test, feature = "test-dependencies"))]
 #[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
 pub mod testing {
-    use std::fmt;
+    use core::fmt;
 
     use ff::Field;
     use group::{Group, GroupEncoding};
@@ -507,7 +508,7 @@ pub mod testing {
     use rand::{rngs::StdRng, SeedableRng};
 
     use crate::{
-        circuit::GROTH_PROOF_SIZE,
+        constants::GROTH_PROOF_SIZE,
         note::testing::arb_cmu,
         value::{
             testing::{arb_note_value_bounded, arb_trapdoor},
diff --git a/src/circuit.rs b/src/circuit.rs
index 611dc887..2a67f319 100644
--- a/src/circuit.rs
+++ b/src/circuit.rs
@@ -1,15 +1,14 @@
 //! The Sapling circuits.
 
+use alloc::vec::Vec;
 use core::fmt;
-use std::io;
+use core2::io;
 
 use group::{ff::PrimeField, Curve};
 
 use bellman::{groth16, Circuit, ConstraintSystem, SynthesisError};
 use bls12_381::Bls12;
 
-use super::{value::NoteValue, PaymentAddress, ProofGenerationKey};
-
 use bellman::gadgets::blake2s;
 use bellman::gadgets::boolean;
 use bellman::gadgets::multipack;
@@ -21,6 +20,7 @@ use self::constants::{
     PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR,
     VALUE_COMMITMENT_VALUE_GENERATOR,
 };
+use crate::{value::NoteValue, PaymentAddress, ProofGenerationKey};
 
 #[cfg(test)]
 use group::ff::PrimeFieldBits;
@@ -29,9 +29,6 @@ mod constants;
 mod ecc;
 mod pedersen_hash;
 
-// π_A + π_B + π_C
-pub(crate) const GROTH_PROOF_SIZE: usize = 48 + 96 + 48;
-
 /// The opening (value and randomness) of a Sapling value commitment.
 #[derive(Clone)]
 pub struct ValueCommitmentOpening {
@@ -703,7 +700,7 @@ fn test_input_circuit_with_bls12_381() {
                 let mut rhs = uncle;
 
                 if b {
-                    ::std::mem::swap(&mut lhs, &mut rhs);
+                    ::core::mem::swap(&mut lhs, &mut rhs);
                 }
 
                 let lhs = lhs.to_le_bits();
@@ -886,7 +883,7 @@ fn test_input_circuit_with_bls12_381_external_test_vectors() {
                 let mut rhs = uncle;
 
                 if b {
-                    ::std::mem::swap(&mut lhs, &mut rhs);
+                    ::core::mem::swap(&mut lhs, &mut rhs);
                 }
 
                 let lhs = lhs.to_le_bits();
diff --git a/src/circuit/constants.rs b/src/circuit/constants.rs
index 692c688f..17ae9593 100644
--- a/src/circuit/constants.rs
+++ b/src/circuit/constants.rs
@@ -2,6 +2,7 @@
 
 use crate::constants::{PEDERSEN_HASH_CHUNKS_PER_GENERATOR, PEDERSEN_HASH_GENERATORS};
 
+use alloc::vec::Vec;
 use bls12_381::Scalar;
 use group::{ff::Field, Curve, Group};
 use jubjub::ExtendedPoint;
diff --git a/src/circuit/ecc.rs b/src/circuit/ecc.rs
index 6bffd876..ebe73314 100644
--- a/src/circuit/ecc.rs
+++ b/src/circuit/ecc.rs
@@ -1,6 +1,7 @@
 //! Gadgets implementing Jubjub elliptic curve operations.
 
-use std::ops::{AddAssign, MulAssign, Neg, SubAssign};
+use alloc::vec::Vec;
+use core::ops::{AddAssign, MulAssign, Neg, SubAssign};
 
 use bellman::{ConstraintSystem, SynthesisError};
 
@@ -620,6 +621,7 @@ impl MontgomeryPoint {
 
 #[cfg(test)]
 mod test {
+    use alloc::vec::Vec;
     use bellman::ConstraintSystem;
     use group::{
         ff::{Field, PrimeField, PrimeFieldBits},
diff --git a/src/circuit/pedersen_hash.rs b/src/circuit/pedersen_hash.rs
index 94b90140..29718a8a 100644
--- a/src/circuit/pedersen_hash.rs
+++ b/src/circuit/pedersen_hash.rs
@@ -3,6 +3,7 @@
 use super::ecc::{EdwardsPoint, MontgomeryPoint};
 pub use crate::pedersen_hash::Personalization;
 
+use alloc::vec::Vec;
 use bellman::gadgets::boolean::Boolean;
 use bellman::gadgets::lookup::*;
 use bellman::{ConstraintSystem, SynthesisError};
diff --git a/src/constants.rs b/src/constants.rs
index bb730a97..0bfdf4db 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -1,5 +1,6 @@
 //! Various constants used by the Sapling protocol.
 
+use alloc::vec::Vec;
 use ff::PrimeField;
 use group::Group;
 use jubjub::SubgroupPoint;
@@ -38,6 +39,9 @@ pub const VALUE_COMMITMENT_GENERATOR_PERSONALIZATION: &[u8; 8] = b"Zcash_cv";
 /// BLAKE2s Personalization for the nullifier position generator (for computing rho)
 pub const NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION: &[u8; 8] = b"Zcash_J_";
 
+// π_A + π_B + π_C
+pub(crate) const GROTH_PROOF_SIZE: usize = 48 + 96 + 48;
+
 /// The prover will demonstrate knowledge of discrete log with respect to this base when
 /// they are constructing a proof, in order to authorize proof construction.
 pub const PROOF_GENERATION_KEY_GENERATOR: SubgroupPoint = SubgroupPoint::from_raw_unchecked(
diff --git a/src/keys.rs b/src/keys.rs
index b636785d..9e583b09 100644
--- a/src/keys.rs
+++ b/src/keys.rs
@@ -4,8 +4,9 @@
 //!
 //! [section 4.2.2]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
 
-use std::fmt;
-use std::io::{self, Read, Write};
+use alloc::vec::Vec;
+use core::fmt;
+use core2::io::{self, Read, Write};
 
 use super::{
     address::PaymentAddress,
@@ -444,6 +445,7 @@ impl SaplingIvk {
 #[derive(Clone, Debug)]
 pub struct PreparedIncomingViewingKey(PreparedScalar);
 
+#[cfg(feature = "std")]
 impl memuse::DynamicUsage for PreparedIncomingViewingKey {
     fn dynamic_usage(&self) -> usize {
         self.0.dynamic_usage()
@@ -690,6 +692,7 @@ pub mod testing {
 
 #[cfg(test)]
 mod tests {
+    use alloc::string::ToString;
     use group::{Group, GroupEncoding};
 
     use super::{FullViewingKey, SpendAuthorizingKey, SpendValidatingKey};
diff --git a/src/lib.rs b/src/lib.rs
index 654b767a..8cc57207 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -8,17 +8,26 @@
 //! opposed to e.g. an Orchard payment address, which is also shielded).
 //!
 //! ## Feature flags
-#![doc = document_features::document_features!()]
+#![cfg_attr(feature = "std", doc = document_features::document_features!())]
 //!
 
+#![no_std]
 #![cfg_attr(docsrs, feature(doc_cfg))]
 // Catch documentation errors caused by code changes.
 #![deny(rustdoc::broken_intra_doc_links)]
 #![deny(unsafe_code)]
 
+#[macro_use]
+extern crate alloc;
+
+#[cfg(feature = "std")]
+extern crate std;
+
 mod address;
 pub mod builder;
 pub mod bundle;
+
+#[cfg(feature = "circuit")]
 pub mod circuit;
 pub mod constants;
 pub mod group_hash;
@@ -27,11 +36,13 @@ pub mod note;
 pub mod note_encryption;
 pub mod pczt;
 pub mod pedersen_hash;
+#[cfg(feature = "circuit")]
 pub mod prover;
 mod spec;
 mod tree;
 pub mod util;
 pub mod value;
+#[cfg(feature = "circuit")]
 mod verifier;
 pub mod zip32;
 
@@ -43,6 +54,8 @@ pub use tree::{
     merkle_hash, Anchor, CommitmentTree, IncrementalWitness, MerklePath, Node,
     NOTE_COMMITMENT_TREE_DEPTH,
 };
+
+#[cfg(feature = "circuit")]
 pub use verifier::{BatchValidator, SaplingVerificationContext};
 
 #[cfg(any(test, feature = "test-dependencies"))]
diff --git a/src/note/nullifier.rs b/src/note/nullifier.rs
index 8176f40d..58fd01b8 100644
--- a/src/note/nullifier.rs
+++ b/src/note/nullifier.rs
@@ -1,5 +1,6 @@
-use std::array::TryFromSliceError;
-use std::fmt;
+use alloc::fmt;
+use alloc::vec::Vec;
+use core::array::TryFromSliceError;
 
 use subtle::{Choice, ConstantTimeEq};
 
diff --git a/src/note_encryption.rs b/src/note_encryption.rs
index 48f03afa..756e04ec 100644
--- a/src/note_encryption.rs
+++ b/src/note_encryption.rs
@@ -2,8 +2,8 @@
 //!
 //! NB: the example code is only covering the post-Canopy case.
 
+use alloc::vec::Vec;
 use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
-use byteorder::{LittleEndian, WriteBytesExt};
 use ff::PrimeField;
 use memuse::DynamicUsage;
 use rand_core::RngCore;
@@ -193,9 +193,7 @@ impl Domain for SaplingDomain {
             Rseed::AfterZip212(_) => 2,
         };
         input[1..12].copy_from_slice(&note.recipient().diversifier().0);
-        (&mut input[12..20])
-            .write_u64::<LittleEndian>(note.value().inner())
-            .unwrap();
+        input[12..20].copy_from_slice(&note.value().inner().to_le_bytes());
 
         match note.rseed() {
             Rseed::BeforeZip212(rcm) => {
@@ -462,6 +460,7 @@ pub fn try_sapling_output_recovery(
 
 #[cfg(test)]
 mod tests {
+    use alloc::vec::Vec;
     use chacha20poly1305::{
         aead::{AeadInPlace, KeyInit},
         ChaCha20Poly1305,
@@ -486,7 +485,7 @@ mod tests {
 
     use crate::{
         bundle::{GrothProofBytes, OutputDescription},
-        circuit::GROTH_PROOF_SIZE,
+        constants::GROTH_PROOF_SIZE,
         keys::{DiversifiedTransmissionKey, EphemeralSecretKey, OutgoingViewingKey},
         note::ExtractedNoteCommitment,
         note_encryption::PreparedIncomingViewingKey,
diff --git a/src/pczt.rs b/src/pczt.rs
index 0a6ca0cc..c0badbb4 100644
--- a/src/pczt.rs
+++ b/src/pczt.rs
@@ -1,7 +1,9 @@
 //! PCZT support for Sapling.
 
+use alloc::collections::BTreeMap;
+use alloc::string::String;
+use alloc::vec::Vec;
 use core::fmt;
-use std::collections::BTreeMap;
 
 use getset::Getters;
 use redjubjub::{Binding, SpendAuth};
@@ -30,7 +32,9 @@ pub use io_finalizer::IoFinalizerError;
 mod updater;
 pub use updater::{OutputUpdater, SpendUpdater, Updater, UpdaterError};
 
+#[cfg(feature = "circuit")]
 mod prover;
+#[cfg(feature = "circuit")]
 pub use prover::ProverError;
 
 mod signer;
@@ -277,7 +281,7 @@ pub struct Output {
 }
 
 impl fmt::Debug for Output {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
         f.debug_struct("Output")
             .field("cv", &self.cv)
             .field("cmu", &self.cmu)
diff --git a/src/pczt/io_finalizer.rs b/src/pczt/io_finalizer.rs
index 723a4aa4..81642296 100644
--- a/src/pczt/io_finalizer.rs
+++ b/src/pczt/io_finalizer.rs
@@ -1,3 +1,4 @@
+use alloc::vec::Vec;
 use rand::{CryptoRng, RngCore};
 
 use crate::value::{CommitmentSum, TrapdoorSum};
diff --git a/src/pczt/parse.rs b/src/pczt/parse.rs
index 4ac77db9..dc11af4d 100644
--- a/src/pczt/parse.rs
+++ b/src/pczt/parse.rs
@@ -1,4 +1,6 @@
-use std::collections::BTreeMap;
+use alloc::collections::BTreeMap;
+use alloc::string::String;
+use alloc::vec::Vec;
 
 use ff::PrimeField;
 use zcash_note_encryption::{EphemeralKeyBytes, OutgoingCipherKey};
diff --git a/src/pczt/updater.rs b/src/pczt/updater.rs
index 71c7d7a1..357c3ecc 100644
--- a/src/pczt/updater.rs
+++ b/src/pczt/updater.rs
@@ -1,3 +1,6 @@
+use alloc::string::String;
+use alloc::vec::Vec;
+
 use crate::ProofGenerationKey;
 
 use super::{Bundle, Output, Spend, Zip32Derivation};
diff --git a/src/pedersen_hash.rs b/src/pedersen_hash.rs
index 20cc40a5..43d4a102 100644
--- a/src/pedersen_hash.rs
+++ b/src/pedersen_hash.rs
@@ -3,14 +3,12 @@
 #[cfg(test)]
 pub(crate) mod test_vectors;
 
-use byteorder::{ByteOrder, LittleEndian};
+use alloc::vec::Vec;
+use core::ops::{AddAssign, Neg};
 use ff::PrimeField;
 use group::Group;
-use std::ops::{AddAssign, Neg};
 
-use super::constants::{
-    PEDERSEN_HASH_CHUNKS_PER_GENERATOR, PEDERSEN_HASH_EXP_TABLE, PEDERSEN_HASH_EXP_WINDOW_SIZE,
-};
+use super::constants::{PEDERSEN_HASH_CHUNKS_PER_GENERATOR, PEDERSEN_HASH_EXP_WINDOW_SIZE};
 
 #[derive(Copy, Clone)]
 pub enum Personalization {
@@ -41,7 +39,7 @@ where
         .chain(bits.into_iter());
 
     let mut result = jubjub::SubgroupPoint::identity();
-    let mut generators = PEDERSEN_HASH_EXP_TABLE.iter();
+    let mut generators = crate::constants::PEDERSEN_HASH_EXP_TABLE.iter();
 
     loop {
         let mut acc = jubjub::Fr::zero();
@@ -94,7 +92,11 @@ where
         let acc = acc.to_repr();
         let num_limbs: usize = acc.as_ref().len() / 8;
         let mut limbs = vec![0u64; num_limbs + 1];
-        LittleEndian::read_u64_into(acc.as_ref(), &mut limbs[..num_limbs]);
+        for (src, dst) in acc.chunks_exact(8).zip(limbs[..num_limbs].iter_mut()) {
+            let mut limb_bytes = [0u8; 8];
+            limb_bytes.copy_from_slice(src);
+            *dst = u64::from_le_bytes(limb_bytes);
+        }
 
         let mut tmp = jubjub::SubgroupPoint::identity();
 
@@ -124,6 +126,7 @@ where
 
 #[cfg(test)]
 pub mod test {
+    use alloc::string::ToString;
     use group::Curve;
 
     use super::*;
diff --git a/src/pedersen_hash/test_vectors.rs b/src/pedersen_hash/test_vectors.rs
index 4d051afc..84cd8d84 100644
--- a/src/pedersen_hash/test_vectors.rs
+++ b/src/pedersen_hash/test_vectors.rs
@@ -1,6 +1,7 @@
 //! Test vectors from https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_pedersen.py
 
 use super::{test::TestVector, Personalization};
+use alloc::vec::Vec;
 
 pub fn get_vectors<'a>() -> Vec<TestVector<'a>> {
     vec![
diff --git a/src/prover.rs b/src/prover.rs
index f5e2ad7f..cfcbcb28 100644
--- a/src/prover.rs
+++ b/src/prover.rs
@@ -6,7 +6,8 @@ use rand_core::RngCore;
 
 use crate::{
     bundle::GrothProofBytes,
-    circuit::{self, GROTH_PROOF_SIZE},
+    circuit,
+    constants::GROTH_PROOF_SIZE,
     keys::EphemeralSecretKey,
     value::{NoteValue, ValueCommitTrapdoor},
     MerklePath,
@@ -179,7 +180,8 @@ pub mod mock {
     use super::{OutputProver, SpendProver};
     use crate::{
         bundle::GrothProofBytes,
-        circuit::{self, ValueCommitmentOpening, GROTH_PROOF_SIZE},
+        circuit::{self, ValueCommitmentOpening},
+        constants::GROTH_PROOF_SIZE,
         keys::EphemeralSecretKey,
         value::{NoteValue, ValueCommitTrapdoor},
         Diversifier, MerklePath, PaymentAddress, ProofGenerationKey, Rseed,
diff --git a/src/test_vectors/note_encryption.rs b/src/test_vectors/note_encryption.rs
index 09209f29..9f7073c6 100644
--- a/src/test_vectors/note_encryption.rs
+++ b/src/test_vectors/note_encryption.rs
@@ -1,3 +1,5 @@
+use alloc::vec::Vec;
+
 pub(crate) struct TestVector {
     pub ovk: [u8; 32],
     pub ivk: [u8; 32],
diff --git a/src/test_vectors/signatures.rs b/src/test_vectors/signatures.rs
index 7ce341cf..bec89fbd 100644
--- a/src/test_vectors/signatures.rs
+++ b/src/test_vectors/signatures.rs
@@ -1,3 +1,5 @@
+use alloc::vec::Vec;
+
 pub(crate) struct TestVector {
     pub(crate) sk: [u8; 32],
     pub(crate) vk: [u8; 32],
diff --git a/src/tree.rs b/src/tree.rs
index ea339b4b..11c3906d 100644
--- a/src/tree.rs
+++ b/src/tree.rs
@@ -4,7 +4,8 @@ use incrementalmerkletree::{Hashable, Level};
 use lazy_static::lazy_static;
 use subtle::CtOption;
 
-use std::fmt;
+use alloc::vec::Vec;
+use core::fmt;
 
 use super::{
     note::ExtractedNoteCommitment,
@@ -20,14 +21,16 @@ pub type MerklePath = incrementalmerkletree::MerklePath<Node, NOTE_COMMITMENT_TR
 
 lazy_static! {
     static ref UNCOMMITTED_SAPLING: bls12_381::Scalar = bls12_381::Scalar::one();
-    static ref EMPTY_ROOTS: Vec<Node> = {
-        let mut v = vec![Node::empty_leaf()];
-        for d in 0..NOTE_COMMITMENT_TREE_DEPTH {
-            let next = Node::combine(d.into(), &v[usize::from(d)], &v[usize::from(d)]);
-            v.push(next);
-        }
-        v
-    };
+    static ref EMPTY_ROOTS: Vec<Node> = empty_roots();
+}
+
+fn empty_roots() -> Vec<Node> {
+    let mut v = vec![Node::empty_leaf()];
+    for d in 0..NOTE_COMMITMENT_TREE_DEPTH {
+        let next = Node::combine(d.into(), &v[usize::from(d)], &v[usize::from(d)]);
+        v.push(next);
+    }
+    v
 }
 
 /// Compute a parent node in the Sapling commitment tree given its two children.
@@ -144,6 +147,7 @@ impl Node {
     }
 
     /// Returns the wrapped value
+    #[cfg(feature = "circuit")]
     pub(crate) fn inner(&self) -> &jubjub::Base {
         &self.0
     }
diff --git a/src/value/sums.rs b/src/value/sums.rs
index 05436067..12235441 100644
--- a/src/value/sums.rs
+++ b/src/value/sums.rs
@@ -18,6 +18,7 @@ impl fmt::Display for OverflowError {
     }
 }
 
+#[cfg(feature = "std")]
 impl std::error::Error for OverflowError {}
 
 /// A sum of Sapling note values.
diff --git a/src/verifier/batch.rs b/src/verifier/batch.rs
index 0eb196e0..d6306202 100644
--- a/src/verifier/batch.rs
+++ b/src/verifier/batch.rs
@@ -140,7 +140,10 @@ impl BatchValidator {
         }
 
         if let Err(e) = self.signatures.verify(&mut rng) {
+            #[cfg(feature = "std")]
             tracing::debug!("Signature batch validation failed: {}", e);
+            #[cfg(not(feature = "std"))]
+            tracing::debug!("Signature batch validation failed: {:?}", e);
             return false;
         }
 
diff --git a/src/zip32.rs b/src/zip32.rs
index ea744f42..bf04deb4 100644
--- a/src/zip32.rs
+++ b/src/zip32.rs
@@ -6,14 +6,13 @@
 
 use aes::Aes256;
 use blake2b_simd::Params as Blake2bParams;
-use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt};
 use fpe::ff1::{BinaryNumeralString, FF1};
 use subtle::CtOption;
 use zcash_spec::PrfExpand;
 use zip32::{ChainCode, ChildIndex, DiversifierIndex, Scope};
 
-use std::io::{self, Read, Write};
-use std::ops::AddAssign;
+use core::ops::AddAssign;
+use core2::io::{self, Read, Write};
 
 use super::{Diversifier, NullifierDerivingKey, PaymentAddress, ViewingKey};
 use crate::note_encryption::PreparedIncomingViewingKey;
@@ -269,7 +268,7 @@ pub struct ExtendedSpendingKey {
     dk: DiversifierKey,
 }
 
-impl std::cmp::PartialEq for ExtendedSpendingKey {
+impl core::cmp::PartialEq for ExtendedSpendingKey {
     fn eq(&self, rhs: &ExtendedSpendingKey) -> bool {
         self.depth == rhs.depth
             && self.parent_fvk_tag == rhs.parent_fvk_tag
@@ -282,8 +281,8 @@ impl std::cmp::PartialEq for ExtendedSpendingKey {
     }
 }
 
-impl std::fmt::Debug for ExtendedSpendingKey {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+impl core::fmt::Debug for ExtendedSpendingKey {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
         write!(
             f,
             "ExtendedSpendingKey(d = {}, tag_p = {:?}, i = {:?})",
@@ -354,17 +353,20 @@ impl ExtendedSpendingKey {
     /// Reads and decodes the encoded form of the extended spending key as defined in
     /// [ZIP 32](https://zips.z.cash/zip-0032) from the provided reader.
     pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
-        let depth = reader.read_u8()?;
+        let mut depth = [0; 1];
+        reader.read_exact(&mut depth)?;
+        let depth = depth[0];
         let mut tag = [0; 4];
         reader.read_exact(&mut tag)?;
-        let child_index = reader.read_u32::<LittleEndian>().and_then(|i| {
-            KeyIndex::new(depth, i).ok_or_else(|| {
-                io::Error::new(
-                    io::ErrorKind::Unsupported,
+        let mut child_index_bytes = [0; 4];
+        reader.read_exact(&mut child_index_bytes)?;
+        let child_index =
+            KeyIndex::new(depth, u32::from_le_bytes(child_index_bytes)).ok_or_else(|| {
+                core2::io::Error::new(
+                    core2::io::ErrorKind::InvalidData,
                     "Unsupported child index in encoding",
                 )
-            })
-        })?;
+            })?;
         let mut c = [0; 32];
         reader.read_exact(&mut c)?;
         let expsk = ExpandedSpendingKey::read(&mut reader)?;
@@ -420,7 +422,7 @@ impl ExtendedSpendingKey {
         let fvk = FullViewingKey::from_expanded_spending_key(&self.expsk);
         let tmp = {
             let mut le_i = [0; 4];
-            LittleEndian::write_u32(&mut le_i, i.index());
+            le_i.copy_from_slice(&i.index().to_le_bytes());
             PrfExpand::SAPLING_ZIP32_CHILD_HARDENED.with(
                 self.chain_code.as_bytes(),
                 &self.expsk.to_bytes(),
@@ -529,7 +531,7 @@ pub struct ExtendedFullViewingKey {
     pub(crate) dk: DiversifierKey,
 }
 
-impl std::cmp::PartialEq for ExtendedFullViewingKey {
+impl core::cmp::PartialEq for ExtendedFullViewingKey {
     fn eq(&self, rhs: &ExtendedFullViewingKey) -> bool {
         self.depth == rhs.depth
             && self.parent_fvk_tag == rhs.parent_fvk_tag
@@ -542,8 +544,8 @@ impl std::cmp::PartialEq for ExtendedFullViewingKey {
     }
 }
 
-impl std::fmt::Debug for ExtendedFullViewingKey {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+impl core::fmt::Debug for ExtendedFullViewingKey {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
         write!(
             f,
             "ExtendedFullViewingKey(d = {}, tag_p = {:?}, i = {:?})",
@@ -554,17 +556,20 @@ impl std::fmt::Debug for ExtendedFullViewingKey {
 
 impl ExtendedFullViewingKey {
     pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
-        let depth = reader.read_u8()?;
+        let mut depth = [0; 1];
+        reader.read_exact(&mut depth)?;
+        let depth = depth[0];
         let mut tag = [0; 4];
         reader.read_exact(&mut tag)?;
-        let child_index = reader.read_u32::<LittleEndian>().and_then(|i| {
-            KeyIndex::new(depth, i).ok_or_else(|| {
-                io::Error::new(
-                    io::ErrorKind::Unsupported,
+        let mut child_index_bytes = [0; 4];
+        reader.read_exact(&mut child_index_bytes)?;
+        let child_index =
+            KeyIndex::new(depth, u32::from_le_bytes(child_index_bytes)).ok_or_else(|| {
+                core2::io::Error::new(
+                    core2::io::ErrorKind::InvalidData,
                     "Unsupported child index in encoding",
                 )
-            })
-        })?;
+            })?;
         let mut c = [0; 32];
         reader.read_exact(&mut c)?;
         let fvk = FullViewingKey::read(&mut reader)?;
@@ -582,9 +587,9 @@ impl ExtendedFullViewingKey {
     }
 
     pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
-        writer.write_u8(self.depth)?;
+        writer.write_all(&[self.depth])?;
         writer.write_all(&self.parent_fvk_tag.0)?;
-        writer.write_u32::<LittleEndian>(self.child_index.index())?;
+        writer.write_all(&self.child_index.index().to_le_bytes())?;
         writer.write_all(self.chain_code.as_bytes())?;
         writer.write_all(&self.fvk.to_bytes())?;
         writer.write_all(&self.dk.0)?;