diff --git a/Cargo.toml b/Cargo.toml
index 4ae104f..9fb3f1d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,6 +9,7 @@ license = "MIT OR Apache-2.0"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
+once_cell = "1.8"
 ark-ff = "0.3"
-ark-sponge = { git = "https://github.com/arkworks-rs/sponge", rev = "51d6fc9aac1fa69f44a04839202b5de828584ed8" }
+ark-sponge = { git = "https://github.com/penumbra-zone/sponge", branch = "split-sponge" }
 ark-ed-on-bls12-377 = "0.3"
diff --git a/src/hash.rs b/src/hash.rs
new file mode 100644
index 0000000..883dc4e
--- /dev/null
+++ b/src/hash.rs
@@ -0,0 +1,57 @@
+use crate::{Fq, State};
+
+/// Hash a single [`Fq`] element with the provided `domain_separator`.
+pub fn hash_1(domain_separator: &Fq, value: Fq) -> Fq {
+    let mut state = State::from(crate::RATE_1_PARAMS.clone());
+
+    // Use the domain separator as the sponge's capacity element
+    state[0] = domain_separator.clone();
+    state[1] = value;
+
+    state.permute();
+    state[1]
+}
+
+/// Hash two [`Fq`] elements with the provided `domain_separator`.
+pub fn hash_2(domain_separator: &Fq, value: (Fq, Fq)) -> Fq {
+    let mut state = State::from(crate::RATE_2_PARAMS.clone());
+
+    // Use the domain separator as the sponge's capacity element
+    state[0] = domain_separator.clone();
+    state[1] = value.0;
+    state[2] = value.1;
+
+    state.permute();
+    state[1]
+}
+
+/// Hash four [`Fq`] elements with the provided `domain_separator`.
+pub fn hash_4(domain_separator: &Fq, value: (Fq, Fq, Fq, Fq)) -> Fq {
+    let mut state = State::from(crate::RATE_4_PARAMS.clone());
+
+    // Use the domain separator as the sponge's capacity element
+    state[0] = domain_separator.clone();
+    state[1] = value.0;
+    state[2] = value.1;
+    state[3] = value.2;
+    state[4] = value.3;
+
+    state.permute();
+    state[1]
+}
+
+/// Hash five [`Fq`] elements with the provided `domain_separator`.
+pub fn hash_5(domain_separator: &Fq, value: (Fq, Fq, Fq, Fq, Fq)) -> Fq {
+    let mut state = State::from(crate::RATE_5_PARAMS.clone());
+
+    // Use the domain separator as the sponge's capacity element
+    state[0] = domain_separator.clone();
+    state[1] = value.0;
+    state[2] = value.1;
+    state[3] = value.2;
+    state[4] = value.3;
+    state[5] = value.4;
+
+    state.permute();
+    state[1]
+}
diff --git a/src/lib.rs b/src/lib.rs
index def84ee..8bec289 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,45 +1,20 @@
-mod sponge;
+//! An instantiation of Poseidon for the BLS12-377 scalar field.
 
-pub mod params;
+use once_cell::sync::Lazy;
 
-// Since we depend on a git version of ark-sponge, re-exporting it here means
-// our deps can access it without having to keep git revisions in sync.
-//
-// Going forward, this re-export should be removed and the functionality our
-// deps need from direct use of ark-sponge should be folded into this crate.
-// However, it's faster to iterate on required functionality without imposing
-// hard compartmentalization boundaries from the start.
-pub use ark_sponge;
+mod hash;
+mod params;
 
