Publishes the cross-signing identity this device already holds.
This is the call the rest of M4 hangs off. Until an account has a signing
identity of its own, a decrypted event can never report
senderVerification.state === 'verified', however many people compare
however many strings: that value needs our user-signing key over the
sender's master key, read back out of our own store. See
SenderVerification.
Safe to call on every launch, and that is now true without
qualification: this call cannot create an identity. It used to create
the account's first one when it judged there was none, and that judgement
rests on a single /keys/query answer, which is only ever true of the
instant the server sent it. Measured against a live homeserver, with no
misbehaviour anywhere: a device asked about its own fresh account, the
server honestly answered "no identity" because at that instant there was
none, another device of the account published one in the window, and this
call then created a second identity over it. The product had done nothing
wrong. It had called the function this library tells it to call on every
launch.
Creating is now createCrossSigningIdentity, and a product reaches
it only by deciding to. If you are upgrading, this call starts refusing
with 'identity_not_known' on an account that has no identity yet, and
that refusal is where the decision now lives.
The first call in a process is still normally refused once, with the key
query that lifts the refusal already queued by the refusal itself.
Nothing here reaches the network
This library performs no request, here or anywhere. On success, drain
takeOutgoingRequests and send what it hands back in the order it
hands it back, reporting each with markRequestSent. The order
matters here more than anywhere else on this surface, because a signature
may reference a key that is not published yet: device keys, then
'signing_keys_upload', then 'signature_upload'.
Four of the batch's entries come from this call, and the batch is
longer than four. Do not assert a length. Observed after a served
bootstrap on a fresh machine: ['keys_upload', 'signing_keys_upload', 'signature_upload', 'keys_upload', 'keys_query']. The second
'keys_upload' carries the same device keys under a different id and is
harmless to send twice; the endpoint is idempotent.
The part your product has to write, and why this call cannot
The 'signing_keys_upload' request needs user-interactive
authentication. Expect the first attempt to be refused with a 401
carrying a challenge, merge an auth object into body, and send the
same body again. body is opaque JSON this library never interprets, so
adding a field to it is an ordinary edit.
There is deliberately no auth parameter on this function, and there
will not be one. The challenge is only known after the first request
is refused, so an argument here would have to be guessed before the
server has said what it wants. This library has never touched an account
credential and this is where that property would have gone if it were
going to. The cost is real and is named rather than hidden: a product
cannot complete this step without implementing an authentication flow
this library gives it no help with.
The id survives any number of refused attempts.markRequestSent
removes an entry only on success, so loop on the 401 for as long as your
user needs. What retires the id is calling this function again and
draining again, because a second bootstrap re-derives the same three keys
and supersedes the pending publication: the held id then reports
'unknown_request', and the recovery is to drain again and use the newer
id for the identical body. If an authentication loop is in flight, do not
call this again until it finishes. See takeOutgoingRequests for
the general rule this is one case of.
Report only what a success returned
Never report a non-2xx body through markRequestSent, and that
includes the 401 challenge. Send it to markRequestFailed, or
report nothing at all, and report the eventual success through
markRequestSent. This matters more here than anywhere else on the
surface, in two different ways. A failed 'keys_query' reported as a
success is read as "the server answered and this account has no identity",
which is the one fact that authorises creating one over whatever the
account already had. And the signing-keys upload's success response is
{}, so a reported challenge would mark an identity published that never
was.
Refusals
'account_keys_not_fetched' means this process has not yet asked the
server about this account, so it cannot know whether publishing would
destroy an existing identity. This call queues that key query before
returning the refusal, so the remedy is the ordinary loop: drain, send,
report sent, call this again. Holding the private keys is not an
exemption, because a store restored from a backup holds a complete
identity the server may already have replaced.
'identity_already_exists' means the answer named an identity this device
does not hold the private keys for. There is no remedy through this call
and there should not be: this device joins that identity, it does not
replace it. requestSelfVerification is the call that joins it,
and it is where a second login goes from here.
'identity_not_known' is the refusal this call gained, and it is the one
an upgrading product meets first: the server was asked and named no
identity for this account, so there is nothing here to publish. Nothing is
wrong; this is the call declining to make a decision it used to make
silently.
Do not answer it by calling createCrossSigningIdentity from the
handler that caught it. That is the shape this split exists to prevent:
it puts the destructive call back on the launch path, where an answer that
was true when the server sent it can be stale by the time it is acted on.
The decision belongs where your product knows something this library
cannot, and createCrossSigningIdentity lists what that can be.
After a join, this call starts being served again
A device that has joined holds the account's private keys, so this
republishes the identity it now holds rather than being refused, and the
'signing_keys_upload' in the batch needs the same user-interactive
authentication as the first time. "Call it on every launch" is still the
right advice, but a joined device following it meets an authentication
challenge, and a product that only expected one during setup should expect
this one too.
Publishes the cross-signing identity this device already holds.
This is the call the rest of M4 hangs off. Until an account has a signing identity of its own, a decrypted event can never report
senderVerification.state === 'verified', however many people compare however many strings: that value needs our user-signing key over the sender's master key, read back out of our own store. See SenderVerification.Safe to call on every launch, and that is now true without qualification: this call cannot create an identity. It used to create the account's first one when it judged there was none, and that judgement rests on a single
/keys/queryanswer, which is only ever true of the instant the server sent it. Measured against a live homeserver, with no misbehaviour anywhere: a device asked about its own fresh account, the server honestly answered "no identity" because at that instant there was none, another device of the account published one in the window, and this call then created a second identity over it. The product had done nothing wrong. It had called the function this library tells it to call on every launch.Creating is now createCrossSigningIdentity, and a product reaches it only by deciding to. If you are upgrading, this call starts refusing with
'identity_not_known'on an account that has no identity yet, and that refusal is where the decision now lives.The first call in a process is still normally refused once, with the key query that lifts the refusal already queued by the refusal itself.
Nothing here reaches the network
This library performs no request, here or anywhere. On success, drain takeOutgoingRequests and send what it hands back in the order it hands it back, reporting each with markRequestSent. The order matters here more than anywhere else on this surface, because a signature may reference a key that is not published yet: device keys, then
'signing_keys_upload', then'signature_upload'.Four of the batch's entries come from this call, and the batch is longer than four. Do not assert a length. Observed after a served bootstrap on a fresh machine:
['keys_upload', 'signing_keys_upload', 'signature_upload', 'keys_upload', 'keys_query']. The second'keys_upload'carries the same device keys under a different id and is harmless to send twice; the endpoint is idempotent.The part your product has to write, and why this call cannot
The
'signing_keys_upload'request needs user-interactive authentication. Expect the first attempt to be refused with a401carrying a challenge, merge anauthobject intobody, and send the same body again.bodyis opaque JSON this library never interprets, so adding a field to it is an ordinary edit.There is deliberately no
authparameter on this function, and there will not be one. The challenge is only known after the first request is refused, so an argument here would have to be guessed before the server has said what it wants. This library has never touched an account credential and this is where that property would have gone if it were going to. The cost is real and is named rather than hidden: a product cannot complete this step without implementing an authentication flow this library gives it no help with.The id survives any number of refused attempts. markRequestSent removes an entry only on success, so loop on the
401for as long as your user needs. What retires the id is calling this function again and draining again, because a second bootstrap re-derives the same three keys and supersedes the pending publication: the held id then reports'unknown_request', and the recovery is to drain again and use the newer id for the identical body. If an authentication loop is in flight, do not call this again until it finishes. See takeOutgoingRequests for the general rule this is one case of.Report only what a success returned
Never report a non-2xx body through markRequestSent, and that includes the
401challenge. Send it to markRequestFailed, or report nothing at all, and report the eventual success throughmarkRequestSent. This matters more here than anywhere else on the surface, in two different ways. A failed'keys_query'reported as a success is read as "the server answered and this account has no identity", which is the one fact that authorises creating one over whatever the account already had. And the signing-keys upload's success response is{}, so a reported challenge would mark an identity published that never was.Refusals
'account_keys_not_fetched'means this process has not yet asked the server about this account, so it cannot know whether publishing would destroy an existing identity. This call queues that key query before returning the refusal, so the remedy is the ordinary loop: drain, send, report sent, call this again. Holding the private keys is not an exemption, because a store restored from a backup holds a complete identity the server may already have replaced.'identity_already_exists'means the answer named an identity this device does not hold the private keys for. There is no remedy through this call and there should not be: this device joins that identity, it does not replace it. requestSelfVerification is the call that joins it, and it is where a second login goes from here.'identity_not_known'is the refusal this call gained, and it is the one an upgrading product meets first: the server was asked and named no identity for this account, so there is nothing here to publish. Nothing is wrong; this is the call declining to make a decision it used to make silently.Do not answer it by calling createCrossSigningIdentity from the handler that caught it. That is the shape this split exists to prevent: it puts the destructive call back on the launch path, where an answer that was true when the server sent it can be stale by the time it is acted on. The decision belongs where your product knows something this library cannot, and createCrossSigningIdentity lists what that can be.
After a join, this call starts being served again
A device that has joined holds the account's private keys, so this republishes the identity it now holds rather than being refused, and the
'signing_keys_upload'in the batch needs the same user-interactive authentication as the first time. "Call it on every launch" is still the right advice, but a joined device following it meets an authentication challenge, and a product that only expected one during setup should expect this one too.