diff --git a/db/src/keys.rs b/db/src/keys.rs
index ced890746bed7f72d910bf66d2e3e0f26c3ea091..866dc6b0348018d28711578f840927a9baa30678 100644
--- a/db/src/keys.rs
+++ b/db/src/keys.rs
@@ -13,5 +13,6 @@
 // You should have received a copy of the GNU Affero General Public License
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
+pub mod cert_id;
 pub mod gva_utxo_id;
 pub mod wallet_hash_with_bn;
diff --git a/db/src/keys/cert_id.rs b/db/src/keys/cert_id.rs
new file mode 100644
index 0000000000000000000000000000000000000000..fa2b255621b4a57ca2e8842710b01c8d64dd376a
--- /dev/null
+++ b/db/src/keys/cert_id.rs
@@ -0,0 +1,66 @@
+//  Copyright (C) 2021 Pascal Engélibert
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub struct GvaCertIdKeyDbV1 {
+    pub issuer: U64BE,
+    pub target: U64BE,
+}
+
+impl Default for GvaCertIdKeyDbV1 {
+    fn default() -> Self {
+        Self {
+            issuer: U64BE(0),
+            target: U64BE(0),
+        }
+    }
+}
+
+impl std::fmt::Display for GvaCertIdKeyDbV1 {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}:{}", self.issuer.0, self.target.0,)
+    }
+}
+
+impl AsBytes for GvaCertIdKeyDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(&([self.issuer.0.to_be_bytes(), self.target.0.to_be_bytes()].concat()))
+    }
+}
+
+impl FromBytes for GvaCertIdKeyDbV1 {
+    type Err = CorruptedBytes;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        Ok(Self {
+            issuer: U64BE::from_bytes(&bytes[0..8])
+                .map_err(|_| CorruptedBytes("db corrupted".to_owned()))?,
+            target: U64BE::from_bytes(&bytes[8..16])
+                .map_err(|_| CorruptedBytes("db corrupted".to_owned()))?,
+        })
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableKey for GvaCertIdKeyDbV1 {
+    fn from_explorer_str(_: &str) -> std::result::Result<Self, FromExplorerKeyErr> {
+        unimplemented!()
+    }
+    fn to_explorer_string(&self) -> KvResult<String> {
+        Ok(self.to_string())
+    }
+}
diff --git a/db/src/lib.rs b/db/src/lib.rs
index 5c64cd70ec1bdcb6072fb31d9ce2a8292f827489..6df6423ea592eb516ab5b4f1cb299d783068eed4 100644
--- a/db/src/lib.rs
+++ b/db/src/lib.rs
@@ -25,17 +25,19 @@
 mod keys;
 mod values;
 
+pub use keys::cert_id::GvaCertIdKeyDbV1;
 pub use keys::gva_utxo_id::GvaUtxoIdDbV1;
 pub use keys::wallet_hash_with_bn::WalletHashWithBnV1Db;
 pub use values::gva_block_db::GvaBlockDbV1;
+pub use values::gva_cert_db::{GvaCertDbV1, GvaCertIdDbV1, GvaExpiredCertDbV1};
 pub use values::gva_idty_db::GvaIdtyDbV1;
 pub use values::gva_tx::GvaTxDbV1;
 pub use values::wallet_script_array::WalletScriptArrayV2;
-pub use values::HashDb;
+pub use values::{HashDb, PublicKeyDb};
 
 pub(crate) use bincode::Options as _;
 pub(crate) use duniter_core::common::prelude::*;
-pub(crate) use duniter_core::crypto::hashs::Hash;
+pub(crate) use duniter_core::crypto::{hashs::Hash, keys::ed25519::PublicKey};
 pub(crate) use duniter_core::dbs::kv_typed;
 pub(crate) use duniter_core::dbs::kv_typed::db_schema;
 pub(crate) use duniter_core::dbs::kv_typed::prelude::*;
@@ -57,7 +59,10 @@ db_schema!(
         ["blocks_with_ud", BlocksWithUd, U32BE, ()],
         ["blockchain_time", BlockchainTime, U32BE, u64],
         ["blocks_chunk_hash", BlocksChunkHash, U32BE, HashDb],
+        ["certifications", Certifications, GvaCertIdKeyDbV1, GvaCertDbV1],
+        ["certs_by_expire", CertsByExpire, U64BE, Vec<GvaCertIdDbV1>],
         ["current_blocks_chunk", CurrentBlocksChunk, U32BE, GvaBlockDbV1],
+        ["expired_certs", ExpiredCerts, U32BE, Vec<GvaExpiredCertDbV1>],
         ["gva_identities", GvaIdentities, PubKeyKeyV2, GvaIdtyDbV1],
         [
             "gva_utxos",
@@ -65,6 +70,7 @@ db_schema!(
             GvaUtxoIdDbV1,
             SourceAmountValV2
         ],
+        ["idties_by_id", IdtiesById, U64BE, PublicKeyDb],
         [
             "scripts_by_pubkey",
             ScriptsByPubkey,
diff --git a/db/src/values.rs b/db/src/values.rs
index 8dbd3baab691823e99536f44345773b2d364e47e..df7b16036c792e3aa766f915bfaf406b53c2365c 100644
--- a/db/src/values.rs
+++ b/db/src/values.rs
@@ -16,6 +16,7 @@
 use crate::*;
 
 pub mod gva_block_db;
+pub mod gva_cert_db;
 pub mod gva_idty_db;
 pub mod gva_tx;
 pub mod wallet_script_array;
@@ -56,3 +57,40 @@ impl ExplorableValue for HashDb {
         Ok(serde_json::Value::String(self.0.to_hex()))
     }
 }
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
+pub struct PublicKeyDb(pub PublicKey);
+
+impl AsBytes for PublicKeyDb {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(self.0.as_ref())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for PublicKeyDb {
+    type Err = bincode::Error;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let mut pubkey_bytes = [0; 32];
+        pubkey_bytes.copy_from_slice(bytes);
+        Ok(Self(PublicKey::from_32_bytes_array(pubkey_bytes)))
+    }
+}
+
+impl ToDumpString for PublicKeyDb {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for PublicKeyDb {
+    fn from_explorer_str(source: &str) -> Result<Self, FromExplorerValueErr> {
+        Ok(Self(
+            PublicKey::from_hex(source).map_err(|e| FromExplorerValueErr(e.into()))?,
+        ))
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        Ok(serde_json::Value::String(self.0.to_hex()))
+    }
+}
diff --git a/db/src/values/gva_cert_db.rs b/db/src/values/gva_cert_db.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5e10778e5e2d9090d67b063a9c227d130f5639b8
--- /dev/null
+++ b/db/src/values/gva_cert_db.rs
@@ -0,0 +1,174 @@
+//  Copyright (C) 2021 Pascal Engélibert
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+// TODO remove repr(packed)
+#[derive(
+    Copy,
+    Clone,
+    Debug,
+    Default,
+    PartialEq,
+    Serialize,
+    Deserialize,
+    zerocopy::AsBytes,
+    zerocopy::FromBytes,
+)]
+#[repr(packed)]
+pub struct GvaCertIdDbV1 {
+    pub issuer: usize,
+    pub target: usize,
+}
+
+impl GvaCertIdDbV1 {
+    pub fn to_key(self) -> GvaCertIdKeyDbV1 {
+        GvaCertIdKeyDbV1 {
+            issuer: U64BE(self.issuer as u64),
+            target: U64BE(self.target as u64),
+        }
+    }
+}
+
+impl AsBytes for GvaCertIdDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(&bincode_db()
+            .serialize(&self)
+            .unwrap_or_else(|_| unreachable!()))
+    }
+}
+
+impl kv_typed::prelude::FromBytes for GvaCertIdDbV1 {
+    type Err = bincode::Error;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        bincode_db().deserialize(bytes)
+    }
+}
+
+impl ToDumpString for GvaCertIdDbV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for GvaCertIdDbV1 {
+    fn from_explorer_str(source: &str) -> Result<Self, FromExplorerValueErr> {
+        serde_json::from_str(source).map_err(|e| FromExplorerValueErr(e.into()))
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        serde_json::to_value(&self).map_err(|e| KvError::DeserError(e.into()))
+    }
+}
+
+// TODO remove repr(packed)
+#[derive(
+    Copy,
+    Clone,
+    Debug,
+    Default,
+    PartialEq,
+    Serialize,
+    Deserialize,
+    zerocopy::AsBytes,
+    zerocopy::FromBytes,
+)]
+#[repr(packed)]
+pub struct GvaCertDbV1 {
+    pub written_on: BlockNumber,
+}
+
+impl AsBytes for GvaCertDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(&bincode_db()
+            .serialize(&self)
+            .unwrap_or_else(|_| unreachable!()))
+    }
+}
+
+impl kv_typed::prelude::FromBytes for GvaCertDbV1 {
+    type Err = bincode::Error;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        bincode_db().deserialize(bytes)
+    }
+}
+
+impl ToDumpString for GvaCertDbV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for GvaCertDbV1 {
+    fn from_explorer_str(source: &str) -> Result<Self, FromExplorerValueErr> {
+        serde_json::from_str(source).map_err(|e| FromExplorerValueErr(e.into()))
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        serde_json::to_value(&self).map_err(|e| KvError::DeserError(e.into()))
+    }
+}
+
+// TODO remove repr(packed)
+#[derive(
+    Copy,
+    Clone,
+    Debug,
+    Default,
+    PartialEq,
+    Serialize,
+    Deserialize,
+    zerocopy::AsBytes,
+    zerocopy::FromBytes,
+)]
+#[repr(packed)]
+pub struct GvaExpiredCertDbV1 {
+    pub id: GvaCertIdDbV1,
+    pub cert: GvaCertDbV1,
+}
+
+impl AsBytes for GvaExpiredCertDbV1 {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(&bincode_db()
+            .serialize(&self)
+            .unwrap_or_else(|_| unreachable!()))
+    }
+}
+
+impl kv_typed::prelude::FromBytes for GvaExpiredCertDbV1 {
+    type Err = bincode::Error;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        bincode_db().deserialize(bytes)
+    }
+}
+
+impl ToDumpString for GvaExpiredCertDbV1 {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableValue for GvaExpiredCertDbV1 {
+    fn from_explorer_str(source: &str) -> Result<Self, FromExplorerValueErr> {
+        serde_json::from_str(source).map_err(|e| FromExplorerValueErr(e.into()))
+    }
+    fn to_explorer_json(&self) -> KvResult<serde_json::Value> {
+        serde_json::to_value(&self).map_err(|e| KvError::DeserError(e.into()))
+    }
+}
diff --git a/db/src/values/gva_idty_db.rs b/db/src/values/gva_idty_db.rs
index 13332d92d16a119d13f7fdf51898aab13a25c2f9..add9b5e6f435768613ca79b025b66244a31a909a 100644
--- a/db/src/values/gva_idty_db.rs
+++ b/db/src/values/gva_idty_db.rs
@@ -14,6 +14,7 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 use crate::*;
+use std::collections::HashSet;
 
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 pub struct GvaIdtyDbV1 {
@@ -22,6 +23,7 @@ pub struct GvaIdtyDbV1 {
     pub leaves: BTreeSet<BlockNumber>,
     pub first_ud: Option<BlockNumber>,
     pub wot_id: WotId,
+    pub certifiers: HashSet<WotId>,
 }
 
 impl AsBytes for GvaIdtyDbV1 {
diff --git a/dbs-reader/src/uds_of_pubkey.rs b/dbs-reader/src/uds_of_pubkey.rs
index 6e240966a71b8842e8ec10eda5a4d0b9c39cc85d..a132deb78883de9b8897064b8135f8a37742fcbc 100644
--- a/dbs-reader/src/uds_of_pubkey.rs
+++ b/dbs-reader/src/uds_of_pubkey.rs
@@ -474,6 +474,7 @@ mod tests {
     use duniter_core::dbs::{databases::bc_v2::BcV2DbWritable, SourceAmountValV2, UdIdV2};
     use duniter_core::{dbs::smallvec::smallvec as svec, wot::WotId};
     use duniter_gva_db::GvaV1DbWritable;
+    use std::collections::HashSet;
 
     #[test]
     fn test_filter_blocks_numbers() -> KvResult<()> {
@@ -483,6 +484,7 @@ mod tests {
             leaves: [BlockNumber(32)].iter().copied().collect(),
             first_ud: Some(BlockNumber(29)),
             wot_id: WotId(0),
+            certifiers: HashSet::new(),
         };
         let blocks_with_ud = vec![
             BlockNumber(3),
@@ -533,6 +535,7 @@ mod tests {
             leaves: [BlockNumber(32)].iter().copied().collect(),
             first_ud: Some(BlockNumber(29)),
             wot_id: WotId(0),
+            certifiers: HashSet::new(),
         };
 
         let bc_db = duniter_core::dbs::databases::bc_v2::BcV2Db::<Mem>::open(MemConf::default())?;
diff --git a/indexer/src/certifications.rs b/indexer/src/certifications.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5a10c47a5c4846411c0332f624a0119e59fc6d44
--- /dev/null
+++ b/indexer/src/certifications.rs
@@ -0,0 +1,438 @@
+//  Copyright (C) 2021 Pascal Engélibert
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+pub(crate) fn update_certifications<B: Backend>(
+    block: &DubpBlockV10,
+    gva_db: &mut GvaV1DbTxRw<B::Col>,
+) -> KvResult<()> {
+    let mut expiring_certs = Vec::new();
+    gva_db
+        .certs_by_expire
+        .iter(U64BE(0)..U64BE(block.common_time()), |it| {
+            for row in it {
+                if let Ok((_, cert_ids)) = row {
+                    for cert_id in cert_ids {
+                        // TODO error handling
+                        if let Ok(Some(cert)) = gva_db.certifications.get(&cert_id.to_key()) {
+                            expiring_certs.push(GvaExpiredCertDbV1 { id: cert_id, cert });
+                        } else {
+                            // TODO what if certification doesn't exist?
+                        }
+                    }
+                }
+            }
+        });
+    for cert in expiring_certs.iter() {
+        gva_db.certifications.remove(cert.id.to_key());
+        if let Some(target_pubkey) = gva_db.idties_by_id.get(&U64BE(cert.id.target as u64))? {
+            if let Some(mut target_idty) =
+                gva_db.gva_identities.get(&PubKeyKeyV2(target_pubkey.0))?
+            {
+                target_idty.certifiers.remove(&WotId(cert.id.issuer));
+                gva_db
+                    .gva_identities
+                    .upsert(PubKeyKeyV2(target_pubkey.0), target_idty);
+            }
+        }
+    }
+    if !expiring_certs.is_empty() {
+        gva_db
+            .expired_certs
+            .upsert(U32BE(block.number().0), expiring_certs);
+    }
+
+    for cert in block.certifications() {
+        if let (Some(issuer_idty), Some(target_idty)) = (
+            gva_db.gva_identities.get(&PubKeyKeyV2(cert.issuer))?,
+            gva_db.gva_identities.get(&PubKeyKeyV2(cert.target))?,
+        ) {
+            let cert_id = GvaCertIdDbV1 {
+                issuer: issuer_idty.wot_id.0,
+                target: target_idty.wot_id.0,
+            };
+            if gva_db.certifications.get(&cert_id.to_key())?.is_none()
+            // TODO !contains_key
+            {
+                let mut target_idty = target_idty.clone();
+                target_idty.certifiers.insert(issuer_idty.wot_id);
+                gva_db
+                    .gva_identities
+                    .upsert(PubKeyKeyV2(cert.target), target_idty);
+            }
+            gva_db.certifications.upsert(
+                cert_id.to_key(),
+                GvaCertDbV1 {
+                    written_on: block.number(),
+                },
+            );
+
+            // TODO sig_validity
+            if let Some(created_time) = gva_db.blockchain_time.get(&U32BE(cert.block_number.0))? {
+                let mut certs_same_expire = gva_db
+                    .certs_by_expire
+                    .get(&U64BE(created_time + 42))?
+                    .unwrap_or_else(Vec::new);
+                certs_same_expire.push(cert_id);
+                gva_db
+                    .certs_by_expire
+                    .upsert(U64BE(created_time + 42), certs_same_expire);
+            } else {
+                // TODO what if the block don't exist?
+            }
+        } else {
+            // TODO what if idties don't exist?
+        }
+    }
+
+    Ok(())
+}
+
+pub(crate) fn revert_certifications<B: Backend>(
+    _block: &DubpBlockV10,
+    _gva_db: &mut GvaV1DbTxRw<B::Col>,
+) -> KvResult<()> {
+    // TODO
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::super::identities::update_identities;
+    use super::*;
+    use duniter_core::{
+        crypto::{
+            bases::b58::ToBase58,
+            keys::{ed25519::Ed25519KeyPair, KeyPair},
+        },
+        documents_parser::prelude::FromStringObject,
+    };
+    use maplit::hashset;
+
+    #[test]
+    fn test_update_certifications() -> KvResult<()> {
+        let u1_pubkey = Ed25519KeyPair::generate_random().unwrap().public_key();
+        let u2_pubkey = Ed25519KeyPair::generate_random().unwrap().public_key();
+        let u3_pubkey = Ed25519KeyPair::generate_random().unwrap().public_key();
+        let u4_pubkey = Ed25519KeyPair::generate_random().unwrap().public_key();
+        let u1_b58 = u1_pubkey.to_base58();
+        let u2_b58 = u2_pubkey.to_base58();
+        let u3_b58 = u3_pubkey.to_base58();
+        let u4_b58 = u4_pubkey.to_base58();
+
+        let gva_db = GvaV1Db::<Mem>::open(MemConf::default())?;
+
+        let b0 = DubpBlockV10::from_string_object(&DubpBlockV10Stringified {
+            number: 0,
+            median_time: 1000,
+            identities: vec![
+                format!("{}:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==:0-0000000000000000000000000000000000000000000000000000000000000000:u1", u1_b58),
+                format!("{}:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==:0-0000000000000000000000000000000000000000000000000000000000000000:u2", u2_b58),
+                format!("{}:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==:0-0000000000000000000000000000000000000000000000000000000000000000:u3", u3_b58),
+            ],
+            joiners: vec![
+                format!("{}:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==:0-0000000000000000000000000000000000000000000000000000000000000000:0-0000000000000000000000000000000000000000000000000000000000000000:u1", u1_b58),
+                format!("{}:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==:0-0000000000000000000000000000000000000000000000000000000000000000:0-0000000000000000000000000000000000000000000000000000000000000000:u2", u2_b58),
+                format!("{}:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==:0-0000000000000000000000000000000000000000000000000000000000000000:0-0000000000000000000000000000000000000000000000000000000000000000:u3", u3_b58),
+            ],
+            certifications: vec![
+                format!("{}:{}:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", u1_b58, u2_b58),
+                format!("{}:{}:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", u2_b58, u1_b58),
+                format!("{}:{}:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", u1_b58, u3_b58),
+            ],
+            inner_hash: Some("0000000000000000000000000000000000000000000000000000000000000000".into()),
+            signature: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==".into(),
+            hash: Some("0000000000000000000000000000000000000000000000000000000000000000".into()),
+            ..Default::default()
+        }).unwrap();
+
+        (&gva_db).write(|mut db| {
+            db.blockchain_time.upsert(U32BE(0), b0.common_time());
+            update_identities::<Mem>(&b0, &mut db.gva_identities, &mut db.idties_by_id)?;
+            update_certifications::<Mem>(&b0, &mut db)
+        })?;
+
+        assert_eq!(gva_db.certifications().count()?, 3);
+        assert_eq!(
+            gva_db.certifications().get(&GvaCertIdKeyDbV1 {
+                issuer: U64BE(0),
+                target: U64BE(1)
+            })?,
+            Some(GvaCertDbV1 {
+                written_on: BlockNumber(0),
+            })
+        );
+        assert_eq!(
+            gva_db.certifications().get(&GvaCertIdKeyDbV1 {
+                issuer: U64BE(1),
+                target: U64BE(0)
+            })?,
+            Some(GvaCertDbV1 {
+                written_on: BlockNumber(0),
+            })
+        );
+        assert_eq!(
+            gva_db.certifications().get(&GvaCertIdKeyDbV1 {
+                issuer: U64BE(0),
+                target: U64BE(2)
+            })?,
+            Some(GvaCertDbV1 {
+                written_on: BlockNumber(0),
+            })
+        );
+        assert_eq!(gva_db.certs_by_expire().count()?, 1);
+        assert_eq!(
+            gva_db
+                .certs_by_expire()
+                .get(&U64BE(b0.common_time() + 42))?,
+            Some(vec![
+                GvaCertIdDbV1 {
+                    issuer: 0,
+                    target: 1
+                },
+                GvaCertIdDbV1 {
+                    issuer: 1,
+                    target: 0
+                },
+                GvaCertIdDbV1 {
+                    issuer: 0,
+                    target: 2
+                }
+            ])
+        );
+        assert_eq!(
+            gva_db
+                .gva_identities()
+                .get(&PubKeyKeyV2(u1_pubkey))?
+                .unwrap()
+                .certifiers,
+            hashset! {WotId(1)}
+        );
+        assert_eq!(
+            gva_db
+                .gva_identities()
+                .get(&PubKeyKeyV2(u2_pubkey))?
+                .unwrap()
+                .certifiers,
+            hashset! {WotId(0)}
+        );
+        assert_eq!(
+            gva_db
+                .gva_identities()
+                .get(&PubKeyKeyV2(u3_pubkey))?
+                .unwrap()
+                .certifiers,
+            hashset! {WotId(0)}
+        );
+        assert_eq!(gva_db.expired_certs().count()?, 0);
+
+        let b1 = DubpBlockV10::from_string_object(&DubpBlockV10Stringified {
+            number: 1,
+            median_time: 1030,
+            identities: vec![
+                format!("{}:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==:0-0000000000000000000000000000000000000000000000000000000000000000:u4", u4_b58),
+            ],
+            joiners: vec![
+                format!("{}:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==:0-0000000000000000000000000000000000000000000000000000000000000000:0-0000000000000000000000000000000000000000000000000000000000000000:u4", u4_b58),
+            ],
+            certifications: vec![
+                format!("{}:{}:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", u1_b58, u4_b58),
+                format!("{}:{}:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", u2_b58, u4_b58),
+                format!("{}:{}:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", u3_b58, u2_b58),
+            ],
+            inner_hash: Some("0000000000000000000000000000000000000000000000000000000000000001".into()),
+            signature: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==".into(),
+            hash: Some("0000000000000000000000000000000000000000000000000000000000000001".into()),
+            ..Default::default()
+        }).unwrap();
+
+        (&gva_db).write(|mut db| {
+            db.blockchain_time.upsert(U32BE(1), b1.common_time());
+            update_identities::<Mem>(&b1, &mut db.gva_identities, &mut db.idties_by_id)?;
+            update_certifications::<Mem>(&b1, &mut db)
+        })?;
+
+        assert_eq!(gva_db.certifications().count()?, 6);
+        assert_eq!(
+            gva_db.certifications().get(&GvaCertIdKeyDbV1 {
+                issuer: U64BE(0),
+                target: U64BE(3)
+            })?,
+            Some(GvaCertDbV1 {
+                written_on: BlockNumber(1),
+            })
+        );
+        assert_eq!(
+            gva_db.certifications().get(&GvaCertIdKeyDbV1 {
+                issuer: U64BE(1),
+                target: U64BE(3)
+            })?,
+            Some(GvaCertDbV1 {
+                written_on: BlockNumber(1),
+            })
+        );
+        assert_eq!(
+            gva_db.certifications().get(&GvaCertIdKeyDbV1 {
+                issuer: U64BE(2),
+                target: U64BE(1)
+            })?,
+            Some(GvaCertDbV1 {
+                written_on: BlockNumber(1),
+            })
+        );
+        assert_eq!(gva_db.certs_by_expire().count()?, 1);
+        assert_eq!(
+            gva_db
+                .certs_by_expire()
+                .get(&U64BE(b0.common_time() + 42))?,
+            Some(vec![
+                GvaCertIdDbV1 {
+                    issuer: 0,
+                    target: 1
+                },
+                GvaCertIdDbV1 {
+                    issuer: 1,
+                    target: 0
+                },
+                GvaCertIdDbV1 {
+                    issuer: 0,
+                    target: 2
+                },
+                GvaCertIdDbV1 {
+                    issuer: 0,
+                    target: 3
+                },
+                GvaCertIdDbV1 {
+                    issuer: 1,
+                    target: 3
+                },
+                GvaCertIdDbV1 {
+                    issuer: 2,
+                    target: 1
+                }
+            ])
+        );
+        assert_eq!(
+            gva_db
+                .gva_identities()
+                .get(&PubKeyKeyV2(u1_pubkey))?
+                .unwrap()
+                .certifiers,
+            hashset! {WotId(1)}
+        );
+        assert_eq!(
+            gva_db
+                .gva_identities()
+                .get(&PubKeyKeyV2(u2_pubkey))?
+                .unwrap()
+                .certifiers,
+            hashset! {WotId(0), WotId(2)}
+        );
+        assert_eq!(
+            gva_db
+                .gva_identities()
+                .get(&PubKeyKeyV2(u3_pubkey))?
+                .unwrap()
+                .certifiers,
+            hashset! {WotId(0)}
+        );
+        assert_eq!(
+            gva_db
+                .gva_identities()
+                .get(&PubKeyKeyV2(u4_pubkey))?
+                .unwrap()
+                .certifiers,
+            hashset! {WotId(0), WotId(1)}
+        );
+        assert_eq!(gva_db.expired_certs().count()?, 0);
+
+        let b2 = DubpBlockV10::from_string_object(&DubpBlockV10Stringified {
+            number: 2,
+            median_time: 1060,
+            joiners: vec![],
+            certifications: vec![
+                format!("{}:{}:1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", u1_b58, u2_b58),
+            ],
+            inner_hash: Some("0000000000000000000000000000000000000000000000000000000000000002".into()),
+            signature: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==".into(),
+            hash: Some("0000000000000000000000000000000000000000000000000000000000000002".into()),
+            ..Default::default()
+        }).unwrap();
+
+        (&gva_db).write(|mut db| {
+            db.blockchain_time.upsert(U32BE(2), b2.common_time());
+            update_identities::<Mem>(&b2, &mut db.gva_identities, &mut db.idties_by_id)?;
+            update_certifications::<Mem>(&b2, &mut db)
+        })?;
+
+        assert_eq!(gva_db.certifications().count()?, 1);
+        assert_eq!(
+            gva_db.certifications().get(&GvaCertIdKeyDbV1 {
+                issuer: U64BE(0),
+                target: U64BE(1)
+            })?,
+            Some(GvaCertDbV1 {
+                written_on: BlockNumber(2),
+            })
+        );
+        assert_eq!(gva_db.certs_by_expire().count()?, 1);
+        assert_eq!(
+            gva_db
+                .certs_by_expire()
+                .get(&U64BE(b1.common_time() + 42))?,
+            Some(vec![GvaCertIdDbV1 {
+                issuer: 0,
+                target: 1
+            },])
+        );
+        assert_eq!(
+            gva_db
+                .gva_identities()
+                .get(&PubKeyKeyV2(u1_pubkey))?
+                .unwrap()
+                .certifiers,
+            hashset! {}
+        );
+        assert_eq!(
+            gva_db
+                .gva_identities()
+                .get(&PubKeyKeyV2(u2_pubkey))?
+                .unwrap()
+                .certifiers,
+            hashset! {WotId(0)}
+        );
+        assert_eq!(
+            gva_db
+                .gva_identities()
+                .get(&PubKeyKeyV2(u3_pubkey))?
+                .unwrap()
+                .certifiers,
+            hashset! {}
+        );
+        assert_eq!(
+            gva_db
+                .gva_identities()
+                .get(&PubKeyKeyV2(u4_pubkey))?
+                .unwrap()
+                .certifiers,
+            hashset! {}
+        );
+        assert_eq!(gva_db.expired_certs().count()?, 5);
+
+        Ok(())
+    }
+}
diff --git a/indexer/src/identities.rs b/indexer/src/identities.rs
index 2360c7132ea9978c66b60d0ce669f60f7e232e23..787ff3a07185545103497f17ef2e44bdd1086c6b 100644
--- a/indexer/src/identities.rs
+++ b/indexer/src/identities.rs
@@ -18,6 +18,7 @@ use crate::*;
 pub(crate) fn update_identities<B: Backend>(
     block: &DubpBlockV10,
     identities: &mut TxColRw<B::Col, GvaIdentitiesEvent>,
+    idties_by_id: &mut TxColRw<B::Col, IdtiesByIdEvent>,
 ) -> KvResult<()> {
     let mut identities_pubkeys = HashSet::new();
     for idty in block.identities() {
@@ -33,8 +34,10 @@ pub(crate) fn update_identities<B: Backend>(
                 leaves: BTreeSet::new(),
                 first_ud: None,
                 wot_id,
+                certifiers: HashSet::new(),
             },
         );
+        idties_by_id.upsert(U64BE(wot_id.0 as u64), PublicKeyDb(pubkey))
     }
     for mb in block.joiners() {
         let pubkey = mb.issuers()[0];
@@ -70,6 +73,7 @@ pub(crate) fn update_identities<B: Backend>(
 pub(crate) fn revert_identities<B: Backend>(
     block: &DubpBlockV10,
     identities: &mut TxColRw<B::Col, GvaIdentitiesEvent>,
+    idties_by_id: &mut TxColRw<B::Col, IdtiesByIdEvent>,
 ) -> KvResult<()> {
     for mb in block.joiners() {
         let pubkey = mb.issuers()[0];
@@ -83,6 +87,9 @@ pub(crate) fn revert_identities<B: Backend>(
     }
     for idty in block.identities() {
         let pubkey = idty.issuers()[0];
+        if let Some(idty) = identities.get(&PubKeyKeyV2(pubkey))? {
+            idties_by_id.remove(U64BE(idty.wot_id.0 as u64));
+        }
         identities.remove(PubKeyKeyV2(pubkey));
         crate::revert_wot_id();
     }
diff --git a/indexer/src/lib.rs b/indexer/src/lib.rs
index 87cb8c20f90a1188ce79c7c5fc0e0d9404fb0194..f4adb59b962721eb8ea0396d506a030e96827001 100644
--- a/indexer/src/lib.rs
+++ b/indexer/src/lib.rs
@@ -23,6 +23,7 @@
 )]
 
 mod blocks_chunks;
+mod certifications;
 mod identities;
 mod tx;
 mod utxos;
@@ -81,9 +82,10 @@ fn get_next_wot_id() -> WotId {
             next_wot_id
         } else {
             NEXT_WOT_ID = Some(if let Some(main_wot) = MAIN_WOT.get() {
-                main_wot.read().size()
+                main_wot.read().size() + 1
+                // TODO ne faudrait-il pas dans ce cas renvoyer `main_wot.read().size()` ?
             } else {
-                0
+                1
             });
             0
         }
@@ -125,7 +127,8 @@ pub fn apply_block<B: Backend>(
             .upsert(U64BE(block.common_time()), block.number().0);
         db.blockchain_time
             .upsert(U32BE(block.number().0), block.common_time());
-        identities::update_identities::<B>(&block, &mut db.gva_identities)?;
+        identities::update_identities::<B>(&block, &mut db.gva_identities, &mut db.idties_by_id)?;
+        certifications::update_certifications::<B>(&block, &mut db)?;
         if let Some(divident_amount) = block.dividend() {
             db.blocks_with_ud.upsert(U32BE(blockstamp.number.0), ());
             apply_ud::<B>(
@@ -152,7 +155,8 @@ pub fn revert_block<B: Backend>(
     gva_db.write(|mut db| {
         db.blocks_by_common_time.remove(U64BE(block.common_time()));
         db.blockchain_time.remove(U32BE(block.number().0));
-        identities::revert_identities::<B>(&block, &mut db.gva_identities)?;
+        certifications::revert_certifications::<B>(&block, &mut db)?;
+        identities::revert_identities::<B>(&block, &mut db.gva_identities, &mut db.idties_by_id)?;
         if let Some(divident_amount) = block.dividend() {
             db.blocks_with_ud.remove(U32BE(block.number().0));
             revert_ud::<B>(