Sign In With Solana
The primary purpose of this document is to define how Solana accounts authenticate with off-chain services. By signing a standard message format parameterized by scope, session details, and a nonce.
While decentralized identity is not a novel concept, the most common implementations of blockchain-based credentials are either certificate-based or rely on centralized providers. We're proposing an alternative that doesn't require a trusted third party.
Assumptions
Currently, there is no propsed standard for signing messages on Solana. We are proposing SIP-99
as a placeholder to conform to CAIP-74.
Specification
The specification for Sign In With Solana is based on https://eips.ethereum.org/EIPS/eip-4361 with the intention to make it compatible with https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-74.md
The message created follows the following structure :-
CACAO
header: Headers;
payload: Payload;
signature: Signature;
Payload
domain: string;
address: string;
statement?: string;
uri: string;
version: string;
chainId?: number;
nonce: string;
issuedAt: string;
expirationTime?: string;
notBefore?: string;
requestId?: string;
resources?: Array<string>;
Signature
t: string;
m?: SignatureMeta;
s: string;
A sample sign in message would look like :-
localhost:8080 wants you to sign in with your Solana account:
4Cw1koUQtqybLFem7uqhzMBznMPGARbFS4cjaYbM9RnR
Sign in with Solana to the app.
URI: http://localhost:8080
Version: 1
Chain ID: 1
Nonce: zNTPldYfb8ESmhPmL
Issued At: 2022-04-25T14:51:12.040Z
Workflow
- The user connects the wallet to the website.
- From the frontend pass the domain, address, statement, uri, version, nonce, issuedAt, expirationTime, notBefore, requestId, resources (Array) to the SignInWithSolanaMessage constructor. There is additional regex validation in place as mentioned in the below sections
- Nonce is needed as a security mechanism from replay attacks and hence it is generated at the server side.
- The created message needs to be prepared in a wallet friendly format for which .prepareMessage() needs to be called
- The resultant has to be passed to signMessage method of window.solana.request
- This function would return the signedMessage
Regex rules
Each field specified in the Specification section needs to follow the following regex rules
DOMAIN = "(?<domain>([^?#]*)) wants you to sign in with your Solana account:";
ADDRESS = "\\n(?<address>[a-zA-Z0-9]{32,44})\\n\\n";
STATEMENT = "((?<statement>[^\\n]+)\\n)?";
URI = "(([^:?#]+):)?(([^?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))";
URI_LINE = `\\nURI: (?<uri>${URI}?)`;
VERSION = "\\nVersion: (?<version>1)";
CHAIN_ID = "\\nChain ID: (?<chainId>[0-9]+)";
NONCE = "\\nNonce: (?<nonce>[a-zA-Z0-9]{8,})";
DATETIME = `([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(.[0-9]+)?(([Zz])|([+|-]([01][0-9]|2[0-3]):[0-5][0-9]))`;
ISSUED_AT = `\\nIssued At: (?<issuedAt>${DATETIME})`;
EXPIRATION_TIME = `(\\nExpiration Time: (?<expirationTime>${DATETIME}))?`;
NOT_BEFORE = `(\\nNot Before: (?<notBefore>${DATETIME}))?`;
REQUEST_ID = "(\\nRequest ID: (?<requestId>[-._~!$&'()*+,;=:@%a-zA-Z0-9]*))?";
RESOURCES = `(\\nResources:(?<resources>(\\n- ${URI}?)+))?`;
Verification flow
The verify function takes in the following params (as VerifyParams)
signature
publicKey
domain
nonce
time (optional)
There are certain checks in place such as invalid domain check,nonce binding check, expiry checks etc
import nacl from "@toruslabs/tweetnacl-js";
.
.
nacl.sign.detached.verify(encodedMessage, base58.decode(signature), base58.decode(publicKey))
If this function returns a true value then it is a valid signature
User flow
Disclaimer :
We haven't undergone a Formal Security Audit yet.