silk.crypto.minisign.

Sign and verify files with minisign, the Ed25519 signature format Silk releases use.

When to use#

Use verify to check a downloaded file against a publisher's minisign public key, and sign with a MinisignSecretKey to publish signatures that minisign -V and verify accept. Use silk.crypto.ed25519 for raw Ed25519 signatures in another format.

Details#

A public key is the base64 text of Ed, an 8-byte key id and a 32-byte Ed25519 key. A signature file has four lines: an untrusted comment, the base64 of an algorithm, the key id and a 64-byte signature, trusted comment: <text>, and the base64 of a 64-byte global signature over the signature followed by the trusted comment text. The ED algorithm signs the BLAKE2b-512 digest of the content and the legacy Ed algorithm signs the content itself; sign writes ED.

A secret key file holds the base64 of the algorithms, the scrypt salt and limits, and the key id, Ed25519 key and BLAKE2b-256 checksum, XORed with the scrypt output of the password when the key is encrypted. secretKeyFile encrypts with minisign's default limits, which make scrypt use 1 GiB of memory, or writes an unencrypted key like minisign -G -W without a password.

Gotchas#

Ed25519 signing is deterministic, so signing the same content with the same key and comment gives the same file. Secrets are not erased from memory.

Examples#

Sign and verify a message#

silk
import silk.core.effect { Effect }
import silk.core.result { Result }
import silk.crypto.minisign as Minisign { MinisignError, MinisignKey, MinisignSecretKey }
import silk.collections.bytes { Bytes }
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.system.random { Random }
import silk.system.random_native { OsRandom }

effect fn roundTrip() -> i32 ! OutOfMemoryError ? &mut Allocator | &mut Random {
  let secret = run MinisignSecretKey.generate()
  let signature = match run Minisign.sign(&secret, b"hello", b"greeting") {
    Result<Bytes, MinisignError>.Success { value } => move value
    Result<Bytes, MinisignError>.Failure { error } => { return 1 }
  }
  let public = match Minisign.key(&secret.publicKeyText()) {
    Result<MinisignKey, MinisignError>.Success { value } => move value
    Result<MinisignKey, MinisignError>.Failure { error } => { return 2 }
  }
  return match Minisign.verify(&public, Bytes.asSlice(&signature), b"hello") {
    Result<&[u8], MinisignError>.Success { value } => 0
    Result<&[u8], MinisignError>.Failure { error } => 3
  }
}

pub effect fn main() -> i32 ! OutOfMemoryError {
  let mut allocator = Allocator.systemAllocatorProvider()
  let mut random = OsRandom.make()
  return run roundTrip()
    |> Effect.provideMut<Random>(&mut random)
    |> Effect.provideMut<Allocator>(&mut allocator)
}

Import as Minisign with import silk.crypto.minisign as Minisign.

Public declarations: 10.

MinisignError#

silk
pub enum MinisignError

Why a key or signature was refused.

InvalidKey#

silk
InvalidKey

The public key text is not the base64 of an Ed25519 minisign key.

MalformedSignature#

silk
MalformedSignature

The signature file does not have the four minisign lines.

UnsupportedAlgorithm#

silk
UnsupportedAlgorithm

The signature names an algorithm other than ED or Ed.

WrongKey#

silk
WrongKey

The signature was made with a different key.

InvalidSignature#

silk
InvalidSignature

The signature does not verify for this content.

InvalidTrustedComment#

silk
InvalidTrustedComment

The global signature over the trusted comment does not verify.

InvalidSecretKey#

silk
InvalidSecretKey

The secret key text is not a minisign Ed25519 secret key, or its key derivation settings are out of range.

PasswordRequired#

silk
PasswordRequired

The secret key is encrypted and no password was given.

WrongPassword#

silk
WrongPassword

The password does not decrypt the secret key: its checksum does not match.

InvalidComment#

silk
InvalidComment

A trusted comment to sign has a line break or is longer than 1024 bytes.

MinisignKey#

silk
pub struct MinisignKey

A decoded minisign public key.

describe#

silk
pub fn describe(error: MinisignError) -> &'static [u8]

A short description of error for messages.

key#

silk
pub fn key(text: &[u8]) -> Result<MinisignKey, MinisignError>

Decodes the base64 public key text, such as the contents of minisign.pub after its comment line.

verify#

silk
pub fn verify<'s>(signer: &MinisignKey, signature: &'s [u8], content: &[u8]) -> Result<
  &'s [u8],
  MinisignError
>

Verifies the minisign signature file of content against signer, and returns its trusted comment.

Both the signature over the content and the global signature over the trusted comment must verify, so the returned comment is authenticated too.

MinisignSecretKey#

silk
pub struct MinisignSecretKey

A minisign secret key: its key id and Ed25519 key.

Implementation MinisignSecretKey#

silk
impl MinisignSecretKey

Associated function generate#

silk
pub effect fn generate() -> MinisignSecretKey ? &mut Random

Returns a new key with a random key id, from the Random provider.

Gotchas#

Use a secure Random provider, such as silk.system.random_native.OsRandom.

Method keyId#

silk
pub fn keyId(self: &Self) -> [u8; 8]

Returns the 8-byte key id, which signatures carry to name their key.

Method idText#

silk
pub fn idText(self: &Self) -> [u8; 16]

Returns the key id as minisign prints it: 16 uppercase hexadecimal digits of the id read as a little-endian number, such as 2EFCB60D3D0A7C1E.

Method publicKeyText#

silk
pub fn publicKeyText(self: &Self) -> [u8; 56]

Returns the base64 public key, the second line of a minisign.pub file, which key reads.

publicKeyFile#

silk
pub effect fn publicKeyFile(secret: &MinisignSecretKey) -> Bytes ! OutOfMemoryError ? &mut Allocator

Returns the text of a minisign.pub file for secret's public key: an untrusted comment naming the key id, then the base64 key.

secretKeyFile#

silk
pub effect fn secretKeyFile(secret: &MinisignSecretKey, password: Option<&[u8]>) -> Bytes
  ! OutOfMemoryError
  ? &mut Allocator | &mut Random

Returns the text of a minisign secret key file for secret, encrypted with password as minisign -G writes it, or unencrypted as minisign -G -W writes it when password is None.

Details#

Encrypting draws a random salt from Random and runs scrypt with minisign's default limits, which use 1 GiB of memory.

secretKey#

silk
pub effect fn secretKey(text: &[u8], password: Option<&[u8]>) -> Result<MinisignSecretKey, MinisignError>
  ! OutOfMemoryError
  ? &mut Allocator

Reads a minisign secret key file, decrypting it with password when it is encrypted.

Errors#

  • InvalidSecretKey when the text is not a minisign Ed25519 secret key.

  • PasswordRequired when the key is encrypted and password is None.

  • WrongPassword when the checksum does not match after decryption.

Gotchas#

Decrypting runs scrypt with the limits the file names, 1 GiB of memory for minisign's defaults.

sign#

silk
pub effect fn sign(secret: &MinisignSecretKey, content: &[u8], trustedComment: &[u8]) -> Result<Bytes, MinisignError>
  ! OutOfMemoryError
  ? &mut Allocator

Returns the minisign signature file of content for secret, with trustedComment signed too.

Details#

The signature uses the ED algorithm, over the BLAKE2b-512 digest of content, which minisign -V and verify accept.

Errors#

InvalidComment when trustedComment has a line break or is longer than 1024 bytes.

Category: Cryptography.

Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2, x86_64-pc-windows-msvc, wasm64-unknown-unknown.

View the source of this page