Comunicaciones Certificadas con Blockchain

Comunicaciones Certificadas con Blockchain

Introducción

En el post anterior anterior presentamos un framework, Substrate, para desarrollar redes propias Blockchain, interoperables, de manera rápida y sencilla. En este artículo extenderemos el proyecto, incorporando diferentes nodos a la red. Una vez tengamos el mecanismo para desplegar la red, desarrollaremos la funcionalidad específica para certificar cualquier tipo de comunicación.

Uno de los casos de uso más utilizados es el de notarizar las comunicaciones o utilizar la red Blockchain como autoridad de certificación distribuida. Es lo que se denomina Proof of Existence (PoE) y como su nombre indica, se trata de registrar y probar que existe (o existió) una determinada información o entidad. La forma en que funciona también es bastante simple y brinda más información sobre la definición de PoE: básicamente, para mantener la Prueba de existencia de una entidad digital, se asocia una marca de tiempo y una firma para “probar” que un registro en particular fue creado en la fecha y hora mencionadas. Como veremos, esto abre la puerta a certificar cualquier acto comunicativo que realicemos.

En este artículo realizaremos:

  • Creación red blockchain privada
  • Desarrollo funcionalidad PoE en red creada (usando Pallets de Substrate)
  • Creación de cliente de prueba
  • Discusión de casos de uso.

Creación de red privada con Substrate

En este apartado crearemos una red privada simplificada, con dos nodos, utilizando la plantilla de proyecto que substrate nos facilita. Estos dos nodos serán autoridades dentro de la red, responsables de autorizar a otros nodos a incorporarse a la red. Los pasos a realizar son:

  • Disponer de la plantilla de proyecto del nodo. Esto ya lo tenemos si partimos del trabajo del artículo anterior
  • Creación de claves de los nodos particpantes
  • Generación de la Chain Spec de la red
  • Inicialización de los nodos de la red

Los nodeos que crearemos tendrán rol Validator, esto quiere decir que serán los nodos que mantendrán el sistema de consenso. Significa que participarán en la producción de bloques y en la finalización, más que en la sincronización de la red.

Creación de las claves de los nodos participantes

Cada miembro que quiera participar en la blockchain debe tener sus propias claves. En la esta página explican como generarlas. Para diferenciar el proyecto de los usuarios de demo Alice y Bob, hemos decidido crear dos usuarios diferentes, para mostrar como se hace. El mecanismo que hemos utilizado es el de usar la herramienta subkey

¿Qué es Subkey ?

Subkey es una utilidad criptográfica incluida en Substrate. Su característica principal es generar y inspeccionar claves, y soporta los esquemas sr25519, ed25519 y secp256k1. Todas las claves en las redes basadas en Substrate usan la codificación SS58 como mecanismo principal para gestionar claves.

Instalación

En nuestro caso seguimos la pauta de instalación

# Use the `--fast` flag to get the dependencies without needing to install the Substrate and Subkey binary
$ curl https://getsubstrate.io -sSf | bash -s -- --fast 
# Install only `subkey`, at a specific version
$ cargo install --force subkey --git https://github.com/paritytech/substrate --version 2.0.1 --locked

$ curl https://getsubstrate.io -sSf | bash -s -- --fast 
Ubuntu/Debian Linux detected.
Des:1 http://deb.debian.org/debian buster InRelease [122 kB]
Des:2 http://security.debian.org/debian-security buster/updates InRelease [65,4 kB]             
...
...