-#[cfg(test)]
-mod tests {
-    use super::*;
+pub use hash::{hash_1, hash_2, hash_4, hash_5};
 
-    #[test]
-    fn it_works() {
-        use ark_ed_on_bls12_377::Fq; // lazy import, fix
-        use ark_ff::{One, Zero};
-        use ark_sponge::{
-            poseidon::PoseidonSponge, CryptographicSponge, DuplexSpongeMode,
-            FieldBasedCryptographicSponge,
-        };
+/// Parameters for the rate-1 instance of Poseidon.
+pub const RATE_1_PARAMS: Lazy<Parameters<Fq>> = Lazy::new(params::rate_1);
+/// Parameters for the rate-2 instance of Poseidon.
+pub const RATE_2_PARAMS: Lazy<Parameters<Fq>> = Lazy::new(params::rate_2);
+/// Parameters for the rate-4 instance of Poseidon.
+pub const RATE_4_PARAMS: Lazy<Parameters<Fq>> = Lazy::new(params::rate_4);
+/// Parameters for the rate-5 instance of Poseidon.
+pub const RATE_5_PARAMS: Lazy<Parameters<Fq>> = Lazy::new(params::rate_5);
 
-        // Current API has a `new()` method as part of the `CryptographicSponge`
-        // trait, but this method doesn't allow setting the initial state
-        // manually.  Instead, the fields can be set manually.
-        // Slightly inconvenient that we have to initialize the mode.
-        let mut sponge = PoseidonSponge {
-            parameters: params::rate_2(),
-            state: vec![Fq::zero(); 3],
-            mode: DuplexSpongeMode::Absorbing {
-                next_absorb_index: 0,
-            },
-        };
-
-        sponge.absorb(&Fq::one());
-        sponge.absorb(&Fq::one());
-
-        let output = sponge.squeeze_native_field_elements(1);
-        dbg!(output);
-    }
-}
+pub use ark_ed_on_bls12_377::Fq;
+pub use ark_sponge::poseidon::{Parameters, State};
diff --git a/src/params.rs b/src/params.rs
index 23faac4..0a9e60c 100644
--- a/src/params.rs
+++ b/src/params.rs
@@ -6,13 +6,13 @@
 // Generated with `generate_mds.sage`. Do not edit manually.
 // Regenerate with: `sage vendor/generate_mds.sage > src/params.rs`
 
+use crate::Parameters;
 use ark_ff::PrimeField;
-use ark_sponge::poseidon::PoseidonParameters;
 
 /// Parameters for the rate-1 instance of Poseidon.
 ///
 /// Note: `F` must be the BLS12-377 scalar field.
