Skip to content
Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1095 lines (703 sloc) 30.5 KB

Index




Preface

Shambhala is a convenient and secure transaction-signing service based on:

Preface

User is the only owner of the MASTER MNEMONIC with an optional PASSPHRASE (see BIP-0039) and thus effectively is the only owner of the MASTER PRIVATE KEY to the account. The user is, therefore, the one and only entity in full control of such account.

Opting-out of shambhala service is strictly up to the user and cannot be prevented.

Transaction XDR and signatures exchange is done using in-browser cross-origin communication preventing most of the threats listed in Security Best Practices chapter of SEP-0007.




Modes of operation

Also variants of onboarding.

  • [Variant A] - provides full and secure key management solution. Generates "MASTER" MNEMONIC for a user and allows him/her to choose a PASSPHRASE. Typical cases include:

    • no previous experience with the stellar ecosystem
    • no account entry on the stellar ledger
    • no ownership of private key(s) for the stellar account(s)
    • rudimentary/no knowledge about general crypto
  • [Variant B] - assumes that user possesses means of generating a valid signature that can be attached to a transaction, which should be then successfuly validated by the stellar network. Account has to have a minimum reserve for data subentries as well as operation costs. Valid signature allows for full, secure key management for the account the user is claiming. Typical cases include:

    • user has previously generated a valid public/private keypair for stellar network and has created account with enough funds
    • user owns a secure element keyring (also known as the "hardware wallet" i.e. LedgerHQ) and can sign a transaction on the stellar network
    • user possesses some knowledge about cryptography, stellar ecosystem and can manage signing of transactions externally by any means



Actors

  • user - [👨👩] - A human interacting with the system.

  • host application - [💻] - Any application that operates on / interacts with the stellar network and needs a robust transaction signing mechanism, e.g. wallet or exchange.

  • client - [🎭] - Shambhala frontend service interacting with the user, host application and server. Responsible for storing data in Web Storage, key derivation, encryption/dectyption of secrets and, most importantly, inspecting and signing transactions.

  • server - [🏢] - Shambhala backend service interacting with the client. Responsible for storing data in database, key validation, encryption/decryption of secrets, checking permissions, checking constraints and signing transactions.




Architecture overview

Architecture overview




Data units

  • account id - related:

    • KEYPAIR - StellarSDK.Keypair object
    • PASSPHRASE - up to 100 characters long, alphanumeric word
    • G_MNEMONIC - genesis 24 word MASTER MNEMONIC with an optional PASSPHRASE, stored by user (in a form of e.g. paper wallet) and known only to him/her
    • G_PUBLIC - user account number, stellar public key (G...), derived from G_MNEMONIC
    • GKP - genesis KEYPAIR, derived from G_MNEMONIC, comprised of G_PUBLIC and "MASTER" PRIVATE KEY
  • client side - related:

    • C_SECRET - client secret, stellar private key (S...)
    • C_PUBLIC - client public key (G...)
    • CKP - client KEYPAIR, (C_PUBLIC and C_SECRET)
    • SALT - random 512-bit value, generated using CSPRNG, stored using Web Storage
    • C_UUID - client's Universally Unique Identifier, stored using Web Storage
    • PIN - minimum 5-digits secret (alphanumeric as option) - known only by user
    • C_PASSPHRASE - passphrase derived from S_KEY and PEPPER
    • C_KEY - key derived from C_PASSPHRASE and SALT, used to encrypt/decrypt ENC_CKP
    • ENC_CKP - C_SECRET encrypted using C_KEY, stored using Web Storage
  • server side - related:

    • S_SECRET - server secret, stellar private key (S...)
    • S_PUBLIC - server public key (G...)
    • SKP - server KEYPAIR, (S_PUBLIC and S_SECRET)
    • S_KEY - key derived from PIN and SALT, used to encrypt/decrypt ENC_SKP
    • PEPPER - random 256-bit value, generated using CSPRNG, not stored anywhere in a plain form
    • ENC_SKP - ([PEPPER, S_SECRET]) encrypted with S_KEY, stored on server
  • transaction - related:

    • TX_PAYLOAD - TransactionSignaturePayload XDR (as defined in StelarSDK)
    • C_SIGNATURE - TX_PAYLOAD signed with CKP
    • S_SIGNATURE - TX_PAYLOAD signed with SKP



