Vásquez Drexler Abogado

Blockchain y pagaré electrónico con Python

Hace unos cuantos meses estuve en una charla acerca de la temática del pagaré electrónico o digital, sobre el cual me enfoque principalmente en el funcionamiento hipotético del pagaré electrónico a través de código computacional. Considero que la programación puede ser una forma efectiva de lograr una comprensión más profunda de conceptos legales, algo que comúnmente en nuestro ámbito se queda en una nube teórica. Que todavía no exista una regulación específica, no significa que no pueda explorarse su aplicación práctica como pruebas de concepto - al contrario - ayuda a la creación normativa con mayor profundidad, a determinar posibles lagunas o huecos lógicos sobre propuestas normativas, y además, es una de las razones por las cuales muchas leyes en materia de tecnologías de información nacen a la vida jurídica con profundas falencias.

Antes que nada, si queremos discutir sobre el pagaré electrónico es necesario hablar sobre el concepto de título valor que lo engloba; que a modo de resumen es un documento transmisible apto para ejercitar el derecho sobre él mencionado. Varios principios lo componen: 1) el de incorporación (el derecho y el documento se unen en un título valor, de modo que el derecho no existe sin el documento); 2) el de titularidad o legitimidad (todas las operaciones permitidas respecto de los títulos-valores, requieren de la presencia del título); 3) literalidad (el título contiene una obligación y un derecho); 4) autonomía (la persona que suscribe un título se obliga autónomamente); 5) circulación (la forma en que se transmite un título valor).

Con los medios digitales o electrónicos, la dinámica en la que el título valor circula cambia. Físicamente, la circulación de un título valor se hace mediante endoso, ¿pero qué ocurre dentro del contexto digital? El endoso se transforma para incorporarse dentro de los metadatos del mensaje o del propio documento digital, obligando a agregar principios adicionales como el de neutralidad tecnológica, equivalencia funcional e inalteración del derecho preexistente. Pero sobre todo, surge el reto de la seguridad: ¿cómo evitamos la duplicación de títulos valores?

¿El blockchain como una posible solución al problema de duplicación?

Los documentos electrónicos años atrás, se caracterizan por poseer un defecto de nacimiento, el cual es el de cómo hacer que dicho documento sea único. Las copias se generan tanto por actividad humana como por automatización. Un respaldo del documento digital pues es una copia de ese documento. Ahora bien, no queremos copias de un pagaré electrónico circulando por ahí, especialmente porque es un atentado contra la literalidad y titularidad.

Para solucionar este problema nos acercamos al derecho computacional - rama del derecho informático enfocada en la automatización del razonamiento legal. Un pagaré electrónico o digital, por ejemplo, puede ser un documento (y debería ser) un documento compuesto de datos estructurados: pensemos en un archivo .JSON, .XML o .YAML. La razón de ello es que necesitamos que la data sea fácilmente accesible para validación, lo que se dificultará si fuese un documento con datos no estructurados - tendríamos que preprocesar el documento antes de validarlo. Para ilustrar: una factura digital es un documento digital en formato de archivo .XML que contiene un campo específico de firma electrónica que autentica el documento. Los datos contenidos luego son consumidos por otros programas, recopilando dentro de sus propias bases de datos.

No obstante, a diferencia de la factura electrónica que surge a la vida jurídica como autenticado y en teoría inmutable; con un pagaré electrónico la inmutabilidad surge con la entrega y finalmente la aceptación del beneficiario, lo que agrega una capa de complejidad al tener que asegurarnos que el pagaré llegue a su destino, sea único digitalmente hablando, se autentiquen las partes y se asegure la privacidad de los datos contenidos en este.

Es aquí donde el blockchain se presenta como una posible solución. Para explicar cómo, usaré los componentes de un pequeño proyecto que hice en Python, a modo de ilustrar los el funcionamiento de un pagaré electrónico. Importante añadir que el código no está listo para producción, tampoco está optimizado y requiere de afinamiento, como por ejemplo el uso de encriptación asimétrica para transferencia del pagaré (y posiblemente haya algunos bugs por ahí). Sin embargo, creo que cumple aceptablemente con la idea.

