Skip to content

Instantly share code, notes, and snippets.

@andimiller
Last active June 25, 2024 23:44
Show Gist options
  • Save andimiller/ece21f767e02d335316a01b50244b26d to your computer and use it in GitHub Desktop.
Save andimiller/ece21f767e02d335316a01b50244b26d to your computer and use it in GitHub Desktop.
import scala.scalanative.unsafe.*
import scala.scalanative.unsigned.*
// Raw bindings
@link("sodium")
@extern
object libsodium {
def sodium_init(): CInt = extern
// sizes
def crypto_sign_publickeybytes(): CInt = extern
def crypto_sign_secretkeybytes(): CInt = extern
def crypto_sign_ed25519_bytes(): CInt = extern
def crypto_sign_keypair(pk: Ptr[Byte], sk: Ptr[Byte]): CInt = extern
def crypto_sign_detached(sig: Ptr[Byte], siglen_p: Ptr[ULong], m: Ptr[Byte], mlen: ULong, sk: Ptr[Byte]): CInt = extern
def crypto_sign_verify_detached(sig: Ptr[Byte], m: Ptr[Byte], mlen: ULong, pk: Ptr[Byte]): CInt = extern
}
// Low level bindings
object sodium {
class SodiumCouldNotInitialise extends Throwable("libsodium's sodium_init() has failed, the library cannot be used")
def generateKeyPair(using Zone): (Array[Byte], Array[Byte]) = if (libsodium.sodium_init() >= 0) {
val pk = new Array[Byte](libsodium.crypto_sign_publickeybytes())
val sk = new Array[Byte](libsodium.crypto_sign_secretkeybytes())
assert(libsodium.crypto_sign_keypair(pk.at(0), sk.at(0)) == 0)
(pk, sk)
} else {
throw new SodiumCouldNotInitialise()
}
def signDetached(message: Array[Byte], signKey: Array[Byte])(using Zone): Array[Byte] = if (libsodium.sodium_init() >= 0) {
val sigByteCount = libsodium.crypto_sign_ed25519_bytes()
val signature = new Array[Byte](sigByteCount)
libsodium.crypto_sign_detached(signature.at(0), null, message.at(0), message.length.toULong, signKey.at(0))
signature
} else {
throw new SodiumCouldNotInitialise()
}
def verifyDetached(signature: Array[Byte], message: Array[Byte], publicKey: Array[Byte])(using Zone): Int = if (
libsodium.sodium_init() >= 0
) {
libsodium.crypto_sign_verify_detached(signature.at(0), message.at(0), message.length.toULong, publicKey.at(0)).toInt
} else {
throw new SodiumCouldNotInitialise()
}
}
import scodec.bits.ByteVector
// OO Bindings
object nacl {
opaque type PublicKey = ByteVector
object PublicKey:
def fromHex(s: String): Option[PublicKey] = ByteVector.fromHex(s)
opaque type SecretKey = ByteVector
object SecretKey:
def fromHex(s: String): Option[SecretKey] = ByteVector.fromHex(s)
extension (sk: SecretKey)
def sign(data: ByteVector): Signature = Zone { implicit z =>
ByteVector(sodium.signDetached(data.toArray, sk.toArray))
}
opaque type Signature = ByteVector
object Signature:
def fromHex(s: String): Option[Signature] = ByteVector.fromHex(s)
extension (sig: Signature)
def isValid(data: ByteVector, pk: PublicKey): Boolean = Zone { implicit z =>
sodium.verifyDetached(sig.toArray, data.toArray, pk.toArray) == 0
}
def generateKeyPair(): (PublicKey, SecretKey) = Zone { implicit z =>
val (pk, sk) = sodium.generateKeyPair
(ByteVector(pk), ByteVector(sk))
}
}
import cats.effect.*
import cats.Show
// FP bindings
object sodium4s {
opaque type PublicKey = ByteVector
object PublicKey:
def fromHex(s: String): Option[PublicKey] = ByteVector.fromHex(s)
given Show[PublicKey] = _.toHex
opaque type SecretKey = ByteVector
object SecretKey:
def fromHex(s: String): Option[SecretKey] = ByteVector.fromHex(s)
given Show[SecretKey] = _.toHex
opaque type Signature = ByteVector
object Signature:
def fromHex(s: String): Option[Signature] = ByteVector.fromHex(s)
given Show[Signature] = _.toHex
trait Sodium[F[_]] {
def generateKeyPair(): F[(PublicKey, SecretKey)]
def sign(sk: SecretKey)(data: ByteVector): F[Signature]
def validate(pk: PublicKey)(sig: Signature)(data: ByteVector): F[Boolean]
}
object Sodium {
def create[F[_]: Sync] = new Sodium[F] {
def generateKeyPair(): F[(PublicKey, SecretKey)] =
Sync[F].delay {
Zone { implicit z =>
val (pk, sk) = sodium.generateKeyPair
(ByteVector(pk), ByteVector(sk))
}
}
def sign(sk: SecretKey)(data: ByteVector): F[Signature] =
Sync[F].delay {
Zone { implicit z =>
ByteVector(sodium.signDetached(data.toArray, sk.toArray))
}
}
def validate(pk: PublicKey)(sig: Signature)(data: ByteVector): F[Boolean] =
Sync[F].delay {
Zone { implicit z =>
sodium.verifyDetached(sig.toArray, data.toArray, pk.toArray) == 0
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment