The code for this flow, for a person to hold up to another camera.
What your product has to do, and what this library will not
You own the scanner, the camera permission and the screen. Nothing in
this library sees an image, asks for a permission, or draws anything. It
produces the value a code carries and it consumes one back; everything
between that value and a person's eyes is yours.
Draw ScannableCode.modules, not ScannableCode.payload.
That is the reason two forms come back rather than one, and it is not a
convenience. The payload is about 126 bytes of binary that is not text: it
carries two raw signing keys and a random shared secret. There is no
string it can honestly be turned into, so a JavaScript code-drawing
component -- which nearly always takes a string -- cannot be given it. The
grid is the symbol this protocol's own encoder built, at a version and
error-correction level it fixes deliberately, in its own words because
mobile clients have trouble decoding otherwise. Draw width rows of
width squares from it, leave a quiet margin, and what a camera reads is
what the protocol meant. Re-encoding the payload yourself produces a code
this library's own scanner would read back and another client may not.
The payload is there because a product may need to move it -- to a
component of its own, to a test -- not because it should be turned into a
picture by hand.
Treat both as secret while the flow is open, exactly as
SasMaterial is treated: anything that learns either learns what an
interposed party would need to answer the flow as though it had read the
screen.
After it is drawn
The other device scans it and no call returns to tell you: watch
getVerificationStage for 'code-scanned', then ask a person and
call confirmScan when they say yes. That is the step this method's
security rests on, exactly as confirmVerification is for a short
string.
One thing that can go wrong here and is not yours
A flow where you show the code will not complete against a client that
announces itself finished as soon as it has scanned. The specification
puts that message last, after the person on the showing side has confirmed
the scan, and that confirmation is the entire security argument of this
method. A client that sends it early spends it while this side is still
waiting for a person, and the layer underneath correctly ignores it. The
flow then sits at 'confirmed' for ever.
This has been measured against a real third-party client rather than
inferred: its own source calls the message "immediately", the two events
arrive in one sync batch before any confirmation could exist, and the
repository's rust/matrix-crypto-core/tests/level_two_scanned.rs asserts
exactly that off the wire. The deviation is that client's, and
nothing in this library can make such a flow finish. Scanning their
code with submitScannedCode completes normally against the same
client, and giving a person a way out with cancelVerification is
what makes the failure recoverable.
Refusals
Every one of them is a sentence a product can show, which is the whole
point: the layer underneath answers seven different conditions with an
empty code and a warning nobody reads, and this call names them instead.
'code_not_offered' -- codes were not negotiated on this flow. Waiting
will never help. Two causes, and you can always tell which: either this
build never called offerScannableCodes, or the other device did
not offer to scan, in which case offer a short-string comparison
instead.
'identity_not_known' -- this account has no signing identity for the
code to carry. See createCrossSigningIdentity, which is the call
that makes one; bootstrapCrossSigning publishes an identity this
device already holds and answers this same refusal.
'peer_identity_not_known' -- the other user has none, and nothing
this device does will produce one.
'private_keys_not_held' -- verifying another user puts this account's
own key in the code and this device cannot prove it holds one. See
requestSelfVerification, recoverIdentity or, on an
account with no identity at all, createCrossSigningIdentity.
Not bootstrapCrossSigning: it refuses a device holding no
private keys with 'identity_already_exists'. Verifying your own new
login does not need them.
'wrong_stage' -- nobody has agreed to this flow yet, or it is over.
'unknown_flow' -- no flow of that id.
'malformed_identifier' -- the flow's own identifier is too long to fit
in a code. Only reachable from a peer that chose the identifier, since
the ones this library mints are ordinary transaction ids, and nothing a
product does about it will help: offer a short-string comparison.
Calling it twice is legal and produces a code for the same flow. Draw the
newer one: it is the live one.
The code for this flow, for a person to hold up to another camera.
What your product has to do, and what this library will not
You own the scanner, the camera permission and the screen. Nothing in this library sees an image, asks for a permission, or draws anything. It produces the value a code carries and it consumes one back; everything between that value and a person's eyes is yours.
Draw ScannableCode.modules, not ScannableCode.payload. That is the reason two forms come back rather than one, and it is not a convenience. The payload is about 126 bytes of binary that is not text: it carries two raw signing keys and a random shared secret. There is no string it can honestly be turned into, so a JavaScript code-drawing component -- which nearly always takes a string -- cannot be given it. The grid is the symbol this protocol's own encoder built, at a version and error-correction level it fixes deliberately, in its own words because mobile clients have trouble decoding otherwise. Draw
widthrows ofwidthsquares from it, leave a quiet margin, and what a camera reads is what the protocol meant. Re-encoding the payload yourself produces a code this library's own scanner would read back and another client may not.The payload is there because a product may need to move it -- to a component of its own, to a test -- not because it should be turned into a picture by hand.
Treat both as secret while the flow is open, exactly as SasMaterial is treated: anything that learns either learns what an interposed party would need to answer the flow as though it had read the screen.
After it is drawn
The other device scans it and no call returns to tell you: watch getVerificationStage for
'code-scanned', then ask a person and call confirmScan when they say yes. That is the step this method's security rests on, exactly as confirmVerification is for a short string.One thing that can go wrong here and is not yours
A flow where you show the code will not complete against a client that announces itself finished as soon as it has scanned. The specification puts that message last, after the person on the showing side has confirmed the scan, and that confirmation is the entire security argument of this method. A client that sends it early spends it while this side is still waiting for a person, and the layer underneath correctly ignores it. The flow then sits at
'confirmed'for ever.This has been measured against a real third-party client rather than inferred: its own source calls the message "immediately", the two events arrive in one sync batch before any confirmation could exist, and the repository's
rust/matrix-crypto-core/tests/level_two_scanned.rsasserts exactly that off the wire. The deviation is that client's, and nothing in this library can make such a flow finish. Scanning their code with submitScannedCode completes normally against the same client, and giving a person a way out with cancelVerification is what makes the failure recoverable.Refusals
Every one of them is a sentence a product can show, which is the whole point: the layer underneath answers seven different conditions with an empty code and a warning nobody reads, and this call names them instead.
'code_not_offered'-- codes were not negotiated on this flow. Waiting will never help. Two causes, and you can always tell which: either this build never called offerScannableCodes, or the other device did not offer to scan, in which case offer a short-string comparison instead.'identity_not_known'-- this account has no signing identity for the code to carry. See createCrossSigningIdentity, which is the call that makes one; bootstrapCrossSigning publishes an identity this device already holds and answers this same refusal.'peer_identity_not_known'-- the other user has none, and nothing this device does will produce one.'private_keys_not_held'-- verifying another user puts this account's own key in the code and this device cannot prove it holds one. See requestSelfVerification, recoverIdentity or, on an account with no identity at all, createCrossSigningIdentity. Not bootstrapCrossSigning: it refuses a device holding no private keys with'identity_already_exists'. Verifying your own new login does not need them.'wrong_stage'-- nobody has agreed to this flow yet, or it is over.'unknown_flow'-- no flow of that id.'malformed_identifier'-- the flow's own identifier is too long to fit in a code. Only reachable from a peer that chose the identifier, since the ones this library mints are ordinary transaction ids, and nothing a product does about it will help: offer a short-string comparison.Calling it twice is legal and produces a code for the same flow. Draw the newer one: it is the live one.