$~/devel/chain# cargo install --force subkey --git https://github.com/paritytech/substrate --version 2.0.1 --locked

    Updating git repository `https://github.com/paritytech/substrate`
  Installing subkey v2.0.1 (https://github.com/paritytech/substrate#4d28ebeb)
    Updating crates.io index
warning: package `crossbeam-epoch v0.9.3` in Cargo.lock is yanked in registry `crates.io`, consider running without --locked
warning: package `tokio v1.6.0` in Cargo.lock is yanked in registry `crates.io`, consider running without --locked
  Downloaded memchr v2.3.4
  Downloaded ahash v0.7.4
  Downloaded version_check v0.9.2
  Downloaded trie-db v0.22.6
  ...
  ...
   Compiling sc-rpc v4.0.0-dev (/root/.cargo/git/checkouts/substrate-7e08433d4c370a21/4d28ebe/client/rpc)
   Compiling sc-service v0.10.0-dev (/root/.cargo/git/checkouts/substrate-7e08433d4c370a21/4d28ebe/client/service)
   Compiling sc-cli v0.10.0-dev (/root/.cargo/git/checkouts/substrate-7e08433d4c370a21/4d28ebe/client/cli)
   Compiling subkey v2.0.1 (/root/.cargo/git/checkouts/substrate-7e08433d4c370a21/4d28ebe/bin/utils/subkey)
    Finished release [optimized] target(s) in 23m 00s
  Installing /developer/.cargo/bin/subkey
   Installed package `subkey v2.0.1 (https://github.com/paritytech/substrate#4d28ebeb)` (executable `subkey`)

# verificamos que el comando se ha instalado correctamente

 ~/devel/chain$ subkey -h
subkey 2.0.1
Parity Team <admin@parity.io>
Utility for generating and restoring with Substrate keys

USAGE:
    subkey <SUBCOMMAND>

Generación de claves

Con el ejecutable instalado generamos dos pares de claves por nodo:

  • Clave con esquema sr25519, para la producción de bloques (Aura)
  • Clave con esquema ed25519, para la finalización de bloques (Grandpa)

Mostraremos el proceso para un nodo. El segundo es repetir este paso de nuevo.

#Clave con esquema sr25519
~/devel/chain$ subkey generate --scheme sr25519
Secret phrase `modify document mixed shoot always undo absurd skill twice deer shove correct` is account:
  Secret seed:       0xd3ec52f9344209392a0f87d6662031d7d77acf85022f6630484bfba4fb3ada3b
  Public key (hex):  0x6299928485d9109556e9915283a1c9e733af84bc7b6c0af2f961da7f135b5d15
  Public key (SS58): 5EHzAqPex3FrJaeqAsx3PUECsBwxfVuh6Lc38NjDAXi73JbH
  Account ID:        0x6299928485d9109556e9915283a1c9e733af84bc7b6c0af2f961da7f135b5d15
  SS58 Address:      5EHzAqPex3FrJaeqAsx3PUECsBwxfVuh6Lc38NjDAXi73JbH


The output gives us the following information about our key:

    Secret phrase (aka "mnemonic phrase") - A series of English words that encodes the seed in a more human-friendly way. Mnemonic phrases were first introduced in Bitcoin (see BIP39) and make it much easier to write down your key by hand.
    Secret Seed (aka "Private Key" or "Raw Seed") - The minimum necessary information to restore the key pair. All other information is calculated from the seed.
    Public Key (hex) - The public half of the cryptographic key pair in hexadecimal.
    Public Key (SS58) - The public half of the cryptographic key pair in SS58 encoding.
    Account ID - Alias for the Public Key in hexadecimal.
    SS58 Address (aka "Public Address") - An SS58-encoded address based on the public key.

#Clave con esquema ed25519
subkey inspect --scheme ed25519 "modify document mixed shoot always undo absurd skill twice deer shove correct"
Secret phrase `modify document mixed shoot always undo absurd skill twice deer shove correct` is account:
  Secret seed:       0xd3ec52f9344209392a0f87d6662031d7d77acf85022f6630484bfba4fb3ada3b
  Public key (hex):  0x2d2e29e8c3f82cdbec3490fc40dad41fd7e8f21b8a674b404cc3f3f3113ddd24
  Public key (SS58): 5D5wj2hBMhmiqFv2yYpbx4b6zighJRSsgbH9RFsX4CqTw9Ew
  Account ID:        0x2d2e29e8c3f82cdbec3490fc40dad41fd7e8f21b8a674b404cc3f3f3113ddd24
  SS58 Address:      5D5wj2hBMhmiqFv2yYpbx4b6zighJRSsgbH9RFsX4CqTw9Ew

Repetimos lo mismo para el segunod nodo.

Resumen:

Par Tipo Valor
Nodo1 sr25519: 5EHzAqPex3FrJaeqAsx3PUECsBwxfVuh6Lc38NjDAXi73JbH
Nodo1 ed25519: 5D5wj2hBMhmiqFv2yYpbx4b6zighJRSsgbH9RFsX4CqTw9Ew
Nodo1 Mnemonic phrase modify document mixed shoot always undo absurd skill twice deer shove correct
Nodo2 sr25519: 5EngVmxvLp5u5xQhzDwSv1H4YsFDdJpq83UCrozB9qgV5J1Q
Nodo2 ed25519: 5C7dEABXtxNoutzrj5c5obNHsgBD8EYvQ5EN13gvv8mw1efd
Nodo2 Mnemonic phrase tragic curtain option forward mention dawn enact brass pave auction south canvas

Generación de la Chain Spec de la red

Una Chain Spec, o “especificación de cadena”, es una colección de información de configuración que muestra a qué red se conectará un nodo de blockchain, con qué entidades se comunicará inicialmente y qué consenso debe tener en la génesis.

Usaremos la especificación del proyecto plantilla de Substrate, exportando el resultado a un fichero json.

./target/release/node-template build-spec --disable-default-bootnode --chain local > customSpec.json
2021-07-30 16:21:02 Building chain spec    

Nos centraremos en la parte del fichero para definir las autoridades de la funcionalidad Aura (sin entrar en detalles, el mecanismo de generación de bloques). También modificaremos la parte Grandpa (igualmente sin detallar el mecanismo, se trata de la gestión de la finzalización de bloques)

Lo que tenemos que cambiar son la direcciones “authorities” con las que hemos generado anteriormente. Las direcciones sr25519 irán a la sección palletAura->authorities. Las direcciones ed2519 irán a la sección palletGrandpa->authorities. En este caso, el mecanismo Grandpa es más complejo, porque permite votación balanceada. Indicamos a cada dirección el mismo peso (1). Si en nuestra red queremos añadir más nodos validadores, podemos añadir las direcciones correspondientes.

"palletAura": {
        "authorities": [
          "5EHzAqPex3FrJaeqAsx3PUECsBwxfVuh6Lc38NjDAXi73JbH",
          "5EngVmxvLp5u5xQhzDwSv1H4YsFDdJpq83UCrozB9qgV5J1Q"
        ]
      },
      "palletGrandpa": {
        "authorities": [
          [
            "5D5wj2hBMhmiqFv2yYpbx4b6zighJRSsgbH9RFsX4CqTw9Ew",
            1
          ],
          [
            "5C7dEABXtxNoutzrj5c5obNHsgBD8EYvQ5EN13gvv8mw1efd",
            1
          ]
        ]
      },

No modificaremos ningún parámetros más para este ejemplo. Para distribuir esta configuración entre los nodos participantes, se debe convertir a un formato “raw”. Con esto aseguramos que la misma información está codificada y minimizamos errores al distribuir la configuración. Se recomienda que un usuario único genere la Chain Spec y comparta el resultado con el resto de la comunidad participante en la cadena.

./target/release/node-template build-spec --chain=customSpec.json --raw --disable-default-bootnode > customSpecRaw.json
2021-07-30 18:41:46 Building chain spec

Inicialización de los nodos de la red

En nuestro ejemplo lanzaremos dos nodos. Lanzamos la ejecución en la misma máquina, dos terminales diferentes. El proceso a realizar:

  • Lanzar nodo01
  • Insertar claves en el keystore del nodo para poder producir y finalizar bloques
  • Lanzar nodo02
  • Insertar claves en el keystore del nodo para poder producir y finalizar bloques
  • Parar nodos y volver a iniciarlos

Lanzar Nodo 1

developer@debian:~/devel/chain/node/substrate-node-template# ./target/release/node-template \
>   --base-path /tmp/node01 \
>   --chain ./customSpecRaw.json \
>   --port 30333 \
>   --ws-port 9945 \
>   --rpc-port 9933 \
>   --telemetry-url 'wss://telemetry.polkadot.io/submit/ 0' \
>   --validator \
>   --rpc-methods Unsafe \
>   --name RgmNode01

2021-07-30 22:56:23 Substrate Node    
2021-07-30 22:56:23 ✌️  version 3.0.0-12d56a7-x86_64-linux-gnu    
2021-07-30 22:56:23 ❤️  by Substrate DevHub <https://github.com/substrate-developer-hub>, 2017-2021    
2021-07-30 22:56:23 📋 Chain specification: Local Testnet    
2021-07-30 22:56:23 🏷 Node name: RGMNode01    
2021-07-30 22:56:23 👤 Role: AUTHORITY    
2021-07-30 22:56:23 💾 Database: RocksDb at /tmp/node01/chains/local_testnet/db    
2021-07-30 22:56:23 ⛓  Native runtime: node-template-100 (node-template-1.tx1.au1)    
2021-07-30 22:56:23 Using default protocol ID "sup" because none is configured in the chain specs    
2021-07-30 22:56:23 🏷 Local node identity is: 12D3KooWSLUZRj5zF8i67s2zSJigThXpTdFTqUB534eHAzMiuVZv    
2021-07-30 22:56:23 📦 Highest known block at #0    
2021-07-30 22:56:23 〽️ Prometheus server started at 127.0.0.1:9615    
2021-07-30 22:56:23 Listening for new connections on 127.0.0.1:9945.    
2021-07-30 22:56:27 Accepted a new tcp connection from 127.0.0.1:46268.    
2021-07-30 22:56:28 💤 Idle (0 peers), best: #0 (0xc9e7…6ee5), finalized #0 (0xc9e7…6ee5), ⬇ 0 ⬆ 0    

2021-07-30 22:56:33 💤 Idle (0 peers), best: #0 (0xc9e7…6ee5), finalized #0 (0xc9e7…6ee5), ⬇ 0 ⬆ 0    
2021-07-30 22:56:38 💤 Idle (0 peers), best: #0 (0xc9e7…6ee5), finalized #0 (0xc9e7…6ee5), ⬇ 0 ⬆ 0    
2021-07-30 22:56:43 ❌ Error while dialing /dns/telemetry.polkadot.io/tcp/443/x-parity-wss/%2Fsubmit%2F: Custom { kind: Other, error: Timeout }    
2021-07-30 22:56:43 💤 Idle (0 peers), best: #0 (0xc9e7…6ee5), finalized #0 (0xc9e7…6ee5), ⬇ 0 ⬆ 0    
2021-07-30 22:56:48 💤 Idle (0 peers), best: #0 (0xc9e7…6ee5), finalized #0 (0xc9e7…6ee5), ⬇ 0 ⬆ 0    
2021-07-30 22:56:53 💤 Idle (0 peers), best: #0 (0xc9e7…6ee5), finalized #0 (0xc9e7…6ee5), ⬇ 0 ⬆ 0 

A destacar:

  • Id del nodo: 12D3KooWSLUZRj5zF8i67s2zSJigThXpTdFTqUB534eHAzMiuVZv con rol “AUTHORITY”
  • Hay que tener en cuenta los puertos de comunicación por cada servicio del nodo: rpc, ws, etc ..
  • Cuando lanzamos el nodo, no se producirán bloques. Esto se debe a que debemos incorporar la clave en el keystore para indicar a la red (ChainSpec) qué nodos serán productores y finalizadores de bloques.

Inserción de claves al keystore

Utilizaremos, entre los mecanismos disponibles, el front-end Polkadot-Js, que ya vimos en el artículo anterior.

Nos conecatamos al nodo

Conexión

Accedemos al menú “Developer”–>“RPC Call”, para realizar la llamada y insertar la clave al Keystore del nodo.

Elegimos “author” y la función “insertKey”.

Los datos para este nodo serán:

Para aura, la clave es la sr25519.

Parámetro Valor
keytype aura
suri modify document mixed shoot always undo absurd skill twice deer shove correct
publicKey 5EHzAqPex3FrJaeqAsx3PUECsBwxfVuh6Lc38NjDAXi73JbH

Llamada

Para grandpa, la clave es la ed2519

Parámetro Valor
keytype gran
suri modify document mixed shoot always undo absurd skill twice deer shove correct
publicKey 5D5wj2hBMhmiqFv2yYpbx4b6zighJRSsgbH9RFsX4CqTw9Ew

Llamada

Ahora abriremos otro terminal y lanzamos el segundo nodo. Los siguientes validadores pueden entrar en la red, especificando en el parámetro –bootnode, cual es el nodo inicial (usando url del nodo y el identificador del nodo)

root@debian:~/devel/chain/node/substrate-node-template# ./target/release/node-template  
>   --base-path /tmp/node02   
>   --chain ./customSpecRaw.json   
>   --port 30334   
>   --ws-port 9946   
>   --rpc-port 9934   
>   --telemetry-url 'wss://telemetry.polkadot.io/submit/ 0'   
>   --validator   
>   --rpc-methods Unsafe   
>   --name MyRGMNode02   
>   --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWSLUZRj5zF8i67s2zSJigThXpTdFTqUB534eHAzMiuVZv

2021-07-30 23:11:13 Substrate Node    
2021-07-30 23:11:13 ✌️  version 3.0.0-12d56a7-x86_64-linux-gnu    
2021-07-30 23:11:13 ❤️  by Substrate DevHub <https://github.com/substrate-developer-hub>, 2017-2021    
2021-07-30 23:11:13 📋 Chain specification: Local Testnet    
2021-07-30 23:11:13 🏷 Node name: MyRGMNdeode02    
2021-07-30 23:11:13 👤 Role: AUTHORITY    
2021-07-30 23:11:13 💾 Database: RocksDb at /tmp/node02/chains/local_testnet/db    
2021-07-30 23:11:13 ⛓  Native runtime: node-template-100 (node-template-1.tx1.au1)    
2021-07-30 23:11:13 Using default protocol ID "sup" because none is configured in the chain specs    
2021-07-30 23:11:13 🏷 Local node identity is: 12D3KooWCfiwGUxnvBQartMR2vKDtVzF914dAtCndWLFoNyJBn4X    
2021-07-30 23:11:13 📦 Highest known block at #2    
2021-07-30 23:11:13 Listening for new connections on 127.0.0.1:9946.    
2021-07-30 23:11:14 🔍 Discovered new external address for our node: /ip4/127.0.0.1/tcp/30334/p2p/12D3KooWCfiwGUxnvBQartMR2vKDtVzF914dAtCndWLFoNyJBn4X    
2021-07-30 23:11:18 💤 Idle (1 peers), best: #2 (0x157f…8f3b), finalized #0 (0xc9e7…6ee5), ⬇ 0.6kiB/s ⬆ 0.6kiB/s    
2021-07-30 23:11:23 💤 Idle (1 peers), best: #2 (0x157f…8f3b), finalized #0 (0xc9e7…6ee5), ⬇ 27 B/s ⬆ 32 B/s    
2021-07-30 23:11:24 ✨ Imported #3 (0x7afc…356f)    
2021-07-30 23:11:28 💤 Idle (1 peers), best: #3 (0x7afc…356f), finalized #0 (0xc9e7…6ee5), ⬇ 0.2kiB/s ⬆ 65 B/s    

A destacar:

  • Id del nodo: 12D3KooWCfiwGUxnvBQartMR2vKDtVzF914dAtCndWLFoNyJBn4X con rol “AUTHORITY”
  • Hay que tener en cuenta los puertos de comunicación por cada servicio del nodo: rpc, ws, etc .., que son diferentes del nodo anterior por estar en la misma máquina.
  • En el log observamos que el segundo nodo se ha emparejado (1 peers) con el nodo 1 y que se producen blqoues.

Para que este nodo actue de forma efectiva como validador debemos introducir las claves generadas para el segundo nodo en el keystore. Usaremos el mismo mecanismo que para el nodo 1

Podremos conectarnos a cada nodo de la red y revisar la información.

Conexión segundo nodo

Una vez hecho, reiniciamos los dos nodos y ya tendremos nuestra red privada con dos nodos correctamente configurada :)

