kingpin-apps/swift-goldilocks
A Swift package that vendors libgoldilocks
What it provides
Goldilocks.Ed448— RFC 8032 pure Ed448 signing, verification, and
public-key derivation.
Goldilocks.X448— RFC 7748 X448 key agreement and public-key
derivation.
Goldilocks.SHAKE128/Goldilocks.SHAKE256— FIPS 202 SHAKE
extendable-output functions, one-shot and streaming.
The underlying C symbols are also available via the CGoldilocks module for consumers that want to skip the Swift wrapper.
Platforms
This package builds and runs everywhere Swift does. It carries no external crypto dependencies — Keccak/SHAKE is implemented in the vendored libgoldilocks code, so it works on Android and Wasm where libcrypto is unavailable.
Installation
Add the dependency to your Package.swift:
dependencies: [
.package(url: "https://github.com/Kingpin-Apps/swift-goldilocks.git", from: "0.1.0"),
],Then add the product to your target. Most consumers want the Swift wrapper:
.target(
name: "MyTarget",
dependencies: [
.product(name: "Goldilocks", package: "swift-goldilocks"),
]
),Or, for raw C access:
.product(name: "CGoldilocks", package: "swift-goldilocks"),Usage
All public APIs are static functions on nested types under the Goldilocks namespace. There is no instance to construct, except for the streaming SHAKE classes that hold sponge state. Fallible APIs use Swift 6 typed throws — throws(Goldilocks.Error).
Ed448 signing (RFC 8032)
Ed448 uses a 57-byte private-key seed, a 57-byte public key, and produces 114-byte signatures.
import Goldilocks
let seed: [UInt8] = (0..<Goldilocks.Ed448.privateKeyByteCount).map { _ in
UInt8.random(in: .min ... .max)
}
let publicKey = try Goldilocks.Ed448.derivePublicKey(privateKey: seed)
let message = Array("hello".utf8)
let signature = try Goldilocks.Ed448.sign(
message: message,
privateKey: seed,
publicKey: publicKey
)
let isValid = try Goldilocks.Ed448.verify(
signature: signature,
message: message,
publicKey: publicKey
)
// isValid == truesign requires the public key alongside the private seed — the C API takes it for efficiency. If you don't have it cached, derive it first with derivePublicKey(privateKey:).
X448 key agreement (RFC 7748)
X448 uses a 56-byte scalar (private key), produces 56-byte public keys (u-coordinates), and yields a 56-byte shared secret.
import Goldilocks
let aliceScalar: [UInt8] = ... // 56 bytes
let bobScalar: [UInt8] = ... // 56 bytes
let alicePublic = try Goldilocks.X448.derivePublicKey(scalar: aliceScalar)
let bobPublic = try Goldilocks.X448.derivePublicKey(scalar: bobScalar)
// Alice and Bob each compute the same shared secret.
let aliceShared = try Goldilocks.X448.sharedSecret(
scalar: aliceScalar,
peerPublicKey: bobPublic
)
let bobShared = try Goldilocks.X448.sharedSecret(
scalar: bobScalar,
peerPublicKey: alicePublic
)
// aliceShared == bobSharedsharedSecret throws Goldilocks.Error.invalidPeerPublicKey if the peer's key lies in a small subgroup (which would produce the all-zero shared point — unsafe to use).
SHAKE128 / SHAKE256 (FIPS 202)
SHAKE is an extendable-output function: you choose the digest length at call time. Both variants offer a one-shot API and a streaming API. The streaming form is useful when the input arrives in pieces or when you want to draw more output later.
One-shot
import Goldilocks
let digest32 = Goldilocks.SHAKE256.hash(Array("abc".utf8), outputByteCount: 32)
let digest64 = Goldilocks.SHAKE128.hash(Array("abc".utf8), outputByteCount: 64)Streaming
let shake = Goldilocks.SHAKE128()
shake.update(Array("part 1 ".utf8))
shake.update(Array("part 2".utf8))
// Squeeze output incrementally — calls compose into a single stream.
let first32 = shake.output(byteCount: 32)
let next32 = shake.output(byteCount: 32)
// first32 + next32 == single 64-byte squeezeOnce you call output(byteCount:), the sponge is in squeeze mode and must not be update'd again. Create a fresh instance for a new input.
Errors
Every fallible API throws Goldilocks.Error:
public enum Error: Swift.Error, Equatable, Sendable {
case invalidKeyLength(expected: Int, actual: Int)
case invalidSignatureLength(expected: Int, actual: Int)
case invalidPeerPublicKey
}Because the API uses typed throws, a do / catch block can match each case exhaustively without the usual as Goldilocks.Error cast.
Raw C surface
If you'd rather drive libgoldilocks directly — for example to share state with existing C code — depend on CGoldilocks instead and import its symbols:
import CGoldilocks
var publicKey = [UInt8](repeating: 0, count: Int(CE_ED448_PUBLIC_KEY_BYTES))
publicKey.withUnsafeMutableBufferPointer { pubBuf in
privateKey.withUnsafeBufferPointer { privBuf in
ce_ed448_derive_public_key(pubBuf.baseAddress, privBuf.baseAddress)
}
}The C surface exposes the ce_ed448_, ce_x448_, and cg_shake_* families. See the headers in Sources/CGoldilocks/include.
Documentation
API reference is published via DocC. Build it locally with:
swift package generate-documentation --target GoldilocksOr open the package in Xcode and choose Product → Build Documentation. The hosted reference lives at swiftpackageindex.com/Kingpin-Apps/swift-goldilocks.
License
MIT, with the bundled libgoldilocks code carrying its own MIT attribution (see Sources/CGoldilocks/LICENSE.libgoldilocks.txt).
Package Metadata
Repository: kingpin-apps/swift-goldilocks
Default branch: main
README: README.md