-
Notifications
You must be signed in to change notification settings - Fork 3
Substrate Signing Request Specification
0.1
A Substrate Signing Request is a request to sign a substrate transaction. A wallet implementing this standard can create SSR URLs and process SSR URLs.
A substrate transaction is encoded as JSON.
This allows a signing wallet to sign arbitrary transactions.
A SSR has the following properties:
- Chain ID - a substrate Chain ID (insert link) specifying the chain the transaction should be signed on
- Type - identity or sign - The default type is sign. Standard 1.0 will have an identity type request that allows a user to authenticate themselves using their substrate account key.
- Callback URL - called for identity and also to inform a backend that a transaction has been made or has failed.
- Transactions - a list of extrinsics calls with data parameters to be executed.
- "signer" account template - allows the SSR to specify the "signer" account id without knowing this account ID - the client replaces all occurrences of signer with the signing account they choose to execute the transction.
- SSR is a JSON object, encoded as URL with a ssr:// prefix
- ssr URL can be encoded as QR code for convenient parsing with a wallet
Version 0.1 implements the standard to a minimal degree, it has the following limitations
- No identity request, only transactions
- Only a single extrinsics call, not more than 1
It implements the rest of the standard.
Sample
{
"chainId": "hashed",
"callback": "https://myserver.somwhere.com/?tx_id=$tx_id"
"transactions": [
{
"module": "recovery",
"call": "createRecovery",
"sender": "signer",
"params": "[[\"5Da6BeYLC3BRvS2H3bQ6JWgMGZtqKGdaoKMPhdtYMf56VaCU\",\"5EUqh98iKNwWQjpzYQPVw3LEQiiaVMaB4Yp2ugXA5fMKFDLk\",\"5GEbpz29EkSM3vKtzuUEXtwpK8vguYm2TRRsmekQufYJDJpz\"],2,3000]"
}
]
}
Corresponding encoded URL
ssr://H4sIAAAAAAAAE0WMu26DQBRE_-XWFBYEZLsLhpDwiAwO2AQormF5hKeXBRQs_7tXSpFudObM3CEtseo-MthDiWNJMhCAUexGTFnVdyPsozu0fTY1hCuUpP1M6C-XUmwaTlJKkBHvn4-kywjlzVgVHQ8CDEix5UcQRTHIGioqCe2DpHrzSXyXrq5ingvH-GY3y8iwt5xjmbHQyWUlwIMfg8BHun8rd9vK-lzO7s-whu4xWCRbd6sKAwfVl3AQp-LyKueO9abZ9d_I0K_DKu70-uRIs8XWydcvbBms7VxMYSt-ed7Yktqd8tDUzGGNIRFEQdpsNgk8kscTHuDEbRoBAAA=
An identity request differs from a sign and send request in that
- No on chain action takes place
- The callback URL must be defined and contain placeholders
- A type of identity field is expected
- A challenge - random string - is expected
Upon receiving an identity request, the client is expected to sign the challenge, and send it back to the callback URL.
This way a server can authenticate a user, create a session, etc.
The callback link includes placeholders for the challenge, the account used for signing, and the signed challenge.
Since only the account holder has the private key, only the account holder can sign the challenge for the account, thereby priving identity to the server.
Example:
{
"type": "identity",
"challenge": "afx90972"
"callback": "https://myserver.somwhere.com/?challenge=$c&sign=$s&acct=$acct"
}
An very detailed example of signing and verifying signatures with Polkadot JS can be found here: https://ioannis-tsiokos.medium.com/polkadot-aws-cognito-user-authentication-d144081fbc4e
We can use this code as a template for implementing signing and verification of signatures server side.
- Take a json Object - either a dictionary / map style object, or an array.
- Convert it to a string using a JSON encoder
- Convert the string to bytes with UTF8 encoding.
- Compress the bytes with gzip
- Convert the zipped bytes to a string using the Base64Url standard - a subset of base64 suitable for URLs.
- Create URL with URL scheme ssr://
For decoding, the reverse.
Code example (dart)
static const urlScheme = "ssr";
String jsonToSigningRequestUrl(dynamic jsonObj) {
final string = json.encode(jsonObj);
final bytes = utf8.encode(string);
final zippedBytes = gzip.encode(bytes);
final base64url = base64Url.encode(zippedBytes);
return "$urlScheme://$base64url";
}
/// returns JSON object
dynamic signingRequestUrlToJson(String url) {
final urlComponents = url.split("://");
if (urlComponents.length == 2) {
if (urlComponents[0] == urlScheme) {
final payload = urlComponents[1];
final zippedBytes = base64Url.decode(payload);
final bytes = gzip.decode(zippedBytes);
final string = utf8.decode(bytes);
final result = json.decode(string);
return result;
} else {
throw "Unsupported URL scheme: ${urlComponents[0]}";
}
} else {
throw "Invalid URL: $url";
}
}
A full binary encoding would yield the perfect compressed data.
However, experiments show that binary is only around 30% more efficient than zipped json (Reference Link)
Binary encoding of all possible field values would significantly increase the complexity of implementing clients that can handle this format.
Whereas JSON, UTF8, and gzip are all readily available on all platforms, and easy and straightforward to test and to use.
QR codes are limited in size, so size is important.
However, the benefits of simplicity outweigh the drawbacks of a roughtly 30% larger payload.