Appearance
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 - deprecated
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
.