Programación de un pagaré electrónico con Python

El pagaré electrónico desde el punto de vista computacional, puede ser entendido como una transacción de un documento digital estructurado con inclusión de firma digital de los participantes y un monto monetario X.

La arquitectura del proyecto se muestra en el siguiente diagrama:

Diagrama pagaré

El código se compone de:

Funciones de Seguridad:
  • Función Hash: Genera un valor hash único para los datos, asegurando su integridad.
  • Firma Digital: Aplica una firma digital que certifica la autenticidad del documento.
  • Encriptación: Protege la información mediante encriptación, en este caso simétrica.
  • Validación: Verifica la integridad y autenticidad de los datos.
Canal de Transferencia:
  • Pagaré Digital: El proceso comienza con la creación de un pagaré digital.
  • Documento Digital con firma estructurada: El pagaré se convierte en un documento digital firmado por el emisor y oosteriormente por el beneficiario.
  • Creación de un bloque: se crea un nuevo bloque con el contenido de la transacción.
  • Bloque Confirmado: Una vez validado, el bloque se confirma y se añade a la cadena de bloques.
Registro Blockchain:
  • Bloque Génesis: El primer bloque de la cadena.
  • Bloques Subsiguientes: Los bloques siguientes (Block 1, Block 2, ..., Block N) que contienen las transacciones.
  • Estructura del Bloque:
    • Encabezado: Contiene metadatos del bloque.
    • Hash Previo: El hash del bloque anterior.
    • Timestamp: Marca de tiempo de la transacción.
    • Datos del Pagaré: La información específica del pagaré.
    • Raíz de Merkle: Verifica la integridad de los datos.

Ahora, ilustraremos el flujo del programa incluyendo el código, mediante un ejemplo.

def transaction_example(sender, recipient, doc_path, description, key):
# Create a new promissory note
promissory_note = PromissoryNote(doc_path, description)
 
# Sign and encrypt the promissory note
encrypted_note_path = promissory_note.sign_and_encrypt_note(sender, key)
print("Encrypted note path:", encrypted_note_path)
 
# Generate a hash for the promissory note
hash_value = promissory_note.generate_hash(encrypted_note_path)
print("\nPromissory note hash:", hash_value)
 
# Transfer the promissory note
block = promissory_note.transfer_promissory_note(sender, recipient, hash_value)
print("\nPromissory note transferred to:", recipient)
print("\nNew block forged:", block)
 
# Accept the promissory note
new_owner = promissory_note.accept_promissory_note(
    recipient, encrypted_note_path, key
)
print("\nPromissory note verified and accepted by:", new_owner)
 
# Get the chain
chain = requests.get(f"{BaseURL.BLOCKCHAIN_API_URL.value}/chain")
print("\nBlockchain chain:", chain.json())

Supongamos que Alicia quiere hacer la transferencia de un pagaré a Bob. Para ello crea o genera el pagaré como un documento estructurado en .XML:

<?xml version="1.0" encoding="UTF-8"?>
<promissory_note>
    <lender>
        <name>Alice</name>
        <address>123 Main Street</address>
        <city>New York</city>
        <state>NY</state>
        <zip>10001</zip>
    </lender>
    <borrower>
        <name>Bob</name>
        <address>456 Elm Street</address>
        <city>Los Angeles</city>
        <state>CA</state>
        <zip>90001</zip>
    </borrower>
    <amount>1000</amount>
    <due_date>2022-12-31</due_date>
</promissory_note>

El pagaré pasa por la clase PromissoryNote, mismo que contiene los siguientes métodos:

class BaseURL(Enum):
BLOCKCHAIN_API_URL = "http://localhost:8001"
XML_PROCESSOR_API_URL = "http://localhost:8002"
 