Fundamental operations

Current set of fundamental operations is build on top of:


Crypto-related operations

For current implementation please refer to cryptops.js.

  • generation of a random value which can be used as a salt, nonce or pepper:

    // random 256-bit value
    const salt32 = () => { ... }
    
    // random 512-bit value
    const salt64 = () => { ... }
  • password-based key-derivation:

    // pbkdf2, not too many iterations, 256-bit output
    const genKey = (pass, salt32) => { ... }
    
    // scrypt, 2^16 iterations, 512-bit output
    const deriveKey = (pass, salt64) => { ... }
  • symmetric encryption:

    // two round xsalsa20-poly1305/aes encryption,
    // key composed of two 256-bit keys
    const encrypt = (key, secret) => { ... }
  • symmetric decryption:

    // two round aes/xsalsa20-poly1305 decryption,
    // key composed of two 256-bit keys
    const decrypt = (key, ciphertext) => { ... }
  • generation of 128-bit Universally Unique Identifier:

    // monotonic, 128-bit value:
    //     - 48 bits of miliseconds since epoch
    //     - 32 bits of truncated `sha256` sum of userAgent string
    //     - 48 random bits
    const genUUID = () => { ... }

Transaction-related operations

For current implementation please refer to txops.js.

  • transaction inspection:

    // decode relevant information from a given
    // `TransactionSignaturePayload` XDR
    const inspectTSP = (tspXDR) => { ... }
  • transaction signing:

    // sign given `TransactionSignaturePayload` XDR using provided
    // _stellar_ secret key and produce valid signature
    const signTSP = (secret, tspXDR) => { ... }



Onboarding process

User Interface:


1. Initial state

  • client - [🎭]:

    let clientData = {
        G_PUBLIC: null,
        C_UUID: null,
        C_PUBLIC: null,
        S_PUBLIC: null,
        SALT: null,
        ENC_CKP: null
    }
  • server - [🏢]:

    let serverData = {
        G_PUBLIC: null,
        C_UUID: null,
        S_PUBLIC: null,
        ENC_SKP: null
    }

2. [Variant A] - Onboarding with an account creation

For current implementation please refer to client/generate_address.js and server/generate_address.js.

Onboarding with an account creation

  • user - [👨👩]:

    • doesn't have an account on the stellar network
    • initiated the process from within a host application
  • host application - [💻]:

    const shambhala = new Shambhala(...)
    let G_PUBLIC = await shambhala.generateAddress()
  • client - [🎭]:

    • [❗] - generate G_MNEMONIC and present it to the user:

      let G_MNEMONIC = redshift.genMnemonic()
    • ask user to create PASSPHRASE (can be empty)

    • [❗] - create GKP (path 0):

      let GKP = func.compose(
          redshift.genKeypair,
          redshift.mnemonicToSeedHex
      )(G_MNEMONIC, PASSPHRASE)
    • [💥] - destroy G_MNEMONIC and PASSPHRASE

    • extract G_PUBLIC, store it and send it to the server:

      let G_PUBLIC = GKP.publicKey()
    • generate C_UUID, store it and send it to the server:

      let C_UUID = cryptops.genUUID()
    • confirm account creation to the host application by passing extracted G_PUBLIC

  • server - [🏢]:

    • receive G_PUBLIC and C_UUID from the client and store them
  • host application - [💻]:

    • ask sfox service (friendbot on test net) to fund G_PUBLIC with minimal amount of XLM, required to perform appropriate setOptions operation that adds two new ed25519 keys as signers. The number of account subentries will increase by two. The minimal required amount of XLM will be kept as the minimum account balance required by these subentries.

3. [Variant B] - Onboarding with existing account association

For current implementation please refer to client/associate_address.js and server/associate_address.js.

