diff --git a/Cargo.toml b/Cargo.toml
index 3fed51b96fa4824ed8b1944178d1d97733e9305f..fe2526e0d30554ee108fcee703063597b739bdeb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -62,3 +62,7 @@ required-features = ["aplonk"]
 [[example]]
 name = "fri"
 required-features = ["fri"]
+
+[[example]]
+name = "fec"
+required-features = ["fri"]
\ No newline at end of file
diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml
index 5b980d23761cd31e92c272912c39bbc18306a4bb..2b866b2d25cabea57095434645e662c5379e9cdc 100644
--- a/benchmarks/Cargo.toml
+++ b/benchmarks/Cargo.toml
@@ -20,6 +20,7 @@ ark-secp256r1 = "0.4.0"
 ark-std = "0.4.0"
 ark-vesta = "0.4.0"
 clap = { version = "4.5.4", features = ["derive"] }
-komodo = { path = ".." }
+komodo = { path = "..", features = ["fri"] }
 plnk = { git = "https://gitlab.isae-supaero.fr/a.stevan/plnk", tag = "0.7.0", version = "0.7.0" }
 rand = "0.8.5"
+dragoonfri = { version = "0.1.0"}
diff --git a/benchmarks/src/bin/fec.rs b/benchmarks/src/bin/fec.rs
index 173438d28c6138cd603b059aaa6bb630795e229e..d73830605c614bb67eb5f331618bc5113adf2c1d 100644
--- a/benchmarks/src/bin/fec.rs
+++ b/benchmarks/src/bin/fec.rs
@@ -1,8 +1,10 @@
 // see `examples/benches/README.md`
 use ark_ff::PrimeField;
-
+use ark_poly::univariate::DensePolynomial;
+use benchmarks::fields::Fq128;
 use clap::{arg, command, Parser, ValueEnum};
-use komodo::{algebra::linalg::Matrix, fec};
+use dragoonfri::algorithms::Sha3_512;
+use komodo::{algebra::linalg::Matrix, fec, fri};
 use plnk::Bencher;
 use rand::{rngs::ThreadRng, thread_rng, Rng, RngCore};
 
@@ -10,6 +12,14 @@ fn random_bytes(n: usize, rng: &mut ThreadRng) -> Vec<u8> {
     (0..n).map(|_| rng.gen::<u8>()).collect()
 }
 