class PromissoryNote:
def **init**(self, doc_path, description):
self.doc_path = doc_path
self.description = description
 
def sign_and_encrypt_note(self, owner, key) -> str:
    # Add ownership to the promissory note
    ownership = requests.post(
        f"{BaseURL.XML_PROCESSOR_API_URL.value}/xml/add-ownership",
        files={"xml_file": open(self.doc_path, "rb")},
        data={"owner": owner, "owned_by_file_path": self.doc_path + ".owned"},
    )
    # Get owned_by_file_path
    owned_by_file_path = ownership.json()[
        "owned_by_file_path"
    ]  # src/data/promissory_note.xml.owned
 
    # Sign the promissory note with the new ownership
    signed_note = requests.post(
        f"{BaseURL.XML_PROCESSOR_API_URL.value}/xml/sign",
        files={"xml_file": open(owned_by_file_path, "rb")},
        data={"signed_xml_file_path": owned_by_file_path + ".signed"},
    )
    # Get signed_note path
    signed_note_path = signed_note.json()[
        "signed_xml_file_path"
    ]  # src/data/promissory_note.xml.owned.signed
 
    # Encrypt the promissory note
    encrypted_note = requests.post(
        f"{BaseURL.XML_PROCESSOR_API_URL.value}/xml/encrypt",
        files={"xml_file": open(signed_note_path, "rb")},
        data={"encrypted_xml_file_path": signed_note_path + ".enc", "key": key},
    )
 
    # Return encrypted_note path
    return encrypted_note.json()[
        "encrypted_xml_file_path"
    ]  # src/data/promissory_note.xml.owned.signed.enc
 
def generate_hash(self, encrypted_note_path):
    # Generate a hash for the promissory note
    hash_value = requests.post(
        f"{BaseURL.XML_PROCESSOR_API_URL.value}/xml/generate-hash",
        files={"xml_file": open(encrypted_note_path, "rb")},
    )
    return hash_value.json()["hash"]
 
def transfer_promissory_note(self, sender, recipient, hash_value):
    # Create a new transaction for the promissory note
    requests.post(
        f"{BaseURL.BLOCKCHAIN_API_URL.value}/transactions/new",
        json={
            "sender": sender,
            "recipient": recipient,
            "doc_hash": hash_value,
            "description": self.description,
        },
    )
 
    # Create a new block for the transaction
    block = requests.post(f"{BaseURL.BLOCKCHAIN_API_URL.value}/blocks/new")
 
    # Verify the transaction
    transaction = requests.get(f"{BaseURL.BLOCKCHAIN_API_URL.value}/chain")
 
    if transaction.json()["chain"][-1]["transactions"][0]["doc_hash"] != hash_value:
        raise ValueError("Transaction failed")
 
    return block.json()
 
def accept_promissory_note(self, recipient, encrypted_note_path, key):
    # Decrypt the promissory note
    decrypted_note = requests.post(
        f"{BaseURL.XML_PROCESSOR_API_URL.value}/xml/decrypt",
        files={"encrypted_xml_file_path": open(encrypted_note_path, "rb")},
        data={"decrypted_xml_file_path": encrypted_note_path + ".dec", "key": key},
    )
    decrypted_note_path = decrypted_note.json()[
        "decrypted_xml_file_path"
    ]  # src/data/promissory_note.xml.owned.signed.enc.dec
 
    # Change the ownership of the promissory note
    new_owner_note = requests.post(
        f"{BaseURL.XML_PROCESSOR_API_URL.value}/xml/change-ownership",
        files={"xml_file": open(decrypted_note_path, "rb")},
        data={
            "new_owner": recipient,
            "owned_by_file_path": decrypted_note_path + ".owned",
        },
    )
    new_owner_note_path = new_owner_note.json()[
        "owned_by_file_path"
    ]  # src/data/promissory_note.xml.owned.signed.enc.dec.owned
 
    # Verify the new owner of the promissory note
    owner = requests.post(
        f"{BaseURL.XML_PROCESSOR_API_URL.value}/xml/verify-owner",
        params={"xml_file_path": new_owner_note_path},
    )
 
    return owner.json()["owner"]

