Skip to content
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

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions docs/rfc/ethereum_attestation.md
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.
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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 😏


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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is version supposed to be in the payload above? Or where did it come from?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or would you use claim/v1 and claim/v2 URLs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The payload is the Link-stored data, the version is used only in Ethereum. It has no corelation with the Link payload URL versioning.

Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we attest this? weh weh weh

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
212 changes: 212 additions & 0 deletions docs/rfc/identity_proofs.md
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