+fn random_loss<T>(shards: &mut Vec<T>, k: usize, rng: &mut impl Rng) {
+    // Randomly drop some shards until k are left
+    while shards.len() > k {
+        let i = rng.gen_range(0..shards.len());
+        shards.remove(i);
+    }
+}
+
 fn build_encoding_mat<F: PrimeField>(
     k: usize,
     n: usize,
@@ -24,48 +34,93 @@ fn build_encoding_mat<F: PrimeField>(
                 .collect();
             Matrix::vandermonde_unchecked(&points, k)
         }
+        _ => panic!("FFT encoding is not supported for matrix encoding"),
     }
 }
 
 fn template<F: PrimeField>(b: &Bencher, nb_bytes: usize, k: usize, n: usize, encoding: &Encoding) {
     let mut rng = thread_rng();
 
-    let encoding_mat = build_encoding_mat(k, n, encoding, &mut rng);
-
-    plnk::bench(
-        b,
-        &format!(
-            r#"{{"bytes": {}, "step": "encode", "k": {}, "n": {}}}"#,
-            nb_bytes, k, n
-        ),
-        || {
+    match encoding {
+        Encoding::Fft => {
+            assert_eq!(n.count_ones(), 1, "n must be a power of 2");
+            assert_eq!(k.count_ones(), 1, "k must be a power of 2");
             let bytes = random_bytes(nb_bytes, &mut rng);
+            let mut shards: Vec<fec::Shard<F>> = vec![];
+            plnk::bench(
+                b,
+                &format!(
+                    r#"{{"bytes": {}, "step": "encode", "method": "fft", "k": {}, "n": {}}}"#,
+                    nb_bytes, k, n
+                ),
+                || {
+                    plnk::timeit(|| {
+                        let evaluations = fri::evaluate::<F>(&bytes, k, n);
+                        shards = fri::encode::<F>(&bytes, evaluations, k)
+                    })
+                },
+            );
+
+            let evaluations = fri::evaluate::<F>(&bytes, k, n);
+            let mut blocks =
+                fri::prove::<2, F, Sha3_512, DensePolynomial<F>>(evaluations, shards, 2, 2, 1)
+                    .unwrap();
+
+            random_loss(&mut blocks, k, &mut rng);
+
+            plnk::bench(
+                b,
+                &format!(
+                    r#"{{"bytes": {}, "step": "decode", "method":"fft" "k": {}, "n": {}}}"#,
+                    nb_bytes, k, n
+                ),
+                || {
+                    plnk::timeit(|| {
+                        fri::decode::<F, Sha3_512>(blocks.clone(), n);
+                    })
+                },
+            );
+        }
+        _ => {
+            let encoding_mat = build_encoding_mat(k, n, encoding, &mut rng);
+
+            plnk::bench(
+                b,
+                &format!(
+                    r#"{{"bytes": {}, "step": "encode", "method": "matrix", "k": {}, "n": {}}}"#,
+                    nb_bytes, k, n
+                ),
+                || {
+                    let bytes = random_bytes(nb_bytes, &mut rng);
 
-            plnk::timeit(|| fec::encode::<F>(&bytes, &encoding_mat).unwrap())
-        },
-    );
+                    plnk::timeit(|| fec::encode::<F>(&bytes, &encoding_mat).unwrap())
+                },
+            );
 
-    let encoding_mat = build_encoding_mat(k, k, encoding, &mut rng);
+            let encoding_mat = build_encoding_mat(k, k, encoding, &mut rng);
 
-    plnk::bench(
-        b,
-        &format!(
-            r#"{{"bytes": {}, "step": "decode", "k": {}, "n": {}}}"#,
-            nb_bytes, k, n
-        ),
-        || {
-            let bytes = random_bytes(nb_bytes, &mut rng);
-            let shards = fec::encode::<F>(&bytes, &encoding_mat).unwrap();
+            plnk::bench(
+                b,
+                &format!(
+                    r#"{{"bytes": {}, "step": "decode", "method":"matrix", "k": {}, "n": {}}}"#,
+                    nb_bytes, k, n
+                ),
+                || {
+                    let bytes = random_bytes(nb_bytes, &mut rng);
+                    let shards = fec::encode::<F>(&bytes, &encoding_mat).unwrap();
 
-            plnk::timeit(|| fec::decode::<F>(shards.clone()).unwrap())
-        },
-    );
+                    plnk::timeit(|| fec::decode::<F>(shards.clone()).unwrap())
+                },
+            );
+        }
+    }
 }
 
 #[derive(ValueEnum, Clone)]
 enum Encoding {
     Vandermonde,
     Random,
+    Fft,
 }
 
 #[derive(ValueEnum, Clone, Hash, PartialEq, Eq)]
@@ -73,6 +128,7 @@ enum Curve {
     BLS12381,
     BN254,
     Pallas,
+    FP128,
 }
 
 #[derive(Parser)]
@@ -124,6 +180,9 @@ fn main() {
                     cli.n,
                     &cli.encoding,
                 ),
+                Curve::FP128 => {
+                    template::<Fq128>(&b.with_name("FP128"), n, cli.k, cli.n, &cli.encoding)
+                }
             }
         }
     }