Ésta clase PromissoryNote maneja pagarés electrónicos mediante la adición de titularidad, firma digital, encriptación, generación de hash, transferencia y aceptación usando dos URLs base: BLOCKCHAIN_API_URL y XML_PROCESSOR_API_URL correspondientes (como describe el nombre de las variables) a endpoints de APIs.

A continuación, un desglose detallado de la clase:

  1. BaseURL: Define dos URLs base como valores de enumeración - una para la API de blockchain y otra para el procesador de XML.
  2. PromissoryNote:
    • __init__ - Inicializa la clase con la ruta del documento y una descripción.
    • Método sign_and_encrypt_note:
      • Añade titularidad del emisor al pagaré.
      • Firma el pagaré con la nueva titularidad.
      • Encriptación simétrica del pagaré.
      • Devuelve la ruta del archivo encriptado promissory_note.xml.owned.signed.enc.
    • Método generate_hash: Genera un hash para el pagaré encriptado y devuelve el valor del hash.
    • Método transfer_promissory_note:
      • Crea una nueva transacción para el pagaré.
      • Crea un nuevo bloque para la transacción.
      • Verifica la transacción.
      • Devuelve la información del bloque creado.
    • Método accept_promissory_note:
      • Desencripta el pagaré.
      • Cambia la titularidad del pagaré al beneficiario.
      • Verifica el nuevo titular o propietario del pagaré.
      • Devuelve el nuevo titular o propietario del pagaré.

Para hacer seguimiento de cada paso, a modo didáctico, se renombra el archivo que contiene el pagaré. Entonces, si Alice generó el pagaré, ella lo va a firmar digitalmente produciendo el archivo con nombre promissory_note.xml.owned.signed. Posteriormente encripta el documento, creándose el archivo con nombre promissory_note.xml.owned.signed.enc. Se genera un hash de referencia que luego es agregado a la cadena de bloques.

A continuación Bob recibe el pagaré y lo desencripta, produciendo un promissory_note.xml.owned.signed.enc.dec para luego verificar la autenticidad de la firma digital de Alice y decidir si lo acepta o no. Si lo acepta, el .XML del pagaré se cambia generando el archivo promissory_note.xml.owned.signed.enc.dec.owned para luego estampar su firma digital de decidirlo cuando.

Ahora, adentrémonos un poco al código del blockchain.

class Blockchain:
def __init__(self):
    self.chain = []
    self.current_transactions = []
 
    # Create the genesis block
    self.new_block(previous_hash="0")
 
def new_block(self, previous_hash):
    block = {
        "index": len(self.chain) + 1,
        "timestamp": str(date.datetime.now()),
        "transactions": self.current_transactions,
        "previous_hash": previous_hash or self.hash(self.chain[-1]),
    }
    self.current_transactions = []
    self.chain.append(block)
    return block
 
def new_transaction(self, sender, recipient, doc_hash, description):
    self.current_transactions.append(
        {
            "sender": sender,
            "recipient": recipient,
            "doc_hash": doc_hash,
            "description": description,
        }
    )
    return self.last_block["index"] + 1
 
# Get transaction details
def get_transaction(self, index):
    return self.chain[index - 1]["transactions"]
 
# Clear the blockchain
def clear(self):
    self.chain = []
    self.current_transactions = []
    self.new_block(previous_hash="0")
 
# Verify the blockchain
def verify(self):
    for i in range(1, len(self.chain)):
        current_block = self.chain[i]
        previous_block = self.chain[i - 1]
        if current_block["previous_hash"] != self.hash(
            previous_block
        ):  # Check if the previous hash is correct by hashing the previous block
            return False
    return True
 
@staticmethod
def hash(block):
    block_string = json.dumps(block, sort_keys=True).encode()
    return hasher.sha256(block_string).hexdigest()
 
