Last active
May 12, 2024 19:26
-
-
Save homakov/19a4f9b65a25614406cf571bcbb98685 to your computer and use it in GitHub Desktop.
entity.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.8.19; | |
contract EntityProvider { | |
struct Entity { | |
address tokenAddress; | |
string name; | |
bytes32 currentAuthenticatorHash; | |
bytes32 proposedAuthenticatorHash; | |
} | |
struct Delegate { | |
bytes entityId; | |
uint16 votingPower; | |
} | |
struct Authenticator { | |
uint16 votingThreshold; | |
Delegate[] delegates; | |
} | |
Entity[] public entities; | |
mapping (bytes32 => Entity) public entityMap; | |
/** | |
* @notice Verifies the entity signed _hash, | |
returns uint16: 0 when invalid, or ratio of Yes to Yes+No. | |
*/ | |
function isValidSignature( | |
bytes32 _hash, | |
bytes calldata entityParams, | |
bytes calldata encodedEntityAuthenticator, | |
bytes calldata encodedSignature, | |
bytes32[] calldata entityStack | |
) external view returns (uint16) { | |
bytes32 authenticatorHash = keccak256(encodedEntityAuthenticator); | |
if (authenticatorHash == entityParams) { | |
// uses static authenticator | |
} else { | |
// uses dynamic authenticator | |
require(authenticatorHash == entities[uint(entityParams)].currentAuthenticatorHash); | |
} | |
Authenticator memory authenticator = abi.decode(encodedEntityAuthenticator, (Authenticator)); | |
bytes[] memory signatures = abi.decode(encodedSignature, (bytes[])); | |
uint16 voteYes = 0; | |
uint16 voteNo = 0; | |
for (uint i = 0; i < authenticator.delegates.length; i += 1) { | |
Delegate memory delegate = authenticator.delegates[i]; | |
if (delegate.entityId.length == 20) { | |
// EOA address | |
if (address(delegate.entityId[0, 20]) == recoverSigner(_hash, signatures[i])) { | |
voteYes += delegate.votingPower; | |
} else { | |
voteNo += delegate.votingPower; | |
} | |
} else { | |
// if entityId already exists in stack - recursive, add it to voteYes | |
bool recursive = false; | |
bytes32 delegateHash = keccak256(delegate.entityId); | |
for (uint i2 = 0; i2 < entityStack.length; i2 += 1) { | |
if (entityStack[i2] == delegateHash) { | |
recursive = true; | |
break; | |
} | |
} | |
if (recursive) { | |
voteYes += delegate.votingPower; | |
continue; | |
} | |
(address externalEntityProvider, bytes memory externalEntityId) = abi.decode(delegate.entityId, (address, bytes)); | |
// decode nested signatures | |
(bytes memory nestedAuthenticator, bytes memory nestedSignature) = abi.decode(signatures[i], (bytes, bytes) ); | |
if (EntityProvider(externalEntityProvider).isValidSignature( | |
_hash, | |
externalEntityId, | |
nestedAuthenticator, | |
nestedSignature | |
) > uint16(0)) { | |
voteYes += delegate.votingPower; | |
} else { | |
voteNo += delegate.votingPower; | |
} | |
// | |
} | |
// check if address is in authenticator | |
} | |
uint16 votingResult = voteYes / (voteYes + voteNo); | |
if (votingResult < authenticator.votingThreshold) { | |
return 0; | |
} else { | |
return votingResult; | |
} | |
} | |
function proposeAuthenticator(bytes entityId, bytes proposedAuthenticator, bytes[] tokenHolders, bytes[] signatures) { | |
for (uint i = 0; i < tokenHolders.length; i += 1) { | |
require( | |
Token(bytesToAddress(tokenHolders[i])).balanceOf(bytesToAddress(entityId)) > 0, | |
"EntityProvider#proposeAuthenticator: token holder does not own any tokens" | |
); | |
require( | |
Token(bytesToAddress(tokenHolders[i])).isValidSignature( | |
keccak256(proposedAuthenticator), | |
signatures[i] | |
), | |
"EntityProvider#proposeAuthenticator: token holder did not sign the proposed authenticator" | |
); | |
} | |
entities[uint(entityId)].proposedAuthenticatorHash = keccak256(proposedAuthenticator); | |
} | |
function activateAuthenticator(bytes entityId) { | |
activateAtBlock[uint(entityId)] = block.number; | |
entities[uint(entityId)].currentAuthenticatorHash = entities[uint(entityId)].proposedAuthenticatorHash; | |
} | |
function bytesToAddress(bytes bys) private pure returns (address addr) { | |
assembly { | |
addr := mload(add(bys,20)) | |
} | |
} | |
/** | |
* @notice Recover the signer of hash, assuming it's an EOA account | |
* @dev Only for EthSign signatures | |
* @param _hash Hash of message that was signed | |
* @param _signature Signature encoded as (bytes32 r, bytes32 s, uint8 v) | |
*/ | |
function recoverSigner( | |
bytes32 _hash, | |
bytes memory _signature | |
) internal pure returns (address signer) { | |
require(_signature.length == 65, "SignatureValidator#recoverSigner: invalid signature length"); | |
// Variables are not scoped in Solidity. | |
uint8 v = uint8(_signature[64]); | |
bytes32 r = _signature.readBytes32(0); | |
bytes32 s = _signature.readBytes32(32); | |
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature | |
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines | |
// the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most | |
// signatures from current libraries generate a unique signature with an s-value in the lower half order. | |
// | |
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value | |
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or | |
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept | |
// these malleable signatures as well. | |
// | |
// Source OpenZeppelin | |
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/cryptography/ECDSA.sol | |
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { | |
revert("SignatureValidator#recoverSigner: invalid signature 's' value"); | |
} | |
if (v != 27 && v != 28) { | |
revert("SignatureValidator#recoverSigner: invalid signature 'v' value"); | |
} | |
// Recover ECDSA signer | |
signer = ecrecover(_hash, v, r, s); | |
// Prevent signer from being 0x0 | |
require( | |
signer != address(0x0), | |
"SignatureValidator#recoverSigner: INVALID_SIGNER" | |
); | |
return signer; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment