Skip to content

Instantly share code, notes, and snippets.

@molekilla
Created April 23, 2020 11:34
Show Gist options
  • Save molekilla/0d2fdf105b52ea43f672417c804248ad to your computer and use it in GitHub Desktop.
Save molekilla/0d2fdf105b52ea43f672417c804248ad to your computer and use it in GitHub Desktop.
MDV Docs Draft

MDV - Mensajeria de Documentos Verificables

WF API v1.0.0-rc.0

Autor: Rogelio Morrell C. , 2020

Introduccion

MDV es una solucion descentralizada de solicitudes de documentos que permite crear workflows de tramites por medio de Solidity y Javascript.

Adicionalmente incluye soporte para PKCS#7 para documentos PDF e integracion de identidad digital por medio de ethr-did.

Contratos

WTemplate

WTemplate define la plantilla madre de la solucion, esta se encarga de:

  • Creacion de la secuencia de pasos
  • Validacion y ejecuccion de pasos
  • Roles
  • Administracion de subscripciones de usuarios

IWModel y WFStorage

Un modelo requiere heredar de IWModel, esta interfaz es usada por WTemplate para acceder e interactuar con el modelo.

WFStorage contiene validadores y otros utilitarios.

ExtensionEventRegistry

El registro de extension es el contrato del API de Extensiones.

Ambientes de pruebas

Ropsten

  • Approvals: 0x7740A63CF6FC3827589861f7D3a1Ca6307a59480
  • ExtensionEventRegistry: 0xfA72D80472b10e5f315e8f9d4652C0f8d3E31EE7
  • WTemplate: 0x82f22A2bf1741fD83850ffd79F2bd82fcC5863BD

Mainnet

Pronto

Plantillas de Flujos

Un WTemplate consta de los siguientes componentes:

  • Actores: Los diferentes tipos de personas en un flujo.
  • Pasos: Las transiciones entre los estados del flujo, contiene ademas ramificaciones, extensiones y validaciones.
  • Modelo: El modelo implementa la logica de negocios del flujo, aplicando un API CRU (Create Read Update).

Para crear una nueva plantilla, se utiliza el API Javascript WFactoryHelper.

  // Factory helper
  let wf = new WFactoryHelper();

Actores

  wf.createActors(['USER', 'NOTARY']);

Estados

    wf.createStates([
    'NONE',
    'CREATED',
    'ACCEPTED',
    'CERTIFIED',
    'COMPLETED',
    'MULTI_SIGNERS',
  ]);

Pasos

Propiedades

  • currentActor: Actor cual ejecuta el paso actual
  • current: Paso actual
  • next: Siguiente paso
  • mappingType: 0 = Crear, 2 = Actualizar
  • forkId: Ramificacion, inicia en 1
  • stepValidations: Validaciones de estados, usando un operador OR
  • senderValidations: Validaciones de persona que envia la solicitud
  • recipientValidations: Validaciones de recipientes
  wf.createStep({
    currentActor: wf.getActor('USER'),
    current: wf.getState('NONE'), // none
    next: wf.getState('CREATED'), // created
    mappingType: 0, // init
  });
  wf.createStep({
    currentActor: wf.getActor('NOTARY'),
    current: wf.getState('CREATED'), // created
    next: wf.getState('ACCEPTED'), // accepted
    mappingType: 2, // status
    stepValidations: [wf.getState('CREATED')],
  });
  wf.createStep({
    currentActor: wf.getActor('NOTARY'),
    current: wf.getState('ACCEPTED'), // accepted
    next: wf.getState('MULTI_SIGNERS'), // certified
    mappingType: 2, // updated_diff
    forkId: wf.getState('CERTIFIED'),
    stepValidations: [wf.getState('ACCEPTED')],
    recipientValidations: ['0x1198258023eD0D6fae5DBCF3Af2aeDaaA363571r'],
  });
  wf.createStep({
    currentActor: wf.getActor('NOTARY'),
    current: wf.getState('MULTI_SIGNERS'), // accepted
    next: wf.getState('CERTIFIED'), // certified
    mappingType: 2, // updated_diff
    forkId: wf.getState('MULTI_SIGNERS'),
    stepValidations: [wf.getState('ACCEPTED'), wf.getState('MULTI_SIGNERS')],
    recipientValidations: ['0x1198258023eD0D6fae5DBCF3Af2aeDaaA363571r','0xaaC58E89996496640c8b5898A7e0218E9b6E90cR'],
  });
  wf.createStep({
    currentActor: wf.getActor('USER'),
    current: wf.getState('CERTIFIED'), //       forkId: 0,
    next: wf.getState('COMPLETED'), // completed
    mappingType: 2, // status
    stepValidations: [wf.getState('CERTIFIED')],
    senderValidations: ['0xaaC58E89996496640c8b5898A7e0218E9b6E90cR'],
  });

Modelos

El modelo implementa onAdd y updateSwitch que son llamados por WTemplate. Cada modelo es recomendable tenga uno o mas structs que definen el modelo de datos enviados en el payload.

pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;


