URIs are a convenient method for endpoints to convey connectivity information in any out-of-band medium. This section defines a process for using "link:" URIs (scheme yet to be registered) or other, app-specific URIs to automatically detect associated keys and paths, as well as standard practices for embedding that information in any generated or existing URIs.
Once a URI is successfully resolved and a link is established to an endpoint, the key and path information should be stored and used in place of the URI since a successful resolution only needs to be performed once.
New URIs that are generated by applications for the sole purpose of establishing a new link take the minimal form of: link://host:port/?csid=base32
link://
- defaults tolink
but may be any app-specific name for registering custom URL handlershost:port
- thehost
is often an IP address, and the port defaults to42424
if not includedcs??=base32
- (optional) a key/value pair for each of the sender's public keys in base 32
A generated URI will often include additional pathname or query string values as needed by the application.
Examples:
chat://127.0.0.1:55772/?cs1a=aof7...supy
A common deployment architecture includes a designated stable router that facilitates the connection process with peers. The router can generate an authorative base URI for peers to re-use and advertise it to them in a path request. This allows a peer to be reachable via a URI but still remain private and not share any of its identity information (hashname, keys, or paths).
The router may include its own keys in the query string but may not attach a #fragment
so that a peer can use the fragment part to include additional data before distributing the URI. The router must always include a value in the base URI to validate the request and internally map it to the peer it was generated for; it should never embed or expose the hashname or other specific details about the peer in the base URI.
The peer endpoint must generate a fragment value that it can use to validate incoming requests and the recipient can use to verify the peer's hashname. This fragment has two parts separated by a .
, both are base32 encoded byte strings. The first part is always an 8 byte SipHash digest of all of the second part, using the first 16 bytes of the peer's hashname as the key input to the digest (identical to the chat channel). The variable bytes remaining in the fragment must be generated by the peer such that they are unique for every URI it shares.
The connecting party might not have a complete fragment, and it only requires the first check digest before the .
separator. When the URI is resolved the peer must always respond with the full fragment in URI handshake and the connecting party must verify that the hashname is the correct key to generate the digest in the fragment of the complete second part. This ensures that only that peer can correctly link from a specific URI and that the router cannot redirect to another party.
Example URI that uses a router as the base and includes the peer fragment (where X
, Y
, and Z
are base32 values):
link://127.0.0.1/?sid=42&cs1a=X#Y.Z
When a URI is processed that contains a fragment it generates a new peer request to the router that includes the "uri":"..."
(without the fragment) for the router to validate and resolve to the right peer. The peer request must still also include a full URI handshake to be forwarded directly to the peer for its own validation.
When processing a new URI, the steps are:
- detect included keys in the query string and derive hashname
- [optional] fallback to discover keys via WebFinger
- fallback to resolve the canonical hostname to discover keys via DNS SRV
- fallback to discover keys via OpenID Connect Discovery
- generate paths for all supported transports with any resolved IP and port
- create a link with the keys and path(s) including a URI handshake
- if there's a fragment hashname, issue a
peer
request over the link to it including a URI handshake
- process any response URI handshake with a validating fragment as the final resolution
The CSKs
for an endpoint may be included in the query string of any existing URI.
Each CSK
is included as an individual key/value pair where the key is the CSID
in the format cs??
(cs1a
, cs2a
, etc) and the value is always the base32 encoded key bytes.
The current paths may also be included in the query string of any existing URI. Each available path has its JSON object base32 encoded as the value and is included with a common paths
key, multiple paths have the same key.
When the paths match the ones generated from the hostname in the URI it is not necessary to include them in the query string.
Example paths:
[
{
"url": "http://192.168.0.36:42424",
"type": "http"
},
{
"ip": "192.168.0.36",
"port": 42424,
"type": "udp4"
},
{
"ip": "fe80::bae8:56ff:fe43:3de4",
"port": 42424,
"type": "tcp6"
}
]
URL: proto://host/path?
key=value
&paths=pmrh...yce7i
&paths=pmrg...qrh2
&paths=pmrg...rcpu
The canonical
hostname (or IP and port) of a URI or the resolved SRV port
and IP
should be treated as a potential path
for all available transports for an endpoint. Handshakes should be sent to the given address in every transport supported that can use an IP and port, including UDP, TCP, TLS, and HTTP(S).
When an endpoint's keys cannot be included directly in the URI they may be discovered via automated techniques from other parts of the URI.
SRV records always resolve to a hashname-prefixed host, with TXT records returning all of the keys.
_link._udp.example.com. 86400 IN SRV 0 5 42424 uvab...hv7g.example.com.
uvab...hv7g IN A 1.2.3.4
uvab...hv7g IN TXT "1a=base32"
uvab...hv7g IN TXT "2a=base32"
uvab...hv7g IN TXT "2a2=base32"
uvab...hv7g IN TXT "3a=base32"
If a key's base32 encoding is larger than 250 characters (TXT limit), it is broken into multiple TXT records with the CSID
being numerically increased so that it can be consistently reassembled.
No other DNS record type is supported, only SRV records resulting in one or more A and TXT records.
Use WebFinger against the canonical hostname, passing the given URI in as the resource
and a rel
value of http://telehash.org/link
. If successful, it will result in a valid href
that must return the standard JSON link description format.
GET https://example.com/.well-known/webfinger?
resource=http://example.com/~user1
&ref=http://telehash.org/link
{
"subject": "http://example.com/~user1",
"links" : [
{
"rel" : "http://telehash.org/link",
"href" : "https://www.example.com/~user1/link.json"
}
]
}
GET https://example.com/~user1/link.json
{
"keys":{...},
"paths":[...]
}
One or more hashnames may be advertised as part of OpenID Connect Discovery by simply including their JWK in the jwks_uri
response. If there are multiple keys in the response only the first one in the array should be used to resolve the URI.
The discovery endpoint may also be used as a default authority to validate against for incoming unknown hashnames before responding to them.
## URI HandshakeWhen a URI is the source of a new link, a "type":"uri"
handshake should be sent including the original URI.
Example:
{
"type":"uri",
"uri":"https://example.com/link?ref=42#u8kb..."
}