Skip to content

Commit

Permalink
Swift APIs for MuSig2 (#560)
Browse files Browse the repository at this point in the history
* Initial Swift APIs for MuSig2

* Refactoring

* Added test for MuSig APIs

* Unit test passing

* Making secnonce non-Copyable

* Updating documentation

* Enable MuSig APIs in base library

* Bumping Swift version

* README updates
  • Loading branch information
csjones authored Oct 16, 2024
1 parent 74959db commit 57ce9af
Show file tree
Hide file tree
Showing 23 changed files with 1,173 additions and 124 deletions.
2 changes: 1 addition & 1 deletion Exhaustive/Package/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Use an official Swift runtime image
FROM swift:5.8.0
FROM swift:6.0.1

# Copies the root directory of the repository into the image's filesystem at `/LinuxTests`
ADD . /LinuxTests
Expand Down
6 changes: 3 additions & 3 deletions Exhaustive/Package/Package.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
// swift-tools-version: 5.6
// swift-tools-version: 6.0

import PackageDescription

let package = Package(
name: "Package",
dependencies: [
.package(name: "secp256k1.swift", path: "../..")
.package(name: "swift-secp256k1", path: "../..")
],
targets: [
.testTarget(
name: "secp256k1Tests",
dependencies: [
.product(name: "secp256k1", package: "secp256k1.swift")
.product(name: "secp256k1", package: "swift-secp256k1")
]
)
]
Expand Down
45 changes: 20 additions & 25 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// swift-tools-version:5.8
// swift-tools-version: 6.0

import PackageDescription

let package = Package(
name: "secp256k1.swift",
name: "swift-secp256k1",
products: [
// WARNING: These APIs should not be considered stable and may change at any time.
.library(name: "secp256k1", targets: ["secp256k1"]),
Expand All @@ -20,36 +20,16 @@ let package = Package(
.target(name: "zkp", dependencies: ["zkp_bindings"]),
.target(
name: "secp256k1_bindings",
cSettings: [
// Basic config values that are universal and require no dependencies.
.define("ECMULT_GEN_PREC_BITS", to: "4"),
.define("ECMULT_WINDOW_SIZE", to: "15"),
// Enabling additional secp256k1 modules.
.define("ENABLE_MODULE_ECDH"),
.define("ENABLE_MODULE_ELLSWIFT"),
.define("ENABLE_MODULE_EXTRAKEYS"),
.define("ENABLE_MODULE_RECOVERY"),
.define("ENABLE_MODULE_SCHNORRSIG")
]
cSettings: PackageDescription.CSetting.baseSettings
),
.target(
name: "zkp_bindings",
cSettings: [
// Basic config values that are universal and require no dependencies.
.define("ECMULT_GEN_PREC_BITS", to: "4"),
.define("ECMULT_WINDOW_SIZE", to: "15"),
// Enabling additional secp256k1-zkp modules.
cSettings: PackageDescription.CSetting.baseSettings + [
.define("ENABLE_MODULE_BPPP"),
.define("ENABLE_MODULE_ECDH"),
.define("ENABLE_MODULE_ECDSA_ADAPTOR"),
.define("ENABLE_MODULE_ECDSA_S2C"),
.define("ENABLE_MODULE_ELLSWIFT"),
.define("ENABLE_MODULE_EXTRAKEYS"),
.define("ENABLE_MODULE_GENERATOR"),
.define("ENABLE_MODULE_MUSIG"),
.define("ENABLE_MODULE_RANGEPROOF"),
.define("ENABLE_MODULE_RECOVERY"),
.define("ENABLE_MODULE_SCHNORRSIG"),
.define("ENABLE_MODULE_SCHNORRSIG_HALFAGG"),
.define("ENABLE_MODULE_SURJECTIONPROOF"),
.define("ENABLE_MODULE_WHITELIST"),
Expand All @@ -60,6 +40,21 @@ let package = Package(
),
.testTarget(name: "zkpTests", dependencies: ["zkp"])
],
swiftLanguageVersions: [.v5],
swiftLanguageModes: [.v5],
cLanguageStandard: .c89
)

extension PackageDescription.CSetting {
static let baseSettings: [Self] = [
// Basic config values that are universal and require no dependencies.
.define("ECMULT_GEN_PREC_BITS", to: "4"),
.define("ECMULT_WINDOW_SIZE", to: "15"),
// Enabling additional secp256k1 modules.
.define("ENABLE_MODULE_ECDH"),
.define("ENABLE_MODULE_ELLSWIFT"),
.define("ENABLE_MODULE_EXTRAKEYS"),
.define("ENABLE_MODULE_MUSIG"),
.define("ENABLE_MODULE_RECOVERY"),
.define("ENABLE_MODULE_SCHNORRSIG")
]
}
128 changes: 97 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,67 +1,77 @@
[![Build Status](https://app.bitrise.io/app/18c18db60fc4fddf/status.svg?token=nczB4mTPCrlTfDQnXH_8Pw&branch=main)](https://app.bitrise.io/app/18c18db60fc4fddf) [![Build Status](https://app.bitrise.io/app/f1bbbdfeff08cd5c/status.svg?token=ONB3exCALsB-_ayi6KsXFQ&branch=main)](https://app.bitrise.io/app/f1bbbdfeff08cd5c) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FGigaBitcoin%2Fsecp256k1.swift%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/GigaBitcoin/secp256k1.swift) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FGigaBitcoin%2Fsecp256k1.swift%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/GigaBitcoin/secp256k1.swift)
[![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)

# 🔐 secp256k1.swift
Swift package with elliptic curve public key cryptography, ECDSA, Schnorr Signatures for Bitcoin and C bindings from [libsecp256k1](https://github.com/bitcoin-core/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
## Objectives

Long-term goals are:
- Lightweight ECDSA & Schnorr Signatures functionality
- Built for simple or advance usage with things like BIP340
- Exposed C bindings to take full control of the secp256k1 implementation
- Familiar API design by modeling after [Swift Crypto](https://github.com/apple/swift-crypto)
- Automatic updates for Swift and libsecp256k1
- Availability for Linux and Apple platform ecosystems
- 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

# Getting Started
This package uses Swift Package Manager. To add it to your project:

This repository primarily uses Swift package manager as its build tool, so we recommend using that as well. Xcode comes with [built-in support](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app) for Swift packages. From the menu bar, goto: `File > Add Packages...` If you manage packages via a `Package.swift` file, simply add `secp256k1.swift` as a dependencies' clause in your Swift manifest:
### 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: "secp256k1.swift", url: "https://github.com/GigaBitcoin/secp256k1.swift.git", exact: "0.15.0"),
.package(name: "swift-secp256k1", url: "https://github.com/21-DOT-DEV/swift-secp256k1", exact: "0.18.0"),
```

Include `secp256k1` as a dependency for your executable target:
Then, include `secp256k1` as a dependency in your target:

```swift
.target(name: "<target>", dependencies: [
.product(name: "secp256k1", package: "secp256k1.swift")
.product(name: "secp256k1", package: "swift-secp256k1")
]),
```

Try in a [playground](spi-playgrounds://open?dependencies=GigaBitcoin/secp256k1.swift) using the [SPI Playgrounds app](https://swiftpackageindex.com/try-in-a-playground) or 🏟 [Arena](https://github.com/finestructure/arena)
> [!WARNING]
> These APIs are not considered stable and may change with any update. Specify a version using `exact:` to avoid breaking changes.
```swift
arena GigaBitcoin/secp256k1.swift
```
### Try it out

Use [SPI Playgrounds app](https://swiftpackageindex.com/try-in-a-playground):

# Example Usage
```swift
arena 21-DOT-DEV/swift-secp256k1
```

## ECDSA
## Usage Examples

### ECDSA
```swift
import secp256k1

// Private key
// Private key
let privateBytes = try! "14E4A74438858920D8A35FB2D88677580B6A2EE9BE4E711AE34EC6B396D87B5C".bytes
let privateKey = try! secp256k1.Signing.PrivateKey(rawRepresentation: privateBytes)

// Public key
// Public key
print(String(bytes: privateKey.publicKey.rawRepresentation))

// ECDSA
// ECDSA signature
let messageData = "We're all Satoshi.".data(using: .utf8)!
let signature = try! privateKey.signature(for: messageData)

// DER signature
// DER signature
print(try! signature.derRepresentation.base64EncodedString())
```

## Schnorr

### Schnorr
```swift
// Strict BIP340 mode is disabled by default for Schnorr signatures with variable length messages
let privateKey = try! secp256k1.Schnorr.PrivateKey()
Expand Down Expand Up @@ -161,7 +171,63 @@ oUQDQgAEt2uDn+2GqqYs/fmkBr5+rCQ3oiFSIJMAcjHIrTDS6HEELgguOatmFBOp
let privateKey = try! secp256k1.Signing.PrivateKey(pemRepresentation: privateKeyString)
```

## MuSig2

# Danger
These APIs should not be considered stable and may change at any time.

```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)")
```
1 change: 1 addition & 0 deletions Sources/secp256k1/MuSig.swift
1 change: 1 addition & 0 deletions Sources/secp256k1/Nonces.swift
1 change: 1 addition & 0 deletions Sources/secp256k1_bindings/include/secp256k1_musig.h
1 change: 1 addition & 0 deletions Sources/secp256k1_bindings/src/modules/musig/keyagg.h
1 change: 1 addition & 0 deletions Sources/secp256k1_bindings/src/modules/musig/keyagg_impl.h
1 change: 1 addition & 0 deletions Sources/secp256k1_bindings/src/modules/musig/main_impl.h
1 change: 1 addition & 0 deletions Sources/secp256k1_bindings/src/modules/musig/session.h
2 changes: 1 addition & 1 deletion Sources/zkp/Asymmetric.swift
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ public extension secp256k1 {
/// - Parameter data: A data representation of the x-only public key.
/// - Parameter keyParity: The key parity as an `Int32`.
public init<D: ContiguousBytes>(dataRepresentation data: D, keyParity: Int32) {
self.baseKey = XonlyKeyImplementation(dataRepresentation: data, keyParity: keyParity)
self.baseKey = XonlyKeyImplementation(dataRepresentation: data.bytes, keyParity: keyParity)
}
}
}
Expand Down
30 changes: 2 additions & 28 deletions Sources/zkp/Combine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,38 +23,12 @@ public extension secp256k1.Signing.PublicKey {
var combinedKey = secp256k1_pubkey()
var combinedBytes = [UInt8](repeating: 0, count: pubKeyLen)

guard withUnsafePointersToPubKeys(allPubKeys.map { $0.rawRepresentation }, { ptrsToCombine in
secp256k1_ec_pubkey_combine(context, &combinedKey, ptrsToCombine, ptrsToCombine.count).boolValue
guard PointerArrayUtility.withUnsafePointerArray(allPubKeys.map { $0.rawRepresentation }, { pointers in
secp256k1_ec_pubkey_combine(context, &combinedKey, pointers, pointers.count).boolValue
}), secp256k1_ec_pubkey_serialize(context, &combinedBytes, &pubKeyLen, &combinedKey, format.rawValue).boolValue else {
throw secp256k1Error.underlyingCryptoError
}

return try Self(dataRepresentation: combinedBytes, format: format)
}
}

extension secp256k1.Signing.PublicKey {
/// Executes a closure with an array of `UnsafePointer<secp256k1_pubkey>?` for pointer operations on an array of `secp256k1_pubkey`.
/// - Parameters:
/// - pubKeys: An array of `secp256k1_pubkey` to be converted to `UnsafePointer<secp256k1_pubkey>?`.
/// - body: A closure that takes an array of `UnsafePointer<secp256k1_pubkey>?` and returns a result of type `Result`.
/// - Returns: The result of the closure of type `Result`.
func withUnsafePointersToPubKeys<Result>(
_ pubKeys: [secp256k1_pubkey],
_ body: ([UnsafePointer<secp256k1_pubkey>?]) -> Result
) -> Result {
let pointers = pubKeys.map { pubKey -> UnsafePointer<secp256k1_pubkey>? in
let mutablePubKey = UnsafeMutablePointer<secp256k1_pubkey>.allocate(capacity: 1)
mutablePubKey.initialize(to: pubKey)
return UnsafePointer(mutablePubKey)
}

defer {
for ptr in pointers {
ptr?.deallocate()
}
}

return body(pointers)
}
}
Loading

0 comments on commit 57ce9af

Please sign in to comment.