En información del nodo, podremos ver el identificador del par que se ha conectado a la red.

Resumen

Desarrollo funcionalidad PoE en la red

Substrate permite diferentes aproximaciones para construir aplicaciones descentralizadas. Se puede realizar mediante módulos en el Runtime de la red (denominados Pallets) o mediante un modelo basado en Smart Contracts. Para el desarrollo de la funcionalidad de PoE usaremos los Pallet de Substrate. Si entrar en profundidad, un Pallet es un módulo (Software) desarrollado en Rust que define una serie de tipos de datos, características y funciones que implementan la especificación necesaria para añadir funcionalidades a la red. Existen Pallets incorporados por defecto y otros que podemos desarrollar. Aquí se muestra la estructura que debe mantener el módulo.

Los pasos a realizar son:

  • Explicación del Proof of Existnece
  • Modificación Plantilla para incorporar nuestro Pallet
  • Desarrollo cliente y verificación de pruebas

Proof of Existence ( Prueba de existencia )

Se define Proof of Existence (PoE) como

Servicio que verifica la existencia de un fichero en un momento (tiempo) determinado via transacciones en una red blockchain, selladas en el tiempo

Si extendemos el activo de fichero a cualquier dato, podremos crear un servicio de notarización de información. Los usos habituales son:

  • Demostrar la propiedad de los datos sin revelar datos reales. Se puede publicar el resumen (hash) de cualquier información y, si surge un conflicto, se puede demostrar que se disponían los datos que generan este resumen. Útil para material protegido por derechos de autor, patentes, etc.

  • Sellado de tiempo del documento. Se puede probar que existen ciertos datos en un momento determinado. Como usamos una blockchain (bitcoin, ethereum o nuestra red substrate) para almacenar la prueba del documento, se puede certificar la existencia de su documento sin la necesidad de una autoridad central.

  • Comprobación de la integridad del documento. Si se almacena una prueba para un documento y luego se vuelve a cargar, el sistema solo lo reconocerá si es completamente el mismo documento. El más mínimo cambio, y lo reconoceremos, es diferente, lo que le brinda la seguridad de que los datos certificados no se pueden cambiar.