Onboarding with existing account association

  • user - [👨👩]:

    • already has a stellar network account with funds
    • initiated the process from within a host application
  • host application - [💻]:

    let shambhala = new Shambhala(...)
    await shambhala.associateAddress("GDVKSJYZ...")
  • client - [🎭]:

    • receive G_PUBLIC from a host application

    • validate checksum of the received G_PUBLIC:

      StellarSDK.Keypair.fromPublicKey(G_PUBLIC)
    • present G_PUBLIC to the user and ask for confirmation

    • generate C_UUID, store it and send it to the server:

      let C_UUID = cryptops.genUUID()
    • store G_PUBLIC and send it to the server

  • server - [🏢]:

    • receive G_PUBLIC and C_UUID from the client and store them

4. Signing keys generation, encryption and storage

For current implementation please refer to client/generate_signing_keys.js and server/generate_signing_keys.js.

Signing keys generation

  • user - [👨👩]:

    • already has a stellar network account with funds and has associated it with Shambhala service (has done step 2. or step 3. described above)
  • host application - [💻]:

    await shambhala.generateSigningKeys("GDVKSJYZ...")
  • client - [🎭]:

    • validate received G_PUBLIC from host application and check if it has been associated with shambhala service

    • generate and store SALT:

      let SALT = cryptops.salt64()
    • ask user to provide PIN (should be double-checked by a two independent input fields)

    • compute S_KEY:

      let S_KEY = await cryptops.deriveKey(PIN, SALT)
    • send S_KEY to the server

    • [💥] - destroy PIN and S_KEY

  • server - [🏢]:

    • receive S_KEY from the client

    • generate S_SECRET:

      let S_SECRET = StellarSDK.Keypair.random().secret()
    • extract S_PUBLIC from S_SECRET and store it:

      let S_PUBLIC = StellarSDK.Keypair.fromSecret(S_SECRET).publicKey()
    • generate PEPPER:

      let PEPPER = cryptops.salt32()
    • compute and store ENC_SKP:

      let ENC_SKP = cryptops.encrypt(S_KEY, [PEPPER, S_SECRET])
    • compute C_PASSPHRASE:

      let C_PASSPHRASE = cryptops.genKey(S_KEY, PEPPER)
    • send C_PASSPHRASE and S_PUBLIC to the client

    • [💥] - destroy S_KEY, S_SECRET, PEPPER and C_PASSPHRASE

  • client - [🎭]:

    • receive C_PASSPHRASE and S_PUBLIC from the server

    • store S_PUBLIC

    • compute C_KEY:

      let C_KEY = cryptops.deriveKey(C_PASSPHRASE, SALT)
    • generate C_SECRET:

      let C_SECRET = StellarSDK.Keypair.random().secret()
    • compute and store ENC_CKP:

      let ENC_CKP = cryptops.encrypt(C_KEY, C_SECRET)
    • extract C_PUBLIC from C_SECRET and store it:

      let C_PUBLIC = StellarSDK.Keypair.fromSecret(C_SECRET).publicKey()
    • [💥] - destroy C_PASSPHRASE, C_KEY, C_SECRET

    • respond to the host application with [C_PUBLIC, S_PUBLIC]


5. Actual state

  • user - [👨👩]:

    • know his/her G_MNEMONIC and an optional PASSPHRASE
    • know his/her PIN
    • know his/her account public address - G_PUBLIC
    • is aware that C_PUBLIC, S_PUBLIC and corresponding C_SECRET and S_SECRET have been created
  • host application - [💻]:

    • has G_PUBLIC, C_PUBLIC and S_PUBLIC
    • G_PUBLIC is created (account exists on the network) and has minimum required balance
  • client - [🎭]:

    let clientData = {
        G_PUBLIC: "GDVKSJYZ...",
        C_UUID: "01663f03...",
        C_PUBLIC: "GDNQLOS2...",
        S_PUBLIC: "GASVBIEZ...",
        SALT: "1f93a39461e87f2320071ad38362685...",
        ENC_CKP: "2rAAAey7/PNEaPpKNG8BtEZZOXFGaXLPDG..."
    }
  • server - [🏢]:

    let serverData = {
        G_PUBLIC: "GDVKSJYZ...",
        C_UUID: "01663f03...",
        S_PUBLIC: "GASVBIEZ...",
        ENC_SKP: "AWY/D5eKB9FxqqS/wt8XDgfDy..."
    }

6. [Variant A] - Automatic keys association