diff --git a/examples/fec.rs b/examples/fec.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ab49d8a1e263a4bfec1a550184fd80e4bfd8881c
--- /dev/null
+++ b/examples/fec.rs
@@ -0,0 +1,106 @@
+use ark_poly::univariate::DensePolynomial;
+use clap::{arg, Parser, ValueEnum};
+use dragoonfri::algorithms::Sha3_512;
+use dragoonfri_test_utils::Fq;
+use komodo::algebra::linalg;
+use komodo::{fec, fri};
+use rand::{Rng, RngCore, SeedableRng};
+
+use ark_ff::PrimeField;
+use komodo::fec::Shard;
+use std::time::Instant;
+
+#[path = "utils/time.rs"]
+mod time;
+
+fn random_loss<T>(shards: &mut Vec<T>, k: usize, rng: &mut impl Rng) {
+    // Randomly drop some shards until k are left
+    while shards.len() > k {
+        let i = rng.gen_range(0..shards.len());
+        shards.remove(i);
+    }
+}
+
+#[derive(ValueEnum, Debug, Clone)]
+enum Coding {
+    Matrix,
+    Fft,
+}
+
+#[derive(ValueEnum, Debug, Clone)]
+enum FiniteField {
+    FP128,
+}
+
+#[derive(Parser, Debug)]
+#[command(version, about, long_about = None)]
+struct Args {
+    #[arg(short, long)]
+    data_size: usize,
+
+    #[arg(long, default_value = "1234")]
+    seed: u64,
+
+    #[arg(short)]
+    k: usize,
+
+    #[arg(short)]
+    n: usize,
+
+    #[arg(long, default_value = "fp128")]
+    finite_field: FiniteField,
+
+    #[arg(long, default_value = "matrix")]
+    coding: Coding,
+}
+
+fn encode_fft<F: PrimeField>(bytes: &[u8], k: usize, n: usize) -> Vec<Shard<F>> {
+    let evaluations = fri::evaluate::<F>(bytes, k, n);
+    fri::encode::<F>(bytes, evaluations, k)
+}
+
+fn run<F: PrimeField>(bytes: &[u8], k: usize, n: usize, seed: u64, coding: Coding) {
+    let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
+
+    match coding {
+        Coding::Matrix => {
+            let matrix = linalg::Matrix::random(k, n, &mut rng);
+            let mut shards = timeit_and_print!("encoding", fec::encode, bytes, &matrix).unwrap();
+            random_loss(&mut shards, k, &mut rng);
+            let recovered = timeit_and_print!("decoding", fec::decode::<F>, shards).unwrap();
+            assert_eq!(bytes, recovered);
+        }
+        Coding::Fft => {
+            assert_eq!(n.count_ones(), 1, "n must be a power of 2");
+            assert_eq!(k.count_ones(), 1, "k must be a power of 2");
+            let shards = timeit_and_print!("encoding", encode_fft::<F>, bytes, k, n);
+
+            let evaluations = fri::evaluate::<F>(bytes, k, n);
+            let mut blocks =
+                fri::prove::<2, F, Sha3_512, DensePolynomial<F>>(evaluations, shards, 2, 2, 1)
+                    .unwrap();
+
+            random_loss(&mut blocks, k, &mut rng);
+
+            let recovered = timeit_and_print!("decoding", fri::decode::<F, Sha3_512>, blocks, n);
+            assert_eq!(
+                bytes, recovered,
+                "decoded data does not match original data"
+            );
+        }
+    }
+}
+
+fn main() {
+    let args = Args::parse();
+
+    let mut rng = rand::rngs::StdRng::seed_from_u64(args.seed);
+
+    let mut bytes = vec![0u8; args.data_size];
+    rng.fill_bytes(&mut bytes);
+
+    let (k, n) = (args.k, args.n);
+    match args.finite_field {
+        FiniteField::FP128 => run::<Fq>(&bytes, k, n, args.seed, args.coding),
+    }
+}
diff --git a/examples/utils/time.rs b/examples/utils/time.rs
new file mode 100644
index 0000000000000000000000000000000000000000..db9e627675e59bf8b36d9f0aaa49b2d87e8ea481
--- /dev/null
+++ b/examples/utils/time.rs
@@ -0,0 +1,54 @@
+/// measure the time it takes to apply a function on a set of arguments and returns the result of
+/// the call
+///
+/// ```rust
+/// fn add(a: i32, b: i32) { a + b }
+/// let (res, time) = timeit!(add, 1, 2);
+/// ```
+/// will be the same as
+/// ```rust
+/// fn add(a: i32, b: i32) { a + b }
+/// let (res, time) = {
+///     let start = Instant::now();
+///     let res = add(1, 2);
+///     let time = start.elapsed();
+///     (res, time)
+/// };
+/// ```
+#[macro_export]
+macro_rules! timeit {
+    ($func:expr, $( $args:expr ),*) => {{
+        let start = Instant::now();
+        let res = $func( $( $args ),* );
+        let time = start.elapsed();
+        (res, time)
+    }};
+}
+
+/// same as [`timeit`] but prints a name and the time at the end directly
+///
+/// ```rust
+/// fn add(a: i32, b: i32) { a + b }
+/// let res = timeit_and_print!("addition", add, 1, 2);
+/// ```
+/// will be the same as
+/// ```rust
+/// fn add(a: i32, b: i32) { a + b }
+/// let res = {
+///     print!("addition: ");
+///     let start = Instant::now();
+///     let res = add(1, 2);
+///     let time = start.elapsed();
+///     println!("{}", time.as_nanos());
+///     res
+/// };
+/// ```
+#[macro_export]
+macro_rules! timeit_and_print {
+    ($name: expr, $func:expr, $( $args:expr ),*) => {{
+        print!("{}: ", $name);
+        let (res, time) = timeit!($func, $($args),*);
+        println!("{}", time.as_nanos());
+        res
+    }};
+}