-pub fn rate_1<F: PrimeField>() -> PoseidonParameters<F> {
+pub fn rate_1<F: PrimeField>() -> Parameters<F> {
     let mds = vec![
         vec![
             F::from_str(
@@ -498,7 +498,7 @@ pub fn rate_1<F: PrimeField>() -> PoseidonParameters<F> {
         ],
     ];
 
-    PoseidonParameters {
+    Parameters {
         full_rounds: 8,
         partial_rounds: 30,
         alpha: 17,
@@ -512,7 +512,7 @@ pub fn rate_1<F: PrimeField>() -> PoseidonParameters<F> {
 /// Parameters for the rate-2 instance of Poseidon.
 ///
 /// Note: `F` must be the BLS12-377 scalar field.
-pub fn rate_2<F: PrimeField>() -> PoseidonParameters<F> {
+pub fn rate_2<F: PrimeField>() -> Parameters<F> {
     let mds = vec![
         vec![
             F::from_str(
@@ -1232,7 +1232,7 @@ pub fn rate_2<F: PrimeField>() -> PoseidonParameters<F> {
         ],
     ];
 
-    PoseidonParameters {
+    Parameters {
         full_rounds: 8,
         partial_rounds: 31,
         alpha: 17,
@@ -1246,7 +1246,7 @@ pub fn rate_2<F: PrimeField>() -> PoseidonParameters<F> {
 /// Parameters for the rate-4 instance of Poseidon.
 ///
 /// Note: `F` must be the BLS12-377 scalar field.
-pub fn rate_4<F: PrimeField>() -> PoseidonParameters<F> {
+pub fn rate_4<F: PrimeField>() -> Parameters<F> {
     let mds = vec![
         vec![
             F::from_str(
@@ -2305,7 +2305,7 @@ pub fn rate_4<F: PrimeField>() -> PoseidonParameters<F> {
         ],
     ];
 
-    PoseidonParameters {
+    Parameters {
         full_rounds: 8,
         partial_rounds: 26,
         alpha: 17,
@@ -2319,7 +2319,7 @@ pub fn rate_4<F: PrimeField>() -> PoseidonParameters<F> {
 /// Parameters for the rate-5 instance of Poseidon.
 ///
 /// Note: `F` must be the BLS12-377 scalar field.
-pub fn rate_5<F: PrimeField>() -> PoseidonParameters<F> {
+pub fn rate_5<F: PrimeField>() -> Parameters<F> {
     let mds = vec![
         vec![
             F::from_str(
@@ -3509,7 +3509,7 @@ pub fn rate_5<F: PrimeField>() -> PoseidonParameters<F> {
         ],
     ];
 
-    PoseidonParameters {
+    Parameters {
         full_rounds: 8,
         partial_rounds: 23,
         alpha: 17,
diff --git a/src/sponge.rs b/src/sponge.rs
deleted file mode 100644
index ab7223b..0000000
--- a/src/sponge.rs
+++ /dev/null
@@ -1,59 +0,0 @@
-use ark_ed_on_bls12_377::Fq;
-use ark_ff::One;
-use ark_sponge::{poseidon::PoseidonSponge, CryptographicSponge, FieldBasedCryptographicSponge};
-
-use crate::params;
-
-// This struct will let us replace the implementation of the inner
-// PoseidonSponge if we later choose to do so.
-struct Sponge {
-    inner: PoseidonSponge<Fq>,
-    // TODOs: Domain separation, mode variable, padding fcn, cache?
-}
-
-impl Sponge {
-    fn new() -> Self {
-        let mut sponge = PoseidonSponge::<Fq>::new(&params::rate_2());
-        Sponge { inner: sponge }
-    }
-
-    /// Take a single field element into the sponge.
-    fn absorb(&mut self, element: Fq) {
-        self.inner.absorb(&element)
-    }
-
-    /// Produce a single field element.
-    fn squeeze(&mut self) -> Fq {
-        self.inner.squeeze_native_field_elements(1)[0]
-    }
-
-    /// Hash variable-length input into a hash.
-    pub fn hash(&mut self, message: Vec<Fq>, out_len: usize) -> Vec<Fq> {
-        for i in 0..message.len() {
-            self.absorb(message[i]);
-        }
-
-        // Domain separation
-        self.absorb(Fq::one());
-
-        let mut output = Vec::<Fq>::new();
-        for _i in 0..out_len {
-            output.push(self.squeeze());
-        }
-        output
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_variable_len_hash() {
-        let out_len = 1;
-        let mut sponge = Sponge::new();
-        let message = vec![Fq::one(), Fq::one()];
-        let result = sponge.hash(message, out_len);
-        assert_eq!(result.len(), out_len);
-    }
-}
diff --git a/vendor/generate_mds.sage b/vendor/generate_mds.sage
index f9b02bb..eb452e3 100644
--- a/vendor/generate_mds.sage
+++ b/vendor/generate_mds.sage
@@ -330,11 +330,11 @@ def generate_poseidon_param_code(
 /// Parameters for the rate-{rate} instance of Poseidon.
 ///
 /// Note: `F` must be the BLS12-377 scalar field.
-pub fn rate_{rate}<F: PrimeField>() -> PoseidonParameters<F> {{
+pub fn rate_{rate}<F: PrimeField>() -> Parameters<F> {{
 """
 
     closing = f"""
-    PoseidonParameters {{
+    Parameters {{
         full_rounds: {num_rounds - R_p},
         partial_rounds: {R_p},
         alpha: {alpha},
@@ -396,7 +396,7 @@ print("""//! Parameters for various Poseidon instances over the BLS12-377 scalar
 // Regenerate with: `sage vendor/generate_mds.sage > src/params.rs`
 
 use ark_ff::PrimeField;
-use ark_sponge::poseidon::PoseidonParameters;
+use crate::Parameters;
 
 """)