Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/21-DOT-DEV/swift-secp256k1
Elliptic Curve, Schnorr, and ZKP for Bitcoin. Supports iOS macOS tvOS watchOS visionOS + Linux.
https://github.com/21-DOT-DEV/swift-secp256k1
bitcoin c ecdh ecdsa ios library linux macos mobile public-key schnorr secp256k1 swift swift-package-manager taproot xcode zero-knowledge
Last synced: 2 months ago
JSON representation
Elliptic Curve, Schnorr, and ZKP for Bitcoin. Supports iOS macOS tvOS watchOS visionOS + Linux.
- Host: GitHub
- URL: https://github.com/21-DOT-DEV/swift-secp256k1
- Owner: 21-DOT-DEV
- License: mit
- Created: 2020-07-05T07:26:05.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2024-10-29T17:01:46.000Z (3 months ago)
- Last Synced: 2024-10-29T18:28:34.480Z (3 months ago)
- Topics: bitcoin, c, ecdh, ecdsa, ios, library, linux, macos, mobile, public-key, schnorr, secp256k1, swift, swift-package-manager, taproot, xcode, zero-knowledge
- Language: Swift
- Homepage:
- Size: 646 KB
- Stars: 108
- Watchers: 8
- Forks: 53
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
- awesome-bitcoin - secp256k1.swift - Swift package for secp256k1 applications, includes Elliptic Curve operations, Schnorr, ZKP and more for Bitcoin. (Swift libraries)
README
[![Build Status](https://app.bitrise.io/app/18c18db60fc4fddf/status.svg?token=nczB4mTPCrlTfDQnXH_8Pw&branch=main)](https://app.bitrise.io/app/18c18db60fc4fddf) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2F21-DOT-DEV%2Fswift-secp256k1%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/21-DOT-DEV/swift-secp256k1) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2F21-DOT-DEV%2Fswift-secp256k1%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/21-DOT-DEV/swift-secp256k1)
# 🔐 swift-secp256k1
Swift package for elliptic curve public key cryptography, ECDSA, and Schnorr Signatures for Bitcoin, with C bindings from [libsecp256k1](https://github.com/bitcoin-core/secp256k1).
## Objectives
- Provide lightweight ECDSA & Schnorr Signatures functionality
- Support simple and advanced usage, including BIP-327 and BIP-340
- Expose C bindings for full control of the secp256k1 implementation
- Offer a familiar API design inspired by [Swift Crypto](https://github.com/apple/swift-crypto)
- Maintain automatic updates for Swift and libsecp256k1
- Ensure availability for Linux and Apple platform ecosystems## Installation
This package uses Swift Package Manager. To add it to your project:
### Using Xcode
1. Go to `File > Add Packages...`
2. Enter the package URL: `https://github.com/21-DOT-DEV/swift-secp256k1`
3. Select the desired version### Using Package.swift
Add the following to your `Package.swift` file:
```swift
.package(name: "swift-secp256k1", url: "https://github.com/21-DOT-DEV/swift-secp256k1", exact: "0.18.0"),
```Then, include `secp256k1` as a dependency in your target:
```swift
.target(name: "", dependencies: [
.product(name: "secp256k1", package: "swift-secp256k1")
]),
```> [!WARNING]
> These APIs are not considered stable and may change with any update. Specify a version using `exact:` to avoid breaking changes.### Try it out
Use [SPI Playgrounds app](https://swiftpackageindex.com/try-in-a-playground):
```swift
arena 21-DOT-DEV/swift-secp256k1
```## Usage Examples
### ECDSA
```swift
import secp256k1// Private key
let privateBytes = try! "14E4A74438858920D8A35FB2D88677580B6A2EE9BE4E711AE34EC6B396D87B5C".bytes
let privateKey = try! secp256k1.Signing.PrivateKey(rawRepresentation: privateBytes)// Public key
print(String(bytes: privateKey.publicKey.rawRepresentation))// ECDSA signature
let messageData = "We're all Satoshi.".data(using: .utf8)!
let signature = try! privateKey.signature(for: messageData)// DER signature
print(try! signature.derRepresentation.base64EncodedString())
```### Schnorr
```swift
// Strict BIP340 mode is disabled by default for Schnorr signatures with variable length messages
let privateKey = try! secp256k1.Schnorr.PrivateKey()// Extra params for custom signing
var auxRand = try! "C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906".bytes
var messageDigest = try! "7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C".bytes// API allows for signing variable length messages
let signature = try! privateKey.signature(message: &messageDigest, auxiliaryRand: &auxRand)
```## Tweak
```swift
let privateKey = try! secp256k1.Signing.PrivateKey()// Adding a tweak to the private key and public key
let tweak = try! "5f0da318c6e02f653a789950e55756ade9f194e1ec228d7f368de1bd821322b6".bytes
let tweakedPrivateKey = try! privateKey.add(tweak)
let tweakedPublicKeyKey = try! privateKey.publicKey.add(tweak)
```## Elliptic Curve Diffie Hellman
```swift
let privateKey = try! secp256k1.KeyAgreement.PrivateKey()
let publicKey = try! secp256k1.KeyAgreement.PrivateKey().publicKey// Create a compressed shared secret with a private key from only a public key
let sharedSecret = try! privateKey.sharedSecretFromKeyAgreement(with: publicKey, format: .compressed)// By default, libsecp256k1 hashes the x-coordinate with version information.
let symmetricKey = SHA256.hash(data: sharedSecret.bytes)
```## Silent Payments Scheme
```swift
let privateSign1 = try! secp256k1.Signing.PrivateKey()
let privateSign2 = try! secp256k1.Signing.PrivateKey()let privateKey1 = try! secp256k1.KeyAgreement.PrivateKey(rawRepresentation: privateSign1.rawRepresentation)
let privateKey2 = try! secp256k1.KeyAgreement.PrivateKey(rawRepresentation: privateSign2.rawRepresentation)let sharedSecret1 = try! privateKey1.sharedSecretFromKeyAgreement(with: privateKey2.publicKey)
let sharedSecret2 = try! privateKey2.sharedSecretFromKeyAgreement(with: privateKey1.publicKey)let sharedSecretSign1 = try! secp256k1.Signing.PrivateKey(rawRepresentation: sharedSecret1.bytes)
let sharedSecretSign2 = try! secp256k1.Signing.PrivateKey(rawRepresentation: sharedSecret2.bytes)// Payable Silent Payment public key
let xonlyTweak2 = try! sharedSecretSign2.publicKey.xonly.add(privateSign1.publicKey.xonly.bytes)// Spendable Silent Payment private key
let privateTweak1 = try! sharedSecretSign1.add(xonly: privateSign1.publicKey.xonly.bytes)
```## Recovery
```swift
let privateKey = try! secp256k1.Recovery.PrivateKey()
let messageData = "We're all Satoshi.".data(using: .utf8)!// Create a recoverable ECDSA signature
let recoverySignature = try! privateKey.signature(for: messageData)// Recover an ECDSA public key from a signature
let publicKey = try! secp256k1.Recovery.PublicKey(messageData, signature: recoverySignature)// Convert a recoverable signature into a normal signature
let signature = try! recoverySignature.normalize
```## Combine Public Keys
```swift
let privateKey = try! secp256k1.Signing.PrivateKey()
let publicKey = try! secp256k1.Signing.PrivateKey().public// The Combine API arguments are an array of PublicKey objects and an optional format
publicKey.combine([privateKey.publicKey], format: .uncompressed)
```## PEM Key Format
```swift
let privateKeyString = """
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIBXwHPDpec6b07GeLbnwetT0dvWzp0nV3MR+4pPKXIc7oAcGBSuBBAAK
oUQDQgAEt2uDn+2GqqYs/fmkBr5+rCQ3oiFSIJMAcjHIrTDS6HEELgguOatmFBOp
2wU4P2TAl/0Ihiq+nMkrAIV69m2W8g==
-----END EC PRIVATE KEY-----
"""// Import keys generated from OpenSSL
let privateKey = try! secp256k1.Signing.PrivateKey(pemRepresentation: privateKeyString)
```## MuSig2
```swift
// Initialize private keys for two signers
let firstPrivateKey = try secp256k1.Schnorr.PrivateKey()
let secondPrivateKey = try secp256k1.Schnorr.PrivateKey()// Aggregate the public keys using MuSig
let aggregateKey = try secp256k1.MuSig.aggregate([firstPrivateKey.publicKey, secondPrivateKey.publicKey])// Message to be signed
let message = "Vires in Numeris.".data(using: .utf8)!
let messageHash = SHA256.hash(data: message)// Generate nonces for each signer
let firstNonce = try secp256k1.MuSig.Nonce.generate(
secretKey: firstPrivateKey,
publicKey: firstPrivateKey.publicKey,
msg32: Array(messageHash)
)let secondNonce = try secp256k1.MuSig.Nonce.generate(
secretKey: secondPrivateKey,
publicKey: secondPrivateKey.publicKey,
msg32: Array(messageHash)
)// Aggregate nonces
let aggregateNonce = try secp256k1.MuSig.Nonce(aggregating: [firstNonce.pubnonce, secondNonce.pubnonce])// Create partial signatures
let firstPartialSignature = try firstPrivateKey.partialSignature(
for: messageHash,
pubnonce: firstNonce.pubnonce,
secureNonce: firstNonce.secnonce,
publicNonceAggregate: aggregateNonce,
publicKeyAggregate: aggregateKey
)let secondPartialSignature = try secondPrivateKey.partialSignature(
for: messageHash,
pubnonce: secondNonce.pubnonce,
secureNonce: secondNonce.secnonce,
publicNonceAggregate: aggregateNonce,
publicKeyAggregate: aggregateKey
)// Aggregate partial signatures into a full signature
let aggregateSignature = try secp256k1.MuSig.aggregateSignatures([firstPartialSignature, secondPartialSignature])// Verify the aggregate signature
let isValid = aggregateKey.isValidSignature(
aggregateSignature,
publicKey: firstPublicKey,
nonce: firstNonce.pubnonce,
for: messageHash
)print("Is valid MuSig signature: \(isValid)")
```