import "./WStep.sol";
import "./WFStorage.sol";
import './ExtensionEventRegistry.sol';
import './IExtension.sol';
import "../../node_modules/@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract TestDocumentModel is WFStorage, ExtensionEventRegistry, ReentrancyGuard {

    address owner;
    struct TestDocument {
        string fileIpfsJson;
        string certifiedFilesIpfsJson;
        string email;
        string description;
        string name;
        string lastname;
        uint256 amount;
        // system
        address user;
        address recipient;
        bytes32 status;
    }

    mapping (uint256 => TestDocument) public table;
    ExtensionEventRegistry private extReg;
    constructor(address r) public {
        owner = msg.sender;
        extReg = ExtensionEventRegistry(r);
    }
    
    function getStructAsTuple(bytes calldata data) external returns(string memory, string memory, string memory, string memory, 
        string memory, string memory, uint256) {
        return abi.decode(data, (string, string, string, string, string, string, uint256));
    }   

    function getStruct(bytes calldata data) external returns(TestDocument memory) {
    (string memory a, string memory b, string memory c, string memory d, 
        string memory e, string memory f, uint256 g) =  abi.decode(data, (string, string, string, string, string, string, uint256));
        return TestDocument(a, b, c, d, e, f, g, address(0), address(0), bytes32(0));
    }   
    function onAddRow(
        address to,
        address sender, 
        uint256 next,
        uint256 current,
        bytes memory fields
        ) public nonReentrant returns (bool) {

        currentDocumentIndex = rowCount;
        rowCount = rowCount + 1;
        
        table[rowCount-1] = this.getStruct(fields);
        table[rowCount-1].user = sender;
        table[rowCount-1].recipient = to;
        table[rowCount-1].status = keccak256(abi.encodePacked(stepsByInitState[next].current));

        return true;
    }

    function onUpdate(uint256 doc, 
        address to,
        address sender, 
        uint256 next,
        uint256 current,
        TestDocument memory document
        ) public returns (bool) {

        validateSender(current,  sender);
        validateRecipient(current, to);
        validateStatus(current, stepsByInitState[current].current);

        currentDocumentIndex = doc;


        table[doc].status = keccak256(abi.encodePacked(stepsByInitState[next].current));
        if (keccak256(abi.encodePacked(table[doc].certifiedFilesIpfsJson)) != keccak256(abi.encodePacked(""))) {
            table[doc].certifiedFilesIpfsJson = document.certifiedFilesIpfsJson;
        }

        return true;
    }

    function onExtensionUpdate(uint256 doc, 
        address to,
        address sender, 
        uint256 next,
        uint256 current, uint256 extensionEvtId,
        TestDocument memory document
        ) public nonReentrant returns (bool, uint256) {
        validateStatus(next, stepsByInitState[current].current);

        uint256 calculatedNextFork = stepsByInitState[current].forkId;
        IExtension ext = IExtension(extReg.read(extensionEvtId).extContract);
        if (ext.canExec(extReg.read(extensionEvtId).id, sender) && keccak256(bytes(extReg.read(extensionEvtId).name)) == keccak256(bytes("approvals"))) {        
            if (ext.executeExtension(extReg.read(extensionEvtId).id, sender, abi.encodePacked(document.amount))) {
                table[doc].status = keccak256(abi.encodePacked(stepsByInitState[current].next));
            } else {
                calculatedNextFork =  stepsByInitState[current].forkId;
                table[doc].status = keccak256(abi.encodePacked( stepsByInitState[current].forkId));
            }
            
        }

        currentDocumentIndex = doc;

        if (keccak256(abi.encodePacked(table[doc].certifiedFilesIpfsJson)) != keccak256(abi.encodePacked(""))) {
            table[doc].certifiedFilesIpfsJson = document.certifiedFilesIpfsJson;
        }

        return (true, calculatedNextFork);
    }

    function updateSwitch(uint256 doc, 
        address to,
        address sender, 
        uint256 next,
        uint256 current,
        uint256 extensionEvtId,
        bytes memory fields
        ) public returns (bool, uint256) {
         
        uint256 calculatedNextFork = next;
            if (extensionEvtId > 0) {
                (bool ok, uint256 fork) = this.onExtensionUpdate(doc, to, sender, next, current, extensionEvtId, this.getStruct(fields));
                calculatedNextFork = fork;
            } else {
                this.onUpdate(doc, to, sender, next, current, this.getStruct(fields));
            }
        return (true, calculatedNextFork);
    }
}

Este payload se codifica en bytes y es creado de este modo:

    const payload = wf.createPayload({
        fileIpfsJson: JSON.stringify([{ path: '', content: '' }]),
        certifiedFilesIpfsJson: '',
        email: '',
        description: 'Testing workflow',
        name: 'John',
        lastname: 'Lopez',
        amount: 100,
    });

Para crear la plantilla, definimos cada secuencia utilizando wf.tightLinkSteps y ejecutamos wf.createWF:

  // 7) Create WF
  await template.createWF(
    wf.tightLinkSteps([
      [wf.getActor('USER'), wf.getState('NONE'), wf.getState('CREATED')],
      [wf.getActor('NOTARY'), wf.getState('CREATED'), wf.getState('ACCEPTED')],
      [
        wf.getActor('NOTARY'),
        wf.getState('ACCEPTED'),
        wf.getState('MULTI_SIGNERS'),
      ],
      [
        wf.getActor('NOTARY'),
        wf.getState('MULTI_SIGNERS'),
        wf.getState('CERTIFIED'),
      ],
      [wf.getActor('USER'), wf.getState('CERTIFIED'), wf.getState('COMPLETED')],
    ]),
    wf.getSteps()
  );

Extension API

Forking

Librerias

Identidad Digital

Soporte para el metodo DID ethr-did, lo cual permite acceder a los features del ethr-did-resolver (delegacion, reasignacion, etc)

PKCS#7

Soporte para X509 PKCS#7 en modo detached. El firmado se realiza desde un cliente web, y sube al storage descentralizado el documento y firma criptografica.

Un API backend valida la firma del documento con la llave publica del firmante.

Encriptacion Descentralizada

Soporte para Swarm Ethereum que ofrece encriptacion en reposo integrado, lo cual permite mas eficiencia al no tener que procesar por servicios intermediarios.

Solicitudes Verificables

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment