Kambani is a Chrome extension for cryptographic keys and digital identities management, tailored to the Factom blockchain.
The main feature of the extension is the ability for existing web sites to easily integrate with Kambani and send arbitrary signing requests for review and approval. This flexibility can be used as a building block in a wide variety of applications, such as:
- passwordless authentication and authorization
- initiation of blockchain transactions (e.g. for web wallets)
- encrypted P2P communication
- on-chain voting
The extension provides the following additional functionality:
- creation of encrypted vaults for storing private keys, using the AES-GCM encryption algorithm
- support for creation and update of digital identities based on W3C DID
- support for importing and creating FCT and EC keys
- ECDSA digital signatures over the
secp256k1
(a.k.a. Bitcoin) elliptic curve - EdDSA digital signatures over the
edwards25519
curve, as specified in RFC8032 - RSA digital signatures
- paper backups for vaults
Run ng build --aot
to build the project. The build artifacts will be stored in the dist/
directory.
Run ng build --aot --watch
to build the project for development. Any changes you make to the source code should be
automatically reflected in your location version of Kambani.
Run ng build --prod
for a production build. The build artifacts will be stored in the dist/
directory.
To install the extension locally:
- Checkout the repository
- Run
npm install
- Run
ng build --prod
orng build --aot --watch
(for a development build) from the project root directory - Open a Chrome browser and go to the special URL
chrome://extensions
- Make sure Developer Mode is switched on in the top-right corner
- Click on the
Load Unpacked
link in the top-left corner - Choose the
dist/kambani
folder in the project root directory
The plugin should now be visible in your Chrome browser and should be listed on the chrome://extensions
page
Kambani is designed to allow easy communication with existing websites. This is achieved by using DOM CustomEvents.
The rest of this document assumes that you are familiar with CustomEvents
, in particular the way in which data can be passed
inside those events, so please take a minute to read through the docs if you are not.
The basic workflow for websites looking to integrate with Kambani is as follows:
- the website dispatches a
SigningRequest
CustomEvent
, which contains the data that needs to be reviewed and signed by the user - the user receives a notification of the incoming request(s)
- the user reviews the request(s) and either signs or cancels it(them)
- Kambani dispatches a
SigningResponse
CustomEvent
, which can be intercepted by the website. TheSigningResponse
contains the signature from the user, together with additional metadata such as the public key and the type of signature, or an indication that the request has not been approved
To make it easier for application developers, the extension provides both generic data signing requests, in which an arbitrary JSON object can be sent to the user, as well as application specific requests such as PegNet transfer, conversion and burning requests.
If you prefer to look at an example application, which integrates with Kambani, instead of reading through the rest of the
documentation, you can find a demo in the examples/demo-voting-app
folder.
The application provides a working demonstration of obtaining the user's FCT & EC addresses, as well as multiple examples of signing requests with FCT, EC and DID keys. Check the associated README for more information.
A website can detected if Kambani is installed in the user's browser by dispatching an IsKambaniInstalled
CustomEvent
and
listening for a KambaniInstalled
event fired by Kambani in response.
Example code:
window.addEventListener("KambaniInstalled", event => {
console.log("Kambani is installed")
})
window.dispatchEvent(new CustomEvent("IsKambaniInstalled"))
A website can request access to the users public FCT addresses. This can be accomplished by dispatching a GetFCTAddresses
CustomEvent
and listening for an FCTAddresses
event fired by Kambani in response, following the user's approval.
Example code for requesting the user's FCT addresses:
const fctAddressesEvent = new CustomEvent("GetFCTAddresses");
window.dispatchEvent(fctAddressesEvent);
Example code for listening for a response from Kambani:
window.addEventListener("FCTAddresses", event => {
if (event.detail.success) {
const fctAddresses = event.detail.fctAddresses;
if (fctAddresses.length > 0) {
console.log(JSON.stringify(fctAddresses, null, 2));
}
} else {
console.log("GetFCTAddresses request not approved");
}
});
Note that if the user does not grant access to their FCT addresses, the event.detail.success
value will be set to false
.
If the user does grant access, subsequent requests from the same domain can obtain the FCT addresses without requiring explicit approval from the user, unless the user manually revokes the access from their settings inside Kambani.
If access to the FCT addresses is granted, this also exposes the website to an FCTAddressesChanged
event, which is used to
notify the website of any additions or removals of FCT addresses inside Kambani (more on this in the following sections).
Similarly to FCT addresses, websites can request access to the EC addresses of the user.
Example code for requesting the user's EC addresses:
const ecAddressesEvent = new CustomEvent("GetECAddresses");
window.dispatchEvent(ecAddressesEvent);
Example code for listening for a response from Kambani:
window.addEventListener("ECAddresses", event => {
if (event.detail.success) {
const ecAddresses = event.detail.ecAddresses;
if (ecAddresses.length > 0) {
console.log(JSON.stringify(ecAddresses, null, 2));
}
} else {
console.log("GetECAddresses request not approved");
}
});
The same disclaimers about subsequent access and changes to EC addresses apply as for FCT addresses.
Once a given domain is granted access to the user's FCT and EC addresses, websites under this domain can listen for changes in
the user's addresses in the background in order to have an up-to-date state. This is accomplished by registering event listeners
for the FCTAddressesChanged
and ECAddressesChanged
events.
Example code for listening to EC addresses changes:
window.addEventListener("ECAddressesChanged", event => {
console.log(event.detail);
if (event.detail.removed) {
const removedEcAddresses = event.detail.removed.map(addr => JSON.stringify(addr));
console.log(removedEcAddresses);
}
if (event.detail.added) {
for (const addedAddress of event.detail.added) {
console.log(addedAddress);
}
}
});
Identical code can be used to monitor for changes in FCT addresses using the FCTAddressesChanged
event. A complete working example
is available in the examples/demo-voting-app
.
The most flexible type of signing request supported by Kambani is a "data" signing request. Websites can request a signature of any JSON object by using this type of request. The signature is produced over the output bytes of SHA-256 of the stringified JSON object.
In order to send a data signing request, the website needs to dispatch a SigningRequest
event containing a detail
object with
the following schema:
{
"requestId": unique identifier (int/string, required),
"requestType": "data" (required),
"requestInfo": {
"data": JSON object to sign (required),
"keyType": one of "fct", "ec", "didKey" or "managementKey" (required),
"keyIdentifier": string (optional),
"did": string (optional)
},
}
The semantics of the fields in the above schema is:
requestId
: should be a unique identifier for this request, which can be used for bookkeeping purposes in the websiterequestType
: must be "data" and signifies to Kambani how this request should be treatedrequestInfo.keyType
: what type of key must be used by the user to sign the message. Kambani will filter the keys stored in the encrypted vault based on the providedkeyType
and will not allow signature from a differentkeyType
requestInfo.keyIdentifier
: the website has the ability to request a signature from a specific key. The user will only be able to sign with the exact requested key. If akeyType
offct
orec
is specified, the identifier should be the public FCT or EC key. If adidKey
ormanagementKey
is specified, thekeyIdentifier
value should be the key identifier of the DID or management key (see the DID spec). If there is no value specified forkeyIdentifier
, then the user will be able to choose with which key to sign from all available keys of the givenkeyType
requestInfo.did
: must be a valid DID, as given in the DID spec. This field can be set to request a signature from a specific DID and it must be used only if thekeyType
isdidKey
ormanagementKey
. The field is required if one of thosekeyType
s is used AND akeyIdentifier
is used as well
Kambani supports several types of PegNet specific requests to ease development of websites integrating with PegNet, such as web wallets, trading platforms, e-shops, content platforms, etc.
To ensure the security of users, the PegNet requests initiated from a website contain only metadata, as opposed to the transactions themselves. The building of the raw transaction bytes that are signed by the user is done inside Kambani. Since the extension is fully open-sourced and anyone can audit the code, this provides strong guarantees that users cannot be tricked into sending funds to the wrong address by a malicious website, for example.
To initiate a request for an FCT to pFCT burn, a website must send a SigningRequest
CustomEvent
with the following
schema of the detail
object:
{
"requestId": unique identifier (int/string, required),
"requestType": "pegnet",
"requestInfo": {
"txType": "burn",
"inputAddress": "FA..." (required),
"inputAmount": integer (required),
}
}
In case of a successfully signed transaction, the SigningResponse
event contains a transaction
field, which has the
raw signed transaction bytes ready to be submitted to the factoid-submit
API endpoint of factomd
without any modification.
To initiate a request for a PegNet transfer, a website must send a SigningRequest
CustomEvent
with the following
data in the detail
object:
{
"requestId": unique identifier (int/string, required),
"requestType": "pegnet",
"requestInfo": {
"txType": "transfer",
"inputAddress": "FA..." (required),
"inputAsset": string (required),
"inputAmount": integer (required)
"outputAddress": "FA..." (required),
"txMetadata": JSON object (optional),
}
}
In case of a successfully signed transaction, the SigningResponse
event contains an entry
field, which is an array of
two values: the first being the extIDs
for the transfer entry and the second being the entry content.
Note that:
- the entry is not signed by an EC key and so is not paid for by the user. Websites using the PegNet transfer request type are currently expected to pay for those entries themselves, before broadcasting the entry to the Factom blockchain. We plan to modify this request type to allow websites to request payment from the user in a future version of Kambani
- the
txMetadata
field can be used to record optional metadata for the conversion entry. The JSON object passed to this field will be put directly inside themetadata
field of the PegNet transfer entry.
To initiate a request for a PegNet conversion, a website must send a SigningRequest
CustomEvent
with the following
schema of the detail
object:
{
"requestId": unique identifier (int/string, required),
"requestType": "pegnet",
"requestInfo": {
"txType": "conversion",
"inputAddress": "FA..." (required),
"inputAsset": string (required),
"inputAmount": integer (required)
"outputAsset": string (required),
"txMetadata": JSON object (optional),
}
}
In case of a successfully signed transaction, the SigningResponse
event contains an entry
field, which is an array of
two values: the first being the extIDs
for the conversion entry and the second being the entry content.
The same disclaimers for the payment of the entry and the txMetadata
as for PegNet transfer transactions apply.
We treat the security and privacy of Kambani users with utmost care.
All private keys in Kambani are stored encrypted in-memory and at-rest using state-of-the-art AES-GCM encryption, with a strong passphrase.
Decryption of the private keys is only done when signing an incoming message, or when importing them from an encrypted file, thus greatly limiting the time window during which any sensitive cryptographic material is exposed in plaintext to a potential attacker.
As of the latest version of the extension, to protect the privacy of users, any requests for accessing their FCT or EC public keys need to be explicitly approved. This is done to prevent websites from silently accessing the public keys and checking the user's FCT balance (and subsequently using the person's assumed net worth to modify the web site content, such as prices on an e-shop, e.g.). In addition to this, preventing access to the user's addresses, removes the possibility of tracking the user across different websites.