Modificación Plantilla para incorporar nuestro Pallet

La plantilla de projecto que utilizamos que hemos utilizado tiene un runtime basado en FRAME, que es una biblioteca/librería que permite definir un runtime a partir de módulos llamados “pallets”. Estos módulos aportan lógica y dan funcionalidad a la red Substrate. Existen unos pallets prefabricados que se pueden activar en el proyecto a través de un fichero de configuración.

FRAME

Para activar los diferentes pallets debemos modificar el fichero ubicado en runtime/Cargo.toml

Nuestro objetivo es desarrollar un módulo FRAME con la funcionalidad PoE, para incluirla en nuestra blockchain. Usaremos la plnatilla de pallet que incorpora el proyecto para crear el servicio.

Copiamos la carpeta substrate-node-template/node/pallets/template a la carpeta substrate-node-template/node/pallets/poe

substrate-node-template
|
+-- node
|
+-- pallets
|   |
|   +-- poe
|       |
|       +-- Cargo.toml     <-- *Modificamos* este fichero
|       |
|       +-- src
|           |
|           +-- lib.rs     <-- *Borramos* contenido
|           |
|           +-- mock.rs    <-- *Borramos* 
|           |
|           +-- tests.rs   <-- *Borramos* 
|
+-- runtime
|
+-- scripts
|
+-- ...

