// SPDX-License-Identifier: MIT // OpenZeppelin Tron Contracts (last updated v5.5.0) (utils/cryptography/signers/draft-TRC7739.sol) pragma solidity ^0.8.24; import {AbstractSigner} from "@openzeppelin/tron-contracts/utils/cryptography/signers/AbstractSigner.sol"; import {TIP712Upgradeable} from "../TIP712Upgradeable.sol"; import {TRC7739Utils} from "@openzeppelin/tron-contracts/utils/cryptography/draft-TRC7739Utils.sol"; import {ITRC1271} from "@openzeppelin/tron-contracts/interfaces/ITRC1271.sol"; import {MessageHashUtils} from "@openzeppelin/tron-contracts/utils/cryptography/MessageHashUtils.sol"; import {Initializable} from "@openzeppelin/tron-contracts/proxy/utils/Initializable.sol"; /** * @dev Validates signatures wrapping the message hash in a nested TIP712 type. See {TRC7739Utils}. * * Linking the signature to the TIP-712 domain separator is a security measure to prevent signature replay across different * TIP-712 domains (e.g. a single offchain owner of multiple contracts). * * This contract requires implementing the {_rawSignatureValidation} function, which passes the wrapped message hash, * which may be either an typed data or a personal sign nested type. * * NOTE: Validating a nested typed-data signature reads the TIP-712 domain fields through {eip712Domain}. In the * constructor-based xref:api:utils/cryptography#TIP712[TIP712], `name` and `version` are held as * xref:api:utils/cryptography#ShortStrings[ShortStrings] when they fit in 31 bytes and in a storage fallback * otherwise, so longer values make that read more expensive. The upgradeable variant always reads both from storage. */ abstract contract TRC7739Upgradeable is Initializable, AbstractSigner, TIP712Upgradeable, ITRC1271 { using TRC7739Utils for *; using MessageHashUtils for bytes32; function __TRC7739_init() internal onlyInitializing {} function __TRC7739_init_unchained() internal onlyInitializing {} /** * @dev Attempts validating the signature in a nested TIP-712 type. * * A nested TIP-712 type might be presented in 2 different ways: * * - As a nested TIP-712 typed data * - As a _personal_ signature (a TIP-712 mimic of the `eth_personalSign` for a smart contract) */ function isValidSignature(bytes32 hash, bytes calldata signature) public view virtual returns (bytes4 result) { // For the hash `0x7739773977397739773977397739773977397739773977397739773977397739` and an empty signature, // we return the magic value `0x77390001` as it's assumed impossible to find a preimage for it that can be used // maliciously. Useful for simulation purposes and to validate whether the contract supports TRC-7739. return (_isValidNestedTypedDataSignature(hash, signature) || _isValidNestedPersonalSignSignature(hash, signature)) ? ITRC1271.isValidSignature.selector : (hash == 0x7739773977397739773977397739773977397739773977397739773977397739 && signature.length == 0) ? bytes4(0x77390001) : bytes4(0xffffffff); } /** * @dev Nested personal signature verification. */ function _isValidNestedPersonalSignSignature(bytes32 hash, bytes calldata signature) private view returns (bool) { return _rawSignatureValidation(_domainSeparatorV4().toTypedDataHash(hash.personalSignStructHash()), signature); } /** * @dev Nested TIP-712 typed data verification. */ function _isValidNestedTypedDataSignature( bytes32 hash, bytes calldata encodedSignature ) private view returns (bool) { // decode signature ( bytes calldata signature, bytes32 appSeparator, bytes32 contentsHash, string calldata contentsDescr ) = encodedSignature.decodeTypedDataSig(); ( , string memory name, string memory version, uint256 chainId, address verifyingContract, bytes32 salt, ) = eip712Domain(); // A malformed (but non-empty) `contentsDescr` makes `typedDataSignStructHash` return // `bytes32(0)`, which would collapse the verified digest to `appSeparator.toTypedDataHash(0)` // and drop the binding to `contentsHash` and the account-domain fields. Reject that case. bytes32 structHash = TRC7739Utils.typedDataSignStructHash( contentsDescr, contentsHash, abi.encode(keccak256(bytes(name)), keccak256(bytes(version)), chainId, verifyingContract, salt) ); // Check that contentHash and separator are correct // Rebuild nested hash return hash == appSeparator.toTypedDataHash(contentsHash) && bytes(contentsDescr).length != 0 && structHash != bytes32(0) && _rawSignatureValidation(appSeparator.toTypedDataHash(structHash), signature); } }