Git Product home page Git Product logo

k1's Introduction

K1 ๐Ÿ”

Safer than K2

K1 is Swift wrapper around libsecp256k1 (bitcoin-core/secp256k1), offering ECDSA, Schnorr (BIP340) and ECDH features.

Documentation

Read full documentation here on SwiftPackageIndex.

Quick overview

The API of K1 maps almost 1:1 with Apple's CryptoKit, vendoring a set of keypairs, one per feature. E.g. in CryptoKit you have Curve25519.KeyAgreement.PrivateKey and Curve25519.KeyAgreement.PublicKey which are seperate for Curve25519.Signing.PrivateKey and Curve25519.Signing.PublicKey.

Just like that K1 vendors these key pairs:

  • K1.KeyAgreement.PrivateKey / K1.KeyAgreement.PublicKey for key agreement (ECDH)
  • K1.Schnorr.PrivateKey / K1.Schnorr.PublicKey for sign / verify methods using Schnorr signature scheme
  • K1.ECDSAWithKeyRecovery.PrivateKey / K1.ECDSAWithKeyRecovery.PublicKey for sign / verify methods using ECDSA (producing/validating signature where public key is recoverable)
  • K1.ECDSA.PrivateKey / K1.ECDSA.PublicKey for sign / verify methods using ECDSA (producing/validating signature where public key is not recoverable)

Just like you can convert between e.g. Curve25519.KeyAgreement.PrivateKey and Curve25519.Signing.PrivateKey back and forth using any of the initializers and serializer, you can convert between all PrivateKeys and all PublicKeys of all features in K1.

All keys can be serialized using these computed properties:

{
    var rawRepresentation: Data { get }
    var derRepresentation: Data { get }
    var pemRepresentation: String { get }
    var x963Representation: Data { get }
}

All keys can be deserialize using these initializer:

{
    init(rawRepresentation: some ContiguousBytes) throws
    init(derRepresentation: some RandomAccessCollection<UInt8>) throws
    init(pemRepresentation: String) throws
    init(x963Representation: some ContiguousBytes) throws
}

Furthermore, all PrivateKey's have these additional APIs:

{
    init()
    associatedtype PublicKey
    var publicKey: PublicKey { get }
}

Furthermore, all PublicKeys's have these additional APIs:

{
    init(compressedRepresentation: some ContiguousBytes) throws
    var compressedRepresentation: Data { get }
}

ECDSA (Elliptic Curve Digital Signature Algorithm)

There exists two set of ECDSA key pairs:

  • A key pair for signatures from which you can recover the public key, specifically: K1.ECDSAWithKeyRecovery.PrivateKey and K1.ECDSAWithKeyRecovery.PublicKey
  • A key pair for signatures from which you can not recover the public key, specifically: K1.ECDSA.PrivateKey and K1.ECDSA.PublicKey

For each private key there exists two different signature:for:options (one taking hashed data and taking Digest as argument) methods and one signature:forUnhashed:options.

The option is a K1.ECDSA.SigningOptions struct, which by default specifies RFC6979 deterministic signing, as per Bitcoin standard, however, you can change to use secure random nonce instead.

NonRecoverable

Sign

let alice = K1.ECDSA.PrivateKey()
Hashed (Data)
let hashedMessage: Data = // from somewhere
let signature = try alice.signature(for: hashedMessage)
Digest
let message: Data = // from somewhere
let digest = SHA256.hash(data: message)
let signature = try alice.signature(for: digest)
Hash and Sign

The forUnhashed will SHA256 hash the message and then sign it.

let message: Data = // from somewhere
let signature = try alice.signature(forUnhashed: message)

Validate

Hashed (Data)
let hashedMessage: Data = // from somewhere
let publicKey: K1.ECDSA.PublicKey = alice.publcKey
let signature: K1.ECDSA.Signature // from above

assert(
    publicKey.isValidSignature(signature, hashed: hashedMessage)
) // PASS
Digest
let message: Data = // from somewhere
let digest = SHA256.hash(data: message)
let signature: K1.ECDSA.Signature // from above

