Skip to content

Examples

Log in

There are 2 ways to log in and you should choose the most appropriate one based on your application's needs.

TL;DR

Applications that utilize ONLY publicly available information can use connect. Applications that store presonal information MUST use requestSignBuffer to verify the validity of the key.

  • connect: only checks if the user has the required private key in their wallet. This doesn't guarantee that the key is valid.
  • requestSignBuffer: can be used to sign a known message with the user's private key and verify that it's valid.

To display the trade history of an account connect can be used, because the information publicly available in the blockchain. To store user settings or preferences on your server the log in must be secure and requestSignBuffer must be used.

Quick connect

Alice is building a simple dex that allows users to swap HIVE to HBD. She could blindly requestBroadcast to perform the swap, but to make her app's user experience better she wants to verify that the users have their active key stored in the wallet. She can just call the connect function.

javascript
// Account to connect, if left empty the user can choose
const account = ''

// Request connect
const res = await peakvault.connect(account, 'active')

if (res.success) {
  // Get the account used to log in
  const chosenAccount = res.account
  // Swap
} else {
  // Error, user doesn't have the active key
  console.log(res.error)
}

Leaving the account parameter empty allows the user to choose with which account to connect, while specifying account forces the connection with a specific account.

Alice now wants to diplay trade history, does she need a more secure form of log in? No, because the trade data is public and can be retrieved from the blockchain.

Web3 Log in

Alice wants to make her dex more complex and wants to allow users to store preferences and settings on her platform. She needs a server and a secure way for users to log in. So she makes users sign a message with their private key, then on her server she verifies the signature using the corresponding public key.

To prevent malicious behaviour the message should always be different. A way to achieve this is to use account name + date.

Client side

javascript
// Account to log into, must be specified
const account = 'muwave'
// Message to sign
const message = `${account}${Date.now()}`
// Key used to sign
const keyRole = 'posting'

// Request signature
const res = await peakvault.requestSignBuffer(account, keyRole, message)

if (res.success) {
  // Extract the signature
  const signature = res.result
  // Send message and signature to Alice's server to validate the signature
  const data = {
    account,
    signature,
    keyRole
  }
  verifySignature(message, data)
}

Server side

Server side Alice has access to the message and data variables passed through her verifySignature function. She wants to see if the decoded signature holds the correct message. Depending on the Hive library used on the backend, the speficic implementation differs. We are going to demonstrate with hive-tx. A more general example can be found here.

References: getPublicKeys.

javascript
import { PublicKey, Signature } from 'hive-tx'

const verifySignature = async (message, data) => {
  // Get public keys as { [KeyRole]: string[] }
  const publicKeys = await getPublicKeys(data.account)
  // Get buffer from message
  const buff = await createBuffer(message)
  // Get signature
  const signature = Signature.from(data.signature)

  // The user could have multiple keys of the same role (e.g. 4 posting keys)
  // We need to test all of them against the signature
  for (const key of publicKeys[data.keyRole]) {
    const pubKey = PublicKey.from(key)
    // Verify signature with public key
    const isVerified = pubKey.verify(buff, signature)
    if (isVerified) {
      return true
    }
  }

  return false
}
javascript
const createBuffer = async (message: string) => {
  const encoded = new TextEncoder().encode(message)
  const sha = await crypto.subtle.digest('SHA-256', encoded)
  return Buffer.from(sha)
}

Get account information

This function doesn't use Peak Vault in any way.

typescript
const getAccountData = async (account: string) => {
  // Request url and body
  const url = 'https://api.hive.blog'
  const requestBody = {
    jsonrpc: '2.0',
    method: 'condenser_api.get_accounts',
    params: [[account]],
    id: 1
  }

  // Send POST request to Hive API
  const resAccountData = await fetch(url, {
    body: JSON.stringify(requestBody),
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    method: 'POST'
  })

  // Parse response
  const accountData = (await resAccountData.json()).result[0]

  return accountData
}

Get account public keys

