Asks this account's other devices to verify this one, so that this
device can join the cross-signing identity the account already has.
This is what a second login does. A device that does not hold the account's
private signing keys joins the identity; it does not create one.
bootstrapCrossSigning refuses such a device with
'identity_already_exists', and that refusal is the one thing standing
between an ordinary second login and an account whose identity has been
silently replaced, resetting the trust of every device and every person who
had verified it. This call is the remedy that refusal points at, and it
is not a way around it.
It names no device, because a new login is in no position to choose
one. The invitation goes to every other device of yours that the account's
identity has signed, and whichever is in front of a person answers first;
the others are told the flow was taken. A device of yours that the identity
has never signed is not invited, which is deliberate: it is a login this
account's identity has never vouched for.
The signature at the end is made with a different key, and by the other
side. The device that already holds the private keys signs this one with
the account's self-signing key. This device has nothing to sign with yet,
which is the whole reason it is asking.
It asks for the account's secrets, which verifying somebody else never
does. Once the comparison completes, this library asks your other devices
for the cross-signing seeds it lacks. Those go out as ordinary entries in
takeOutgoingRequests' output, and the encrypted answer arrives in a
later receiveSyncChanges, which imports it.
Nothing returns to you when the seeds land
The call that started all this resolved long before. Two things tell you it
happened, and you want the first:
onCryptoSignal announces 'trust_changed' for your own user id on
the sync that carried the seeds. That is the signal to read
getIdentityStatus again. It is the same variant a completed
comparison produces, so read the status rather than counting signals;
see onCryptoSignal's own comment.
getIdentityStatus is the durable answer:
privateKeysHeld === true means this device can now sign with the
account's identity rather than only recognise it. Read it when you are
told to, not on a timer.
By a scanned code, if the product asked for one, the person points
one of their own phones at the other instead of reading symbols off both,
and this is where that is most natural, because both screens are already
in front of them. Both self modes work: the established device may show
the code and the new login read it, or the other way round, and which
happens is decided by which phone is held up rather than by anything you
pass. Showing a code needs none of the account's private signing keys,
which is what makes it reachable on the device that is joining. See
offerScannableCodes and getVerificationCode.
Including the one sync between two verifications, which for this call
means your own account: a second self-verification opened without a
receiveSyncChanges after the first comes back already cancelled.
requestVerification says why, and the reason is the layer underneath
rather than anything either call does.
Refusals
'account_keys_not_fetched' means this process has not yet asked the
server about this account, so it cannot know whether there is an identity
to join. This call queues that key query before returning the refusal,
so the remedy is the ordinary loop: drain the pump, send, report sent, and
call this again. You do not have to reach for
bootstrapCrossSigning to get unstuck, and on a device that is
joining you should not: it is the call that would create a second identity
if the state ever moved under you.
Expect this refusal on every launch, not only the first. Whether the
server has been asked is not persisted, and the layer underneath will not
volunteer the question for an account it already knows about, so a
relaunched store starts out having asked nothing.
'identity_not_known' means one of two things, and
getIdentityStatus().identityPublicationPending tells them apart. False:
the server was asked and said this account has no identity, so there is
nothing to join and no retry helps. True: this device holds an identity it
minted that no homeserver has ever asserted back, so there is nothing yet
for another device to join it to, and the flow would sign under an
identity the account may never have.
createCrossSigningIdentity is the call for both, and for the same
reason each time: creating a first identity and finishing a publication
that was interrupted are the same decision, and it is one your product
makes rather than something this handler calls. This is the same refusal,
with the same remedy, that bootstrapCrossSigning reports. The
paragraph used to name bootstrapCrossSigning as the answer, which
stopped being true when creating became its own call and left the two
surfaces saying different things about one error, and it used to give only
the first of the two meanings, which stopped being true when the gate
gained its second condition.
Asks this account's other devices to verify this one, so that this device can join the cross-signing identity the account already has.
This is what a second login does. A device that does not hold the account's private signing keys joins the identity; it does not create one. bootstrapCrossSigning refuses such a device with
'identity_already_exists', and that refusal is the one thing standing between an ordinary second login and an account whose identity has been silently replaced, resetting the trust of every device and every person who had verified it. This call is the remedy that refusal points at, and it is not a way around it.Three ways it differs from requestVerification
It names no device, because a new login is in no position to choose one. The invitation goes to every other device of yours that the account's identity has signed, and whichever is in front of a person answers first; the others are told the flow was taken. A device of yours that the identity has never signed is not invited, which is deliberate: it is a login this account's identity has never vouched for.
The signature at the end is made with a different key, and by the other side. The device that already holds the private keys signs this one with the account's self-signing key. This device has nothing to sign with yet, which is the whole reason it is asking.
It asks for the account's secrets, which verifying somebody else never does. Once the comparison completes, this library asks your other devices for the cross-signing seeds it lacks. Those go out as ordinary entries in takeOutgoingRequests' output, and the encrypted answer arrives in a later receiveSyncChanges, which imports it.
Nothing returns to you when the seeds land
The call that started all this resolved long before. Two things tell you it happened, and you want the first:
onCryptoSignalannounces'trust_changed'for your own user id on the sync that carried the seeds. That is the signal to read getIdentityStatus again. It is the same variant a completed comparison produces, so read the status rather than counting signals; seeonCryptoSignal's own comment.privateKeysHeld === truemeans this device can now sign with the account's identity rather than only recognise it. Read it when you are told to, not on a timer.Driving the flow
Identical to requestVerification from the moment this resolves, by either method. By short string: pump, wait for getVerificationStage to read
'ready', startVerificationComparison, pump, read getVerificationMaterial, show it, and confirmVerification or cancelVerification. The person is comparing two of their own screens instead of talking to somebody else, which changes none of the calls.By a scanned code, if the product asked for one, the person points one of their own phones at the other instead of reading symbols off both, and this is where that is most natural, because both screens are already in front of them. Both self modes work: the established device may show the code and the new login read it, or the other way round, and which happens is decided by which phone is held up rather than by anything you pass. Showing a code needs none of the account's private signing keys, which is what makes it reachable on the device that is joining. See offerScannableCodes and getVerificationCode.
Including the one sync between two verifications, which for this call means your own account: a second self-verification opened without a receiveSyncChanges after the first comes back already cancelled.
requestVerificationsays why, and the reason is the layer underneath rather than anything either call does.Refusals
'account_keys_not_fetched'means this process has not yet asked the server about this account, so it cannot know whether there is an identity to join. This call queues that key query before returning the refusal, so the remedy is the ordinary loop: drain the pump, send, report sent, and call this again. You do not have to reach for bootstrapCrossSigning to get unstuck, and on a device that is joining you should not: it is the call that would create a second identity if the state ever moved under you.Expect this refusal on every launch, not only the first. Whether the server has been asked is not persisted, and the layer underneath will not volunteer the question for an account it already knows about, so a relaunched store starts out having asked nothing.
'identity_not_known'means one of two things, andgetIdentityStatus().identityPublicationPendingtells them apart. False: the server was asked and said this account has no identity, so there is nothing to join and no retry helps. True: this device holds an identity it minted that no homeserver has ever asserted back, so there is nothing yet for another device to join it to, and the flow would sign under an identity the account may never have.createCrossSigningIdentity is the call for both, and for the same reason each time: creating a first identity and finishing a publication that was interrupted are the same decision, and it is one your product makes rather than something this handler calls. This is the same refusal, with the same remedy, that bootstrapCrossSigning reports. The paragraph used to name
bootstrapCrossSigningas the answer, which stopped being true when creating became its own call and left the two surfaces saying different things about one error, and it used to give only the first of the two meanings, which stopped being true when the gate gained its second condition.