Key Ring
Note:
This functionality is only available with BramblSC. Please see here for steps to setup BramblSC
What's a Topl Key Ring?
The BramblSC Key Ring allows you to manage multiple addresses at a time including reading your balance, sending transactions, and more!
The Key Ring is only a tool for managing your Topl addresses. That means you can switch between BramblSC Key Ring and BramblJS Key Manager at any time. The advantage of using the Key Ring is that you can manage several addresses from one application.
It can be thought of in terms of a cold wallet that stores your keys offline and only uses the keys to sign transactions locally. This makes it very secure and less prone to hacker attacks.
In addition, you can use the Key Ring to sign transactions using your private key file.
The Key Ring is created automatically when instantiating a Brambl instance, and uses the key_file_directory parameter as the export location for the key-file that is generated when instantiating a Brambl instance.
The Key Ring is able to create a new secure Topl Keyfile for you, or work with an existing Keyfile.
Initial Setup
In each of the following examples, we'll start with the following objects in scope
import akka.actor.ActorSystem
import cats.data.{EitherT, NonEmptyChain}
import cats.implicits._
import co.topl.akkahttprpc.RpcClientFailure
import co.topl.akkahttprpc.implicits.client.rpcToClient
import co.topl.attestation.keyManagement.{KeyRing, KeyfileCurve25519, PrivateKeyCurve25519}
import co.topl.attestation.{Address, AddressEncoder, PublicKeyPropositionCurve25519}
import co.topl.client.Provider.PrivateTestNet
import co.topl.modifier.box.{AssetCode, AssetValue}
import co.topl.rpc.ToplRpc
import co.topl.rpc.ToplRpc.NodeView._
import co.topl.rpc.ToplRpc.Transaction.{BroadcastTx, RawArbitTransfer, RawAssetTransfer, RawPolyTransfer}
import co.topl.rpc.implicits.client._
import io.circe.syntax.EncoderOps
import scala.concurrent.{ExecutionContext, Future}
object exampleState {
type RpcErrorOr[T] = EitherT[Future, RpcClientFailure, T]
val provider: Provider = new PrivateTestNet(apiKey = "topl_the_world!")
implicit val system: ActorSystem = ActorSystem()
implicit val executionContext: ExecutionContext = system.dispatcher
val externalAddress: Seq[Address] = List(
"AUAvJqLKc8Un3C6bC4aj8WgHZo74vamvX8Kdm6MhtdXgw51cGfix",
"AU9upSwu8MtmQz6EBMuv34bJ4G8i6Aw64xxRShJ3kpZRec5Ucp9Q",
"AU9NkZmX5Pch2kUA28GUtv9m4bNaLNtKLoFXphcAAc9PUQXinXRm",
"AU9avKWiVVPKyU9LoMqDpduS4knoLDMdPEK54qKDNBpdnAMwQZcS",
"AU9Xs4B5HnsTiYGb7D71CCxg5mYhaQv1WH3ptfiGbV4LUGb87W54",
"AUA3RmKwr39nVQFFTV1BQFELbFhJQVWfFDdS5YDx7r1om5UCbqef",
"AU9dn9YhqL1YWxfemMfS97zjVXR6G9QX74XRq1jVLtP3snQtuuVk",
"AUANVY6RqbJtTnQS1AFTQBjXMFYDknhV8NEixHFLmeZynMxVbp64",
"AU9sKKy7MN7U9G6QeasZUMTirD6SeGQx8Sngmb9jmDgNB2EzA3rq",
"AUAbSWQxzfoCN4FizrKKf6E1qCSRffHhjrvo2v7L6q8xFZ7pxKqh"
).map(s => AddressEncoder.fromStringWithCheck(s, provider.networkPrefix).get)
val keyRing: KeyRing[PrivateKeyCurve25519, KeyfileCurve25519] =
KeyRing.empty[PrivateKeyCurve25519, KeyfileCurve25519]()(
provider.networkPrefix,
PrivateKeyCurve25519.secretGenerator,
KeyfileCurve25519Companion
)
val assetCode: AssetCode = AssetCode(1: Byte, externalAddress.head, "test_1")
def genKeys(): Unit = keyRing.generateNewKeyPairs(10, Some("test"))
def clearKeyRing(): Unit = keyRing.addresses.map(keyRing.removeFromKeyring)
}
Create a new key and add it to the keyRing
Here we will generate a new keyfile and add the newly generated key to the KeyRing as a side effect. Note: Anytime a new address is generated, it is added to the keyRing.
object CreateANewKeyInTheKeyRing {
import exampleState._
import provider._
val genKeyfile: Either[RpcClientFailure, KeyfileCurve25519] = Brambl.generateNewCurve25519Keyfile("password", keyRing)
def main(args: Array[String]): Unit =
genKeyfile match {
case Left(value) => println(s"Got some error: $value")
case Right(value) => println(s"Got a success response: ${value.asJson}")
}
}
How to reinstate a keyfile and get its Json.
Keyfiles are most easily transported using encrypted JSON files. These key files are encrypted using a symmetric "password" key to generate the ciphertext.
object ReinstateAKeyFile {
import exampleState._
import provider._
val response: Either[RpcClientFailure, Address] = for {
keyfileJson <- CreateANewKeyInTheKeyRing.genKeyfile.map { keyfile =>
println(s"keyRing after generating a new key: ${keyRing.addresses}")
keyRing.removeFromKeyring(keyfile.address) // side effect mutation of keyRing
println(s"keyRing after removing generated key: ${keyRing.addresses}")
keyfile.asJson
}
address <- Brambl.importCurve25519JsonToKeyRing(keyfileJson, "test", keyRing)
} yield {
println(s"keyRing after re-importing the generated key from Json: ${keyRing.addresses}")
address
}
def main(args: Array[String]): Unit =
response match {
case Left(value) => println(s"Got some error: $value")
case Right(value) => println(s"Got a success response: $value")
}
}
Example of failing to reinstate a keyfile from Json
An example response when an invalid password is provided at decryption time.
object FailedToReinstateAKeyFile {
import exampleState._
import provider._
val response: Either[RpcClientFailure, Address] = for {
keyfileJson <- CreateANewKeyInTheKeyRing.genKeyfile.map { keyfile =>
println(s"keyRing after generating a new key: ${keyRing.addresses}")
keyRing.removeFromKeyring(keyfile.address) // side effect mutation of keyRing
println(s"keyRing after removing generated key: ${keyRing.addresses}")
keyfile.asJson
}
address <- Brambl.importCurve25519JsonToKeyRing(keyfileJson, "someOtherPassword", keyRing)
} yield {
println(s"keyRing after re-importing the generated key from Json: ${keyRing.addresses}")
address
}
def main(args: Array[String]): Unit =
response match {
case Left(value) => println(s"Got some error: $value")
case Right(value) => println(s"Got a success response: $value")
}
}
Updated over 1 year ago