-
Notifications
You must be signed in to change notification settings - Fork 39
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: Identity Proofs #525
base: master
Are you sure you want to change the base?
RFC: Identity Proofs #525
Changes from all commits
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,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. | ||
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. Perhaps the answer to the above question is missing here: the exact revision to be signed is the one which contains the claim, correct? Perhaps worth mentioning if that is the case. |
||
|
||
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. | ||
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. This might be a good candidate for a separate RFC with some proposed plain-text formats 💭 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. Wondering if we should do something fun instead of the usual ones. Matrix? lobste.rs? 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. hahaha yes. mastodon, ssb? |
||
|
||
## 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` | ||
cloudhead marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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.
Why after the fact?
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 almost seems like a non-feature, since it requires a new identity revision, I'm not sure if clients will deem it worthwhile to support 🤷
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.
Since there’s no central database, this would mean that you can only navigate from the external system to link, but not the other way round — which would be a bit lame I think.
Perhaps the proof signature could be over the previous revision + a random nonce 🤔 If space is constrained, maybe the preimage could be omitted entirely (except the root hash)?
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.
Yeah, true.
Signing the previous revision + account + nonce is not a bad idea 🤔 but seems like it may complicate implementations slightly due to the verification requiring two revisions as input instead of one?
I'm wondering in what scenarios the proof URL will be used? Because if there are legit use-cases for it, then it probably shouldn't be optional, and we should go with your idea.
So the typical use-case is that of sybil.org for example, where you can verify the statement: "I am @cloudhead and address 0xabcdef123 belongs to me". They must store the tweet id somewhere, since that's how they verify the signature. So yeah it seems kind of important 🤔
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.
Myeah, so:
With Nonce
R || nonce
, storeURL
R+1
containingnonce
,SERVICE: {..., proof: Some(URL) }
R+1
usingdelegations
to "finalise" itVerify
R+1
, check thatR
is parentR
has key in delegationsR+1
has claimR+1
claim is not expiredHEAD
still has the claimvs.
Without Nonce
R
containingSERVICE: { ..., proof: None }
R
, storeURL
R+1
withSERVICE: { ..., proof: Some(URL) }
R
andR+1
usingdelegations
to finaliseVerify
R
, check thatHEAD
still has the claimSo the issue with the nonce is that you need to publish the nonce, or that you can only verify the signature if you know
R+1
's payload. The issue without the nonce is that you need to sign two successive revisions for finalisation.It's when you start from
link
-- you'll want an O(1) method of getting at the proof payload.Without a nonce, it's a chicken-and-egg problem :)
sybil.org is simpler, because:
I mean, the proposed proof URL doesn't technically need to be signed, either. But if it's not in the identity document, then where else could we put it? Annotate
R
with a git-notes note?