react-native-matrix-crypto
    Preparing search index...

    Function bootstrapCrossSigning

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

      Returns Promise<void>