An account can have multiple posting and active keys, but only one memo key. To simplify things, in this function we return an array also for the memo key.

References: getAccountData.

typescript
const getPublicKeys = async (
  account: string
): Promise<{ [keyRole in 'posting' | 'active' | 'memo']: string[] }> => {
  // Get account data
  const accountData = await getAccountData(account)

  // Extract public keys
  const publicKeys = {
    posting: accountData.posting.key_auths.map((el: [string, number]) => el[0]),
    active: accountData.active.key_auths.map((el: [string, number]) => el[0]),
    memo: [accountData.memo_key]
  }

  return publicKeys
}

Authorities

Every Hive account comes with 3 authorities: owner, active and posting. Authorities determine who can sign a transaction with the account. They do so by storing which keys and usernames are allowed. When a new account is created, a private-public key pair is generated for each authority. The public keys are stored along with the account's public information. This is what the getPublicKeys methods exploits.

Each authority is an array [string, number], where the string is the public key (or account name) and the number the weight. The weight determines how strong the signature is. By default each account authority has a weight_threshold of 1, which means that only 1 signature is required to approve transactions. This may be different for multi-sig accounts. For exmaple, an account could have active weight_threshold of 3, so a total summed weight of 3 would be required to approve an active transaction.

Assume Alice has created a new account for Bob, her child. She wants to allow Bob to post on Hive's social media platforms, but she doesn't want him to be able to approve any transaction that involves money. Monetary operations are controlled by the active authority, so she needs to add herself to Bob's active authority and then remove him.

Bob's active authority only has his public active key with a weight of 1:

json
"active": {
  "weight_threshold": 1,
  "account_auths": [],
  "key_auths": [["STM7Mg5yWyeUAYz7smMVWFaWwzmUzSPVdEmXfEHkYAcRyR2F8bSPV", 1]]
}

Add authority

There are two ways Alice can gain control of Bob's active authority: add her username or her public key to Bob's active authority. To do so she needs to approve a transaction from Bob's account, using his active key. Since she just created Bob's account, the weight_threshold on the authority is 1, so Alice's weight just needs to be 1.

The easiest way is to add her username (@alice) to Bob's active account authority list.

typescript
const authorizer = 'bob'
const accountToAuthorize = 'alice'
const weight = 1
const keyRole = 'active'

// Request add account authority
const res = await peakvault.requestAddAccountAuthority(
  authorizer,
  accountToAuthorize,
  weight,
  keyRole
)

An alternative to do the same thing is adding Alice's public active key to Bob's key authorities.

typescript
const authorizer = 'bob'
const keyToAuthorize = 'STM7Mg5yWyeUAYz7smMVWFaWwzmUzSPVdEmXfEHkYAcRyR2F8bSPV'
const weight = 1
const keyRole = 'active'

// Request add key authority
const res = await peakvault.requestAddKeyAuthority(authorizer, keyToAuthorize, weight, keyRole)

Remove authority

At this point both Alice and Bob can sign active transactions on Bob's account. Assuming alice added her username as account_auth, Bob's active authority now looks like this:

json
"active": {
  "weight_threshold": 1,
  "account_auths": [["alice", 1]],
  "key_auths": [["STM7Mg5yWyeUAYz7smMVWFaWwzmUzSPVdEmXfEHkYAcRyR2F8bSPV", 1]]
}

However that's not what Alice wants: she wants exclusive control of Bob's active authority, so she needs to remove his public key.

typescript
// Get Bob's public active key
const bobPublicKeys = await getPublicKeys('bob')

const account = 'bob'
const keyToRemove = bobPublicKeys['active']
const keyRole = 'active'

const res = await peakvault.requestRemoveKeyAuthority(account, keyToRemove, keyRole)

Please note that since now Alice has active authority on Bob's account, she could sign this transaction with her own private active key.

Now Bob cannot sign active transactions on his account anymore and this is his active authority:

json
"active": {
  "weight_threshold": 1,
  "account_auths": [["alice", 1]],
  "key_auths": []
}

References: getPublicKeys.