-
Notifications
You must be signed in to change notification settings - Fork 36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Ethereum attestation #557
Changes from all commits
fb3102b
9d40759
260f5e9
212b1e5
4be4d8b
99e7055
4f29a16
6d07495
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
# RFC: Ethereum attestation | ||
|
||
* Author: @CodeSandwich | ||
* Date: 2021-03-05 | ||
* Status: draft | ||
* Community discussion: n/a | ||
|
||
## Motivation | ||
|
||
The attestation between Link and Ethereum is a valuable building block for a user identity. | ||
It brings the Link reputation coming from projects and contributions to the | ||
Ethereum world of DAOs and donations, where it's important to know who's behind an address. | ||
On the other hand, it lends Ethereum account reputation with its assets and undeniable history | ||
to Link to build user's trust in the identity. | ||
|
||
## Overview | ||
|
||
This RFC is built on top of the [identities spec][identities]. | ||
It introduces support for Ethereum address claims on Link | ||
and a smart contract on Ethereum to make Link identity claims on Ethereum. | ||
|
||
## Link identity JSON extension | ||
|
||
The identity doc `payload` structure supports a new key: `https://radicle.xyz/ethereum/claim/v1`. | ||
It can be used only in person identities. | ||
Under this key there is stored an Ethereum address claim in this format: | ||
|
||
- `address` - the claimed Ethereum address, encoded according to [EIP-55][eip-55], | ||
e.g. using [ethers.js][ethers-addr] | ||
- `expiration` - the claim expiration timestamp, encoded as a [JavaScript Date][date] | ||
|
||
Example: | ||
```json | ||
{ | ||
"payload": { | ||
"https://radicle.xyz/ethereum/claim/v1": { | ||
"address": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", | ||
"expiration": "2021-03-19T23:15:30.000Z" | ||
} | ||
... | ||
} | ||
... | ||
} | ||
``` | ||
|
||
## Ethereum smart contract | ||
|
||
A new Ethereum smart contract is deployed to the network, | ||
which lets users claim their Link identities: | ||
|
||
```solidity | ||
contract Claims { | ||
event Claimed(address indexed addr); | ||
function claim(uint8 version, uint256 root) public; | ||
} | ||
``` | ||
|
||
To claim an identity, call `claim` using your Ethereum account and pass your Link identity root. | ||
It will emit an event `Claimed`, which later can be queried to discover your attestation. | ||
The claims have no expiration date and don't need to be renewed. | ||
|
||
Every new claim invalidates previous ones made with the same account. | ||
To only revoke a claim without creating a new one, use root `0`, | ||
which is guaranteed to not match any existing identity. | ||
|
||
Currently supported `version` values: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or would you use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The payload is the Link-stored data, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I missed it in the contract function :) |
||
- `1` - `root` is a SHA-1 root hash. The excess high bytes are zeros, e.g. for hash | ||
`fb3102b74d7254eed7f18a31a3ba1ea946bb1a99` the passed `root` is | ||
`000000000000000000000000fb3102b74d7254eed7f18a31a3ba1ea946bb1a99` | ||
- `2` - `root` is a SHA-256 root hash | ||
|
||
We need to deploy an official instance of the `Claims` smart contract and | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do we attest this? weh weh weh There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't, there can be multiple instances deployed and it won't affect the overall security. If you start using a different instance, you probably won't notice other people's claims and they won't notice yours There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mostly tongue-in-cheek :) But thanks for the clarification 😄 |
||
it must be used by all the users. | ||
If anybody makes a claim using a different instance, it won't be recognized by others. | ||
|
||
## Creation of an attestation | ||
|
||
You need to perform 2 actions in any order: | ||
- Add or update an `https://radicle.xyz/ethereum/claim/v1` entry in your identity doc. | ||
The entry's `address` must be your Ethereum address. | ||
It's highly recommended to set a short expiration date as Ethereum claims don't expire. | ||
- Call `claim` in the `Claims` smart contract. The `root` must point to your link identity. | ||
|
||
## Discovery from an Ethereum address | ||
|
||
When you know an Ethereum address, you can find the claimed link identity using an Ethereum client. | ||
The example calls are based on the standard [client JSON RPC API][rpc] and should be exposed | ||
by your favourite Ethereum client library. | ||
It's important that the client must be trusted not to hide the events. | ||
|
||
- Use [getLogs][rpc-logs] to get the newest `Claimed` event filtered for the given ethereum address | ||
- Get the event's `transactionHash` field and use it to fetch the transaction which emitted it with | ||
[getTransactionByHash][rpc-tx] | ||
- Validate that the transaction signature matches its data and the ethereum address. | ||
For reference the signature payload content is listed [here][rpc-sign]. | ||
- Read the Link identity root from the transaction data | ||
- Verify that the Link identity doc claims back the Ethereum address, | ||
see [Discovery from a Link identity](#discovery-from-a-link-identity) | ||
|
||
## Discovery from a Link identity | ||
|
||
When you know a Link identity, you can find the claimed Ethereum address. | ||
Obtain the tip of its identity chain and read the Ethereum address from the identity doc | ||
`address` field in section `https://radicle.xyz/ethereum/claim/v1`, unless it's expired. | ||
You need to verify that the given Ethereum address claims back the Link identity root, | ||
see [Discovery from an Ethereum address](#discovery-from-an-ethereum-address). | ||
|
||
## Revocation of an attestation | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be good to call out here that a revocation will only be as good as the propagation of the document in the network. What I mean is that if I revoke my address, I still have to wait for others to fetch those changes -- so could still end up transferring MONIEZ to the old address. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added |
||
|
||
When your attestation for whatever reason is no longer valid, | ||
you should revoke it as soon as possible. | ||
Only one claim needs to be revoked to break the attestation, | ||
but to improve security you should revoke both sides if you can. | ||
|
||
To revoke a claim on Link, update the identity doc and publish it in a new revision. | ||
You can change the claimed Ethereum address or remove | ||
the `https://radicle.xyz/ethereum/claim/v1` section altogether. | ||
The other Link nodes will not notice this update until they fetch the new revision. | ||
|
||
To revoke a claim on Ethereum, call the `claim` function in `Claims` contract. | ||
You can claim a different Link identity root or the `0` root to revoke any claim you may have. | ||
The other users will notice this update almost immediately if they | ||
are subscribed to the `Claimed` events for your Ethereum address in their Ethereum client. | ||
|
||
--- | ||
|
||
[identities]: ../spec/sections/002-identities/index.md | ||
[eip-55]: https://eips.ethereum.org/EIPS/eip-55 | ||
[date]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toJSON | ||
[ethers-addr]: https://docs.ethers.io/v5/api/utils/address/ | ||
[rpc]: https://eth.wiki/json-rpc/API | ||
[rpc-logs]: https://eth.wiki/json-rpc/API#eth_getlogs | ||
[rpc-tx]: https://eth.wiki/json-rpc/API#eth_gettransactionbyhash | ||
[rpc-sign]: https://eth.wiki/json-rpc/API#eth_signtransaction |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
# RFC: Identity Proofs | ||
|
||
* Author: @kim | ||
* Date: 2021-02-23 | ||
* Status: draft | ||
* Community discussion: n/a | ||
|
||
## Motivation | ||
|
||
A `radicle-link` [identity][ids] is a hash-linked sequence of signed statements | ||
about public-key delegations: if an entry in this sequence is found to conform | ||
to a quorum rule of cryptographic signatures, the set of keys it delegates to | ||
can be considered trustworthy _iff_ the previous set was. Yet, how can we trust | ||
the initial set of keys in this chain? | ||
|
||
## Overview | ||
|
||
We consider it impractical for most participants in the `radicle-link` network | ||
to exchange public keys out-of-band, given a pattern of casual interaction with | ||
others. While the protocol mandates connections between participants, similar to | ||
"following" relationships found in social media, we thus consider it | ||
insufficient to infer a [web of trust][wot] from those relationships. | ||
|
||
To lift the requirement for physical authenticity checks, but still increase | ||
confidence of a given public key being associated with a particular person, | ||
[keybase] have popularised a scheme dubbed "Social Proofs": a statement claiming | ||
ownership of a particular account on a social media site is written to the | ||
keybase "sigchain" (which has properties similar to a `radicle-link` identity). | ||
This claim (implying also the history of the sigchain) is signed using a key | ||
currently valid according to the chain, and the signature (along with the public | ||
key) is stored at the social media site. To verify the claim, the signature is | ||
retrieved from the social media site in such a way that it could _plausibly_ | ||
only be created by the owner of the claimed account. If both the sigchain | ||
integrity and the claim signature can be verified, the association is proven. | ||
|
||
This mechanism can be considered a practical application of the [Turing | ||
test][tt]: even though it can not be proven beyond doubt that the account is | ||
indeed associated with a real person, the evidence of others accepting it as | ||
such, as well as conversational behaviour, can increase the confidence in the | ||
authenticity of the online persona. Because key and account ownership at a given | ||
point in time can be cryptographically verified, this confidence can be extended | ||
to the proving side. | ||
|
||
We conclude that this mechanism would be a good fit for `radicle-link` (due to | ||
the similarities), and a desirable feature of applications built on top of it. | ||
|
||
The following sections describe how such claims shall be stored in the identity | ||
payload of a `radicle-link` identity, how to obtain a publishable proof, and how | ||
to verify it. | ||
|
||
## Claims | ||
|
||
Claims can only be made by identities of kind `Person`, and claim a single | ||
external account identifier. They are introduced by defining a new payload type, | ||
identified by the URL: | ||
|
||
https://radicle.xyz/link/claim/v1 | ||
|
||
The shape of the JSON structure is: | ||
|
||
```json | ||
{ | ||
"SERVICE": { | ||
"account": "STRING" | ||
"expiration": { | ||
"created": INTEGER, | ||
"expires": INTEGER | ||
}, | ||
"proof": "URL" | ||
} | ||
} | ||
``` | ||
|
||
Where the fields denote: | ||
|
||
* `SERVICE` | ||
|
||
A conventional identifier of the external service, e.g. "github", "twitter", | ||
"radicle-ethereum" | ||
|
||
* `account` | ||
|
||
The unique account identifier within the service, using the service-specific | ||
canonical string representation e.g. "kim", "0x32be343b94f860124dc4fee278fdcbd38c102d88". | ||
|
||
* `expiration` (optional) | ||
* `created` | ||
|
||
Creation timestamp of the claim, in seconds since 1970-01-01T00:00:00Z. | ||
|
||
* `expires` | ||
|
||
Seconds relative to `created`, after which the claim should no longer be | ||
considered. | ||
|
||
* `proof` (optional) | ||
|
||
A URL to assist verification tooling in retrieving the proof from the external | ||
system. This is mainly a convenience, and obviously requires creation of a new | ||
revision after the fact. | ||
|
||
## Proof Generation | ||
|
||
The above claim payload is committed to the identity history as a new revision. | ||
Technically, this revision needs to be approved by all key delegations for | ||
verification to pass later on, but since we assume that eligible keys are held | ||
by the same person, it may be acceptable to publish the proof right away for | ||
user experience reasons. | ||
|
||
The actual proof consists of the following tuple: | ||
|
||
(root, revision, public-key, signature) | ||
|
||
Note that the "git" protocol specifier of `radicle-link` URNs is implied, that | ||
is, future version MUST treat the absence of a disambiguating value as denoting | ||
"git". | ||
|
||
The values `root`, `revision`, and `public-key` are specified in | ||
[identities][ids], and it is RECOMMENDED to follow the serialisation formats | ||
devised there. `signature` is the Ed25519 signature over `revision`, in much the | ||
same way as the actual revision is signed. All values can thus be obtained by | ||
inspecting the identity storage. | ||
|
||
It is beyond the scope of this document to devise the exact external format to | ||
serialise the tuple into, as this is expected to vary from service to service. | ||
|
||
## Revocation | ||
|
||
A claim can be revoked by creating a new identity revision which simply does not | ||
contain the claim payload. Likewise, a later claim describing the same `SERVICE` | ||
invalidates an earlier one. | ||
|
||
## Verification | ||
|
||
Inputs: the 4-tuple as specified [above](#proof-generation), and `(SERVICE, | ||
account)` as inferred from the source it was retrieved from. | ||
|
||
1. Given the 4-tuple specified, it is first verified that the signature is valid | ||
for the given `revision` and `public-key`. | ||
|
||
2. If it is, the identity history needs to be resolved from local storage, or | ||
the network. | ||
|
||
Using `git` storage, the history tip should be located at | ||
|
||
refs/namespaces/<root>/refs/remotes/<public-key>/rad/id | ||
|
||
substituting `<root>` and `<public-key>` with their respective encodings as | ||
defined in [`Identities`][ids]. | ||
|
||
3. If the history tip could be resolved, the identity MUST be verified as per | ||
[`Identities`][ids]. If this fails, the proof is rejected. | ||
|
||
4. If the identity could be verified, the identity document is read from its | ||
latest valid tip (recall that this is not necessarily the same as what the | ||
ref points to). The proof is rejected if one of the following is true: | ||
|
||
4.1 `root` does not match | ||
|
||
4.2 the document does not contain a claim for `(SERVICE, account)` | ||
|
||
4.3 the document contains a claim for `(SERVICE, account)`, has an | ||
`expiration`, and `expiration.expires` is smaller than `time() - | ||
expiration.created` | ||
|
||
5. Lastly, the identity history is walked backwards until `revision` is found | ||
(or else, the proof is rejected). The proof is accepted _iff_ all of the | ||
following are true: | ||
|
||
5.1 the document's `delegations` at `revision` contain `public-key` | ||
|
||
5.2 the document at `revision` contains a claim for `(SERVICE, account)`, and | ||
the claim is not expired as described in 4.3 | ||
|
||
Note that steps 3.-5. can be optimised by persisting verification results, or by | ||
adding an additional accumulator to the verification fold which yields the | ||
targeted `revision`. | ||
|
||
## Discussion | ||
|
||
The inclusion of the `revision` in the proof allows to assert that `root` is | ||
indeed an ancestor, which opens up another way to detect "forks" of the identity | ||
history: due to the peer-to-peer nature of the `radicle-link` network, it is | ||
vulnerable to attacks which involve withholding data from other participants, in | ||
which case a fork may go unnoticed. | ||
|
||
It should be noted, however, that refreshing the proof from time to time in | ||
order to ensure freshness of the data retrieved through `radicle-link` is not | ||
always practicable. | ||
|
||
In order to prove that the(ir own) server is not lying by omission, Keybase | ||
[anchors a merkle root][keybase-stellar] on a blockchain, which includes all | ||
sigchains registered in the Keybase directory. Because `radicle-link` does not | ||
have such a central directory, this approach could only be applied to a partial | ||
view of the network. | ||
|
||
While conceivable that, given the right incentives, such a directory service | ||
could be operated independently (similar to what the [ceramic] network devises), | ||
it is unclear what value blockchain anchors of individual identities have, given | ||
that transaction costs discourage frequent updates. | ||
|
||
We thus RECOMMEND to explore Layer 2 solutions for blockchain anchoring. | ||
|
||
--- | ||
|
||
[ids]: ../spec/sections/002-identities/index.md | ||
[wot]: https://en.wikipedia.org/wiki/Web_of_trust | ||
[keybase]: https://book.keybase.io/account#proofs | ||
[tt]: https://en.wikipedia.org/wiki/Turing_test | ||
[keybase-stellar]: https://book.keybase.io/docs/server/stellar | ||
[ceramic]: https://github.com/ceramicnetwork/ceramic/blob/master/SPECIFICATION.md#blockchain-anchoring | ||
[radicle-contracts]: https://github.com/radicle-dev/radicle-contracts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means that the link can be revoked with only the ethereum account, right? Is that desirable? Basically, since the Link identity doesn't point to a specific attestation, it can be revoked from the ethereum side, without having the Link key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's right, if you loose your Link key, you need to be able to revoke the attestation using only your Ethereum key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The payload above has an expiration date. So this "claim" seems to be false 😏