assert(
    publicKey.isValidSignature(signature, digest: digest)
) // PASS
Hash and Validate
let message: Data = // from somewhere
let signature: K1.ECDSA.Signature // from above

assert(
    publicKey.isValidSignature(signature, unhashed: message)
) // PASS

Recoverable

All signing and validation APIs are identical to the NonRecoverable namespace.

let alice = K1.ECDSA.PrivateKey()
let message: Data = // from somewhere
let digest = SHA256.hash(data: message)
let signature: K1.ECDSAWithKeyRecovery.Signature = try alice.signature(for: digest)
let publicKey: K1.ECDSAWithKeyRecovery.PublicKey = alice.publicKey
assert(
    publicKey.isValidSignature(signature, digest: digest)
) // PASS

Schnorr Signature Scheme

Sign

let alice = K1.Schnorr.PrivateKey()
let signature = try alice.signature(forUnhashed: message)

There exists other sign variants, signature:for:options (hashed data) and signature:for:options (Digest) if you already have a hashed message. All three variants takes a K1.Schnorr.SigningOptions struct where you can pass auxiliaryRandomData to be signed.

Validate

let publicKey: K1.Schnorr.PublicKey = alice.publicKey
assert(publicKey.isValidSignature(signature, unhashed: message)) // PASS

Or alternatively isValidSignature:digest or isValidSignature:hashed.

Schnorr Scheme

The Schnorr signature implementation is BIP340, since we use libsecp256k1 which only provides the BIP340 Schnorr scheme.

It is worth noting that some Schnorr implementations are incompatible with BIP340 and thus this library, e.g. Zilliqa's (kudelski report, libsecp256k1 proposal, Twitter thread).

ECDH

This library vendors three different EC Diffie-Hellman (ECDH) key exchange functions:

  1. ASN1 x9.63 - No hash, return only the X coordinate of the point - sharedSecretFromKeyAgreement:with -> SharedSecret
  2. libsecp256k1 - SHA-256 hash the compressed point - ecdh:with -> SharedSecret
  3. Custom - No hash, return point uncompressed - ecdhPoint -> Data
let alice = try K1.KeyAgreement.PrivateKey()
let bob = try K1.KeyAgreement.PrivateKey()

ASN1 x9.63 ECDH

Returning only the X coordinate of the point, following ANSI X9.63 standards, embedded in a CryptoKit.SharedSecret, which is useful since you can use CryptoKit key derivation functions on this SharedSecret, e.g. x963DerivedSymmetricKey or hkdfDerivedSymmetricKey.

You can retrieve the X coordinate as raw data using withUnsafeBytes if you need to.

let ab: CryptoKit.SharedSecret = try alice.sharedSecretFromKeyAgreement(with: bob.publicKey) 
let ba: CryptoKit.SharedSecret = try bob.sharedSecretFromKeyAgreement(with: alice.publicKey)

assert(ab == ba) // pass

ab.withUnsafeBytes {
    assert(Data($0).count == 32) // pass
}

libsecp256k1 ECDH

Using libsecp256k1 default behaviour, returning a SHA-256 hash of the compressed point, embedded in a CryptoKit.SharedSecret, which is useful since you can use CryptoKit key derivation functions.

let ab: CryptoKit.SharedSecret = try alice.ecdh(with: bob.publicKey) 
let ba: CryptoKit.SharedSecret = try bob.ecdh(with: alice.publicKey)
assert(ab == ba) // pass

ab.withUnsafeBytes {
    assert(Data($0).count == 32) // pass
}

Custom ECDH

Returns an entire uncompressed EC point, without hashing it. Might be useful if you wanna construct your own cryptographic functions, e.g. some custom ECIES.

let ab: Data = try alice.ecdhPoint(with: bob.publicKey) 
let ba: Data = try bob.ecdhPoint(with: alice.publicKey)
assert(ab == ba) // pass

assert(ab.count == 65) // pass

Acknowledgements

K1 is a Swift wrapper around libsecp256k1, so this library would not exist without the Bitcoin Core developers. Massive thank you for a wonderful library! I've included it as a submodule, without any changes to the code, i.e. with copyright headers in files intact.