Seguiremos el ejemplo que proporciona Substrate.

Esquelleto del Pallet:

Se modificará los datos en el código, marcado por Step

pallet/poe/src/lib.rs
#![cfg_attr(not(feature = "std"), no_std)]

// Re-export pallet items so that they can be accessed from the crate namespace.
pub use pallet::*;

#[frame_support::pallet]
pub mod pallet {
use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*};
use frame_system::pallet_prelude::*;
use sp_std::vec::Vec; // Step 

#[pallet::config]  // <-- Step 

#[pallet::event]   // <-- Step  

#[pallet::error]   // <-- Step 

#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);

#[pallet::storage] // <-- Step

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}

#[pallet::call]   // <-- Step 
}

#![cfg_attr(not(feature = "std"), no_std)]

// Re-export pallet items so that they can be accessed from the crate namespace.
pub use pallet::*;

#[frame_support::pallet]
pub mod pallet {
    use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*};
    use frame_system::pallet_prelude::*;
    use sp_std::vec::Vec; // Step 3.1 will include this in `Cargo.toml`

  
    /// Configure the pallet by specifying the parameters and types on which it depends.
    #[pallet::config]
    pub trait Config: frame_system::Config {
        /// Because this pallet emits events, it depends on the runtime's definition of an event.
        type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
    }


    // Pallets use events to inform users when important changes are made.
    // Event documentation should end with an array that provides descriptive names for parameters.
    // https://substrate.dev/docs/en/knowledgebase/runtime/events
    #[pallet::event]
    #[pallet::metadata(T::AccountId = "AccountId")]
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event<T: Config> {
        /// Event emitted when a proof has been claimed. [who, claim]
        ClaimCreated(T::AccountId, Vec<u8>),
    /// Event emitted when a claim is revoked by the owner. [who, claim]
        ClaimRevoked(T::AccountId, Vec<u8>),
    }

    
    #[pallet::error]
    pub enum Error<T> {
            /// The proof has already been claimed.
            ProofAlreadyClaimed,
            /// The proof does not exist, so it cannot be revoked.
            NoSuchProof,
            /// The proof is claimed by another account, so caller can't revoke it.
            NotProofOwner,
        }

    
    #[pallet::pallet]
    #[pallet::generate_store(pub(super) trait Store)]
    pub struct Pallet<T>(_);
    
    #[pallet::storage] 
    pub(super) type Proofs<T: Config> = StorageMap<_, Blake2_128Concat, Vec<u8>, (T::AccountId, T::BlockNumber), ValueQuery>;   

    
    #[pallet::hooks]
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
    
    // Dispatchable functions allows users to interact with the pallet and invoke state changes.
    // These functions materialize as "extrinsics", which are often compared to transactions.
    // Dispatchable functions must be annotated with a weight and must return a DispatchResult.
        #[pallet::call]
        impl<T: Config> Pallet<T> {
            #[pallet::weight(1_000)]
            pub(super) fn create_claim(
                origin: OriginFor<T>,
                proof: Vec<u8>,
            ) -> DispatchResultWithPostInfo {

                // Check that the extrinsic was signed and get the signer.
                // This function will return an error if the extrinsic is not signed.
                // https://substrate.dev/docs/en/knowledgebase/runtime/origin
                let sender = ensure_signed(origin)?;
            
                // Verify that the specified proof has not already been claimed.         
                ensure!(!Proofs::<T>::contains_key(&proof), Error::<T>::ProofAlreadyClaimed);

                // Get the block number from the FRAME System module.
                let current_block = <frame_system::Module<T>>::block_number();

                // Store the proof with the sender and block number.
                Proofs::<T>::insert(&proof, (&sender, current_block));

                // Emit an event that the claim was created.
                Self::deposit_event(Event::ClaimCreated(sender, proof));

                Ok(().into())
            }

            #[pallet::weight(10_000)]
            fn revoke_claim(
                origin: OriginFor<T>,
                proof: Vec<u8>,
            ) -> DispatchResultWithPostInfo {
                // Check that the extrinsic was signed and get the signer.
                // This function will return an error if the extrinsic is not signed.
                // https://substrate.dev/docs/en/knowledgebase/runtime/origin
                let sender = ensure_signed(origin)?;

                // Verify that the specified proof has been claimed.
                ensure!(Proofs::<T>::contains_key(&proof), Error::<T>::NoSuchProof);

                // Get owner of the claim.
                let (owner, _) = Proofs::<T>::get(&proof);

                // Verify that sender of the current call is the claim owner.
                ensure!(sender == owner, Error::<T>::NotProofOwner);

                // Remove claim from storage.
                Proofs::<T>::remove(&proof);

                // Emit an event that the claim was erased.
                Self::deposit_event(Event::ClaimRevoked(sender, proof));

                Ok(().into())
            }
        }

}

