{"id":25613605,"url":"https://github.com/sajjon/k1","last_synced_at":"2026-01-30T12:03:12.014Z","repository":{"id":87412575,"uuid":"453351346","full_name":"Sajjon/K1","owner":"Sajjon","description":"Swift wrapper around libsecp256k1 with API's like CryptoKit.","archived":false,"fork":false,"pushed_at":"2023-11-24T11:24:08.000Z","size":3243,"stargazers_count":19,"open_issues_count":2,"forks_count":9,"subscribers_count":5,"default_branch":"main","last_synced_at":"2024-04-25T05:22:19.392Z","etag":null,"topics":["cryptography","ecc","ecdh","ecdsa","elliptic-curve-cryptography","key-agreement","publickey-recovery","schnorr","schnorr-signatures","secp256k1","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Sajjon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["Sajjon"]}},"created_at":"2022-01-29T09:22:18.000Z","updated_at":"2024-03-19T02:09:40.000Z","dependencies_parsed_at":null,"dependency_job_id":"75ae5b6b-9353-42dd-9fde-c9dbaac3d87b","html_url":"https://github.com/Sajjon/K1","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sajjon%2FK1","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sajjon%2FK1/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sajjon%2FK1/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sajjon%2FK1/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Sajjon","download_url":"https://codeload.github.com/Sajjon/K1/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248766748,"owners_count":21158301,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cryptography","ecc","ecdh","ecdsa","elliptic-curve-cryptography","key-agreement","publickey-recovery","schnorr","schnorr-signatures","secp256k1","swift"],"created_at":"2025-02-22T01:36:15.354Z","updated_at":"2026-01-30T12:03:12.006Z","avatar_url":"https://github.com/Sajjon.png","language":"Swift","funding_links":["https://github.com/sponsors/Sajjon"],"categories":[],"sub_categories":[],"readme":"[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FSajjon%2FK1%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/Sajjon/K1)[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FSajjon%2FK1%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/Sajjon/K1)\n\n# K1 🏔\n\u003e Safer than K2\n\n_K1_ is Swift wrapper around [libsecp256k1 (bitcoin-core/secp256k1)][lib], offering ECDSA, Schnorr ([BIP340][bip340]) and ECDH features.\n\n\u003e [!NOTE]\n\u003e Current `libsecp256k1` version is [v0.7.1 (1a53f4961f337b4d166c25fce72ef0dc88806618)](https://github.com/bitcoin-core/secp256k1/releases/tag/v0.7.1)\n\n# Documentation\nRead full documentation [here on SwiftPackageIndex][doc].\n\n## Quick overview\nThe API of K1 maps almost 1:1 with Apple's [CryptoKit][ck], vendoring a set of keypairs, one per feature. E.g. in CryptoKit you have `Curve25519.KeyAgreement.PrivateKey` and `Curve25519.KeyAgreement.PublicKey` which are separate for `Curve25519.Signing.PrivateKey` and `Curve25519.Signing.PublicKey`. \n\nJust like that K1 vendors these key pairs:\n- `K1.KeyAgreement.PrivateKey` / `K1.KeyAgreement.PublicKey` for key agreement (ECDH)\n- `K1.Schnorr.PrivateKey` / `K1.Schnorr.PublicKey` for sign / verify methods using Schnorr signature scheme\n- `K1.ECDSAWithKeyRecovery.PrivateKey` / `K1.ECDSAWithKeyRecovery.PublicKey` for sign / verify methods using ECDSA (producing/validating signature where public key is recoverable)\n- `K1.ECDSA.PrivateKey` / `K1.ECDSA.PublicKey` for sign / verify methods using ECDSA (producing/validating signature where public key is **not** recoverable)\n\nJust 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.\n\nAll keys can be serialized using these computed properties:\n\n```swift\n{\n    var rawRepresentation: Data { get }\n    var derRepresentation: Data { get }\n    var pemRepresentation: String { get }\n    var x963Representation: Data { get }\n}\n```\n\nAll keys can be deserialize using these initializer:\n\n```swift\n{\n    init(rawRepresentation: some ContiguousBytes) throws\n    init(derRepresentation: some RandomAccessCollection\u003cUInt8\u003e) throws\n    init(pemRepresentation: String) throws\n    init(x963Representation: some ContiguousBytes) throws\n}\n```\n\nFurthermore, all PrivateKey's have these additional APIs:\n\n```swift\n{\n    init()\n    associatedtype PublicKey\n    var publicKey: PublicKey { get }\n}\n```\n\nFurthermore, all PublicKeys's have these additional APIs:\n\n```swift\n{\n    init(compressedRepresentation: some ContiguousBytes) throws\n    var compressedRepresentation: Data { get }\n}\n```\n\n\n## ECDSA (Elliptic Curve Digital Signature Algorithm)\n\nThere exists two set of ECDSA key pairs:\n- A key pair for signatures from which you can recover the public key, specifically: `K1.ECDSAWithKeyRecovery.PrivateKey` and `K1.ECDSAWithKeyRecovery.PublicKey`\n- A key pair for signatures from which you can **not** recover the public key, specifically: `K1.ECDSA.PrivateKey` and `K1.ECDSA.PublicKey`\n\nFor 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`.\n\nThe `option` is a `K1.ECDSA.SigningOptions` struct, which by default specifies [`RFC6979`][rfc6979] deterministic signing, as per Bitcoin standard, however, you can change to use secure random nonce instead.\n\n### NonRecoverable\n\n#### Sign\n\n```swift\nlet alice = K1.ECDSA.PrivateKey()\n```\n\n##### Hashed (Data)\n\n```swift\nlet hashedMessage: Data = // from somewhere\nlet signature = try alice.signature(for: hashedMessage)\n```\n\n##### Digest\n\n```swift\nlet message: Data = // from somewhere\nlet digest = SHA256.hash(data: message)\nlet signature = try alice.signature(for: digest)\n```\n\n##### Hash and Sign\n\nThe `forUnhashed` will `SHA256` hash the message and then sign it. \n\n```swift\nlet message: Data = // from somewhere\nlet signature = try alice.signature(forUnhashed: message)\n```\n\n#### Validate\n\n##### Hashed (Data)\n\n```swift\nlet hashedMessage: Data = // from somewhere\nlet publicKey: K1.ECDSA.PublicKey = alice.publicKey\nlet signature: K1.ECDSA.Signature // from above\n\nassert(\n    publicKey.isValidSignature(signature, hashed: hashedMessage)\n) // PASS\n```\n\n##### Digest\n\n```swift\nlet message: Data = // from somewhere\nlet digest = SHA256.hash(data: message)\nlet signature: K1.ECDSA.Signature // from above\n\nassert(\n    publicKey.isValidSignature(signature, digest: digest)\n) // PASS\n```\n\n##### Hash and Validate\n\n```swift\nlet message: Data = // from somewhere\nlet signature: K1.ECDSA.Signature // from above\n\nassert(\n    publicKey.isValidSignature(signature, unhashed: message)\n) // PASS\n```\n\n\n### Recoverable\n\nAll signing and validation APIs are identical to the `NonRecoverable` namespace.\n\n```swift\nlet alice = K1.ECDSA.PrivateKey()\nlet message: Data = // from somewhere\nlet digest = SHA256.hash(data: message)\nlet signature: K1.ECDSAWithKeyRecovery.Signature = try alice.signature(for: digest)\nlet publicKey: K1.ECDSAWithKeyRecovery.PublicKey = alice.publicKey\nassert(\n    publicKey.isValidSignature(signature, digest: digest)\n) // PASS\n```\n\n\n## Schnorr Signature Scheme\n\n### Sign\n\n```swift\nlet alice = K1.Schnorr.PrivateKey()\nlet signature = try alice.signature(forUnhashed: message)\n```\n\nThere 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.\n\n### Validate\n\n```swift\nlet publicKey: K1.Schnorr.PublicKey = alice.publicKey\nassert(publicKey.isValidSignature(signature, unhashed: message)) // PASS\n```\n\nOr alternatively `isValidSignature:digest` or `isValidSignature:hashed`.\n\n##### Schnorr Scheme\n\nThe Schnorr signature implementation is [BIP340][bip340], since we use _libsecp256k1_ which only provides the [BIP340][bip340] Schnorr scheme. \n\nIt is worth noting that some Schnorr implementations are incompatible with [BIP340][bip340] and thus this library, e.g. [Zilliqa's](https://github.com/Zilliqa/schnorr/blob/master/src/libSchnorr/src/Schnorr.cpp#L86-L242) ([kudelski report](https://docs.zilliqa.com/zilliqa-schnorr-audit-by-kudelski_public-release.pdf), [libsecp256k1 proposal](https://github.com/bitcoin-core/secp256k1/issues/1070), [Twitter thread](https://twitter.com/AmritKummer/status/1489645007699066886?s=20\u0026t=eDgd5221qEPOVyStY0A8SA)).\n\n\n## ECDH\n\nThis library vendors three different EC Diffie-Hellman (ECDH) key exchange functions:\n1. `ASN1 x9.63` - No hash, return only the `X` coordinate of the point - `sharedSecretFromKeyAgreement:with -\u003e SharedSecret`\n2. `libsecp256k1` - SHA-256 hash the compressed point - `ecdh:with -\u003e SharedSecret`\n3. Custom - No hash, return point uncompressed - `ecdhPoint -\u003e Data`\n\n```swift\nlet alice = try K1.KeyAgreement.PrivateKey()\nlet bob = try K1.KeyAgreement.PrivateKey()\n```\n\n### `ASN1 x9.63` ECDH\nReturning only the `X` coordinate of the point, following [ANSI X9.63][x963] standards, embedded in a [`CryptoKit.SharedSecret`][ckss], which is useful since you can use `CryptoKit` key derivation functions on this SharedSecret, e.g. [`x963DerivedSymmetricKey`](https://developer.apple.com/documentation/cryptokit/sharedsecret/x963derivedsymmetrickey(using:sharedinfo:outputbytecount:)) or [`hkdfDerivedSymmetricKey`](https://developer.apple.com/documentation/cryptokit/sharedsecret/hkdfderivedsymmetrickey(using:salt:sharedinfo:outputbytecount:)).\n\nYou can retrieve the `X` coordinate as raw data using `withUnsafeBytes` if you need to.\n\n```swift\nlet aliceBob: CryptoKit.SharedSecret = try alice.sharedSecretFromKeyAgreement(with: bob.publicKey) \nlet bobAlice: CryptoKit.SharedSecret = try bob.sharedSecretFromKeyAgreement(with: alice.publicKey)\n\nassert(aliceBob == bobAlice) // pass\n\naliceBob.withUnsafeBytes {\n    assert(Data($0).count == 32) // pass\n}\n```\n\n### `libsecp256k1` ECDH\n\nUsing `libsecp256k1` default behaviour, returning a SHA-256 hash of the **compressed** point, embedded in a [`CryptoKit.SharedSecret`][ckss], which is useful since you can use `CryptoKit` key derivation functions.\n\n```swift\nlet aliceBob: CryptoKit.SharedSecret = try alice.ecdh(with: bob.publicKey) \nlet bobAlice: CryptoKit.SharedSecret = try bob.ecdh(with: alice.publicKey)\nassert(aliceBob == bobAlice) // pass\n\naliceBob.withUnsafeBytes {\n    assert(Data($0).count == 32) // pass\n}\n```\n\n### Custom ECDH\n\nReturns an entire uncompressed EC point, without hashing it. Might be useful if you wanna construct your own cryptographic functions, e.g. some custom ECIES.\n\n```swift\nlet aliceBob: Data = try alice.ecdhPoint(with: bob.publicKey) \nlet bobAlice: Data = try bob.ecdhPoint(with: alice.publicKey)\nassert(aliceBob == bobAlice) // pass\n\nassert(aliceBob.count == 65) // pass\n```\n\n\n# Acknowledgements\n`K1` is a Swift wrapper around [libsecp256k1][lib], 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.\n\n`K1` uses some code from [`swift-crypto`][swc], which has been copied over with relevant copyright header. Since [`swift-crypto`][swc] is licensed under [Apache](https://github.com/apple/swift-crypto/blob/main/LICENSE.txt), so is this library.\n\n# Development\n\n`K1` uses [`just`](https://github.com/casey/just) as a command runner instead of make.\n\n## Setup submodule\nStand in root and run to setup submodule\n\n```sh\njust submodules\n```\n\n## Update submodule\n```sh\njust bump-dep\n```\n\nOr to use dry run:\n```sh\njust bump-dep true\n```\n\n## `gyb`\n\nSome of the files in this project are autogenerated (metaprogramming) using the Swift Utils tools called [gyb](https://github.com/apple/swift/blob/main/utils/gyb.py) (_\"generate your boilerplate\"_). `gyb` is included in [`./scripts/gyb`](scripts/gyb).\n\n`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.\n\nYou run `gyb` for a single file like so:\n\n```bash\n./scripts/gyb --line-directive \"\" Sources/Foobar.swift.gyb -o Sources/Foobar.swift\n```\n\nMore conveniently, to generate all Swift files from their corresponding gyb template, you can run:\n```sh\njust gyb\n```\n\n**If you add a new `.gyb` file, you should append a `// MARK: - Generated file, do NOT edit` warning** inside it, e.g.\n\n```swift\n// MARK: - Generated file, do NOT edit\n// any edits of this file WILL be overwritten and thus discarded\n// see section `gyb` in `README` for details.\n```\n\n\n# Alternatives\n\n- [GigaBitcoin/secp256k1.swift](https://github.com/GigaBitcoin/secp256k1.swift) (also using `libsecp256k1`, ⚠️ possibly unsafe, ✅ Schnorr support)  \n- [KevinVitale/WalletKit](https://github.com/KevinVitale/WalletKit/) (also using `libsecp256k1`, ❌ No Schnorr)  \n- [leif-ibsen/SwiftECC](https://github.com/leif-ibsen/SwiftECC) (Custom ECC impl, ⚠️ possibly unsafe, ❌ No Schnorr)  \n- [yenom/BitcoinKit](https://github.com/yenom/BitcoinKit) (💀 Discontinued, also using `libsecp256k1`, ❌ No Schnorr)  \n- [oleganza/CoreBitcoin](https://github.com/oleganza/CoreBitcoin) (OpenSSL as ECC impl, ObjC + Swift, ⚠️ possibly unsafe, ❌ No Schnorr)  \n- [Sajjon/EllipticCurveKit](https://github.com/Sajjon/EllipticCurveKit) (Custom ECC impl (mine), ☣️ unsafe, ✅ Schnorr support)\n\n[doc]: https://swiftpackageindex.com/sajjon/k1/documentation/k1/k1/ecdsa\n[ck]: https://developer.apple.com/documentation/cryptokit\n[BIP340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki\n[lib]: https://github.com/bitcoin-core/secp256k1\n[x963]: https://webstore.ansi.org/standards/ascx9/ansix9632011r2017\n[ckss]: https://developer.apple.com/documentation/cryptokit/sharedsecret\n[swc]: https://github.com/apple/swift-crypto\n[rfc6979]: https://www.rfc-editor.org/rfc/rfc6979\n[mall]: https://en.bitcoin.it/wiki/Transaction_malleability\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsajjon%2Fk1","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsajjon%2Fk1","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsajjon%2Fk1/lists"}