Skip to content

Commit 57ce9af

Browse files
authored
Swift APIs for MuSig2 (#560)
* 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
1 parent 74959db commit 57ce9af

File tree

23 files changed

+1173
-124
lines changed

23 files changed

+1173
-124
lines changed

Exhaustive/Package/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Use an official Swift runtime image
2-
FROM swift:5.8.0
2+
FROM swift:6.0.1
33

44
# Copies the root directory of the repository into the image's filesystem at `/LinuxTests`
55
ADD . /LinuxTests

Exhaustive/Package/Package.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
// swift-tools-version: 5.6
1+
// swift-tools-version: 6.0
22

33
import PackageDescription
44

55
let package = Package(
66
name: "Package",
77
dependencies: [
8-
.package(name: "secp256k1.swift", path: "../..")
8+
.package(name: "swift-secp256k1", path: "../..")
99
],
1010
targets: [
1111
.testTarget(
1212
name: "secp256k1Tests",
1313
dependencies: [
14-
.product(name: "secp256k1", package: "secp256k1.swift")
14+
.product(name: "secp256k1", package: "swift-secp256k1")
1515
]
1616
)
1717
]

Package.swift

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
// swift-tools-version:5.8
1+
// swift-tools-version: 6.0
22

33
import PackageDescription
44

55
let package = Package(
6-
name: "secp256k1.swift",
6+
name: "swift-secp256k1",
77
products: [
88
// WARNING: These APIs should not be considered stable and may change at any time.
99
.library(name: "secp256k1", targets: ["secp256k1"]),
@@ -20,36 +20,16 @@ let package = Package(
2020
.target(name: "zkp", dependencies: ["zkp_bindings"]),
2121
.target(
2222
name: "secp256k1_bindings",
23-
cSettings: [
24-
// Basic config values that are universal and require no dependencies.
25-
.define("ECMULT_GEN_PREC_BITS", to: "4"),
26-
.define("ECMULT_WINDOW_SIZE", to: "15"),
27-
// Enabling additional secp256k1 modules.
28-
.define("ENABLE_MODULE_ECDH"),
29-
.define("ENABLE_MODULE_ELLSWIFT"),
30-
.define("ENABLE_MODULE_EXTRAKEYS"),
31-
.define("ENABLE_MODULE_RECOVERY"),
32-
.define("ENABLE_MODULE_SCHNORRSIG")
33-
]
23+
cSettings: PackageDescription.CSetting.baseSettings
3424
),
3525
.target(
3626
name: "zkp_bindings",
37-
cSettings: [
38-
// Basic config values that are universal and require no dependencies.
39-
.define("ECMULT_GEN_PREC_BITS", to: "4"),
40-
.define("ECMULT_WINDOW_SIZE", to: "15"),
41-
// Enabling additional secp256k1-zkp modules.
27+
cSettings: PackageDescription.CSetting.baseSettings + [
4228
.define("ENABLE_MODULE_BPPP"),
43-
.define("ENABLE_MODULE_ECDH"),
4429
.define("ENABLE_MODULE_ECDSA_ADAPTOR"),
4530
.define("ENABLE_MODULE_ECDSA_S2C"),
46-
.define("ENABLE_MODULE_ELLSWIFT"),
47-
.define("ENABLE_MODULE_EXTRAKEYS"),
4831
.define("ENABLE_MODULE_GENERATOR"),
49-
.define("ENABLE_MODULE_MUSIG"),
5032
.define("ENABLE_MODULE_RANGEPROOF"),
51-
.define("ENABLE_MODULE_RECOVERY"),
52-
.define("ENABLE_MODULE_SCHNORRSIG"),
5333
.define("ENABLE_MODULE_SCHNORRSIG_HALFAGG"),
5434
.define("ENABLE_MODULE_SURJECTIONPROOF"),
5535
.define("ENABLE_MODULE_WHITELIST"),
@@ -60,6 +40,21 @@ let package = Package(
6040
),
6141
.testTarget(name: "zkpTests", dependencies: ["zkp"])
6242
],
63-
swiftLanguageVersions: [.v5],
43+
swiftLanguageModes: [.v5],
6444
cLanguageStandard: .c89
6545
)
46+
47+
extension PackageDescription.CSetting {
48+
static let baseSettings: [Self] = [
49+
// Basic config values that are universal and require no dependencies.
50+
.define("ECMULT_GEN_PREC_BITS", to: "4"),
51+
.define("ECMULT_WINDOW_SIZE", to: "15"),
52+
// Enabling additional secp256k1 modules.
53+
.define("ENABLE_MODULE_ECDH"),
54+
.define("ENABLE_MODULE_ELLSWIFT"),
55+
.define("ENABLE_MODULE_EXTRAKEYS"),
56+
.define("ENABLE_MODULE_MUSIG"),
57+
.define("ENABLE_MODULE_RECOVERY"),
58+
.define("ENABLE_MODULE_SCHNORRSIG")
59+
]
60+
}

README.md

Lines changed: 97 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,77 @@
1-
[![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)
1+
[![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)
22

3-
# 🔐 secp256k1.swift
4-
Swift package with elliptic curve public key cryptography, ECDSA, Schnorr Signatures for Bitcoin and C bindings from [libsecp256k1](https://github.com/bitcoin-core/secp256k1).
3+
# 🔐 swift-secp256k1
54

5+
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).
66

7-
# Objectives
7+
## Objectives
88

9-
Long-term goals are:
10-
- Lightweight ECDSA & Schnorr Signatures functionality
11-
- Built for simple or advance usage with things like BIP340
12-
- Exposed C bindings to take full control of the secp256k1 implementation
13-
- Familiar API design by modeling after [Swift Crypto](https://github.com/apple/swift-crypto)
14-
- Automatic updates for Swift and libsecp256k1
15-
- Availability for Linux and Apple platform ecosystems
9+
- Provide lightweight ECDSA & Schnorr Signatures functionality
10+
- Support simple and advanced usage, including BIP-327 and BIP-340
11+
- Expose C bindings for full control of the secp256k1 implementation
12+
- Offer a familiar API design inspired by [Swift Crypto](https://github.com/apple/swift-crypto)
13+
- Maintain automatic updates for Swift and libsecp256k1
14+
- Ensure availability for Linux and Apple platform ecosystems
1615

16+
## Installation
1717

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

20-
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:
20+
### Using Xcode
21+
22+
1. Go to `File > Add Packages...`
23+
2. Enter the package URL: `https://github.com/21-DOT-DEV/swift-secp256k1`
24+
3. Select the desired version
25+
26+
### Using Package.swift
27+
28+
Add the following to your `Package.swift` file:
2129

2230
```swift
23-
.package(name: "secp256k1.swift", url: "https://github.com/GigaBitcoin/secp256k1.swift.git", exact: "0.15.0"),
31+
.package(name: "swift-secp256k1", url: "https://github.com/21-DOT-DEV/swift-secp256k1", exact: "0.18.0"),
2432
```
2533

26-
Include `secp256k1` as a dependency for your executable target:
34+
Then, include `secp256k1` as a dependency in your target:
2735

2836
```swift
2937
.target(name: "<target>", dependencies: [
30-
.product(name: "secp256k1", package: "secp256k1.swift")
38+
.product(name: "secp256k1", package: "swift-secp256k1")
3139
]),
3240
```
3341

34-
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)
42+
> [!WARNING]
43+
> These APIs are not considered stable and may change with any update. Specify a version using `exact:` to avoid breaking changes.
3544
36-
```swift
37-
arena GigaBitcoin/secp256k1.swift
38-
```
45+
### Try it out
3946

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

41-
# Example Usage
49+
```swift
50+
arena 21-DOT-DEV/swift-secp256k1
51+
```
4252

43-
## ECDSA
53+
## Usage Examples
4454

55+
### ECDSA
4556
```swift
4657
import secp256k1
4758

48-
// Private key
59+
// Private key
4960
let privateBytes = try! "14E4A74438858920D8A35FB2D88677580B6A2EE9BE4E711AE34EC6B396D87B5C".bytes
5061
let privateKey = try! secp256k1.Signing.PrivateKey(rawRepresentation: privateBytes)
5162

52-
// Public key
63+
// Public key
5364
print(String(bytes: privateKey.publicKey.rawRepresentation))
5465

55-
// ECDSA
66+
// ECDSA signature
5667
let messageData = "We're all Satoshi.".data(using: .utf8)!
5768
let signature = try! privateKey.signature(for: messageData)
5869

59-
// DER signature
70+
// DER signature
6071
print(try! signature.derRepresentation.base64EncodedString())
6172
```
6273

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

174+
## MuSig2
164175

165-
# Danger
166-
These APIs should not be considered stable and may change at any time.
167-
176+
```swift
177+
// Initialize private keys for two signers
178+
let firstPrivateKey = try secp256k1.Schnorr.PrivateKey()
179+
let secondPrivateKey = try secp256k1.Schnorr.PrivateKey()
180+
181+
// Aggregate the public keys using MuSig
182+
let aggregateKey = try secp256k1.MuSig.aggregate([firstPrivateKey.publicKey, secondPrivateKey.publicKey])
183+
184+
// Message to be signed
185+
let message = "Vires in Numeris.".data(using: .utf8)!
186+
let messageHash = SHA256.hash(data: message)
187+
188+
// Generate nonces for each signer
189+
let firstNonce = try secp256k1.MuSig.Nonce.generate(
190+
secretKey: firstPrivateKey,
191+
publicKey: firstPrivateKey.publicKey,
192+
msg32: Array(messageHash)
193+
)
194+
195+
let secondNonce = try secp256k1.MuSig.Nonce.generate(
196+
secretKey: secondPrivateKey,
197+
publicKey: secondPrivateKey.publicKey,
198+
msg32: Array(messageHash)
199+
)
200+
201+
// Aggregate nonces
202+
let aggregateNonce = try secp256k1.MuSig.Nonce(aggregating: [firstNonce.pubnonce, secondNonce.pubnonce])
203+
204+
// Create partial signatures
205+
let firstPartialSignature = try firstPrivateKey.partialSignature(
206+
for: messageHash,
207+
pubnonce: firstNonce.pubnonce,
208+
secureNonce: firstNonce.secnonce,
209+
publicNonceAggregate: aggregateNonce,
210+
publicKeyAggregate: aggregateKey
211+
)
212+
213+
let secondPartialSignature = try secondPrivateKey.partialSignature(
214+
for: messageHash,
215+
pubnonce: secondNonce.pubnonce,
216+
secureNonce: secondNonce.secnonce,
217+
publicNonceAggregate: aggregateNonce,
218+
publicKeyAggregate: aggregateKey
219+
)
220+
221+
// Aggregate partial signatures into a full signature
222+
let aggregateSignature = try secp256k1.MuSig.aggregateSignatures([firstPartialSignature, secondPartialSignature])
223+
224+
// Verify the aggregate signature
225+
let isValid = aggregateKey.isValidSignature(
226+
aggregateSignature,
227+
publicKey: firstPublicKey,
228+
nonce: firstNonce.pubnonce,
229+
for: messageHash
230+
)
231+
232+
print("Is valid MuSig signature: \(isValid)")
233+
```

Sources/secp256k1/MuSig.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../zkp/MuSig.swift

Sources/secp256k1/Nonces.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../zkp/Nonces.swift
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../Submodules/secp256k1/include/secp256k1_musig.h
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../../Submodules/secp256k1/src/modules/musig/keyagg.h
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../../Submodules/secp256k1/src/modules/musig/keyagg_impl.h
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../../Submodules/secp256k1/src/modules/musig/main_impl.h

0 commit comments

Comments
 (0)