Modificamos el nombre del pallet en el fichero de configuración.

pallet/poe/src/Cargo.toml

Modificamos el parámetro name y lo denominamos pallet-poe

license = 'Unlicense'
name = 'pallet-poe'
readme = 'README.md'
repository = 'https://github.com/substrate-developer-hub/substrate-node-template/'
version = '3.0.0'

Añadimos nuestro pallet en la configuración del proyecto, en el apartado de dependencias locales

runtime/Cargo.toml
# local dependencies
pallet-poe = {default-features = false, version = '3.0.0', path = '../pallets/poe'}

Modificamos la inicialización del nodo para instanciar el nuevo pallet

runtime/src/lib.rs
/// Import the template pallet.
pub use pallet_template;

pub use pallet_poe;

...
...
...

impl pallet_poe::Config for Runtime {
	type Event = Event;
}

// Create the runtime by composing the FRAME pallets that were previously configured.
construct_runtime!(
	pub enum Runtime where
		Block = Block,
		NodeBlock = opaque::Block,
		UncheckedExtrinsic = UncheckedExtrinsic
	{
		System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
		RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Call, Storage},
		Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent},
		Aura: pallet_aura::{Pallet, Config<T>},
		Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event},
		Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
		TransactionPayment: pallet_transaction_payment::{Pallet, Storage},
		Sudo: pallet_sudo::{Pallet, Call, Config<T>, Storage, Event<T>},
		// Include the custom logic from the pallet-template in the runtime.
		TemplateModule: pallet_template::{Pallet, Call, Storage, Event<T>},
		PoeModule: pallet_poe::{Pallet, Call, Storage, Event<T>},
	}
);

Una vez hemos modificado el código procedemos a crear nuestra red modificada con la nueva funcionalidad.

cargo build --release

