@substrate-system/keys
    Preparing search index...

    @substrate-system/keys

    keys

    tests types module semantic versioning Common Changelog install size GZip size license

    Create and store keypairs in the browser with the web crypto API.

    Use indexedDB to store non-extractable keypairs in the browser. "Non-extractable" means that the browser prevents you from ever reading the private key, but the keys can be persisted and re-used indefinitely.

    Tip

    Use the persist method to tell the browser not to delete from indexedDB.

    Each instance of Keys has two keypairs -- one for signing, and another for encrypting.

    See also, the API docs generated from typescript.

    Contents

    npm i -S @substrate-system/keys
    

    This exposes ESM and common JS via package.json exports field.

    import { EccKeys, verify } from '@substrate-system/keys/ecc'
    import { RsaKeys, verify } from '@substrate-system/keys/rsa'
    import { AES } from '@substrate-system/keys/aes'
    const { EccKeys, verify } = require('@substrate-system/keys/ecc')
    const { RsaKeys, verify } = require('@substrate-system/keys/rsa')
    const { AES } = require('@substrate-system/keys/aes')

    This package exposes minified JS files too. Copy them to a location that is accessible to your web server, then link to them in HTML.

    cp ./node_modules/@substrate-system/keys/dist/index.min.js ./public/keys.min.js
    
    <script type="module" src="./keys.min.js"></script>
    

    This function takes either an Ed25519 key or an RSA key.

    import { verify } from '@substrate-system/keys/crypto'

    // ed25519
    const isOk = await verify({ message, publicKey: ecc.DID, signature })

    // RSA
    const isOk = await verify({ message, publicKey: rsa.DID, signature })

    Create a new keypair, then save it in indexedDB.

    ECC is now supported in all major browsers.

    import { EccKeys, verify } from '@substrate-system/keys/ecc'

    const keys = await EccKeys.create()

    // save the keys to indexedDB
    await keys.persist()

    // ... sometime in the future ...
    // get our keys from indexedDB
    const keysAgain = await EccKeys.load()

    console.assert(keys.DID === keysAgain.DID) // true
    'did:key:z13V3Sog2YaUKhdGCmgx9UZuW...'
    

    This is the DID string for the signing key for this instance. The DID looks like this:

    The Ed25519 DID looks like this:

    did:key:zStERvoWtx7FQS432smzbGENZHjsN55X8pUZ3np8DXGhZFf3TCorijCPeJoLytwb
    

    The RSA DID looks like this:

    did:key:z13V3Sog2YaUKhdGCmgx9UZuW1o1ShFJYc6DvGYe7NTt689NoL2HdpC46K4PjfsUVAopaqmnySjJV8T6K9dMB2FUXhqfKYLUz9o9fA7xkgiNr25sUQq4vJPuPfP1kbSqtYXe5V2CZTUMucF2jfNMrWjHHRUZpEzPwGeZd5prsu9pxnVhPKnVxxKTJAVqQCp3CDRASeYKQmqVRmyPSrdQaYz4AoQxaBd52mNC7dEC4xXKbVw45dhQc52j5chR8YKCeaNANWh8DEZ7U8Dtb89PL5qP8817oxswQhz4e97p8EGtJDVrGbCXN4EucnpYaacRane4YPcmevs1pMYV8iqzJd8UZLWtDcUBNrCkeVTdnzXnM4Rq9sWiFwF3nYk6fxqfUZDfYyfPtxacSoGaSjo38ye
    

    Return a 32 character, DNS friendly hash of the signing public key.

    const name = await keys.getDeviceName()

    // a promise is exposed as property `deviceName`
    const name = await keys.deviceName

    A flag indicating whether .persist has been called, meaning that these keys are saved in indexedDB.

    The public encryption CryptoKey. For ECC keys, this is the X25519 exchange key. For RSA keys, this is the RSA encryption key.

    Get the public encryption key as a base64 string. For other formats, see below.

    {
    async publicExchangeKeyAsString (format?:SupportedEncodings):Promise<string>
    }

    The public signing CryptoKey. This is the Ed25519 or RSA signing key.

    Get the public signing key as a string.

    {
    async publicWriteKeyAsString (format?:SupportedEncodings):Promise<string>
    }

    Delete the keys from indexedDB.

    await keys.delete()
    

    .verify takes the content, the signature, and the DID for the public key used to sign. The DID is exposed as the property .DID on a Keys instance.

    Note


    verify is exposed as a separate function, so you don't have to include all of Keys just to verify a signature.

    import { RsaKeys, verify } from '@substrate-system/keys/rsa'
    // or: import { EccKeys, verify } from '@substrate-system/keys/ecc'

    const keys = await RsaKeys.create()
    // or: const keys = await EccKeys.create()

    // sign something
    const sig = await keys.signAsString('hello string')
    // or string format: const sig = await keys.sign.asString('hello string')

    // verify the signature
    const isOk = await verify('hello string', sig, keys.DID)

    Take the public key we are encrypting to, return encrypted content.

    Encrypt something, return a Uint8Array.

    ECC:

    Note


    recipient is optional. If it is omitted, then this will encrypt to its own public key, a "note to self."

    async encrypt (
    content:string|Uint8Array,
    recipient?:CryptoKey|string, // their public key
    info?:string,
    aesKey?:SymmKey|Uint8Array|string,
    keysize?:SymmKeyLength
    ):Promise<Uint8Array>

    RSA:

    async encrypt (
    content:string|Uint8Array,
    recipient?:CryptoKey|string,
    aesKey?:SymmKey|Uint8Array|string, // For RSA, can pass in AES key
    keysize?:SymmKeyLength,
    ):Promise<Uint8Array>

    Encrypt something, return a string.

    ECC:

    async encryptAsString (
    content:string|Uint8Array,
    recipient?:CryptoKey|string,
    info?:string,
    aesKey?:SymmKey|Uint8Array|string,
    keysize?:SymmKeyLength,
    ):Promise<string>

    RSA:

    async encryptAsString (
    content:string|Uint8Array,
    recipient?:CryptoKey|string,
    aesKey?:SymmKey|Uint8Array|string,
    keysize?:SymmKeyLength,
    ):Promise<string>
    import { encryptTo } from '@substrate-system/keys/rsa'  // RSA version

    // need to know the public key we are encrypting for
    const publicKey = await keys.publicExchangeKeyAsString() // Both ECC and RSA

    const encrypted = await encryptTo({
    content: 'hello public key',
    publicKey
    }) // => ArrayBuffer

    const encrypted = await encryptTo.asString({
    content: 'hello public key',
    publicKey
    }) // => <encrypted text>

    A Keys instance has a method decrypt. The encryptedMessage argument is an ArrayBuffer, as returned from encryptTo, above.

    import { EccKeys } from '@substrate-system/keys/ecc'
    // or: import { RsaKeys } from '@substrate-system/keys/rsa'

    const keys = await EccKeys.create()
    // or: const keys = await RsaKeys.create()

    // This will decrypt the message using our own public key
    const decrypted = await keys.decrypt(encryptedMsg)

    Use the factory function EccKeys.create or RsaKeys.create. The optional parameters, encryptionKeyName and signingKeyName, are added as properties to the keys instance. These are used as indexes for saving the keys in indexedDB.

    ECC:

    class EccKeys {
    static EXCHANGE_KEY_NAME:string = 'ecc-exchange'
    static WRITE_KEY_NAME:string = 'ecc-write'

    static async create (session?:boolean):Promise<EccKeys>
    }

    RSA:

    class RsaKeys {
    static EXCHANGE_KEY_NAME:string = 'rsa-exchange'
    static WRITE_KEY_NAME:string = 'rsa-write'

    static async create (session?:boolean):Promise<RsaKeys>
    }

    Use the factory function b/c async.

    import { EccKeys } from '@substrate-system/keys/ecc'
    // or: import { RsaKeys } from '@substrate-system/keys/rsa'

    const keys = await EccKeys.create()

    Get a 32-character, DNS-friendly string of the hash of the given DID. Available as static or instance method. If called as an instance method, this will use the DID assigned to the given Keys instance.

    The static method requires a DID string to be passed in.

    class EccKeys {  // or RsaKeys
    static async deviceName (did:DID):Promise<string>
    }

    If used as an instance method, this will use the DID assigned to the instance.

    class EccKeys {  // or RsaKeys
    async getDeviceName ():Promise<string>
    }

    Save the keys to indexedDB. This depends on the values of the static class properties EXCHANGE_KEY_NAME and WRITE_KEY_NAME. Set them if you want to change the indexes under which the keys are saved to indexedDB.

    By default we use these:

    • ECC: 'ecc-exchange' and 'ecc-write'
    • RSA: 'rsa-exchange' and 'rsa-write'
    class EccKeys {  // or RsaKeys
    async persist ():Promise<void>
    }
    import { EccKeys } from '@substrate-system/keys/ecc'

    const keys = await EccKeys.create()
    EccKeys.EXCHANGE_KEY_NAME = 'encryption-key-custom-name'
    EccKeys.WRITE_KEY_NAME = 'signing-key-custom-name'
    await keys.persist()

    Create a Keys instance from data saved to indexedDB. Pass in different indexedDB key names for the keys if you need to.

    class EccKeys {  // or RsaKeys
    static async load (opts?:{
    encryptionKeyName?:string,
    signingKeyName?:string,
    session?:boolean,
    }):Promise<EccKeys>
    }
    import { EccKeys } from '@substrate-system/keys/ecc'
    // or: import { RsaKeys } from '@substrate-system/keys/rsa'

    const newKeys = await EccKeys.load()

    Create a new signature for the given input.

    ECC:

    async sign (msg:Msg, _charsize?:CharSize):Promise<Uint8Array>
    

    RSA:

    async sign (
    msg:Msg,
    charsize:CharSize = DEFAULT_CHAR_SIZE
    ):Promise<Uint8Array>
    const sig = await keys.sign('hello signatures')
    

    Sign a message and return the signature as a base64 encoded string.

    {
    async signAsString (msg:string, charsize?:CharSize):Promise<string>
    }
    const sig = await keys.signAsString('hello string')
    // => ubW9PIjb360v...

    For backward compatibility, the .asString method is still available:

    const sig = await keys.sign.asString('hello string')
    // => ubW9PIjb360v...

    Check if a given signature is valid. This is exposed as a stateless function so that it can be used independently from any keypairs. You need to pass in the data that was signed, the signature, and the DID string of the public key used to create the signature.

    This works the same for either RSA or ECC keys.

    async function verify (
    msg:string|Uint8Array,
    sig:string|Uint8Array,
    signingDid:DID
    ):Promise<boolean>

    RSA verification uses RSA-PSS with SHA-256:

    import { verify } from '@substrate-system/keys/rsa'

    const isOk = await verify('hello string', sig, keys.DID)

    ECC verification uses Ed25519:

    import { verify } from '@substrate-system/keys/ecc'

    const isOk = await verify('hello string', sig, keys.DID)

    Use asymmetric (RSA) encryption to encrypt an AES key to the given public key.

    async function encryptKeyTo ({ key, publicKey }:{
    key:string|Uint8Array|CryptoKey;
    publicKey:CryptoKey|Uint8Array|string;
    }, format?:'uint8array'|'arraybuffer'):Promise<Uint8Array|ArrayBuffer>
    import { encryptKeyTo } from '@substrate-system/keys/rsa'

    // pass in a CryptoKey
    const encrypted = await encryptKeyTo({
    key: myAesKey,
    publicKey: keys.publicExchangeKey
    })

    // pass in a base64 string
    const encryptedTwo = await encryptKeyTo({
    key: aesKey,
    publicKey: await keys.publicExchangeKeyAsString()
    }) // => Uint8Array

    Encrypt the given key to the public key, and return the result as a base64 string.

    !NOTE This is only relevant for RSA keys

    import { encryptKeyTo } from '@substrate-system/keys/rsa'

    encryptKeyTo.asString = async function ({ key, publicKey }:{
    key:string|Uint8Array|CryptoKey;
    publicKey:CryptoKey|string|Uint8Array;
    }, format?:SupportedEncodings):Promise<string> {

    encryptKeyTo.asString takes an optional second argument for the format of the returned string. Format is anything supported by uint8arrays. By default, if omitted, it is base64.

    Encrypt the given message to the given public key. If an AES key is not provided, one will be created. Use the AES key to encrypt the given content, then encrypt the AES key to the given public key.

    !NOTE This is only relevant for RSA keys. If using ECC keys, a symmetric key is automatically generated via diffie-hellman.

    The return value is an ArrayBuffer containing the encrypted AES key + the iv + the encrypted content if using RSA. It is salt + iv + cipher text if using ECC.

    To decrypt, pass the returned value to keys.decrypt, where keys is an instance with the corresponding private key.

    async function encryptTo (
    opts:{
    content:string|Uint8Array;
    publicKey:CryptoKey|string;
    },
    aesKey?:SymmKey|Uint8Array|string,
    ):Promise<ArrayBuffer>
    import { encryptTo } from '@substrate-system/keys/rsa'

    const encrypted = await encryptTo({
    content: 'hello encryption',
    publicKey: keys.publicExchangeKey
    })

    // => ArrayBuffer

    Encrypt the given string, and return a new string that is the (encrypted) AES key concatenated with the iv and cipher text. The corresponding method keys.decryptAsString will know how to parse and decrypt the resulting text.

    Use the functions encryptTo.asString and keys.decryptAsString.

    ECC:

    async decryptAsString (
    msg:string|Uint8Array|ArrayBuffer,
    publicKey?:CryptoKey|string,
    aesAlgorithm?:string,
    info?:string,
    ):Promise<string>

    RSA:

    async decryptAsString (
    msg:string|Uint8Array|ArrayBuffer,
    keysize?:CryptoKey|string|SymmKeyLength,
    _aesAlgorithm?:string,
    ):Promise<string>
    import { RsaKeys, encryptTo } from '@substrate-system/keys/rsa'  // RSA example
    // or: import { EccKeys } from '@substrate-system/keys/ecc'

    const keys = await RsaKeys.create()
    // or: const keys = await EccKeys.create()
    const pubKey = await keys.publicExchangeKeyAsString() // Both ECC and RSA
    const msg = { type: 'test', content: 'hello' }
    const cipherText = await encryptTo.asString({
    content: JSON.stringify(msg),
    // pass in a string public key or crypto key or Uint8Array
    publicKey: pubKey
    }) // => string

    const text = await keys.decryptAsString(cipherText)
    const data = JSON.parse(text)
    // => { type: 'test', content: 'hello' }

    ECC:

    async decrypt (
    msg:string|Uint8Array|ArrayBuffer,
    publicKey?:CryptoKey|string,
    aesAlgorithm?:string,
    info?:string,
    ):Promise<ArrayBuffer>

    !NOTE ECC keys will use our own public key if it is not passed in.

    RSA:

    async decrypt (
    msg:string|Uint8Array|ArrayBuffer,
    keysize?:CryptoKey|string|SymmKeyLength,
    _aesAlgorithm?:string,
    ):Promise<Uint8Array>
    const decrypted = await keys.decrypt(encrypted)
    // => ArrayBuffer (ECC) or Uint8Array (RSA)

    Decrypt a message, and stringify the result.

    await keys.decrypt.asString(encryptedString)
    // => 'hello encryption'

    Create a keypair, but do not save it in indexedDB, even if you call persist. Pass true as the session parameter to .create or pass { session: true } to .load.

    import { EccKeys } from '@substrate-system/keys/ecc'
    // or: import { RsaKeys } from '@substrate-system/keys/rsa'

    const keys = await EccKeys.create(true)
    // or: const keys = await RsaKeys.create(true)

    // or pass it to `.load`
    const keysTwo = await EccKeys.load({ session: true })
    // or: const keysTwo = await RsaKeys.load({ session: true })

    Expose several AES functions with nice defaults.

    • algorithm: AES-GCM
    • key size: 256
    • iv size: 12 bytes (96 bits)
    import { AES } from '@substrate-system/keys/aes'

    const key = await AES.create(/* ... optional arguments ... */)

    Create a new AES key. By default uses 256 bits & GCM algorithm.

    function create (opts:{ alg:string, length:number } = {
    alg: DEFAULT_SYMM_ALGORITHM, // AES-GCM
    length: DEFAULT_SYMM_LENGTH // 256
    }):Promise<CryptoKey>
    import { AES } from '@substrate-system/keys/aes'
    const aesKey = await AES.create()

    Get the AES key as a Uint8Array.

      async function export (key:CryptoKey):Promise<Uint8Array>
    
    import { AES } from '@substrate-system/keys/aes'
    const exported = await AES.export(aesKey)

    Get the key as a string, base64 encoded.

    async function asString (
    key:CryptoKey,
    format?:SupportedEncoding
    ):Promise<string>
    import { AES } from '@substrate-system/keys/aes'
    const exported = await AES.export.asString(aesKey)

    Take a Uint8Array, return an encrypted Uint8Array.

    async function encrypt (
    data:Uint8Array,
    cryptoKey:CryptoKey|Uint8Array,
    iv?:Uint8Array
    ):Promise<Uint8Array>
    import { AES } from '@substrate-system/keys/aes'
    import { fromString } from 'uint8arrays'

    const encryptedText = await AES.encrypt(fromString('hello AES'), aesKey)
    async function decrypt (
    encryptedData:Uint8Array|string,
    cryptoKey:CryptoKey|Uint8Array|ArrayBuffer,
    iv?:Uint8Array
    ):Promise<Uint8Array>
    import { AES } from '@substrate-system/keys/aes'

    const decryptedText = await AES.decrypt(encryptedText, aesKey)