Skip to content

Sajjon/EllipticCurveKit

⚠️ THIS SDK IS NOT SAFE/PRODUCTION READY (YET!) ⚠️

I'm no cryptography expert, If you find mistakes, inaccuracies or if you have suggestions for improvements of this README or the source code, please submit an issue!

Goal

"Swifty", safe and fast Elliptic Curve Cryptography SDK in pure Swift (no dependency to a library written in any other language than Swift).

Swifty?

Swift is a very expressible, type safe, and fast programming language having the mottos "Clarity is more important than brevity" and "Clarity at the point of use" which makes it read out as English. The main goal of this Swift SDK is to be Swifty (while also safe and fast). By the way, did you know that Swift is the fastest growing programming language?

Usage

Swift is perfect for Protocol Oriented Programming (POP) and strongly typed language, which allows for these kinds of protocols.

public protocol EllipticCurveCryptographyKeyGeneration {
    /// Elliptic Curve used, e.g. `secp256k1`
    associatedtype CurveType: EllipticCurve

    /// Generates a new key pair (PrivateKey and PublicKey)
    static func generateNewKeyPair() -> KeyPair<CurveType>

    /// Support Wallet Import Format (a.k.a. WIF)
    static func restoreKeyPairFrom(privateKey: PrivateKey<CurveType>) -> KeyPair<CurveType>

    /// A `Wallet` is a `KeyPair` and with `PublicAddresses` derived (compressed/uncompressed)
    static func createWallet(using keyPair: KeyPair<CurveType>) -> Wallet<CurveType>
}

public protocol EllipticCurveCryptographySigning {
    /// Which method to use for signing, e.g. `Schnorr`
    associatedtype SigningMethodUsed: Signing
    typealias CurveType = SigningMethodUsed.CurveType

    /// Signs `message` using `keyPair`
    static func sign(_ message: Message, using keyPair: KeyPair<CurveType>) -> SignatureType

    /// Checks if `signature` is valid for `message` or not.
    static func verify(_ message: Message, wasSignedBy signature: SignatureType, publicKey: PublicKey<CurveType>) -> Bool
}

Since both protocols above require an associatedtype which specify which Curve and Signature to use, we can use type-erased types, similar to Swift Foundation's AnyCollection or AnyHashable. We use type-erased wrappers AnyKeyGenerator and AnyKeySigner below:

let privateKey = PrivateKey<Secp256k1>(hex: "B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF")!

let keyPair = AnyKeyGenerator<Secp256k1>.restoreKeyPairFrom(privateKey: privateKey)