Desarrollo cliente y verificación de pruebas

Para verificar la funcionalizar realizada podemos

  • Crear un cliente en Javascript, para interactuar con la red
  • Usar el front-end de gestión para interactuar

En este caso, desarrollaremos un cliente sencillo para mostrar como conectarnos y dar pie a nuevos desarrollos utilizando este modelo de trabajo. Para verificar el funcionamiento del cliente, revisaremos los resultados en el front-end de gestión.

En mis primeros aprendizajes con Blockchain usé Web3.js como elemento central en mis desarrollos. En este proyecto utilizaré la librería Polkadot.js. Para usar correctamente esta librería, debemos disponer en nuestro entorno de desarrollo de la versión de NodeJS >14.0

#Tip ŕápido para actualizar a última versión, usando Node versión manager

$ npm install -g n
$ n stable
   installing : node-v14.17.3
   mkdir : /usr/local/n/versions/node/14.17.3
   fetch : https://nodejs.org/dist/v14.17.3/node-v14.17.3-linux-x64.tar.xz
   installed : v14.17.3 to /usr/local/bin/node
   active : v14.17.3 to /usr/local/bin/node

Una vez tenemos el entorno correcto:

  • Inicializamos proyecto
  • Instalamos dependencias
  • Desarrollamos cliente

La inicialización del proyecto es senzila, utilizando npm init

$:~/devel/chain/dummy_app# 
$ npm init
...
...

About to write to devel/chain/dummy_app/package.json:

{
  "name": "poa_test",
  "version": "1.0.0",
  "description": "poa substrate client",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Ricardo Gonzalez",
  "license": "ISC"
}

Is this OK? (yes) yes

Las librerías que necesitamos para desarrollar el cliente son: ‘@polkadot/api’, ‘@polkadot/util’, ‘@polkadot/keyring’, ‘@polkadot/util-crypto’

#usaremos el gestor de paquetes npm para instalar las dependencias. Nos ubicamos dentro de la carpeta del proyecto (dummy_app) y lanzamos npm.
$:/devel/chain/dummy_app# npm i polkadot/api
....
$:/devel/chain/dummy_app# npm i @polkadot/util
$:/devel/chain/dummy_app# npm i @polkadot/keyring
$:/devel/chain/dummy_app# npm i @polkadot/util-crypto

Ahora creamos nuestro programa de prueba (app.js)

// Importamos librerías necesarias
const { ApiPromise, WsProvider } = require('@polkadot/api');
const { stringToU8a, u8aToHex } = require('@polkadot/util');
const { Keyring } = require('@polkadot/keyring');
const { signatureVerify } = require('@polkadot/util-crypto');


async function main () {

  ///TEST 1: Conexión a proveedor y consulta de datos de la red
  /// Conectamos a uno de los nodos de la red
  const provider = new WsProvider('ws://127.0.0.1:9945');

  // Creamos API y esperamos hasta que esté disponible
  const api = await ApiPromise.create({ provider });

  // Obtenemos la red y información del nodo via llamadas RPC
  const [chain, nodeName, nodeVersion, header, now] = await Promise.all([
    api.rpc.system.chain(),
    api.rpc.system.name(),
    api.rpc.system.version(),
    api.rpc.chain.getHeader(),
    api.query.timestamp.now()
  ]);

  console.log(`>>>Conectado a la red ${chain} usando node ${nodeName} v${nodeVersion} número de cabecera ${header.number} timestamp ${now.toNumber()}`);


 /// TEST 2: Llamamos e la funcion del Pallet Proof Of Existence, createClaim, con la cuenta de test Alice
 // Usamos Alice Account
  const keyring = new Keyring({ type: 'sr25519' });
  const alice = keyring.addFromUri('//Alice');

  const addrAlice = alice.publicKey 
  let account = await api.query.system.account(addrAlice);
  console.log(`alice balance before: ${account.data.free}`)

  // Llamamos a la llamada createClaim
  // Opcionalmente podríamos verificar si el claim existe y quien lo ha hecho. Si el bloque es 0, el claim no existe.
  // const [accountId, block]  = await api.query.templateModule.proofs('This is a message') 

  // Los datos certificados son 'This is a message'

  const [claim] = await Promise.all([
    api.tx.poeModule.createClaim('This is a message'),
  ]);

  // Firmamos la transacción y la enviamos usando la cuenta seleccionada
  const hash = await claim.signAndSend(alice);

  console.log('Transferencia realizada con Hash', hash.toHex());


  //Verificamos que el PoA funciona, realizando una Query sobre la prueba

  const [accountId, block]  = await api.query.poeModule.proofs('This is a message')
  console.log(`Poa test ${accountId} Block Number ${block}`) // Printa account I i block number


/// TEST 3: Firmar qualquier mensaje con una cuenta.

// Creamos cualquier mensaje y lo firmamos
const message = stringToU8a('Hello world');
const signature = alice.sign(message);

// Verificamos si el mensaje se ha firmaod por parte de la cuenta
const { isValid } = signatureVerify(message, signature, alice.address);

// output the result
console.log(`${u8aToHex(signature)} is ${isValid ? 'valid' : 'invalid'}`);

}

