Secret Key

It all starts from a Secret Key. Without it the whole process is unavailable.

While constructor has next interface:

class SecretKey(
    val key: ByteArray, // 32-byte size
    val nonce: ByteArray // 32-byte size
)

It is a uniform secret key. And that would be inconvenient for you to use this, right?

Uniform

So here are two methods to use it combined!

fun fromByteArray(byteArray: ByteArray): SecretKey
fun toByteArray(): ByteArray

Considering you have a byte array of 64 bytes of full secret key, it's easy as that to instantiate it:

val my64BytesOfSecretKey: ByteArray
val secretKey: SecretKey = SecretKey.fromByteArray(my64BytesOfSecretKey)
val secretKeyAsBytes: ByteArray = secretKey.toByteArray()

ed25519

But not everyone wants to store their keys in the uniform representation. So here comes the ed25519 form:

fun fromEd25519ByteArray(byteArray: ByteArray): SecretKey
fun toEd25519ByteArray(): ByteArray

Considering you have ed25519 secret key you can use this like that to convert to sr25519:

val myEd25519SecretKey: ByteArray
val secretKey: SecretKey = SecretKey.fromEd25519ByteArray(myEd25519SecretKey)
val backAgainMyEd25519SecretKey: ByteArray = secretKey.toEd25519ByteArray()

Generating Secret Key

If you want to create random Secret Key, use this simple method:

val generatedSecretKey: SecretKey = SecretKey.generate()

Under the hood it uses Kotlin's SecureRandom from krypt library which supports Multi Platform.

But if you want to generate it using your CSPRNG, use next method:

val yourSecureRandom: kotlin.random.Random
val generatedSecretKey: SecretKey = SecretKey.generateWith(yourSecureRandom)

Signing messages

The life's goal of Secret Key.

Unfortunately, it's not that simple as passing byte array to sign, as we need to set the context for it. Like in Substrate, there is a context "substrate". You can use your own context for any kind of application.

val yourContextLabel: String
val messageToSign: ByteArray

val signingContext: SigningContext = SigningContext
    .fromContext(yourContextLabel.toByteArray())
val signingTranscript: SigningTranscript = signingContext.bytes(messageToSign)
    
val yourSecretKey: SecretKey
val signedMessage: Signature = yourSecretKey.sign(signingTranscript)

Signature object contains compressed ristretto and scalar component.

So to convert it to ByteArray for your network communication or to verify the signature use this:

val signedMessage: Signature
val signedMessageByteArray: ByteArray = signedMessage.toByteArray()

If you have a ByteArray with signature in it, and want to verify it on low level, instantiate Signature object from this byte array:

val signatureByteArray: ByteArray
val signature: Signature = Signature.fromByteArray(signatureByteArray)

Other than that there's a double check signature! We took this from Rust's schnorrkel implementation and this might be useful for some of you:

val signingTranscript: SigningTranscript
val yourSecretKey: SecretKey

val signedMessage: Signature = yourSecretKey.signDoubleCheck(signingTranscript)

Under the hood, it creates signature, converts it to byte array, reads back, verifies with public key, and if everything passed, returns the unwrapped signature. Basically, unit test within a signature generation.

But using SigningTranscript isn't so convenient, right? So here guys from W3F created next functions for easier use:

val yourContextLabel: String
val messageToSign: ByteArray

// Still, we need to set the context
val signingContext: SigningContext = SigningContext
    .fromContext(yourContextLabel.toByteArray())
    
val yourSecretKey: SecretKey
val signedMessage: Signature = yourSecretKey.signSimple(signingContext, messageToSign)
val doubleCheckedSignedMessage: Signature = yourSecretKey.signSimpleDoubleCheck(signingContext, messageToSign)

Conversion to Public Key and Key Pair.

Very simple. Just use this to calculate Public Key from Secret Key:

val mySecretKey: SecretKey
val myPublicKey: PublicKey = mySecretKey.toPublicKey()

Don't want ot keep secret and public keys separately and have all of the signing and verifying magic in one place? No problem, convert it to Key Pair!

val mySecretKey: SecretKey
val myKeyPair: KeyPair = mySecretKey.toKeyPair()

Last updated