let message = Message(hex: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")

let signature = AnyKeySigner<Schnorr<Secp256k1>>.sign(message, using: keyPair)

let expectedSignature = Signature<Secp256k1>(hex: "2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD")!

if signature == expectedSignature {
    print("Correct signature!")
}

if AnyKeySigner<Schnorr<Secp256k1>>.verify(message, wasSignedBy: signature, publicKey: keyPair.publicKey) {
     print("Yes, message was indeed signed by public key and prodouced that signature.")
}

The code above takes around 0.5 seconds to execute (using Release optimization flags), which I'm working on optimizing.

The privatekey, signature, and message hex strings above are "Test Vector 2" from the Bitcoin BIP-Schnorr wiki.

Message

As you can see in the code example above, the signer (e.g. AnyKeySigner<Schnorr<Secp256k1>>) has a method for signing func sign(_ message: Message, using... a Message. Which is a container for any data you wanna sign. You should only be signing hashed data. Message contains four initializers, two for unhashed data (resulting in the data being hashed by the provided Hasher (shadowing Swift's Hasher)). Or if you know that you have already hashed the data, you can either pass a hashed hex string representation or the hashed data (to make sure you know what you are doing you are still required to provide info about which Hasher has been used to hash the data. Probably DefaultHasher.sha256).

Alternatives

There are many - production like alternatives to this Swift SDK. The goal of this library is to be rid of dependencies to C (and other programming languages) code. While there is alternative to this Swift SDK that is written in pure swift, it is too slow (read #pure-swift).

Bitcoin C Bindings

The Bitcoin Core's secp256k1 library developed in C seems to be the industry standard library for Elliptic Curve Cryptography. It is proven and robust and has many developers, why many projects in other programming languages just provide and a wrapper around it. Here is a short list of Bitcoin secp256k1 C library wrappers:

Other languages

Go, Javascript, PHP, Python Binding, Ruby, Rust, Scala

Bitcoin C Bindings (Swift)

There are some bindings to bitcoin-core/secp256k1 in Swift too. The most promising seems to be kishikawakatsumi/BitcoinKit (here are some others Boilertalk/secp256k1.swift, noxproject/ASKSecp256k1, pebble8888/secp256k1swift and skywinder/ios-secp256k1.

The SDK kishikawakatsumi/BitcoinKit stands out since it provides additional Swift layers to bitcoin-core/secp256k1. For production purposes, I recommend looking at kishikawakatsumi/BitcoinKit.

Pure Swift

The only Pure Swift Elliptic Curve cryptography SDK I have found so far is hyugit/EllipticCurve. The code is very Swifty and nice indeed, great work by Huang Yu aka hyugit! However, the code runs too slow. Taking over 10 minutes for Key generation. While this SDK takes around 0.1 seconds (using Release optimization flags).

Status

This SDK is in a proof-of-concept stage, but most features are supported, the code is Swifty and fast, but not yet safe to use. I'm working on optimizing the performance first, then making it safe to use.

Status of goal

  • "Swifty"
  • Fast (fastest pure Swift ECC SDK, but 250x slower than Bitcoin C SDK)
  • Safe

Dependencies

This SDK should never require any bridge to some C library (OpenSSL or bitcoin core for example) or even Objective-C. This SDK should be "Swifty" through and through.

Big Numbers

Elliptic Curve Cryptography requires big numbers (256 bits and more), but natively we only have support for 64 bits (on 64-bit platforms) using UInt64. I started on developing my own BigInt code, which I eventually throw away since Apple Developer Karoy Lorentey a.k.a. "lorentey" already created BigInt SDK attaswift/BigInt which works beautifully. I am also keeping an eye on a BigInt implementation from Apple, which is in prototype stage, might switch over to it if ever officially released.

I have also evalutated hyugit/UInt256 which conforms to Swifts FixedWidthInteger protocol, but that won't scale well since we might need 512 and 1024 bit large numbers. I also suspect that arithemtic operations in attaswift/BigInt are faster than hyugit/UInt256 (needs verification). There are also discontinued CryptoCoinSwift/UInt256 which seems inferior to hyugit/UInt256.

Apple Accelerate vBignum

Apple's library Accelerate seems to offer BigNumbers but in a very unswifty way using UnsafePointers here is addition of vbignum's vU256:

func vU256Add(_ a: UnsafePointer<vU256>, 
            _ b: UnsafePointer<vU256>, 
            _ result: UnsafeMutablePointer<vU256>)

However, I should probably investigate it further and measure performance. Perhaps a small struct wrapping it and decorating it with a Swifty API would give greater performance than attaswift/BigInt.

Hash functions

I use the SHA-256 hashing functions provided by krzyzanowskim/CryptoSwift.

Key inspiration

I have used lots of open source projects as inspiration. Bitcoin Improvement Proposal Wiki bip-schnorr by the bitcoin core developer Pieter Wuille a.k.a. "Sipa" has been a priceless inspiration for Schnorr Signature.

Sylvestre Blanc a.k.a. "HurlSly"'s Python Code has also been very helpful.

Roadmap

Signatures

  • ECDSA
  • Schnorr
  • ed25519 (EdDSA)

Key Formats

Private Key

  • Raw
  • WIF Uncompressed
  • WIF Compressed

Public Key

  • Uncompressed
  • Compressed

Public Addresses

  • Bitcoin (mainnet + testnet)
  • Zilliqa (testnet)

Common Curves

It is plan to support most of the common curves listed by running CLI command openssl ecparam -list_curves, but these four are the ones I will be starting with:

  • secp256k1 (Bitcoin, Ethereum, Zilliqa, Radix)
  • secp256r1 (NEO)
  • X25519 - Curve25519 used for ECDH (Nano, Stellar, Cardano)

Donate

This SDK has been developed by the single author Alexander Cyon in his free time. If you find it useful, please consider donating.

Any donation would be much appreciated:

  • ZIL: zil108t2jdgse760d88qjqmffhe9uy0nk4wvzx404t
  • BTC: 3GarsdAzLpEYbhkryYz1WiZxhtTLLaNJwo
  • ETH: 0xAB8F0137295BFE37f50b581F76418518a91ab8DB
  • NEO: AbbnnCLP26ccSnLooDDcwPLDnfXbVL5skH