@property
def last_block(self):
    return self.chain[-1]

La idea de ésta clase es la implementación de una blockchain o cadena de bloques básico, precisamente para entender las bases de su funcionamiento. Por supuesto, existen librerías ya hechas para esto como lo son Hyperledger Fabric o Corda, que incluye entre otros aspectos algoritmos de consenso, implementación de "smart contracts" y demás; pero el fin de éste proyecto es entender el funcionamiento básico de una cadena de bloques. Así entonces, tenemos:

  1. __init__:
    • Inicializa una cadena de bloques (chain) y una lista de transacciones actuales (current_transactions).
    • Crea el "bloque génesis" llamando a new_block con un hash previo de "0". Importante saber que un blockchain se inicializa con un bloque génesis precisamente por el encadenamiento de hash.
  2. Método new_block:
    • Crea un nuevo bloque con un índice, marca de tiempo, transacciones actuales y hash del bloque anterior.
    • Limpia las transacciones actuales y añade el bloque a la cadena.
    • Finalmente devuelve el nuevo bloque.
  3. Método new_transaction:
    • Añade una nueva transacción a la lista de transacciones actuales.
    • Devuelve el índice del siguiente bloque donde se incluirá la transacción.
  4. Método get_transaction:
    • Devuelve las transacciones de un bloque específico en la cadena.
  5. Método clear:
    • Reinicia la cadena y las transacciones actuales.
    • Crea un nuevo "bloque génesis".
  6. Método verify:
    • Verifica que cada bloque en la cadena tenga un hash previo correcto comparándolo con el hash del bloque anterior.
    • Devuelve True si toda la cadena es válida, False si no lo es.
  7. Método hash:
    • Calcula y devuelve el hash de un bloque usando el algoritmo sha256.
  8. Propiedad last_block:
    • Propiedad que devuelve el último bloque en la cadena.

La utilidad del blockchain o cadena de bloques para el pagaré electrónico o digital, desde un punto de vista jurídico, es que cada movimiento, cada transacción queda registrado. Es decir, la circulación del título valor se compila en un ledger, que por naturaleza es inmutable; como se visualiza en el siguiente resultado:

Encrypted note path: src/data/promissory_note.xml.owned.signed.enc
 
Promissory note hash: 7f5e1d2fe53bffdd83e8b715ea13df4387c4bea85103def029f4736c40184e59
 
Promissory note transferred to: Bob
 
New block forged: {'message': 'New block forged', 'block': {'index': 2, 'timestamp': '2024-11-14 09:15:33.922320', 'transactions': [{'sender': 'Alice', 'recipient': 'Bob', 'doc_hash': '7f5e1d2fe53bffdd83e8b715ea13df4387c4bea85103def029f4736c40184e59', 'description': 'Promissory note'}], 'previous_hash': '58bbecc7fb6b5da1367919d0ec2ac705c584d1f350cb1a2ed9a3744d68c50837'}}
 
Promissory note verified and accepted by: Bob
 
