Títulos validados
935902
Transacciones validadas
1362
Título Certificado
Ministerio de Educación de la República Argentina
Certificado MINED Títulos digitales - está vigente
Hash Blockchain Ministerio de Educacion de la Nacion:
N° de bloque:
34518474
ID de bloque:
0x868326bbc91042b4bd0f37b9e137f49aab841467b370c318bd123036fcb49a40
Nº de Serie:
06-00103037-2024
Hash del Contenido Analítico:
f692a3f7636282791a59cbd30aefecfe9300a2e81c970aff41740c4f1039b22719fb69590a18ab41c0ba83381e981af920b598981af42a0e5c96cbbbe6812443
Fecha y hora de emisión del título:
hace 1 año (03/04/2024 18:00:16)
Hash de transacción:
0x9bf6fba7c5978046ae2d4fc5f3d146a21d62ba6a3e2e3ec75e7f0f67f650da24
Cabeza de Merkle:
0x9d34a412101451bf5239e364d0b5f84030fc5f919af62de613e411aaff5a9875
Dirección del contrato:
0x7e56220069CAaF8367EA42817EA9210296AeC7c6
Status:
Success
Arbol de Merkle del Sistema Nacional de Titulos Digitales
El árbol de Merkle es una estructura de datos utilizada en criptografía y cadenas de bloques para verificar rápidamente la integridad de grandes conjuntos de datos. Se construye mediante funciones hash, donde cada bloque de datos se asigna a un nodo hoja, y los nodos se combinan hasta formar una "raíz de Merkle". Esta raíz se utiliza para representar de manera única todos los datos, y cualquier cambio en los datos afecta la raíz, facilitando la detección de manipulaciones en sistemas distribuidos como la cadena de bloques.
Datos Iniciales:
Cada bloque de datos (por ejemplo, transacciones en una cadena
de bloques) se asigna a un nodo hoja del árbol.
Para éste caso
particular, nuestros datos iniciales serán la concatenación
del N° de Serie y el Hash del Contenido Analítico
Hash de Hojas:
Se aplica una función de hashing
a cada bloque de datos inicial para crear los nodos hoja del árbol.
La función de hashing que utilizamos
se llama keccak-256, la misma utilizada por la Blockchain Ethereum para
generar sus hashes de transacción, direcciones, etc.
Combinación de Nodos: Los nodos hoja se emparejan, concatenando sus hashes y aplicando a esta concatenación la misma función de hashing. El valor resultante se almacena en un nodo "padre" de sus dos "hijos". Este proceso continúa hasta que solo queda un único hash, conocido como la "raíz de Merkle".
Raíz de Merkle: La raíz de Merkle se utiliza para representar de manera única todos los datos incluidos en el árbol. Cualquier cambio en los datos originales alteraría la raíz de Merkle.
Validación:
Para validar que un valor en particular fue utilizado para construir una raíz de Merkle,
no es necesario reconstruir el árbol entero con todos los datos iniciales.
La cantidad de nodos mínimo necesarios para realizar la validación es igual al logaritmo
en base 2 de la cantidad de datos iniciales.
Los nodos en particular necesarios son el nodo hermano del nodo con el valor en cuestión,
el hermano del nodo padre, el hermano del nodo abuelo, etc.
A continuación, una representación visual de la verificación del uso de los datos
de éste título en la generación de la Raíz de Merkle metida en la Blockchain.
Haciendo click en cada nodo del tronco central, verá como fue construído.
Datos del contrato
Código Fuente del Contrato (Solidity)
SelladorFederalTitulos.sol
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.5.2; import "./Ownable.sol"; contract SelladorFederalTitulos is Ownable { address private _owner; struct Item { uint256 timestamp; uint256 blockNumber; } event ItemAdded( bytes32 indexed itemId, uint256 timestamp, uint256 blockNumber ); mapping(bytes32 => Item) public items; constructor() public Ownable() { } function put(bytes32 key) public onlyOwner { if (items[key].timestamp == 0) { items[key] = Item(block.timestamp, block.number); emit ItemAdded(key, block.timestamp, block.number); } } function exists(bytes32 key) public view returns (bool) { return items[key].timestamp > 0; } function get(bytes32 key) public view returns (uint256 timestamp, uint256 blockNumber) { Item storage item = items[key]; return (item.timestamp, item.blockNumber); } function verify(bytes32 leafNode, bytes32[] memory proof) public view returns (bool) { bytes32 currentHash = leafNode; for (uint256 i = 0; i < proof.length; i++) { bytes32 proofElement = proof[i]; if (currentHash < proofElement) { currentHash = _hash(abi.encodePacked(currentHash, proofElement)); } else { currentHash = _hash(abi.encodePacked(proofElement, currentHash)); } } return exists(currentHash); } function _hash(bytes memory packedData) private pure returns (bytes32) { return keccak256(packedData); } }
Ownable.sol
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol) pragma solidity ^0.5.2; contract Ownable { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor() public { _transferOwnership(msg.sender); } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { _checkOwner(); _; } /** * @dev Returns the address of the current owner. */ function owner() public view returns (address) { return _owner; } /** * @dev Throws if the sender is not the owner. */ function _checkOwner() internal view { require(owner() == msg.sender, "Ownable: caller is not the owner"); } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby disabling any functionality that is only available to the owner. */ function renounceOwnership() public onlyOwner { _transferOwnership(address(0)); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); _transferOwnership(newOwner); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Internal function without access restriction. */ function _transferOwnership(address newOwner) internal { address oldOwner = _owner; _owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } }
Contract ABI
[ { "constant": true, "inputs": [ { "name": "", "type": "bytes32" } ], "name": "items", "outputs": [ { "name": "timestamp", "type": "uint256" }, { "name": "blockNumber", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [], "name": "renounceOwnership", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "owner", "outputs": [ { "name": "", "type": "address" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "newOwner", "type": "address" } ], "name": "transferOwnership", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "payable": false, "stateMutability": "nonpayable", "type": "constructor" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "itemId", "type": "bytes32" }, { "indexed": false, "name": "timestamp", "type": "uint256" }, { "indexed": false, "name": "blockNumber", "type": "uint256" } ], "name": "ItemAdded", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "previousOwner", "type": "address" }, { "indexed": true, "name": "newOwner", "type": "address" } ], "name": "OwnershipTransferred", "type": "event" }, { "constant": false, "inputs": [ { "name": "key", "type": "bytes32" } ], "name": "put", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [ { "name": "key", "type": "bytes32" } ], "name": "exists", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "key", "type": "bytes32" } ], "name": "get", "outputs": [ { "name": "timestamp", "type": "uint256" }, { "name": "blockNumber", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "leafNode", "type": "bytes32" }, { "name": "proof", "type": "bytes32[]" } ], "name": "verify", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "view", "type": "function" } ]