For current implementation please refer to client/generate_signed_key_assoc_tx.js.

  • host application - [💻]:

    • has G_PUBLIC, C_PUBLIC and S_PUBLIC

    • shambhala is asked to generate signed transaction allowing S_PUBLIC and C_PUBLIC to sign on behalf of G_PUBLIC:

      await shambhala.generateSignedKeyAssocTX(G_PUBLIC)
  • client - [🎭]:

    • [❗] - GKP is present in memory (NOT in the persistent storage), as it was created in onboarding with an account creation.

    • G_PUBLIC, C_PUBLIC and S_PUBLIC are present in memory

    • construct StellarSDK.Transaction with an appropriate setOptions operation:

      • G_PUBLIC, C_PUBLIC, S_PUBLIC weights
      • appropriate tresholds
      • C_PUBLIC and S_PUBLIC allowed for multisig on G_PUBLIC
    • [❗] - sign the transaction with GKP

    • [💥] - destroy GKP

    • return signed transaction to a host application

  • host application - [💻]:

    • send transaction to the stellar network

7. [Variant B] - Manual keys association

For current implementation please refer to client/generate_key_assoc_tx.js.

  • host application - [💻]:

    • has G_PUBLIC, C_PUBLIC and S_PUBLIC

    • shambhala is asked to generate transaction allowing S_PUBLIC and C_PUBLIC to sign on behalf of G_PUBLIC:

      await shambhala.generateKeyAssocTX(G_PUBLIC)
  • client - [🎭]:

    • G_PUBLIC, C_PUBLIC and S_PUBLIC are present in memory

    • construct StellarSDK.Transaction with an appropriate setOptions operation:

      • G_PUBLIC, C_PUBLIC, S_PUBLIC weights
      • appropriate tresholds
      • C_PUBLIC and S_PUBLIC allowed for multisig on G_PUBLIC
    • present TX_PAYLOAD to the user

    • return created transaction to the host application

  • host application - [💻]:

    • receive TX_PAYLOAD from shambhala service

    • if user has LedgerHQ, let him sign the transaction with it and send the signed transaction to the stellar network

    • (alternatively): redirect to appropriate place in Stellar Laboratory

  • user - [👨👩]:




Transaction signing process

User Interface:


Signing flow

Signing flow


1. Initial state

  • user - [👨👩]:

    • know his/her PIN
    • has already associated C_PUBLIC and S_PUBLIC to his/her account during the process of onboarding
  • client - [🎭]:

    let clientData = {
        G_PUBLIC: "GDVKSJYZ...",
        C_UUID: "01663f03...",
        C_PUBLIC: "GDNQLOS2...",
        S_PUBLIC: "GASVBIEZ...",
        SALT: "1f93a39461e87f2320071ad38362685...",
        ENC_CKP: "2rAAAey7/PNEaPpKNG8BtEZZOXFGaXLPDG..."
    }
  • server - [🏢]:

    let serverData = {
        G_PUBLIC: "GDVKSJYZ...",
        C_UUID: "01663f03...",
        S_PUBLIC: "GASVBIEZ...",
        ENC_SKP: "AWY/D5eKB9FxqqS/wt8XDgfDy..."
    }

2. Transaction verification

  • host application - [💻]:

    • request transaction signing:

      await shambhala.signTransaction(G_PUBLIC, TX_PAYLOAD)
  • client - [🎭]:

    • decode TX_PAYLOAD received from a host application and present it to the user:

      return txops.inspectTSP(TX_PAYLOAD)
      • source account identifier
      • transaction fee
      • sequence number
      • time bounds
      • memo
      • operations

3. Transaction signing