Blockchain chain: {'chain': [{'index': 1, 'timestamp': '2024-11-14 09:11:13.243062', 'transactions': [], 'previous_hash': '0'}, {'index': 2, 'timestamp': '2024-11-14 09:15:33.92232
0', 'transactions': [{'sender': 'Alice', 'recipient': 'Bob', 'doc_hash': '7f5e1d2fe53bffdd83e8b715ea13df4387c4bea85103def029f4736c40184e59', 'description': 'Promissory note'}], 'previous_hash': '58bbecc7fb6b5da1367919d0ec2ac705c584d1f350cb1a2ed9a3744d68c50837'}], 'length': 2}
Encrypted note path: src/data/promissory_note.xml.owned.signed.enc
 
Promissory note hash: 8d0e3e9a69b0144956a260c36070389dda98e5e6d7a8d65c2ba00c9ef43c4f0b
 
Promissory note transferred to: Charlie
 
New block forged: {'message': 'New block forged', 'block': {'index': 3, 'timestamp': '2024-11-14 09:15:56.450610', 'transactions': [{'sender': 'Bob', 'recipient': 'Charlie', 'doc_h
ash': '8d0e3e9a69b0144956a260c36070389dda98e5e6d7a8d65c2ba00c9ef43c4f0b', 'description': 'Promissory note'}], 'previous_hash': 'ff2cdb40db5ef99afe0eea08bc9846b5cc28708cd6cc99b5dff08ecbbbcd3465'}}
 
Promissory note verified and accepted by: Charlie
 
Blockchain chain: {'chain': [{'index': 1, 'timestamp': '2024-11-14 09:11:13.243062', 'transactions': [], 'previous_hash': '0'}, {'index': 2, 'timestamp': '2024-11-14 09:15:33.92232
0', 'transactions': [{'sender': 'Alice', 'recipient': 'Bob', 'doc_hash': '7f5e1d2fe53bffdd83e8b715ea13df4387c4bea85103def029f4736c40184e59', 'description': 'Promissory note'}], 'pr
evious_hash': '58bbecc7fb6b5da1367919d0ec2ac705c584d1f350cb1a2ed9a3744d68c50837'}, {'index': 3, 'timestamp': '2024-11-14 09:15:56.450610', 'transactions': [{'sender': 'Bob', 'recip
ient': 'Charlie', 'doc_hash': '8d0e3e9a69b0144956a260c36070389dda98e5e6d7a8d65c2ba00c9ef43c4f0b', 'description': 'Promissory note'}], 'previous_hash': 'ff2cdb40db5ef99afe0eea08bc9846b5cc28708cd6cc99b5dff08ecbbbcd3465'}], 'length': 3}
Encrypted note path: src/data/promissory_note.xml.owned.signed.enc
 
Promissory note hash: 9b31b178c5ada4bb5a2e28b4d713f58b97f67cbb7d96c0c0077be6ac8dace19e
 
Promissory note transferred to: Alice
 
New block forged: {'message': 'New block forged', 'block': {'index': 4, 'timestamp': '2024-11-14 09:16:18.988040', 'transactions': [{'sender': 'Charlie', 'recipient': 'Alice', 'doc
_hash': '9b31b178c5ada4bb5a2e28b4d713f58b97f67cbb7d96c0c0077be6ac8dace19e', 'description': 'Promissory note'}], 'previous_hash': '24d07ed12e75d92ab2682552425cd5797d63596b6e62625f51a10c7be5ad4d9b'}}
 
Promissory note verified and accepted by: Alice
 
Blockchain chain: {'chain': [{'index': 1, 'timestamp': '2024-11-14 09:11:13.243062', 'transactions': [], 'previous_hash': '0'}, {'index': 2, 'timestamp': '2024-11-14 09:15:33.92232
0', 'transactions': [{'sender': 'Alice', 'recipient': 'Bob', 'doc_hash': '7f5e1d2fe53bffdd83e8b715ea13df4387c4bea85103def029f4736c40184e59', 'description': 'Promissory note'}], 'pr
evious_hash': '58bbecc7fb6b5da1367919d0ec2ac705c584d1f350cb1a2ed9a3744d68c50837'}, {'index': 3, 'timestamp': '2024-11-14 09:15:56.450610', 'transactions': [{'sender': 'Bob', 'recip
ient': 'Charlie', 'doc_hash': '8d0e3e9a69b0144956a260c36070389dda98e5e6d7a8d65c2ba00c9ef43c4f0b', 'description': 'Promissory note'}], 'previous_hash': 'ff2cdb40db5ef99afe0eea08bc98
46b5cc28708cd6cc99b5dff08ecbbbcd3465'}, {'index': 4, 'timestamp': '2024-11-14 09:16:18.988040', 'transactions': [{'sender': 'Charlie', 'recipient': 'Alice', 'doc_hash': '9b31b178c5
ada4bb5a2e28b4d713f58b97f67cbb7d96c0c0077be6ac8dace19e', 'description': 'Promissory note'}], 'previous_hash': '24d07ed12e75d92ab2682552425cd5797d63596b6e62625f51a10c7be5ad4d9b'}], 'length': 4}

Estos registros tienen valor probatorio al enriquecerse con aún más datos, porque con ella sería posible la trazabilidad de la circulación y titularidad del título valor, algo casi imposible actualmente en su versión física. La prueba se mantiene integra e inalterable, ya que cualquier cambio sobre un bloque afecta toda la cadena.

Finalmente, las APIs fueron creadas usando la librearía FastAPI. Pensando en una arquitectura de microservicios, se crearon dos servicios (¿más modularidad en sacrificio de la explicación? Aunque sigo pensando que lo ideal hubiera sido un sólo servicio): uno para el blockchain y otro para el procesador de .XML. No me detendré a explicar este código ya que es bastante directo:

blockchain_api.py

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Optional
from blockchain import Blockchain
 
app = FastAPI()
 
# Initialize a new blockchain
blockchain = Blockchain()
 
 
class Transaction(BaseModel):
    sender: str
    recipient: str
    doc_hash: str
    description: Optional[str] = None
 
 
@app.post("/transactions/new")
def new_transaction(transaction: Transaction):
    index = blockchain.new_transaction(
        sender=transaction.sender,
        recipient=transaction.recipient,
        doc_hash=transaction.doc_hash,
        description=transaction.description,
    )
    return {"message": f"Transaction will be added to Block {index}"}
 
 
@app.get("/chain")
def full_chain():
    return {
        "chain": blockchain.chain,
        "length": len(blockchain.chain),
    }
 
 
@app.post("/blocks/new")
def new_block():
    last_block = blockchain.last_block
    previous_hash = blockchain.hash(last_block)
    block = blockchain.new_block(previous_hash=previous_hash)
    return {"message": "New block forged", "block": block}
 
 
@app.post("/clear")
def clear():
    blockchain.clear()
    return {"message": "Blockchain cleared"}
 
 
@app.get("/verify")
def verify():
    verified = blockchain.verify()
    return {"message": "Blockchain verified", "verified": verified}
 
 
@app.get("/")
def read_root():
    return {"message": "Welcome to the Blockchain API"}

Y xml_processor_api.py:

from fastapi import FastAPI, UploadFile, File, Form, Query
from xml_processor import (
    XMLProcessor,
)  # Assuming xml_processor.py is in the same directory
import shutil
 
app = FastAPI()
 
xml_processor = XMLProcessor()
 
 
@app.post("/xml/sign")
async def sign_xml(
    xml_file: UploadFile = File(...), signed_xml_file_path: str = Form(...)
):
    with open(xml_file.filename, "wb") as buffer:
        shutil.copyfileobj(xml_file.file, buffer)
    xml_processor.sign_xml(xml_file.filename, signed_xml_file_path)
    return {
        "message": "XML signed successfully",
        "signed_xml_file_path": signed_xml_file_path,
    }
 
 
@app.post("/xml/verify-signed-xml")
async def verify_signed_xml(
    signed_xml_file_path: str = Query(
        ..., description="The path to the signed XML file"
    )
):
    # Implementation for verifying the signed XML
    # This is a placeholder for the actual verification logic
    verification_result = xml_processor.verify_signed_xml(signed_xml_file_path)
    return {"message": "Verification completed", "result": verification_result}
 
 
@app.post("/xml/verify-owner")
async def verify_owner(
    xml_file_path: str = Query(..., description="The path to the XML file")
):
    # Implementation for verifying the owner of the XML file
    # This is a placeholder for the actual verification logic
    owner = xml_processor.verify_owner(xml_file_path)
    return {"owner": owner}
 
 
@app.post("/xml/generate-hash")
async def generate_hash(xml_file: UploadFile = File(...)):
    with open(xml_file.filename, "wb") as buffer:
        shutil.copyfileobj(xml_file.file, buffer)
    hash_value = xml_processor.generate_hash(xml_file.filename)
    return {"hash": hash_value}
 
 
@app.post("/xml/add-ownership")
async def add_file_ownership(
    xml_file: UploadFile = File(...),
    owner: str = Form(...),
    owned_by_file_path: str = Form(...),
):
    with open(xml_file.filename, "wb") as buffer:
        shutil.copyfileobj(xml_file.file, buffer)
    owned_by_file_path = xml_processor.add_file_ownership(
        xml_file.filename, owner, owned_by_file_path
    )
    return {
        "message": "Owner added successfully",
        "owned_by_file_path": owned_by_file_path,
    }
 
 
@app.post("/xml/change-ownership")
async def change_file_ownership(
    xml_file: UploadFile = File(...),
    new_owner: str = Form(...),
    owned_by_file_path: str = Form(...),
):
    with open(xml_file.filename, "wb") as buffer:
        shutil.copyfileobj(xml_file.file, buffer)
    xml_processor.change_file_ownership(
        xml_file.filename, new_owner, owned_by_file_path
    )
    return {
        "message": "Owner changed successfully",
        "owned_by_file_path": owned_by_file_path,
    }
 
 
@app.post("/xml/remove-ownership")
async def remove_file_ownership(xml_file: UploadFile = File(...)):
    with open(xml_file.filename, "wb") as buffer:
        shutil.copyfileobj(xml_file.file, buffer)
    xml_processor.remove_file_ownership(xml_file.filename)
    return {"message": "Owner removed successfully"}
 
 
@app.post("/xml/encrypt")
async def symmetric_encrypt(
    xml_file: UploadFile = File(...),
    encrypted_xml_file_path: str = Form(...),
    key: str = Form(...),
):
    with open(xml_file.filename, "wb") as buffer:
        shutil.copyfileobj(xml_file.file, buffer)
    xml_processor.symmetric_encrypt(
        xml_file.filename, encrypted_xml_file_path, key.encode()
    )
    return {
        "message": "XML encrypted successfully",
        "encrypted_xml_file_path": encrypted_xml_file_path,
    }
 
 
@app.post("/xml/decrypt")
async def symmetric_decrypt(
    encrypted_xml_file_path: UploadFile = File(...),
    decrypted_xml_file_path: str = Form(...),
    key: str = Form(...),
):
    with open(encrypted_xml_file_path.filename, "wb") as buffer:
        shutil.copyfileobj(encrypted_xml_file_path.file, buffer)
    xml_processor.symmetric_decrypt(
        encrypted_xml_file_path.filename, decrypted_xml_file_path, key.encode()
    )
    return {
        "message": "XML decrypted successfully",
        "decrypted_xml_file_path": decrypted_xml_file_path,
    }
 
 
@app.post("/xml/generate-key")
async def generate_key():
    key = xml_processor.generate_key()
    return {"key": key.decode()}

Conclusiones

Digitalizar los títulos valores, como en este caso el pagaré, no es tarea de sólo traducirlo a un documento .PDF y mandarlo por correo electrónico. Va más allá...

Se requiere entender de la base misma de las dinámicas del título valor y los principios que lo componen para realmente hacerlo equivalente funcionalmente. El problema es que la equivalencia funcional (al menos de lo que he visto de doctrina), se olvida de la praxis: ¿cuándo y cómo se hace para que una figura legal sea equivalente en su versión digital?

Como vimos, el proceso de transformación de un título valor a su versión digital requiere de estrategia de diseño, y surgen durante nuevos retos a enfrentar en su desarrolo, como lo son el problema de duplicación y el de seguridad, teniendo que introducir nuevos principios asociados para justificar.

Lo que se presentó fue una solución alternativa, por lo que se invita al lector a desarrollar la suya propia. Pueden descargar el código a través del siguiente enlace: Repositorio de Github del Proyecto.