EIP1616 - Attribute Registry Standard
# Simple Summary
EIP-1616 provides a basic interface for querying a registry for attribute metadata assigned to Ethereum accounts.
# Abstract
This EIP contains the following core ideas:
- Instead of relying directly on the reputation of a claims issuer to assess the veracity of a given claim, trust can be brought up to the level of a registry curator. This registry which we call an "Attribute Registry" allows for reduced complexity in implementation since a party needing to verify an attribute can now work with a trusted claims aggregator instead of relying on individual claim providers.
- Claims are abstracted as standard "attributes" which represent metadata assigned to an account, with claims decoupled from the issuing party. Attributes are registered as a flat
uint256 -> uint256
key-value pair on each account, with the important property that each attribute type has one canonical value per address. This property allows for composability of attribute registries and advanced attribute formation. - There is a generic method for determining the set of attribute keys or IDs made available by the registry. The standard does not specify requirements or recommendations for how attributes and their values are managed, or what additional metadata may be associated with attributes. It is likely that a standard set of attribute names and metadata schema could be proposed in a separate EIP.
Potential advanced uses of attribute registries include:
- Encoding complex boolean expressions which combine multiple attributes into a single uint256 key, which is then parsed and evaluated by the registry logic.
- Using values associated with an attribute to query additional on-chain or off-chain metadata.
- Resolving attribute values by calling into separate attribute registries or other contracts, delegating authority without changing the interface of the registry.
# Motivation
This EIP is motivated by the need for contracts and external accounts to be able to verify information about a given address from a single trusted source without concerning themselves with the particular details of how the information was obtained, and to do so in as simple a manner as possible. It is also motivated by the desire to promote broad cross-compatibility and composability between attribute registries, a property which is amplified by both the simplicity of the interface as well as by the guarantees on uniqueness provided by the proposed standard.
Existing EIPs for assigning metadata to an account include EIP-735 and EIP-780, which both allow for multiple claims to be issued on the same address for any given claim topic. This forces verifiers of said metadata to assess the veracity of each claim, taking into account the relative reputation of each claim issuer. It also prescribes a methodology for adding and removing claims, which may not be appropriate for all use cases.
This EIP proposes a light-weight abstraction layer for a standard account metadata registry interface. This abstraction layer can sit on top of claims registries like EIP-735 and EIP-780 or others as the attribute registry curator selects trusted data sources.
# Specification
The Attribute Registry interface contains four functions, outlined as follows:
/**
* @title EIP-1616 Attribute Registry Standard interface. EIP-165 ID: 0x5f46473f
*/
interface AttributeRegistryInterface {
function hasAttribute(address account, uint256 attributeTypeID) external view returns (bool);
function getAttributeValue(address account, uint256 attributeTypeID) external view returns (uint256);
function countAttributeTypes() external view returns (uint256);
function getAttributeTypeID(uint256 index) external view returns (uint256);
}
2
3
4
5
6
7
8
9
Contracts that comply with the Attribute Registry EIP MUST implement the above interface.
As an additional requirement, the ERC-165 interface MUST be included:
/**
* @title EIP-165 interface. EIP-165 ID: 0x01ffc9a7
*/
interface EIP-165 {
/**
* @notice EIP-165 support. Attribute Registry interface ID is 0x5f46473f.
* @param _interfaceID The interface identifier, as specified in EIP-165
* @return True for 0x01ffc9a7 & 0x5f46473f, false for unsupported interfaces.
*/
function supportsInterface(bytes4 _interfaceID) external view returns (bool);
}
2
3
4
5
6
7
8
9
10
11
The implementation MUST follow the specifications described below.
# View Functions
The view functions detailed below MUST be implemented.
# hasAttribute
function
function hasAttribute(address account, uint256 attributeTypeID) external view returns (bool)
Check if an attribute has been assigned to a given account on the registry and is currently valid.
NOTE: This function MUST return either true or false - i.e. calling this function MUST NOT cause the caller to revert. Implementations that wish to call into another contract during execution of this function MUST catch any revert
and instead return false
.
NOTE: This function MUST return two equal values when performing two directly consecutive function calls with identical account
and attributeTypeID
parameters, regardless of differences in the caller's address, the transaction origin, or other out-of-band information.
# getAttributeValue
function
function getAttributeValue(address account, uint256 attributeTypeID) external view returns (uint256)
Retrieve the uint256
value of an attribute on a given account on the registry, assuming the attribute is currently valid.
NOTE: This function MUST revert if a directly preceding or subsequent function call to hasAttribute
with identical account
and attributeTypeID
parameters would return false.
NOTE: This function MUST return two equal values when performing two directly consecutive function calls with identical account
and attributeTypeID
parameters, regardless of differences in the caller's address, the transaction origin, or other out-of-band information.
# countAttributeTypes
function
function countAttributeTypes() external view returns (uint256)
Retrieve the total number of valid attribute types defined on the registry. Used alongside getAttributeTypeID
to determine all of the attribute types that are available on the registry.
NOTE: This function MUST return a positive integer value - i.e. calling this function MUST NOT cause the caller to revert.
NOTE: This function MUST return a value that encompasses all indexes of attribute type IDs whereby a call to hasAttribute
on some address with an attribute type ID at the given index would return true
.
# getAttributeTypeID
function
function getAttributeTypeID(uint256 index) external view returns (uint256)
Retrieve an ID of an attribute type defined on the registry by index. Used alongside countAttributeTypes
to determine all of the attribute types that are available on the registry.
NOTE: This function MUST revert if the provided index
value falls outside of the range of the value returned from a directly preceding or subsequent function call to countAttributeTypes
. It MUST NOT revert if the provided index
value falls inside said range.
NOTE: This function MUST return an attributeTypeID
value on some index if the same attributeTypeID
value would cause a given call to hasAttribute
to return true
when passed as a parameter.
# Rationale
This standard extends the applicability of metadata assignment to those use cases that are not adequately represented by EIP-735, EIP-780, or similar proposals. Namely, it enforces the constraint of one attribute value per attribute ID per address, as opposed to one value per ID per address per issuer.
Aside from the prescribed attribute value, attribute properties are deliberately omitted from the standard. While many attribute registries will require additional metadata on attributes at both the instance and the class level, reliable and flexible interoperability between highly variable registry extensions is facilitated more effectively by enforcing a widely-applicable base layer for attributes.
# Backwards Compatibility
There are no backwards compatibility concerns.
# Test Cases
Targeted test cases with 100% code coverage can be found at this repository (opens new window). See here (opens new window) for tests on a more complex contract that implements the application registry interface.
# Implementation
The basic implementation that follows can be found at this repository (opens new window) (see here (opens new window) for an example of a more complex implementing contract):
pragma solidity ^0.4.25;
/**
* @title Attribute Registry interface. EIP-165 ID: 0x5f46473f
*/
interface AttributeRegistryInterface {
/**
* @notice Check if an attribute of the type with ID `attributeTypeID` has
* been assigned to the account at `account` and is currently valid.
* @param account address The account to check for a valid attribute.
* @param attributeTypeID uint256 The ID of the attribute type to check for.
* @return True if the attribute is assigned and valid, false otherwise.
* @dev This function MUST return either true or false - i.e. calling this
* function MUST NOT cause the caller to revert.
*/
function hasAttribute(
address account,
uint256 attributeTypeID
) external view returns (bool);
/**
* @notice Retrieve the value of the attribute of the type with ID
* `attributeTypeID` on the account at `account`, assuming it is valid.
* @param account address The account to check for the given attribute value.
* @param attributeTypeID uint256 The ID of the attribute type to check for.
* @return The attribute value if the attribute is valid, reverts otherwise.
* @dev This function MUST revert if a directly preceding or subsequent
* function call to `hasAttribute` with identical `account` and
* `attributeTypeID` parameters would return false.
*/
function getAttributeValue(
address account,
uint256 attributeTypeID
) external view returns (uint256);
/**
* @notice Count the number of attribute types defined by the registry.
* @return The number of available attribute types.
* @dev This function MUST return a positive integer value - i.e. calling
* this function MUST NOT cause the caller to revert.
*/
function countAttributeTypes() external view returns (uint256);
/**
* @notice Get the ID of the attribute type at index `index`.
* @param index uint256 The index of the attribute type in question.
* @return The ID of the attribute type.
* @dev This function MUST revert if the provided `index` value falls outside
* of the range of the value returned from a directly preceding or subsequent
* function call to `countAttributeTypes`. It MUST NOT revert if the provided
* `index` value falls inside said range.
*/
function getAttributeTypeID(uint256 index) external view returns (uint256);
}
/**
* @title A simple example of an Attribute Registry implementation.
*/
contract AttributeRegistry is AttributeRegistryInterface {
// This particular implementation just defines two attribute types.
enum Affiliation { Whitehat, Blackhat }
// Top-level information about attribute types held in a static array.
uint256[2] private _attributeTypeIDs;
// The number of attributes currently issued tracked in a static array.
uint256[2] private _issuedAttributeCounters;
// Issued attributes held in a nested mapping by account & attribute type.
mapping(address => mapping(uint256 => bool)) private _issuedAttributes;
// Issued attribute values held in a nested mapping by account & type.
mapping(address => mapping(uint256 => uint256)) private _issuedAttributeValues;
/**
* @notice The constructor function, defines the two attribute types available
* on this particular registry.
*/
constructor() public {
// Set the attribute type IDs for whitehats (8008) and blackhats (1337).
_attributeTypeIDs = [8008, 1337];
}
/**
* @notice Assign a "whitehat" attribute type to `msg.sender`.
* @dev The function may not be called by accounts with a "blackhat" attribute
* type already assigned. This function is arbitrary and not part of the
* Attribute Registry specification.
*/
function joinWhitehats() external {
// Get the index of the blackhat attribute type on the attribute registry.
uint256 blackhatIndex = uint256(Affiliation.Blackhat);
// Get the attribute type ID of the blackhat attribute type.
uint256 blackhatAttributeTypeID = _attributeTypeIDs[blackhatIndex];
// Do not allow the whitehat attribute to be set if blackhat is already set.
require(
!_issuedAttributes[msg.sender][blackhatAttributeTypeID],
"no blackhats allowed!"
);
// Get the index of the whitehat attribute type on the attribute registry.
uint256 whitehatIndex = uint256(Affiliation.Whitehat);
// Get the attribute type ID of the whitehat attribute type.
uint256 whitehatAttributeTypeID = _attributeTypeIDs[whitehatIndex];
// Mark the attribute as issued on the given address.
_issuedAttributes[msg.sender][whitehatAttributeTypeID] = true;
// Calculate the new number of total whitehat attributes.
uint256 incrementCounter = _issuedAttributeCounters[whitehatIndex] + 1;
// Set the attribute value to the new total assigned whitehat attributes.
_issuedAttributeValues[msg.sender][whitehatAttributeTypeID] = incrementCounter;
// Update the value of the counter for total whitehat attributes.
_issuedAttributeCounters[whitehatIndex] = incrementCounter;
}
/**
* @notice Assign a "blackhat" attribute type to `msg.sender`.
* @dev The function may be called by any account, but assigned "whitehat"
* attributes will be removed. This function is arbitrary and not part of the
* Attribute Registry specification.
*/
function joinBlackhats() external {
// Get the index of the blackhat attribute type on the attribute registry.
uint256 blackhatIndex = uint256(Affiliation.Blackhat);
// Get the attribute type ID of the blackhat attribute type.
uint256 blackhatAttributeTypeID = _attributeTypeIDs[blackhatIndex];
// Mark the attribute as issued on the given address.
_issuedAttributes[msg.sender][blackhatAttributeTypeID] = true;
// Calculate the new number of total blackhat attributes.
uint256 incrementCounter = _issuedAttributeCounters[blackhatIndex] + 1;
// Set the attribute value to the new total assigned blackhat attributes.
_issuedAttributeValues[msg.sender][blackhatAttributeTypeID] = incrementCounter;
// Update the value of the counter for total blackhat attributes.
_issuedAttributeCounters[blackhatIndex] = incrementCounter;
// Get the index of the whitehat attribute type on the attribute registry.
uint256 whitehatIndex = uint256(Affiliation.Whitehat);
// Get the attribute type ID of the whitehat attribute type.
uint256 whitehatAttributeTypeID = _attributeTypeIDs[whitehatIndex];
// Determine if a whitehat attribute type has been assigned.
if (_issuedAttributes[msg.sender][whitehatAttributeTypeID]) {
// If so, delete the attribute.
delete _issuedAttributes[msg.sender][whitehatAttributeTypeID];
// Delete the attribute value as well.
delete _issuedAttributeValues[msg.sender][whitehatAttributeTypeID];
// Set the attribute value to the new total assigned whitehat attributes.
uint256 decrementCounter = _issuedAttributeCounters[whitehatIndex] - 1;
// Update the value of the counter for total whitehat attributes.
_issuedAttributeCounters[whitehatIndex] = decrementCounter;
}
}
/**
* @notice Get the total number of assigned whitehat and blackhat attributes.
* @return Array with counts of assigned whitehat and blackhat attributes.
* @dev This function is arbitrary and not part of the Attribute Registry
* specification.
*/
function totalHats() external view returns (uint256[2]) {
// Return the array containing counter values.
return _issuedAttributeCounters;
}
/**
* @notice Check if an attribute of the type with ID `attributeTypeID` has
* been assigned to the account at `account` and is currently valid.
* @param account address The account to check for a valid attribute.
* @param attributeTypeID uint256 The ID of the attribute type to check for.
* @return True if the attribute is assigned and valid, false otherwise.
* @dev This function MUST return either true or false - i.e. calling this
* function MUST NOT cause the caller to revert.
*/
function hasAttribute(
address account,
uint256 attributeTypeID
) external view returns (bool) {
// Return assignment status of attribute by account and attribute type ID
return _issuedAttributes[account][attributeTypeID];
}
/**
* @notice Retrieve the value of the attribute of the type with ID
* `attributeTypeID` on the account at `account`, assuming it is valid.
* @param account address The account to check for the given attribute value.
* @param attributeTypeID uint256 The ID of the attribute type to check for.
* @return The attribute value if the attribute is valid, reverts otherwise.
* @dev This function MUST revert if a directly preceding or subsequent
* function call to `hasAttribute` with identical `account` and
* `attributeTypeID` parameters would return false.
*/
function getAttributeValue(
address account,
uint256 attributeTypeID
) external view returns (uint256 value) {
// Revert if attribute with given account & attribute type ID is unassigned
require(
_issuedAttributes[account][attributeTypeID],
"could not find a value with the provided account and attribute type ID"
);
// Return the attribute value.
return _issuedAttributeValues[account][attributeTypeID];
}
/**
* @notice Count the number of attribute types defined by the registry.
* @return The number of available attribute types.
* @dev This function MUST return a positive integer value - i.e. calling
* this function MUST NOT cause the caller to revert.
*/
function countAttributeTypes() external view returns (uint256) {
// Return the length of the attribute type IDs array.
return _attributeTypeIDs.length;
}
/**
* @notice Get the ID of the attribute type at index `index`.
* @param index uint256 The index of the attribute type in question.
* @return The ID of the attribute type.
* @dev This function MUST revert if the provided `index` value falls outside
* of the range of the value returned from a directly preceding or subsequent
* function call to `countAttributeTypes`. It MUST NOT revert if the provided
* `index` value falls inside said range.
*/
function getAttributeTypeID(uint256 index) external view returns (uint256) {
// Revert if the provided index is out of range.
require(
index < _attributeTypeIDs.length,
"provided index is outside of the range of defined attribute type IDs"
);
// Return the attribute type ID at the given index in the array.
return _attributeTypeIDs[index];
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# Copyright
Copyright and related rights waived via CC0 (opens new window).