main().catch(console.error).finally(() => process.exit());

Para lanzar el programa:

$: node app.js

>>>Conectado a la red Local Testnet usando node Substrate Node v3.0.0-12d56a7-x86_64-linux-gnu número de cabecera 80 timestamp 1627680030001
alice balance before: 1152921504481846851
Transferencia realizada con Hash 0xc32b43f1bfdfe8c16d1d2f86d86f45546770bf611c5dcd7c6faafa3d93418008
Poa test 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY Block Number 79
0x1451fbbf2b8fa2ca5a48e1176333b75fcf986677d700c1df1b63081189bfb4398663b101eb9a89da51a9633274c57f38e4eecb889e6471117d550fa4ef932989 is valid

Una vez realizado el ejemplo, podemos verificar el resultado también a través del front-end Polkadot.js

  • Podemos realizar la llamada al módulo directamente des de el Font-End, a través del Menú Developer->Extrinsics

PoE

  • En el explorador de bloques podemos buscar el número 79, para ver el detalle de toda la transacción

Transacción 1

  • Vemos el detalle de la transacción

Transacción 2

Casos de uso de comunicación certificada

Uno de los principales usos para redes blockchain, son los procesos de notarización, para generear evidencias de multitud de procesos de negocio de una organización o empresa. Si nos centramos en el caso de una AAPP podemos plantear una serie de casos de uso sobre el ejemplo que hemos desarrollado:

  • Atención ciudadana automatizada

    La generalizazión de Chtabots para la atención ciudadana harà necesario desarrollar canales de comunicación seguros, confiables, no suplantables, que puedan acreditar la comunicaciones que se realicen y desarrollar procesos no solo de comunicación, si no de tramitación plena, con todas las garantías para la ciudadanía y la AAPP. A parte de integrar mecanismos de SSI (hablaremos en otro artículo…) para identificar al ciudadano, se deberían poder desarrollar tramitaciones automatizadas y intercambios de información y documentos, de forma guiada (documentos, imatge y vídeo).

  • Comunicaciones certificadas

    Las comunicaciones/notificaciones realizadas por las AAPP tienden a ser poco usables. Acceder a la sede electrónica con certificado electrónico o clave de un solo uso es una barrera de entrada para generalizar el uso de las mismas. A pesar que en entorno empresarial está plenamente integrado, simplificar estas comunicaciones acercaría mucho más al ciudadano a las AAPP. ¿Por qué no podemos notificar con garantías mediante un simple correo, un SMS o un mensaje de Whatsapp o Telegram? Las comunicaciones certificadas, son una forma fácil, rápida y económica de dotar de seguridad jurídica a las comunicaciones electrónicas de las AAPP.

    1 E-mail certificado

    Se puede plantear un esquema similar a :

    Esquema

    El usuario (AAPP) envía un mail directamente al destino (Ciudadanía) y al sistema de certificación a través de una dirección en copia (notari@vallschain.io) o también enviar directamente al sistema que lee los detalles del mensaje para obtener la información del destinatario y los documentos adjuntos. Toda la comunicación queda registrada en la red distribuida y confirma la recepción simplemente contestando al correo. En el cuerpo del correo se puede incluir mecanismos para asegurar que el correo se ha recibido, se ha abierto o se ha leido.

    2 SMS certificado

    El mecanismo es similar al del e-mail certificado. Se puede registrar el momento de envío, des de donde, el contenido del mensaje, etc etc

    3 Any-app certificada

    Via APIs de diferentes apps de comunicación (Facebook, Twitter, Telegram, Whatsapp) se podría implementar el mecanismo de comunicación certificada, registrando cada acción en la red blockchain.

  • Gestión de evidencias para eventos multimedia

    Certificación

    • Existe un evento multimedia: una llamada, una videoconferencia, un accidente, un control de acceso
    • Se captura el evento: cualquier sensor o dispositivo como cámara, móvil o sensores
    • Se genera un documento digital
    • Se interactúa con la red
    • Se genera la evidencia. Puede desencadenar mediante AI otros procesos de negocio.

    Esta gestión de evidencias daria pie a certificar la Atención ciudadana via teléfono y a poder trámitar por este canal, videoactas, controles de acceso en edificios municipales, etc etc

  • Publicaciones Web. Registros de publicación

    Publicaciones

Podemos realizar el mismo modelo de trabajo que en el proyecto OpenVallsChain Cualquier publicación (dato, fichero, artículo, etc etc) puede ser registrada en la red y generar evidencia del origen de la publicación. La idea es publicar en IPFS el documento y generar la evidencia a partir del hash IPFS generado. Aplicable a todas las aplicaciones que necesiten generar una evidencia de un hecho relacionado con la presentación de datos: registro E/S, web municipal, sede electrónica, etc etc.

En próximos artículos desarrollaremos nuevos conceptos alrededor de la tecnología de registro distribuido y como poder aplicarlos en la AAPP para mejorar y simplificar procesos de trabajo que actualmente son ineficientes y poco usables.


See also