Skip to content

Instantly share code, notes, and snippets.

@homakov
Last active May 12, 2024 19:26
Show Gist options
  • Save homakov/19a4f9b65a25614406cf571bcbb98685 to your computer and use it in GitHub Desktop.
Save homakov/19a4f9b65a25614406cf571bcbb98685 to your computer and use it in GitHub Desktop.
entity.sol
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