Comments (4)
Here's a draft of a smart contract, assuming we rely on a server which holds a private key and submits transactions to certify an address's reputation.
I think it should be improved to use EIP-712 (https://eips.ethereum.org/EIPS/eip-712), but that would just enrich the signed data, the flow would be the same.
Steps
- After successful authentication with a web 2 provider and connection with an address, users can get their reputation score from the Reputation Service
- Users can then sign an attestation which contains the following information:
chainId
to avoid replay on another chain, their address (_recipient
) and theamount
of reputation as determined from the previous step. - Our server calls
setReputation
on theReputable
contract with the signature, the user's address, and the amount: if the signature is valid, it effectively gives a balance ofamount
number of tokens to this particular address. Only our server (attestor
) can callsetReputation
.
From there on, checking if the reputation of an address 0x3e5...
is greater than X
can be performed very easily with 0 downtime:
In Solidity
require(Reputable.reputationOf(0x3e5...) > X, "Reputation is insufficient")
And with ethers
const isReputableEnough = reputable.reputationOf("0x3e5...") > X
Contracts
pragma solidity >=0.8.0;
import "./Controlled.sol";
contract Reputable is Controlled {
// name and symbol of the token
string public name;
string public symbol;
// Each address's balance represents its level of reputation
mapping(address => uint256) private _balances;
event ReputationSet(address indexed account, uint256 amount);
/**
* Constructor
* @param chainId_ makes signatures unique to a chain
* @param attestor_ is the address of the Reputation Service (backend)
*/
constructor(string memory name_, string memory symbol_, uint256 chainId_, address attestor_)
Controlled(chainId_, attestor_) {
name = name_;
symbol = symbol_;
}
function _mint(address account, uint256 amount) private {
require(account != address(0), "Can't mint to the zero address");
// overrides with new reputation level
_balances[account] = amount;
emit ReputationSet(account, amount);
}
function setReputation(address _recipient, uint256 _amount, bytes memory _signature) public
onlyAttestor()
withValidAttestation( _recipient, _amount, _signature) {
_mint(_recipient, _amount);
}
// Returns the reputation level of _account
function reputationOf(address _account) public view returns (uint256) {
return _balances[_account];
}
}
//////////////////////////////////////////////////////////////////////
pragma solidity >=0.8.0;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol";
contract Controlled {
uint256 public chainId;
address private attestor;
constructor(uint256 _chainId, address _attestor) {
chainId = _chainId;
attestor = _attestor;
}
/**
* Outputs the full "attestation hash" including chainId
*/
function _getAttestationHash(address _recipient, uint256 _amount)
private
view
returns (bytes32)
{
bytes32 hash = keccak256(
abi.encodePacked(chainId, _recipient, _amount)
);
return hash;
}
/**
* Outputs the signer of _signature
* after re-constructing the full attestation hash
*/
function _recoverSigner(
address _recipient,
uint256 _amount,
bytes memory _signature
) private view returns (address) {
bytes32 hash = _getAttestationHash(_recipient, _amount);
address signer = ECDSA.recover(hash, _signature);
return signer;
}
/**
* Verifies that the attestation was signed by _recipient
* and reverts if it's not the case
*/
modifier withValidAttestation(
address _recipient,
uint256 _amount,
bytes memory _signature
) {
require(
_recoverSigner(_recipient, _amount, _signature) == _recipient,
"Invalid signature"
);
_;
}
/**
* Restricts execution to the attestor
*/
modifier onlyAttestor() {
require(msg.sender == attestor, "Permission denied");
_;
}
}
Considerations:
- Someone whose reputation on Twitter change should be able to go through the process to have their on-chain reputation be updated.
setReputation
would be called again and the previous balance overridden for simplicity sake. - Similarly, the reputation of an address could be revoked by setting its balance to 0. For transferring reputation from address A to address B, that means 2 transactions. We can use the signed data to do that with only 1 transaction, I'll provide an updated version.
- Is
decimals
needed? I think we can stick to integers which already provide a level of flexibility : for example a 0-100 or 0-1000 scale - Instead of a mapping to balances, we can map to a boolean (reputable or not) with limited changes
- I wanted to keep this first draft simple but we should add access control - at least to update the attestor's address - pause mechanism, make it upgradable etc...
from reputation-service.
To make a record of our conversations:
- Instead of balances or a boolean, I think we need to do NTTs (non-transferable tokens) as "badges" for reputation attestations. We don't have a clear rubric for how balances would be calculated right now, and it adds more complexity than a simple token that associates an address with a Web2 account.
- When a badge is unlinked or revoked, it should be sent to the 0 address, and then our service can issue a new one.
- There should be an expiry period on a badge, so in case an address is compromised it doesn't steal that reputation forever. Maybe the InterRep service can also unilaterally revoke them? Centralizes power though.
In terms of implementation, we talked today about having a master InterRep contract that links to sub-contracts for each provider - a Twitter contract, a Github contract, etc. Should look into if this is the best approach.
from reputation-service.
Following our conversation, I remove URIs associated with tokens and the hasBadge
method. So here's below the updated version.
Regarding the generation of token ids being off-chain, one upside I was thinking of is a bit more privacy. If we have a simple counter that goes 1, 2, 3... it's easy to call ownerOf
repeatedly and get a list of addresses with the badge. That might not be a problem for NFT art pieces. But in our case, we could have very different ids among the whole spectrum of 256-bit integers. Now that would take longer to search through.
However, because we're emitting events for each time a token is minted and because they will be indexed, it will be easy to get the list of addresses that got the badge.
Interface
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IBadge {
/**
* @dev Emitted when `tokenId` token is minted to `to`.
* @param to The address that received the token
* @param tokenId The id of the token that was minted
* @param timestamp Block timestamp from when the token was minted
*/
event Minted(
address indexed to,
uint256 indexed tokenId,
uint256 timestamp
);
/**
* @dev Emitted when `tokenId` token is burned.
* @param owner The address that used to own the token
* @param tokenId The id of the token that was burned
* @param timestamp Block timestamp from when the token was burned
*/
event Burned(
address indexed owner,
uint256 indexed tokenId,
uint256 timestamp
);
/**
* @dev Returns the badge's name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the badge's symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the ID of the token owned by `owner`, if it owns one, and 0 otherwise
*
* Requirements:
*
* - `owner` cannot be the zero address.
*/
function tokenOf(address owner) external view returns (uint256);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address);
}
Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IBadge.sol";
contract Badge is IBadge {
// Badge's name
string private _name;
// Badge's symbol
string private _symbol;
// Mapping from token ID to owner's address
mapping(uint256 => address) private _owners;
// Mapping from owner's address to token ID
mapping(address => uint256) private _tokens;
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
// Returns the badge's name
function name() public view virtual override returns (string memory) {
return _name;
}
// Returns the badge's symbol
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
// Returns the token ID owned by `owner`, if it exists, and 0 otherwise
function tokenOf(address owner) public view virtual override returns (uint256) {
require(owner != address(0), "Invalid owner at zero address");
return _tokens[owner];
}
// Returns the owner of a given token ID, reverts if the token does not exist
function ownerOf(uint256 tokenId) public view virtual override returns (address) {
require(tokenId != 0, "Invalid tokenId value");
address owner = _owners[tokenId];
require(owner != address(0), "Invalid owner at zero address");
return owner;
}
// Checks if a token ID exists
function _exists(uint256 tokenId) internal view virtual returns (bool) {
return _owners[tokenId] != address(0);
}
/**
* @dev Mints `tokenId` and transfers it to `to`.
* Requirements:
*
* - `tokenId` must not exist.
* - `to` cannot be the zero address.
*
* Emits a {Minted} event.
*/
function _mint(address to, uint256 tokenId) internal virtual {
require(to != address(0), "Invalid owner at zero address");
require(tokenId != 0, "Token ID cannot be zero");
require(!_exists(tokenId), "Token already minted");
require(tokenOf(to) == 0, "Owner already has a token");
_tokens[to] = tokenId;
_owners[tokenId] = to;
emit Minted(to, tokenId, block.timestamp);
}
/**
* @dev Burns `tokenId`.
*
* Requirements:
*
* - `tokenId` must exist.
*
* Emits a {Burned} event.
*/
function _burn(uint256 tokenId) internal virtual {
address owner = Badge.ownerOf(tokenId);
delete _tokens[owner];
delete _owners[tokenId];
emit Burned(owner, tokenId, block.timestamp);
}
}
TwitterBadge
Example of implementation for our case
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./Badge.sol";
import "../shared/Controlled.sol";
contract TwitterBadge is Badge, Controlled {
constructor(address backendAddress_)
Badge("TwitterBadge", "TWITT")
Controlled(backendAddress_)
{}
function mint(address to, uint256 tokenId) external {
require(msg.sender == _backendAddress, "Unauthorized");
_mint(to, tokenId);
}
function burn(uint256 tokenId) external {
require(
msg.sender == _backendAddress || msg.sender == ownerOf(tokenId),
"Unauthorized"
);
_burn(tokenId);
}
}
from reputation-service.
Closing this issue as we went for a standard ERC721 NFT. The reasons were better interoperability and allowing transfers so that the NFTs can be used as collateral.
from reputation-service.
Related Issues (20)
- Goal 1: Update documentation
- Goal 3: Enhancements & minor issues
- Integrate Semaphore V2
- Goal 5: EOD Mon, Feb 28
- Goal 6: EOD Mon, March 7th
- Goal 7: EOD Mon, March 14th
- Goal 2: Update UI & rebranding
- Update web app UI
- Create new landing page
- Feature: support goerli test network HOT 3
- Request: API_WHITELIST additions HOT 3
- ZK-Keeper integration HOT 2
- Upgrade Manifest to V3
- Set different group size (i.e. tree depth) for each group
- Request: WalletConnect HOT 1
- UX issue HOT 1
- Deploy to sepolia
- Add API to get a leaf index
- Provide identity commitments with a expiry date
- Deploy apps on AWS
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from reputation-service.