K1 uses some code from swift-crypto, which has been copied over with relevant copyright header. Since swift-crypto is licensed under Apache, so is this library.

Development

Stand in root and run

./scripts/build.sh

To clone the dependency libsecp256k1, using commit 427bc3cdcfbc74778070494daab1ae5108c71368 (semver 0.3.0)

gyb

Some of the files in this project are autogenerated (metaprogramming) using the Swift Utils tools called gyb ("generate your boilerplate"). gyb is included in ./scripts/gyb.

gyb will generate some Foobar.swift Swift file from some Foobar.swift.gyb template file. You should not edit Foobar.swift directly, since all manual edits in that generated file will be overwritten the next time gyb is run.

You run gyb for a single file like so:

./scripts/gyb --line-directive "" Sources/Foobar.swift.gyb -o Sources/Foobar.swift

More conveniently you can run the bash script ./scripts/generate_boilerplate_files_with_gyb.sh to generate all Swift files from their corresponding gyb template.

If you add a new .gyb file, you should append a // MARK: - Generated file, do NOT edit warning inside it, e.g.

// MARK: - Generated file, do NOT edit
// any edits of this file WILL be overwritten and thus discarded
// see section `gyb` in `README` for details.

Alternatives

k1's People

Contributors

cyonalexrdx avatar sajjon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

k1's Issues

Support Cocoapods integration

Current Situation:

  • The K1 library currently supports only Swift Package Manager.
  • CocoaPods is a widely used dependency manager in the iOS community.

Suggested Enhancement:

  • Add support for CocoaPods in the K1 library.

Benefits:

  • This would make K1 more accessible to a broader range of developers.
  • It provides an alternative for those who prefer CocoaPods over SPM.
  • Cocoapods is more friendly for react-native community

Using playground I have problem

Missing required module 'secp256k1' when I'm beginning to code the package on SPI playground, can you help me resolve this

import K1
let alice = K1.ECDSA.PrivateKey()

Thanks a lot ! I'd like to ask you one last thing

          Thanks a lot ! I'm using K1 and everything seems to work well, thanks ! 

Lasts questions, I hope I can make sense :

  1. I see that even if I'm doing let privateKey = K1.ECDSA.PrivateKey() I cannot have access to the private key of the key pair, right ?
  2. About secure enclave, I can first authenticate the user with faceID by using secure enclave and then use your package to create a keypair on the K1 curve , verify and sign data right ? And store on Keychain the public key.

thanks again for your responce

Originally posted by @dannpr in #39 (comment)

Misc improvments

  • Add link to documentation in README, solved in #32
  • Clean up test folder structure, names etc
  • Upgrade Wycheproof testvectors to v1
  • Serialization/deserialization roundtrip tests for ALL inits/serialize of ALL PublicKeys, fixed in #33
  • Serialization/deserialization roundtrip tests for ALL inits/serialize of ALL PrivateKeys
  • Add soundness.sh script see swift-crypto
  • Add API breakage check as part of CI, see swift-crypto
  • Move ASN1Error into K1.Error (hiding it from top level documentation)
  • Integrate Compact into ECDSA.NonRecoverable/ECDSA.KeyRecovery so that the entire API of ECDSA.NonRecoverable/ECDSA.KeyRecovery becomes that of Compact (and make Compact "invisible" (internal))

Rationale for ecdh_skip_hash_extract_x_and_y

This libarary is great, I really like the minimalist API but I'm having interoperability issues using it for ECDH.

I believe it is due to PrivateKey.sharedSecret using ecdh_skip_hash_extract_x_and_y

ecdh_skip_hash_extract_x_and_y, // hashfp

The result is Data that when put through SHA256.hash produces a inconsistent result from libraries like this:

https://github.com/bitcoin-core/secp256k1/blob/185a6af22792531a629959834fff9257e396abb5/src/modules/ecdh/main_impl.h#L13-L24

I was wondering if this was an intentional design decision and what the rationale was?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.