Skip to content

Commit c2740e2

Browse files
authored
Swift APIs for Elliptic Curve Diffie Hellman (#195)
1 parent b5f01b7 commit c2740e2

File tree

12 files changed

+532
-386
lines changed

12 files changed

+532
-386
lines changed

Package.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,13 @@ let package = Package(
145145
"swift-crypto/Tests/Test Vectors"
146146
],
147147
sources: [
148+
"Asymmetric.swift",
149+
"DH.swift",
148150
"Digests.swift",
151+
"ECDH.swift",
149152
"ECDSA.swift",
150153
"EdDSA.swift",
151154
"Errors.swift",
152-
"NISTCurvesKeys.swift",
153155
"PrettyBytes.swift",
154156
"SafeCompare.swift",
155157
"Schnorr.swift",

README.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,35 @@ var messageDigest = try! "7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9
5151
let signature = try! privateKey.schnorr.signature(message: &messageDigest, auxiliaryRand: &auxRand)
5252
```
5353

54+
## Tweak
55+
56+
```swift
57+
let privateBytes = try! "C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9".bytes
58+
let privateKey = try! secp256k1.Signing.PrivateKey(rawRepresentation: privateBytes)
59+
60+
// Adding a tweak to the private key and public key
61+
let tweak = try! "5f0da318c6e02f653a789950e55756ade9f194e1ec228d7f368de1bd821322b6".bytes
62+
let tweakedPrivateKey = try! privateKey.tweak(tweak)
63+
let tweakedPublicKeyKey = try! privateKey.publicKey.tweak(tweak)
64+
```
65+
66+
## Elliptic Curve Diffie Hellman
67+
68+
```swift
69+
let privateKey1 = try! secp256k1.KeyAgreement.PrivateKey()
70+
let privateKey2 = try! secp256k1.KeyAgreement.PrivateKey()
71+
72+
let sharedSecret1 = try! privateKey1.sharedSecretFromKeyAgreement(with: privateKey2.publicKey)
73+
let sharedSecret2 = try! privateKey2.sharedSecretFromKeyAgreement(with: privateKey1.publicKey)
74+
```
75+
5476

5577
# Getting Started
5678

57-
In your `Package.swift`:
79+
This repository primarily uses Swift package manager as its build tool, so we recommend using that as well. If you want to depend on `secp256k1.swift` in your own project, simply add it as a dependencies' clause in your `Package.swift`:
5880

5981
```swift
60-
.package(url: "https://github.com/GigaBitcoin/secp256k1.swift.git", .upToNextMajor(from: "0.5.0"))
82+
.package(url: "https://github.com/GigaBitcoin/secp256k1.swift.git", .upToNextMajor(from: "0.6.0"))
6183
```
6284

6385
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)
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
//
2+
// Asymmetric.swift
3+
// GigaBitcoin/secp256k1.swift
4+
//
5+
// Copyright (c) 2022 GigaBitcoin LLC
6+
// Distributed under the MIT software license
7+
//
8+
// See the accompanying file LICENSE for information
9+
//
10+
11+
import Foundation
12+
13+
/// The secp256k1 Elliptic Curve.
14+
public extension secp256k1 {
15+
/// Signing operations on secp256k1
16+
enum Signing {
17+
/// A Private Key for signing.
18+
public struct PrivateKey: Equatable {
19+
/// Generated secp256k1 Signing Key.
20+
private let baseKey: PrivateKeyImplementation
21+
22+
/// The secp256k1 private key object
23+
var key: SecureBytes {
24+
baseKey.key
25+
}
26+
27+
/// ECDSA Signing object.
28+
public var ecdsa: secp256k1.Signing.ECDSASigner {
29+
ECDSASigner(signingKey: baseKey)
30+
}
31+
32+
/// Schnorr Signing object.
33+
public var schnorr: secp256k1.Signing.SchnorrSigner {
34+
SchnorrSigner(signingKey: baseKey)
35+
}
36+
37+
/// The associated public key for verifying signatures done with this private key.
38+
///
39+
/// - Returns: The associated public key
40+
public var publicKey: PublicKey {
41+
PublicKey(baseKey: baseKey.publicKey)
42+
}
43+
44+
/// A data representation of the private key
45+
public var rawRepresentation: Data {
46+
baseKey.rawRepresentation
47+
}
48+
49+
/// Creates a random secp256k1 private key for signing
50+
public init(format: secp256k1.Format = .compressed) throws {
51+
self.baseKey = try PrivateKeyImplementation(format: format)
52+
}
53+
54+
/// Creates a secp256k1 private key for signing from a data representation.
55+
/// - Parameter data: A raw representation of the key.
56+
/// - Throws: An error is thrown when the raw representation does not create a private key for signing.
57+
public init<D: ContiguousBytes>(rawRepresentation data: D, format: secp256k1.Format = .compressed) throws {
58+
self.baseKey = try PrivateKeyImplementation(rawRepresentation: data, format: format)
59+
}
60+
61+
public static func == (lhs: Self, rhs: Self) -> Bool {
62+
lhs.key == rhs.key
63+
}
64+
}
65+
66+
/// The corresponding public key.
67+
public struct PublicKey {
68+
/// Generated secp256k1 public key.
69+
private let baseKey: PublicKeyImplementation
70+
71+
/// The secp256k1 public key object
72+
var keyBytes: [UInt8] {
73+
baseKey.bytes
74+
}
75+
76+
/// A data representation of the public key
77+
public var rawRepresentation: Data {
78+
baseKey.rawRepresentation
79+
}
80+
81+
/// ECDSA Validating object.
82+
public var ecdsa: secp256k1.Signing.ECDSAValidator {
83+
ECDSAValidator(validatingKey: baseKey)
84+
}
85+
86+
/// Schnorr Validating object.
87+
public var schnorr: secp256k1.Signing.SchnorrValidator {
88+
SchnorrValidator(validatingKey: baseKey)
89+
}
90+
91+
/// The associated x-only public key for verifying Schnorr signatures.
92+
///
93+
/// - Returns: The associated x-only public key
94+
public var xonly: XonlyKey {
95+
XonlyKey(baseKey: baseKey.xonly)
96+
}
97+
98+
/// A key format representation of the public key
99+
public var format: secp256k1.Format {
100+
baseKey.format
101+
}
102+
103+
/// Generates a secp256k1 public key.
104+
/// - Parameter baseKey: generated secp256k1 public key.
105+
fileprivate init(baseKey: PublicKeyImplementation) {
106+
self.baseKey = baseKey
107+
}
108+
109+
/// Generates a secp256k1 public key from a raw representation.
110+
/// - Parameter data: A raw representation of the key.
111+
/// - Throws: An error is thrown when the raw representation does not create a public key.
112+
public init<D: ContiguousBytes>(rawRepresentation data: D, xonly: D, format: secp256k1.Format) {
113+
self.baseKey = PublicKeyImplementation(rawRepresentation: data, xonly: xonly, format: format)
114+
}
115+
}
116+
117+
/// The corresponding x-only public key.
118+
public struct XonlyKey {
119+
/// Generated secp256k1 x-only public key.
120+
private let baseKey: XonlyKeyImplementation
121+
122+
/// The secp256k1 x-only public key object
123+
public var bytes: [UInt8] {
124+
baseKey.bytes
125+
}
126+
127+
fileprivate init(baseKey: XonlyKeyImplementation) {
128+
self.baseKey = baseKey
129+
}
130+
131+
public init<D: ContiguousBytes>(rawRepresentation data: D) {
132+
self.baseKey = XonlyKeyImplementation(rawRepresentation: data)
133+
}
134+
}
135+
}
136+
}

Sources/implementation/DH.swift

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//
2+
// DH.swift
3+
// GigaBitcoin/secp256k1.swift
4+
//
5+
// Modifications Copyright (c) 2022 GigaBitcoin LLC
6+
// Distributed under the MIT software license
7+
//
8+
// See the accompanying file LICENSE for information
9+
//
10+
//
11+
// NOTICE: THIS FILE HAS BEEN MODIFIED BY GigaBitcoin LLC
12+
// UNDER COMPLIANCE WITH THE APACHE 2.0 LICENSE FROM THE
13+
// ORIGINAL WORK OF THE COMPANY Apple Inc.
14+
//
15+
// THE FOLLOWING IS THE COPYRIGHT OF THE ORIGINAL DOCUMENT:
16+
//
17+
//
18+
//===----------------------------------------------------------------------===//
19+
//
20+
// This source file is part of the SwiftCrypto open source project
21+
//
22+
// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
23+
// Licensed under Apache License v2.0
24+
//
25+
// See LICENSE.txt for license information
26+
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
27+
//
28+
// SPDX-License-Identifier: Apache-2.0
29+
//
30+
//===----------------------------------------------------------------------===//
31+
32+
import Foundation
33+
34+
/// A Diffie-Hellman Key Agreement Key
35+
protocol DiffieHellmanKeyAgreement {
36+
/// The public key share type to perform the DH Key Agreement
37+
associatedtype P
38+
var publicKey: P { get }
39+
40+
/// Performs a Diffie-Hellman Key Agreement
41+
///
42+
/// - Parameter publicKeyShare: The public key share
43+
/// - Returns: The resulting key agreement result
44+
func sharedSecretFromKeyAgreement(with publicKeyShare: P) throws -> SharedSecret
45+
}
46+
47+
/// A Key Agreement Result
48+
/// A SharedSecret has to go through a Key Derivation Function before being able to use by a symmetric key operation.
49+
public struct SharedSecret: ContiguousBytes {
50+
var ss: SecureBytes
51+
52+
public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
53+
try ss.withUnsafeBytes(body)
54+
}
55+
}
56+
57+
extension SharedSecret: Hashable {
58+
public func hash(into hasher: inout Hasher) {
59+
ss.withUnsafeBytes { hasher.combine(bytes: $0) }
60+
}
61+
}
62+
63+
// We want to implement constant-time comparison for digests.
64+
extension SharedSecret: CustomStringConvertible, Equatable {
65+
public static func == (lhs: Self, rhs: Self) -> Bool {
66+
safeCompare(lhs, rhs)
67+
}
68+
69+
public static func == <D: DataProtocol>(lhs: Self, rhs: D) -> Bool {
70+
if rhs.regions.count != 1 {
71+
let rhsContiguous = Data(rhs)
72+
return safeCompare(lhs, rhsContiguous)
73+
} else {
74+
return safeCompare(lhs, rhs.regions.first!)
75+
}
76+
}
77+
78+
public var description: String {
79+
"\(Self.self): \(ss.hexString)"
80+
}
81+
}

Sources/implementation/ECDH.swift

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//
2+
// ECDH.swift
3+
// GigaBitcoin/secp256k1.swift
4+
//
5+
// Copyright (c) 2022 GigaBitcoin LLC
6+
// Distributed under the MIT software license
7+
//
8+
// See the accompanying file LICENSE for information
9+
//
10+
11+
import Foundation
12+
import secp256k1_bindings
13+
14+
// MARK: - secp256k1 + KeyAgreement
15+
16+
public extension secp256k1 {
17+
enum KeyAgreement {
18+
public struct PublicKey /*: NISTECPublicKey */ {
19+
let baseKey: PublicKeyImplementation
20+
21+
/// Creates a secp256k1 public key for key agreement from a collection of bytes.
22+
/// - Parameters:
23+
/// - data: A raw representation of the public key as a collection of contiguous bytes.
24+
/// - xonly: A raw representation of the xonly key as a collection of contiguous bytes.
25+
/// - format: the format of the public key object
26+
public init<D: ContiguousBytes>(rawRepresentation data: D, xonly: D, format: secp256k1.Format) {
27+
self.baseKey = PublicKeyImplementation(rawRepresentation: data, xonly: xonly, format: format)
28+
}
29+
30+
/// Initializes a secp256k1 public key for key agreement.
31+
/// - Parameter baseKey: generated secp256k1 public key.
32+
init(baseKey: PublicKeyImplementation) {
33+
self.baseKey = baseKey
34+
}
35+
36+
/// A data representation of the public key
37+
public var rawRepresentation: Data { baseKey.rawRepresentation }
38+
39+
/// Implementation public key object
40+
var bytes: [UInt8] { baseKey.bytes }
41+
}
42+
43+
public struct PrivateKey /*: NISTECPrivateKey */ {
44+
let baseKey: PrivateKeyImplementation
45+
46+
/// Creates a random secp256k1 private key for key agreement.
47+
public init(format: secp256k1.Format = .compressed) throws {
48+
self.baseKey = try PrivateKeyImplementation(format: format)
49+
}
50+
51+
/// Creates a secp256k1 private key for key agreement from a collection of bytes.
52+
/// - Parameter data: A raw representation of the key.
53+
/// - Throws: An error is thrown when the raw representation does not create a private key for key agreement.
54+
public init<D: ContiguousBytes>(rawRepresentation data: D, format: secp256k1.Format = .compressed) throws {
55+
self.baseKey = try PrivateKeyImplementation(rawRepresentation: data, format: format)
56+
}
57+
58+
/// Initializes a secp256k1 private key for key agreement.
59+
/// - Parameter baseKey: generated secp256k1 private key.
60+
init(baseKey: PrivateKeyImplementation) {
61+
self.baseKey = baseKey
62+
}
63+
64+
/// The associated public key for verifying signatures done with this private key.
65+
public var publicKey: secp256k1.KeyAgreement.PublicKey {
66+
PublicKey(baseKey: baseKey.publicKey)
67+
}
68+
69+
/// A data representation of the private key
70+
public var rawRepresentation: Data { baseKey.rawRepresentation }
71+
72+
/// Implementation public key object
73+
var bytes: SecureBytes { baseKey.key }
74+
}
75+
}
76+
}
77+
78+
// MARK: - secp256k1 + DH
79+
80+
extension secp256k1.KeyAgreement.PrivateKey: DiffieHellmanKeyAgreement {
81+
/// Performs a key agreement with provided public key share.
82+
///
83+
/// - Parameter publicKeyShare: The public key to perform the ECDH with.
84+
/// - Returns: Returns a shared secret
85+
/// - Throws: An error occurred while computing the shared secret
86+
public func sharedSecretFromKeyAgreement(with publicKeyShare: secp256k1.KeyAgreement.PublicKey) throws -> SharedSecret {
87+
var publicKey = secp256k1_pubkey()
88+
var sharedSecret = [UInt8](repeating: 0, count: 32)
89+
90+
guard secp256k1_ec_pubkey_parse(secp256k1.Context.raw, &publicKey, publicKeyShare.bytes, publicKeyShare.bytes.count).boolValue,
91+
secp256k1_ecdh(secp256k1.Context.raw, &sharedSecret, &publicKey, baseKey.key.bytes, nil, nil).boolValue else {
92+
throw secp256k1Error.underlyingCryptoError
93+
}
94+
95+
return SharedSecret(ss: SecureBytes(bytes: sharedSecret))
96+
}
97+
}

0 commit comments

Comments
 (0)