For current implementation please refer to client/sign_transaction.js and server/sign_transaction.js.

  • client - [🎭]:

    • ask user to provide a PIN

    • compute S_KEY:

      let S_KEY = cryptops.deriveKey(PIN, SALT)
    • send S_KEY and TX_PAYLOAD to the server

    • [💥] - destroy PIN and S_KEY

  • server - [🏢]:

    • receive S_KEY and TX_PAYLOAD from the client

    • extract S_SECRET and PEPPER by decrypting ENC_SKP:

      let [PEPPER, S_SECRET] = cryptops.decrypt(S_KEY, ENC_SKP)
    • if the extraction has failed it means that received S_KEY is invalid, so potential data breach occured on the client side or simply user made a typo in KEY. In such scenario next steps are impossible, so the procedure has to be aborted, failure counter has to be increased and the client has to be notified. Security precautions can be implemented in this step, for example after 3 failed attempts communication with this client should be suspended for a longer period of time, etc.

    • generate SKP:

      let SKP = StellarSDK.Keypair.fromSecret(S_SECRET)
    • compute S_SIGNATURE by singing TX_PAYLOAD with SKP:

      let S_SIGNATURE = txops.signTSB(SKP, TX_PAYLOAD)
    • [💥] - destroy SKP and S_SECRET

    • compute C_PASSPHRASE:

      let C_PASSPHRASE = cryptops.genKey(S_KEY, PEPPER)
    • send C_PASSPHRASE and S_SIGNATURE to the client

    • [💥] - destroy S_KEY, PEPPER, C_PASSPHRASE

  • client - [🎭]:

    • receive C_PASSPHRASE and S_SIGNATURE from the server

    • compute C_KEY:

      let C_KEY = cryptops.deriveKey(C_PASSPHRASE, SALT)
    • extract C_SECRET by decrypting ENC_CKP:

      let C_SECRET = cryptops.decrypt(C_KEY, ENC_CKP)
    • generate CKP:

      let CKP = StellarSDK.Keypair.fromSecret(C_SECRET)
    • compute C_SIGNATURE by singing TX_PAYLOAD with CKP:

       let C_SIGNATURE = txops.signTSB(CKP, TX_PAYLOAD)
    • [💥] - destroy CKP, C_SECRET, C_PASSPHRASE and C_KEY

    • send S_SIGNATURE and C_SIGNATURE to the host application

  • host application - [💻]:

    • attach received signatured to constructed transaction

    • send the signed transaction to the stellar network




Offboarding process (idea)

  • User uses his/her G_MNEMONIC with PASSPHRASE to generate GKP and dissociate (with an appropriate setOptions operation) C_PUBLIC and S_PUBLIC keys from his/her account.

  • User demands from shambhala (by calling appropriate API function) to delete ENC_CKP and ENC_SKP structures.

  • Shambhala checks signers array on a G_PUBLIC account and if there are no C_PUBLIC and S_PUBLIC signers, then ENC_CKP and ENC_SKP can be safely deleted.




Notes

  • There's no way to enforce garbage collect process / shredding of memory remains in JavaScript. All places marked with [💥] are currently hard (unknown) to achieve in a JavaScript environment.

  • After onboarding it is desirable to turn off the phone / computer for at least 30 (??) seconds to wipe out DRAM.

  • Steps marked with [❗] in the onboarding process are fragile from the security point of view. They are related to very sensitive areas - generation of G_MNEMONIC, access to and usage of GKP and "MASTER" PRIVATE KEY.

  • There is always a tradeoff between security and convenience. Also, the more convenient a solution is (in the sense, that an user is not neccesarily an expert in a given field), the more chance there is that some degree of trust to a third party has to be calculated in.

  • Safety of a described scheme is build on a following foundations:

    • all communication between host application and client is done within browser and using cross-origin communication methods
    • all communication betwen client and server is done over HTTPS
    • there are two equally important keypairs (CKP and SKP), both stored in an encrypted form (ENC_CKP and ENC_SKP respectively)
    • secret keys of both CKP and SKP are never leaving their environments (that is Web Storage in case of CKP and server-side database in case of SKP)
    • user knows PIN and has SALT, which are the two neccessary ingredients to derive S_KEY needed to decrypt SKP, so in case of server-side database breach SKP is safe
    • decryption of CKP depends on user knowledge (PIN) and, amongs other things, on element stored in encrypted form on server (PEPPER), so in case of client-side storage breach CKP is safe
    • if user will loose a device with stored CKP or forget a PIN or simply want to cancel using shambhala transaction-signing scheme, his/her funds on the stellar network are safe, because he/she knows G_MNEMONIC with PASSPHRASE from which the master key can be derived
    • the same apply to the situation in which eventual server-side data loss occurs - master key is in the posession of a user, so funds on the stellar account are safe and accessible
  • Strong emphasis has to be put on all security considerations related to onboarding process in [Variant A].

You can’t perform that action at this time.